@graypark/loophaus 3.6.0 → 3.7.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 (104) hide show
  1. package/commands/loop-plan.md +47 -0
  2. package/commands/loop.md +11 -0
  3. package/dist/bin/install.d.ts +0 -1
  4. package/dist/bin/install.js +0 -1
  5. package/dist/bin/loophaus.d.ts +0 -1
  6. package/dist/bin/loophaus.js +205 -3
  7. package/dist/bin/uninstall.d.ts +0 -1
  8. package/dist/bin/uninstall.js +0 -1
  9. package/dist/commands/loop-plan.md +47 -0
  10. package/dist/commands/loop.md +11 -0
  11. package/dist/core/benchmark.d.ts +0 -1
  12. package/dist/core/benchmark.js +0 -1
  13. package/dist/core/cleanup.d.ts +0 -1
  14. package/dist/core/cleanup.js +0 -1
  15. package/dist/core/cost-tracker.d.ts +0 -1
  16. package/dist/core/cost-tracker.js +0 -1
  17. package/dist/core/engine.d.ts +0 -1
  18. package/dist/core/engine.js +0 -1
  19. package/dist/core/event-logger.d.ts +0 -1
  20. package/dist/core/event-logger.js +0 -1
  21. package/dist/core/events.d.ts +0 -1
  22. package/dist/core/events.js +0 -1
  23. package/dist/core/io-helpers.d.ts +0 -1
  24. package/dist/core/io-helpers.js +0 -1
  25. package/dist/core/loop-registry.d.ts +0 -1
  26. package/dist/core/loop-registry.js +0 -1
  27. package/dist/core/merge-strategy.d.ts +0 -1
  28. package/dist/core/merge-strategy.js +0 -1
  29. package/dist/core/parallel-runner.d.ts +0 -1
  30. package/dist/core/parallel-runner.js +0 -1
  31. package/dist/core/policy.d.ts +0 -1
  32. package/dist/core/policy.js +0 -1
  33. package/dist/core/quality-scorer.d.ts +0 -1
  34. package/dist/core/quality-scorer.js +0 -1
  35. package/dist/core/refine-loop.d.ts +0 -1
  36. package/dist/core/refine-loop.js +0 -1
  37. package/dist/core/session.d.ts +0 -1
  38. package/dist/core/session.js +0 -1
  39. package/dist/core/trace-analyzer.d.ts +0 -1
  40. package/dist/core/trace-analyzer.js +0 -1
  41. package/dist/core/types.d.ts +0 -1
  42. package/dist/core/types.js +0 -1
  43. package/dist/core/update-checker.d.ts +30 -0
  44. package/dist/core/update-checker.js +172 -0
  45. package/dist/core/validate.d.ts +0 -1
  46. package/dist/core/validate.js +0 -1
  47. package/dist/core/worktree.d.ts +0 -1
  48. package/dist/core/worktree.js +0 -1
  49. package/dist/lib/paths.d.ts +0 -1
  50. package/dist/lib/paths.js +0 -1
  51. package/dist/lib/stop-hook-core.d.ts +0 -1
  52. package/dist/lib/stop-hook-core.js +0 -1
  53. package/dist/package.json +1 -1
  54. package/dist/store/state-store.d.ts +0 -1
  55. package/dist/store/state-store.js +0 -1
  56. package/package.json +1 -1
  57. package/dist/bin/install.d.ts.map +0 -1
  58. package/dist/bin/install.js.map +0 -1
  59. package/dist/bin/loophaus.d.ts.map +0 -1
  60. package/dist/bin/loophaus.js.map +0 -1
  61. package/dist/bin/uninstall.d.ts.map +0 -1
  62. package/dist/bin/uninstall.js.map +0 -1
  63. package/dist/core/benchmark.d.ts.map +0 -1
  64. package/dist/core/benchmark.js.map +0 -1
  65. package/dist/core/cleanup.d.ts.map +0 -1
  66. package/dist/core/cleanup.js.map +0 -1
  67. package/dist/core/cost-tracker.d.ts.map +0 -1
  68. package/dist/core/cost-tracker.js.map +0 -1
  69. package/dist/core/engine.d.ts.map +0 -1
  70. package/dist/core/engine.js.map +0 -1
  71. package/dist/core/event-logger.d.ts.map +0 -1
  72. package/dist/core/event-logger.js.map +0 -1
  73. package/dist/core/events.d.ts.map +0 -1
  74. package/dist/core/events.js.map +0 -1
  75. package/dist/core/io-helpers.d.ts.map +0 -1
  76. package/dist/core/io-helpers.js.map +0 -1
  77. package/dist/core/loop-registry.d.ts.map +0 -1
  78. package/dist/core/loop-registry.js.map +0 -1
  79. package/dist/core/merge-strategy.d.ts.map +0 -1
  80. package/dist/core/merge-strategy.js.map +0 -1
  81. package/dist/core/parallel-runner.d.ts.map +0 -1
  82. package/dist/core/parallel-runner.js.map +0 -1
  83. package/dist/core/policy.d.ts.map +0 -1
  84. package/dist/core/policy.js.map +0 -1
  85. package/dist/core/quality-scorer.d.ts.map +0 -1
  86. package/dist/core/quality-scorer.js.map +0 -1
  87. package/dist/core/refine-loop.d.ts.map +0 -1
  88. package/dist/core/refine-loop.js.map +0 -1
  89. package/dist/core/session.d.ts.map +0 -1
  90. package/dist/core/session.js.map +0 -1
  91. package/dist/core/trace-analyzer.d.ts.map +0 -1
  92. package/dist/core/trace-analyzer.js.map +0 -1
  93. package/dist/core/types.d.ts.map +0 -1
  94. package/dist/core/types.js.map +0 -1
  95. package/dist/core/validate.d.ts.map +0 -1
  96. package/dist/core/validate.js.map +0 -1
  97. package/dist/core/worktree.d.ts.map +0 -1
  98. package/dist/core/worktree.js.map +0 -1
  99. package/dist/lib/paths.d.ts.map +0 -1
  100. package/dist/lib/paths.js.map +0 -1
  101. package/dist/lib/stop-hook-core.d.ts.map +0 -1
  102. package/dist/lib/stop-hook-core.js.map +0 -1
  103. package/dist/store/state-store.d.ts.map +0 -1
  104. package/dist/store/state-store.js.map +0 -1
@@ -11,6 +11,53 @@ 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 this silently before starting:
17
+
18
+ ```bash
19
+ _UPDATE=$(loophaus update-check 2>/dev/null | grep "UPGRADE_AVAILABLE" || true)
20
+ ```
21
+
22
+ If `_UPDATE` contains `UPGRADE_AVAILABLE`:
23
+ 1. Show: `loophaus update available: v{current} → v{latest}`
24
+ 2. Ask user:
25
+ - **A) Upgrade now** → run `loophaus upgrade`, then continue
26
+ - **B) Always auto-upgrade** → run `loophaus config set autoUpgrade true && loophaus upgrade`, then continue
27
+ - **C) Not now** → run `loophaus config set updateCheck snoozed` silently, then continue
28
+ - **D) Never ask again** → run `loophaus config set updateCheck false`, then continue
29
+
30
+ If auto-upgrade is enabled (`loophaus config get autoUpgrade` returns `true`):
31
+ - Skip prompt, run `loophaus upgrade` silently, continue.
32
+
33
+ If no update or check fails: continue silently (never block the user).
34
+
35
+ ## Pre-flight: Skill Routing Check
36
+
37
+ Check if the project has a CLAUDE.md with loophaus skill routing:
38
+
39
+ ```bash
40
+ CLAUDE_MD="$(pwd)/CLAUDE.md"
41
+ ROUTING_MARKER=".loophaus/routing-offered.json"
42
+ ```
43
+
44
+ If CLAUDE.md exists but has no `## loophaus skill routing` section, AND routing hasn't been offered before:
45
+ 1. Suggest adding this section to CLAUDE.md:
46
+ ```markdown
47
+ ## loophaus skill routing
48
+
49
+ | User intent | Route to |
50
+ |-------------|----------|
51
+ | Feature implementation, refactoring, multi-step task | `/loophaus:loop-plan` |
52
+ | "start the loop", "continue implementing" | `/loophaus:loop` |
53
+ | "stop the loop", "cancel" | `/loophaus:loop-stop` |
54
+ | "what's the status", "how far along" | `/loophaus:loop-pulse` |
55
+ ```
56
+ 2. If user agrees, add the section to CLAUDE.md
57
+ 3. Write `{ "offeredAt": "<ISO date>" }` to `.loophaus/routing-offered.json` so we don't ask again
58
+
59
+ If CLAUDE.md doesn't exist or routing already offered: skip silently.
60
+
14
61
  ## Phase 0: Cleanup Previous Data
15
62
 
16
63
  Before starting a new plan, apply the cleanup policy from `.loophaus/config.json`:
package/commands/loop.md CHANGED
@@ -6,6 +6,17 @@ allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
6
6
 
7
7
  # /loop — Start Iterative Dev Loop
8
8
 
9
+ ## Pre-flight
10
+
11
+ ```bash
12
+ _UPDATE=$(loophaus update-check 2>/dev/null | grep "UPGRADE_AVAILABLE" || true)
13
+ ```
14
+ If update available and autoUpgrade enabled: run `loophaus upgrade` silently.
15
+ If update available and not auto: show one-line notice `loophaus v{new} available. Run: loophaus upgrade`.
16
+ Otherwise: continue silently.
17
+
18
+ ---
19
+
9
20
  Execute the setup script to initialize the loop:
10
21
 
11
22
  ```!
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=install.d.ts.map
@@ -5,4 +5,3 @@ import { fileURLToPath } from "node:url";
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const loophausCli = resolve(dirname(__filename), "loophaus.js");
7
7
  await import(loophausCli);
8
- //# sourceMappingURL=install.js.map
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=loophaus.d.ts.map
@@ -16,12 +16,13 @@ const showHelp = args.includes("--help") || args.includes("-h");
16
16
  const KNOWN_FLAGS = new Set([
17
17
  "--help", "-h", "--version", "--dry-run", "--force", "--local", "--verbose",
18
18
  "--host", "--claude", "--kiro", "--name", "--speed", "--count", "--base", "--story",
19
- "--all", "--traces", "--sessions", "--results", "--before", "--config",
19
+ "--all", "--traces", "--sessions", "--results", "--before", "--config", "--quiet",
20
20
  ]);
21
21
  const VALID_COMMANDS = [
22
22
  "install", "uninstall", "status", "stats", "loops", "watch",
23
23
  "replay", "compare", "worktree", "parallel", "quality",
24
- "sessions", "resume", "benchmark", "clean", "help",
24
+ "sessions", "resume", "benchmark", "clean", "config",
25
+ "update-check", "upgrade", "help",
25
26
  ];
26
27
  function validateFlags() {
27
28
  for (const arg of args) {
@@ -114,6 +115,9 @@ Usage:
114
115
  npx @graypark/loophaus quality [--story US-001]
115
116
  npx @graypark/loophaus benchmark
116
117
  npx @graypark/loophaus clean [--all|--traces|--sessions|--results] [--before DATE]
118
+ npx @graypark/loophaus config [list|get|set] [key] [value]
119
+ npx @graypark/loophaus update-check
120
+ npx @graypark/loophaus upgrade
117
121
  npx @graypark/loophaus sessions
118
122
  npx @graypark/loophaus resume <session-id>
119
123
  npx @graypark/loophaus --version
@@ -156,6 +160,11 @@ async function detectHosts() {
156
160
  return hosts;
157
161
  }
158
162
  async function runInstall() {
163
+ const { getPackageVersion } = await import("../lib/paths.js");
164
+ const version = getPackageVersion();
165
+ const quiet = args.includes("--quiet");
166
+ const loophausDir = join(process.env.HOME || "~", ".loophaus");
167
+ const welcomePath = join(loophausDir, ".welcome-seen");
159
168
  let targets = [];
160
169
  if (host) {
161
170
  targets = [host];
@@ -192,6 +201,57 @@ async function runInstall() {
192
201
  s?.stop();
193
202
  }
194
203
  }
204
+ if (quiet || dryRun)
205
+ return;
206
+ // First-run welcome or upgrade notice
207
+ const { mkdir: mk, writeFile: wf, readFile: rf } = await import("node:fs/promises");
208
+ await mk(loophausDir, { recursive: true });
209
+ let isFirstRun = false;
210
+ try {
211
+ const seen = await rf(welcomePath, "utf-8");
212
+ // Existing install — show What's New if version changed
213
+ if (seen.trim() !== version) {
214
+ await wf(welcomePath, version, "utf-8");
215
+ try {
216
+ const changelog = await rf(join(__dirname, "..", "CHANGELOG.md"), "utf-8");
217
+ const firstEntry = changelog.match(/## \[[\d.]+\][^\n]*\n([\s\S]*?)(?=\n## \[|$)/);
218
+ if (firstEntry) {
219
+ console.log(`\n \x1b[36mWhat's New in v${version}:\x1b[0m`);
220
+ const lines = firstEntry[1].trim().split("\n").slice(0, 8);
221
+ for (const l of lines)
222
+ console.log(` ${l}`);
223
+ }
224
+ }
225
+ catch { /* no CHANGELOG */ }
226
+ }
227
+ }
228
+ catch {
229
+ isFirstRun = true;
230
+ await wf(welcomePath, version, "utf-8");
231
+ }
232
+ if (isFirstRun) {
233
+ console.log(`
234
+ \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
235
+ Welcome to \x1b[1mloophaus\x1b[0m v${version}
236
+
237
+ Control plane for coding agents.
238
+ Iterative dev loops with quality verification.
239
+
240
+ Quick start:
241
+ /loop-plan <describe your task>
242
+
243
+ Commands:
244
+ /loop-plan Interview → PRD → implement → verify
245
+ /loop Start loop with existing PRD
246
+ /loop-pulse Check progress
247
+ /loop-stop Cancel loop
248
+
249
+ CLI:
250
+ loophaus benchmark Project quality score
251
+ loophaus config list View settings
252
+ loophaus upgrade Update to latest
253
+ \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`);
254
+ }
195
255
  }
196
256
  async function runUninstall() {
197
257
  if (host === "claude-code" || args.includes("--claude")) {
@@ -683,6 +743,140 @@ Options:
683
743
  console.log(" Nothing to clean.");
684
744
  }
685
745
  }
746
+ async function runUpdateCheck() {
747
+ const { getPackageVersion } = await import("../lib/paths.js");
748
+ const { checkForUpdate } = await import("../core/update-checker.js");
749
+ const current = getPackageVersion();
750
+ const result = await checkForUpdate(current);
751
+ console.log("Update Check");
752
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
753
+ console.log(` Current: v${result.current}`);
754
+ console.log(` Latest: v${result.latest}`);
755
+ console.log(` Status: ${result.status}`);
756
+ if (result.message)
757
+ console.log(` Note: ${result.message}`);
758
+ if (result.status === "upgrade_available") {
759
+ console.log(`\n \x1b[33mUpdate available: v${result.current} → v${result.latest}\x1b[0m`);
760
+ console.log(` Run: loophaus upgrade`);
761
+ }
762
+ }
763
+ async function runUpgrade() {
764
+ const { getPackageVersion } = await import("../lib/paths.js");
765
+ const { checkForUpdate } = await import("../core/update-checker.js");
766
+ const { execFile: ef } = await import("node:child_process");
767
+ const { promisify } = await import("node:util");
768
+ const execFileAsync = promisify(ef);
769
+ const current = getPackageVersion();
770
+ const result = await checkForUpdate(current);
771
+ if (result.status === "up_to_date") {
772
+ console.log(`Already on latest version: v${current}`);
773
+ return;
774
+ }
775
+ if (result.status !== "upgrade_available" && result.status !== "snoozed") {
776
+ console.log(`No update available (status: ${result.status})`);
777
+ return;
778
+ }
779
+ console.log(`Upgrading loophaus: v${result.current} → v${result.latest}`);
780
+ const s = spinner("Installing...");
781
+ try {
782
+ await execFileAsync("npm", ["install", "-g", `@graypark/loophaus@${result.latest}`], { timeout: 120_000 });
783
+ s.stop();
784
+ console.log(`\u2714 Installed v${result.latest}`);
785
+ const s2 = spinner("Reinstalling plugins...");
786
+ try {
787
+ await execFileAsync("loophaus", ["install", "--force"], { timeout: 60_000 });
788
+ s2.stop();
789
+ console.log("\u2714 Plugins reinstalled");
790
+ }
791
+ catch {
792
+ s2.stop();
793
+ console.log(" Note: Run 'loophaus install --force' to update plugins.");
794
+ }
795
+ console.log(`\n Upgrade complete: v${result.current} → v${result.latest}`);
796
+ }
797
+ catch (err) {
798
+ s.stop();
799
+ console.error(`\u2718 Upgrade failed: ${err.message}`);
800
+ console.error(" Try manually: npm install -g @graypark/loophaus@latest");
801
+ }
802
+ }
803
+ async function runConfigCmd() {
804
+ const { readConfig, writeConfig } = await import("../core/cleanup.js");
805
+ const sub = args[1];
806
+ const KNOWN_KEYS = {
807
+ "cleanup.onNewPlan": "Policy when /loop-plan starts: archive | delete | keep",
808
+ "cleanup.traceRetentionDays": "Days to keep trace data",
809
+ "cleanup.sessionRetentionDays": "Days to keep session checkpoints",
810
+ "updateCheck": "Check for updates on skill execution: true | false",
811
+ "autoUpgrade": "Auto-upgrade without prompting: true | false",
812
+ };
813
+ if (!sub || sub === "list") {
814
+ const config = await readConfig();
815
+ console.log("Configuration (.loophaus/config.json)");
816
+ 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");
817
+ for (const [key, desc] of Object.entries(KNOWN_KEYS)) {
818
+ const val = getNestedValue(config, key);
819
+ console.log(` ${key.padEnd(30)} ${String(val ?? "(default)").padEnd(12)} ${desc}`);
820
+ }
821
+ console.log(`\nUsage: loophaus config set <key> <value>`);
822
+ return;
823
+ }
824
+ if (sub === "get") {
825
+ const key = args[2];
826
+ if (!key) {
827
+ console.log("Usage: loophaus config get <key>");
828
+ return;
829
+ }
830
+ const config = await readConfig();
831
+ const val = getNestedValue(config, key);
832
+ console.log(val !== undefined ? String(val) : "(not set)");
833
+ return;
834
+ }
835
+ if (sub === "set") {
836
+ const key = args[3] ? args[2] : args[2];
837
+ const value = args[3] || args[3];
838
+ if (!key || value === undefined) {
839
+ console.log("Usage: loophaus config set <key> <value>");
840
+ return;
841
+ }
842
+ const rawValue = args[3];
843
+ if (!key || rawValue === undefined) {
844
+ console.log("Usage: loophaus config set <key> <value>");
845
+ return;
846
+ }
847
+ if (!KNOWN_KEYS[key]) {
848
+ console.log(`Warning: '${key}' is not a known config key.`);
849
+ }
850
+ const config = await readConfig();
851
+ const parsed = rawValue === "true" ? true : rawValue === "false" ? false : isNaN(Number(rawValue)) ? rawValue : Number(rawValue);
852
+ setNestedValue(config, key, parsed);
853
+ await writeConfig(config);
854
+ console.log(`Set ${key} = ${String(parsed)}`);
855
+ return;
856
+ }
857
+ console.log("Usage: loophaus config [list|get|set] [key] [value]");
858
+ }
859
+ function getNestedValue(obj, path) {
860
+ const parts = path.split(".");
861
+ let current = obj;
862
+ for (const part of parts) {
863
+ if (current == null || typeof current !== "object")
864
+ return undefined;
865
+ current = current[part];
866
+ }
867
+ return current;
868
+ }
869
+ function setNestedValue(obj, path, value) {
870
+ const parts = path.split(".");
871
+ let current = obj;
872
+ for (let i = 0; i < parts.length - 1; i++) {
873
+ if (!(parts[i] in current) || typeof current[parts[i]] !== "object") {
874
+ current[parts[i]] = {};
875
+ }
876
+ current = current[parts[i]];
877
+ }
878
+ current[parts[parts.length - 1]] = value;
879
+ }
686
880
  try {
687
881
  switch (command) {
688
882
  case "install":
@@ -724,6 +918,15 @@ try {
724
918
  case "clean":
725
919
  await runCleanCmd();
726
920
  break;
921
+ case "config":
922
+ await runConfigCmd();
923
+ break;
924
+ case "update-check":
925
+ await runUpdateCheck();
926
+ break;
927
+ case "upgrade":
928
+ await runUpgrade();
929
+ break;
727
930
  case "sessions":
728
931
  await runSessions();
729
932
  break;
@@ -761,4 +964,3 @@ catch (err) {
761
964
  }
762
965
  process.exit(1);
763
966
  }
764
- //# sourceMappingURL=loophaus.js.map
@@ -5,4 +5,3 @@ export interface UninstallOptions {
5
5
  claude?: boolean;
6
6
  }
7
7
  export declare function uninstall({ dryRun, local, claude, }?: UninstallOptions): Promise<void>;
8
- //# sourceMappingURL=uninstall.d.ts.map
@@ -206,4 +206,3 @@ if (isDirectRun) {
206
206
  process.exit(1);
207
207
  });
208
208
  }
209
- //# sourceMappingURL=uninstall.js.map
@@ -11,6 +11,53 @@ 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 this silently before starting:
17
+
18
+ ```bash
19
+ _UPDATE=$(loophaus update-check 2>/dev/null | grep "UPGRADE_AVAILABLE" || true)
20
+ ```
21
+
22
+ If `_UPDATE` contains `UPGRADE_AVAILABLE`:
23
+ 1. Show: `loophaus update available: v{current} → v{latest}`
24
+ 2. Ask user:
25
+ - **A) Upgrade now** → run `loophaus upgrade`, then continue
26
+ - **B) Always auto-upgrade** → run `loophaus config set autoUpgrade true && loophaus upgrade`, then continue
27
+ - **C) Not now** → run `loophaus config set updateCheck snoozed` silently, then continue
28
+ - **D) Never ask again** → run `loophaus config set updateCheck false`, then continue
29
+
30
+ If auto-upgrade is enabled (`loophaus config get autoUpgrade` returns `true`):
31
+ - Skip prompt, run `loophaus upgrade` silently, continue.
32
+
33
+ If no update or check fails: continue silently (never block the user).
34
+
35
+ ## Pre-flight: Skill Routing Check
36
+
37
+ Check if the project has a CLAUDE.md with loophaus skill routing:
38
+
39
+ ```bash
40
+ CLAUDE_MD="$(pwd)/CLAUDE.md"
41
+ ROUTING_MARKER=".loophaus/routing-offered.json"
42
+ ```
43
+
44
+ If CLAUDE.md exists but has no `## loophaus skill routing` section, AND routing hasn't been offered before:
45
+ 1. Suggest adding this section to CLAUDE.md:
46
+ ```markdown
47
+ ## loophaus skill routing
48
+
49
+ | User intent | Route to |
50
+ |-------------|----------|
51
+ | Feature implementation, refactoring, multi-step task | `/loophaus:loop-plan` |
52
+ | "start the loop", "continue implementing" | `/loophaus:loop` |
53
+ | "stop the loop", "cancel" | `/loophaus:loop-stop` |
54
+ | "what's the status", "how far along" | `/loophaus:loop-pulse` |
55
+ ```
56
+ 2. If user agrees, add the section to CLAUDE.md
57
+ 3. Write `{ "offeredAt": "<ISO date>" }` to `.loophaus/routing-offered.json` so we don't ask again
58
+
59
+ If CLAUDE.md doesn't exist or routing already offered: skip silently.
60
+
14
61
  ## Phase 0: Cleanup Previous Data
15
62
 
16
63
  Before starting a new plan, apply the cleanup policy from `.loophaus/config.json`:
@@ -6,6 +6,17 @@ allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
6
6
 
7
7
  # /loop — Start Iterative Dev Loop
8
8
 
9
+ ## Pre-flight
10
+
11
+ ```bash
12
+ _UPDATE=$(loophaus update-check 2>/dev/null | grep "UPGRADE_AVAILABLE" || true)
13
+ ```
14
+ If update available and autoUpgrade enabled: run `loophaus upgrade` silently.
15
+ If update available and not auto: show one-line notice `loophaus v{new} available. Run: loophaus upgrade`.
16
+ Otherwise: continue silently.
17
+
18
+ ---
19
+
9
20
  Execute the setup script to initialize the loop:
10
21
 
11
22
  ```!
@@ -36,4 +36,3 @@ export declare function scoreBenchmark(metrics: BenchmarkMetrics): BenchmarkResu
36
36
  export declare function runBenchmark(cwd?: string): Promise<BenchmarkResult>;
37
37
  export declare function logBenchmark(result: BenchmarkResult, cwd?: string): Promise<void>;
38
38
  export declare function readBenchmarkHistory(cwd?: string): Promise<BenchmarkEntry[]>;
39
- //# sourceMappingURL=benchmark.d.ts.map
@@ -205,4 +205,3 @@ export async function readBenchmarkHistory(cwd) {
205
205
  return [];
206
206
  }
207
207
  }
208
- //# sourceMappingURL=benchmark.js.map
@@ -21,4 +21,3 @@ export declare function cleanSessions(options?: {
21
21
  export declare function cleanAll(cwd?: string): Promise<CleanResult>;
22
22
  export declare function archiveCurrentData(cwd?: string): Promise<CleanResult>;
23
23
  export declare function applyOnNewPlanPolicy(cwd?: string): Promise<CleanResult>;
24
- //# sourceMappingURL=cleanup.d.ts.map
@@ -142,4 +142,3 @@ export async function applyOnNewPlanPolicy(cwd) {
142
142
  return { removed: [], archived: [], skipped: ["policy: keep (no cleanup)"] };
143
143
  }
144
144
  }
145
- //# sourceMappingURL=cleanup.js.map
@@ -30,4 +30,3 @@ export declare function estimateCost(model: string, inputTokens: number, outputT
30
30
  export declare function formatCost(cost: number): string;
31
31
  export declare function createTracker(): CostTracker;
32
32
  export { MODEL_PRICES };
33
- //# sourceMappingURL=cost-tracker.d.ts.map
@@ -38,4 +38,3 @@ export function createTracker() {
38
38
  };
39
39
  }
40
40
  export { MODEL_PRICES };
41
- //# sourceMappingURL=cost-tracker.js.map
@@ -1,4 +1,3 @@
1
1
  import type { LoopState, StopHookInput, StopHookResult } from "./types.js";
2
2
  export declare function evaluateStopHook(input: StopHookInput, state: LoopState): StopHookResult;
3
3
  export declare function extractPromise(text: string, promisePhrase: string): boolean;
4
- //# sourceMappingURL=engine.d.ts.map
@@ -106,4 +106,3 @@ export function extractPromise(text, promisePhrase) {
106
106
  function escapeRegex(str) {
107
107
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
108
  }
109
- //# sourceMappingURL=engine.js.map
@@ -2,4 +2,3 @@ import type { LoopEvent } from "./types.js";
2
2
  export declare function getTracePath(cwd?: string): string;
3
3
  export declare function logEvents(events: LoopEvent[], metadata?: Record<string, unknown>, cwd?: string): Promise<void>;
4
4
  export declare function readTrace(cwd?: string): Promise<Record<string, unknown>[]>;
5
- //# sourceMappingURL=event-logger.d.ts.map
@@ -45,4 +45,3 @@ export async function readTrace(cwd) {
45
45
  return [];
46
46
  }
47
47
  }
48
- //# sourceMappingURL=event-logger.js.map
@@ -31,4 +31,3 @@ export interface EventSummary {
31
31
  lastTs: string | undefined;
32
32
  }
33
33
  export declare function summarizeEvents(events: LoopEvent[]): EventSummary;
34
- //# sourceMappingURL=events.d.ts.map
@@ -41,4 +41,3 @@ export function summarizeEvents(events) {
41
41
  const durationMs = first && last ? new Date(last.ts).getTime() - new Date(first.ts).getTime() : 0;
42
42
  return { counts, total: events.length, durationMs, firstTs: first?.ts, lastTs: last?.ts };
43
43
  }
44
- //# sourceMappingURL=events.js.map
@@ -1,3 +1,2 @@
1
1
  export declare function getLastAssistantText(transcriptPath?: string): Promise<string>;
2
2
  export declare function hasPendingStories(cwd?: string): Promise<boolean>;
3
- //# sourceMappingURL=io-helpers.d.ts.map
@@ -62,4 +62,3 @@ export async function hasPendingStories(cwd) {
62
62
  throw new Error(`Failed to read prd.json at ${prdPath}: ${err.message}`);
63
63
  }
64
64
  }
65
- //# sourceMappingURL=io-helpers.js.map
@@ -7,4 +7,3 @@ interface LoopEntry {
7
7
  export declare function listLoops(cwd?: string): Promise<LoopEntry[]>;
8
8
  export declare function getLoop(name: string, cwd?: string): Promise<LoopEntry | null>;
9
9
  export {};
10
- //# sourceMappingURL=loop-registry.d.ts.map
@@ -34,4 +34,3 @@ export async function getLoop(name, cwd) {
34
34
  const loops = await listLoops(cwd);
35
35
  return loops.find(l => l.name === name) || null;
36
36
  }
37
- //# sourceMappingURL=loop-registry.js.map
@@ -4,4 +4,3 @@ export declare function mergeSequential(branches: string[], targetBranch?: strin
4
4
  export declare function mergeSquash(branches: string[]): Promise<MergeResult[]>;
5
5
  export declare function mergeCherryPick(branches: string[]): Promise<MergeResult[]>;
6
6
  export declare function merge(strategy: string, branches: string[], targetBranch?: string): Promise<MergeResult[]>;
7
- //# sourceMappingURL=merge-strategy.d.ts.map
@@ -79,4 +79,3 @@ export async function merge(strategy, branches, targetBranch) {
79
79
  default: throw new Error(`Unknown merge strategy: ${strategy}`);
80
80
  }
81
81
  }
82
- //# sourceMappingURL=merge-strategy.js.map
@@ -29,4 +29,3 @@ export declare function runParallel({ prdPath, count, baseBranch, cwd }: {
29
29
  }): Promise<ParallelResult>;
30
30
  export declare function cleanupParallel(): Promise<CleanupResult[]>;
31
31
  export {};
32
- //# sourceMappingURL=parallel-runner.d.ts.map
@@ -85,4 +85,3 @@ export async function cleanupParallel() {
85
85
  }
86
86
  return results;
87
87
  }
88
- //# sourceMappingURL=parallel-runner.js.map
@@ -19,4 +19,3 @@ declare const DEFAULT_POLICY: Policy;
19
19
  export declare function loadPolicy(cwd?: string): Promise<Policy>;
20
20
  export declare function evaluatePolicy(policy: Policy, state: PolicyState, context?: PolicyContext): PolicyResult;
21
21
  export { DEFAULT_POLICY };
22
- //# sourceMappingURL=policy.d.ts.map
@@ -51,4 +51,3 @@ export function evaluatePolicy(policy, state, context = {}) {
51
51
  };
52
52
  }
53
53
  export { DEFAULT_POLICY };
54
- //# sourceMappingURL=policy.js.map
@@ -37,4 +37,3 @@ interface ResultEntry {
37
37
  }
38
38
  export declare function readResults(cwd?: string): Promise<ResultEntry[]>;
39
39
  export {};
40
- //# sourceMappingURL=quality-scorer.d.ts.map
@@ -125,4 +125,3 @@ export async function readResults(cwd) {
125
125
  return [];
126
126
  }
127
127
  }
128
- //# sourceMappingURL=quality-scorer.js.map
@@ -13,4 +13,3 @@ export declare function shouldKeep(newScore: number, baselineScore: number): boo
13
13
  export declare function generateFeedback(evaluation: Evaluation, previousAttempts?: PreviousAttempt[]): string;
14
14
  export declare function identifyRefinementTargets(evaluations: Evaluation[], threshold?: number): Evaluation[];
15
15
  export {};
16
- //# sourceMappingURL=refine-loop.d.ts.map
@@ -23,4 +23,3 @@ export function identifyRefinementTargets(evaluations, threshold = 80) {
23
23
  .filter(e => e.score < threshold)
24
24
  .sort((a, b) => a.score - b.score);
25
25
  }
26
- //# sourceMappingURL=refine-loop.js.map
@@ -24,4 +24,3 @@ interface ResumedState {
24
24
  }
25
25
  export declare function resumeSession(sessionId: string, cwd?: string): Promise<ResumedState | null>;
26
26
  export {};
27
- //# sourceMappingURL=session.d.ts.map
@@ -64,4 +64,3 @@ export async function resumeSession(sessionId, cwd) {
64
64
  await write(state, cwd, checkpoint.name);
65
65
  return state;
66
66
  }
67
- //# sourceMappingURL=session.js.map
@@ -25,4 +25,3 @@ export interface ReplayEvent extends LoopEvent {
25
25
  export declare function analyzeTrace(events: LoopEvent[]): TraceAnalysis;
26
26
  export declare function compareTraces(trace1: LoopEvent[], trace2: LoopEvent[]): TraceComparison;
27
27
  export declare function replayTrace(events: LoopEvent[], speed?: number): ReplayEvent[];
28
- //# sourceMappingURL=trace-analyzer.d.ts.map
@@ -43,4 +43,3 @@ export function replayTrace(events, speed = 1) {
43
43
  relativeMs: Math.round((new Date(e.ts).getTime() - firstTs) / speed),
44
44
  }));
45
45
  }
46
- //# sourceMappingURL=trace-analyzer.js.map
@@ -96,4 +96,3 @@ export interface CostEstimate {
96
96
  totalCost: number;
97
97
  model: string;
98
98
  }
99
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map