From 48237c8d8582d4303fe14e1bbb2308760c8da505 Mon Sep 17 00:00:00 2001 From: Vitali sharp8n Date: Thu, 8 Oct 2026 18:39:26 +0300 Subject: [PATCH] cleanup --- bin/kilocode-loop.mjs | 247 ------------------------------------------ 1 file changed, 247 deletions(-) delete mode 100644 bin/kilocode-loop.mjs diff --git a/bin/kilocode-loop.mjs b/bin/kilocode-loop.mjs deleted file mode 100644 index 1916b7f..0000000 --- a/bin/kilocode-loop.mjs +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env node -import fs from 'node:fs' - -import { DEFAULTS, USAGE, loadConfig } from '../src/config.mjs' -import { Orchestrator } from '../src/orchestrator.mjs' -import { RunController } from '../src/controller.mjs' -import { startServer, runsBaseDir } from '../src/server.mjs' -import { installTerminalControls } from '../src/terminal.mjs' -import { printLog } from '../src/console.mjs' -import { listRuns, loadRun } from '../src/state.mjs' -import { dashboardUrls } from '../src/net.mjs' -import { guardFeedbackText } from '../src/guard.mjs' -import { loadGoal } from '../src/goal.mjs' -import { listGoalSummaries } from '../src/goals.mjs' -import { confirmStart, renderPreflight } from '../src/preflight.mjs' -import { runReportCommand } from '../src/reportcli.mjs' -import { c } from '../src/ansi.mjs' - -/** Exit codes by final run status (documented in the README). */ -export const EXIT_CODES = { - done: 0, - 'done-with-errors': 1, - aborted: 2, - 'stopped-budget': 3, - stalled: 4, - 'stopped-guard': 5, -} - -function dashboardLink(url, note, lan = []) { - process.stdout.write(`\n ${c.gray('Dashboard')} ${url} ${c.gray('(Ctrl+Click to open)')}\n`) - for (const u of lan) { - if (u !== url) process.stdout.write(` ${c.gray('On this network (e.g. phone)')} ${c.bold(u)}\n`) - } - if (note) process.stdout.write(` ${c.gray(note)}\n`) -} - -/** Resolve after `ms`, without keeping the process alive just for the timer. */ -function delay(ms) { - return new Promise((resolve) => { - const t = setTimeout(resolve, ms) - t.unref?.() - }) -} - -/** - * Handle SIGINT/SIGTERM so Ctrl+C always stops the loop and exits — even when a - * dashboard tab holds an SSE connection open. A second signal exits immediately, - * and a hard guard force-exits if graceful cleanup misbehaves. - */ -function installSignalHandlers(onSignal, exitCode = 130) { - let signals = 0 - const handler = async () => { - signals += 1 - if (signals > 1) process.exit(exitCode) - const guard = setTimeout(() => process.exit(exitCode), 8000) - guard.unref?.() - try { - await onSignal() - } catch { - /* ignore — we are exiting anyway */ - } - clearTimeout(guard) - process.exit(exitCode) - } - process.on('SIGINT', handler) - process.on('SIGTERM', handler) -} - -function printGoals(project) { - const goals = listGoalSummaries(project) - if (!goals.length) { - process.stdout.write( - `\n No goals found. Add JSON/Markdown goals under .kilocode-loop/goals/, or a markdown plan with "- [ ]" items under plans/ or .kilo/plans/.\n\n`, - ) - return - } - process.stdout.write(`\n ${c.bold('Goals / plans')} ${c.gray(project)}\n\n`) - for (const g of goals) { - const last = g.lastRun ? `${g.lastRun.status} · ${g.lastRun.startedAt}` : 'never run' - const verify = g.globalVerify ? ` · global verify: ${g.globalVerify}` : '' - process.stdout.write(` ${String(g.percent).padStart(3)}% ${String(g.done)}/${g.total} ${c.bold(g.file)}\n`) - process.stdout.write(` ${g.title}${c.gray(verify)}\n`) - process.stdout.write(` ${c.gray(`last run: ${last}`)}\n\n`) - } -} - -async function main() { - // `kilo-loop report` is a read-only subcommand: it must not load a goal or - // start anything, so handle it before normal config parsing. - const argv = process.argv.slice(2) - if (argv[0] === 'report') { - process.exit(runReportCommand(argv.slice(1))) - } - - let config - try { - config = loadConfig(argv) - } catch (err) { - process.stderr.write(`\n Error: ${err.message}\n\n${USAGE}\n`) - process.exit(2) - } - if (config.help) { - process.stdout.write(USAGE + '\n') - process.exit(0) - } - if (config.listGoals) { - printGoals(config.project) - return - } - - if (config.promptExtra && fs.existsSync(config.promptExtra)) { - config.promptExtra = fs.readFileSync(config.promptExtra, 'utf8') - } - - // Launcher mode: no goal on the CLI. Serve the dashboard so a plan can be - // picked (and its verify configured) in the UI. - if (config.launcher) { - const controller = new RunController(config) - const dashboard = await startServer(config, controller) - dashboardLink(dashboard.url, 'No goal given — pick a plan in the dashboard.', dashboard.urls?.lan || []) - installSignalHandlers(async () => { - if (controller.isActive()) { - controller.requestStop() - // Give the current session a moment to die so no child is orphaned. - await Promise.race([controller.whenRunSettles(), delay(5000)]) - } - await dashboard.close().catch(() => {}) - }) - return - } - - // Resolve the goal up front so the pre-flight shows exactly what will run and - // a broken goal file fails before anything is started. - let goal - try { - goal = loadGoal(config) - } catch (err) { - process.stderr.write(`\n Error: ${err.message}\n`) - process.exit(2) - } - - // --resume: load the previous run before the pre-flight so stages it already - // completed are shown (and treated) as done instead of being repeated. The - // agent may report a variant id (e.g. "phase3-resource-and-policy-cleanup"), - // so match by equality or prefix against the goal's stage ids. - let prevRun = null - const resumedStageIds = new Set() - if (config.resume === 'last' || config.resume === 'latest') { - const [latest] = listRuns(runsBaseDir(config)) - if (latest?.runId) { - config.resume = latest.runId - } else { - process.stderr.write(' Warning: --resume last requested but no saved runs were found — starting fresh.\n') - config.resume = '' - } - } - if (config.resume) { - try { - prevRun = loadRun(runsBaseDir(config), config.resume) - const reported = new Set( - (prevRun.iterations || []).filter((it) => it.stageStatus === 'done' && it.stageId).map((it) => it.stageId), - ) - for (const s of goal.stages || []) { - if ([...reported].some((d) => d === s.id || d.startsWith(s.id))) { - s.done = true - resumedStageIds.add(s.id) - } - } - } catch { - process.stderr.write(` Warning: could not load run "${config.resume}" — starting fresh.\n`) - } - } - - process.stdout.write(renderPreflight(config, goal, dashboardUrls(config).primary)) - if (!(await confirmStart(config))) { - process.stdout.write(`\n ${c.yellow('Cancelled')} — nothing was started. Edit the goal or config, then run again.\n`) - return - } - - const orchestrator = new Orchestrator(config) - orchestrator.state.on('log', (entry) => printLog(entry, { quiet: config.quiet })) - - if (prevRun) { - // Only reuse the previous session when shared context is on; fresh runs - // start a new session every iteration regardless of --resume. - if (prevRun.sessionID && config.sharedContext) orchestrator.state.update({ sessionID: prevRun.sessionID }) - for (const id of resumedStageIds) orchestrator.completedStages.add(id) - const last = (prevRun.iterations || []).slice(-1)[0] - if (last?.percent != null) orchestrator.lastPercent = last.percent - // If the previous run was interrupted by the safety guard, carry that note - // into this run's first iteration so the agent does not retry the command. - const blocked = [...(prevRun.iterations || [])].reverse().find((it) => it.guardViolation) - if (blocked) { - orchestrator.guardFeedback = guardFeedbackText(blocked.guardViolation) - orchestrator.log('warn', `Previous run hit the safety guard (${blocked.guardViolation.id}) — the first iteration carries the safety feedback.`) - } - orchestrator.log('system', `Resumed run ${config.resume} (session ${prevRun.sessionID || '—'}, last ${orchestrator.lastPercent}%).`) - } - - const controller = new RunController(config) - controller.attach(orchestrator) - const dashboard = await startServer(config, controller) - const controls = installTerminalControls(orchestrator) - - dashboardLink(dashboard.url, null, dashboard.urls?.lan || []) - - let exitCode = 0 - const shutdown = async (code) => { - controls.dispose() - // Hard guard: never hang on exit even if close() misbehaves. - const guard = setTimeout(() => process.exit(code), 3000) - guard.unref?.() - await dashboard.close().catch(() => {}) - clearTimeout(guard) - process.exit(code) - } - - const runPromise = orchestrator.run() - installSignalHandlers(async () => { - orchestrator.requestStop() - await Promise.race([runPromise.catch(() => {}), delay(8000)]) - }) - - try { - const final = await runPromise - exitCode = EXIT_CODES[final.status] ?? 1 - // Continuation aid: a run that did not reach `done` can be resumed with the - // same goal, carrying its stage progress and (if any) safety feedback. - if (final.status !== 'done' && final.runId) { - const resume = config.goal - ? `kilo-loop --goal ${config.goal} --resume ${final.runId}` - : `kilo-loop --resume ${final.runId} (pass the same --goal / --goal-text)` - process.stdout.write(`\n ${c.yellow('Resume this run')}: ${resume}\n`) - } - } catch (err) { - process.stderr.write(`\n Fatal: ${err?.stack || err}\n`) - exitCode = 1 - } - - if (config.keepOpen) { - process.stdout.write(`\n Dashboard still open: ${dashboard.url} (Ctrl+Click to open, Ctrl+C to exit)\n`) - return - } - await shutdown(exitCode) -} - -main()