@graypark/loophaus 3.4.0 → 3.5.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.
Files changed (151) hide show
  1. package/README.ko.md +81 -17
  2. package/README.md +69 -15
  3. package/dist/.claude-plugin/plugin.json +11 -0
  4. package/dist/LICENSE +21 -0
  5. package/dist/README.ko.md +422 -0
  6. package/dist/README.md +336 -0
  7. package/dist/bin/install.d.ts +3 -0
  8. package/dist/bin/install.d.ts.map +1 -0
  9. package/{bin/install.mjs → dist/bin/install.js} +3 -5
  10. package/dist/bin/install.js.map +1 -0
  11. package/dist/bin/loophaus.d.ts +3 -0
  12. package/dist/bin/loophaus.d.ts.map +1 -0
  13. package/dist/bin/loophaus.js +654 -0
  14. package/dist/bin/loophaus.js.map +1 -0
  15. package/dist/bin/uninstall.d.ts +8 -0
  16. package/dist/bin/uninstall.d.ts.map +1 -0
  17. package/dist/bin/uninstall.js +209 -0
  18. package/dist/bin/uninstall.js.map +1 -0
  19. package/dist/codex/commands/cancel-ralph.md +30 -0
  20. package/dist/codex/commands/ralph-loop.md +73 -0
  21. package/dist/commands/cancel-ralph.md +23 -0
  22. package/dist/commands/help.md +96 -0
  23. package/dist/commands/loop-plan.md +257 -0
  24. package/dist/commands/loop-pulse.md +38 -0
  25. package/dist/commands/loop-stop.md +29 -0
  26. package/dist/commands/loop.md +17 -0
  27. package/dist/commands/ralph-loop.md +18 -0
  28. package/dist/core/cost-tracker.d.ts +33 -0
  29. package/dist/core/cost-tracker.d.ts.map +1 -0
  30. package/dist/core/cost-tracker.js +41 -0
  31. package/dist/core/cost-tracker.js.map +1 -0
  32. package/dist/core/engine.d.ts +4 -0
  33. package/dist/core/engine.d.ts.map +1 -0
  34. package/dist/core/engine.js +109 -0
  35. package/dist/core/engine.js.map +1 -0
  36. package/dist/core/event-logger.d.ts +5 -0
  37. package/dist/core/event-logger.d.ts.map +1 -0
  38. package/dist/core/event-logger.js +48 -0
  39. package/dist/core/event-logger.js.map +1 -0
  40. package/dist/core/events.d.ts +34 -0
  41. package/dist/core/events.d.ts.map +1 -0
  42. package/dist/core/events.js +44 -0
  43. package/dist/core/events.js.map +1 -0
  44. package/dist/core/io-helpers.d.ts +3 -0
  45. package/dist/core/io-helpers.d.ts.map +1 -0
  46. package/dist/core/io-helpers.js +65 -0
  47. package/dist/core/io-helpers.js.map +1 -0
  48. package/dist/core/loop-registry.d.ts +10 -0
  49. package/dist/core/loop-registry.d.ts.map +1 -0
  50. package/dist/core/loop-registry.js +37 -0
  51. package/dist/core/loop-registry.js.map +1 -0
  52. package/dist/core/merge-strategy.d.ts +7 -0
  53. package/dist/core/merge-strategy.d.ts.map +1 -0
  54. package/dist/core/merge-strategy.js +82 -0
  55. package/dist/core/merge-strategy.js.map +1 -0
  56. package/dist/core/parallel-runner.d.ts +32 -0
  57. package/dist/core/parallel-runner.d.ts.map +1 -0
  58. package/dist/core/parallel-runner.js +88 -0
  59. package/dist/core/parallel-runner.js.map +1 -0
  60. package/dist/core/policy.d.ts +22 -0
  61. package/dist/core/policy.d.ts.map +1 -0
  62. package/dist/core/policy.js +54 -0
  63. package/dist/core/policy.js.map +1 -0
  64. package/dist/core/quality-scorer.d.ts +40 -0
  65. package/dist/core/quality-scorer.d.ts.map +1 -0
  66. package/dist/core/quality-scorer.js +128 -0
  67. package/dist/core/quality-scorer.js.map +1 -0
  68. package/dist/core/refine-loop.d.ts +16 -0
  69. package/dist/core/refine-loop.d.ts.map +1 -0
  70. package/dist/core/refine-loop.js +26 -0
  71. package/dist/core/refine-loop.js.map +1 -0
  72. package/dist/core/session.d.ts +27 -0
  73. package/dist/core/session.d.ts.map +1 -0
  74. package/dist/core/session.js +67 -0
  75. package/dist/core/session.js.map +1 -0
  76. package/dist/core/trace-analyzer.d.ts +28 -0
  77. package/dist/core/trace-analyzer.d.ts.map +1 -0
  78. package/dist/core/trace-analyzer.js +46 -0
  79. package/dist/core/trace-analyzer.js.map +1 -0
  80. package/dist/core/types.d.ts +99 -0
  81. package/dist/core/types.d.ts.map +1 -0
  82. package/dist/core/types.js +2 -0
  83. package/dist/core/types.js.map +1 -0
  84. package/dist/core/validate.d.ts +7 -0
  85. package/dist/core/validate.d.ts.map +1 -0
  86. package/dist/core/validate.js +55 -0
  87. package/dist/core/validate.js.map +1 -0
  88. package/dist/core/worktree.d.ts +13 -0
  89. package/dist/core/worktree.d.ts.map +1 -0
  90. package/dist/core/worktree.js +108 -0
  91. package/dist/core/worktree.js.map +1 -0
  92. package/dist/hooks/hooks.json +15 -0
  93. package/dist/hooks/stop-hook.mjs +111 -0
  94. package/dist/lib/paths.d.ts +18 -0
  95. package/dist/lib/paths.d.ts.map +1 -0
  96. package/dist/lib/paths.js +74 -0
  97. package/dist/lib/paths.js.map +1 -0
  98. package/dist/lib/stop-hook-core.d.ts +19 -0
  99. package/dist/lib/stop-hook-core.d.ts.map +1 -0
  100. package/dist/lib/stop-hook-core.js +36 -0
  101. package/dist/lib/stop-hook-core.js.map +1 -0
  102. package/dist/package.json +61 -0
  103. package/dist/platforms/claude-code/adapter.mjs +20 -0
  104. package/dist/platforms/claude-code/installer.d.mts +3 -0
  105. package/dist/platforms/claude-code/installer.mjs +173 -0
  106. package/dist/platforms/codex-cli/adapter.mjs +20 -0
  107. package/dist/platforms/codex-cli/installer.d.mts +2 -0
  108. package/dist/platforms/codex-cli/installer.mjs +247 -0
  109. package/dist/platforms/kiro-cli/adapter.mjs +21 -0
  110. package/dist/platforms/kiro-cli/installer.d.mts +3 -0
  111. package/dist/platforms/kiro-cli/installer.mjs +257 -0
  112. package/dist/scripts/setup-ralph-loop.sh +145 -0
  113. package/dist/skills/ralph-claude-cancel/SKILL.md +23 -0
  114. package/dist/skills/ralph-claude-interview/SKILL.md +184 -0
  115. package/dist/skills/ralph-claude-loop/SKILL.md +101 -0
  116. package/dist/skills/ralph-claude-orchestrator/SKILL.md +129 -0
  117. package/dist/skills/ralph-interview/SKILL.md +275 -0
  118. package/dist/skills/ralph-orchestrator/SKILL.md +254 -0
  119. package/dist/store/state-store.d.ts +17 -0
  120. package/dist/store/state-store.d.ts.map +1 -0
  121. package/dist/store/state-store.js +108 -0
  122. package/dist/store/state-store.js.map +1 -0
  123. package/hooks/stop-hook.mjs +6 -6
  124. package/package.json +11 -7
  125. package/platforms/claude-code/installer.d.mts +3 -0
  126. package/platforms/claude-code/installer.mjs +2 -2
  127. package/platforms/codex-cli/installer.d.mts +2 -0
  128. package/platforms/codex-cli/installer.mjs +1 -1
  129. package/platforms/kiro-cli/installer.d.mts +3 -0
  130. package/bin/loophaus.mjs +0 -521
  131. package/bin/uninstall.mjs +0 -255
  132. package/core/cost-tracker.mjs +0 -44
  133. package/core/engine.mjs +0 -123
  134. package/core/event-logger.mjs +0 -37
  135. package/core/events.mjs +0 -48
  136. package/core/io-helpers.mjs +0 -33
  137. package/core/loop-registry.mjs +0 -37
  138. package/core/loop.schema.json +0 -29
  139. package/core/merge-strategy.mjs +0 -72
  140. package/core/parallel-runner.mjs +0 -94
  141. package/core/policy.mjs +0 -58
  142. package/core/quality-scorer.mjs +0 -136
  143. package/core/refine-loop.mjs +0 -29
  144. package/core/session.mjs +0 -66
  145. package/core/state.schema.json +0 -24
  146. package/core/trace-analyzer.mjs +0 -51
  147. package/core/validate.mjs +0 -54
  148. package/core/worktree.mjs +0 -97
  149. package/lib/paths.mjs +0 -99
  150. package/lib/stop-hook-core.mjs +0 -42
  151. package/store/state-store.mjs +0 -106
@@ -0,0 +1,257 @@
1
+ ---
2
+ description: "Plan and start loop via interactive interview — auto-parallelizes across worktrees"
3
+ argument-hint: "TASK_DESCRIPTION"
4
+ allowed-tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "Agent", "Skill"]
5
+ ---
6
+
7
+ # /loop-plan — Plan, Parallelize, Execute, Merge
8
+
9
+ End-to-end workflow: interview → PRD → parallel distribution → loop execution → merge.
10
+ The user runs `/loop-plan` once and gets a single merged branch with all work done.
11
+
12
+ ---
13
+
14
+ ## Phase 1: Discovery Interview
15
+
16
+ Ask 3-5 focused questions about $ARGUMENTS:
17
+
18
+ | Category | What to confirm |
19
+ |----------|----------------|
20
+ | Scope | Single feature? Multi-file? Full refactor? |
21
+ | Success criteria | What counts as "done"? |
22
+ | Verification | `npm test`, `npx tsc`, lint commands? |
23
+ | References | Existing code, patterns to follow? |
24
+ | Parallelism | Multiple services? Independent file groups? |
25
+ | Constraints | Must not break existing tests? Library restrictions? |
26
+
27
+ One round of questions only. Skip questions already answered in $ARGUMENTS.
28
+
29
+ ## Phase 2: PRD Generation
30
+
31
+ Generate `prd.json`:
32
+
33
+ ```json
34
+ {
35
+ "title": "<project title>",
36
+ "description": "<1-2 sentence summary>",
37
+ "userStories": [
38
+ {
39
+ "id": "US-001",
40
+ "title": "<story title>",
41
+ "description": "<what to implement>",
42
+ "acceptanceCriteria": ["<criterion 1>", "<criterion 2>"],
43
+ "priority": 1,
44
+ "passes": false,
45
+ "group": "<ownership group — e.g., frontend, backend, auth, database>",
46
+ "testCommand": "<optional: npm test -- US-001>"
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ Rules:
53
+ - Right-size stories: each completable in 1-2 iterations
54
+ - Assign `group` to each story for parallel distribution
55
+ - Order by `priority` (dependencies first)
56
+ - Include `testCommand` when verification is possible
57
+
58
+ Present PRD to user for approval before proceeding.
59
+
60
+ ## Phase 3: Parallelism Assessment
61
+
62
+ Score the task for parallel execution:
63
+
64
+ | Factor | Score |
65
+ |--------|-------|
66
+ | Stories span 3+ directories | +2 |
67
+ | Stories are independent (no shared state) | +2 |
68
+ | Multiple services (frontend/backend/auth) | +3 |
69
+ | 6+ stories total | +1 |
70
+ | Stories need full codebase context | -2 |
71
+ | Strict ordering required | -3 |
72
+ | Cross-file understanding needed | -1 |
73
+
74
+ **Decision:**
75
+ - Score >= 3 → **Parallel mode** (distribute across worktrees by `group`)
76
+ - Score < 3 → **Sequential mode** (single loop, stories in order)
77
+
78
+ Display the score and recommendation to the user. Proceed with the recommended mode unless the user overrides.
79
+
80
+ ## Phase 4A: Parallel Execution (score >= 3)
81
+
82
+ ### Step 1: Distribute stories by group
83
+
84
+ Group stories by their `group` field. Each group becomes a worktree.
85
+
86
+ ```bash
87
+ # Create the current branch as base for worktrees
88
+ BASE_BRANCH=$(git branch --show-current)
89
+ ```
90
+
91
+ ### Step 2: Create worktrees and distribute PRDs
92
+
93
+ For each group, use the Agent tool to spawn a subagent in an isolated worktree:
94
+
95
+ ```
96
+ Agent(
97
+ prompt: "Implement the following stories from prd.json in this worktree.
98
+ Work on one story at a time. For each story:
99
+ 1. Read prd.json and pick the next story where passes=false
100
+ 2. Implement the story
101
+ 3. Verify with the test command if provided
102
+ 4. Set passes=true in prd.json
103
+ 5. Commit: git add -A && git commit -m 'feat: <story-id> <title>'
104
+
105
+ When ALL stories pass, output <promise>TASK COMPLETE</promise>.
106
+
107
+ Stories assigned to you:
108
+ <filtered stories for this group>",
109
+ isolation: "worktree",
110
+ run_in_background: true,
111
+ name: "<group-name>"
112
+ )
113
+ ```
114
+
115
+ Launch ALL group agents in a single message (parallel execution).
116
+
117
+ ### Step 3: Wait and collect results
118
+
119
+ Monitor agent completion. When all agents finish:
120
+
121
+ 1. List completed worktrees and their branches
122
+ 2. For each worktree branch, check if all assigned stories passed
123
+
124
+ ### Step 4: Merge results
125
+
126
+ Merge all worktree branches back to the base branch using squash strategy:
127
+
128
+ ```bash
129
+ git checkout $BASE_BRANCH
130
+
131
+ # For each completed worktree branch:
132
+ git merge --squash loophaus/<group-name>
133
+ git commit -m "feat: merge <group-name> stories"
134
+ ```
135
+
136
+ If merge conflicts occur:
137
+ 1. Report the conflict to the user
138
+ 2. Suggest manual resolution
139
+ 3. Do NOT force-resolve
140
+
141
+ ### Step 5: Cleanup
142
+
143
+ ```bash
144
+ # Remove worktrees
145
+ git worktree list | grep .loophaus/worktrees | awk '{print $1}' | xargs -I {} git worktree remove {} --force
146
+ # Remove branches
147
+ git branch | grep loophaus/ | xargs git branch -D
148
+ ```
149
+
150
+ ### Step 6: Final verification
151
+
152
+ Run the full test/verification command on the merged result.
153
+ Update the main `prd.json` with all stories marked as `passes: true`.
154
+
155
+ ## Phase 4B: Sequential Execution (score < 3)
156
+
157
+ Single loop, no worktrees:
158
+
159
+ 1. Create `.loophaus/state.json`:
160
+ ```json
161
+ {
162
+ "active": true,
163
+ "prompt": "Read prd.json. Pick next story where passes=false. Implement, verify, commit. Update progress.txt.",
164
+ "completionPromise": "TASK COMPLETE",
165
+ "maxIterations": <stories * 2 + 3>,
166
+ "currentIteration": 0,
167
+ "sessionId": ""
168
+ }
169
+ ```
170
+
171
+ 2. Start working on US-001 immediately.
172
+ 3. Each iteration: implement one story, verify, commit, update prd.json.
173
+ 4. Output `<promise>TASK COMPLETE</promise>` when ALL stories pass.
174
+
175
+ ## Phase 5: Evaluate
176
+
177
+ After all stories are implemented (parallel or sequential), evaluate each:
178
+
179
+ For each story in prd.json:
180
+ 1. Run testCommand if defined
181
+ 2. Run typecheck if project has tsconfig.json: `npx tsc --noEmit`
182
+ 3. Run lint if project has eslint config: `npx eslint . --quiet`
183
+ 4. Check .loophaus/verify.sh if exists
184
+ 5. Analyze git diff size
185
+
186
+ Score each story 0-100. Record in `.loophaus/results.tsv`.
187
+
188
+ Display quality dashboard:
189
+ ```
190
+ Quality Evaluation
191
+ ──────────────────
192
+ US-001 Add login API score: 65 (D) <- needs refinement
193
+ US-002 Add auth middleware score: 92 (A) ✓
194
+ US-003 Add login UI score: 45 (F) <- needs refinement
195
+
196
+ Overall: 67/100 — threshold: 80
197
+ Stories needing refinement: 2
198
+ ```
199
+
200
+ ## Phase 6: Refine Loop (autoresearch pattern)
201
+
202
+ For each story below the quality threshold (default: 80):
203
+
204
+ LOOP (max 3 attempts per story):
205
+ 1. Git checkpoint: `git add -A && git commit -m "checkpoint: <story-id> attempt <N>"`
206
+ 2. Read the quality feedback (which criteria failed, error messages)
207
+ 3. Re-implement with a different approach, focusing on weak areas
208
+ 4. Re-evaluate (same criteria as Phase 5)
209
+ 5. If score improved -> KEEP (advance the commit)
210
+ If score same or worse -> DISCARD (git reset --hard to checkpoint)
211
+ 6. Record attempt in .loophaus/results.tsv
212
+ 7. If score >= threshold -> DONE with this story
213
+ If max attempts reached -> move on (best-effort)
214
+
215
+ After all refinements:
216
+ ```
217
+ Refinement Complete
218
+ ───────────────────
219
+ US-001 65 -> 82 (B) ✓ (2 attempts)
220
+ US-003 45 -> 78 (C) (3 attempts, best effort)
221
+
222
+ Overall: 84/100 — PASS
223
+ ```
224
+
225
+ CRITICAL: The refine loop uses git reset --hard to discard bad attempts. This is the autoresearch pattern — safe because we always checkpoint first.
226
+
227
+ ## Phase 7: Summary Report
228
+
229
+ After completion (parallel or sequential), output:
230
+
231
+ ```
232
+ Loop Plan Complete
233
+ ══════════════════
234
+
235
+ Mode: parallel (3 worktrees) | sequential
236
+ Stories: 7/7 done
237
+ Duration: ~15 minutes
238
+ Iterations: 12 total (across all workers)
239
+
240
+ Stories:
241
+ ✓ US-001 Add login API (backend, 2 iterations)
242
+ ✓ US-002 Add auth middleware (backend, 1 iteration)
243
+ ✓ US-003 Add login UI (frontend, 3 iterations)
244
+ ...
245
+
246
+ Branch: feature/auth-system (all work merged)
247
+ Verify: npm test ✓
248
+ ```
249
+
250
+ ## Rules
251
+
252
+ - ALWAYS present PRD for user approval before execution
253
+ - ALWAYS show parallelism score and recommendation
254
+ - If parallel: launch ALL agents simultaneously (single message with multiple Agent calls)
255
+ - If merge conflict: STOP and report. Do not auto-resolve.
256
+ - Use `<promise>TASK COMPLETE</promise>` ONLY when genuinely complete
257
+ - Update `progress.txt` with learnings after each story
@@ -0,0 +1,38 @@
1
+ ---
2
+ description: "Check loop status"
3
+ allowed-tools:
4
+ [
5
+ "Read(.loophaus/state.json)",
6
+ "Read(.claude/ralph-loop.local.md)",
7
+ "Read(prd.json)",
8
+ "Read(progress.txt)",
9
+ ]
10
+ ---
11
+
12
+ # /loop-pulse — Check Loop Status
13
+
14
+ 1. Read `.loophaus/state.json` (or legacy `.claude/ralph-loop.local.md`)
15
+ - If neither exists: "No active loop."
16
+
17
+ 2. If active, display:
18
+ ```
19
+ Loop Status
20
+ ───────────
21
+ Active: yes
22
+ Iteration: 5/20
23
+ Promise: TASK COMPLETE
24
+ ```
25
+
26
+ 3. If `prd.json` exists, also show:
27
+ ```
28
+ Stories
29
+ ───────
30
+ ✓ US-001 Add login API
31
+ ✓ US-002 Add auth middleware
32
+ → US-003 Add JWT refresh (in progress)
33
+ US-004 Add logout endpoint
34
+
35
+ Progress: 2/4 done
36
+ ```
37
+
38
+ 4. If `progress.txt` exists, show last 5 lines.
@@ -0,0 +1,29 @@
1
+ ---
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
+ ]
12
+ ---
13
+
14
+ # /loop-stop — Stop Active Loop
15
+
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"`
18
+
19
+ 2. **If NOT_FOUND** on both: Say "No active loop found."
20
+
21
+ 3. **If EXISTS** (.loophaus/state.json):
22
+ - Read the file to get `currentIteration`
23
+ - Remove it: `rm .loophaus/state.json`
24
+ - Report: "Stopped loop at iteration N."
25
+
26
+ 4. **If LEGACY** (.claude/ralph-loop.local.md):
27
+ - Read it to get the iteration field
28
+ - Remove it: `rm .claude/ralph-loop.local.md`
29
+ - Report: "Stopped loop at iteration N. (migrated from legacy path)"
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: "Start iterative dev loop"
3
+ argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
4
+ allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
5
+ ---
6
+
7
+ # /loop — Start Iterative Dev Loop
8
+
9
+ Execute the setup script to initialize the loop:
10
+
11
+ ```!
12
+ "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
13
+ ```
14
+
15
+ 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.
16
+
17
+ CRITICAL: If a completion promise is set, ONLY output it when genuinely complete. Do not output false promises to escape the loop.
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: "Start Ralph Loop in current session"
3
+ argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
4
+ allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
5
+ hide-from-slash-command-tool: "true"
6
+ ---
7
+
8
+ # Ralph Loop Command
9
+
10
+ Execute the setup script to initialize the Ralph loop:
11
+
12
+ ```!
13
+ "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
14
+ ```
15
+
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.
17
+
18
+ CRITICAL RULE: If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. The loop is designed to continue until genuine completion.
@@ -0,0 +1,33 @@
1
+ interface ModelPrice {
2
+ input: number;
3
+ output: number;
4
+ }
5
+ interface CostBreakdown {
6
+ inputCost: number;
7
+ outputCost: number;
8
+ totalCost: number;
9
+ }
10
+ interface CostRecord extends CostBreakdown {
11
+ label: string;
12
+ model: string;
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ ts: string;
16
+ }
17
+ interface TrackerSummary {
18
+ totalInput: number;
19
+ totalOutput: number;
20
+ totalCost: number;
21
+ records: number;
22
+ }
23
+ export interface CostTracker {
24
+ record(label: string, model: string, inputTokens: number, outputTokens: number): CostBreakdown;
25
+ summary(): TrackerSummary;
26
+ getRecords(): CostRecord[];
27
+ }
28
+ declare const MODEL_PRICES: Record<string, ModelPrice>;
29
+ export declare function estimateCost(model: string, inputTokens: number, outputTokens: number): CostBreakdown;
30
+ export declare function formatCost(cost: number): string;
31
+ export declare function createTracker(): CostTracker;
32
+ export { MODEL_PRICES };
33
+ //# sourceMappingURL=cost-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-tracker.d.ts","sourceRoot":"","sources":["../../core/cost-tracker.ts"],"names":[],"mappings":"AAEA,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAW,SAAQ,aAAa;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/F,OAAO,IAAI,cAAc,CAAC;IAC1B,UAAU,IAAI,UAAU,EAAE,CAAC;CAC5B;AAED,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAS5C,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,aAAa,CAKpG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,aAAa,IAAI,WAAW,CAgB3C;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,41 @@
1
+ // core/cost-tracker.ts — Token cost estimation. Prices in USD per 1M tokens.
2
+ const MODEL_PRICES = {
3
+ "claude-opus-4": { input: 15, output: 75 },
4
+ "claude-sonnet-4": { input: 3, output: 15 },
5
+ "claude-haiku-4": { input: 0.8, output: 4 },
6
+ "gpt-4.1": { input: 2, output: 8 },
7
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
8
+ "gpt-4o": { input: 2.5, output: 10 },
9
+ "o4-mini": { input: 1.1, output: 4.4 },
10
+ "default": { input: 3, output: 15 },
11
+ };
12
+ export function estimateCost(model, inputTokens, outputTokens) {
13
+ const prices = MODEL_PRICES[model] || MODEL_PRICES["default"];
14
+ const inputCost = (inputTokens / 1_000_000) * prices.input;
15
+ const outputCost = (outputTokens / 1_000_000) * prices.output;
16
+ return { inputCost, outputCost, totalCost: inputCost + outputCost };
17
+ }
18
+ export function formatCost(cost) {
19
+ if (cost < 0.01)
20
+ return `$${(cost * 100).toFixed(2)}¢`;
21
+ return `$${cost.toFixed(4)}`;
22
+ }
23
+ export function createTracker() {
24
+ const records = [];
25
+ return {
26
+ record(label, model, inputTokens, outputTokens) {
27
+ const cost = estimateCost(model, inputTokens, outputTokens);
28
+ records.push({ label, model, inputTokens, outputTokens, ...cost, ts: new Date().toISOString() });
29
+ return cost;
30
+ },
31
+ summary() {
32
+ const totalInput = records.reduce((s, r) => s + r.inputTokens, 0);
33
+ const totalOutput = records.reduce((s, r) => s + r.outputTokens, 0);
34
+ const totalCost = records.reduce((s, r) => s + r.totalCost, 0);
35
+ return { totalInput, totalOutput, totalCost, records: records.length };
36
+ },
37
+ getRecords() { return [...records]; },
38
+ };
39
+ }
40
+ export { MODEL_PRICES };
41
+ //# sourceMappingURL=cost-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-tracker.js","sourceRoot":"","sources":["../../core/cost-tracker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { LoopState, StopHookInput, StopHookResult } from "./types.js";
2
+ export declare function evaluateStopHook(input: StopHookInput, state: LoopState): StopHookResult;
3
+ export declare function extractPromise(text: string, promisePhrase: string): boolean;
4
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../core/engine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,cAAc,EAGf,MAAM,YAAY,CAAC;AAEpB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,CA+GvF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAG3E"}
@@ -0,0 +1,109 @@
1
+ // Pure function: no I/O, no side effects. Returns decision + next state + events.
2
+ export function evaluateStopHook(input, state) {
3
+ const events = [];
4
+ const nextState = { ...state };
5
+ if (!nextState.active) {
6
+ return { decision: "allow", nextState, events, output: null };
7
+ }
8
+ if (nextState.sessionId && input.session_id && nextState.sessionId !== input.session_id) {
9
+ return { decision: "allow", nextState, events, output: null };
10
+ }
11
+ nextState.currentIteration += 1;
12
+ events.push({ event: "iteration", iteration: nextState.currentIteration });
13
+ if (nextState.maxIterations > 0 && nextState.currentIteration > nextState.maxIterations) {
14
+ nextState.active = false;
15
+ events.push({ event: "stop", reason: "max_iterations" });
16
+ return {
17
+ decision: "allow",
18
+ nextState,
19
+ events,
20
+ output: null,
21
+ message: `Loop: max iterations (${nextState.maxIterations}) reached.`,
22
+ };
23
+ }
24
+ if (input.policy_result && input.policy_result.shouldStop) {
25
+ nextState.active = false;
26
+ events.push({ event: "stop", reason: "policy_violation", violations: input.policy_result.violations });
27
+ const reasons = input.policy_result.violations.map(v => `${v.type}: ${v.current}/${v.limit}`).join(", ");
28
+ return {
29
+ decision: "allow",
30
+ nextState,
31
+ events,
32
+ output: null,
33
+ message: `Loop: policy violation (${reasons}).`,
34
+ };
35
+ }
36
+ if (nextState.completionPromise && input.last_assistant_text) {
37
+ if (extractPromise(input.last_assistant_text, nextState.completionPromise)) {
38
+ nextState.active = false;
39
+ events.push({ event: "stop", reason: "completion_promise", promise: nextState.completionPromise });
40
+ return {
41
+ decision: "allow",
42
+ nextState,
43
+ events,
44
+ output: null,
45
+ message: `Loop: completion promise "${nextState.completionPromise}" detected.`,
46
+ };
47
+ }
48
+ }
49
+ // Check verify script result (pre-computed by caller)
50
+ if (nextState.verifyScript && input.verify_result) {
51
+ if (input.verify_result.passed) {
52
+ nextState.active = false;
53
+ events.push({ event: "stop", reason: "verify_script", script: nextState.verifyScript });
54
+ return {
55
+ decision: "allow",
56
+ nextState,
57
+ events,
58
+ output: null,
59
+ message: `Loop: verify script passed.`,
60
+ };
61
+ }
62
+ events.push({ event: "verify_failed", script: nextState.verifyScript, output: input.verify_result.output || "" });
63
+ }
64
+ if (input.test_results && input.test_results.length > 0) {
65
+ const allPassed = input.test_results.every(r => r.passed);
66
+ if (allPassed) {
67
+ events.push({ event: "test_result", status: "all_passed", results: input.test_results });
68
+ }
69
+ else {
70
+ const failed = input.test_results.filter(r => !r.passed);
71
+ events.push({ event: "test_result", status: "some_failed", failed: failed.map(f => f.storyId) });
72
+ }
73
+ }
74
+ if (input.stop_hook_active === true) {
75
+ if (!input.has_pending_stories) {
76
+ nextState.active = false;
77
+ events.push({ event: "stop", reason: "all_stories_done" });
78
+ return {
79
+ decision: "allow",
80
+ nextState,
81
+ events,
82
+ output: null,
83
+ message: "Loop: no pending stories. Allowing exit.",
84
+ };
85
+ }
86
+ events.push({ event: "continue", reason: "pending_stories" });
87
+ }
88
+ events.push({ event: "state_change", from: "running", to: "running" });
89
+ const iterInfo = nextState.maxIterations > 0
90
+ ? `${nextState.currentIteration}/${nextState.maxIterations}`
91
+ : `${nextState.currentIteration}`;
92
+ const reason = [nextState.prompt, "", "---", `Loop iteration ${iterInfo}. Continue working on the task above.`].join("\n");
93
+ const output = { decision: "block", reason };
94
+ if (nextState.completionPromise) {
95
+ output.systemMessage = `Loop iteration ${iterInfo} | To stop: output <promise>${nextState.completionPromise}</promise> (ONLY when TRUE)`;
96
+ }
97
+ else {
98
+ output.systemMessage = `Loop iteration ${iterInfo} | No completion promise — loop runs until max iterations`;
99
+ }
100
+ return { decision: "block", nextState, events, output };
101
+ }
102
+ export function extractPromise(text, promisePhrase) {
103
+ const regex = new RegExp(`<promise>\\s*${escapeRegex(promisePhrase)}\\s*</promise>`, "s");
104
+ return regex.test(text);
105
+ }
106
+ function escapeRegex(str) {
107
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
+ }
109
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../core/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { LoopEvent } from "./types.js";
2
+ export declare function getTracePath(cwd?: string): string;
3
+ export declare function logEvents(events: LoopEvent[], metadata?: Record<string, unknown>, cwd?: string): Promise<void>;
4
+ export declare function readTrace(cwd?: string): Promise<Record<string, unknown>[]>;
5
+ //# sourceMappingURL=event-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-logger.d.ts","sourceRoot":"","sources":["../../core/event-logger.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAY5C,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAexH;AAED,wBAAsB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAahF"}
@@ -0,0 +1,48 @@
1
+ // Best-effort trace logger. Failures never affect stop-hook decisions.
2
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
3
+ import { join, dirname } from "node:path";
4
+ const DEBUG = process.env.LOOPHAUS_DEBUG === "1";
5
+ function debug(msg) {
6
+ if (DEBUG)
7
+ process.stderr.write(`[loophaus:debug] ${msg}\n`);
8
+ }
9
+ function isFileNotFound(err) {
10
+ return err != null && typeof err === "object" && ("code" in err) && (err.code === "ENOENT" || err.code === "ENOTDIR");
11
+ }
12
+ export function getTracePath(cwd) {
13
+ return join(cwd || process.cwd(), ".loophaus", "trace.jsonl");
14
+ }
15
+ export async function logEvents(events, metadata = {}, cwd) {
16
+ try {
17
+ const tracePath = getTracePath(cwd);
18
+ await mkdir(dirname(tracePath), { recursive: true });
19
+ const ts = new Date().toISOString();
20
+ const lines = events.map(e => JSON.stringify({ ts, ...metadata, ...e })).join("\n") + "\n";
21
+ await appendFile(tracePath, lines, "utf-8");
22
+ }
23
+ catch (err) {
24
+ debug(`logEvents failed: ${err.message}`);
25
+ // Best-effort: non-ENOENT errors are logged if DEBUG is on, but never thrown
26
+ }
27
+ }
28
+ export async function readTrace(cwd) {
29
+ const tracePath = getTracePath(cwd);
30
+ try {
31
+ const raw = await readFile(tracePath, "utf-8");
32
+ return raw.trim().split("\n").map(line => {
33
+ try {
34
+ return JSON.parse(line);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }).filter((v) => v !== null);
40
+ }
41
+ catch (err) {
42
+ if (!isFileNotFound(err)) {
43
+ debug(`readTrace failed: ${err.message}`);
44
+ }
45
+ return [];
46
+ }
47
+ }
48
+ //# sourceMappingURL=event-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-logger.js","sourceRoot":"","sources":["../../core/event-logger.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAI1C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC;AAEjD,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,KAAK;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,cAAc,CAAC,GAAY;IAClC,OAAO,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAE,GAAwB,CAAC,IAAI,KAAK,QAAQ,IAAK,GAAwB,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AACpK,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAmB,EAAE,WAAoC,EAAE,EAAE,GAAY;IACvG,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAErD,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAEpB,MAAM,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,6EAA6E;IAC/E,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAY;IAC1C,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/C,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACvC,IAAI,CAAC;gBAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,IAAI,CAAC;YAAC,CAAC;QACpF,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAgC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}