opencode-auto-loop 0.1.4 → 0.1.6

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.
@@ -4,26 +4,30 @@ description: "Start Auto Loop - auto-continues until task completion. Use: /auto
4
4
 
5
5
  # Auto Loop
6
6
 
7
- Parse `$ARGUMENTS` for the task description and an optional `--max <number>` flag.
7
+ Parse `$ARGUMENTS` for the task description and optional flags:
8
8
 
9
9
  - If `$ARGUMENTS` contains `--max <number>`, extract that number as **maxIterations** and remove it from the task string.
10
10
  - Otherwise, use **maxIterations**: 25
11
+ - If `$ARGUMENTS` contains `--ralph`, set **forceLoop** to `true` and remove it from the task string. Force mode ignores all completion signals and runs for the full maxIterations.
11
12
 
12
13
  Invoke the `auto-loop` tool with:
13
14
 
14
15
  - **task**: the extracted task description
15
16
  - **maxIterations**: the extracted or default value
17
+ - **forceLoop**: `true` if `--ralph` was present, omit otherwise
16
18
 
17
19
  Examples:
18
20
  - `/auto-loop Build a REST API` → task="Build a REST API", maxIterations=25
19
21
  - `/auto-loop Build a REST API --max 50` → task="Build a REST API", maxIterations=50
20
22
  - `/auto-loop --max 10 Fix all lint errors` → task="Fix all lint errors", maxIterations=10
23
+ - `/auto-loop --ralph Fix all lint errors` → task="Fix all lint errors", maxIterations=25, forceLoop=true
24
+ - `/auto-loop --ralph --max 10 Fix all lint errors` → task="Fix all lint errors", maxIterations=10, forceLoop=true
21
25
 
22
26
  After the tool confirms the loop is active, **immediately begin working on the task**. Do not just acknowledge — start doing the work right away.
23
27
 
24
28
  ## Progress Tracking
25
29
 
26
- Before going idle, you MUST output structured progress so the plugin knows where you left off:
30
+ Before going idle, you MUST output structured progress AND a status line so the plugin knows where you left off:
27
31
 
28
32
  ```markdown
29
33
  ## Completed
@@ -31,15 +35,25 @@ Before going idle, you MUST output structured progress so the plugin knows where
31
35
 
32
36
  ## Next Steps
33
37
  - [ ] What needs to be done next (in priority order)
38
+
39
+ STATUS: IN_PROGRESS
34
40
  ```
35
41
 
36
42
  ## Completion
37
43
 
38
- When the task is FULLY completed, signal completion by outputting the promise-DONE XML tag on its own line:
44
+ When the task is FULLY completed with NO remaining next steps, signal completion:
45
+
46
+ ```
47
+ STATUS: COMPLETE
39
48
 
40
49
  <promise>DONE</promise>
50
+ ```
41
51
 
42
- **IMPORTANT:** ONLY output this when the task is COMPLETELY and VERIFIABLY finished. Do NOT output false promises to escape the loop.
52
+ **IMPORTANT:**
53
+ - ONLY output `STATUS: COMPLETE` and the DONE signal when the task is COMPLETELY and VERIFIABLY finished.
54
+ - Do NOT output the DONE signal if there are ANY unchecked items (`- [ ]`) in your Next Steps — the plugin WILL reject it.
55
+ - If `STATUS: IN_PROGRESS` is present alongside a DONE signal, the plugin will reject it.
56
+ - Do NOT output false promises to escape the loop.
43
57
 
44
58
  ## Cancellation
45
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-loop",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Auto-continue for OpenCode",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -27,7 +27,7 @@ After the tool confirms the loop is active, **immediately begin working on the t
27
27
 
28
28
  ## Progress Tracking - CRITICAL
29
29
 
30
- **Before going idle at the end of each work session, you MUST output structured progress sections.** The plugin parses these to persist your TODOs across iterations so you know exactly where to pick up.
30
+ **Before going idle at the end of each work session, you MUST output structured progress sections AND a status line.** The plugin parses these to persist your TODOs across iterations so you know exactly where to pick up.
31
31
 
32
32
  Use this format in your final message of each iteration:
33
33
 
@@ -41,6 +41,8 @@ Use this format in your final message of each iteration:
41
41
  - [ ] Add JWT authentication middleware
42
42
  - [ ] Create registration endpoint
43
43
  - [ ] Write integration tests
44
+
45
+ STATUS: IN_PROGRESS
44
46
  ```
45
47
 
46
48
  **Rules:**
@@ -48,6 +50,7 @@ Use this format in your final message of each iteration:
48
50
  - Be specific — each item should be a concrete, actionable step
49
51
  - Only list truly completed items under ## Completed
50
52
  - Order ## Next Steps by priority — the continuation will tell you to start from the top
53
+ - You MUST include a `STATUS: IN_PROGRESS` or `STATUS: COMPLETE` line on its own line in EVERY response
51
54
  - The plugin extracts these sections and writes them into `auto-loop.local.md` for the next iteration
52
55
 
53
56
  ## Completion Signal - CRITICAL RULES
@@ -55,22 +58,36 @@ Use this format in your final message of each iteration:
55
58
  When you have FULLY completed the task, signal completion by outputting the promise-DONE XML tag on its own line:
56
59
 
57
60
  ```
61
+ STATUS: COMPLETE
62
+
58
63
  <promise>DONE</promise>
59
64
  ```
60
65
 
61
66
  **IMPORTANT CONSTRAINTS:**
62
67
 
68
+ - **If your Next Steps list has ANY unchecked items (`- [ ]`), you MUST NOT output the DONE signal.** The plugin will detect the contradiction and REJECT the completion, forcing another iteration.
69
+ - You MUST output `STATUS: COMPLETE` (on its own line) alongside the DONE signal. If the plugin detects `STATUS: IN_PROGRESS` with a DONE signal, it will reject the completion.
63
70
  - ONLY output the completion signal when the task is COMPLETELY and VERIFIABLY finished
64
71
  - The completion tag MUST be on its own line (not inline with other text)
65
72
  - Do NOT mention or echo the completion tag in explanatory text — only output it as the actual signal
66
73
  - Do NOT output false completion signals to escape the loop, even if you think you're stuck
67
- - If you're blocked, explain the blocker and request help instead of falsely completing
74
+ - If you're blocked, output `STATUS: IN_PROGRESS` and explain the blocker instead of falsely completing
68
75
 
69
76
  The loop can only be stopped by:
70
77
  1. Truthful completion signal
71
78
  2. Max iterations reached
72
79
  3. User running `/cancel-auto-loop`
73
80
 
81
+ ## Force Mode (--ralph)
82
+
83
+ When started with `--ralph`, the loop ignores ALL completion signals (`<promise>DONE</promise>`, `STATUS: COMPLETE`) and runs for the full iteration count. In force mode:
84
+ - You do NOT need to output `STATUS:` lines or the DONE signal
85
+ - The loop will continue for all iterations regardless
86
+ - Focus on making steady progress each iteration
87
+ - Still output `## Completed` and `## Next Steps` sections so the plugin can track progress
88
+
89
+ Example: `/auto-loop --ralph --max 10 Continue the refactoring task`
90
+
74
91
  ## Checking Status
75
92
 
76
93
  Check current iteration and progress:
@@ -19,6 +19,15 @@ Example:
19
19
 
20
20
  The AI will work on your task and automatically continue until completion.
21
21
 
22
+ ### `/auto-loop <task> --ralph`
23
+ Force mode: ignore all completion signals and run for the full iteration count. Useful when you got interrupted and want to resume, or when you want the AI to keep iterating without stopping early.
24
+
25
+ Examples:
26
+ ```
27
+ /auto-loop --ralph Continue the refactoring
28
+ /auto-loop --ralph --max 10 Fix all lint errors
29
+ ```
30
+
22
31
  ### `/cancel-auto-loop`
23
32
  Cancel an active Auto Loop before it completes.
24
33
 
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ interface LoopState {
16
16
  iteration: number;
17
17
  maxIterations: number;
18
18
  debounceMs: number;
19
+ forceLoop?: boolean;
19
20
  sessionId?: string;
20
21
  prompt?: string;
21
22
  completed?: string;
@@ -31,6 +32,8 @@ const SERVICE_NAME = "auto-loop";
31
32
  const STATE_FILENAME = "auto-loop.local.md";
32
33
  const OPENCODE_CONFIG_DIR = join(homedir(), ".config/opencode");
33
34
  const COMPLETION_TAG = /^\s*<promise>\s*DONE\s*<\/promise>\s*$/im;
35
+ const STATUS_COMPLETE_TAG = /^\s*STATUS:\s*COMPLETE\s*$/im;
36
+ const STATUS_IN_PROGRESS_TAG = /^\s*STATUS:\s*IN_PROGRESS\s*$/im;
34
37
  const DEFAULT_DEBOUNCE_MS = 2000;
35
38
  const DEFAULT_MAX_ITERATIONS = 25;
36
39
 
@@ -122,6 +125,7 @@ function parseState(content: string): LoopState {
122
125
  if (key === "iteration") state.iteration = parseInt(value) || 0;
123
126
  if (key === "maxIterations") state.maxIterations = parseInt(value) || DEFAULT_MAX_ITERATIONS;
124
127
  if (key === "debounceMs") state.debounceMs = parseInt(value) || DEFAULT_DEBOUNCE_MS;
128
+ if (key === "forceLoop") state.forceLoop = value === "true";
125
129
  if (key === "sessionId") state.sessionId = value || undefined;
126
130
  }
127
131
 
@@ -157,6 +161,7 @@ function serializeState(state: LoopState): string {
157
161
  `maxIterations: ${state.maxIterations}`,
158
162
  `debounceMs: ${state.debounceMs}`,
159
163
  ];
164
+ if (state.forceLoop) lines.push(`forceLoop: ${state.forceLoop}`);
160
165
  if (state.sessionId) lines.push(`sessionId: ${state.sessionId}`);
161
166
  lines.push("---");
162
167
  if (state.prompt) lines.push("", state.prompt);
@@ -247,6 +252,65 @@ function checkCompletion(text: string): boolean {
247
252
  return COMPLETION_TAG.test(stripCodeFences(text));
248
253
  }
249
254
 
255
+ // Extract the STATUS signal presence from text.
256
+ function getStatusSignals(text: string): {
257
+ hasComplete: boolean;
258
+ hasInProgress: boolean;
259
+ } {
260
+ const cleaned = stripCodeFences(text);
261
+ return {
262
+ hasComplete: STATUS_COMPLETE_TAG.test(cleaned),
263
+ hasInProgress: STATUS_IN_PROGRESS_TAG.test(cleaned),
264
+ };
265
+ }
266
+
267
+ // Check if the parsed Next Steps section contains unchecked items (- [ ] ...).
268
+ // Returns true if there are incomplete items, meaning the task is NOT done.
269
+ function hasIncompleteSteps(text: string): boolean {
270
+ const nextSteps = extractNextSteps(stripCodeFences(text));
271
+ if (!nextSteps) return false;
272
+
273
+ const uncheckedItems = nextSteps
274
+ .split("\n")
275
+ .filter((line) => /^\s*-\s*\[ \]/.test(line));
276
+
277
+ return uncheckedItems.length > 0;
278
+ }
279
+
280
+ // Validate whether the DONE signal should be honored.
281
+ // Returns { valid: true } if completion is legitimate,
282
+ // or { valid: false, reason: string } if it should be rejected.
283
+ function validateCompletion(text: string): { valid: boolean; reason?: string } {
284
+ // Check 1: contradictory STATUS signals
285
+ const statusSignals = getStatusSignals(text);
286
+ if (statusSignals.hasComplete && statusSignals.hasInProgress) {
287
+ return {
288
+ valid: false,
289
+ reason: "Both STATUS: COMPLETE and STATUS: IN_PROGRESS are present",
290
+ };
291
+ }
292
+
293
+ // Check 2: STATUS signal contradicts DONE
294
+ if (statusSignals.hasInProgress) {
295
+ return {
296
+ valid: false,
297
+ reason: "STATUS: IN_PROGRESS contradicts the DONE signal",
298
+ };
299
+ }
300
+
301
+ // Check 3: Unchecked next steps exist
302
+ if (hasIncompleteSteps(text)) {
303
+ return {
304
+ valid: false,
305
+ reason: "Unchecked next steps (- [ ] ...) found alongside DONE signal",
306
+ };
307
+ }
308
+
309
+ // Check 4: If STATUS signal is present and is COMPLETE, extra confidence
310
+ // If no STATUS signal at all, still allow (backward compatibility)
311
+ return { valid: true };
312
+ }
313
+
250
314
  // Extract next steps / TODOs from assistant message text
251
315
  // Looks for common patterns: ## Next Steps, ## TODO, checkbox lists, numbered lists after keywords
252
316
  function extractNextSteps(text: string): string | undefined {
@@ -351,13 +415,24 @@ function buildProgressSection(state: LoopState): string {
351
415
  // Build the loop context reminder for post-compaction injection
352
416
  function buildLoopContextReminder(state: LoopState): string {
353
417
  const progress = buildProgressSection(state);
354
- return `[AUTO LOOP ACTIVE Iteration ${state.iteration}/${state.maxIterations}]
418
+ const forceLabel = state.forceLoop ? " [FORCE MODE]" : "";
419
+ const rules = state.forceLoop
420
+ ? `IMPORTANT RULES:
421
+ - Before going idle, output ## Completed and ## Next Steps sections
422
+ - FORCE MODE is active — the loop will continue for all ${state.maxIterations} iterations regardless of completion signals
423
+ - Focus on making steady progress each iteration`
424
+ : `IMPORTANT RULES:
425
+ - Before going idle, output ## Completed and ## Next Steps sections
426
+ - You MUST include a STATUS line: either \`STATUS: IN_PROGRESS\` or \`STATUS: COMPLETE\` on its own line
427
+ - Do NOT output <promise>DONE</promise> if there are ANY unchecked items (\`- [ ]\`) in your Next Steps — the plugin WILL reject it
428
+ - Only output \`STATUS: COMPLETE\` and the DONE signal when ALL steps are truly finished and Next Steps is empty
429
+ - Do NOT output false completion promises. If blocked, output \`STATUS: IN_PROGRESS\` and explain the blocker.`;
430
+
431
+ return `[AUTO LOOP${forceLabel} ACTIVE — Iteration ${state.iteration}/${state.maxIterations}]
355
432
 
356
433
  Original task: ${state.prompt || "(no task specified)"}
357
434
  ${progress}
358
- When the task is FULLY complete, you MUST output: <promise>DONE</promise>
359
- Before going idle, list your progress using ## Completed and ## Next Steps sections.
360
- Do NOT output false completion promises. If blocked, explain the blocker.`;
435
+ ${rules}`;
361
436
  }
362
437
 
363
438
  // Check if session is currently busy (not idle)
@@ -440,8 +515,12 @@ export const AutoLoopPlugin: Plugin = async (ctx) => {
440
515
  .number()
441
516
  .optional()
442
517
  .describe("Debounce delay between iterations in ms (default: 2000)"),
518
+ forceLoop: tool.schema
519
+ .boolean()
520
+ .optional()
521
+ .describe("Force mode (--ralph): ignore completion signals and run for all iterations"),
443
522
  },
444
- async execute({ task, maxIterations = DEFAULT_MAX_ITERATIONS, debounceMs = DEFAULT_DEBOUNCE_MS }, context) {
523
+ async execute({ task, maxIterations = DEFAULT_MAX_ITERATIONS, debounceMs = DEFAULT_DEBOUNCE_MS, forceLoop = false }, context) {
445
524
  if (context.abort.aborted) return "Auto Loop start was cancelled.";
446
525
 
447
526
  const state: LoopState = {
@@ -449,6 +528,7 @@ export const AutoLoopPlugin: Plugin = async (ctx) => {
449
528
  iteration: 0,
450
529
  maxIterations,
451
530
  debounceMs,
531
+ forceLoop: forceLoop ? true : undefined,
452
532
  sessionId: context.sessionID,
453
533
  prompt: task,
454
534
  };
@@ -457,16 +537,21 @@ export const AutoLoopPlugin: Plugin = async (ctx) => {
457
537
  continuationInFlight = false;
458
538
  lastContinuation = 0;
459
539
 
460
- log("info", `Loop started for session ${context.sessionID}`);
461
- toast(`Auto Loop started (max ${maxIterations} iterations)`, "success");
540
+ const modeLabel = forceLoop ? " [FORCE MODE]" : "";
541
+ log("info", `Loop started${modeLabel} for session ${context.sessionID}`);
542
+ toast(`Auto Loop started${modeLabel} (max ${maxIterations} iterations)`, "success");
543
+
544
+ const forceNote = forceLoop
545
+ ? `\n\n**FORCE MODE (--ralph):** Completion signals are IGNORED. The loop will run for all ${maxIterations} iterations regardless. Focus on making progress each iteration — you do NOT need to output STATUS or DONE signals.`
546
+ : "";
462
547
 
463
- return `Auto Loop started (max ${maxIterations} iterations).
548
+ return `Auto Loop started (max ${maxIterations} iterations).${forceNote}
464
549
 
465
550
  Task: ${task}
466
551
 
467
- **Begin working on the task now.** The loop will auto-continue until you signal completion.
552
+ **Begin working on the task now.** The loop will auto-continue until ${forceLoop ? `all ${maxIterations} iterations are used` : "you signal completion"}.
468
553
 
469
- Before going idle each iteration, output structured progress:
554
+ Before going idle each iteration, output structured progress${forceLoop ? "" : " AND a status line"}:
470
555
 
471
556
  \`\`\`
472
557
  ## Completed
@@ -474,10 +559,18 @@ Before going idle each iteration, output structured progress:
474
559
 
475
560
  ## Next Steps
476
561
  - [ ] What remains (in priority order)
562
+ ${forceLoop ? "" : "\nSTATUS: IN_PROGRESS"}
477
563
  \`\`\`
478
-
479
- When the task is FULLY and VERIFIABLY complete, output the completion signal on its own line (the promise-DONE XML tag). Do NOT mention or echo the completion tag until you are truly done.
480
-
564
+ ${forceLoop ? "" : `
565
+ ## Completion Rules READ CAREFULLY
566
+
567
+ 1. **If your Next Steps list has ANY unchecked items (\`- [ ]\`), you MUST NOT output the DONE signal.** The plugin will reject it.
568
+ 2. You MUST include a \`STATUS: COMPLETE\` or \`STATUS: IN_PROGRESS\` line on its own line in every response.
569
+ 3. Only when ALL steps are done and Next Steps is empty, output:
570
+ - \`STATUS: COMPLETE\` on its own line
571
+ - The promise-DONE XML tag on its own line
572
+ 4. If you are blocked or stuck, output \`STATUS: IN_PROGRESS\` and explain the blocker. Do NOT output a false DONE.
573
+ `}
481
574
  Use /cancel-auto-loop to stop early.`;
482
575
  },
483
576
  }),
@@ -512,6 +605,8 @@ Use /cancel-auto-loop to stop early.`;
512
605
 
513
606
  - \`/auto-loop <task>\` - Start an auto-continuation loop (default: 25 iterations)
514
607
  - \`/auto-loop <task> --max <n>\` - Start with a custom iteration limit
608
+ - \`/auto-loop <task> --ralph\` - Force mode: ignore completion signals, run all iterations
609
+ - \`/auto-loop <task> --ralph --max <n>\` - Force mode with custom limit
515
610
  - \`/cancel-auto-loop\` - Stop an active loop
516
611
  - \`/auto-loop-help\` - Show this help
517
612
 
@@ -519,6 +614,8 @@ Use /cancel-auto-loop to stop early.`;
519
614
 
520
615
  - \`/auto-loop Build a REST API\` — runs up to 25 iterations
521
616
  - \`/auto-loop Fix all lint errors --max 10\` — runs up to 10 iterations
617
+ - \`/auto-loop --ralph Continue refactoring\` — force runs all 25 iterations, ignores DONE signals
618
+ - \`/auto-loop --ralph --max 50 Big migration\` — force runs all 50 iterations
522
619
 
523
620
  ## How It Works
524
621
 
@@ -527,6 +624,13 @@ Use /cancel-auto-loop to stop early.`;
527
624
  3. Plugin auto-continues if not complete
528
625
  4. Loop stops when AI outputs: <promise>DONE</promise>
529
626
 
627
+ ## Force Mode (--ralph)
628
+
629
+ When \`--ralph\` is used, the loop ignores ALL completion signals and runs for the full iteration count. Useful when:
630
+ - You got interrupted and want to continue no matter what
631
+ - You want the AI to keep iterating and improving
632
+ - You don't want the AI to stop early
633
+
530
634
  ## State File
531
635
 
532
636
  Located at: .opencode/auto-loop.local.md`;
@@ -559,12 +663,23 @@ Located at: .opencode/auto-loop.local.md`;
559
663
  const lastText = await getLastAssistantText(client, sessionId, directory, log);
560
664
 
561
665
  // Skip completion check on iteration 0 (first idle after loop start)
562
- // to avoid false positives from the tool's initial response text
563
- if (state.iteration > 0 && lastText && checkCompletion(lastText)) {
564
- await clearState(directory, log);
565
- log("info", `Loop completed at iteration ${state.iteration}`);
566
- toast(`Auto Loop completed after ${state.iteration} iteration(s)`, "success");
567
- return;
666
+ // to avoid false positives from the tool's initial response text.
667
+ // Also skip entirely when forceLoop is true — force mode ignores
668
+ // all completion signals and runs until max iterations.
669
+ if (!state.forceLoop && state.iteration > 0 && lastText && checkCompletion(lastText)) {
670
+ // Validate the DONE signal reject if there are unchecked steps
671
+ // or if the STATUS signal contradicts completion
672
+ const validation = validateCompletion(lastText);
673
+ if (validation.valid) {
674
+ await clearState(directory, log);
675
+ log("info", `Loop completed at iteration ${state.iteration}`);
676
+ toast(`Auto Loop completed after ${state.iteration} iteration(s)`, "success");
677
+ return;
678
+ } else {
679
+ log("warn", `Rejected premature DONE signal: ${validation.reason}`);
680
+ toast(`Auto Loop: DONE rejected — ${validation.reason}`, "warning");
681
+ // Fall through to send another continuation prompt
682
+ }
568
683
  }
569
684
 
570
685
  if (state.iteration >= state.maxIterations) {
@@ -591,15 +706,26 @@ Located at: .opencode/auto-loop.local.md`;
591
706
  // Build continuation prompt with progress context
592
707
  const progressSection = buildProgressSection(newState);
593
708
 
594
- const continuationPrompt = `[AUTO LOOP ITERATION ${newState.iteration}/${newState.maxIterations}]
709
+ const forceLabel = state.forceLoop ? " [FORCE MODE]" : "";
710
+ const importantRules = state.forceLoop
711
+ ? `IMPORTANT:
712
+ - Pick up from the next incomplete step below
713
+ - Before going idle, list your progress using ## Completed and ## Next Steps sections
714
+ - FORCE MODE is active — the loop will continue for all ${newState.maxIterations} iterations regardless of completion signals
715
+ - Focus on making steady progress each iteration`
716
+ : `IMPORTANT:
717
+ - Pick up from the next incomplete step below
718
+ - Before going idle, list your progress using ## Completed and ## Next Steps sections
719
+ - You MUST include a STATUS line: either \`STATUS: IN_PROGRESS\` or \`STATUS: COMPLETE\` on its own line
720
+ - Do NOT output <promise>DONE</promise> if there are ANY unchecked items (\`- [ ]\`) in your Next Steps — the plugin WILL reject it
721
+ - Only output \`STATUS: COMPLETE\` and the DONE signal when ALL steps are truly finished and Next Steps is empty
722
+ - Do not stop until the task is truly done`;
723
+
724
+ const continuationPrompt = `[AUTO LOOP${forceLabel} — ITERATION ${newState.iteration}/${newState.maxIterations}]
595
725
 
596
726
  Continue working on the task. Do NOT repeat work that is already done.
597
727
  ${progressSection}
598
- IMPORTANT:
599
- - Pick up from the next incomplete step below
600
- - When FULLY complete, output: <promise>DONE</promise>
601
- - Before going idle, list your progress using ## Completed and ## Next Steps sections
602
- - Do not stop until the task is truly done
728
+ ${importantRules}
603
729
 
604
730
  Original task:
605
731
  ${state.prompt || "(no task specified)"}`;