@graypark/loophaus 3.6.1 → 3.8.0

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.ko.md CHANGED
@@ -153,11 +153,13 @@ loophaus는 세 개의 주요 코딩 에이전트 플랫폼을 지원합니다:
153
153
  | 기능 | Claude Code | Codex CLI | Kiro CLI |
154
154
  | --- | --- | --- | --- |
155
155
  | 자동 감지 설치 | `~/.claude/` | `~/.codex/` | `~/.kiro/` |
156
- | Stop hook | bash 기반 | Node.js 기반 | bash 기반 |
156
+ | Stop hook | Node.js 기반 | Node.js 기반 | Node.js 기반 |
157
157
  | 루프 실행 | Skill tool | 네이티브 커맨드 | 네이티브 커맨드 |
158
158
  | 멀티 에이전트 | Agent tool | 서브에이전트 | 서브에이전트 |
159
159
  | 상태 파일 | `.loophaus/state.json` | `.loophaus/state.json` | `.loophaus/state.json` |
160
160
 
161
+ Windows에서도 PowerShell/CMD 기준으로 `install`, `upgrade`, `/loop` 초기화가 동작합니다. Git Bash나 WSL은 선택사항입니다.
162
+
161
163
  ## 설치
162
164
 
163
165
  ### 글로벌 설치 (권장)
@@ -167,6 +169,8 @@ npm install -g @graypark/loophaus
167
169
  loophaus install
168
170
  ```
169
171
 
172
+ Windows에서는 전역 npm 실행 파일 경로(보통 `%AppData%\npm`)가 `PATH`에 포함되어 있어야 합니다.
173
+
170
174
  ### npx로 설치
171
175
 
172
176
  ```bash
@@ -366,6 +370,12 @@ Phase 2 — 순차 수정 (루프):
366
370
 
367
371
  ## 업데이트
368
372
 
373
+ ```bash
374
+ loophaus upgrade
375
+ ```
376
+
377
+ 수동 업데이트가 필요하면:
378
+
369
379
  ```bash
370
380
  npm install -g @graypark/loophaus@latest
371
381
  loophaus install --force
@@ -381,7 +391,7 @@ npm uninstall -g @graypark/loophaus
381
391
  ## 개발
382
392
 
383
393
  ```bash
384
- npm install && npm test # 296개 테스트
394
+ npm install && npm test
385
395
  npm run typecheck # TypeScript strict 모드
386
396
  npm run build # dist/로 컴파일
387
397
  npx vitest # watch 모드
package/README.md CHANGED
@@ -119,6 +119,8 @@ That's it. The interview generates a PRD, activates the loop, and starts impleme
119
119
 
120
120
  All three platforms share the same core engine (`core/engine.ts`) and state store (`store/state-store.ts`). Platform-specific adapters handle the differences.
121
121
 
122
+ Native Windows is supported for install, upgrade, and `/loop` initialization via PowerShell or CMD. Git Bash and WSL are optional, not required.
123
+
122
124
  ## Installation
123
125
 
124
126
  ### Global install (recommended)
@@ -128,6 +130,8 @@ npm install -g @graypark/loophaus
128
130
  loophaus install
129
131
  ```
130
132
 
133
+ On Windows, ensure your global npm bin directory (typically `%AppData%\npm`) is on `PATH`.
134
+
131
135
  ### Via npx
132
136
 
133
137
  ```bash
@@ -283,6 +287,12 @@ Each story is sized to complete in one iteration (one context window). Dependenc
283
287
 
284
288
  ## Update
285
289
 
290
+ ```bash
291
+ loophaus upgrade
292
+ ```
293
+
294
+ Manual upgrade also works:
295
+
286
296
  ```bash
287
297
  npm install -g @graypark/loophaus@latest
288
298
  loophaus install --force
@@ -301,7 +311,7 @@ npm uninstall -g @graypark/loophaus
301
311
  git clone https://github.com/vcz-Gray/loophaus.git
302
312
  cd loophaus
303
313
  npm install
304
- npm test # 296 test cases
314
+ npm test
305
315
  npm run typecheck # TypeScript strict mode
306
316
  npm run build # Compile to dist/
307
317
  npx vitest # watch mode
@@ -1,11 +1,6 @@
1
1
  ---
2
2
  description: "Cancel active Ralph Loop"
3
- allowed-tools:
4
- [
5
- "Bash(test -f .claude/ralph-loop.local.md:*)",
6
- "Bash(rm .claude/ralph-loop.local.md)",
7
- "Read(.claude/ralph-loop.local.md)",
8
- ]
3
+ allowed-tools: ["Bash", "Read"]
9
4
  hide-from-slash-command-tool: "true"
10
5
  ---
11
6
 
@@ -13,11 +8,11 @@ hide-from-slash-command-tool: "true"
13
8
 
14
9
  To cancel the Ralph loop:
15
10
 
16
- 1. Check if `.claude/ralph-loop.local.md` exists using Bash: `test -f .claude/ralph-loop.local.md && echo "EXISTS" || echo "NOT_FOUND"`
11
+ 1. Check if `.claude/ralph-loop.local.md` exists using a cross-platform Node command.
17
12
 
18
13
  2. **If NOT_FOUND**: Say "No active Ralph loop found."
19
14
 
20
15
  3. **If EXISTS**:
21
16
  - Read `.claude/ralph-loop.local.md` to get the current iteration number from the `iteration:` field
22
- - Remove the file using Bash: `rm .claude/ralph-loop.local.md`
17
+ - Remove the file using a cross-platform Node file command
23
18
  - Report: "Cancelled Ralph loop (was at iteration N)" where N is the iteration value
@@ -11,6 +11,49 @@ The user runs `/loop-plan` once and gets a single merged branch with all work do
11
11
 
12
12
  ---
13
13
 
14
+ ## Pre-flight: Update Check
15
+
16
+ Run `loophaus update-check` silently before starting.
17
+
18
+ If the command reports an available update:
19
+ 1. Show: `loophaus update available: v{current} → v{latest}`
20
+ 2. Ask user:
21
+ - **A) Upgrade now** → run `loophaus upgrade`, then continue
22
+ - **B) Always auto-upgrade** → run `loophaus config set autoUpgrade true && loophaus upgrade`, then continue
23
+ - **C) Not now** → run `loophaus config set updateCheck snoozed` silently, then continue
24
+ - **D) Never ask again** → run `loophaus config set updateCheck false`, then continue
25
+
26
+ If auto-upgrade is enabled (`loophaus config get autoUpgrade` returns `true`):
27
+ - Skip prompt, run `loophaus upgrade` silently, continue.
28
+
29
+ If no update is available or the check fails: continue silently and never block the user.
30
+
31
+ ## Pre-flight: Skill Routing Check
32
+
33
+ Check if the project has a CLAUDE.md with loophaus skill routing:
34
+
35
+ ```bash
36
+ CLAUDE_MD="$(pwd)/CLAUDE.md"
37
+ ROUTING_MARKER=".loophaus/routing-offered.json"
38
+ ```
39
+
40
+ If CLAUDE.md exists but has no `## loophaus skill routing` section, AND routing hasn't been offered before:
41
+ 1. Suggest adding this section to CLAUDE.md:
42
+ ```markdown
43
+ ## loophaus skill routing
44
+
45
+ | User intent | Route to |
46
+ |-------------|----------|
47
+ | Feature implementation, refactoring, multi-step task | `/loophaus:loop-plan` |
48
+ | "start the loop", "continue implementing" | `/loophaus:loop` |
49
+ | "stop the loop", "cancel" | `/loophaus:loop-stop` |
50
+ | "what's the status", "how far along" | `/loophaus:loop-pulse` |
51
+ ```
52
+ 2. If user agrees, add the section to CLAUDE.md
53
+ 3. Write `{ "offeredAt": "<ISO date>" }` to `.loophaus/routing-offered.json` so we don't ask again
54
+
55
+ If CLAUDE.md doesn't exist or routing already offered: skip silently.
56
+
14
57
  ## Phase 0: Cleanup Previous Data
15
58
 
16
59
  Before starting a new plan, apply the cleanup policy from `.loophaus/config.json`:
@@ -1,29 +1,21 @@
1
1
  ---
2
2
  description: "Stop active loop"
3
- allowed-tools:
4
- [
5
- "Bash(test -f .loophaus/state.json:*)",
6
- "Bash(rm .loophaus/state.json)",
7
- "Read(.loophaus/state.json)",
8
- "Bash(test -f .claude/ralph-loop.local.md:*)",
9
- "Bash(rm .claude/ralph-loop.local.md)",
10
- "Read(.claude/ralph-loop.local.md)",
11
- ]
3
+ allowed-tools: ["Bash", "Read"]
12
4
  ---
13
5
 
14
6
  # /loop-stop — Stop Active Loop
15
7
 
16
- 1. Check if `.loophaus/state.json` exists: `test -f .loophaus/state.json && echo "EXISTS" || echo "NOT_FOUND"`
17
- - If not found, also check legacy path: `test -f .claude/ralph-loop.local.md && echo "LEGACY" || echo "NOT_FOUND"`
8
+ 1. Check if `.loophaus/state.json` exists using a cross-platform Node command.
9
+ - If not found, also check legacy path `.claude/ralph-loop.local.md`.
18
10
 
19
11
  2. **If NOT_FOUND** on both: Say "No active loop found."
20
12
 
21
13
  3. **If EXISTS** (.loophaus/state.json):
22
14
  - Read the file to get `currentIteration`
23
- - Remove it: `rm .loophaus/state.json`
15
+ - Remove it with a cross-platform Node file command
24
16
  - Report: "Stopped loop at iteration N."
25
17
 
26
18
  4. **If LEGACY** (.claude/ralph-loop.local.md):
27
19
  - Read it to get the iteration field
28
- - Remove it: `rm .claude/ralph-loop.local.md`
20
+ - Remove it with a cross-platform Node file command
29
21
  - Report: "Stopped loop at iteration N. (migrated from legacy path)"
package/commands/loop.md CHANGED
@@ -1,15 +1,24 @@
1
1
  ---
2
2
  description: "Start iterative dev loop"
3
3
  argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
4
- allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
4
+ allowed-tools: ["Bash"]
5
5
  ---
6
6
 
7
7
  # /loop — Start Iterative Dev Loop
8
8
 
9
+ ## Pre-flight
10
+
11
+ Run `loophaus update-check` quietly before starting.
12
+ If it reports an available update and auto-upgrade is enabled, run `loophaus upgrade` and continue.
13
+ If it reports an available update and auto-upgrade is disabled, show one-line notice: `loophaus update available. Run: loophaus upgrade`.
14
+ If the check fails or no update is available, continue silently.
15
+
16
+ ---
17
+
9
18
  Execute the setup script to initialize the loop:
10
19
 
11
20
  ```!
12
- "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
21
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/setup-loop.mjs" $ARGUMENTS
13
22
  ```
14
23
 
15
24
  Work on the task. When you try to exit, the stop hook feeds the SAME PROMPT back for the next iteration. Your previous work persists in files and git history.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: "Start Ralph Loop in current session"
3
3
  argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
4
- allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
4
+ allowed-tools: ["Bash"]
5
5
  hide-from-slash-command-tool: "true"
6
6
  ---
7
7
 
@@ -10,7 +10,7 @@ hide-from-slash-command-tool: "true"
10
10
  Execute the setup script to initialize the Ralph loop:
11
11
 
12
12
  ```!
13
- "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
13
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/setup-loop.mjs" $ARGUMENTS
14
14
  ```
15
15
 
16
16
  Please work on the task. When you try to exit, the Ralph loop will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve.
package/dist/README.ko.md CHANGED
@@ -153,11 +153,13 @@ loophaus는 세 개의 주요 코딩 에이전트 플랫폼을 지원합니다:
153
153
  | 기능 | Claude Code | Codex CLI | Kiro CLI |
154
154
  | --- | --- | --- | --- |
155
155
  | 자동 감지 설치 | `~/.claude/` | `~/.codex/` | `~/.kiro/` |
156
- | Stop hook | bash 기반 | Node.js 기반 | bash 기반 |
156
+ | Stop hook | Node.js 기반 | Node.js 기반 | Node.js 기반 |
157
157
  | 루프 실행 | Skill tool | 네이티브 커맨드 | 네이티브 커맨드 |
158
158
  | 멀티 에이전트 | Agent tool | 서브에이전트 | 서브에이전트 |
159
159
  | 상태 파일 | `.loophaus/state.json` | `.loophaus/state.json` | `.loophaus/state.json` |
160
160
 
161
+ Windows에서도 PowerShell/CMD 기준으로 `install`, `upgrade`, `/loop` 초기화가 동작합니다. Git Bash나 WSL은 선택사항입니다.
162
+
161
163
  ## 설치
162
164
 
163
165
  ### 글로벌 설치 (권장)
@@ -167,6 +169,8 @@ npm install -g @graypark/loophaus
167
169
  loophaus install
168
170
  ```
169
171
 
172
+ Windows에서는 전역 npm 실행 파일 경로(보통 `%AppData%\npm`)가 `PATH`에 포함되어 있어야 합니다.
173
+
170
174
  ### npx로 설치
171
175
 
172
176
  ```bash
@@ -366,6 +370,12 @@ Phase 2 — 순차 수정 (루프):
366
370
 
367
371
  ## 업데이트
368
372
 
373
+ ```bash
374
+ loophaus upgrade
375
+ ```
376
+
377
+ 수동 업데이트가 필요하면:
378
+
369
379
  ```bash
370
380
  npm install -g @graypark/loophaus@latest
371
381
  loophaus install --force
@@ -381,7 +391,7 @@ npm uninstall -g @graypark/loophaus
381
391
  ## 개발
382
392
 
383
393
  ```bash
384
- npm install && npm test # 296개 테스트
394
+ npm install && npm test
385
395
  npm run typecheck # TypeScript strict 모드
386
396
  npm run build # dist/로 컴파일
387
397
  npx vitest # watch 모드
package/dist/README.md CHANGED
@@ -119,6 +119,8 @@ That's it. The interview generates a PRD, activates the loop, and starts impleme
119
119
 
120
120
  All three platforms share the same core engine (`core/engine.ts`) and state store (`store/state-store.ts`). Platform-specific adapters handle the differences.
121
121
 
122
+ Native Windows is supported for install, upgrade, and `/loop` initialization via PowerShell or CMD. Git Bash and WSL are optional, not required.
123
+
122
124
  ## Installation
123
125
 
124
126
  ### Global install (recommended)
@@ -128,6 +130,8 @@ npm install -g @graypark/loophaus
128
130
  loophaus install
129
131
  ```
130
132
 
133
+ On Windows, ensure your global npm bin directory (typically `%AppData%\npm`) is on `PATH`.
134
+
131
135
  ### Via npx
132
136
 
133
137
  ```bash
@@ -283,6 +287,12 @@ Each story is sized to complete in one iteration (one context window). Dependenc
283
287
 
284
288
  ## Update
285
289
 
290
+ ```bash
291
+ loophaus upgrade
292
+ ```
293
+
294
+ Manual upgrade also works:
295
+
286
296
  ```bash
287
297
  npm install -g @graypark/loophaus@latest
288
298
  loophaus install --force
@@ -301,7 +311,7 @@ npm uninstall -g @graypark/loophaus
301
311
  git clone https://github.com/vcz-Gray/loophaus.git
302
312
  cd loophaus
303
313
  npm install
304
- npm test # 296 test cases
314
+ npm test
305
315
  npm run typecheck # TypeScript strict mode
306
316
  npm run build # Compile to dist/
307
317
  npx vitest # watch mode
@@ -3,6 +3,8 @@
3
3
  import { resolve, dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { access } from "node:fs/promises";
6
+ import { getLoophausHome } from "../lib/paths.js";
7
+ import { getGlobalBinaryPath, runCommand } from "../lib/runtime.js";
6
8
  const __filename = fileURLToPath(import.meta.url);
7
9
  const __dirname = dirname(__filename);
8
10
  const PROJECT_ROOT = resolve(__dirname, "..");
@@ -16,12 +18,13 @@ const showHelp = args.includes("--help") || args.includes("-h");
16
18
  const KNOWN_FLAGS = new Set([
17
19
  "--help", "-h", "--version", "--dry-run", "--force", "--local", "--verbose",
18
20
  "--host", "--claude", "--kiro", "--name", "--speed", "--count", "--base", "--story",
19
- "--all", "--traces", "--sessions", "--results", "--before", "--config",
21
+ "--all", "--traces", "--sessions", "--results", "--before", "--config", "--quiet",
20
22
  ]);
21
23
  const VALID_COMMANDS = [
22
24
  "install", "uninstall", "status", "stats", "loops", "watch",
23
25
  "replay", "compare", "worktree", "parallel", "quality",
24
- "sessions", "resume", "benchmark", "clean", "help",
26
+ "sessions", "resume", "benchmark", "clean", "config",
27
+ "update-check", "upgrade", "help",
25
28
  ];
26
29
  function validateFlags() {
27
30
  for (const arg of args) {
@@ -114,6 +117,9 @@ Usage:
114
117
  npx @graypark/loophaus quality [--story US-001]
115
118
  npx @graypark/loophaus benchmark
116
119
  npx @graypark/loophaus clean [--all|--traces|--sessions|--results] [--before DATE]
120
+ npx @graypark/loophaus config [list|get|set] [key] [value]
121
+ npx @graypark/loophaus update-check
122
+ npx @graypark/loophaus upgrade
117
123
  npx @graypark/loophaus sessions
118
124
  npx @graypark/loophaus resume <session-id>
119
125
  npx @graypark/loophaus --version
@@ -156,6 +162,11 @@ async function detectHosts() {
156
162
  return hosts;
157
163
  }
158
164
  async function runInstall() {
165
+ const { getPackageVersion } = await import("../lib/paths.js");
166
+ const version = getPackageVersion();
167
+ const quiet = args.includes("--quiet");
168
+ const loophausDir = getLoophausHome();
169
+ const welcomePath = join(loophausDir, ".welcome-seen");
159
170
  let targets = [];
160
171
  if (host) {
161
172
  targets = [host];
@@ -192,6 +203,57 @@ async function runInstall() {
192
203
  s?.stop();
193
204
  }
194
205
  }
206
+ if (quiet || dryRun)
207
+ return;
208
+ // First-run welcome or upgrade notice
209
+ const { mkdir: mk, writeFile: wf, readFile: rf } = await import("node:fs/promises");
210
+ await mk(loophausDir, { recursive: true });
211
+ let isFirstRun = false;
212
+ try {
213
+ const seen = await rf(welcomePath, "utf-8");
214
+ // Existing install — show What's New if version changed
215
+ if (seen.trim() !== version) {
216
+ await wf(welcomePath, version, "utf-8");
217
+ try {
218
+ const changelog = await rf(join(__dirname, "..", "CHANGELOG.md"), "utf-8");
219
+ const firstEntry = changelog.match(/## \[[\d.]+\][^\n]*\n([\s\S]*?)(?=\n## \[|$)/);
220
+ if (firstEntry) {
221
+ console.log(`\n \x1b[36mWhat's New in v${version}:\x1b[0m`);
222
+ const lines = firstEntry[1].trim().split("\n").slice(0, 8);
223
+ for (const l of lines)
224
+ console.log(` ${l}`);
225
+ }
226
+ }
227
+ catch { /* no CHANGELOG */ }
228
+ }
229
+ }
230
+ catch {
231
+ isFirstRun = true;
232
+ await wf(welcomePath, version, "utf-8");
233
+ }
234
+ if (isFirstRun) {
235
+ console.log(`
236
+ \x1b[36m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1b[0m
237
+ Welcome to \x1b[1mloophaus\x1b[0m v${version}
238
+
239
+ Control plane for coding agents.
240
+ Iterative dev loops with quality verification.
241
+
242
+ Quick start:
243
+ /loop-plan <describe your task>
244
+
245
+ Commands:
246
+ /loop-plan Interview → PRD → implement → verify
247
+ /loop Start loop with existing PRD
248
+ /loop-pulse Check progress
249
+ /loop-stop Cancel loop
250
+
251
+ CLI:
252
+ loophaus benchmark Project quality score
253
+ loophaus config list View settings
254
+ loophaus upgrade Update to latest
255
+ \x1b[36m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1b[0m`);
256
+ }
195
257
  }
196
258
  async function runUninstall() {
197
259
  if (host === "claude-code" || args.includes("--claude")) {
@@ -683,6 +745,139 @@ Options:
683
745
  console.log(" Nothing to clean.");
684
746
  }
685
747
  }
748
+ async function runUpdateCheck() {
749
+ const { getPackageVersion } = await import("../lib/paths.js");
750
+ const { checkForUpdate } = await import("../core/update-checker.js");
751
+ const current = getPackageVersion();
752
+ const result = await checkForUpdate(current);
753
+ console.log("Update Check");
754
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
755
+ console.log(` Current: v${result.current}`);
756
+ console.log(` Latest: v${result.latest}`);
757
+ console.log(` Status: ${result.status}`);
758
+ if (result.message)
759
+ console.log(` Note: ${result.message}`);
760
+ if (result.status === "upgrade_available") {
761
+ console.log(`\n \x1b[33mUpdate available: v${result.current} → v${result.latest}\x1b[0m`);
762
+ console.log(` Run: loophaus upgrade`);
763
+ }
764
+ }
765
+ async function runUpgrade() {
766
+ const { getPackageVersion } = await import("../lib/paths.js");
767
+ const { checkForUpdate } = await import("../core/update-checker.js");
768
+ const current = getPackageVersion();
769
+ const result = await checkForUpdate(current);
770
+ if (result.status === "up_to_date") {
771
+ console.log(`Already on latest version: v${current}`);
772
+ return;
773
+ }
774
+ if (result.status !== "upgrade_available" && result.status !== "snoozed") {
775
+ console.log(`No update available (status: ${result.status})`);
776
+ return;
777
+ }
778
+ console.log(`Upgrading loophaus: v${result.current} → v${result.latest}`);
779
+ const s = spinner("Installing...");
780
+ try {
781
+ await runCommand("npm", ["install", "-g", `@graypark/loophaus@${result.latest}`], { timeout: 120_000 });
782
+ s.stop();
783
+ console.log(`\u2714 Installed v${result.latest}`);
784
+ const s2 = spinner("Reinstalling plugins...");
785
+ try {
786
+ const { stdout: prefixStdout } = await runCommand("npm", ["prefix", "-g"], { timeout: 30_000 });
787
+ const globalLoophaus = getGlobalBinaryPath(prefixStdout.trim(), "loophaus");
788
+ await runCommand(globalLoophaus, ["install", "--force"], { timeout: 60_000 });
789
+ s2.stop();
790
+ console.log("\u2714 Plugins reinstalled");
791
+ }
792
+ catch {
793
+ s2.stop();
794
+ console.log(" Note: Run 'loophaus install --force' to update plugins.");
795
+ }
796
+ console.log(`\n Upgrade complete: v${result.current} → v${result.latest}`);
797
+ }
798
+ catch (err) {
799
+ s.stop();
800
+ console.error(`\u2718 Upgrade failed: ${err.message}`);
801
+ console.error(" Try manually: npm install -g @graypark/loophaus@latest");
802
+ }
803
+ }
804
+ async function runConfigCmd() {
805
+ const { readConfig, writeConfig } = await import("../core/cleanup.js");
806
+ const sub = args[1];
807
+ const KNOWN_KEYS = {
808
+ "cleanup.onNewPlan": "Policy when /loop-plan starts: archive | delete | keep",
809
+ "cleanup.traceRetentionDays": "Days to keep trace data",
810
+ "cleanup.sessionRetentionDays": "Days to keep session checkpoints",
811
+ "updateCheck": "Check for updates on skill execution: true | false",
812
+ "autoUpgrade": "Auto-upgrade without prompting: true | false",
813
+ };
814
+ if (!sub || sub === "list") {
815
+ const config = await readConfig();
816
+ console.log("Configuration (.loophaus/config.json)");
817
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
818
+ for (const [key, desc] of Object.entries(KNOWN_KEYS)) {
819
+ const val = getNestedValue(config, key);
820
+ console.log(` ${key.padEnd(30)} ${String(val ?? "(default)").padEnd(12)} ${desc}`);
821
+ }
822
+ console.log(`\nUsage: loophaus config set <key> <value>`);
823
+ return;
824
+ }
825
+ if (sub === "get") {
826
+ const key = args[2];
827
+ if (!key) {
828
+ console.log("Usage: loophaus config get <key>");
829
+ return;
830
+ }
831
+ const config = await readConfig();
832
+ const val = getNestedValue(config, key);
833
+ console.log(val !== undefined ? String(val) : "(not set)");
834
+ return;
835
+ }
836
+ if (sub === "set") {
837
+ const key = args[3] ? args[2] : args[2];
838
+ const value = args[3] || args[3];
839
+ if (!key || value === undefined) {
840
+ console.log("Usage: loophaus config set <key> <value>");
841
+ return;
842
+ }
843
+ const rawValue = args[3];
844
+ if (!key || rawValue === undefined) {
845
+ console.log("Usage: loophaus config set <key> <value>");
846
+ return;
847
+ }
848
+ if (!KNOWN_KEYS[key]) {
849
+ console.log(`Warning: '${key}' is not a known config key.`);
850
+ }
851
+ const config = await readConfig();
852
+ const parsed = rawValue === "true" ? true : rawValue === "false" ? false : isNaN(Number(rawValue)) ? rawValue : Number(rawValue);
853
+ setNestedValue(config, key, parsed);
854
+ await writeConfig(config);
855
+ console.log(`Set ${key} = ${String(parsed)}`);
856
+ return;
857
+ }
858
+ console.log("Usage: loophaus config [list|get|set] [key] [value]");
859
+ }
860
+ function getNestedValue(obj, path) {
861
+ const parts = path.split(".");
862
+ let current = obj;
863
+ for (const part of parts) {
864
+ if (current == null || typeof current !== "object")
865
+ return undefined;
866
+ current = current[part];
867
+ }
868
+ return current;
869
+ }
870
+ function setNestedValue(obj, path, value) {
871
+ const parts = path.split(".");
872
+ let current = obj;
873
+ for (let i = 0; i < parts.length - 1; i++) {
874
+ if (!(parts[i] in current) || typeof current[parts[i]] !== "object") {
875
+ current[parts[i]] = {};
876
+ }
877
+ current = current[parts[i]];
878
+ }
879
+ current[parts[parts.length - 1]] = value;
880
+ }
686
881
  try {
687
882
  switch (command) {
688
883
  case "install":
@@ -724,6 +919,15 @@ try {
724
919
  case "clean":
725
920
  await runCleanCmd();
726
921
  break;
922
+ case "config":
923
+ await runConfigCmd();
924
+ break;
925
+ case "update-check":
926
+ await runUpdateCheck();
927
+ break;
928
+ case "upgrade":
929
+ await runUpgrade();
930
+ break;
727
931
  case "sessions":
728
932
  await runSessions();
729
933
  break;
@@ -1,11 +1,6 @@
1
1
  ---
2
2
  description: "Cancel active Ralph Loop"
3
- allowed-tools:
4
- [
5
- "Bash(test -f .claude/ralph-loop.local.md:*)",
6
- "Bash(rm .claude/ralph-loop.local.md)",
7
- "Read(.claude/ralph-loop.local.md)",
8
- ]
3
+ allowed-tools: ["Bash", "Read"]
9
4
  hide-from-slash-command-tool: "true"
10
5
  ---
11
6
 
@@ -13,11 +8,11 @@ hide-from-slash-command-tool: "true"
13
8
 
14
9
  To cancel the Ralph loop:
15
10
 
16
- 1. Check if `.claude/ralph-loop.local.md` exists using Bash: `test -f .claude/ralph-loop.local.md && echo "EXISTS" || echo "NOT_FOUND"`
11
+ 1. Check if `.claude/ralph-loop.local.md` exists using a cross-platform Node command.
17
12
 
18
13
  2. **If NOT_FOUND**: Say "No active Ralph loop found."
19
14
 
20
15
  3. **If EXISTS**:
21
16
  - Read `.claude/ralph-loop.local.md` to get the current iteration number from the `iteration:` field
22
- - Remove the file using Bash: `rm .claude/ralph-loop.local.md`
17
+ - Remove the file using a cross-platform Node file command
23
18
  - Report: "Cancelled Ralph loop (was at iteration N)" where N is the iteration value
@@ -11,6 +11,49 @@ The user runs `/loop-plan` once and gets a single merged branch with all work do
11
11
 
12
12
  ---
13
13
 
14
+ ## Pre-flight: Update Check
15
+
16
+ Run `loophaus update-check` silently before starting.
17
+
18
+ If the command reports an available update:
19
+ 1. Show: `loophaus update available: v{current} → v{latest}`
20
+ 2. Ask user:
21
+ - **A) Upgrade now** → run `loophaus upgrade`, then continue
22
+ - **B) Always auto-upgrade** → run `loophaus config set autoUpgrade true && loophaus upgrade`, then continue
23
+ - **C) Not now** → run `loophaus config set updateCheck snoozed` silently, then continue
24
+ - **D) Never ask again** → run `loophaus config set updateCheck false`, then continue
25
+
26
+ If auto-upgrade is enabled (`loophaus config get autoUpgrade` returns `true`):
27
+ - Skip prompt, run `loophaus upgrade` silently, continue.
28
+
29
+ If no update is available or the check fails: continue silently and never block the user.
30
+
31
+ ## Pre-flight: Skill Routing Check
32
+
33
+ Check if the project has a CLAUDE.md with loophaus skill routing:
34
+
35
+ ```bash
36
+ CLAUDE_MD="$(pwd)/CLAUDE.md"
37
+ ROUTING_MARKER=".loophaus/routing-offered.json"
38
+ ```
39
+
40
+ If CLAUDE.md exists but has no `## loophaus skill routing` section, AND routing hasn't been offered before:
41
+ 1. Suggest adding this section to CLAUDE.md:
42
+ ```markdown
43
+ ## loophaus skill routing
44
+
45
+ | User intent | Route to |
46
+ |-------------|----------|
47
+ | Feature implementation, refactoring, multi-step task | `/loophaus:loop-plan` |
48
+ | "start the loop", "continue implementing" | `/loophaus:loop` |
49
+ | "stop the loop", "cancel" | `/loophaus:loop-stop` |
50
+ | "what's the status", "how far along" | `/loophaus:loop-pulse` |
51
+ ```
52
+ 2. If user agrees, add the section to CLAUDE.md
53
+ 3. Write `{ "offeredAt": "<ISO date>" }` to `.loophaus/routing-offered.json` so we don't ask again
54
+
55
+ If CLAUDE.md doesn't exist or routing already offered: skip silently.
56
+
14
57
  ## Phase 0: Cleanup Previous Data
15
58
 
16
59
  Before starting a new plan, apply the cleanup policy from `.loophaus/config.json`: