@ai-dev-methodologies/rlp-desk 0.15.2 → 0.15.4
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/CHANGELOG.md +82 -0
- package/README.md +34 -4
- package/docs/rlp-desk/failure-modes.md +191 -0
- package/package.json +3 -2
- package/src/node/runner/campaign-main-loop.mjs +174 -11
- package/src/node/util/debug-log.mjs +10 -6
- package/src/node/util/lifecycle-metrics.mjs +102 -0
- package/src/scripts/lib_ralph_desk.zsh +141 -0
- package/src/scripts/run_ralph_desk.zsh +44 -0
- package/docs/plans/bug-report-overhaul-backlog.md +0 -49
- package/docs/plans/bug-report-overhaul-v0.md +0 -238
- package/docs/plans/bug-report-overhaul-v1.md +0 -319
- package/docs/plans/native-agent-revert.md +0 -184
- package/docs/plans/polished-gliding-toucan.md +0 -234
- package/docs/plans/spicy-booping-galaxy.md +0 -717
- package/docs/plans/strategic-review/rlp-desk-strategic-review.md +0 -125
- package/docs/plans/v0.15-stabilization-plan.md +0 -178
|
@@ -1,717 +0,0 @@
|
|
|
1
|
-
# Plan — v0.14.1: Codex verifier idle을 frozen으로 오인하는 버그 수정
|
|
2
|
-
|
|
3
|
-
> **상위 우선순위 plan. 아래 v0.14.0 / v0.13.0 plan은 history reference로 보존.**
|
|
4
|
-
> **Trigger**: BOS Bug Report #3 (`/Users/kyjin/dev/doul/bos/docs/exec-plans/active/2026-05-04-rlp-desk-bug-report-3-verifier-noprogress.md`). codex verifier가 verdict 작성 + idle UI ("Worked for 5m 36s ──") 표시 시 main polling loop의 byte-stasis 감지가 BLOCKED 판정 → 8 iter (148분) 손실.
|
|
5
|
-
> **Target version**: 0.14.1
|
|
6
|
-
> **Severity**: HIGH (Bug Report 분류).
|
|
7
|
-
> **승인된 strategy**: 작성 시점 미정 — 본 plan에 단일 추천안 명시 후 ExitPlanMode로 승인 요청.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## A. Context (v0.14.1)
|
|
12
|
-
|
|
13
|
-
### 문제 진단
|
|
14
|
-
|
|
15
|
-
v0.14.0 출시 후 production path는 `--mode tmux` → `run_ralph_desk.zsh`. 신규 버그는 zsh runner의 main polling loop에서 발생. codex (gpt-5.5:high) verifier가:
|
|
16
|
-
1. verdict file (`<slug>-verify-verdict.json`) 작성 완료
|
|
17
|
-
2. 이후 codex CLI는 다음 입력 대기 idle UI 노출 (예: `─ Worked for 5m 36s ──`, `› Summarize recent commits`, `gpt-5.5 high · Context 66% left`)
|
|
18
|
-
3. pane content 변화 0 → main loop의 `check_no_progress()` (`PROGRESS_NO_CHANGE_TIMEOUT`=600s) 가 frozen 판정
|
|
19
|
-
4. BLOCKED `verifier_dead` (sentinel category=infra_failure) 작성 → 캠페인 종료
|
|
20
|
-
5. **그 시점에 verdict 파일은 이미 디스크에 있었다** → 무효화
|
|
21
|
-
|
|
22
|
-
Node leader (`--mode agent`, alpha)에도 동일 결함 존재: `signal-poller.mjs` + `prompt-dismisser.mjs` 에 codex idle 패턴 부재. agent mode에서 동일 시나리오로 회귀할 위험.
|
|
23
|
-
|
|
24
|
-
### Explore 결과 (file:line 인용)
|
|
25
|
-
|
|
26
|
-
**zsh runner**:
|
|
27
|
-
- `src/scripts/run_ralph_desk.zsh:1437-1469` — `check_no_progress()`. `PROGRESS_NO_CHANGE_TIMEOUT` (기본 600s) 이상 pane bytes 동일 시 BLOCKED. **verdict 파일 검사 없음**.
|
|
28
|
-
- `src/scripts/run_ralph_desk.zsh:1407-1427` — `check_prompt_stall()`. `_PROMPT_RE`/`_AFFORDANCE_RE` 만 검사. **codex idle UI 패턴 없음**.
|
|
29
|
-
- `src/scripts/run_ralph_desk.zsh:2185, 2194` — main polling loop가 `check_prompt_stall` + `check_no_progress` 호출.
|
|
30
|
-
- `src/scripts/run_ralph_desk.zsh:2269-2306` — codex verifier 전용 폴링: verdict file 존재 + jq valid JSON + 30s grace + `pane_current_command` shell-back 조기 종료. **그러나 위 main loop의 check_no_progress 가 동시에 작동 → 동일 600s에 BLOCKED 발화**.
|
|
31
|
-
- `src/scripts/run_ralph_desk.zsh:272` — `VERDICT_FILE="${MEMOS_DIR}/${SLUG}-verify-verdict.json"`.
|
|
32
|
-
- `src/scripts/run_ralph_desk.zsh:382, 407, 536, 560` — codex working/thinking/Exploring/Running/reading/searching/editing/writing 키워드만 인식. idle UI 미인식.
|
|
33
|
-
|
|
34
|
-
**Node leader**:
|
|
35
|
-
- `src/node/runner/prompt-dismisser.mjs:18-26` — `PROMPT_RE` + `AFFORDANCE_RE` 모두 claude 패턴. codex 특화 0개.
|
|
36
|
-
- `src/node/runner/prompt-detector.mjs:6-11` — claude permission 시그니처 only.
|
|
37
|
-
- `src/node/polling/signal-poller.mjs:118-242` — `pollForSignal(signalFile, { timeoutMs })`. signal file이 없고 pane이 shell로 돌아오면 `WorkerExitedError`. timeout 시 `TimeoutError`. **verdict 파일 자체를 polling 함** — verdict 작성 후 codex idle 시점이라도 file 존재하면 즉시 반환. 즉 Bug #3는 Node 측에서는 발생 빈도가 낮으나, 여전히 timeout 600s 안에 read 못하면 (예: codex가 verdict를 매우 늦게 atomic-write) 회귀 가능.
|
|
38
|
-
- `src/node/runner/campaign-main-loop.mjs:1449-1468` — verifier 폴링 호출.
|
|
39
|
-
- `src/node/runner/campaign-main-loop.mjs:471-501` — `BLOCK_TAGS`: `VERIFIER_TIMEOUT`, `VERIFIER_EXITED`, `PROMPT_BLOCKED`, `PERMISSION_PROMPT`.
|
|
40
|
-
|
|
41
|
-
### 핵심 결정 (추천안)
|
|
42
|
-
|
|
43
|
-
**zsh runner와 Node leader 양쪽에 "verdict file 우선 검사 + codex idle UI 인식" 이중 방어 추가**. Bug Report Fix-A + Fix-B를 두 경로에 적용. Fix-C(`--verifier-noprogress-timeout` 옵션 분리)는 보류 — Fix-A 가 효력을 보이면 불필요.
|
|
44
|
-
|
|
45
|
-
**근거**:
|
|
46
|
-
- v0.14.0 production path(zsh)에서 즉시 효과. BOS 캠페인 즉시 회복.
|
|
47
|
-
- agent mode(Node, alpha)에도 같은 회귀 위험이 있으므로 동시 적용으로 일관된 contract.
|
|
48
|
-
- 1줄 추가(verdict file 존재 검사)는 risk가 매우 낮고, codex idle 패턴 추가는 기존 패턴 정규식에 alternation 추가로 끝.
|
|
49
|
-
- workaround W1(`--verifier-model sonnet`) 은 BOS 권고이지만 codex consensus 가치를 깎으므로 fix 가 우선.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## B. Approach (5 Phases)
|
|
54
|
-
|
|
55
|
-
### Phase 1 — zsh: verdict-aware no-progress + codex idle 인식 (Day 1, 2시간)
|
|
56
|
-
|
|
57
|
-
**파일**: `src/scripts/run_ralph_desk.zsh`
|
|
58
|
-
|
|
59
|
-
1. **`check_no_progress()` (L1437-1469)** 진입부에 verdict-aware short-circuit 추가:
|
|
60
|
-
```zsh
|
|
61
|
-
check_no_progress() {
|
|
62
|
-
# v0.14.1 Fix-A: codex verifier가 verdict 작성 후 idle UI 노출 시 byte-stasis가
|
|
63
|
-
# frozen으로 오인됨. main verdict 파일이 이미 valid JSON이면 verifier는 done이며
|
|
64
|
-
# main loop 다음 phase(verdict 수확)가 처리해야 한다 — frozen으로 분류 X.
|
|
65
|
-
if [[ "${PHASE:-}" == "verifier" || "${PHASE:-}" == "final_verifier" ]] \
|
|
66
|
-
&& [[ -f "$VERDICT_FILE" ]] \
|
|
67
|
-
&& jq -e . "$VERDICT_FILE" >/dev/null 2>&1; then
|
|
68
|
-
return 0 # verdict already written; let polling loop harvest
|
|
69
|
-
fi
|
|
70
|
-
# ... 기존 byte-stasis 로직 유지 ...
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
- `PHASE` 변수 노출이 미흡하면, current pane id 가 `$VERIFIER_PANE` 인지로 분기.
|
|
74
|
-
- consensus 모드에서는 `${SLUG}-verify-verdict-claude.json` / `${SLUG}-verify-verdict-codex.json` 도 OR 검사.
|
|
75
|
-
|
|
76
|
-
2. **`check_prompt_stall()` (L1407-1427)** 의 `_PROMPT_RE` / `_AFFORDANCE_RE` 에 codex idle 패턴 추가하지 **않는다** — 이건 prompt가 아니라 idle 상태. 대신 신규 helper `is_codex_idle_ui()` 추가:
|
|
77
|
-
```zsh
|
|
78
|
-
is_codex_idle_ui() {
|
|
79
|
-
local pane_text="$1"
|
|
80
|
-
# codex post-work idle: "─ Worked for Xm Ys ──", "› " prefix, "Context X% left"
|
|
81
|
-
echo "$pane_text" | grep -qE '─ Worked for [0-9]+m [0-9]+s ─' \
|
|
82
|
-
|| echo "$pane_text" | grep -qE 'Context [0-9]+% left'
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
`check_no_progress()` 의 byte-stasis 단계에서 verdict file이 부재해도 codex idle UI 가 감지되면 추가 grace 기간(예: +120s) 부여. timeout 사용자에게 명확하도록 stderr 노티스 1회.
|
|
86
|
-
|
|
87
|
-
3. **codex verifier 폴링 (L2269-2306)** 의 grace period(현재 30s) 는 그대로 유지.
|
|
88
|
-
|
|
89
|
-
### Phase 2 — Node leader: signal-poller + prompt-dismisser 보강 (Day 1, 3시간)
|
|
90
|
-
|
|
91
|
-
**파일**: `src/node/runner/prompt-dismisser.mjs`, `src/node/polling/signal-poller.mjs`, `src/node/runner/prompt-detector.mjs`
|
|
92
|
-
|
|
93
|
-
1. **`prompt-dismisser.mjs`** 에 codex idle 인식용 정규식 분리 (기존 PROMPT_RE 와는 다른 카테고리):
|
|
94
|
-
```js
|
|
95
|
-
// v0.14.1: codex post-work idle UI markers. Not a permission prompt — work
|
|
96
|
-
// is done; the CLI is just waiting for next user input. Emitting these as
|
|
97
|
-
// "prompt blocked" would be wrong — the right response is to let the
|
|
98
|
-
// verifier-side polling harvest the already-written verdict file.
|
|
99
|
-
export const CODEX_IDLE_RE = /─\s*Worked for \d+m \d+s\s*─|Context \d+% left/;
|
|
100
|
-
export function isCodexIdleUi(paneText) { return CODEX_IDLE_RE.test(paneText); }
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
2. **`signal-poller.mjs`** (L210-226 의 pane-shell-back 분기 직전):
|
|
104
|
-
```js
|
|
105
|
-
// v0.14.1 Fix-A: re-read signal file once more before declaring exit.
|
|
106
|
-
// Codex may write verdict + return to idle UI almost simultaneously; if
|
|
107
|
-
// the verdict landed on disk after our last readFile, we must not
|
|
108
|
-
// misclassify the idle UI as WorkerExited.
|
|
109
|
-
try {
|
|
110
|
-
const raw = await readFile(signalFile, 'utf8');
|
|
111
|
-
const parsed = JSON.parse(raw);
|
|
112
|
-
return parsed;
|
|
113
|
-
} catch { /* still missing — fall through to existing exit logic */ }
|
|
114
|
-
```
|
|
115
|
-
현재 코드 패스가 이미 readFile loop을 하므로 차이가 작을 수 있음 — 정확한 위치는 구현 시점에 결정.
|
|
116
|
-
|
|
117
|
-
3. **timeout 직전(L deadline 비교)** 에 verdict file 마지막 점검 추가 (Bug Report Fix-A 의 Node 버전):
|
|
118
|
-
```js
|
|
119
|
-
if (Date.now() >= deadline) {
|
|
120
|
-
try {
|
|
121
|
-
const last = await readFile(signalFile, 'utf8');
|
|
122
|
-
return JSON.parse(last);
|
|
123
|
-
} catch {}
|
|
124
|
-
throw new TimeoutError(`Timed out waiting for valid JSON signal at ${signalFile}`);
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
4. **`prompt-detector.mjs`** 는 권한 프롬프트 전용이므로 codex idle 추가하지 않음. 대신 `prompt-dismisser` 의 `isCodexIdleUi()` 를 signal-poller가 임포트해서, codex mode 일 때 idle 감지 시 deadline 연장 (예: 마지막 byte-change 가 600s 전이라도 idle UI 보이면 추가 120s grace).
|
|
129
|
-
|
|
130
|
-
### Phase 3 — 테스트 (Day 1, 3시간)
|
|
131
|
-
|
|
132
|
-
**신규 / 갱신**:
|
|
133
|
-
- `tests/node/test-prompt-dismisser.mjs` (신규 또는 기존 보강): `CODEX_IDLE_RE` / `isCodexIdleUi()` 단위 테스트. 실제 BOS 캡처 텍스트 fixture로 verify (≥3 케이스).
|
|
134
|
-
- `tests/node/test-signal-poller.mjs` (us003 보강): "verdict written between polls + codex idle UI" 시나리오 — `readFile` mock이 첫 호출 ENOENT, 두 번째 호출 valid JSON 반환. timeout 직전 last-read 가 verdict 회수하는지 검증.
|
|
135
|
-
- `tests/node/us003-signal-poller.test.mjs` 의 기존 flake 테스트는 손대지 않음(타이밍 flake — 별도 이슈).
|
|
136
|
-
- zsh 측: `tests/test_us0XX_codex_idle_no_progress.sh` 신규. mock pane 텍스트 + verdict file 시뮬레이션으로 `check_no_progress()` 가 `PHASE=verifier && verdict exists` 일 때 BLOCKED 발화 안 함을 zsh 단위 테스트로 검증.
|
|
137
|
-
|
|
138
|
-
### Phase 4 — SV gate 갱신 (Day 1, 1시간)
|
|
139
|
-
|
|
140
|
-
**파일**: `tests/sv-self-verify-0.14.sh` 보강 또는 신규 `tests/sv-self-verify-0.14.1.sh`.
|
|
141
|
-
신규 시나리오:
|
|
142
|
-
- L6.1 (CRITICAL) verdict-aware no-progress: `PHASE=verifier` + verdict 파일 존재 시 `check_no_progress` 가 BLOCKED 안 함.
|
|
143
|
-
- L6.2 (CRITICAL) Node `signal-poller` last-chance verdict read: deadline 직전 verdict 작성된 경우 timeout 대신 verdict 반환.
|
|
144
|
-
- L6.3 (MEDIUM) `isCodexIdleUi()` 단위 테스트 PASS.
|
|
145
|
-
- L6.4 (MEDIUM) BOS-shape 캡처 텍스트("Worked for 5m 36s ──", "Context 66% left") 가 idle 로 인식.
|
|
146
|
-
- L6.5 (LOW) v0.14.0 contract 회귀 가드: `--mode tmux` 가 여전히 zsh subprocess로 위임 (us008 happy 재실행).
|
|
147
|
-
|
|
148
|
-
### Phase 5 — Ship (Day 2, CLAUDE.md mandate)
|
|
149
|
-
|
|
150
|
-
1. self-verification gate 100% PASS.
|
|
151
|
-
2. ralplan + codex review (governance.md 변경 없으면 ralplan 생략 가능, 단 governance docs에 codex idle 인식 정책 1단락 추가 시 mandatory).
|
|
152
|
-
3. version bump 0.14.1.
|
|
153
|
-
4. CHANGELOG: "Fix codex verifier idle UI being mis-classified as no-progress; verdict-aware short-circuit in zsh runner; symmetric guard in Node signal-poller (agent mode)."
|
|
154
|
-
5. commit + push + gh release + npm publish (각 단계 사용자 승인 필수, CLAUDE.md `Commit & Publish Gate`).
|
|
155
|
-
6. local sync banner-aware verify.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## C. v0.14.0 / v0.13.x 에서 보존되는 것
|
|
160
|
-
|
|
161
|
-
- v0.14.0 routing contract: `--mode tmux` → zsh subprocess. 변경 없음.
|
|
162
|
-
- v0.13.0 path migration (`.rlp-desk/`).
|
|
163
|
-
- v0.13.0 prompt-detector + signal-poller permission_prompt 감지.
|
|
164
|
-
- v0.13.1 detached vs attached tmux 분기.
|
|
165
|
-
- agent-mode alpha 라벨링.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## D. Critical Files
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
src/scripts/run_ralph_desk.zsh # Phase 1 — verdict-aware check_no_progress + is_codex_idle_ui()
|
|
173
|
-
src/node/runner/prompt-dismisser.mjs # Phase 2 — CODEX_IDLE_RE + isCodexIdleUi()
|
|
174
|
-
src/node/polling/signal-poller.mjs # Phase 2 — last-chance verdict read on deadline + idle-UI grace
|
|
175
|
-
src/node/runner/prompt-detector.mjs # Phase 2 — 변경 없음 (확인용)
|
|
176
|
-
src/node/runner/campaign-main-loop.mjs # 변경 거의 없음 — pollForSignal 호출은 그대로
|
|
177
|
-
tests/node/test-prompt-dismisser.mjs # 신규/보강
|
|
178
|
-
tests/node/test-signal-poller.mjs # 보강
|
|
179
|
-
tests/test_us0XX_codex_idle_no_progress.sh # 신규 (zsh 단위)
|
|
180
|
-
tests/sv-self-verify-0.14.1.sh 또는 0.14.sh 보강 # Phase 4
|
|
181
|
-
package.json # Phase 5 — 0.14.1
|
|
182
|
-
docs/plans/spicy-booping-galaxy.md # 본 파일 (최종 plan 기록)
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Verdict file paths (참조 only, 변경 없음):
|
|
186
|
-
- zsh: `${MEMOS_DIR}/${SLUG}-verify-verdict.json` (run_ralph_desk.zsh:272)
|
|
187
|
-
- Node: `paths.verdictFile = <deskRoot>/memos/<slug>-verify-verdict.json` (campaign-main-loop.mjs:78)
|
|
188
|
-
- Consensus: `<slug>-verify-verdict-claude.json`, `<slug>-verify-verdict-codex.json`
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## E. Verification (E2E)
|
|
193
|
-
|
|
194
|
-
1. **BOS 회귀 (CRITICAL)**: BOS Phase 1 캠페인을 `--worker-model sonnet --verifier-model gpt-5.5:high --consensus final-only` 로 재실행. US-003 verifier idle UI 시점에 BLOCKED 발화 안 함, verdict 정상 회수 + 다음 iteration 진입 확인.
|
|
195
|
-
2. **Local sync**: `node scripts/postinstall.js` 후 banner-aware diff 0 mismatches.
|
|
196
|
-
3. **Backward compat**:
|
|
197
|
-
- claude-only verifier (`--verifier-model opus`): 회귀 없음 — verdict-aware short-circuit 도 trip 안 함 (claude는 idle 시점 자체가 다름).
|
|
198
|
-
- `--mode agent`: 동일 fix 적용으로 회귀 없음.
|
|
199
|
-
- 진짜 frozen worker (verdict 부재 + pane 600s 변화 없음): 기존대로 BLOCKED 정상 발화.
|
|
200
|
-
4. **Inspection 세션 정리**: 사용자 측 `tmux session doul-bos-583` (worker pane `%1681` node 잔존) 은 이번 fix 이후 재실행 결정 시점에 사용자가 직접 정리 (`/rlp-desk clean <slug> --kill-session`).
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## F. 기각된 대안
|
|
205
|
-
|
|
206
|
-
- **Fix-C (`--verifier-noprogress-timeout` 옵션 분리)**: 사용자 노출 surface 증가. Fix-A 가 효력을 보이면 불필요. v0.15.0+ 에 별도 백로그.
|
|
207
|
-
- **Workaround W1 영구화 (claude 만 verifier 허용)**: codex consensus 가치 손실. 부정.
|
|
208
|
-
- **agent mode 만 fix, tmux 보류**: production path 가 zsh이므로 BOS 회복 안 됨. 부정.
|
|
209
|
-
- **prompt-detector에 codex idle 추가**: detector 는 permission prompt 전용 — 의미적으로 어긋남. dismisser쪽 분리 함수가 정확.
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## G. Risk Notes
|
|
214
|
-
|
|
215
|
-
- `PHASE` 변수가 zsh runner 전반에 노출되어 있는지 확인 필요(미노출 시 verifier pane id로 분기 변경).
|
|
216
|
-
- consensus 모드 시 두 verdict 파일 OR 검사로 단일 verifier 작성된 시점에 short-circuit 안 됨 — 이 부분은 Phase 1 구현 시 명시 검증.
|
|
217
|
-
- `jq -e .` 검사가 codex가 partial-write 중인 verdict (파일 생성 후 atomic mv 전)에 false-positive 안 발생하는지 확인 — `atomic_write` 가 이미 mv 사용이라 안전.
|
|
218
|
-
- Node 측 last-chance read 가 race condition (deadline 전 다른 thread가 file 작성) 에서도 동작하는지 확인 — fs.readFile 은 atomic 이므로 OK.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
# v0.14.0 (HISTORY) — Restore zsh as primary tmux runner
|
|
223
|
-
|
|
224
|
-
> **Status**: SHIPPED 2026-05-03. v0.14.0 npm + GitHub release 게시 완료.
|
|
225
|
-
> **상위 우선순위 plan. 아래 v0.13.0 plan은 history reference로 보존.**
|
|
226
|
-
> **Trigger**: 사용자 평가 — "rlp-desk가 못 쓸 폐급, 통제 불가능 수준". v0.13.0/v0.13.1 fix는 빙산 일각.
|
|
227
|
-
> **Target version**: 0.14.0
|
|
228
|
-
> **승인된 strategy**: 경로 A (zsh restoration as tmux primary; Node leader는 `--mode agent`만 담당)
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
## A. Context (v0.14.0)
|
|
233
|
-
|
|
234
|
-
### 문제 진단
|
|
235
|
-
|
|
236
|
-
2026-04-12 Node port 시점부터 v0.13.x까지, **Node leader가 zsh runner의 핵심 안전망 11개를 누락한 채 ship**. 사용자(BOS) 평가는 "통제 불가능". v0.13.0/v0.13.1 patch는 다음 2개만 해결:
|
|
237
|
-
1. `.claude/` sensitive prompt hang
|
|
238
|
-
2. detached session UX 회귀
|
|
239
|
-
|
|
240
|
-
**여전히 누락된 것** (file:line 인용):
|
|
241
|
-
| # | 기능 | zsh location | Node 상태 |
|
|
242
|
-
|---|------|--------------|----------|
|
|
243
|
-
| 1 | Copy-mode 가드 send-keys | `safe_send_keys` L976-1083 | 없음 (pane-manager.mjs:50-53 단순 send-keys) |
|
|
244
|
-
| 2 | Heartbeat 주기적 쓰기 + staleness 감지 | L1735-1750, L1158 | 없음 |
|
|
245
|
-
| 3 | No-progress 10분 byte-stasis 감지 | `check_no_progress` L2372-2420 | 없음 |
|
|
246
|
-
| 4 | Prompt-stall 5분 timeout | `check_prompt_stall` L2298-2370 | 없음 |
|
|
247
|
-
| 5 | Stale-context 3 consecutive unchanged iter | `check_stale_context` lib L1162-1179 | 없음 |
|
|
248
|
-
| 6 | Claude 모델 upgrade chain (haiku→sonnet→opus) | `get_next_model` lib L136-155 | 없음 (codex chain만) |
|
|
249
|
-
| 7 | LOCK_WORKER_MODEL flag 처리 | lib L197 | 없음 |
|
|
250
|
-
| 8 | Codex update prompt auto-dismiss | L1007-1011 | 없음 |
|
|
251
|
-
| 9 | Pane lifecycle cleanup | `cleanup_panes` L3310-3320 | 없음 (`_ensureTerminalSentinel` 부분만) |
|
|
252
|
-
| 10 | 사용자 pane-kill graceful detection | L134-150 | 없음 |
|
|
253
|
-
| 11 | Cleanup trap (C-c → /exit → kill-pane) | L1864-2014 | 부분만 |
|
|
254
|
-
|
|
255
|
-
### 핵심 결정
|
|
256
|
-
|
|
257
|
-
**zsh를 tmux mode primary path로 복원, Node leader는 `--mode agent`(LLM-driven orchestration) 단독 담당**.
|
|
258
|
-
|
|
259
|
-
**근거**:
|
|
260
|
-
- zsh runner는 v0.12.0 deprecation 전까지 6주+ production 검증.
|
|
261
|
-
- Node port의 누락 11개를 모두 port = 5-7일 + 새 회귀 위험. zsh 복원 = 1-2일 + 검증된 코드.
|
|
262
|
-
- Node leader는 LLM이 worker/verifier를 spawn하는 agent mode에 고유 가치 — tmux 기계적 orchestration은 zsh가 더 잘함.
|
|
263
|
-
- 사용자 즉시 회복 우선.
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## B. Approach (6 Phases)
|
|
268
|
-
|
|
269
|
-
### Phase 1 — zsh deprecation 게이트 해제 (Day 1, 2시간)
|
|
270
|
-
|
|
271
|
-
**파일**: `src/scripts/run_ralph_desk.zsh`
|
|
272
|
-
- L69-90: `--flywheel`/`--with-self-verification`/`--flywheel-guard` hard-reject 블록 제거. zsh가 이 flag들을 다시 honor.
|
|
273
|
-
- L91 "deprecated" 메시지 제거.
|
|
274
|
-
- `RALPH_DESK_VERSION` 0.14.0으로 갱신.
|
|
275
|
-
|
|
276
|
-
### Phase 2 — Node `--mode tmux` → zsh subprocess 라우팅 (Day 1, 4시간)
|
|
277
|
-
|
|
278
|
-
**파일**: `src/node/run.mjs`
|
|
279
|
-
- `parseRunOptions()` 후 `runRunCommand()` 진입에서 `mode === 'tmux'` 분기:
|
|
280
|
-
- `~/.claude/ralph-desk/run_ralph_desk.zsh` 경로 확인 (postinstall이 sync 보장).
|
|
281
|
-
- 모든 옵션을 env vars로 변환 (`LOOP_NAME`, `WORKER_MODEL`, `FLYWHEEL`, `FLYWHEEL_GUARD`, `WITH_SELF_VERIFICATION`, `MAX_ITER`, `ITER_TIMEOUT`, `CB_THRESHOLD`, `CONSENSUS_*`, `LOCK_WORKER_MODEL` 등).
|
|
282
|
-
- `child_process.spawn('zsh', [zshPath], { env, stdio: 'inherit' })`로 위임.
|
|
283
|
-
- exit code 그대로 propagate.
|
|
284
|
-
- legacy detection (`detectLegacyDeskInRunMode`) 호출은 zsh spawn 전에 유지.
|
|
285
|
-
- claude+tmux warning 유지 (zsh도 같은 worker engine 분기에서 적용).
|
|
286
|
-
|
|
287
|
-
**파일**: `src/node/runner/campaign-main-loop.mjs`
|
|
288
|
-
- `run()` 진입에서 `options.mode === 'tmux'`일 때 가드: `throw new Error('tmux mode is delegated to zsh — invoke via run.mjs router')`. dead-code 표시 + 회귀 방지.
|
|
289
|
-
|
|
290
|
-
### Phase 3 — postinstall + install.sh가 zsh를 항상 sync (Day 1, 1시간)
|
|
291
|
-
|
|
292
|
-
**파일**: `scripts/postinstall.js`
|
|
293
|
-
- 현재 `legacyFiles` 배열로 zsh 3개 삭제 → **유지·sync로 변경**.
|
|
294
|
-
- `runtimeSources`에 `src/scripts/{init,run,lib}_ralph_desk.zsh` → `~/.claude/ralph-desk/` 추가 (또는 `scripts/` 하위 — install.sh와 일관성 결정).
|
|
295
|
-
- banner-aware sync.
|
|
296
|
-
|
|
297
|
-
**파일**: `tests/node/us008-cli-entrypoint.test.mjs:47`
|
|
298
|
-
- 기존 "removes legacy zsh scripts" 테스트 invert: zsh 3개가 install 후 존재 + spawnable 검증.
|
|
299
|
-
|
|
300
|
-
### Phase 4 — Node `--mode agent` 라벨링 (Day 2, 2시간)
|
|
301
|
-
|
|
302
|
-
**파일**: `src/node/run.mjs`, `src/commands/rlp-desk.md`, `README.md`
|
|
303
|
-
- `--mode agent` 진입 시 stderr 경고: "agent mode is alpha — for production use --mode tmux".
|
|
304
|
-
- README mode 표:
|
|
305
|
-
- `tmux` (stable, zsh-backed)
|
|
306
|
-
- `agent` (alpha, Node-native)
|
|
307
|
-
- v0.13.0/0.13.1 fix(`.claude/` 마이그레이션, prompt-detector, claude+tmux warning)는 모두 agent mode에서 잔존 — Node 단독 가치 보존.
|
|
308
|
-
|
|
309
|
-
### Phase 5 — 검증 시나리오 (Day 2-3, 1일)
|
|
310
|
-
|
|
311
|
-
**Self-verification gate (`tests/sv-self-verify-0.14.sh`)** — v0.13 시나리오 + 신규:
|
|
312
|
-
- L5.1 (CRITICAL) BOS 회귀: claude worker + tmux mode + 1 iter 완주 (실제 tmux session 생성, kill-session cleanup).
|
|
313
|
-
- L5.2 (CRITICAL) zsh subprocess routing: `--mode tmux` 호출이 `child_process.spawn('zsh', ...)`로 위임됐는지 mock 검증.
|
|
314
|
-
- L5.3 (CRITICAL) flag → env var conversion 단언 (모든 supported flag 1개씩).
|
|
315
|
-
- L5.4 (MEDIUM) zsh deprecation 게이트 제거 검증 (L69-90).
|
|
316
|
-
- L5.5 (MEDIUM) postinstall이 zsh 3개를 install (us008 invert).
|
|
317
|
-
- L5.6 (MEDIUM) `--mode agent` warning 출력 검증.
|
|
318
|
-
- L5.7 (UX) attached tmux 안에서 leader+worker+verifier panes 사용자 현재 window에 표시 (zsh L815-823 동작).
|
|
319
|
-
|
|
320
|
-
### Phase 6 — Ship (Day 3)
|
|
321
|
-
|
|
322
|
-
CLAUDE.md release workflow 그대로:
|
|
323
|
-
1. self-verification gate 17/17 PASS.
|
|
324
|
-
2. ralplan + codex review (기존 mandate).
|
|
325
|
-
3. version bump 0.14.0.
|
|
326
|
-
4. CHANGELOG: "Restore zsh as primary tmux runner. Node tmux delegates to validated zsh codepath. Node agent mode marked alpha."
|
|
327
|
-
5. commit + push + gh release + npm publish.
|
|
328
|
-
6. local sync banner-aware verify.
|
|
329
|
-
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
## C. v0.13.x에서 보존되는 것
|
|
333
|
-
|
|
334
|
-
- `.claude/ralph-desk/` → `.rlp-desk/` 경로 마이그레이션 (init mode auto-mv, run mode 안내) — zsh도 v0.13.0에서 이미 반영됨.
|
|
335
|
-
- `RLP_DESK_RUNTIME_DIR` env override.
|
|
336
|
-
- prompt-detector + signal-poller permission_prompt 감지 — agent mode 전용.
|
|
337
|
-
- BLOCK_TAGS.PERMISSION_PROMPT 상수.
|
|
338
|
-
- claude+tmux warning (run.mjs 진입 시).
|
|
339
|
-
|
|
340
|
-
---
|
|
341
|
-
|
|
342
|
-
## D. v0.15.0+ 점진 port 백로그 (deferred)
|
|
343
|
-
|
|
344
|
-
**P0 (2주 내, agent mode parity 위해)**:
|
|
345
|
-
- heartbeat 메커니즘
|
|
346
|
-
- copy-mode 가드 send-keys
|
|
347
|
-
- prompt-stall 5분 timeout
|
|
348
|
-
- no-progress 10분 byte-stasis
|
|
349
|
-
|
|
350
|
-
**P1**:
|
|
351
|
-
- stale-context 감지
|
|
352
|
-
- claude 모델 upgrade chain (haiku→sonnet→opus)
|
|
353
|
-
- LOCK_WORKER_MODEL flag
|
|
354
|
-
|
|
355
|
-
**P2**:
|
|
356
|
-
- codex update prompt auto-dismiss
|
|
357
|
-
- pane lifecycle cleanup
|
|
358
|
-
- user-kill graceful detection
|
|
359
|
-
- cleanup trap full parity
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
## E. Critical Files
|
|
364
|
-
|
|
365
|
-
```
|
|
366
|
-
src/scripts/run_ralph_desk.zsh # Phase 1 — deprecation 게이트 해제
|
|
367
|
-
src/scripts/lib_ralph_desk.zsh # 변경 없음 (zsh helpers 그대로)
|
|
368
|
-
src/scripts/init_ralph_desk.zsh # 변경 없음 (v0.13.0 마이그레이션 그대로)
|
|
369
|
-
src/node/run.mjs # Phase 2 — tmux mode 라우터
|
|
370
|
-
src/node/runner/campaign-main-loop.mjs # Phase 2 — tmux mode 가드
|
|
371
|
-
scripts/postinstall.js # Phase 3 — zsh sync 복원
|
|
372
|
-
tests/node/us008-cli-entrypoint.test.mjs # Phase 3 — invert
|
|
373
|
-
src/commands/rlp-desk.md # Phase 4 — mode 표 갱신
|
|
374
|
-
README.md # Phase 4 — stable/alpha 표시
|
|
375
|
-
package.json # Phase 6 — 0.14.0
|
|
376
|
-
tests/sv-self-verify-0.14.sh # Phase 5 — 신규 SV gate
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## F. 검증 (E2E)
|
|
382
|
-
|
|
383
|
-
1. **BOS 회귀 (CRITICAL)**: BOS 프로젝트 worktree에서 `node ~/.claude/ralph-desk/node/run.mjs run bos-phase-1 --mode tmux --worker-model sonnet --max-iter 1 --iter-timeout 600` → tmux session 생성, leader/worker/verifier panes 사용자 현재 window에 split, 1 iter sentinel write 성공, no permission prompt hang.
|
|
384
|
-
2. **Local sync**: `npm install` 후 `~/.claude/ralph-desk/run_ralph_desk.zsh` 존재 + banner.
|
|
385
|
-
3. **Backward compat**: v0.13.x mid-campaign 사용자 — `mv .claude/ralph-desk .rlp-desk` 안내 그대로.
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
## G. 기각된 대안
|
|
390
|
-
|
|
391
|
-
- **B (Node 전면 port)**: 11 features × 평균 3시간 + parity 회귀테스트 = 5-7일 + 신규 버그 위험. v0.15.0+ 점진 port로 deferred.
|
|
392
|
-
- **C (단독 라벨링)**: experimental 라벨만으로는 "통제 불가능" 즉시 해소 불가. 경로 A에 흡수.
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
# v0.13.0 (HISTORY) — Claude worker `.claude/` sensitive prompt hang 수정
|
|
397
|
-
|
|
398
|
-
> **Source bug report**: `/Users/kyjin/dev/doul/bos/docs/exec-plans/active/2026-05-01-rlp-desk-bug-report.md`
|
|
399
|
-
> **Severity**: HIGH — `--mode tmux` + `--worker-model sonnet/haiku/opus` 조합에서 모든 campaign blocking
|
|
400
|
-
> **Target version**: 0.13.0 (breaking — project-local sentinel 경로 이동) — **SHIPPED**, but coverage was a sliver of the real failure surface (see v0.14.0 plan above).
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
## 1. Context
|
|
405
|
-
|
|
406
|
-
### 문제
|
|
407
|
-
|
|
408
|
-
`<project>/.claude/ralph-desk/memos/<slug>-done-claim.json` 등 sentinel 작성 시
|
|
409
|
-
Claude Code가 `.claude/` 경로를 self-modification suspect로 hardcoded 처리하여
|
|
410
|
-
permission prompt를 띄움. `--dangerously-skip-permissions`로도 우회 X.
|
|
411
|
-
Worker hang → Leader pollForSignal 30분 timeout → BLOCKED(`infra_failure`).
|
|
412
|
-
|
|
413
|
-
Codex worker(gpt-5.5:* 등)에서는 미발생 — Claude Code의 sensitive 정책 외부.
|
|
414
|
-
즉 **현재는 Claude 계열 worker가 사실상 사용 불가**.
|
|
415
|
-
|
|
416
|
-
### 핵심 결정
|
|
417
|
-
|
|
418
|
-
프로젝트-로컬 runtime 디렉토리를 `<project>/.claude/ralph-desk/`에서
|
|
419
|
-
`<project>/.rlp-desk/`로 이동.
|
|
420
|
-
|
|
421
|
-
**근거**:
|
|
422
|
-
- Claude Code의 sensitive 검사 트리거는 `.claude/` 디렉토리명 자체.
|
|
423
|
-
- 디렉토리명만 바꾸면 회피 (영감 출처 design-desk도 `.claude/` 안에 sentinel을 두지 않음).
|
|
424
|
-
- `~/.claude/ralph-desk/`(설치 위치 + cross-project analytics)는 변경 없음 — Leader가
|
|
425
|
-
자기 자신의 install dir을 self-modify할 일은 없으므로 sensitive 검사 트리거 안 함.
|
|
426
|
-
|
|
427
|
-
### 비-목표
|
|
428
|
-
|
|
429
|
-
- `~/.claude/ralph-desk/` 설치 경로 변경 (registry, analytics, leader binaries 유지)
|
|
430
|
-
- `--mode agent` 폐지 (Fix-1로 자동 해결되므로 그대로 유지)
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
## 2. Approach (3단계)
|
|
435
|
-
|
|
436
|
-
### Phase 1 — Fix-1: 프로젝트-로컬 sentinel 경로 이동 (`.claude/ralph-desk/` → `.rlp-desk/`)
|
|
437
|
-
|
|
438
|
-
**변경 대상 파일** (Explore 결과):
|
|
439
|
-
|
|
440
|
-
| File | 위치 | 변경 내용 |
|
|
441
|
-
|------|------|----------|
|
|
442
|
-
| `src/node/init/campaign-initializer.mjs` | L5, L13 | `GITIGNORE_RULE` + `deskRoot` 상수 |
|
|
443
|
-
| `src/scripts/init_ralph_desk.zsh` | L77, L1091, L1100, L1105-1137 | `DESK` 변수 + permission marker 패턴 |
|
|
444
|
-
| `src/scripts/run_ralph_desk.zsh` | L255 | `DESK` 변수 |
|
|
445
|
-
| `src/scripts/lib_ralph_desk.zsh` | L57 | 홈 디렉토리 변수 주석 명확화 (변경 없음, 주석만) |
|
|
446
|
-
| `src/node/runner/campaign-main-loop.mjs` | L44-80 | 경로 빌드 함수 |
|
|
447
|
-
| `src/commands/rlp-desk.md` | 24개 라인 | 모든 `.claude/ralph-desk/` → `.rlp-desk/` 참조 |
|
|
448
|
-
| `src/governance.md` | 6개 라인 | 경로 문서화 |
|
|
449
|
-
|
|
450
|
-
**유지(변경 없음)**:
|
|
451
|
-
- `src/node/runner/leader-registry.mjs` (홈 디렉토리 `~/.claude/ralph-desk/registry.jsonl`)
|
|
452
|
-
- `install.sh`, `scripts/postinstall.js` (홈 디렉토리 설치)
|
|
453
|
-
|
|
454
|
-
**Worker/Verifier `--add-dir` whitelist**:
|
|
455
|
-
- 기존: `--add-dir "$HOME/.claude/ralph-desk" "$ROOT"` (lib_ralph_desk.zsh:57-58).
|
|
456
|
-
- `$ROOT`가 이미 whitelist이므로 `$ROOT/.rlp-desk`는 **자동 포함** — 별도 추가 불필요.
|
|
457
|
-
- 핵심은 디렉토리명 변경 자체로 sensitive 검사 trigger를 회피하는 것이지, sandbox/permission 변경이 아님.
|
|
458
|
-
|
|
459
|
-
**Runtime dir override (Synthesis — 미래 회피책)**:
|
|
460
|
-
|
|
461
|
-
`deskRoot`를 환경변수 `RLP_DESK_RUNTIME_DIR`로 외부화. 기본값 `.rlp-desk/`. 향후 platform이 또 sensitive 검사를 확장하면 사용자가 즉시 `RLP_DESK_RUNTIME_DIR=.rlp-runtime/` 등으로 우회 가능. P1(don't fight platform)을 코드 단에 영속화.
|
|
462
|
-
|
|
463
|
-
**Migration race-safety (atomic — Codex Critic 반영)**:
|
|
464
|
-
|
|
465
|
-
이 절차는 **init 모드 진입 시에만** 실행. run 모드는 §2 Phase 3 정책대로 자동 mv 수행 안 함(경고 + 수동 안내).
|
|
466
|
-
|
|
467
|
-
- 락 파일 위치: `<project>/.rlp-desk-migration.lock` (target dir 외부 — target dir이 아직 없을 수 있으므로 parent `<project>/`에 둠).
|
|
468
|
-
- 락 획득: `fs.openSync(lockPath, 'wx')` (exclusive create — TOCTOU 없음). 이미 존재하면 "다른 프로세스가 마이그레이션 중" 에러로 즉시 abort.
|
|
469
|
-
- init 모드 마이그레이션 절차 (락 보유 상태):
|
|
470
|
-
1. 양쪽(legacy `.claude/ralph-desk/` + new `.rlp-desk/`) 존재 여부 검사.
|
|
471
|
-
2. 둘 다 존재 → 자동 mv **거부** + 사용자 정리 안내 (pre-mortem #1 binding).
|
|
472
|
-
3. legacy만 존재 → `fs.renameSync(legacy, new)` (원자적, 같은 파일시스템 내).
|
|
473
|
-
4. 둘 다 없음 → noop (정상 init).
|
|
474
|
-
- run 모드(legacy 발견 시): mv 시도하지 않고 비-zero exit + 수동 명령 안내. 진행 중 캠페인 보호.
|
|
475
|
-
- 락 해제: `try/finally`로 `fs.unlinkSync(lockPath)` 보장. 프로세스 crash 시 다음 실행에서 stale 락 감지(mtime > N분) 시 경고 후 제거.
|
|
476
|
-
- `fresh` 모드(`campaign-initializer.mjs:20`의 `fs.rm({recursive:true})`)는 마이그레이션 완료 후 새 경로에서만 실행.
|
|
477
|
-
|
|
478
|
-
### Phase 2 — Fix-2: Claude worker + tmux 조합 경고
|
|
479
|
-
|
|
480
|
-
**위치**: `src/node/cli/command-builder.mjs` (이미 `CLAUDE_MODELS = Set(['haiku','sonnet','opus'])` 존재).
|
|
481
|
-
|
|
482
|
-
**로직**: `parseRunOptions()` (`src/node/run.mjs:101-180`) 파싱 후
|
|
483
|
-
`runRunCommand` 진입 시점에 다음 검증 추가:
|
|
484
|
-
|
|
485
|
-
```js
|
|
486
|
-
// src/node/run.mjs (파싱 후 검증 단계)
|
|
487
|
-
if (mode === 'tmux' && isClaudeEngine(workerModel)) {
|
|
488
|
-
console.warn(
|
|
489
|
-
'WARNING: Claude worker in tmux mode may hang on .claude/ sentinel writes.\n' +
|
|
490
|
-
'After v0.13.0, sentinels live in <project>/.rlp-desk/ which avoids this.\n' +
|
|
491
|
-
'If hang persists, switch to --worker-model gpt-5.5:high (codex) or --mode agent.'
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
PRD brainstorm 플로우(`src/commands/rlp-desk.md`)에도 동일 경고 문구 노출.
|
|
497
|
-
|
|
498
|
-
**Observability — sentinel hang early-detect 휴리스틱** (Architect synthesis):
|
|
499
|
-
|
|
500
|
-
기존 Leader pollForSignal은 30분 timeout으로만 감지 → silent failure. 보강:
|
|
501
|
-
- Worker pane stdout에 `Do you want to ` / `❯ 1. Yes` 등 prompt 시그니처 grep → 즉시 BLOCKED + `category=permission_prompt`로 라벨링.
|
|
502
|
-
- 위치: `src/node/runner/prompt-dismisser.mjs` 또는 별도 `prompt-detector.mjs`.
|
|
503
|
-
- 효과: 다음 platform 변화 시에도 30분이 아니라 수 초 내 발견.
|
|
504
|
-
|
|
505
|
-
### Phase 3 — 마이그레이션 도우미 (legacy `.claude/ralph-desk/` 감지)
|
|
506
|
-
|
|
507
|
-
**위치**: `src/node/init/campaign-initializer.mjs` 진입 시 + `src/node/runner/campaign-main-loop.mjs` `ensureScaffold()` 직전.
|
|
508
|
-
|
|
509
|
-
**로직**:
|
|
510
|
-
1. `<project>/.claude/ralph-desk/`가 존재하고 `<project>/.rlp-desk/`가 없으면
|
|
511
|
-
감지 후 다음 중 하나:
|
|
512
|
-
- **자동 mv** (init 모드): scaffold가 새로 만들어지는 단계라면 §2 Migration race-safety 절차로 자동 이동.
|
|
513
|
-
- **경고 + 수동 명령 안내** (run 모드): 비-zero exit + "기존 캠페인이 있습니다. `mv .claude/ralph-desk .rlp-desk` 후 재실행하세요."
|
|
514
|
-
2. 양쪽 다 존재 시 — 모드 무관하게 자동 mv **거부** + 비-zero exit + 사용자 정리 안내(stderr에 "both directories exist" 포함). §2 Migration race-safety + §3a MEDIUM-B 검증과 일치.
|
|
515
|
-
3. `.gitignore`에서 `.claude/ralph-desk/` 라인 제거 + `.rlp-desk/` 라인 추가 (init 시점, mv 성공 후).
|
|
516
|
-
|
|
517
|
-
---
|
|
518
|
-
|
|
519
|
-
## 3. Verification
|
|
520
|
-
|
|
521
|
-
CLAUDE.md mandate에 따라 commit 전 다음을 모두 통과해야 함:
|
|
522
|
-
|
|
523
|
-
### 3a. Self-Verification (6 scenarios — `src/governance.md`/`init_ralph_desk.zsh` 변경 시 mandatory; executable commands)
|
|
524
|
-
|
|
525
|
-
각 시나리오: Worker(execution_steps) → Verifier(reasoning, 5 categories) → PASS.
|
|
526
|
-
|
|
527
|
-
#### LOW (단위 — `isClaudeEngine()` + env 해석)
|
|
528
|
-
```bash
|
|
529
|
-
node --test tests/node/test-claude-engine-detect.mjs
|
|
530
|
-
# expected: tests passed; isClaudeEngine('sonnet') === true; resolveDeskRoot(env={RLP_DESK_RUNTIME_DIR:'.x'}) === '.x'
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
#### MEDIUM-A (auto-mv 정상 케이스 — pre-mortem #2 part)
|
|
534
|
-
```bash
|
|
535
|
-
TMP=$(mktemp -d); cd "$TMP"; git init -q
|
|
536
|
-
mkdir -p .claude/ralph-desk/memos && echo data > .claude/ralph-desk/memos/x.md
|
|
537
|
-
node ~/.claude/ralph-desk/node/run.mjs init testslug --autonomous
|
|
538
|
-
test ! -d .claude/ralph-desk && test -f .rlp-desk/memos/x.md && \
|
|
539
|
-
grep -q '"Read(.rlp-desk/\*\*)"' .claude/settings.local.json
|
|
540
|
-
# expected: exit 0, all assertions PASS
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
#### MEDIUM-B (conflict 거부 — pre-mortem #1 binding)
|
|
544
|
-
```bash
|
|
545
|
-
TMP=$(mktemp -d); cd "$TMP"; git init -q
|
|
546
|
-
mkdir -p .claude/ralph-desk .rlp-desk
|
|
547
|
-
node ~/.claude/ralph-desk/node/run.mjs init testslug --autonomous 2> stderr.log
|
|
548
|
-
test $? -ne 0 && grep -q 'both directories exist' stderr.log
|
|
549
|
-
# expected: non-zero exit, conflict 안내
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
#### HIGH-A (claude+tmux E2E — AC4 binding, primary fix 검증)
|
|
553
|
-
```bash
|
|
554
|
-
TMP=$(mktemp -d); cd "$TMP"; git init -q
|
|
555
|
-
node ~/.claude/ralph-desk/node/run.mjs init testslug --autonomous
|
|
556
|
-
timeout 600 node ~/.claude/ralph-desk/node/run.mjs run testslug \
|
|
557
|
-
--mode tmux --worker-model sonnet --max-iter 1 --iter-timeout 300
|
|
558
|
-
test $? -eq 0 && test -f .rlp-desk/memos/testslug-done-claim.json
|
|
559
|
-
# expected: exit 0, sentinel hang 없이 완료
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
#### HIGH-B (codex+tmux 회귀 — AC5 binding, P3 first-class)
|
|
563
|
-
```bash
|
|
564
|
-
TMP=$(mktemp -d); cd "$TMP"; git init -q
|
|
565
|
-
node ~/.claude/ralph-desk/node/run.mjs init testslug --autonomous
|
|
566
|
-
timeout 600 node ~/.claude/ralph-desk/node/run.mjs run testslug \
|
|
567
|
-
--mode tmux --worker-model gpt-5.5:high --max-iter 1 --iter-timeout 300
|
|
568
|
-
test $? -eq 0 && test -f .rlp-desk/memos/testslug-done-claim.json
|
|
569
|
-
# expected: exit 0, codex worker 회귀 없음
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
#### OBSERVABILITY (prompt 조기 감지 — AC6 binding)
|
|
573
|
-
```bash
|
|
574
|
-
# 모의 worker stdout에 "❯ 1. Yes" 라인 주입 → prompt-detector가 5초 이내 BLOCKED 작성
|
|
575
|
-
node tests/node/test-prompt-detector-e2e.mjs
|
|
576
|
-
jq -r .category .rlp-desk/memos/testslug-blocked.json
|
|
577
|
-
# expected: "permission_prompt"
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
### 3b. Review
|
|
581
|
-
|
|
582
|
-
- **ralplan** (Planner→Architect→Critic): governance/template 변경이므로 mandatory.
|
|
583
|
-
- **codex review**: 0 issue 도달까지 반복 (CLAUDE.md mandate).
|
|
584
|
-
|
|
585
|
-
### 3c. Local sync 검증
|
|
586
|
-
|
|
587
|
-
CLAUDE.md `Local File Sync` 섹션의 banner-aware verification 절차로
|
|
588
|
-
모든 `src/` 변경분이 `~/.claude/ralph-desk/`에 sync되었는지 확인:
|
|
589
|
-
|
|
590
|
-
```bash
|
|
591
|
-
diff -rq src/node ~/.claude/ralph-desk/node | grep -v 'DO NOT EDIT'
|
|
592
|
-
# expected: empty output (모든 파일이 banner 차이 외에 동일)
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
### 3d. 수동 reproduction (버그 리포터 시나리오 재현)
|
|
596
|
-
|
|
597
|
-
```bash
|
|
598
|
-
# legacy 경로 시뮬레이션
|
|
599
|
-
mkdir -p /tmp/test-rlp-desk/.claude/ralph-desk
|
|
600
|
-
cd /tmp/test-rlp-desk
|
|
601
|
-
|
|
602
|
-
# init → 마이그레이션 또는 신규 .rlp-desk 생성 확인
|
|
603
|
-
node ~/.claude/ralph-desk/node/run.mjs init test-slug --autonomous
|
|
604
|
-
|
|
605
|
-
# 검증: .rlp-desk/ 존재 + .gitignore 갱신
|
|
606
|
-
test -d .rlp-desk && echo PASS || echo FAIL
|
|
607
|
-
grep -q '^.rlp-desk/$' .gitignore && echo PASS || echo FAIL
|
|
608
|
-
|
|
609
|
-
# 1-iter campaign with claude worker
|
|
610
|
-
node ~/.claude/ralph-desk/node/run.mjs run test-slug \
|
|
611
|
-
--mode tmux --worker-model sonnet --max-iter 1 --iter-timeout 600
|
|
612
|
-
# 기대: sentinel hang 없이 1 iteration 완료
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
---
|
|
616
|
-
|
|
617
|
-
## 4. Release Plan
|
|
618
|
-
|
|
619
|
-
- **버전**: `0.13.0` (npm minor bump — 자동 마이그레이션 + run 모드 명확한 안내로 사용자 영향 흡수).
|
|
620
|
-
- **Release notes** (user-facing only — CLAUDE.md mandate; 최상단에 BREAKING 라벨 강조):
|
|
621
|
-
- **BREAKING**: project-local runtime이 `.claude/ralph-desk/` → `.rlp-desk/`로 이동.
|
|
622
|
-
init 모드는 자동 마이그레이션, run 모드는 경고 + 수동 `mv .claude/ralph-desk .rlp-desk` 안내.
|
|
623
|
-
- **NEW**: `RLP_DESK_RUNTIME_DIR` 환경변수로 runtime 디렉토리 override 가능 (미래 platform 변화 회피용).
|
|
624
|
-
- **FIX**: Claude worker + tmux 조합 sentinel write hang 해결.
|
|
625
|
-
- **NEW**: claude worker + tmux 조합 경고 + permission prompt 조기 감지(BLOCKED `category=permission_prompt`).
|
|
626
|
-
- **Roadmap note**: 1.0.0에서 legacy 감지 로직 deprecation 예정 (deprecation cycle 약속).
|
|
627
|
-
|
|
628
|
-
---
|
|
629
|
-
|
|
630
|
-
## 5. Critical files (이 plan 실행 시 수정 대상 요약)
|
|
631
|
-
|
|
632
|
-
```
|
|
633
|
-
src/node/init/campaign-initializer.mjs # deskRoot 상수 + GITIGNORE_RULE + 마이그레이션 감지
|
|
634
|
-
src/node/runner/campaign-main-loop.mjs # 경로 빌드 함수 + ensureScaffold() 전 legacy 검사
|
|
635
|
-
src/node/cli/command-builder.mjs # isClaudeEngine() helper export
|
|
636
|
-
src/node/run.mjs # parseRunOptions() 후 tmux+claude 경고
|
|
637
|
-
src/scripts/init_ralph_desk.zsh # DESK 변수 + permission marker (.rlp-desk/**)
|
|
638
|
-
src/scripts/run_ralph_desk.zsh # DESK 변수
|
|
639
|
-
src/scripts/lib_ralph_desk.zsh # 변경 없음 ($ROOT 이미 whitelist이므로 .rlp-desk 자동 포함; 주석만 명확화)
|
|
640
|
-
src/commands/rlp-desk.md # 24개 라인 경로 참조 갱신
|
|
641
|
-
src/governance.md # 6개 라인 경로 문서화
|
|
642
|
-
package.json # version 0.13.0
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
---
|
|
646
|
-
|
|
647
|
-
## 6. Resolved decisions (사용자 확정 — 추천안)
|
|
648
|
-
|
|
649
|
-
- **마이그레이션 정책**: init 모드 = 자동 mv, run 모드 = 경고 + 수동 mv 안내. 사용자 데이터(memos, plans)
|
|
650
|
-
존재 시 init은 새 scaffold가 만들어지는 시점이라 안전하게 이동 가능, run은 진행 중인 campaign일 수
|
|
651
|
-
있으므로 명시적 사용자 확인 필요.
|
|
652
|
-
- **버전 강도**: `0.13.0` (npm minor). Project-local 경로 변경은 breaking이지만 자동 마이그레이션
|
|
653
|
-
도우미 + run 모드 명확한 안내가 있으므로 minor로 충분. major(1.0.0)는 후속 안정화 단계에서.
|
|
654
|
-
- **`--mode agent` 처리**: Fix-1(경로 이동)으로 자동 해결. agent mode worker도 동일하게 `.rlp-desk/`에
|
|
655
|
-
쓰므로 Claude Code sensitive trigger 발생 안 함. 별도 작업 불필요.
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
## 7. RALPLAN-DR Deliberation Summary
|
|
660
|
-
|
|
661
|
-
### Principles
|
|
662
|
-
|
|
663
|
-
1. **Don't fight platform-reserved namespaces** — Claude Code hardcoded sensitive policy 우회 불가; 회피만이 답.
|
|
664
|
-
2. **Project-local runtime은 git 트리에 머물러야** — 캠페인 memory/plans는 iteration 간 영속 필요.
|
|
665
|
-
3. **Cross-engine fallback은 first-class** — codex worker는 영구적 회피책이 아닌 동등한 옵션.
|
|
666
|
-
4. **마이그레이션 안전성 우선, 자동화는 명확히 안전한 시점에만** — init 모드는 fresh scaffold 시점이라 자동 mv, run 모드는 진행 중 캠페인 보호를 위해 경고 + 수동 mv. "default 자동"이 아닌 "context-aware 자동/수동 선택".
|
|
667
|
-
|
|
668
|
-
### Decision Drivers (top 3)
|
|
669
|
-
|
|
670
|
-
1. **Unblock HIGH severity blocker** — claude-worker + tmux 모든 캠페인 차단 중.
|
|
671
|
-
2. **Minimize breaking surface** — 진행 중 campaign 손실 방지.
|
|
672
|
-
3. **Reference parity** — design-desk(영감 출처)도 sentinel을 `.claude/` 밖에 둠.
|
|
673
|
-
|
|
674
|
-
### Viable Options
|
|
675
|
-
|
|
676
|
-
| Option | Pros | Cons |
|
|
677
|
-
|---|---|---|
|
|
678
|
-
| **A. `.rlp-desk/` 이동 (권장)** | `.claude/` trigger 완전 회피, design-desk 패턴 일치, git 트리 유지 | 모든 사용자 마이그레이션 필요 (자동화로 완화) |
|
|
679
|
-
| **B. `.claude/ralph-desk/` 유지 + 권한 escape** | 경로 변경 없음 | 사용자 repro에서 permission allowlist도 우회 실패. Claude Code 내부 동작 의존 → brittle |
|
|
680
|
-
| **C. `$TMPDIR/rlp-desk-<slug>/`** | 프로젝트 트리 청결 | git 추적 끊김, campaign memory 영속성 깨짐, resume 취약 |
|
|
681
|
-
|
|
682
|
-
### Invalidation rationale
|
|
683
|
-
|
|
684
|
-
- **B**: 버그 리포트 §2 표에서 `Read/Edit/Write(.claude/ralph-desk/**)` allowlist 추가가 실패함이 입증. Claude Code의 sensitive 게이트는 일반 permission 시스템과 별도로 동작 → 의존 불가.
|
|
685
|
-
- **C**: campaign memory(`memos/<slug>-memory.md` 등)는 iteration 간 영속이 핵심 설계. tmpfs 기반은 OS 재부팅/clean 시 유실되어 resume 불가능.
|
|
686
|
-
|
|
687
|
-
→ **A가 유일한 viable option**.
|
|
688
|
-
|
|
689
|
-
### Pre-mortem (3 scenarios — verification §3a에 명시 binding)
|
|
690
|
-
|
|
691
|
-
1. **자동 mv가 사용자 데이터 덮어쓰기**: 양쪽 디렉토리 모두 존재 시 mv 충돌 → mitigation: §2 Phase 3의 atomic lock + 충돌 거부. **검증**: §3a MEDIUM-B.
|
|
692
|
-
2. **권한 marker 누락**: `.rlp-desk/**` permission이 `init_ralph_desk.zsh`에 추가 안 되면 worker가 새 경로에서도 prompt 발생 → mitigation: permission marker 패턴 갱신 + assertion. **검증**: §3a MEDIUM-A의 `grep -q '"Read(.rlp-desk/\*\*)"' .claude/settings.local.json` 단언 + §3a HIGH-A의 1-iter 캠페인 sentinel write 성공(end-to-end).
|
|
693
|
-
3. **Sandbox `--add-dir` 미커버**: `$ROOT`가 이미 whitelist이므로 자동 포함이지만, 만약 `--add-dir` 인자 변경으로 회귀하면 sandbox가 새 경로 거부 → mitigation: 통합 테스트에서 worker 명령 빌드 결과 단언. **검증**: §3a HIGH-A의 worker spawn 단계에서 `claude --add-dir "$ROOT" ...` 명시 확인 + §3d 수동 1-iter 재현.
|
|
694
|
-
|
|
695
|
-
### Acceptance Criteria (자동 검증 가능 — pass 신호 명시)
|
|
696
|
-
|
|
697
|
-
- [ ] **AC1** — `<project>/.rlp-desk/`만 사용. 검증: `find . -type d -path '*.claude/ralph-desk' -newer <campaign-start-marker> | wc -l` == 0.
|
|
698
|
-
- [ ] **AC2** — Legacy `.claude/ralph-desk/` 존재 시 init은 자동 mv 후 `.gitignore`에 `.rlp-desk/` 라인 존재. 검증: `test ! -d .claude/ralph-desk && test -d .rlp-desk && grep -q '^\.rlp-desk/$' .gitignore`.
|
|
699
|
-
- [ ] **AC3** — Run 모드에서 legacy 발견 시 비-zero exit + stderr에 `mv .claude/ralph-desk .rlp-desk` 안내 문자열 포함. 검증: `node run.mjs run ...; echo $? != 0; grep -q "mv .claude/ralph-desk" stderr.log`.
|
|
700
|
-
- [ ] **AC4** — `--mode tmux --worker-model sonnet` 1-iter 캠페인 600초 이내 종료 + `done-claim.json` 존재 + exit 0. 검증: `timeout 600 node run.mjs run ... --max-iter 1; test $? == 0 && test -f .rlp-desk/memos/<slug>-done-claim.json`.
|
|
701
|
-
- [ ] **AC5** — `--worker-model gpt-5.5:high` 1-iter 캠페인 동일 단언(AC4 패턴) — 회귀 없음.
|
|
702
|
-
- [ ] **AC6** — Permission prompt 조기 감지: worker에 mock prompt 주입 시 5초 이내 BLOCKED + sentinel `category=permission_prompt`. 검증: `jq -r .category .rlp-desk/memos/<slug>-blocked.json == "permission_prompt"`.
|
|
703
|
-
|
|
704
|
-
---
|
|
705
|
-
|
|
706
|
-
## 8. ADR (Architectural Decision Record)
|
|
707
|
-
|
|
708
|
-
- **Decision**: Project-local sentinel/runtime을 `.claude/ralph-desk/` → `.rlp-desk/`로 이동.
|
|
709
|
-
- **Drivers**: Claude Code hardcoded sensitive policy로 worker hang. 우회 불가 → 디렉토리 명 변경.
|
|
710
|
-
- **Alternatives considered**: B(`.claude/` 유지 + escape) — 사용자 repro에서 입증 실패. C(`$TMPDIR/`) — campaign memory 영속성 손상.
|
|
711
|
-
- **Why chosen**: A는 design-desk 참조 패턴과 일치하고, sensitive trigger의 root cause(`.claude/` 디렉토리명)를 직접 회피. 자동 마이그레이션으로 사용자 영향 최소화.
|
|
712
|
-
- **Consequences**: 0.13.0 minor breaking. 모든 사용자 `.gitignore` + 디렉토리 갱신 필요(자동화). 문서 업데이트 광범위(rlp-desk.md 24 lines, governance.md 6 lines).
|
|
713
|
-
- **Follow-ups**:
|
|
714
|
-
1. **1.0.0 deprecation cycle**: legacy `.claude/ralph-desk/` 감지 로직 제거 (사용자에게 1 minor 사이클 마이그레이션 시간 확보).
|
|
715
|
-
2. **`~/.claude/ralph-desk/` 이동 검토**: 현재 sensitive trigger 미발생이지만 platform 변화 대비 1.x에서 검토.
|
|
716
|
-
3. **Permission prompt 조기 감지**: `prompt-detector.mjs` 추가 후 다른 platform-shaped silent failure에도 재사용 (예: codex CLI의 미래 정책 변화).
|
|
717
|
-
4. **Steelman 대응**: Architect 지적("`.rlp-desk/`도 미래 sensitive화 가능") — `RLP_DESK_RUNTIME_DIR` env 외부화로 기술적 대응 완료. 정책 모니터링은 운영 영역.
|