opencode-ralph-rlm 0.1.7 → 0.1.8

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
@@ -438,6 +438,7 @@ Bind the current session as the supervisor and optionally start attempt 1 immedi
438
438
  Stop supervision for the current process. This prevents further auto-loop orchestration until restarted.
439
439
 
440
440
  - Use this when you want to pause/stop Ralph from spawning more sessions.
441
+ - Use this after verification passes and the user confirms they are done, or when the user asks to stop the loop.
441
442
  - Resume later with `ralph_create_supervisor_session(restart_if_done=true)`.
442
443
 
443
444
  #### `ralph_supervision_status()`
@@ -572,6 +573,16 @@ args:
572
573
  post_to_conversation boolean optional Post to main conversation (default: true)
573
574
  ```
574
575
 
576
+ #### `ralph_peek_worker(maxLines?, post_to_conversation?)`
577
+
578
+ Snapshot the active worker's `CURRENT_STATE.md` and optionally post it into the main conversation for quick "peek" access in the TUI.
579
+
580
+ ```
581
+ args:
582
+ maxLines number optional Max lines to include (default: 120)
583
+ post_to_conversation boolean optional Post to main conversation (default: true)
584
+ ```
585
+
575
586
  #### `ralph_ask(question, context?, timeout_minutes?)`
576
587
 
577
588
  Ask a question and **block** until you respond via `ralph_respond()`. The question is written to `.opencode/pending_input.json`, a toast appears in the main session, and the main conversation is prompted with the question ID and response instruction. The calling session polls every 5 seconds.
package/dist/ralph-rlm.js CHANGED
@@ -23509,6 +23509,7 @@ var DEFAULT_TEMPLATES = {
23509
23509
  " When you receive one, call ralph_respond(id, answer) to unblock the session.",
23510
23510
  "- Use ralph_doctor() to check setup, ralph_bootstrap_plan() to generate PLAN/TODOS,",
23511
23511
  " ralph_create_supervisor_session() to bind/start explicitly, ralph_pause_supervision()/ralph_resume_supervision() to control execution, and ralph_end_supervision() to stop.",
23512
+ "- End supervision when verification has passed and the user confirms they are done, or when the user explicitly asks to stop the loop.",
23512
23513
  "- Optional reviewer flow: worker marks readiness with ralph_request_review(); supervisor runs ralph_run_reviewer().",
23513
23514
  "- Monitor progress in SUPERVISOR_LOG.md, CONVERSATION.md, or via toast notifications."
23514
23515
  ].join(`
@@ -24102,7 +24103,21 @@ var RalphRLM = async ({ client, $, worktree }) => {
24102
24103
  - ${nowISO()} rotated
24103
24104
  `)).catch(() => {});
24104
24105
  };
24106
+ const DEDUPE_WINDOW_MS = 5000;
24107
+ const recentNotices = new Map;
24108
+ const shouldDedupe = (key) => {
24109
+ const now2 = Date.now();
24110
+ const last3 = recentNotices.get(key);
24111
+ if (last3 && now2 - last3 < DEDUPE_WINDOW_MS)
24112
+ return true;
24113
+ if (recentNotices.size > 200)
24114
+ recentNotices.clear();
24115
+ recentNotices.set(key, now2);
24116
+ return false;
24117
+ };
24105
24118
  const appendConversationEntry = async (source, message) => {
24119
+ if (shouldDedupe(`conv|${source}|${message}`))
24120
+ return;
24106
24121
  const cfg = await run(getConfig());
24107
24122
  await rotateConversationLogIfNeeded(cfg);
24108
24123
  const ts = nowISO();
@@ -24113,6 +24128,8 @@ var RalphRLM = async ({ client, $, worktree }) => {
24113
24128
  });
24114
24129
  };
24115
24130
  const notifySupervisor = async (source, message, level = "info", postToConversation = true, originSessionId) => {
24131
+ if (shouldDedupe(`sup|${source}|${level}|${message}`))
24132
+ return;
24116
24133
  const cfg = await run(getConfig());
24117
24134
  if (!messagePassesVerbosity(cfg.statusVerbosity, level))
24118
24135
  return;
@@ -24137,6 +24154,34 @@ var RalphRLM = async ({ client, $, worktree }) => {
24137
24154
  }).catch(() => {});
24138
24155
  }
24139
24156
  };
24157
+ const detectProjectDefaults = (root) => exports_Effect.gen(function* () {
24158
+ const j = (f) => NodePath.join(root, f);
24159
+ const hasBunLock = (yield* fileExists(j("bun.lockb"))) || (yield* fileExists(j("bun.lock")));
24160
+ if (hasBunLock)
24161
+ return { verify: ["bun", "run", "verify"], install: "bun install" };
24162
+ const hasYarnLock = yield* fileExists(j("yarn.lock"));
24163
+ if (hasYarnLock)
24164
+ return { verify: ["yarn", "test"], install: "yarn install" };
24165
+ const hasPnpmLock = yield* fileExists(j("pnpm-lock.yaml"));
24166
+ if (hasPnpmLock)
24167
+ return { verify: ["pnpm", "test"], install: "pnpm install" };
24168
+ const hasPkg = yield* fileExists(j("package.json"));
24169
+ if (hasPkg)
24170
+ return { verify: ["npm", "test"], install: "npm install" };
24171
+ const hasCargo = yield* fileExists(j("Cargo.toml"));
24172
+ if (hasCargo)
24173
+ return { verify: ["cargo", "test"], install: "cargo build" };
24174
+ const hasPy = yield* fileExists(j("pyproject.toml"));
24175
+ const hasReq = yield* fileExists(j("requirements.txt"));
24176
+ if (hasReq)
24177
+ return { verify: ["python", "-m", "pytest"], install: "pip install -r requirements.txt" };
24178
+ if (hasPy)
24179
+ return { verify: ["python", "-m", "pytest"], install: "pip install ." };
24180
+ const hasMake = yield* fileExists(j("Makefile"));
24181
+ if (hasMake)
24182
+ return { verify: ["make", "test"], install: "make" };
24183
+ return { verify: ["bun", "run", "verify"], install: "bun install" };
24184
+ });
24140
24185
  const checkSetup = async (root, cfg) => {
24141
24186
  const diagnostics = {
24142
24187
  ready: true,
@@ -24148,7 +24193,8 @@ var RalphRLM = async ({ client, $, worktree }) => {
24148
24193
  if (!cfg.verify || cfg.verify.command.length === 0) {
24149
24194
  diagnostics.ready = false;
24150
24195
  diagnostics.issues.push("Missing verify.command in .opencode/ralph.json.");
24151
- diagnostics.suggestions.push('Set verify.command, e.g. ["bun", "run", "verify"].');
24196
+ const defaults = await run(detectProjectDefaults(root));
24197
+ diagnostics.suggestions.push(`Set verify.command, e.g. ${JSON.stringify(defaults.verify)}.`);
24152
24198
  }
24153
24199
  const planExists = await run(fileExists(j(FILES.PLAN)));
24154
24200
  if (!planExists) {
@@ -24502,6 +24548,41 @@ ${args2.nextStep}
24502
24548
  return run(runVerify(root));
24503
24549
  }
24504
24550
  });
24551
+ const tool_ralph_peek_worker = tool({
24552
+ description: "Snapshot the active RLM worker's CURRENT_STATE.md and optionally post it into the main conversation.",
24553
+ args: {
24554
+ maxLines: tool.schema.number().int().min(20).max(400).optional(),
24555
+ post_to_conversation: tool.schema.boolean().optional().describe("Whether to post the peek into the main conversation (default: true).")
24556
+ },
24557
+ async execute(args2, ctx) {
24558
+ const root = ctx.worktree ?? worktree;
24559
+ const currPath = NodePath.join(root, FILES.CURR);
24560
+ const ok = await run(fileExists(currPath));
24561
+ if (!ok)
24562
+ return JSON.stringify({ ok: false, missing: FILES.CURR }, null, 2);
24563
+ const raw = await run(readFile(currPath).pipe(exports_Effect.orElseSucceed(() => "")));
24564
+ const text = clampLines(raw, args2.maxLines ?? 120);
24565
+ const attempt = supervisor.attempt;
24566
+ const workerId = supervisor.currentWorkerSessionId;
24567
+ const header = `Worker peek${attempt ? ` (attempt ${attempt})` : ""}${workerId ? ` \u2014 ${workerId}` : ""}`;
24568
+ await notifySupervisor("peek", header, "info", false, ctx.sessionID);
24569
+ const postToConv = args2.post_to_conversation !== false;
24570
+ if (postToConv && supervisor.sessionId && supervisor.sessionId !== ctx.sessionID) {
24571
+ await client.session.promptAsync({
24572
+ path: { id: supervisor.sessionId },
24573
+ body: { parts: [{ type: "text", text: `[peek] ${header}
24574
+
24575
+ ${text}` }] }
24576
+ }).catch(() => {});
24577
+ }
24578
+ return JSON.stringify({
24579
+ ok: true,
24580
+ attempt: attempt || null,
24581
+ workerSessionId: workerId ?? null,
24582
+ text
24583
+ }, null, 2);
24584
+ }
24585
+ });
24505
24586
  const tool_subagent_peek = tool({
24506
24587
  description: "Read file-first state from a sub-agent directory (.opencode/agents/<name>/).",
24507
24588
  args: {
@@ -24777,6 +24858,7 @@ No pending questions found.`));
24777
24858
  const diagnosticsBefore = await checkSetup(root, cfg);
24778
24859
  const actions = [];
24779
24860
  if (args2.autofix) {
24861
+ const defaults = await run(detectProjectDefaults(root));
24780
24862
  const configPath = NodePath.join(root, ".opencode", "ralph.json");
24781
24863
  const configExists = await run(fileExists(configPath));
24782
24864
  if (!configExists) {
@@ -24786,7 +24868,7 @@ No pending questions found.`));
24786
24868
  statusVerbosity: "normal",
24787
24869
  maxAttempts: 25,
24788
24870
  heartbeatMinutes: 15,
24789
- verify: { command: ["bun", "run", "verify"], cwd: "." },
24871
+ verify: { command: defaults.verify, cwd: "." },
24790
24872
  gateDestructiveToolsUntilContextLoaded: true,
24791
24873
  maxRlmSliceLines: 200,
24792
24874
  requireGrepBeforeLargeSlice: true,
@@ -24814,8 +24896,8 @@ No pending questions found.`));
24814
24896
  "# Project Agent Rules",
24815
24897
  "",
24816
24898
  "## Build and verify",
24817
- "- Install: bun install",
24818
- "- Verify: bun run verify",
24899
+ `- Install: ${defaults.install}`,
24900
+ `- Verify: ${defaults.verify.join(" ")}`,
24819
24901
  "",
24820
24902
  "## Loop note",
24821
24903
  "- This project uses ralph-rlm.",
@@ -25149,13 +25231,14 @@ Set a new goal and run again.
25149
25231
  const configPath = NodePath.join(root, ".opencode", "ralph.json");
25150
25232
  const configExists = await run(fileExists(configPath));
25151
25233
  if (!configExists) {
25234
+ const defaults = await run(detectProjectDefaults(root));
25152
25235
  const defaultCfg = {
25153
25236
  enabled: true,
25154
25237
  autoStartOnMainIdle: false,
25155
25238
  statusVerbosity: "normal",
25156
25239
  maxAttempts: 25,
25157
25240
  heartbeatMinutes: 15,
25158
- verify: { command: ["bun", "run", "verify"], cwd: "." },
25241
+ verify: { command: defaults.verify, cwd: "." },
25159
25242
  gateDestructiveToolsUntilContextLoaded: true,
25160
25243
  maxRlmSliceLines: 200,
25161
25244
  requireGrepBeforeLargeSlice: true,
@@ -25592,6 +25675,7 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25592
25675
  ralph_update_rlm_instructions: tool_ralph_update_rlm_instructions,
25593
25676
  ralph_rollover: tool_ralph_rollover,
25594
25677
  ralph_verify: tool_ralph_verify,
25678
+ ralph_peek_worker: tool_ralph_peek_worker,
25595
25679
  ralph_spawn_worker: tool_ralph_spawn_worker,
25596
25680
  subagent_peek: tool_subagent_peek,
25597
25681
  subagent_spawn: tool_subagent_spawn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ralph-rlm",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "OpenCode plugin: Ralph outer loop + RLM inner loop. Iterative AI development with file-first discipline and sub-agent support.",
5
5
  "type": "module",
6
6
  "main": "./dist/ralph-rlm.js",