@matthugh1/conductor-cli 0.2.0 → 0.2.3

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/agent.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- ensureDatabaseUrl,
4
- postToServer
5
- } from "./chunk-MJKFQIYA.js";
6
2
 
7
3
  // ../../src/cli/agent.ts
8
4
  import { resolve } from "path";
@@ -18,6 +14,12 @@ function parseArgs(argv) {
18
14
  let consecutiveFailures = 3;
19
15
  let skipPermissions = false;
20
16
  let dryRun = false;
17
+ let checkInterval = 3e4;
18
+ let maxPerDay = 50;
19
+ let maxConcurrent = 1;
20
+ let noWorktree = false;
21
+ let apiUrl;
22
+ let apiKey;
21
23
  let command;
22
24
  let subcommand;
23
25
  for (let i = 0; i < args.length; i++) {
@@ -36,6 +38,18 @@ function parseArgs(argv) {
36
38
  skipPermissions = true;
37
39
  } else if (arg === "--dry-run") {
38
40
  dryRun = true;
41
+ } else if (arg === "--check-interval" && i + 1 < args.length) {
42
+ checkInterval = Number.parseInt(args[++i], 10);
43
+ } else if (arg === "--max-per-day" && i + 1 < args.length) {
44
+ maxPerDay = Number.parseInt(args[++i], 10);
45
+ } else if (arg === "--max-concurrent" && i + 1 < args.length) {
46
+ maxConcurrent = Number.parseInt(args[++i], 10);
47
+ } else if (arg === "--no-worktree") {
48
+ noWorktree = true;
49
+ } else if (arg === "--api-url" && i + 1 < args.length) {
50
+ apiUrl = args[++i];
51
+ } else if (arg === "--api-key" && i + 1 < args.length) {
52
+ apiKey = args[++i];
39
53
  } else if (arg === "--json") {
40
54
  json = true;
41
55
  } else if (arg === "--help" || arg === "-h") {
@@ -48,7 +62,7 @@ function parseArgs(argv) {
48
62
  subcommand = arg;
49
63
  }
50
64
  }
51
- return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun };
65
+ return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun, checkInterval, maxPerDay, maxConcurrent, noWorktree, apiUrl, apiKey };
52
66
  }
53
67
  var HELP_TEXT = `
54
68
  Conductor \u2014 local agent CLI
@@ -62,10 +76,12 @@ Commands:
62
76
  hooks Check hook status or install hooks
63
77
  watch Poll for cleanup tasks and execute them locally
64
78
  run Autonomous runner \u2014 work through deliverable queue
79
+ daemon Long-running daemon \u2014 polls for autonomous work
80
+ daemon cancel Stop a running daemon
65
81
 
66
82
  Options:
67
83
  --project-root <path> Project directory (default: cwd)
68
- --project <name> Project name (for run command)
84
+ --project <name> Project name (for run/daemon commands)
69
85
  --json Output as JSON instead of plain English
70
86
  --once Process one task and exit (watch only)
71
87
  --help Show this help text
@@ -77,6 +93,15 @@ Run options:
77
93
  --skip-permissions Allow Claude to write files without prompting
78
94
  --dry-run Show queue without spawning agents
79
95
 
96
+ Daemon options:
97
+ --check-interval <ms> Poll interval in milliseconds (default: 30000)
98
+ --max-per-day <n> Max tasks per day (default: 50)
99
+ --max-concurrent <n> Max concurrent agent tasks (default: 1)
100
+ --timeout <minutes> Max minutes per deliverable (default: 120)
101
+ --consecutive-failures <n> Stop after N failures in a row (default: 3)
102
+ --no-worktree Skip git worktree isolation
103
+ --skip-permissions Allow Claude to write files without prompting
104
+
80
105
  Examples:
81
106
  conductor check
82
107
  conductor init --project-root /path/to/project
@@ -85,6 +110,8 @@ Examples:
85
110
  conductor watch
86
111
  conductor run --project Conductor --skip-permissions
87
112
  conductor run --project Conductor --dry-run
113
+ conductor daemon --project Conductor
114
+ conductor daemon cancel --project Conductor
88
115
  `.trim();
89
116
  function writeOut(text) {
90
117
  process.stdout.write(text + "\n");
@@ -93,12 +120,10 @@ function writeErr(text) {
93
120
  process.stderr.write(text + "\n");
94
121
  }
95
122
  async function cmdCheck(projectRoot, jsonOutput) {
96
- const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
97
- const projects = await dbQuery(
98
- "SELECT id FROM projects WHERE path = $1 LIMIT 1",
99
- [projectRoot]
100
- );
101
- if (projects.length === 0) {
123
+ const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
124
+ const client = createDaemonClient();
125
+ const project = await client.resolveProjectByPath(projectRoot);
126
+ if (!project) {
102
127
  if (jsonOutput) {
103
128
  writeOut(
104
129
  JSON.stringify({
@@ -113,8 +138,8 @@ async function cmdCheck(projectRoot, jsonOutput) {
113
138
  }
114
139
  return 1;
115
140
  }
116
- const { getEnvironmentHealthReport } = await import("./health-CTND2ANA.js");
117
- const report = await getEnvironmentHealthReport(projects[0].id);
141
+ const projectId = project.id;
142
+ const report = await runLocalHealthChecks(projectRoot);
118
143
  if (jsonOutput) {
119
144
  writeOut(JSON.stringify(report, null, 2));
120
145
  } else {
@@ -126,18 +151,15 @@ async function cmdCheck(projectRoot, jsonOutput) {
126
151
  }
127
152
  }
128
153
  try {
129
- const { saveHealthSnapshot } = await import("./health-snapshots-6MUVHE3G.js");
130
- await saveHealthSnapshot(projects[0].id, report);
154
+ await client.saveHealthSnapshot(projectId, report);
131
155
  } catch {
132
- const posted = await postToServer("/api/environment/health", { report }, projectRoot);
133
- if (!posted && !jsonOutput) {
156
+ if (!jsonOutput) {
134
157
  writeErr("\u26A0 Could not sync results to the dashboard.");
135
158
  }
136
159
  }
137
160
  try {
138
- const { getGitBranchInfo, formatGitBranchLine, getStatusSummary, getRecentCommits } = await import("./git-wrapper-DVJ46TMA.js");
139
- const { getHookStatus } = await import("./git-hooks-UZJ6AER4.js");
140
- const { saveGitStatusSnapshot } = await import("./git-snapshots-N3FBS7T3.js");
161
+ const { getGitBranchInfo, formatGitBranchLine, getStatusSummary, getRecentCommits, runGit } = await import("./git-wrapper-QRZYTYCZ.js");
162
+ const { getHookStatus } = await import("./git-hooks-RQ6WJQS4.js");
141
163
  const branchInfo = await getGitBranchInfo(projectRoot);
142
164
  const branchLine = formatGitBranchLine(branchInfo);
143
165
  const status = await getStatusSummary(projectRoot);
@@ -151,21 +173,20 @@ async function cmdCheck(projectRoot, jsonOutput) {
151
173
  hookStatus = await getHookStatus(projectRoot);
152
174
  } catch {
153
175
  }
154
- const worktreeRows = await dbQuery(
155
- "SELECT branch_name, worktree_path FROM worktrees WHERE project_id = $1 AND status = 'active'",
156
- [projects[0].id]
157
- );
158
176
  const worktreeCommits = {};
159
- const { runGit } = await import("./git-wrapper-DVJ46TMA.js");
160
- for (const wt of worktreeRows) {
161
- try {
162
- const out = await runGit(wt.worktree_path, ["log", "-1", "--format=%cI"]);
163
- const date = out.trim();
164
- if (date) worktreeCommits[wt.branch_name] = date;
165
- } catch {
177
+ try {
178
+ const worktrees = await client.listActiveWorktrees(projectId);
179
+ for (const wt of worktrees) {
180
+ try {
181
+ const out = await runGit(wt.worktreePath, ["log", "-1", "--format=%cI"]);
182
+ const date = out.trim();
183
+ if (date) worktreeCommits[wt.branchName] = date;
184
+ } catch {
185
+ }
166
186
  }
187
+ } catch {
167
188
  }
168
- await saveGitStatusSnapshot(projects[0].id, {
189
+ await client.saveGitStatusSnapshot(projectId, {
169
190
  branch: branchInfo.branchName,
170
191
  branchLine,
171
192
  status,
@@ -187,17 +208,16 @@ async function cmdCheck(projectRoot, jsonOutput) {
187
208
  }
188
209
  }
189
210
  try {
190
- const { getGitBranchInfo } = await import("./git-wrapper-DVJ46TMA.js");
191
- const { getBranchOverview } = await import("./branch-overview-XVHTGFCJ.js");
192
- const { saveBranchOverviewSnapshot } = await import("./git-snapshots-N3FBS7T3.js");
211
+ const { getGitBranchInfo } = await import("./git-wrapper-QRZYTYCZ.js");
212
+ const { getBranchOverview } = await import("./branch-overview-RRHX3XGY.js");
193
213
  const branchInfo = await getGitBranchInfo(projectRoot);
194
214
  let currentBranch = branchInfo.branchName;
195
215
  if (currentBranch?.startsWith("detached (")) currentBranch = null;
196
216
  const overview = await getBranchOverview(projectRoot, {
197
- projectId: projects[0].id,
217
+ projectId: null,
198
218
  currentBranch
199
219
  });
200
- await saveBranchOverviewSnapshot(projects[0].id, overview);
220
+ await client.saveBranchOverviewSnapshot(projectId, overview);
201
221
  if (!jsonOutput) {
202
222
  writeOut(`Branches: ${overview.branches.length} found`);
203
223
  }
@@ -213,14 +233,73 @@ async function cmdCheck(projectRoot, jsonOutput) {
213
233
  if (hasWarn) return 2;
214
234
  return 0;
215
235
  }
236
+ async function runLocalHealthChecks(projectRoot) {
237
+ const { existsSync } = await import("fs");
238
+ const { readFile } = await import("fs/promises");
239
+ const { join } = await import("path");
240
+ const { runGit } = await import("./git-wrapper-QRZYTYCZ.js");
241
+ const start = performance.now();
242
+ const checks = [];
243
+ const gitStart = performance.now();
244
+ try {
245
+ const output = await runGit(projectRoot, ["--version"]);
246
+ const version = output.trim().replace("git version ", "");
247
+ checks.push({ name: "Git", status: "pass", message: `Git ${version} found`, durationMs: Math.round(performance.now() - gitStart) });
248
+ } catch {
249
+ checks.push({ name: "Git", status: "fail", message: "Git not found or not working", durationMs: Math.round(performance.now() - gitStart) });
250
+ }
251
+ const hookStart = performance.now();
252
+ const hookPath = join(projectRoot, ".git", "hooks", "post-commit");
253
+ const CONDUCTOR_HOOK_MARKER = "# Conductor narration hook";
254
+ if (!existsSync(hookPath)) {
255
+ checks.push({
256
+ name: "Hooks",
257
+ status: "warn",
258
+ message: "Hooks not installed",
259
+ fixAction: { label: "Install hooks", href: "/git" },
260
+ durationMs: Math.round(performance.now() - hookStart)
261
+ });
262
+ } else {
263
+ try {
264
+ const content = await readFile(hookPath, "utf-8");
265
+ if (content.includes(CONDUCTOR_HOOK_MARKER)) {
266
+ checks.push({ name: "Hooks", status: "pass", message: "Hooks installed", durationMs: Math.round(performance.now() - hookStart) });
267
+ } else {
268
+ checks.push({
269
+ name: "Hooks",
270
+ status: "warn",
271
+ message: "Hook file exists but is not a Conductor hook",
272
+ fixAction: { label: "Install hooks", href: "/git" },
273
+ durationMs: Math.round(performance.now() - hookStart)
274
+ });
275
+ }
276
+ } catch {
277
+ checks.push({ name: "Hooks", status: "warn", message: "Could not read hook file", durationMs: Math.round(performance.now() - hookStart) });
278
+ }
279
+ }
280
+ const apiStart = performance.now();
281
+ try {
282
+ const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
283
+ await createDaemonClient().resolveProjectByPath(projectRoot);
284
+ checks.push({ name: "API", status: "pass", message: "Conductor server reachable", durationMs: Math.round(performance.now() - apiStart) });
285
+ } catch {
286
+ checks.push({ name: "API", status: "warn", message: "Conductor server not reachable", durationMs: Math.round(performance.now() - apiStart) });
287
+ }
288
+ const hasFail = checks.some((c) => c.status === "fail");
289
+ const hasWarn = checks.some((c) => c.status === "warn");
290
+ const overall = hasFail ? "unhealthy" : hasWarn ? "degraded" : "healthy";
291
+ return {
292
+ checks,
293
+ overall,
294
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
295
+ totalDurationMs: Math.round(performance.now() - start)
296
+ };
297
+ }
216
298
  async function cmdScan(projectRoot, jsonOutput) {
217
- const { scanWorktrees } = await import("./worktree-manager-QKRBTPVC.js");
218
- const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
219
- const projects = await dbQuery(
220
- "SELECT id FROM projects WHERE path = $1 LIMIT 1",
221
- [projectRoot]
222
- );
223
- if (projects.length === 0) {
299
+ const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
300
+ const client = createDaemonClient();
301
+ const project = await client.resolveProjectByPath(projectRoot);
302
+ if (!project) {
224
303
  if (jsonOutput) {
225
304
  writeOut(
226
305
  JSON.stringify({
@@ -235,8 +314,7 @@ async function cmdScan(projectRoot, jsonOutput) {
235
314
  }
236
315
  return 1;
237
316
  }
238
- const projectId = projects[0].id;
239
- const result = await scanWorktrees(projectId, projectRoot);
317
+ const { scan: result } = await client.scanWorktreesViaApi(project.id);
240
318
  if (jsonOutput) {
241
319
  writeOut(JSON.stringify(result, null, 2));
242
320
  } else {
@@ -262,7 +340,7 @@ async function cmdScan(projectRoot, jsonOutput) {
262
340
  async function cmdInit(projectRoot, jsonOutput) {
263
341
  const { resolve: resolvePath, basename, join } = await import("path");
264
342
  const { realpath, mkdir, readFile, writeFile, access } = await import("fs/promises");
265
- const { getServerBaseUrl } = await import("./cli-config-TDSTAXIA.js");
343
+ const { getServerBaseUrl } = await import("./cli-config-LEERSU5N.js");
266
344
  let root = resolvePath(projectRoot.trim());
267
345
  try {
268
346
  root = await realpath(root);
@@ -337,7 +415,7 @@ async function cmdInit(projectRoot, jsonOutput) {
337
415
  return 0;
338
416
  }
339
417
  async function cmdHooks(projectRoot, subcommand, jsonOutput) {
340
- const { getHookStatus, installHooks } = await import("./git-hooks-UZJ6AER4.js");
418
+ const { getHookStatus, installHooks } = await import("./git-hooks-RQ6WJQS4.js");
341
419
  if (subcommand === "install") {
342
420
  const result = await installHooks(projectRoot);
343
421
  if (jsonOutput) {
@@ -379,13 +457,11 @@ async function cmdHooks(projectRoot, subcommand, jsonOutput) {
379
457
  return 2;
380
458
  }
381
459
  async function cmdWatch(projectRoot, once, jsonOutput) {
382
- const { claimNextTask, executeTask, failStaleTasks } = await import("./cli-tasks-NW3BONXC.js");
383
- const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
384
- const projects = await dbQuery(
385
- "SELECT id FROM projects WHERE path = $1 LIMIT 1",
386
- [projectRoot]
387
- );
388
- if (projects.length === 0) {
460
+ const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
461
+ const { runGit } = await import("./git-wrapper-QRZYTYCZ.js");
462
+ const client = createDaemonClient();
463
+ const project = await client.resolveProjectByPath(projectRoot);
464
+ if (!project) {
389
465
  if (jsonOutput) {
390
466
  writeOut(
391
467
  JSON.stringify({
@@ -400,13 +476,56 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
400
476
  }
401
477
  return 1;
402
478
  }
403
- const projectId = projects[0].id;
404
- const staleCount = await failStaleTasks(projectId);
479
+ const projectId = project.id;
480
+ async function executeTaskViaApi(task) {
481
+ try {
482
+ switch (task.taskType) {
483
+ case "remove_worktree": {
484
+ const worktreeId = task.payload.worktreeId;
485
+ if (!worktreeId) {
486
+ const msg = "Missing worktreeId in task payload.";
487
+ await client.failCliTask(task.id, msg);
488
+ return msg;
489
+ }
490
+ await client.removeWorktreeViaApi(projectId, worktreeId);
491
+ await client.completeCliTask(task.id, { worktreeId });
492
+ return `Removed worktree ${worktreeId}`;
493
+ }
494
+ case "delete_branch": {
495
+ const branchName = task.payload.branchName;
496
+ if (!branchName) {
497
+ const msg = "Missing branchName in task payload.";
498
+ await client.failCliTask(task.id, msg);
499
+ return msg;
500
+ }
501
+ await runGit(projectRoot, ["branch", "-D", branchName]);
502
+ await client.logGitActivityViaApi({
503
+ projectRoot,
504
+ eventType: "branch_delete",
505
+ branch: branchName,
506
+ summary: `Deleted branch "${branchName}"`
507
+ });
508
+ await client.completeCliTask(task.id, { branchName });
509
+ return `Deleted branch "${branchName}"`;
510
+ }
511
+ default: {
512
+ const msg = `Unknown task type: ${task.taskType}`;
513
+ await client.failCliTask(task.id, msg);
514
+ return msg;
515
+ }
516
+ }
517
+ } catch (err) {
518
+ const msg = err instanceof Error ? err.message : String(err);
519
+ await client.failCliTask(task.id, msg);
520
+ return `Failed: ${msg}`;
521
+ }
522
+ }
523
+ const staleCount = await client.failStaleCliTasks(projectId);
405
524
  if (staleCount > 0 && !jsonOutput) {
406
525
  writeOut(`Auto-failed ${staleCount} stale task(s).`);
407
526
  }
408
527
  if (once) {
409
- const task = await claimNextTask(projectId);
528
+ const task = await client.claimNextCliTask(projectId);
410
529
  if (!task) {
411
530
  if (jsonOutput) {
412
531
  writeOut(JSON.stringify({ message: "No pending tasks." }));
@@ -415,7 +534,7 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
415
534
  }
416
535
  return 0;
417
536
  }
418
- const summary = await executeTask(task);
537
+ const summary = await executeTaskViaApi(task);
419
538
  if (jsonOutput) {
420
539
  writeOut(JSON.stringify({ taskId: task.id, taskType: task.taskType, summary }));
421
540
  } else {
@@ -437,9 +556,9 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
437
556
  process.on("SIGTERM", shutdown);
438
557
  while (running) {
439
558
  try {
440
- const task = await claimNextTask(projectId);
559
+ const task = await client.claimNextCliTask(projectId);
441
560
  if (task) {
442
- const summary = await executeTask(task);
561
+ const summary = await executeTaskViaApi(task);
443
562
  if (jsonOutput) {
444
563
  writeOut(
445
564
  JSON.stringify({ taskId: task.id, taskType: task.taskType, summary })
@@ -448,7 +567,7 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
448
567
  writeOut(`[${task.taskType}] ${summary}`);
449
568
  }
450
569
  }
451
- await failStaleTasks(projectId);
570
+ await client.failStaleCliTasks(projectId);
452
571
  } catch (err) {
453
572
  const msg = err instanceof Error ? err.message : String(err);
454
573
  if (!jsonOutput) {
@@ -464,24 +583,22 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
464
583
  async function cmdRun(opts) {
465
584
  const { spawn } = await import("child_process");
466
585
  const { readFileSync, existsSync } = await import("fs");
467
- const { assembleAutonomousPrompt } = await import("./runner-prompt-2B6EXGN6.js");
468
- const { computeWorkQueue } = await import("./work-queue-YE5P4S7R.js");
469
- const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
470
- let project = opts.projectName ?? opts.projectRoot;
471
- if (opts.projectName) {
472
- const rows = await dbQuery(
473
- "SELECT path FROM projects WHERE name = $1 LIMIT 1",
474
- [opts.projectName]
475
- );
476
- if (rows.length === 0) {
477
- writeErr(`Project "${opts.projectName}" not found in Conductor.`);
478
- return 1;
479
- }
480
- if (rows[0].path === null) {
481
- writeErr(`Project "${opts.projectName}" has no local path registered.`);
482
- return 1;
483
- }
484
- project = rows[0].path;
586
+ const { assembleAutonomousPrompt } = await import("./runner-prompt-MOOPKA5P.js");
587
+ const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
588
+ const client = createDaemonClient();
589
+ let project;
590
+ let projectId;
591
+ try {
592
+ const resolved = await client.resolveProject({
593
+ name: opts.projectName,
594
+ path: opts.projectName ? void 0 : opts.projectRoot
595
+ });
596
+ project = resolved.path;
597
+ projectId = resolved.id;
598
+ } catch (err) {
599
+ const msg = err instanceof Error ? err.message : String(err);
600
+ writeErr(`Project not found: ${msg}`);
601
+ return 1;
485
602
  }
486
603
  function ts() {
487
604
  return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
@@ -525,7 +642,7 @@ async function cmdRun(opts) {
525
642
  });
526
643
  }
527
644
  async function getNextItem(skipIds) {
528
- const queue = await computeWorkQueue(project, 50, { autonomous: true });
645
+ const queue = await client.pollWorkQueue(projectId, 50, { autonomous: true });
529
646
  for (const item of queue.queue) {
530
647
  if (item.type === "deliverable" && (item.tier === "ready" || item.tier === "active") && !skipIds.has(item.entityId)) {
531
648
  return item;
@@ -647,13 +764,6 @@ async function main() {
647
764
  writeOut(HELP_TEXT);
648
765
  process.exit(parsed.help ? 0 : 1);
649
766
  }
650
- const hasDb = await ensureDatabaseUrl();
651
- if (!hasDb) {
652
- writeErr(
653
- "Could not find a database connection. Make sure the MCP server is running (npm run mcp) and try again."
654
- );
655
- process.exit(1);
656
- }
657
767
  try {
658
768
  let exitCode;
659
769
  switch (parsed.command) {
@@ -692,6 +802,34 @@ async function main() {
692
802
  jsonOutput: parsed.json
693
803
  });
694
804
  break;
805
+ case "daemon": {
806
+ const { cmdDaemon, cmdDaemonCancel } = await import("./daemon-ZJDZIP3R.js");
807
+ if (parsed.subcommand === "cancel") {
808
+ exitCode = await cmdDaemonCancel(
809
+ parsed.projectRoot,
810
+ parsed.project,
811
+ parsed.json,
812
+ parsed.apiUrl,
813
+ parsed.apiKey
814
+ );
815
+ } else {
816
+ exitCode = await cmdDaemon({
817
+ projectRoot: parsed.projectRoot,
818
+ projectName: parsed.project,
819
+ apiUrl: parsed.apiUrl,
820
+ apiKey: parsed.apiKey,
821
+ checkInterval: parsed.checkInterval,
822
+ maxPerDay: parsed.maxPerDay,
823
+ maxConsecutiveFailures: parsed.consecutiveFailures,
824
+ timeout: parsed.timeout,
825
+ maxConcurrent: parsed.maxConcurrent,
826
+ noWorktree: parsed.noWorktree,
827
+ skipPermissions: parsed.skipPermissions,
828
+ jsonOutput: parsed.json
829
+ });
830
+ }
831
+ break;
832
+ }
695
833
  default:
696
834
  writeErr(`Unknown command: ${parsed.command}`);
697
835
  writeErr("Run conductor --help for usage.");
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ConductorError,
3
4
  runGit
4
- } from "./chunk-FAZ7FCZQ.js";
5
- import {
6
- query
7
- } from "./chunk-PANC6BTV.js";
5
+ } from "./chunk-N2KKNG4C.js";
8
6
 
9
7
  // ../../src/core/branch-overview-match.ts
10
8
  function slugifyTitle(title) {
@@ -71,6 +69,114 @@ function resolveDeliverableLink(branchName, hints, sessionByBranch) {
71
69
  return null;
72
70
  }
73
71
 
72
+ // ../../src/core/db.ts
73
+ import { Pool } from "pg";
74
+ var pool = null;
75
+ function getDatabaseUrl() {
76
+ const url = process.env.DATABASE_URL;
77
+ if (!url) {
78
+ throw new ConductorError(
79
+ "DB_UNREACHABLE" /* DB_UNREACHABLE */,
80
+ "DATABASE_URL is not set. Set it to your Supabase (or other Postgres) connection string."
81
+ );
82
+ }
83
+ return url;
84
+ }
85
+ function getPool() {
86
+ if (pool) {
87
+ return pool;
88
+ }
89
+ const connectionString = getDatabaseUrl();
90
+ const isRemote = connectionString.includes("supabase.co");
91
+ pool = new Pool({
92
+ connectionString,
93
+ max: 10,
94
+ connectionTimeoutMillis: 5e3,
95
+ idleTimeoutMillis: 3e4,
96
+ ...isRemote ? { ssl: { rejectUnauthorized: false } } : {}
97
+ });
98
+ pool.on("error", () => {
99
+ console.error(
100
+ "Unexpected database error on an idle connection. Check that Postgres is running."
101
+ );
102
+ });
103
+ return pool;
104
+ }
105
+ var TRANSIENT_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"]);
106
+ function isTransientConnectionError(err) {
107
+ const code = getErrorCode(err);
108
+ if (code && TRANSIENT_CODES.has(code)) return true;
109
+ const msg = err instanceof Error ? err.message : String(err);
110
+ return msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("connection terminated unexpectedly");
111
+ }
112
+ async function query(sql, params) {
113
+ const p = getPool();
114
+ try {
115
+ const result = await p.query(sql, params);
116
+ return result.rows;
117
+ } catch (err) {
118
+ if (isTransientConnectionError(err)) {
119
+ await new Promise((r) => setTimeout(r, 1e3));
120
+ try {
121
+ const result = await p.query(sql, params);
122
+ return result.rows;
123
+ } catch (retryErr) {
124
+ throw mapDbError(retryErr);
125
+ }
126
+ }
127
+ throw mapDbError(err);
128
+ }
129
+ }
130
+ function getErrorCode(err) {
131
+ if (err !== null && typeof err === "object" && "code" in err) {
132
+ const code = err.code;
133
+ return typeof code === "string" ? code : void 0;
134
+ }
135
+ return void 0;
136
+ }
137
+ function getBestErrorMessage(err) {
138
+ if (err instanceof AggregateError && err.errors.length > 0) {
139
+ const first = err.errors[0];
140
+ if (first instanceof Error && first.message.trim().length > 0) {
141
+ return first.message;
142
+ }
143
+ }
144
+ if (err instanceof Error && err.message.trim().length > 0) {
145
+ return err.message;
146
+ }
147
+ return String(err);
148
+ }
149
+ function mapDbError(err) {
150
+ const code = getErrorCode(err);
151
+ const message = getBestErrorMessage(err);
152
+ if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ETIMEDOUT" || message.includes("ECONNREFUSED") || message.includes("ECONNRESET") || message.includes("ETIMEDOUT") || message.includes("connect ECONNREFUSED") || message.includes("connection terminated unexpectedly")) {
153
+ return new ConductorError(
154
+ "DB_UNREACHABLE" /* DB_UNREACHABLE */,
155
+ "Could not reach the database. Postgres may not be running on this machine.",
156
+ true
157
+ );
158
+ }
159
+ if (message.includes("password") || message.includes("authentication") || message.includes("28P01")) {
160
+ return new ConductorError(
161
+ "DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
162
+ "Could not sign in to the database. Check your username and password."
163
+ );
164
+ }
165
+ if (message.includes("does not exist") && message.includes("database")) {
166
+ return new ConductorError(
167
+ "DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
168
+ "That database does not exist yet. Create it first, then try again."
169
+ );
170
+ }
171
+ if (message.trim().length > 0) {
172
+ return new ConductorError("DB_QUERY_FAILED" /* DB_QUERY_FAILED */, message);
173
+ }
174
+ return new ConductorError(
175
+ "DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
176
+ "Something went wrong while talking to the database."
177
+ );
178
+ }
179
+
74
180
  // ../../src/core/branch-overview.ts
75
181
  var STALE_MS = 7 * 24 * 60 * 60 * 1e3;
76
182
  var MAX_COMMITS = 20;
@@ -339,8 +445,9 @@ async function getConflictBranchesForWorkQueue(projectRoot, options) {
339
445
  }
340
446
  }
341
447
  const conflicts = [];
448
+ const SKIP_BRANCHES = /* @__PURE__ */ new Set(["dev", "develop", "staging"]);
342
449
  for (const name of namesToScan) {
343
- if (name === defaultBranch) {
450
+ if (name === defaultBranch || SKIP_BRANCHES.has(name)) {
344
451
  continue;
345
452
  }
346
453
  let lastIso = "";
@@ -595,11 +702,10 @@ async function getMergeStats(projectRoot, defaultBranch, featureBranch) {
595
702
  const files = await loadChangedFiles(projectRoot, defaultBranch, featureBranch);
596
703
  return { commitCount, fileCount: files.length };
597
704
  }
598
-
599
705
  export {
600
- isBranchFullyOnMain,
601
- getConflictBranchesForWorkQueue,
602
- getBranchOverview,
603
706
  assertCanQueueMerge,
604
- getMergeStats
707
+ getBranchOverview,
708
+ getConflictBranchesForWorkQueue,
709
+ getMergeStats,
710
+ isBranchFullyOnMain
605
711
  };