jonah-fleet 1.4.1 → 1.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.
package/dist/index.js CHANGED
@@ -49,7 +49,8 @@ var PRESET_CONFIGS = {
49
49
  "diagnosing-bugs",
50
50
  "resolving-merge-conflicts",
51
51
  "writing-for-agents",
52
- "triage"
52
+ "triage",
53
+ "grill-me"
53
54
  ]
54
55
  },
55
56
  full: {
@@ -71,6 +72,7 @@ var PRESET_CONFIGS = {
71
72
  "resolving-merge-conflicts",
72
73
  "writing-for-agents",
73
74
  "triage",
75
+ "grill-me",
74
76
  "to-spec",
75
77
  "to-tickets"
76
78
  ]
@@ -90,7 +92,7 @@ var ROUTINE_TO_WORKFLOW_MAP = {
90
92
  "product-planning": [],
91
93
  "analytics-review": []
92
94
  };
93
- var FLEET_VERSION = "1.4.1";
95
+ var FLEET_VERSION = "1.5.0";
94
96
  var SCHEMA_URL = "https://raw.githubusercontent.com/juliendurandeu/jonah-fleet/main/schema.json";
95
97
 
96
98
  // src/lib/manifest.ts
@@ -2155,13 +2157,13 @@ async function runTelemetry(options = {}) {
2155
2157
  }
2156
2158
 
2157
2159
  // src/commands/run.ts
2158
- import pc9 from "picocolors";
2160
+ import pc10 from "picocolors";
2159
2161
 
2160
2162
  // src/lib/runner.ts
2161
- import fs11 from "fs";
2162
- import path11 from "path";
2163
+ import fs12 from "fs";
2164
+ import path12 from "path";
2163
2165
  import os2 from "os";
2164
- import { spawn } from "child_process";
2166
+ import { spawn, execSync as execSync2 } from "child_process";
2165
2167
 
2166
2168
  // src/lib/worktree.ts
2167
2169
  import fs10 from "fs";
@@ -2284,18 +2286,369 @@ async function cleanupStaleWorktrees(repoRoot) {
2284
2286
  return cleaned;
2285
2287
  }
2286
2288
 
2289
+ // src/lib/terminal-card.ts
2290
+ import fs11 from "fs";
2291
+ import path11 from "path";
2292
+ import pc9 from "picocolors";
2293
+ function sanitizeWorktreePaths(text) {
2294
+ let cleaned = text.replace(/file:\/\/\/[^\s"'()]+?\/\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, "");
2295
+ cleaned = cleaned.replace(/(?:^|[\s"'(`[])(?:\/[^\s"'()]+?)?\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, (match) => {
2296
+ const prefix = match.charAt(0);
2297
+ return prefix === "/" ? "" : prefix;
2298
+ });
2299
+ cleaned = cleaned.replace(/\[`?([^`\]]+?)`?\]\(file:\/\/\/[^\s)]+\)/g, "`$1`");
2300
+ return cleaned;
2301
+ }
2302
+ function extractExecutionSummary(output) {
2303
+ const summaryHeaderRegex = /#\s+([A-Za-z0-9\s_-]+?Execution\s+Summary[\s\S]*)/i;
2304
+ const match = output.match(summaryHeaderRegex);
2305
+ if (!match) return null;
2306
+ let summary = match[1].trim();
2307
+ const trailingSeparators = [
2308
+ "\u2713 Local peer-review completed",
2309
+ "\u2713 Local autowork completed",
2310
+ "\u2713 Local agent session",
2311
+ "[6:",
2312
+ "Peer Review Watchdog:"
2313
+ ];
2314
+ for (const sep of trailingSeparators) {
2315
+ const idx = summary.indexOf(sep);
2316
+ if (idx !== -1) {
2317
+ summary = summary.slice(0, idx).trim();
2318
+ }
2319
+ }
2320
+ return sanitizeWorktreePaths(summary);
2321
+ }
2322
+ function findLatestRunLog(repoRoot, routine) {
2323
+ const logsDir = path11.join(repoRoot, ".github", "prompts", "logs", routine);
2324
+ if (!fs11.existsSync(logsDir)) return null;
2325
+ try {
2326
+ const files = fs11.readdirSync(logsDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
2327
+ if (files.length === 0) return null;
2328
+ files.sort().reverse();
2329
+ return path11.join(logsDir, files[0]);
2330
+ } catch {
2331
+ return null;
2332
+ }
2333
+ }
2334
+ function parseRunLog(logContent) {
2335
+ const summary = {
2336
+ passes: [],
2337
+ actions: []
2338
+ };
2339
+ const lines = logContent.split("\n");
2340
+ for (const line of lines) {
2341
+ const trimmed = line.trim();
2342
+ if (trimmed.startsWith("|") && trimmed.includes("|")) {
2343
+ const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
2344
+ if (parts.length >= 2) {
2345
+ const key = parts[0].toLowerCase();
2346
+ const value = parts[1].replace(/`/g, "");
2347
+ if (key.includes("routine")) summary.routine = value;
2348
+ if (key.includes("target pr") || key.includes("target issue")) summary.target = value;
2349
+ if (key.includes("decision")) summary.decision = value;
2350
+ if (key.includes("result")) summary.result = value;
2351
+ if (key.includes("duration")) summary.duration = value;
2352
+ }
2353
+ }
2354
+ }
2355
+ let inDoD = false;
2356
+ let inFindings = false;
2357
+ let inActions = false;
2358
+ for (const line of lines) {
2359
+ const trimmed = line.trim();
2360
+ if (trimmed.startsWith("## Definition of Done")) {
2361
+ inDoD = true;
2362
+ inFindings = false;
2363
+ inActions = false;
2364
+ continue;
2365
+ } else if (trimmed.startsWith("## Code Review Findings") || trimmed.startsWith("## Findings")) {
2366
+ inDoD = false;
2367
+ inFindings = true;
2368
+ inActions = false;
2369
+ continue;
2370
+ } else if (trimmed.startsWith("## Execution Trace") || trimmed.startsWith("### Actions Taken")) {
2371
+ inDoD = false;
2372
+ inFindings = false;
2373
+ inActions = true;
2374
+ continue;
2375
+ } else if (trimmed.startsWith("## ")) {
2376
+ inDoD = false;
2377
+ inFindings = false;
2378
+ inActions = false;
2379
+ }
2380
+ if (inDoD && trimmed.startsWith("|") && !trimmed.includes("Criterion") && !trimmed.includes("---")) {
2381
+ const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
2382
+ if (parts.length >= 2) {
2383
+ const criterion = parts[0];
2384
+ const met = parts[1].toUpperCase() === "YES" || parts[1].toUpperCase() === "PASS";
2385
+ const evidence = parts[2] ? ` (${parts[2].slice(0, 60)}...)` : "";
2386
+ summary.passes?.push({
2387
+ name: criterion,
2388
+ status: met ? "pass" : "fail",
2389
+ detail: evidence
2390
+ });
2391
+ }
2392
+ }
2393
+ if (inActions && (trimmed.startsWith("- ") || trimmed.startsWith("* "))) {
2394
+ summary.actions?.push(sanitizeWorktreePaths(trimmed.slice(2)));
2395
+ }
2396
+ }
2397
+ return summary;
2398
+ }
2399
+ function detectActivePhase(chunk, currentPhase = "Executing routine") {
2400
+ const lower = chunk.toLowerCase();
2401
+ if (lower.includes("\u{1F512} claimed") || lower.includes("claimed by local autowork") || lower.includes("claimed by autowork")) {
2402
+ return "Claimed target issue, starting implementation";
2403
+ }
2404
+ if (lower.includes("starting review (round")) {
2405
+ return "Claimed review window, starting review passes";
2406
+ }
2407
+ if (lower.includes("check-client-boundary")) return "Verifying React Server Component boundaries";
2408
+ if (lower.includes("type-check") || lower.includes("tsc --noemit")) return "Running TypeScript type checks";
2409
+ if (lower.includes("lint") || lower.includes("eslint")) return "Running codebase linter";
2410
+ if (lower.includes("test") || lower.includes("vitest") || lower.includes("jest")) return "Running automated test suite";
2411
+ if (lower.includes("build") || lower.includes("next build") || lower.includes("tsup")) return "Running production build verification";
2412
+ if (lower.includes("code-review") || lower.includes("subagent")) return "Running multi-angle code review passes";
2413
+ if (lower.includes("squash-merge") || lower.includes("pr merge")) return "Squash-merging target PR to main";
2414
+ if (lower.includes("gh issue create") || lower.includes("autonomous issue synthesis")) return "Synthesizing tracking issue";
2415
+ if (lower.includes("--undo") || lower.includes("draft")) return "Bouncing PR back to draft for author fixes";
2416
+ if (lower.includes("issue edit") || lower.includes("pr edit")) return "Linking PR & tracking issues";
2417
+ if (lower.includes("pr comment") || lower.includes("review summary")) return "Submitting review comment";
2418
+ if (lower.includes("worktree")) return "Preparing workspace worktree";
2419
+ return currentPhase;
2420
+ }
2421
+ function detectClaimedIssue(chunk) {
2422
+ const claimMatch = chunk.match(/🔒\s*Claimed[^\n#]*?#(\d+)/i);
2423
+ if (claimMatch) return `Issue #${claimMatch[1]}`;
2424
+ const ghMatch = chunk.match(/gh\s+issue\s+(?:view|edit|comment|develop)\s+(\d+)/i);
2425
+ if (ghMatch) return `Issue #${ghMatch[1]}`;
2426
+ const textMatch = chunk.match(/(?:selected|claimed|claiming|target(?:ing)?|working|candidate)\s+(?:candidate\s+)?issue\s+#?(\d+)/i);
2427
+ if (textMatch) return `Issue #${textMatch[1]}`;
2428
+ const passiveMatch = chunk.match(/issue\s+#(\d+)\s+(?:claimed|selected)/i);
2429
+ if (passiveMatch) return `Issue #${passiveMatch[1]}`;
2430
+ return null;
2431
+ }
2432
+ function detectClaimedPR(chunk) {
2433
+ const reviewMatch = chunk.match(/Starting\s+review[^\n#]*?#(\d+)/i);
2434
+ if (reviewMatch) return `PR #${reviewMatch[1]}`;
2435
+ const prMatch = chunk.match(/(?:selected|target|reviewing)\s+(?:target\s+)?PR:?\s*\[?PR\s*#?(\d+)/i);
2436
+ if (prMatch) return `PR #${prMatch[1]}`;
2437
+ const ghPrMatch = chunk.match(/gh\s+pr\s+(?:view|diff|checkout|review)\s+(\d+)/i);
2438
+ if (ghPrMatch) return `PR #${ghPrMatch[1]}`;
2439
+ return null;
2440
+ }
2441
+ function renderSummaryCard(options) {
2442
+ const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 86);
2443
+ const horizontal = "\u2500".repeat(width - 2);
2444
+ const rawSummary = options.output ? extractExecutionSummary(options.output) : null;
2445
+ let parsedFromLog = null;
2446
+ if (options.repoRoot) {
2447
+ const latestLog = findLatestRunLog(options.repoRoot, options.routine);
2448
+ if (latestLog) {
2449
+ try {
2450
+ const content = fs11.readFileSync(latestLog, "utf8");
2451
+ parsedFromLog = parseRunLog(content);
2452
+ parsedFromLog.logPath = path11.relative(options.repoRoot, latestLog);
2453
+ } catch {
2454
+ }
2455
+ }
2456
+ }
2457
+ let target = options.pr ? `PR #${options.pr}` : options.issue ? `Issue #${options.issue}` : "";
2458
+ if (!target && parsedFromLog?.target) target = parsedFromLog.target;
2459
+ let decision = parsedFromLog?.decision || "";
2460
+ if (!decision && rawSummary) {
2461
+ const decisionMatch = rawSummary.match(/\*\*Final Action\*\*:\s*([^\n]+)/i);
2462
+ if (decisionMatch) decision = decisionMatch[1].replace(/[`*]/g, "").trim();
2463
+ }
2464
+ const durationStr = options.durationMs ? `${Math.round(options.durationMs / 1e3)}s` : parsedFromLog?.duration || "";
2465
+ const lines = [];
2466
+ lines.push(pc9.cyan(`\u250C${horizontal}\u2510`));
2467
+ const title = ` Jonah Fleet Routine: ${pc9.bold(options.routine.toUpperCase())} `;
2468
+ lines.push(
2469
+ pc9.cyan("\u2502") + ` ${pc9.bold(pc9.white(options.routine.toUpperCase()))}` + (target ? ` \xB7 ${pc9.yellow(target)}` : "") + (durationStr ? pc9.dim(` (${durationStr})`) : "") + " ".repeat(
2470
+ Math.max(
2471
+ 1,
2472
+ width - 4 - options.routine.length - target.length - (durationStr ? durationStr.length + 3 : 0)
2473
+ )
2474
+ ) + pc9.cyan("\u2502")
2475
+ );
2476
+ if (decision) {
2477
+ let decisionBadge = pc9.green(`\u2714 ${decision}`);
2478
+ if (/bounce|draft|reject|fail/i.test(decision)) {
2479
+ decisionBadge = pc9.yellow(`\u26A0\uFE0F ${decision}`);
2480
+ } else if (/escalat/i.test(decision)) {
2481
+ decisionBadge = pc9.red(`\u{1F6A8} ${decision}`);
2482
+ }
2483
+ lines.push(
2484
+ pc9.cyan("\u2502") + ` Action: ${decisionBadge}` + " ".repeat(Math.max(1, width - 11 - decision.length)) + pc9.cyan("\u2502")
2485
+ );
2486
+ }
2487
+ lines.push(pc9.cyan(`\u251C${horizontal}\u2524`));
2488
+ if (rawSummary) {
2489
+ const summaryLines = rawSummary.split("\n");
2490
+ for (const rawLine of summaryLines) {
2491
+ const line = rawLine.trim();
2492
+ if (!line) continue;
2493
+ if (line.startsWith("# ")) continue;
2494
+ if (line.startsWith("---")) continue;
2495
+ if (line.startsWith("**Selected Target") || line.startsWith("**Final Action") || line.startsWith("**Mode**:")) {
2496
+ continue;
2497
+ }
2498
+ if (line.startsWith("### ")) {
2499
+ const heading = line.replace("### ", "").trim();
2500
+ lines.push(
2501
+ pc9.cyan("\u2502") + ` ${pc9.bold(pc9.cyan(heading))}` + " ".repeat(Math.max(1, width - 3 - heading.length)) + pc9.cyan("\u2502")
2502
+ );
2503
+ } else if (line.startsWith("- ") || line.startsWith("* ")) {
2504
+ const item = sanitizeWorktreePaths(line.slice(2)).trim();
2505
+ const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc9.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc9.yellow(code));
2506
+ const plainLen = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").length;
2507
+ if (plainLen <= width - 6) {
2508
+ lines.push(pc9.cyan("\u2502") + ` \u2022 ${formatted}` + " ".repeat(Math.max(1, width - 5 - plainLen)) + pc9.cyan("\u2502"));
2509
+ } else {
2510
+ const truncated = formatted.slice(0, width - 10) + "...";
2511
+ lines.push(pc9.cyan("\u2502") + ` \u2022 ${truncated}` + " ".repeat(Math.max(1, width - 5 - (width - 7))) + pc9.cyan("\u2502"));
2512
+ }
2513
+ } else if (/^[0-9]+\.\s+/.test(line)) {
2514
+ const item = sanitizeWorktreePaths(line.replace(/^[0-9]+\.\s+/, "")).trim();
2515
+ const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc9.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc9.yellow(code));
2516
+ const plainLen = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").length;
2517
+ if (plainLen <= width - 6) {
2518
+ lines.push(pc9.cyan("\u2502") + ` \u2714 ${formatted}` + " ".repeat(Math.max(1, width - 5 - plainLen)) + pc9.cyan("\u2502"));
2519
+ } else {
2520
+ const truncated = formatted.slice(0, width - 10) + "...";
2521
+ lines.push(pc9.cyan("\u2502") + ` \u2714 ${truncated}` + " ".repeat(Math.max(1, width - 5 - (width - 7))) + pc9.cyan("\u2502"));
2522
+ }
2523
+ }
2524
+ }
2525
+ } else if (parsedFromLog && parsedFromLog.passes && parsedFromLog.passes.length > 0) {
2526
+ lines.push(pc9.cyan("\u2502") + ` ${pc9.bold("Verification Passes:")}` + " ".repeat(Math.max(1, width - 23)) + pc9.cyan("\u2502"));
2527
+ for (const pass of parsedFromLog.passes.slice(0, 6)) {
2528
+ const icon = pass.status === "pass" ? pc9.green("\u2714") : pc9.red("\u2716");
2529
+ const text = `${pass.name}${pass.detail || ""}`;
2530
+ const plainLen = text.length + 4;
2531
+ const truncated = plainLen > width - 6 ? text.slice(0, width - 10) + "..." : text;
2532
+ lines.push(pc9.cyan("\u2502") + ` ${icon} ${truncated}` + " ".repeat(Math.max(1, width - 5 - truncated.length)) + pc9.cyan("\u2502"));
2533
+ }
2534
+ }
2535
+ if (parsedFromLog?.logPath) {
2536
+ lines.push(pc9.cyan(`\u251C${horizontal}\u2524`));
2537
+ const logInfo = ` Run log: ${pc9.dim(parsedFromLog.logPath)}`;
2538
+ const logPlain = ` Run log: ${parsedFromLog.logPath}`;
2539
+ lines.push(pc9.cyan("\u2502") + logInfo + " ".repeat(Math.max(1, width - 2 - logPlain.length)) + pc9.cyan("\u2502"));
2540
+ }
2541
+ lines.push(pc9.cyan(`\u2514${horizontal}\u2518`));
2542
+ return lines.join("\n");
2543
+ }
2544
+ function renderErrorCard(options) {
2545
+ const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 86);
2546
+ const horizontal = "\u2500".repeat(width - 2);
2547
+ const lines = [];
2548
+ lines.push(pc9.red(`\u250C${horizontal}\u2510`));
2549
+ const target = options.pr ? ` \xB7 PR #${options.pr}` : options.issue ? ` \xB7 Issue #${options.issue}` : "";
2550
+ const durationStr = options.durationMs ? ` (${Math.round(options.durationMs / 1e3)}s)` : "";
2551
+ const routineUpper = options.routine.toUpperCase();
2552
+ const header = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
2553
+ const headerPlain = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
2554
+ lines.push(
2555
+ pc9.red("\u2502") + pc9.bold(pc9.red(header.slice(0, width - 3))) + " ".repeat(Math.max(1, width - 2 - headerPlain.length)) + pc9.red("\u2502")
2556
+ );
2557
+ lines.push(pc9.red(`\u251C${horizontal}\u2524`));
2558
+ const logPath = path11.join(options.repoRoot, ".jonah-fleet", "daemon.log");
2559
+ lines.push(pc9.red("\u2502") + pc9.yellow(" Recent Log Output:") + " ".repeat(Math.max(1, width - 21)) + pc9.red("\u2502"));
2560
+ if (fs11.existsSync(logPath)) {
2561
+ try {
2562
+ const logContent = fs11.readFileSync(logPath, "utf8");
2563
+ const allLines = logContent.split("\n").filter((l) => l.trim().length > 0);
2564
+ const tailLines = allLines.slice(-10);
2565
+ for (const line of tailLines) {
2566
+ const cleaned = sanitizeWorktreePaths(line).trim();
2567
+ const truncated = cleaned.length > width - 6 ? cleaned.slice(0, width - 9) + "..." : cleaned;
2568
+ lines.push(pc9.red("\u2502") + pc9.dim(` ${truncated}`) + " ".repeat(Math.max(1, width - 4 - truncated.length)) + pc9.red("\u2502"));
2569
+ }
2570
+ } catch {
2571
+ lines.push(
2572
+ pc9.red("\u2502") + pc9.dim(" (Could not read .jonah-fleet/daemon.log)") + " ".repeat(Math.max(1, width - 45)) + pc9.red("\u2502")
2573
+ );
2574
+ }
2575
+ } else {
2576
+ lines.push(pc9.red("\u2502") + pc9.dim(" (No daemon.log found)") + " ".repeat(Math.max(1, width - 26)) + pc9.red("\u2502"));
2577
+ }
2578
+ lines.push(pc9.red(`\u251C${horizontal}\u2524`));
2579
+ const relLogPath = path11.relative(options.repoRoot, logPath) || ".jonah-fleet/daemon.log";
2580
+ const footer = ` Full trace: ${relLogPath}`;
2581
+ const truncatedFooter = footer.slice(0, width - 4);
2582
+ lines.push(pc9.red("\u2502") + pc9.dim(truncatedFooter) + " ".repeat(Math.max(1, width - 2 - truncatedFooter.length)) + pc9.red("\u2502"));
2583
+ lines.push(pc9.red(`\u2514${horizontal}\u2518`));
2584
+ return lines.join("\n");
2585
+ }
2586
+ var TerminalSpinner = class {
2587
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2588
+ currentFrame = 0;
2589
+ intervalId = null;
2590
+ startTime = 0;
2591
+ message = "";
2592
+ isRunning = false;
2593
+ isTTY;
2594
+ constructor() {
2595
+ this.isTTY = Boolean(process.stderr.isTTY);
2596
+ }
2597
+ start(initialMessage) {
2598
+ this.message = initialMessage;
2599
+ this.startTime = Date.now();
2600
+ this.isRunning = true;
2601
+ if (!this.isTTY) {
2602
+ process.stderr.write(`[jonah-fleet] ${initialMessage}
2603
+ `);
2604
+ return;
2605
+ }
2606
+ this.intervalId = setInterval(() => {
2607
+ this.render();
2608
+ }, 80);
2609
+ }
2610
+ update(newMessage) {
2611
+ this.message = newMessage;
2612
+ if (!this.isTTY) {
2613
+ process.stderr.write(`[jonah-fleet] ${newMessage}
2614
+ `);
2615
+ }
2616
+ }
2617
+ render() {
2618
+ if (!this.isRunning || !this.isTTY) return;
2619
+ const frame = pc9.cyan(this.frames[this.currentFrame]);
2620
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
2621
+ const elapsedSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
2622
+ const mins = Math.floor(elapsedSeconds / 60);
2623
+ const secs = elapsedSeconds % 60;
2624
+ const timeStr = pc9.dim(`[${mins}m ${secs < 10 ? "0" : ""}${secs}s]`);
2625
+ process.stderr.write(`\r\x1B[K ${frame} ${this.message} ${timeStr}`);
2626
+ }
2627
+ stop() {
2628
+ if (!this.isRunning) return;
2629
+ this.isRunning = false;
2630
+ if (this.intervalId) {
2631
+ clearInterval(this.intervalId);
2632
+ this.intervalId = null;
2633
+ }
2634
+ if (this.isTTY) {
2635
+ process.stderr.write("\r\x1B[K");
2636
+ }
2637
+ }
2638
+ };
2639
+
2287
2640
  // src/lib/runner.ts
2288
2641
  function discoverSkillsPrompt(targetDir) {
2289
- const skillsDir = path11.join(targetDir, ".agents", "skills");
2290
- if (!fs11.existsSync(skillsDir)) return "";
2642
+ const skillsDir = path12.join(targetDir, ".agents", "skills");
2643
+ if (!fs12.existsSync(skillsDir)) return "";
2291
2644
  let skillsPrompt = "";
2292
2645
  try {
2293
- const entries = fs11.readdirSync(skillsDir, { withFileTypes: true });
2646
+ const entries = fs12.readdirSync(skillsDir, { withFileTypes: true });
2294
2647
  for (const entry of entries) {
2295
2648
  if (entry.isDirectory()) {
2296
- const skillPath = path11.join(".agents", "skills", entry.name, "SKILL.md");
2297
- const fullPath = path11.join(targetDir, skillPath);
2298
- if (fs11.existsSync(fullPath)) {
2649
+ const skillPath = path12.join(".agents", "skills", entry.name, "SKILL.md");
2650
+ const fullPath = path12.join(targetDir, skillPath);
2651
+ if (fs12.existsSync(fullPath)) {
2299
2652
  skillsPrompt += `Read and follow ${skillPath}. `;
2300
2653
  }
2301
2654
  }
@@ -2322,14 +2675,14 @@ function buildRoutinePrompt(targetDir, routine, options = {}) {
2322
2675
  return `You are the ${routine} routine for this repository. Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}`;
2323
2676
  }
2324
2677
  async function runLocalRoutine(options) {
2325
- const targetDir = path11.resolve(options.targetDir);
2678
+ const targetDir = path12.resolve(options.targetDir);
2326
2679
  const routine = options.routine;
2327
2680
  const model = options.model || "gemini-3.7-flash-high";
2328
2681
  const printTimeout = options.printTimeout || "30m";
2329
2682
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2330
2683
  const hostname = os2.hostname();
2331
- const promptFile = path11.join(targetDir, ".github", "prompts", `${routine}.md`);
2332
- if (!fs11.existsSync(promptFile)) {
2684
+ const promptFile = path12.join(targetDir, ".github", "prompts", `${routine}.md`);
2685
+ if (!fs12.existsSync(promptFile)) {
2333
2686
  throw new Error(`Routine prompt file not found: ${promptFile}`);
2334
2687
  }
2335
2688
  let branchName = `agent/${routine}-${timestamp}`;
@@ -2382,7 +2735,19 @@ Timeout: ${printTimeout}`,
2382
2735
  ];
2383
2736
  let output = "";
2384
2737
  let exitCode = 0;
2738
+ const startTime = Date.now();
2739
+ const logDir = path12.join(targetDir, ".jonah-fleet");
2740
+ fs12.mkdirSync(logDir, { recursive: true });
2741
+ const logFilePath = path12.join(logDir, "daemon.log");
2742
+ let targetLabel = options.pr ? `PR #${options.pr}` : options.issue ? `Issue #${options.issue}` : routine;
2743
+ let dynamicTargetDetected = Boolean(options.pr || options.issue);
2744
+ let activePhase = "Starting session...";
2745
+ const spinner = !options.verbose ? new TerminalSpinner() : null;
2746
+ if (spinner) {
2747
+ spinner.start(`${targetLabel}: ${activePhase}`);
2748
+ }
2385
2749
  const cleanup = async () => {
2750
+ spinner?.stop();
2386
2751
  if (worktreePath && !options.keepWorktree) {
2387
2752
  await removeWorktree(targetDir, worktreePath, { deleteBranch: false }).catch(() => {
2388
2753
  });
@@ -2394,6 +2759,40 @@ Timeout: ${printTimeout}`,
2394
2759
  };
2395
2760
  process.once("SIGINT", sigintHandler);
2396
2761
  process.once("SIGTERM", sigintHandler);
2762
+ const processChunk = (chunk, isStderr = false) => {
2763
+ output += chunk;
2764
+ try {
2765
+ fs12.appendFileSync(logFilePath, chunk, "utf8");
2766
+ } catch {
2767
+ }
2768
+ if (!dynamicTargetDetected) {
2769
+ const detected = routine === "peer-review" ? detectClaimedPR(chunk) : detectClaimedIssue(chunk);
2770
+ if (detected) {
2771
+ dynamicTargetDetected = true;
2772
+ targetLabel = detected;
2773
+ options.onTargetDetected?.(detected);
2774
+ if (spinner) {
2775
+ spinner.update(`${targetLabel}: ${activePhase}`);
2776
+ }
2777
+ }
2778
+ }
2779
+ if (options.onLog) {
2780
+ options.onLog(chunk);
2781
+ }
2782
+ if (options.verbose) {
2783
+ if (isStderr) {
2784
+ process.stderr.write(chunk);
2785
+ } else {
2786
+ process.stdout.write(chunk);
2787
+ }
2788
+ } else if (spinner) {
2789
+ const newPhase = detectActivePhase(chunk, activePhase);
2790
+ if (newPhase !== activePhase) {
2791
+ activePhase = newPhase;
2792
+ spinner.update(`${targetLabel}: ${activePhase}`);
2793
+ }
2794
+ }
2795
+ };
2397
2796
  try {
2398
2797
  exitCode = await new Promise((resolve, reject) => {
2399
2798
  const child = spawn("agy", args, {
@@ -2402,37 +2801,56 @@ Timeout: ${printTimeout}`,
2402
2801
  stdio: ["inherit", "pipe", "pipe"]
2403
2802
  });
2404
2803
  child.stdout?.on("data", (data) => {
2405
- const chunk = data.toString();
2406
- output += chunk;
2407
- if (options.onLog) {
2408
- options.onLog(chunk);
2409
- } else {
2410
- process.stdout.write(chunk);
2411
- }
2804
+ processChunk(data.toString(), false);
2412
2805
  });
2413
2806
  child.stderr?.on("data", (data) => {
2414
- const chunk = data.toString();
2415
- output += chunk;
2416
- if (options.onLog) {
2417
- options.onLog(chunk);
2418
- } else {
2419
- process.stderr.write(chunk);
2420
- }
2807
+ processChunk(data.toString(), true);
2421
2808
  });
2422
2809
  child.on("error", (err) => {
2810
+ spinner?.stop();
2423
2811
  reject(err);
2424
2812
  });
2425
2813
  child.on("close", (code) => {
2814
+ spinner?.stop();
2426
2815
  resolve(code ?? 0);
2427
2816
  });
2428
2817
  });
2429
2818
  } finally {
2819
+ spinner?.stop();
2430
2820
  process.removeListener("SIGINT", sigintHandler);
2431
2821
  process.removeListener("SIGTERM", sigintHandler);
2432
2822
  if (!options.keepWorktree) {
2433
2823
  await cleanup();
2434
2824
  }
2435
2825
  }
2826
+ if (options.showCard !== false && !options.verbose) {
2827
+ const durationMs = Date.now() - startTime;
2828
+ const effectiveIssue = options.issue || (targetLabel.startsWith("Issue #") ? targetLabel.replace("Issue #", "") : void 0);
2829
+ const effectivePR = options.pr || (targetLabel.startsWith("PR #") ? targetLabel.replace("PR #", "") : void 0);
2830
+ if (exitCode === 0) {
2831
+ console.log(
2832
+ "\n" + renderSummaryCard({
2833
+ routine,
2834
+ output,
2835
+ repoRoot: targetDir,
2836
+ issue: effectiveIssue,
2837
+ pr: effectivePR,
2838
+ durationMs
2839
+ }) + "\n"
2840
+ );
2841
+ } else {
2842
+ console.log(
2843
+ "\n" + renderErrorCard({
2844
+ routine,
2845
+ exitCode,
2846
+ repoRoot: targetDir,
2847
+ issue: effectiveIssue,
2848
+ pr: effectivePR,
2849
+ durationMs
2850
+ }) + "\n"
2851
+ );
2852
+ }
2853
+ }
2436
2854
  return {
2437
2855
  success: exitCode === 0,
2438
2856
  exitCode,
@@ -2448,28 +2866,31 @@ async function runRoutineCommand(routine, options = {}) {
2448
2866
  const manifest = loadManifest(cwd);
2449
2867
  if (!manifest) {
2450
2868
  console.warn(
2451
- pc9.yellow(`\u26A0\uFE0F No agents-manifest.json found in ${cwd}. Running in unmanaged repository mode.`)
2869
+ pc10.yellow(`\u26A0\uFE0F No agents-manifest.json found in ${cwd}. Running in unmanaged repository mode.`)
2452
2870
  );
2453
2871
  } else if (manifest.routines && manifest.routines[routine] === false) {
2454
2872
  console.warn(
2455
- pc9.yellow(`\u26A0\uFE0F Routine '${routine}' is disabled in agents-manifest.json. Running anyway via explicit command.`)
2873
+ pc10.yellow(`\u26A0\uFE0F Routine '${routine}' is disabled in agents-manifest.json. Running anyway via explicit command.`)
2456
2874
  );
2457
2875
  }
2458
- console.log(pc9.cyan(`
2459
- \u{1F680} Launching local agent session for routine: ${pc9.bold(routine)}`));
2876
+ console.log(pc10.cyan(`
2877
+ \u{1F680} Launching local agent session for routine: ${pc10.bold(routine)}`));
2460
2878
  if (options.issue) {
2461
- console.log(pc9.dim(` Target issue: #${options.issue}`));
2879
+ console.log(pc10.dim(` Target issue: #${options.issue}`));
2462
2880
  }
2463
2881
  if (options.pr) {
2464
- console.log(pc9.dim(` Target pull request: #${options.pr}`));
2882
+ console.log(pc10.dim(` Target pull request: #${options.pr}`));
2465
2883
  }
2466
2884
  if (options.model) {
2467
- console.log(pc9.dim(` Model override: ${options.model}`));
2885
+ console.log(pc10.dim(` Model override: ${options.model}`));
2886
+ }
2887
+ if (options.verbose) {
2888
+ console.log(pc10.dim(` Verbose output: Enabled (streaming raw tokens)`));
2468
2889
  }
2469
2890
  if (options.worktree !== false) {
2470
- console.log(pc9.dim(` Workspace isolation: Git Worktree (.jonah-fleet/worktrees/)`));
2891
+ console.log(pc10.dim(` Workspace isolation: Git Worktree (.jonah-fleet/worktrees/)`));
2471
2892
  } else {
2472
- console.log(pc9.yellow(` Workspace isolation: Disabled (running in current directory)`));
2893
+ console.log(pc10.yellow(` Workspace isolation: Disabled (running in current directory)`));
2473
2894
  }
2474
2895
  console.log("");
2475
2896
  try {
@@ -2482,59 +2903,60 @@ async function runRoutineCommand(routine, options = {}) {
2482
2903
  printTimeout: options.timeout,
2483
2904
  noWorktree: options.worktree === false,
2484
2905
  keepWorktree: options.keepWorktree,
2485
- dryRun: options.dryRun
2906
+ dryRun: options.dryRun,
2907
+ verbose: options.verbose
2486
2908
  });
2487
2909
  if (options.dryRun) {
2488
- console.log(pc9.green(result.output));
2910
+ console.log(pc10.green(result.output));
2489
2911
  return;
2490
2912
  }
2491
2913
  if (result.success) {
2492
- console.log(pc9.green(`
2914
+ console.log(pc10.green(`
2493
2915
  \u2713 Local agent session for '${routine}' completed successfully.`));
2494
2916
  } else {
2495
- console.error(pc9.red(`
2917
+ console.error(pc10.red(`
2496
2918
  \u2717 Local agent session for '${routine}' failed with exit code ${result.exitCode}.`));
2497
2919
  process.exit(result.exitCode);
2498
2920
  }
2499
2921
  } catch (error) {
2500
- console.error(pc9.red(`
2922
+ console.error(pc10.red(`
2501
2923
  \u2717 Failed to execute routine '${routine}': ${error.message}`));
2502
2924
  process.exit(1);
2503
2925
  }
2504
2926
  }
2505
2927
 
2506
2928
  // src/commands/daemon.ts
2507
- import pc11 from "picocolors";
2929
+ import pc12 from "picocolors";
2508
2930
 
2509
2931
  // src/lib/daemon.ts
2510
- import fs12 from "fs";
2511
- import path12 from "path";
2932
+ import fs13 from "fs";
2933
+ import path13 from "path";
2512
2934
  import { spawn as spawn2, execFile as execFile3 } from "child_process";
2513
2935
  import { promisify as promisify3 } from "util";
2514
- import pc10 from "picocolors";
2936
+ import pc11 from "picocolors";
2515
2937
  var execFileAsync3 = promisify3(execFile3);
2516
2938
  function getDaemonStatePath(repoRoot) {
2517
- return path12.join(repoRoot, ".jonah-fleet", "daemon.json");
2939
+ return path13.join(repoRoot, ".jonah-fleet", "daemon.json");
2518
2940
  }
2519
2941
  function readDaemonState(repoRoot) {
2520
2942
  const statePath = getDaemonStatePath(repoRoot);
2521
- if (!fs12.existsSync(statePath)) return null;
2943
+ if (!fs13.existsSync(statePath)) return null;
2522
2944
  try {
2523
- return JSON.parse(fs12.readFileSync(statePath, "utf8"));
2945
+ return JSON.parse(fs13.readFileSync(statePath, "utf8"));
2524
2946
  } catch {
2525
2947
  return null;
2526
2948
  }
2527
2949
  }
2528
2950
  function writeDaemonState(repoRoot, state) {
2529
2951
  const statePath = getDaemonStatePath(repoRoot);
2530
- fs12.mkdirSync(path12.dirname(statePath), { recursive: true });
2531
- fs12.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
2952
+ fs13.mkdirSync(path13.dirname(statePath), { recursive: true });
2953
+ fs13.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
2532
2954
  }
2533
2955
  function clearDaemonState(repoRoot) {
2534
2956
  const statePath = getDaemonStatePath(repoRoot);
2535
- if (fs12.existsSync(statePath)) {
2957
+ if (fs13.existsSync(statePath)) {
2536
2958
  try {
2537
- fs12.unlinkSync(statePath);
2959
+ fs13.unlinkSync(statePath);
2538
2960
  } catch {
2539
2961
  }
2540
2962
  }
@@ -2570,9 +2992,9 @@ async function startBackgroundDaemon(repoRoot, options = {}) {
2570
2992
  const reviewInterval = options.reviewInterval || 3;
2571
2993
  const autoworkInterval = options.autoworkInterval || options.interval || 30;
2572
2994
  const routines = options.routines || ["peer-review", "autowork"];
2573
- const logFilePath = path12.join(repoRoot, ".jonah-fleet", "daemon.log");
2574
- fs12.mkdirSync(path12.dirname(logFilePath), { recursive: true });
2575
- const logFd = fs12.openSync(logFilePath, "a");
2995
+ const logFilePath = path13.join(repoRoot, ".jonah-fleet", "daemon.log");
2996
+ fs13.mkdirSync(path13.dirname(logFilePath), { recursive: true });
2997
+ const logFd = fs13.openSync(logFilePath, "a");
2576
2998
  const cliPath = process.argv[1];
2577
2999
  const args = [
2578
3000
  "daemon",
@@ -2587,6 +3009,9 @@ async function startBackgroundDaemon(repoRoot, options = {}) {
2587
3009
  if (options.model) {
2588
3010
  args.push("--model", options.model);
2589
3011
  }
3012
+ if (options.verbose) {
3013
+ args.push("--verbose");
3014
+ }
2590
3015
  const child = spawn2(process.execPath, [cliPath, ...args], {
2591
3016
  cwd: repoRoot,
2592
3017
  detached: true,
@@ -2631,19 +3056,48 @@ async function runDaemonLoop(repoRoot, options = {}) {
2631
3056
  status: "idle"
2632
3057
  };
2633
3058
  writeDaemonState(repoRoot, state);
2634
- console.log(pc10.cyan(`
3059
+ console.log(pc11.cyan(`
2635
3060
  \u{1F916} Jonah Fleet Multi-Cadence Local Agent Daemon Started`));
2636
- console.log(pc10.dim(` PID: ${process.pid}`));
2637
- console.log(pc10.dim(` Peer Review Watchdog: Every ${reviewInterval} minutes (with zero-cost PR preflight)`));
2638
- console.log(pc10.dim(` Autowork Backlog Scan: Every ${autoworkInterval} minutes`));
2639
- console.log(pc10.dim(` Working Directory: ${repoRoot}
3061
+ console.log(pc11.dim(` PID: ${process.pid}`));
3062
+ console.log(pc11.dim(` Peer Review Watchdog: Every ${reviewInterval} minutes (with zero-cost PR preflight)`));
3063
+ console.log(pc11.dim(` Autowork Backlog Scan: Every ${autoworkInterval} minutes`));
3064
+ console.log(pc11.dim(` Working Directory: ${repoRoot}
2640
3065
  `));
2641
3066
  let isStopping = false;
2642
3067
  let isWorking = false;
3068
+ const reviewIntervalMs = reviewInterval * 60 * 1e3;
3069
+ const autoworkIntervalMs = autoworkInterval * 60 * 1e3;
3070
+ let nextReviewCheckTime = Date.now() + (routines.includes("peer-review") ? reviewIntervalMs : Infinity);
3071
+ let nextAutoworkCheckTime = Date.now() + (routines.includes("autowork") ? autoworkIntervalMs : Infinity);
3072
+ let lastOpenPRCount = void 0;
3073
+ const clearTicker = () => {
3074
+ if (process.stderr.isTTY && !options.verbose) {
3075
+ process.stderr.write("\r\x1B[K");
3076
+ }
3077
+ };
3078
+ const updateTicker = () => {
3079
+ if (isStopping || isWorking || options.verbose || !process.stderr.isTTY) return;
3080
+ const now = Date.now();
3081
+ const nextCheck = Math.min(nextReviewCheckTime, nextAutoworkCheckTime);
3082
+ const diffMs = Math.max(0, nextCheck - now);
3083
+ const remainingSecs = Math.ceil(diffMs / 1e3);
3084
+ const mins = Math.floor(remainingSecs / 60);
3085
+ const secs = remainingSecs % 60;
3086
+ const timeStr = `${mins}m ${secs < 10 ? "0" : ""}${secs}s`;
3087
+ const prStr = lastOpenPRCount !== void 0 ? ` (${lastOpenPRCount} ready PRs)` : "";
3088
+ process.stderr.write(
3089
+ `\r\x1B[K${pc11.dim("[" + (/* @__PURE__ */ new Date()).toLocaleTimeString() + "]")} \u{1F4A4} ${pc11.dim("Watchdog Idle \xB7 Next check in " + timeStr + prStr)}`
3090
+ );
3091
+ };
3092
+ const tickerInterval = setInterval(updateTicker, 1e3);
2643
3093
  const handleStop = async () => {
2644
3094
  if (isStopping) return;
2645
3095
  isStopping = true;
2646
- console.log(pc10.yellow(`
3096
+ clearInterval(tickerInterval);
3097
+ clearInterval(reviewTimer);
3098
+ clearInterval(autoworkTimer);
3099
+ clearTicker();
3100
+ console.log(pc11.yellow(`
2647
3101
  Stopping local agent daemon...`));
2648
3102
  clearDaemonState(repoRoot);
2649
3103
  await cleanupStaleWorktrees(repoRoot);
@@ -2656,36 +3110,52 @@ Stopping local agent daemon...`));
2656
3110
  state.lastReviewCheckAt = (/* @__PURE__ */ new Date()).toISOString();
2657
3111
  writeDaemonState(repoRoot, state);
2658
3112
  const openPRCount = await countOpenReadyPRs(repoRoot);
3113
+ lastOpenPRCount = openPRCount;
2659
3114
  if (openPRCount === 0) {
2660
- console.log(pc10.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Peer Review Watchdog: 0 ready PRs found (0 tokens used).`));
3115
+ if (options.verbose) {
3116
+ console.log(pc11.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Peer Review Watchdog: 0 ready PRs found (0 tokens used).`));
3117
+ }
3118
+ nextReviewCheckTime = Date.now() + reviewIntervalMs;
3119
+ updateTicker();
2661
3120
  return;
2662
3121
  }
2663
3122
  try {
2664
3123
  isWorking = true;
3124
+ clearTicker();
2665
3125
  state.status = "working";
2666
3126
  state.activeRoutine = "peer-review";
2667
3127
  writeDaemonState(repoRoot, state);
2668
- console.log(pc10.cyan(`
3128
+ console.log(pc11.cyan(`
2669
3129
  [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F50D} Peer Review Watchdog: Found ${openPRCount} ready PR(s). Starting review session...`));
2670
3130
  await cleanupStaleWorktrees(repoRoot);
2671
3131
  const result = await runLocalRoutine({
2672
3132
  targetDir: repoRoot,
2673
3133
  routine: "peer-review",
2674
3134
  model: options.model,
2675
- noWorktree: false
3135
+ verbose: options.verbose,
3136
+ noWorktree: false,
3137
+ onTargetDetected: (target) => {
3138
+ state.activeTarget = target;
3139
+ writeDaemonState(repoRoot, state);
3140
+ }
2676
3141
  });
2677
3142
  if (result.success) {
2678
- console.log(pc10.green(`\u2713 Local peer-review completed successfully.`));
3143
+ console.log(pc11.green(`\u2713 Local peer-review completed successfully.
3144
+ `));
2679
3145
  } else {
2680
- console.warn(pc10.yellow(`\u26A0\uFE0F Local peer-review completed with code ${result.exitCode}.`));
3146
+ console.warn(pc11.yellow(`\u26A0\uFE0F Local peer-review completed with code ${result.exitCode}.
3147
+ `));
2681
3148
  }
2682
3149
  } catch (err) {
2683
- console.error(pc10.red(`\u2717 Error in peer-review: ${err.message}`));
3150
+ console.error(pc11.red(`\u2717 Error in peer-review: ${err.message}`));
2684
3151
  } finally {
2685
3152
  isWorking = false;
2686
3153
  state.status = "idle";
2687
3154
  state.activeRoutine = void 0;
3155
+ state.activeTarget = void 0;
2688
3156
  writeDaemonState(repoRoot, state);
3157
+ nextReviewCheckTime = Date.now() + reviewIntervalMs;
3158
+ updateTicker();
2689
3159
  }
2690
3160
  };
2691
3161
  const runAutoworkCheck = async () => {
@@ -2694,30 +3164,41 @@ Stopping local agent daemon...`));
2694
3164
  writeDaemonState(repoRoot, state);
2695
3165
  try {
2696
3166
  isWorking = true;
3167
+ clearTicker();
2697
3168
  state.status = "working";
2698
3169
  state.activeRoutine = "autowork";
2699
3170
  writeDaemonState(repoRoot, state);
2700
- console.log(pc10.cyan(`
3171
+ console.log(pc11.cyan(`
2701
3172
  [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F680} Autowork Backlog Scan: Starting session...`));
2702
3173
  await cleanupStaleWorktrees(repoRoot);
2703
3174
  const result = await runLocalRoutine({
2704
3175
  targetDir: repoRoot,
2705
3176
  routine: "autowork",
2706
3177
  model: options.model,
2707
- noWorktree: false
3178
+ verbose: options.verbose,
3179
+ noWorktree: false,
3180
+ onTargetDetected: (target) => {
3181
+ state.activeTarget = target;
3182
+ writeDaemonState(repoRoot, state);
3183
+ }
2708
3184
  });
2709
3185
  if (result.success) {
2710
- console.log(pc10.green(`\u2713 Local autowork completed successfully.`));
3186
+ console.log(pc11.green(`\u2713 Local autowork completed successfully.
3187
+ `));
2711
3188
  } else {
2712
- console.warn(pc10.yellow(`\u26A0\uFE0F Local autowork completed with code ${result.exitCode}.`));
3189
+ console.warn(pc11.yellow(`\u26A0\uFE0F Local autowork completed with code ${result.exitCode}.
3190
+ `));
2713
3191
  }
2714
3192
  } catch (err) {
2715
- console.error(pc10.red(`\u2717 Error in autowork: ${err.message}`));
3193
+ console.error(pc11.red(`\u2717 Error in autowork: ${err.message}`));
2716
3194
  } finally {
2717
3195
  isWorking = false;
2718
3196
  state.status = "idle";
2719
3197
  state.activeRoutine = void 0;
3198
+ state.activeTarget = void 0;
2720
3199
  writeDaemonState(repoRoot, state);
3200
+ nextAutoworkCheckTime = Date.now() + autoworkIntervalMs;
3201
+ updateTicker();
2721
3202
  }
2722
3203
  };
2723
3204
  if (routines.includes("peer-review")) {
@@ -2726,8 +3207,6 @@ Stopping local agent daemon...`));
2726
3207
  if (routines.includes("autowork")) {
2727
3208
  await runAutoworkCheck();
2728
3209
  }
2729
- const reviewIntervalMs = reviewInterval * 60 * 1e3;
2730
- const autoworkIntervalMs = autoworkInterval * 60 * 1e3;
2731
3210
  const reviewTimer = setInterval(runReviewCheck, reviewIntervalMs);
2732
3211
  const autoworkTimer = setInterval(runAutoworkCheck, autoworkIntervalMs);
2733
3212
  await new Promise(() => {
@@ -2744,7 +3223,8 @@ async function runDaemonCommand(action, options = {}) {
2744
3223
  autoworkInterval: options.autoworkInterval ? parseInt(options.autoworkInterval, 10) : options.interval ? parseInt(options.interval, 10) : void 0,
2745
3224
  routines: options.routines ? options.routines.split(",").map((r) => r.trim()) : void 0,
2746
3225
  model: options.model,
2747
- foreground: options.foreground
3226
+ foreground: options.foreground,
3227
+ verbose: options.verbose
2748
3228
  };
2749
3229
  if (act === "start") {
2750
3230
  if (options.foreground) {
@@ -2753,16 +3233,16 @@ async function runDaemonCommand(action, options = {}) {
2753
3233
  }
2754
3234
  try {
2755
3235
  const state2 = await startBackgroundDaemon(cwd, daemonOpts);
2756
- console.log(pc11.green(`
3236
+ console.log(pc12.green(`
2757
3237
  \u2713 Background agent daemon started successfully.`));
2758
- console.log(pc11.dim(` PID: ${state2.pid}`));
2759
- console.log(pc11.dim(` Peer Review Watchdog: Every ${state2.reviewIntervalMinutes} minutes (zero-cost PR preflight)`));
2760
- console.log(pc11.dim(` Autowork Backlog Scan: Every ${state2.autoworkIntervalMinutes} minutes`));
2761
- console.log(pc11.dim(` Routines: ${state2.routines.join(", ")}`));
2762
- console.log(pc11.dim(` Log file: .jonah-fleet/daemon.log`));
2763
- console.log(pc11.dim(` Run 'jonah-fleet daemon status' or 'jonah-fleet daemon stop' to manage.`));
3238
+ console.log(pc12.dim(` PID: ${state2.pid}`));
3239
+ console.log(pc12.dim(` Peer Review Watchdog: Every ${state2.reviewIntervalMinutes} minutes (zero-cost PR preflight)`));
3240
+ console.log(pc12.dim(` Autowork Backlog Scan: Every ${state2.autoworkIntervalMinutes} minutes`));
3241
+ console.log(pc12.dim(` Routines: ${state2.routines.join(", ")}`));
3242
+ console.log(pc12.dim(` Log file: .jonah-fleet/daemon.log`));
3243
+ console.log(pc12.dim(` Run 'jonah-fleet daemon status' or 'jonah-fleet daemon stop' to manage.`));
2764
3244
  } catch (err) {
2765
- console.error(pc11.red(`
3245
+ console.error(pc12.red(`
2766
3246
  \u2717 Failed to start daemon: ${err.message}`));
2767
3247
  process.exit(1);
2768
3248
  }
@@ -2770,18 +3250,18 @@ async function runDaemonCommand(action, options = {}) {
2770
3250
  }
2771
3251
  if (act === "stop") {
2772
3252
  if (!isDaemonRunning(cwd)) {
2773
- console.log(pc11.yellow(`
3253
+ console.log(pc12.yellow(`
2774
3254
  \u26A0\uFE0F No local agent daemon is currently running in this repository.`));
2775
3255
  return;
2776
3256
  }
2777
3257
  const state2 = readDaemonState(cwd);
2778
- console.log(pc11.cyan(`
3258
+ console.log(pc12.cyan(`
2779
3259
  Stopping background agent daemon (PID ${state2?.pid})...`));
2780
3260
  const stopped = await stopDaemon(cwd);
2781
3261
  if (stopped) {
2782
- console.log(pc11.green(`\u2713 Local agent daemon stopped successfully.`));
3262
+ console.log(pc12.green(`\u2713 Local agent daemon stopped successfully.`));
2783
3263
  } else {
2784
- console.error(pc11.red(`\u2717 Could not terminate daemon process.`));
3264
+ console.error(pc12.red(`\u2717 Could not terminate daemon process.`));
2785
3265
  process.exit(1);
2786
3266
  }
2787
3267
  return;
@@ -2793,17 +3273,18 @@ Stopping background agent daemon (PID ${state2?.pid})...`));
2793
3273
  const running = isDaemonRunning(cwd);
2794
3274
  const state = readDaemonState(cwd);
2795
3275
  const activeWorktrees = await listActiveWorktrees(cwd);
2796
- console.log(pc11.cyan(`
3276
+ console.log(pc12.cyan(`
2797
3277
  \u{1F916} Jonah Fleet Local Daemon Status
2798
3278
  `));
2799
3279
  if (running && state) {
2800
- console.log(` Status: ${pc11.green(pc11.bold("RUNNING"))}`);
3280
+ console.log(` Status: ${pc12.green(pc12.bold("RUNNING"))}`);
2801
3281
  console.log(` PID: ${state.pid}`);
2802
3282
  console.log(` Started: ${new Date(state.startedAt).toLocaleString()}`);
2803
3283
  console.log(` Peer Review Cadence: Every ${state.reviewIntervalMinutes} minutes (0-token fast preflight)`);
2804
3284
  console.log(` Autowork Cadence: Every ${state.autoworkIntervalMinutes} minutes`);
2805
3285
  console.log(` Routines: ${state.routines.join(", ")}`);
2806
- console.log(` Current State: ${state.status === "working" ? pc11.yellow("WORKING on " + state.activeRoutine) : pc11.green("IDLE")}`);
3286
+ const workingDesc = state.activeRoutine + (state.activeTarget ? ` (${pc12.bold(state.activeTarget)})` : "");
3287
+ console.log(` Current State: ${state.status === "working" ? pc12.yellow("WORKING on " + workingDesc) : pc12.green("IDLE")}`);
2807
3288
  if (state.lastReviewCheckAt) {
2808
3289
  console.log(` Last Review Check: ${new Date(state.lastReviewCheckAt).toLocaleTimeString()}`);
2809
3290
  }
@@ -2811,13 +3292,13 @@ Stopping background agent daemon (PID ${state2?.pid})...`));
2811
3292
  console.log(` Last Autowork Check: ${new Date(state.lastAutoworkCheckAt).toLocaleTimeString()}`);
2812
3293
  }
2813
3294
  } else {
2814
- console.log(` Status: ${pc11.gray("STOPPED")}`);
2815
- console.log(pc11.dim(` Run 'jonah-fleet daemon start' to start the local worker daemon.`));
3295
+ console.log(` Status: ${pc12.gray("STOPPED")}`);
3296
+ console.log(pc12.dim(` Run 'jonah-fleet daemon start' to start the local worker daemon.`));
2816
3297
  }
2817
3298
  console.log(`
2818
3299
  Active Worktrees: ${activeWorktrees.length}`);
2819
3300
  for (const wt of activeWorktrees) {
2820
- console.log(pc11.dim(` - [${wt.branch}] ${wt.path}`));
3301
+ console.log(pc12.dim(` - [${wt.branch}] ${wt.path}`));
2821
3302
  }
2822
3303
  console.log("");
2823
3304
  }
@@ -2825,10 +3306,10 @@ Stopping background agent daemon (PID ${state2?.pid})...`));
2825
3306
  // src/index.ts
2826
3307
  var program = new Command();
2827
3308
  program.name("jonah-fleet").description("Manage autonomous agent fleet, prompt routines, workflows, and skills").version(FLEET_VERSION);
2828
- program.command("run <routine>").description("Run a specific prompt routine locally in an isolated git worktree").option("-i, --issue <number>", "Targeted issue number for autowork").option("-p, --pr <number>", "Targeted pull request number for peer-review").option("-m, --model <model>", "LLM model override (defaults to gemini-3.7-flash-high)").option("--timeout <duration>", "CLI execution print timeout (default: 30m)").option("--no-worktree", "Execute directly in current directory without creating a git worktree").option("--keep-worktree", "Preserve the git worktree after routine execution completes").option("-d, --dry-run", "Preview prompt and execution parameters without launching agent").action(async (routine, options) => {
3309
+ program.command("run <routine>").description("Run a specific prompt routine locally in an isolated git worktree").option("-i, --issue <number>", "Targeted issue number for autowork").option("-p, --pr <number>", "Targeted pull request number for peer-review").option("-m, --model <model>", "LLM model override (defaults to gemini-3.7-flash-high)").option("--timeout <duration>", "CLI execution print timeout (default: 30m)").option("--no-worktree", "Execute directly in current directory without creating a git worktree").option("--keep-worktree", "Preserve the git worktree after routine execution completes").option("-d, --dry-run", "Preview prompt and execution parameters without launching agent").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (routine, options) => {
2829
3310
  await runRoutineCommand(routine, options);
2830
3311
  });
2831
- program.command("daemon [action]").description("Manage background local worker daemon polling for unclaimed issues and pull requests").option("-i, --interval <minutes>", "Legacy global polling interval in minutes (default: 30)").option("--review-interval <minutes>", "Peer Review watchdog cadence in minutes (default: 3)").option("--autowork-interval <minutes>", "Autowork backlog cadence in minutes (default: 30)").option("-r, --routines <list>", "Comma-separated routines to run (default: peer-review,autowork)").option("-m, --model <model>", "LLM model override").option("--foreground", "Run daemon in foreground with live console logs").action(async (action, options) => {
3312
+ program.command("daemon [action]").description("Manage background local worker daemon polling for unclaimed issues and pull requests").option("-i, --interval <minutes>", "Legacy global polling interval in minutes (default: 30)").option("--review-interval <minutes>", "Peer Review watchdog cadence in minutes (default: 3)").option("--autowork-interval <minutes>", "Autowork backlog cadence in minutes (default: 30)").option("-r, --routines <list>", "Comma-separated routines to run (default: peer-review,autowork)").option("-m, --model <model>", "LLM model override").option("--foreground", "Run daemon in foreground with live console logs").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (action, options) => {
2832
3313
  await runDaemonCommand(action, options);
2833
3314
  });
2834
3315
  program.command("init").description("Initialize Jonah Fleet configuration, routines, workflows, and skills in the current repo").option("-p, --preset <preset>", "Preset profile to install (minimal | standard | full)", "standard").option("-f, --force", "Force overwrite existing files", false).option("--stack <stack>", "Override detected tech stack name").option("--package-manager <pm>", "Override package manager (npm, pnpm, yarn, bun, uv, poetry, cargo, go)").option("--test-cmd <cmd>", "Override test execution command").option("--build-cmd <cmd>", "Override build execution command").option("--interactive", "Force interactive prompts for stack configuration").option("--no-interactive", "Disable interactive prompts").action(async (options) => {