opencodekit 0.14.2 → 0.14.4

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.
@@ -0,0 +1,300 @@
1
+ ---
2
+ name: ralph
3
+ description: Use when running autonomous agent loops for extended task completion without human intervention - handles PRD-driven development, migration tasks, or batch operations
4
+ license: MIT
5
+ compatibility: opencode
6
+ metadata:
7
+ source: https://ghuntley.com/ralph/
8
+ category: automation
9
+ updated: 2026-01-10
10
+ ---
11
+
12
+ # Ralph Wiggum - Autonomous Agent Loop
13
+
14
+ A workflow pattern for extended autonomous work sessions where the agent iterates through tasks until completion.
15
+
16
+ ## When to Use
17
+
18
+ - **PRD-driven development**: Work through a product requirements document task by task
19
+ - **Migration tasks**: "Migrate all Jest tests to Vitest" - agent works until done
20
+ - **Batch operations**: Process multiple files/components with the same pattern
21
+ - **Away-from-keyboard work**: Let the agent work autonomously while you're away
22
+
23
+ ## When NOT to Use
24
+
25
+ - Simple single-step tasks (just do them directly)
26
+ - Tasks requiring frequent human decisions
27
+ - Exploratory work where the goal isn't clear
28
+ - Critical production changes (requires human oversight)
29
+
30
+ ## Quick Start
31
+
32
+ Invoke the workflow with:
33
+
34
+ ```
35
+ /ralph Migrate all Jest tests to Vitest --prd PRD.md
36
+ ```
37
+
38
+ Or manually follow the loop pattern below.
39
+
40
+ ## The Loop Pattern
41
+
42
+ ```
43
+ ┌─────────────────────────────────────────────────┐
44
+ │ 1. Read PRD/task list + progress.txt │
45
+ │ 2. Pick highest-priority incomplete task │
46
+ │ 3. Implement ONE feature only │
47
+ │ 4. Run feedback: test → typecheck → lint │
48
+ │ 5. If pass → commit + update progress.txt │
49
+ │ 6. If all done → output <promise>COMPLETE │
50
+ │ 7. Otherwise → repeat from step 1 │
51
+ └─────────────────────────────────────────────────┘
52
+ ```
53
+
54
+ ## Using LSP Tools (Experimental)
55
+
56
+ OpenCode provides LSP tools for code intelligence.
57
+
58
+ ### Available Operations
59
+
60
+ ```typescript
61
+ // Understand file structure before editing
62
+ lsp({
63
+ operation: "documentSymbol",
64
+ filePath: "src/auth.ts",
65
+ line: 1,
66
+ character: 1,
67
+ });
68
+
69
+ // Find where a symbol is defined
70
+ lsp({
71
+ operation: "goToDefinition",
72
+ filePath: "src/auth.ts",
73
+ line: 42,
74
+ character: 10,
75
+ });
76
+
77
+ // Find all usages of a symbol (impact analysis)
78
+ lsp({
79
+ operation: "findReferences",
80
+ filePath: "src/auth.ts",
81
+ line: 42,
82
+ character: 10,
83
+ });
84
+
85
+ // Get type info and documentation
86
+ lsp({ operation: "hover", filePath: "src/auth.ts", line: 42, character: 10 });
87
+
88
+ // Find implementations of interface/abstract
89
+ lsp({
90
+ operation: "goToImplementation",
91
+ filePath: "src/types.ts",
92
+ line: 15,
93
+ character: 10,
94
+ });
95
+
96
+ // Search symbols across entire workspace
97
+ lsp({
98
+ operation: "workspaceSymbol",
99
+ filePath: "src/index.ts",
100
+ line: 1,
101
+ character: 1,
102
+ });
103
+
104
+ // Call hierarchy analysis
105
+ lsp({
106
+ operation: "prepareCallHierarchy",
107
+ filePath: "src/api.ts",
108
+ line: 20,
109
+ character: 5,
110
+ });
111
+ lsp({
112
+ operation: "incomingCalls",
113
+ filePath: "src/api.ts",
114
+ line: 20,
115
+ character: 5,
116
+ });
117
+ lsp({
118
+ operation: "outgoingCalls",
119
+ filePath: "src/api.ts",
120
+ line: 20,
121
+ character: 5,
122
+ });
123
+ ```
124
+
125
+ ### LSP-First Workflow
126
+
127
+ Before editing ANY file in the loop:
128
+
129
+ ```
130
+ 1. Read the file
131
+ 2. Use documentSymbol to understand structure
132
+ 3. Use findReferences to check impact
133
+ 4. Use hover for type info
134
+ 5. THEN make edits
135
+ ```
136
+
137
+ This prevents breaking changes and ensures you understand the code before modifying it.
138
+
139
+ ## Smart Language Detection
140
+
141
+ OpenCode auto-detects languages via LSP based on file extensions. Use this to determine the project type:
142
+
143
+ ### Primary Detection (File Extensions)
144
+
145
+ | Language | Extensions | LSP Server |
146
+ | ---------- | ----------------------------- | ------------- |
147
+ | TypeScript | `.ts`, `.tsx`, `.mts`, `.cts` | typescript |
148
+ | JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | typescript |
149
+ | Python | `.py`, `.pyi` | pyright |
150
+ | Rust | `.rs` | rust-analyzer |
151
+ | Go | `.go` | gopls |
152
+ | C/C++ | `.c`, `.cpp`, `.h`, `.hpp` | clangd |
153
+ | Java | `.java` | jdtls |
154
+ | C# | `.cs` | csharp |
155
+ | Ruby | `.rb`, `.rake` | ruby-lsp |
156
+ | PHP | `.php` | intelephense |
157
+ | Swift | `.swift` | sourcekit-lsp |
158
+ | Kotlin | `.kt`, `.kts` | kotlin-ls |
159
+ | Elixir | `.ex`, `.exs` | elixir-ls |
160
+ | Dart | `.dart` | dart |
161
+ | Zig | `.zig` | zls |
162
+ | Gleam | `.gleam` | gleam |
163
+ | Lua | `.lua` | lua-ls |
164
+ | Clojure | `.clj`, `.cljs`, `.cljc` | clojure-lsp |
165
+ | OCaml | `.ml`, `.mli` | ocaml-lsp |
166
+ | Svelte | `.svelte` | svelte |
167
+ | Vue | `.vue` | vue |
168
+ | Astro | `.astro` | astro |
169
+
170
+ ### Secondary Detection (Lock Files → Package Manager)
171
+
172
+ | Lock File | Package Manager | Runtime |
173
+ | ------------------- | --------------- | ------- |
174
+ | `bun.lockb` | Bun | Bun |
175
+ | `yarn.lock` | Yarn | Node |
176
+ | `pnpm-lock.yaml` | pnpm | Node |
177
+ | `package-lock.json` | npm | Node |
178
+ | `deno.json` | Deno | Deno |
179
+ | `Cargo.toml` | Cargo | Rust |
180
+ | `go.mod` | Go Modules | Go |
181
+ | `pyproject.toml` | Poetry/pip | Python |
182
+ | `Gemfile.lock` | Bundler | Ruby |
183
+ | `composer.lock` | Composer | PHP |
184
+ | `Package.swift` | SwiftPM | Swift |
185
+ | `mix.lock` | Mix | Elixir |
186
+ | `pubspec.lock` | Pub | Dart |
187
+
188
+ ### Validation Commands by Language
189
+
190
+ ```bash
191
+ # TypeScript/JavaScript (Bun)
192
+ bun test && bun run typecheck && bun run lint
193
+
194
+ # TypeScript/JavaScript (npm)
195
+ npm test && npm run typecheck && npm run lint
196
+
197
+ # TypeScript/JavaScript (Deno)
198
+ deno test && deno check . && deno lint
199
+
200
+ # Python
201
+ pytest && ruff check . && mypy .
202
+
203
+ # Rust
204
+ cargo test && cargo check && cargo clippy --all-targets
205
+
206
+ # Go
207
+ go test ./... && go vet ./... && golangci-lint run
208
+
209
+ # Ruby
210
+ bundle exec rspec && bundle exec rubocop
211
+
212
+ # PHP
213
+ composer test && composer phpstan && composer phpcs
214
+
215
+ # Swift
216
+ swift test && swift build
217
+
218
+ # Elixir
219
+ mix test && mix credo && mix dialyzer
220
+
221
+ # Dart
222
+ dart test && dart analyze
223
+
224
+ # Java (Maven)
225
+ mvn test && mvn compile && mvn checkstyle:check
226
+
227
+ # Java (Gradle)
228
+ ./gradlew test && ./gradlew check
229
+
230
+ # C# (.NET)
231
+ dotnet test && dotnet build && dotnet format --verify-no-changes
232
+ ```
233
+
234
+ ## Required Files
235
+
236
+ ### PRD File (Recommended)
237
+
238
+ Create a `PRD.md` with clear task list:
239
+
240
+ ```markdown
241
+ # Migration PRD
242
+
243
+ ## Tasks
244
+
245
+ - [ ] Convert test-utils.test.js
246
+ - [ ] Convert api.test.js
247
+ - [ ] Update CI config
248
+ - [ ] Remove Jest dependencies
249
+ ```
250
+
251
+ ### Progress File (Agent-maintained)
252
+
253
+ Agent creates/updates `progress.txt`:
254
+
255
+ ```markdown
256
+ # Progress Log
257
+
258
+ ## Session Started: 2026-01-10
259
+
260
+ ### Project: TypeScript with Bun
261
+
262
+ ### Commands: bun test | bun run typecheck | bun run lint
263
+
264
+ ### Completed Tasks
265
+
266
+ - [x] test-utils.test.js - migrated, 12 tests passing
267
+ - [x] api.test.js - migrated, 8 tests passing
268
+
269
+ ### Notes for Next Iteration
270
+
271
+ - CI config needs Vitest runner update
272
+ ```
273
+
274
+ ## Completion Signal
275
+
276
+ The loop ends when agent outputs:
277
+
278
+ ```
279
+ <promise>COMPLETE</promise>
280
+ ```
281
+
282
+ ## Best Practices
283
+
284
+ 1. **Use LSP for detection**: Check file extensions, not just lock files
285
+ 2. **Small steps**: ONE feature per iteration (prevents context rot)
286
+ 3. **Quality gates**: Test, typecheck, lint MUST pass before commit
287
+ 4. **Prioritize risk**: Hard tasks first, easy wins last
288
+ 5. **Track progress**: Update progress.txt every iteration
289
+ 6. **Explicit scope**: Vague tasks loop forever
290
+ 7. **Graceful fallbacks**: If a command doesn't exist, skip and note it
291
+
292
+ ## Troubleshooting
293
+
294
+ | Issue | Solution |
295
+ | -------------------- | ------------------------------------------- |
296
+ | Loop not progressing | Break task into smaller pieces |
297
+ | Tests failing | Fix before continuing, don't skip |
298
+ | Context getting long | Summarize progress, restart session |
299
+ | Stuck on decision | Note in progress.txt, ask user next session |
300
+ | Unknown language | Check file extensions against LSP table |
@@ -66,7 +66,7 @@ function findBdPath(): string {
66
66
  execSync(`test -x "${p}"`, { timeout: 1000 });
67
67
  return p;
68
68
  } catch {
69
- continue;
69
+ // Try next path
70
70
  }
71
71
  }
72
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,97 +0,0 @@
1
- ---
2
- description: Start Ralph Wiggum autonomous loop for task completion
3
- argument-hint: "<task> [--prd <file>] [--max <iterations>] [--afk]"
4
- agent: build
5
- ---
6
-
7
- # Ralph Wiggum Loop
8
-
9
- You are starting a Ralph Wiggum autonomous loop. This pattern enables you to work autonomously on a task list until completion.
10
-
11
- ## Task
12
-
13
- $ARGUMENTS
14
-
15
- ## Setup
16
-
17
- 1. **Start the loop** by calling the `ralph-start` tool:
18
-
19
- ```typescript
20
- ralph -
21
- start({
22
- task: "$1",
23
- prdFile: "$2" || null, // Optional: PRD.md, tasks.md, etc.
24
- progressFile: "progress.txt",
25
- maxIterations: 50,
26
- mode: "hitl", // or "afk" for autonomous
27
- });
28
- ```
29
-
30
- 2. **Create progress.txt** if it doesn't exist:
31
-
32
- ```markdown
33
- # Progress Log
34
-
35
- ## Session Started: [date]
36
-
37
- ### Completed Tasks
38
-
39
- (none yet)
40
-
41
- ### Notes for Next Iteration
42
-
43
- - Starting fresh
44
- ```
45
-
46
- ## Loop Behavior
47
-
48
- After each iteration, the loop will automatically:
49
-
50
- 1. Check if you output `<promise>COMPLETE</promise>`
51
- 2. If yes → Loop ends, success!
52
- 3. If no → Send continuation prompt for next iteration
53
- 4. Repeat until completion or max iterations
54
-
55
- ## Your Instructions
56
-
57
- For each iteration:
58
-
59
- 1. **Review** the PRD/task list and progress file
60
- 2. **Choose** the highest-priority incomplete task (YOU decide, not first in list)
61
- 3. **Implement** ONE feature only (small steps prevent context rot)
62
- 4. **Validate** with feedback loops:
63
- - `npm run typecheck` (must pass)
64
- - `npm run test` (must pass)
65
- - `npm run lint` (must pass)
66
- 5. **Commit** if all pass
67
- 6. **Update** progress.txt with:
68
- - Task completed
69
- - Key decisions made
70
- - Files changed
71
- - Notes for next iteration
72
-
73
- ## Exit Conditions
74
-
75
- Output `<promise>COMPLETE</promise>` when:
76
-
77
- - ALL tasks in the PRD are complete
78
- - ALL feedback loops pass
79
- - Code is committed
80
-
81
- The loop will also stop if:
82
-
83
- - Max iterations reached
84
- - You call `ralph-stop` tool
85
- - An error occurs
86
-
87
- ## Best Practices
88
-
89
- - **Small steps**: One feature per iteration
90
- - **Quality over speed**: Never skip tests
91
- - **Explicit scope**: Vague tasks loop forever
92
- - **Track progress**: Update progress.txt every iteration
93
- - **Prioritize risk**: Hard tasks first, easy wins last
94
-
95
- ## Start Now
96
-
97
- Call `ralph-start` with the task description to begin the loop.
@@ -1,37 +0,0 @@
1
- /**
2
- * OpenCode Handoff Plugin
3
- * Injects the most recent handoff file into session compaction
4
- *
5
- * Workflow:
6
- * 1. User creates handoff markdown file in .opencode/memory/handoffs/
7
- * 2. Session compaction (Ctrl+K) includes handoff context
8
- * 3. New session resumes from previous state
9
- */
10
-
11
- import type { Plugin } from "@opencode-ai/plugin";
12
-
13
- export const HandoffPlugin: Plugin = async ({ $, directory }) => {
14
- const HANDOFF_DIR = `${directory}/.opencode/memory/handoffs`;
15
-
16
- return {
17
- "experimental.session.compacting": async (_input, output) => {
18
- // Find most recent handoff file
19
- const result =
20
- await $`ls -t ${HANDOFF_DIR}/*.md 2>/dev/null | head -1`.quiet();
21
-
22
- if (!result.stdout) return;
23
-
24
- const handoffPath = result.stdout.toString().trim();
25
- const handoffContent = await $`cat ${handoffPath}`.text();
26
-
27
- // Inject into compaction context
28
- output.context.push(`
29
- ## Previous Session Handoff
30
-
31
- ${handoffContent}
32
-
33
- **IMPORTANT**: Resume work from where previous session left off.
34
- `);
35
- },
36
- };
37
- };
@@ -1,182 +0,0 @@
1
- /**
2
- * Ralph Wiggum Plugin for OpenCode
3
- *
4
- * Handles the session.idle event to continue the Ralph loop.
5
- * Tools are defined separately in .opencode/tool/ralph.ts
6
- *
7
- * Based on: https://ghuntley.com/ralph/
8
- */
9
-
10
- import fs from "node:fs/promises";
11
- import type { Plugin } from "@opencode-ai/plugin";
12
-
13
- const STATE_FILE = ".opencode/.ralph-state.json";
14
- const IDLE_DEBOUNCE_MS = 2000;
15
- let lastIdleTime = 0;
16
-
17
- interface RalphState {
18
- active: boolean;
19
- sessionID: string | null;
20
- iteration: number;
21
- maxIterations: number;
22
- completionPromise: string;
23
- task: string;
24
- prdFile: string | null;
25
- progressFile: string;
26
- startedAt: number | null;
27
- mode: "hitl" | "afk";
28
- }
29
-
30
- async function loadState(): Promise<RalphState | null> {
31
- try {
32
- const content = await fs.readFile(STATE_FILE, "utf-8");
33
- return JSON.parse(content);
34
- } catch {
35
- return null;
36
- }
37
- }
38
-
39
- async function saveState(state: RalphState): Promise<void> {
40
- await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2));
41
- }
42
-
43
- async function resetState(): Promise<void> {
44
- try {
45
- await fs.unlink(STATE_FILE);
46
- } catch {
47
- // File doesn't exist, that's fine
48
- }
49
- }
50
-
51
- export const RalphWiggum: Plugin = async ({ client }) => {
52
- const log = async (
53
- message: string,
54
- level: "info" | "warn" | "error" = "info",
55
- ) => {
56
- await client.app
57
- .log({
58
- body: { service: "ralph-wiggum", level, message },
59
- })
60
- .catch(() => {});
61
- };
62
-
63
- const showToast = async (
64
- title: string,
65
- message: string,
66
- variant: "info" | "success" | "warning" | "error" = "info",
67
- ) => {
68
- await client.tui
69
- .showToast({
70
- body: {
71
- title: `Ralph: ${title}`,
72
- message,
73
- variant,
74
- duration: variant === "error" ? 8000 : 5000,
75
- },
76
- })
77
- .catch(() => {});
78
- };
79
-
80
- const buildContinuationPrompt = (state: RalphState): string => {
81
- const prdRef = state.prdFile ? `@${state.prdFile} ` : "";
82
- const progressRef = `@${state.progressFile}`;
83
-
84
- return `
85
- ${prdRef}${progressRef}
86
-
87
- ## Ralph Wiggum Loop - Iteration ${state.iteration}/${state.maxIterations}
88
-
89
- You are in an autonomous loop. Continue working on the task.
90
-
91
- **Task:** ${state.task}
92
-
93
- **Instructions:**
94
- 1. Review the PRD/task list and progress file
95
- 2. Choose the highest-priority INCOMPLETE task
96
- 3. Implement ONE feature/change only
97
- 4. Run feedback loops: typecheck, test, lint
98
- 5. Commit if all pass
99
- 6. Update ${state.progressFile}
100
- 7. If ALL tasks complete, output: ${state.completionPromise}
101
-
102
- **Constraints:** ONE feature per iteration. Quality over speed.
103
- `.trim();
104
- };
105
-
106
- const handleSessionIdle = async (sessionID: string): Promise<void> => {
107
- const now = Date.now();
108
- if (now - lastIdleTime < IDLE_DEBOUNCE_MS) return;
109
- lastIdleTime = now;
110
-
111
- const state = await loadState();
112
- if (!state?.active || state.sessionID !== sessionID) return;
113
-
114
- try {
115
- const messagesResponse = await client.session.messages({
116
- path: { id: sessionID },
117
- });
118
- const messages = messagesResponse.data || [];
119
- const lastMessage = messages[messages.length - 1];
120
-
121
- const lastText =
122
- lastMessage?.parts
123
- ?.filter((p) => p.type === "text")
124
- .map((p) => ("text" in p ? (p.text as string) : ""))
125
- .join("") || "";
126
-
127
- if (lastText.includes(state.completionPromise)) {
128
- const duration = state.startedAt
129
- ? Math.round((Date.now() - state.startedAt) / 1000 / 60)
130
- : 0;
131
- await showToast(
132
- "Complete!",
133
- `Finished in ${state.iteration} iterations (${duration} min)`,
134
- "success",
135
- );
136
- await log(`Loop completed in ${state.iteration} iterations`);
137
- await resetState();
138
- return;
139
- }
140
-
141
- state.iteration++;
142
- if (state.iteration >= state.maxIterations) {
143
- await showToast(
144
- "Stopped",
145
- `Max iterations (${state.maxIterations}) reached`,
146
- "warning",
147
- );
148
- await log(`Max iterations reached: ${state.maxIterations}`, "warn");
149
- await resetState();
150
- return;
151
- }
152
-
153
- await saveState(state);
154
-
155
- await client.session.prompt({
156
- path: { id: sessionID },
157
- body: {
158
- parts: [{ type: "text", text: buildContinuationPrompt(state) }],
159
- },
160
- });
161
-
162
- await log(`Iteration ${state.iteration}/${state.maxIterations}`);
163
- } catch (error) {
164
- await log(`Error in Ralph loop: ${error}`, "error");
165
- await resetState();
166
- }
167
- };
168
-
169
- return {
170
- event: async ({ event }) => {
171
- if (event.type === "session.idle") {
172
- const sessionID = (event as { properties?: { sessionID?: string } })
173
- .properties?.sessionID;
174
- if (sessionID) {
175
- await handleSessionIdle(sessionID);
176
- }
177
- }
178
- },
179
- };
180
- };
181
-
182
- export default RalphWiggum;