opencode-auto-loop 0.1.3 → 0.1.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.
package/README.md CHANGED
@@ -43,7 +43,7 @@ The AI will work on your task and automatically continue until completion.
43
43
  1. `/auto-loop` creates a state file at `.opencode/auto-loop.local.md`
44
44
  2. When the AI goes idle, the plugin checks if `<promise>DONE</promise>` was output
45
45
  3. If not found, it extracts progress (## Completed / ## Next Steps) and injects a continuation prompt
46
- 4. Loop continues until DONE is found or max iterations (100) reached
46
+ 4. Loop continues until DONE is found or max iterations (25) reached
47
47
  5. State file is deleted when complete
48
48
  6. Loop context survives session compaction
49
49
 
@@ -75,7 +75,7 @@ Format (markdown with YAML frontmatter):
75
75
  ---
76
76
  active: true
77
77
  iteration: 3
78
- maxIterations: 100
78
+ maxIterations: 25
79
79
  sessionId: ses_abc123
80
80
  ---
81
81
 
@@ -23,7 +23,7 @@ After the tool confirms the loop is active, **immediately begin working on the t
23
23
 
24
24
  ## Progress Tracking
25
25
 
26
- Before going idle, you MUST output structured progress so the plugin knows where you left off:
26
+ Before going idle, you MUST output structured progress AND a status line so the plugin knows where you left off:
27
27
 
28
28
  ```markdown
29
29
  ## Completed
@@ -31,15 +31,25 @@ Before going idle, you MUST output structured progress so the plugin knows where
31
31
 
32
32
  ## Next Steps
33
33
  - [ ] What needs to be done next (in priority order)
34
+
35
+ STATUS: IN_PROGRESS
34
36
  ```
35
37
 
36
38
  ## Completion
37
39
 
38
- When the task is FULLY completed, signal completion by outputting the promise-DONE XML tag on its own line:
40
+ When the task is FULLY completed with NO remaining next steps, signal completion:
41
+
42
+ ```
43
+ STATUS: COMPLETE
39
44
 
40
45
  <promise>DONE</promise>
46
+ ```
41
47
 
42
- **IMPORTANT:** ONLY output this when the task is COMPLETELY and VERIFIABLY finished. Do NOT output false promises to escape the loop.
48
+ **IMPORTANT:**
49
+ - ONLY output `STATUS: COMPLETE` and the DONE signal when the task is COMPLETELY and VERIFIABLY finished.
50
+ - Do NOT output the DONE signal if there are ANY unchecked items (`- [ ]`) in your Next Steps — the plugin WILL reject it.
51
+ - If `STATUS: IN_PROGRESS` is present alongside a DONE signal, the plugin will reject it.
52
+ - Do NOT output false promises to escape the loop.
43
53
 
44
54
  ## Cancellation
45
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-loop",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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,16 +58,20 @@ 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
@@ -57,7 +57,7 @@ Located at `.opencode/auto-loop.local.md` (add to `.gitignore`):
57
57
  ---
58
58
  active: true
59
59
  iteration: 3
60
- maxIterations: 100
60
+ maxIterations: 25
61
61
  sessionId: ses_abc123
62
62
  ---
63
63
 
package/src/index.ts CHANGED
@@ -31,6 +31,8 @@ const SERVICE_NAME = "auto-loop";
31
31
  const STATE_FILENAME = "auto-loop.local.md";
32
32
  const OPENCODE_CONFIG_DIR = join(homedir(), ".config/opencode");
33
33
  const COMPLETION_TAG = /^\s*<promise>\s*DONE\s*<\/promise>\s*$/im;
34
+ const STATUS_COMPLETE_TAG = /^\s*STATUS:\s*COMPLETE\s*$/im;
35
+ const STATUS_IN_PROGRESS_TAG = /^\s*STATUS:\s*IN_PROGRESS\s*$/im;
34
36
  const DEFAULT_DEBOUNCE_MS = 2000;
35
37
  const DEFAULT_MAX_ITERATIONS = 25;
36
38
 
@@ -247,6 +249,65 @@ function checkCompletion(text: string): boolean {
247
249
  return COMPLETION_TAG.test(stripCodeFences(text));
248
250
  }
249
251
 
252
+ // Extract the STATUS signal presence from text.
253
+ function getStatusSignals(text: string): {
254
+ hasComplete: boolean;
255
+ hasInProgress: boolean;
256
+ } {
257
+ const cleaned = stripCodeFences(text);
258
+ return {
259
+ hasComplete: STATUS_COMPLETE_TAG.test(cleaned),
260
+ hasInProgress: STATUS_IN_PROGRESS_TAG.test(cleaned),
261
+ };
262
+ }
263
+
264
+ // Check if the parsed Next Steps section contains unchecked items (- [ ] ...).
265
+ // Returns true if there are incomplete items, meaning the task is NOT done.
266
+ function hasIncompleteSteps(text: string): boolean {
267
+ const nextSteps = extractNextSteps(stripCodeFences(text));
268
+ if (!nextSteps) return false;
269
+
270
+ const uncheckedItems = nextSteps
271
+ .split("\n")
272
+ .filter((line) => /^\s*-\s*\[ \]/.test(line));
273
+
274
+ return uncheckedItems.length > 0;
275
+ }
276
+
277
+ // Validate whether the DONE signal should be honored.
278
+ // Returns { valid: true } if completion is legitimate,
279
+ // or { valid: false, reason: string } if it should be rejected.
280
+ function validateCompletion(text: string): { valid: boolean; reason?: string } {
281
+ // Check 1: contradictory STATUS signals
282
+ const statusSignals = getStatusSignals(text);
283
+ if (statusSignals.hasComplete && statusSignals.hasInProgress) {
284
+ return {
285
+ valid: false,
286
+ reason: "Both STATUS: COMPLETE and STATUS: IN_PROGRESS are present",
287
+ };
288
+ }
289
+
290
+ // Check 2: STATUS signal contradicts DONE
291
+ if (statusSignals.hasInProgress) {
292
+ return {
293
+ valid: false,
294
+ reason: "STATUS: IN_PROGRESS contradicts the DONE signal",
295
+ };
296
+ }
297
+
298
+ // Check 3: Unchecked next steps exist
299
+ if (hasIncompleteSteps(text)) {
300
+ return {
301
+ valid: false,
302
+ reason: "Unchecked next steps (- [ ] ...) found alongside DONE signal",
303
+ };
304
+ }
305
+
306
+ // Check 4: If STATUS signal is present and is COMPLETE, extra confidence
307
+ // If no STATUS signal at all, still allow (backward compatibility)
308
+ return { valid: true };
309
+ }
310
+
250
311
  // Extract next steps / TODOs from assistant message text
251
312
  // Looks for common patterns: ## Next Steps, ## TODO, checkbox lists, numbered lists after keywords
252
313
  function extractNextSteps(text: string): string | undefined {
@@ -355,9 +416,12 @@ function buildLoopContextReminder(state: LoopState): string {
355
416
 
356
417
  Original task: ${state.prompt || "(no task specified)"}
357
418
  ${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.`;
419
+ IMPORTANT RULES:
420
+ - Before going idle, output ## Completed and ## Next Steps sections
421
+ - You MUST include a STATUS line: either \`STATUS: IN_PROGRESS\` or \`STATUS: COMPLETE\` on its own line
422
+ - Do NOT output <promise>DONE</promise> if there are ANY unchecked items (\`- [ ]\`) in your Next Steps — the plugin WILL reject it
423
+ - Only output \`STATUS: COMPLETE\` and the DONE signal when ALL steps are truly finished and Next Steps is empty
424
+ - Do NOT output false completion promises. If blocked, output \`STATUS: IN_PROGRESS\` and explain the blocker.`;
361
425
  }
362
426
 
363
427
  // Check if session is currently busy (not idle)
@@ -466,7 +530,7 @@ Task: ${task}
466
530
 
467
531
  **Begin working on the task now.** The loop will auto-continue until you signal completion.
468
532
 
469
- Before going idle each iteration, output structured progress:
533
+ Before going idle each iteration, output structured progress AND a status line:
470
534
 
471
535
  \`\`\`
472
536
  ## Completed
@@ -474,9 +538,18 @@ Before going idle each iteration, output structured progress:
474
538
 
475
539
  ## Next Steps
476
540
  - [ ] What remains (in priority order)
541
+
542
+ STATUS: IN_PROGRESS
477
543
  \`\`\`
478
544
 
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.
545
+ ## Completion Rules READ CAREFULLY
546
+
547
+ 1. **If your Next Steps list has ANY unchecked items (\`- [ ]\`), you MUST NOT output the DONE signal.** The plugin will reject it.
548
+ 2. You MUST include a \`STATUS: COMPLETE\` or \`STATUS: IN_PROGRESS\` line on its own line in every response.
549
+ 3. Only when ALL steps are done and Next Steps is empty, output:
550
+ - \`STATUS: COMPLETE\` on its own line
551
+ - The promise-DONE XML tag on its own line
552
+ 4. If you are blocked or stuck, output \`STATUS: IN_PROGRESS\` and explain the blocker. Do NOT output a false DONE.
480
553
 
481
554
  Use /cancel-auto-loop to stop early.`;
482
555
  },
@@ -561,10 +634,19 @@ Located at: .opencode/auto-loop.local.md`;
561
634
  // Skip completion check on iteration 0 (first idle after loop start)
562
635
  // to avoid false positives from the tool's initial response text
563
636
  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;
637
+ // Validate the DONE signal — reject if there are unchecked steps
638
+ // or if the STATUS signal contradicts completion
639
+ const validation = validateCompletion(lastText);
640
+ if (validation.valid) {
641
+ await clearState(directory, log);
642
+ log("info", `Loop completed at iteration ${state.iteration}`);
643
+ toast(`Auto Loop completed after ${state.iteration} iteration(s)`, "success");
644
+ return;
645
+ } else {
646
+ log("warn", `Rejected premature DONE signal: ${validation.reason}`);
647
+ toast(`Auto Loop: DONE rejected — ${validation.reason}`, "warning");
648
+ // Fall through to send another continuation prompt
649
+ }
568
650
  }
569
651
 
570
652
  if (state.iteration >= state.maxIterations) {
@@ -597,8 +679,10 @@ Continue working on the task. Do NOT repeat work that is already done.
597
679
  ${progressSection}
598
680
  IMPORTANT:
599
681
  - Pick up from the next incomplete step below
600
- - When FULLY complete, output: <promise>DONE</promise>
601
682
  - Before going idle, list your progress using ## Completed and ## Next Steps sections
683
+ - You MUST include a STATUS line: either \`STATUS: IN_PROGRESS\` or \`STATUS: COMPLETE\` on its own line
684
+ - Do NOT output <promise>DONE</promise> if there are ANY unchecked items (\`- [ ]\`) in your Next Steps — the plugin WILL reject it
685
+ - Only output \`STATUS: COMPLETE\` and the DONE signal when ALL steps are truly finished and Next Steps is empty
602
686
  - Do not stop until the task is truly done
603
687
 
604
688
  Original task: