@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 +12 -2
- package/README.md +11 -1
- package/commands/cancel-ralph.md +3 -8
- package/commands/loop-plan.md +43 -0
- package/commands/loop-stop.md +5 -13
- package/commands/loop.md +11 -2
- package/commands/ralph-loop.md +2 -2
- package/dist/README.ko.md +12 -2
- package/dist/README.md +11 -1
- package/dist/bin/loophaus.js +206 -2
- package/dist/commands/cancel-ralph.md +3 -8
- package/dist/commands/loop-plan.md +43 -0
- package/dist/commands/loop-stop.md +5 -13
- package/dist/commands/loop.md +11 -2
- package/dist/commands/ralph-loop.md +2 -2
- package/dist/core/benchmark.js +25 -14
- package/dist/core/quality-scorer.js +13 -12
- package/dist/core/update-checker.d.ts +30 -0
- package/dist/core/update-checker.js +173 -0
- package/dist/core/worktree.js +3 -3
- package/dist/hooks/stop-hook.mjs +4 -9
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.js +3 -0
- package/dist/lib/runtime.d.ts +21 -0
- package/dist/lib/runtime.js +77 -0
- package/dist/package.json +1 -1
- package/dist/platforms/claude-code/installer.mjs +2 -0
- package/dist/platforms/codex-cli/installer.mjs +1 -1
- package/dist/scripts/setup-loop.mjs +129 -0
- package/hooks/stop-hook.mjs +4 -9
- package/package.json +1 -1
- package/platforms/claude-code/installer.mjs +2 -0
- package/platforms/codex-cli/installer.mjs +1 -1
- package/scripts/setup-loop.mjs +129 -0
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 |
|
|
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
|
|
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
|
|
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
|
package/commands/cancel-ralph.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/commands/loop-plan.md
CHANGED
|
@@ -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`:
|
package/commands/loop-stop.md
CHANGED
|
@@ -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
|
|
17
|
-
- If not found, also check legacy path
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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.
|
package/commands/ralph-loop.md
CHANGED
|
@@ -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
|
|
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-
|
|
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 |
|
|
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
|
|
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
|
|
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
|
package/dist/bin/loophaus.js
CHANGED
|
@@ -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", "
|
|
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
|
|
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
|
|
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`:
|