opencode-ralph-rlm 0.1.6 → 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
@@ -265,6 +265,28 @@ If you are setting up a repo from scratch, use this sequence:
265
265
 
266
266
  If setup is incomplete, auto-start is skipped and the plugin emits a warning with next actions.
267
267
 
268
+ ## Recommended OpenCode agent setup
269
+
270
+ The plugin already manages the core loop roles (`main` / `ralph` / `worker` / `subagent`).
271
+ Use OpenCode agents to control posture and delegation, not to replace loop orchestration.
272
+
273
+ Recommended split:
274
+
275
+ - `supervisor` (primary): your default top-level operator for safe orchestration.
276
+ - built-in `plan` (primary): dry analysis/planning without edits.
277
+ - built-in `build` (primary): full manual implementation when needed.
278
+ - project subagents (optional): focused review/docs/security helpers.
279
+
280
+ This repo now includes project-local agent files under `.opencode/agents/`:
281
+
282
+ - `.opencode/agents/supervisor.md`
283
+ - `.opencode/agents/ralph-reviewer.md`
284
+ - `.opencode/agents/docs-writer.md`
285
+ - `.opencode/agents/security-auditor.md`
286
+
287
+ These profiles intentionally keep loop ownership in `ralph-rlm`.
288
+ Do not model Ralph strategist/worker as OpenCode primary/subagent replacements.
289
+
268
290
 
269
291
  ## Protocol files
270
292
 
@@ -416,6 +438,7 @@ Bind the current session as the supervisor and optionally start attempt 1 immedi
416
438
  Stop supervision for the current process. This prevents further auto-loop orchestration until restarted.
417
439
 
418
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.
419
442
  - Resume later with `ralph_create_supervisor_session(restart_if_done=true)`.
420
443
 
421
444
  #### `ralph_supervision_status()`
@@ -550,6 +573,16 @@ args:
550
573
  post_to_conversation boolean optional Post to main conversation (default: true)
551
574
  ```
552
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
+
553
586
  #### `ralph_ask(question, context?, timeout_minutes?)`
554
587
 
555
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.6",
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",