opencodekit 0.14.3 → 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.
package/dist/index.js CHANGED
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.14.3",
753
+ version: "0.14.4",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  type: "module",
756
756
  repository: {
@@ -0,0 +1,41 @@
1
+ ---
2
+ description: Start Ralph Wiggum autonomous loop for task completion
3
+ argument-hint: "<task> [--prd <file>]"
4
+ agent: build
5
+ ---
6
+
7
+ # Ralph Wiggum Loop
8
+
9
+ First, load the ralph skill for complete instructions:
10
+
11
+ ```typescript
12
+ skill({ name: "ralph" });
13
+ ```
14
+
15
+ ## Task
16
+
17
+ $ARGUMENTS
18
+
19
+ ## Quick Start
20
+
21
+ 1. **Load the skill** (above) to get full workflow instructions
22
+ 2. **Detect environment**: Check lock files to determine package manager
23
+ 3. **Create progress.txt**: Track completed tasks and notes
24
+ 4. **Start iterating**: Follow the loop pattern from the skill
25
+
26
+ ## The Loop (Summary)
27
+
28
+ ```
29
+ Read PRD → Pick task → Implement ONE feature → Validate → Commit → Update progress → Repeat
30
+ ```
31
+
32
+ Exit when ALL tasks complete by outputting:
33
+
34
+ ```
35
+ <promise>COMPLETE</promise>
36
+ ```
37
+
38
+ ## See Also
39
+
40
+ - Full instructions: `skill({ name: "ralph" })`
41
+ - Skill location: `.opencode/skill/ralph/SKILL.md`
@@ -77,7 +77,11 @@
77
77
  "extensions": [".html", ".css", ".scss", ".sass", ".md", ".yaml", ".yml"]
78
78
  }
79
79
  },
80
- "instructions": [".opencode/memory/user.md", ".opencode/memory/project/*.md"],
80
+ "instructions": [
81
+ ".opencode/memory/user.md",
82
+ ".opencode/memory/project/*.md",
83
+ ".opencode/memory/observations/*.md"
84
+ ],
81
85
  "keybinds": {
82
86
  "command_list": ";",
83
87
  "leader": "`",
@@ -5,7 +5,8 @@
5
5
  * 1. Load session-context.md (CONTINUITY.md pattern)
6
6
  * 2. Load project memory files
7
7
  * 3. Inject beads in-progress state
8
- * 4. Append workflow-specific compaction rules
8
+ * 4. Load most recent handoff file for session resumption
9
+ * 5. Append workflow-specific compaction rules
9
10
  *
10
11
  * Session context format (agent-maintained via memory-update):
11
12
  * - Goal: What we're trying to achieve + success criteria
@@ -20,6 +21,7 @@ import type { Plugin } from "@opencode-ai/plugin";
20
21
 
21
22
  export const CompactionPlugin: Plugin = async ({ $, directory }) => {
22
23
  const MEMORY_DIR = `${directory}/.opencode/memory`;
24
+ const HANDOFF_DIR = `${MEMORY_DIR}/handoffs`;
23
25
 
24
26
  return {
25
27
  "experimental.session.compacting": async (input, output) => {
@@ -69,8 +71,29 @@ export const CompactionPlugin: Plugin = async ({ $, directory }) => {
69
71
  // Beads not available, skip
70
72
  }
71
73
 
74
+ // 4. Load most recent handoff file (session continuity)
75
+ let handoffContext = "";
76
+ try {
77
+ const result =
78
+ await $`ls -t ${HANDOFF_DIR}/*.md 2>/dev/null | head -1`.quiet();
79
+ if (result.stdout) {
80
+ const handoffPath = result.stdout.toString().trim();
81
+ if (handoffPath) {
82
+ const handoffContent = await $`cat ${handoffPath}`.text();
83
+ handoffContext = `\n## Previous Session Handoff\n\n${handoffContent}\n\n**IMPORTANT**: Resume work from where previous session left off.`;
84
+ }
85
+ }
86
+ } catch {
87
+ // No handoff files, skip
88
+ }
89
+
72
90
  // Inject all context - session context FIRST (most important)
73
- const allContext = [sessionContext, beadsContext, memoryContext]
91
+ const allContext = [
92
+ sessionContext,
93
+ beadsContext,
94
+ handoffContext,
95
+ memoryContext,
96
+ ]
74
97
  .filter(Boolean)
75
98
  .join("\n");
76
99
 
@@ -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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.14.3",
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;
@@ -1,203 +0,0 @@
1
- /**
2
- * Ralph Wiggum Tools
3
- *
4
- * Standalone tools for the Ralph Wiggum autonomous loop pattern.
5
- * The plugin (ralph-wiggum.ts) handles event listening, these handle user interaction.
6
- */
7
-
8
- import fs from "node:fs/promises";
9
- import path from "node:path";
10
- import { tool } from "@opencode-ai/plugin";
11
-
12
- const STATE_FILE = ".opencode/.ralph-state.json";
13
-
14
- interface RalphState {
15
- active: boolean;
16
- sessionID: string | null;
17
- iteration: number;
18
- maxIterations: number;
19
- completionPromise: string;
20
- task: string;
21
- prdFile: string | null;
22
- progressFile: string;
23
- startedAt: number | null;
24
- mode: "hitl" | "afk";
25
- }
26
-
27
- const DEFAULT_STATE: RalphState = {
28
- active: false,
29
- sessionID: null,
30
- iteration: 0,
31
- maxIterations: 50,
32
- completionPromise: "<promise>COMPLETE</promise>",
33
- task: "",
34
- prdFile: null,
35
- progressFile: "progress.txt",
36
- startedAt: null,
37
- mode: "hitl",
38
- };
39
-
40
- async function loadState(): Promise<RalphState> {
41
- try {
42
- const content = await fs.readFile(STATE_FILE, "utf-8");
43
- return JSON.parse(content);
44
- } catch {
45
- return { ...DEFAULT_STATE };
46
- }
47
- }
48
-
49
- async function saveState(state: RalphState): Promise<void> {
50
- await fs.mkdir(path.dirname(STATE_FILE), { recursive: true });
51
- await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2));
52
- }
53
-
54
- /**
55
- * Start Ralph Wiggum autonomous loop
56
- */
57
- export const ralph_start = tool({
58
- description:
59
- "Start Ralph Wiggum autonomous loop. Agent will work on tasks until completion or max iterations.",
60
- args: {
61
- task: tool.schema
62
- .string()
63
- .describe(
64
- "Task description or goal (e.g., 'Migrate all Jest tests to Vitest')",
65
- ),
66
- prdFile: tool.schema
67
- .string()
68
- .optional()
69
- .describe("Path to PRD/task list file (e.g., 'PRD.md')"),
70
- progressFile: tool.schema
71
- .string()
72
- .optional()
73
- .describe("Path to progress tracking file (default: progress.txt)"),
74
- completionPromise: tool.schema
75
- .string()
76
- .optional()
77
- .describe(
78
- "Text to output when done (default: <promise>COMPLETE</promise>)",
79
- ),
80
- maxIterations: tool.schema
81
- .number()
82
- .optional()
83
- .describe("Maximum iterations before stopping (default: 50)"),
84
- mode: tool.schema
85
- .enum(["hitl", "afk"])
86
- .optional()
87
- .describe("Mode: hitl (human-in-the-loop) or afk (away-from-keyboard)"),
88
- },
89
- execute: async (args, context) => {
90
- const state: RalphState = {
91
- active: true,
92
- sessionID: context.sessionID,
93
- iteration: 0,
94
- maxIterations: args.maxIterations || 50,
95
- completionPromise:
96
- args.completionPromise || "<promise>COMPLETE</promise>",
97
- task: args.task,
98
- prdFile: args.prdFile || null,
99
- progressFile: args.progressFile || "progress.txt",
100
- startedAt: Date.now(),
101
- mode: args.mode || "hitl",
102
- };
103
-
104
- await saveState(state);
105
-
106
- const modeDesc =
107
- state.mode === "hitl"
108
- ? "Human-in-the-loop (watch and intervene)"
109
- : "Away-from-keyboard (autonomous)";
110
-
111
- return `
112
- ## Ralph Loop Active
113
-
114
- **Task:** ${state.task}
115
- **Mode:** ${modeDesc}
116
- **Max Iterations:** ${state.maxIterations}
117
- **Completion Signal:** ${state.completionPromise}
118
- **PRD File:** ${state.prdFile || "(none - using task description)"}
119
- **Progress File:** ${state.progressFile}
120
-
121
- ### Next Steps
122
-
123
- 1. Work on the task described above
124
- 2. After each feature, update ${state.progressFile}
125
- 3. Run feedback loops (typecheck, test, lint)
126
- 4. Commit changes
127
- 5. When ALL tasks complete, output: ${state.completionPromise}
128
-
129
- The loop will continue automatically after each completion until:
130
- - You output the completion promise, OR
131
- - Max iterations (${state.maxIterations}) reached
132
-
133
- **Remember:** ONE feature per iteration. Small steps. Quality over speed.
134
- `.trim();
135
- },
136
- });
137
-
138
- /**
139
- * Stop Ralph Wiggum loop
140
- */
141
- export const ralph_stop = tool({
142
- description: "Stop the Ralph Wiggum loop gracefully",
143
- args: {
144
- reason: tool.schema.string().optional().describe("Reason for stopping"),
145
- },
146
- execute: async (args) => {
147
- const state = await loadState();
148
-
149
- if (!state.active) {
150
- return "No Ralph loop is currently running.";
151
- }
152
-
153
- const duration = state.startedAt
154
- ? Math.round((Date.now() - state.startedAt) / 1000 / 60)
155
- : 0;
156
-
157
- const summary = `
158
- ## Ralph Loop Stopped
159
-
160
- **Iterations Completed:** ${state.iteration}
161
- **Duration:** ${duration} minutes
162
- **Reason:** ${args.reason || "Manual stop requested"}
163
- **Task:** ${state.task}
164
-
165
- Progress has been saved to ${state.progressFile}.
166
- `.trim();
167
-
168
- // Reset state
169
- await saveState(DEFAULT_STATE);
170
-
171
- return summary;
172
- },
173
- });
174
-
175
- /**
176
- * Get Ralph Wiggum status
177
- */
178
- export const ralph_status = tool({
179
- description: "Get current Ralph Wiggum loop status",
180
- args: {},
181
- execute: async () => {
182
- const state = await loadState();
183
-
184
- if (!state.active) {
185
- return "No Ralph loop is currently active.";
186
- }
187
-
188
- const duration = state.startedAt
189
- ? Math.round((Date.now() - state.startedAt) / 1000 / 60)
190
- : 0;
191
-
192
- return `
193
- ## Ralph Loop Active
194
-
195
- **Task:** ${state.task}
196
- **Iteration:** ${state.iteration}/${state.maxIterations}
197
- **Duration:** ${duration} minutes
198
- **Mode:** ${state.mode}
199
- **Completion Signal:** ${state.completionPromise}
200
- **Progress File:** ${state.progressFile}
201
- `.trim();
202
- },
203
- });