@ai-dev-methodologies/rlp-desk 0.14.1 → 0.14.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-dev-methodologies/rlp-desk",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Fresh-context iterative loops for Claude Code — autonomous task completion with independent verification",
5
5
  "scripts": {
6
6
  "postinstall": "node scripts/postinstall.js",
@@ -127,6 +127,11 @@ export async function pollForSignal(
127
127
  capturePane = defaultCapturePane,
128
128
  sendKeys = defaultSendKeys,
129
129
  log = () => {},
130
+ // v0.14.2 Bug Report #4 Fix-D: optional legacy fallback path checked
131
+ // only after the canonical signalFile last-chance read fails. Caller
132
+ // (campaign-main-loop) supplies the pre-v0.13.0 .claude/ralph-desk
133
+ // memos path; signal-poller stays read-only and never migrates.
134
+ legacySignalFile = null,
130
135
  } = {},
131
136
  ) {
132
137
  const deadline = Date.now() + timeoutMs;
@@ -249,7 +254,23 @@ export async function pollForSignal(
249
254
  const rawContent = await readFile(signalFile);
250
255
  return JSON.parse(rawContent);
251
256
  } catch {
252
- // fall through to TimeoutError
257
+ // fall through
258
+ }
259
+
260
+ // v0.14.2 Bug Report #4 Fix-D: codex sometimes lands the verdict at the
261
+ // pre-v0.13.0 legacy path (.claude/ralph-desk/memos/...) instead of the
262
+ // canonical .rlp-desk/memos/. If the caller passed `legacySignalFile`,
263
+ // try that path before declaring timeout — same semantics as the
264
+ // canonical last-chance read. campaign-main-loop is responsible for
265
+ // migrating the file into the canonical location after observing it;
266
+ // signal-poller stays read-only.
267
+ if (legacySignalFile) {
268
+ try {
269
+ const rawContent = await readFile(legacySignalFile);
270
+ return JSON.parse(rawContent);
271
+ } catch {
272
+ // fall through to TimeoutError
273
+ }
253
274
  }
254
275
 
255
276
  throw new TimeoutError(`Timed out waiting for valid JSON signal at ${signalFile}`);
@@ -180,6 +180,11 @@ export async function assembleVerifierPrompt({
180
180
  verifiedUs = [],
181
181
  autonomousMode = false,
182
182
  conflictLogPath = '',
183
+ // v0.14.2 Bug Report #4 Fix-E: when supplied, the assembled prompt
184
+ // ends with a strong "MUST write verdict to <absolute_path>" rule so
185
+ // codex (which sometimes infers the legacy .claude/ralph-desk path
186
+ // from CWD) lands the verdict where the leader is polling.
187
+ verdictWritePath = '',
183
188
  } = {}) {
184
189
  const basePrompt = await readRequiredFile(promptBase, 'Verifier prompt base file');
185
190
  const promptLines = [
@@ -209,5 +214,19 @@ export async function assembleVerifierPrompt({
209
214
  appendAutonomousModeSection(promptLines, { conflictLogPath, verifier: true });
210
215
  }
211
216
 
217
+ if (verdictWritePath) {
218
+ promptLines.push('');
219
+ promptLines.push('---');
220
+ promptLines.push('## CRITICAL: Verdict file write path (v0.14.2)');
221
+ promptLines.push('');
222
+ promptLines.push('Write `verify-verdict.json` ONLY to this absolute path:');
223
+ promptLines.push('');
224
+ promptLines.push(` ${verdictWritePath}`);
225
+ promptLines.push('');
226
+ promptLines.push('DO NOT write to `.claude/ralph-desk/memos/` — that path is deprecated since');
227
+ promptLines.push('v0.13.0. The leader polls only the absolute path above; writing elsewhere');
228
+ promptLines.push('triggers BLOCKED `verifier_dead` even though your verdict is correct.');
229
+ }
230
+
212
231
  return `${promptLines.join('\n')}\n`;
213
232
  }
@@ -43,6 +43,31 @@ const MODEL_UPGRADES = {
43
43
  'gpt-5.3-codex-spark:xhigh': 'BLOCKED',
44
44
  };
45
45
 
46
+ // v0.14.2 Bug Report #4 Fix-D: codex occasionally lands the verdict at the
47
+ // pre-v0.13.0 `.claude/ralph-desk/memos/` path despite prompt instructions.
48
+ // signal-poller's `legacySignalFile` last-chance branch returns the parsed
49
+ // verdict in memory; these two helpers move the file into the canonical
50
+ // .rlp-desk/memos/ location AFTER the polling loop succeeds, so analytics
51
+ // archival + sentinel hygiene remain consistent.
52
+ export async function _verdictMigrationNeeded(paths) {
53
+ if (!paths?.legacyVerdictFile || !paths?.verdictFile) return false;
54
+ // Migration is only meaningful when the legacy file exists AND the
55
+ // canonical file does not. If both exist, canonical wins (already
56
+ // observed) and we leave legacy alone.
57
+ let legacyExists = false;
58
+ let canonicalExists = false;
59
+ try { legacyExists = await exists(paths.legacyVerdictFile); } catch {}
60
+ try { canonicalExists = await exists(paths.verdictFile); } catch {}
61
+ return legacyExists && !canonicalExists;
62
+ }
63
+
64
+ export async function _migrateLegacyVerdict(paths) {
65
+ if (!paths?.legacyVerdictFile || !paths?.verdictFile) return false;
66
+ await fs.mkdir(path.dirname(paths.verdictFile), { recursive: true });
67
+ await fs.rename(paths.legacyVerdictFile, paths.verdictFile);
68
+ return true;
69
+ }
70
+
46
71
  // v0.13.0: legacy .claude/ralph-desk/ guidance for run mode (no auto-mv).
47
72
  export function detectLegacyDeskInRunMode(rootDir, env = process.env) {
48
73
  const legacyPath = path.join(rootDir, LEGACY_DESK_REL);
@@ -76,6 +101,12 @@ function buildPaths(rootDir, slug, env = process.env) {
76
101
  doneClaimFile: path.join(deskRoot, 'memos', `${slug}-done-claim.json`),
77
102
  signalFile: path.join(deskRoot, 'memos', `${slug}-iter-signal.json`),
78
103
  verdictFile: path.join(deskRoot, 'memos', `${slug}-verify-verdict.json`),
104
+ // v0.14.2 Bug Report #4 Fix-D: codex sometimes lands the verdict at the
105
+ // pre-v0.13.0 legacy path. We track the absolute legacy location so the
106
+ // signal-poller last-chance read can fall back to it before declaring
107
+ // timeout. Always rooted at <project>/.claude/ralph-desk/memos/, even
108
+ // when RLP_DESK_RUNTIME_DIR overrides the canonical deskRoot.
109
+ legacyVerdictFile: path.join(rootDir, '.claude', 'ralph-desk', 'memos', `${slug}-verify-verdict.json`),
79
110
  blockedSentinel: path.join(deskRoot, 'memos', `${slug}-blocked.md`),
80
111
  completeSentinel: path.join(deskRoot, 'memos', `${slug}-complete.md`),
81
112
  contextFile: path.join(deskRoot, 'context', `${slug}-latest.md`),
@@ -376,6 +407,11 @@ async function dispatchVerifier({
376
407
  verifyMode: 'per-us',
377
408
  usId,
378
409
  verifiedUs: state.verified_us,
410
+ // v0.14.2 Fix-E: hand the absolute canonical verdict path to the
411
+ // verifier prompt. assembleVerifierPrompt appends a "CRITICAL: write
412
+ // verdict to <path>" footer so codex does not infer the legacy
413
+ // .claude/ralph-desk/memos/ location from CWD.
414
+ verdictWritePath: paths.verdictFile,
379
415
  });
380
416
  const fileName = suffix
381
417
  ? `${suffix}.verifier-prompt.md`
@@ -1452,7 +1488,21 @@ async function _runCampaignBody(slug, options, paths, rootDir) {
1452
1488
  mode: parseModelFlag(verifierModel, 'verifier').engine,
1453
1489
  paneId: state.verifier_pane_id,
1454
1490
  timeoutMs: iterTimeoutMs,
1491
+ // v0.14.2 Fix-D: codex sometimes writes the verdict at the legacy
1492
+ // .claude/ralph-desk/memos/ path. signal-poller's last-chance read
1493
+ // tries this fallback before timing out.
1494
+ legacySignalFile: paths.legacyVerdictFile,
1455
1495
  });
1496
+ // v0.14.2 Fix-D continued: if the verdict came from the legacy path,
1497
+ // migrate it into the canonical location so the rest of the pipeline
1498
+ // (analytics archival, sentinels, status) sees a single canonical
1499
+ // file. Best-effort — any rename failure is logged but does not
1500
+ // re-throw because we already have the parsed verdict in memory.
1501
+ if (await _verdictMigrationNeeded(paths)) {
1502
+ await _migrateLegacyVerdict(paths).catch((migrateErr) => {
1503
+ console.error('[v0.14.2] legacy verdict migration failed:', migrateErr?.message ?? migrateErr);
1504
+ });
1505
+ }
1456
1506
  validateArtifact(verdict, {
1457
1507
  expectedSlug: slug,
1458
1508
  iterationFloor: state.iteration,
@@ -36,13 +36,22 @@ const DEFAULT_NO_RE = /\[y\/N\]|\(yes\/no,\s*default\s+no\)|[Dd]efault[: ]+[Nn]o
36
36
  const ACTIVE_TASK_RE =
37
37
  /esc to interrupt|background terminal running|^\s*[·✻]\s+[A-Za-z]+(\.{3}|…)/m;
38
38
 
39
- // v0.14.1: codex post-work idle UI markers. NOT a permission prompt — the
40
- // codex CLI has finished its task and is waiting for the next user input.
41
- // Pattern: a divider line "─ Worked for Xm Ys ─", a "› " input prompt, and
42
- // a status bar "Context X% left". Sources: BOS Bug Report #3 (2026-05-04).
39
+ // v0.14.1 / v0.14.2: codex post-work idle UI markers. NOT a permission prompt
40
+ // — the codex CLI has finished its task and is waiting for the next user
41
+ // input. Sources: BOS Bug Report #3 (2026-05-04) + #4 (2026-05-05).
43
42
  // Treat this as "task done, idle awaiting input"; callers should harvest
44
43
  // the verdict file rather than escalate as `prompt_blocked`.
45
- export const CODEX_IDLE_RE = /─\s*Worked for \d+m \d+s\s*─|Context \d+%\s*left/;
44
+ //
45
+ // v0.14.2 relaxation (Bug #4): the v0.14.1 strict "─ Worked for Xm Ys ─"
46
+ // regex required the surrounding horizontal rule to match. tmux capture
47
+ // truncation occasionally dropped those rules so the pattern missed in
48
+ // production. Match on multiple independent markers; ANY one is enough.
49
+ // 1. "Worked for Xm Ys" — duration line, codex-only
50
+ // 2. "Context X%left" (no space) — status bar; tolerate wrap removal
51
+ // 3. "gpt-X.Y reasoning · branch" — codex idle status line
52
+ // 4. codex default suggestions — only printed at the idle prompt
53
+ export const CODEX_IDLE_RE =
54
+ /Worked for \d+m \d+s|Context \d+%\s*left|gpt-\d+(\.\d+)? (low|medium|high|xhigh) ·|Improve documentation in @|Summarize recent commits|Explain (this )?code/;
46
55
  export function isCodexIdleUi(paneText) {
47
56
  if (typeof paneText !== 'string' || paneText.length === 0) return false;
48
57
  return CODEX_IDLE_RE.test(paneText);