(directory, job)
| 627 | if (name === "loop-prompt") return { intervalMs, action: extra, kind: "prompt", name: "prompt", immediate: true } |
| 628 | if (name === "loop-ask") return { intervalMs, action: extra, kind: "prompt", name: "ask", immediate: false } |
| 629 | if (name === "loop-shell") return { intervalMs, action: extra, kind: "shell", name: "shell", immediate: false } |
| 630 | if (name === "loop-testfix") return { intervalMs, name: "testfix", safe: true, askNever: true, verifyCommand: extra || "npm test", action: `Run the project tests. Fix failures. Re-run the tests. Test command hint: ${extra || "npm test"}` } |
| 631 | if (name === "loop-progress") return { intervalMs, name: "progress", safe: true, askNever: true, progressFile: "progress.md", action: extra || "Read progress.md and continue the next unfinished TODO. Mark completed TODOs with [x]. Add useful TODOs when you discover them." } |
| 632 | if (name === "loop-safe-dev") return { intervalMs, name: "safe-dev", safe: true, askNever: true, noOverlap: true, checkpointOnly: true, batch: 5, progressFile: "progress.md", action: extra || "Develop the project from progress.md. Work in small safe batches. Mark completed TODOs with [x]. Add new ideas to progress.md. Run tests/lint/build if available." } |
| 633 | return { intervalMs, name: "dev", askNever: true, progressFile: "progress.md", action: extra || "Continue developing the project from progress.md. Mark completed TODOs with [x]. Add new ideas to progress.md. Run tests/lint/build if available." } |
| 634 | } |
| 635 | |
| 636 | function jobLabel(job) { |
| 637 | const title = job.name ? `${job.name}: ` : "" |
| 638 | const kind = job.kind ? ` [${job.kind}]` : "" |
| 639 | const limit = job.maxRuns > 0 ? `, max ${job.maxRuns}` : "" |
| 640 | const runtime = job.maxRuntimeMs > 0 ? `, runtime ${durationToText(job.maxRuntimeMs)}` : "" |
| 641 | const timeout = job.timeoutMs > 0 ? `, timeout ${durationToText(job.timeoutMs)}` : "" |
| 642 | const compact = job.compactEveryRuns > 0 ? `, compact every ${job.compactEveryRuns} runs` : job.compactEveryMs > 0 ? `, compact every ${durationToText(job.compactEveryMs)}` : "" |
| 643 | const verify = job.verifyCommand ? ", verify" : "" |
| 644 | const preflight = job.preflightCommand ? ", preflight" : "" |
| 645 | const failures = job.maxFailures > 0 ? `, max failures ${job.maxFailures}` : "" |
| 646 | const noProgress = isGoalJob(job) && (job.maxNoProgress ?? DEFAULT_GOAL_MAX_NO_PROGRESS) > 0 ? `, max no-progress ${job.maxNoProgress ?? DEFAULT_GOAL_MAX_NO_PROGRESS}` : "" |
| 647 | const stopFile = job.stopFile ? ", stop-file" : "" |
| 648 | const watch = job.watchPaths?.length ? `, watch ${job.watchPaths.join(",")}` : "" |
| 649 | const paused = job.paused ? ", paused" : "" |
| 650 | return `${title}${durationToText(job.intervalMs)}${kind} -> ${job.action || `[prompt-file: ${job.promptFile}]`}${limit}${runtime}${timeout}${compact}${verify}${preflight}${failures}${noProgress}${stopFile}${watch}${paused}` |
| 651 | } |
| 652 | |
| 653 | function matchJob(job, target, index) { |
| 654 | const text = String(target || "").trim() |
| 655 | if (!text || text.toLowerCase() === "all") return true |
| 656 | return job.id === text || job.name === text || String(index + 1) === text |
| 657 | } |
| 658 | |
| 659 | async function appendLoopLog(directory, line, extra = {}) { |
| 660 | try { |
| 661 | await ensureDir(stateDir(directory)) |
| 662 | await fs.appendFile(path.join(stateDir(directory), "loop.log"), JSON.stringify({ time: new Date().toISOString(), line, ...extra }) + "\n") |
| 663 | } catch {} |
| 664 | } |
| 665 | |
| 666 | async function readSmallTextFile(filePath, maxBytes = 120_000) { |
| 667 | try { |
| 668 | const stat = await fs.stat(filePath) |
| 669 | if (!stat.isFile() || stat.size > maxBytes) return "" |
| 670 | return await fs.readFile(filePath, "utf8") |
| 671 | } catch { return "" } |
| 672 | } |
no test coverage detected