@matthugh1/conductor-cli 0.1.0 → 0.2.2

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.
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../../src/core/daemon-client-types.ts
4
+ var DaemonClientError = class extends Error {
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ this.name = "DaemonClientError";
9
+ }
10
+ status;
11
+ };
12
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
13
+ var MAX_RETRIES = 2;
14
+ async function fetchWithRetry(url, init) {
15
+ let lastError;
16
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
17
+ try {
18
+ const res = await fetch(url, init);
19
+ if (attempt < MAX_RETRIES && RETRY_STATUSES.has(res.status)) {
20
+ await sleep(backoffMs(attempt));
21
+ continue;
22
+ }
23
+ return res;
24
+ } catch (err) {
25
+ lastError = err;
26
+ if (attempt < MAX_RETRIES && isTransient(err)) {
27
+ await sleep(backoffMs(attempt));
28
+ continue;
29
+ }
30
+ throw err;
31
+ }
32
+ }
33
+ throw lastError;
34
+ }
35
+ function isTransient(err) {
36
+ if (err instanceof TypeError) return true;
37
+ if (err instanceof Error) {
38
+ const msg = err.message.toLowerCase();
39
+ return msg.includes("econnrefused") || msg.includes("econnreset");
40
+ }
41
+ return false;
42
+ }
43
+ function backoffMs(attempt) {
44
+ return (attempt + 1) * 500;
45
+ }
46
+ function sleep(ms) {
47
+ return new Promise((r) => setTimeout(r, ms));
48
+ }
49
+ async function assertOk(res, context) {
50
+ if (res.ok) return;
51
+ let message = `${context}: HTTP ${res.status}`;
52
+ try {
53
+ const body = await res.json();
54
+ if (body.error) message = `${context}: ${body.error}`;
55
+ } catch {
56
+ }
57
+ throw new DaemonClientError(message, res.status);
58
+ }
59
+ async function jsonBody(res, context) {
60
+ await assertOk(res, context);
61
+ return await res.json();
62
+ }
63
+
64
+ // ../../src/core/daemon-client.ts
65
+ function createDaemonClient(baseUrl = "https://conductor-297703646986.europe-west2.run.app", apiKey) {
66
+ const base = baseUrl.replace(/\/+$/, "");
67
+ let defaultProjectId;
68
+ let resolvedKey = apiKey ?? process.env.CONDUCTOR_API_KEY;
69
+ let cliConfigLoaded = false;
70
+ async function ensureConfig() {
71
+ if (cliConfigLoaded) return;
72
+ cliConfigLoaded = true;
73
+ try {
74
+ const { readApiKey, readProjectId } = await import("./cli-config-2ZDXUUQN.js");
75
+ if (!resolvedKey) resolvedKey = readApiKey();
76
+ if (!defaultProjectId) defaultProjectId = readProjectId();
77
+ } catch {
78
+ }
79
+ }
80
+ function authHeaders() {
81
+ const headers = {};
82
+ if (resolvedKey) {
83
+ headers.Authorization = `Bearer ${resolvedKey}`;
84
+ }
85
+ return headers;
86
+ }
87
+ function url(path, params) {
88
+ const u = new URL(path, base);
89
+ if (defaultProjectId) {
90
+ u.searchParams.set("projectId", defaultProjectId);
91
+ }
92
+ if (params) {
93
+ for (const [k, v] of Object.entries(params)) {
94
+ u.searchParams.set(k, v);
95
+ }
96
+ }
97
+ return u.toString();
98
+ }
99
+ async function post(path, body, params) {
100
+ await ensureConfig();
101
+ return fetchWithRetry(url(path, params), {
102
+ method: "POST",
103
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
104
+ body: JSON.stringify(body)
105
+ });
106
+ }
107
+ async function patch(path, body, params) {
108
+ await ensureConfig();
109
+ return fetchWithRetry(url(path, params), {
110
+ method: "PATCH",
111
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
112
+ body: JSON.stringify(body)
113
+ });
114
+ }
115
+ async function get(path, params) {
116
+ await ensureConfig();
117
+ return fetchWithRetry(url(path, params), {
118
+ method: "GET",
119
+ headers: authHeaders()
120
+ });
121
+ }
122
+ async function del(path, params) {
123
+ await ensureConfig();
124
+ return fetchWithRetry(url(path, params), {
125
+ method: "DELETE",
126
+ headers: authHeaders()
127
+ });
128
+ }
129
+ return {
130
+ async resolveProject(opts) {
131
+ const res = await get("/api/projects");
132
+ const { projects } = await jsonBody(
133
+ res,
134
+ "resolveProject"
135
+ );
136
+ const match = projects.find((p) => {
137
+ if (opts.name && p.name === opts.name) return true;
138
+ if (opts.path && p.path === opts.path) return true;
139
+ return false;
140
+ });
141
+ if (!match) {
142
+ throw new DaemonClientError(
143
+ `Project not found (name=${opts.name ?? ""}, path=${opts.path ?? ""})`,
144
+ 404
145
+ );
146
+ }
147
+ if (!match.path) {
148
+ throw new DaemonClientError(
149
+ `Project "${match.name}" has no local path registered.`,
150
+ 400
151
+ );
152
+ }
153
+ defaultProjectId = match.id;
154
+ return { id: match.id, path: match.path };
155
+ },
156
+ async upsertHeartbeat(opts) {
157
+ const res = await post("/api/daemon/heartbeat", opts);
158
+ await assertOk(res, "upsertHeartbeat");
159
+ },
160
+ async clearHeartbeat(projectId) {
161
+ const res = await del("/api/daemon/heartbeat", { projectId });
162
+ await assertOk(res, "clearHeartbeat");
163
+ },
164
+ async createRun(opts) {
165
+ const res = await post("/api/daemon/runs", opts);
166
+ const { run } = await jsonBody(res, "createRun");
167
+ return run;
168
+ },
169
+ async completeRun(runId, opts) {
170
+ const res = await patch(`/api/daemon/runs/${runId}`, opts);
171
+ await assertOk(res, "completeRun");
172
+ },
173
+ async updateRunPid(runId, pid) {
174
+ const res = await patch(`/api/daemon/runs/${runId}`, { pid });
175
+ await assertOk(res, "updateRunPid");
176
+ },
177
+ async getQuota(projectId) {
178
+ const res = await get("/api/daemon/quota", { projectId });
179
+ const { count } = await jsonBody(res, "getQuota");
180
+ return count;
181
+ },
182
+ async purgeOldRuns(daysOld) {
183
+ const body = daysOld !== void 0 ? { daysOld } : {};
184
+ const res = await post("/api/daemon/purge", body);
185
+ const { purged } = await jsonBody(
186
+ res,
187
+ "purgeOldRuns"
188
+ );
189
+ return purged;
190
+ },
191
+ async pollWorkQueue(projectId, limit, opts) {
192
+ const params = { projectId };
193
+ if (limit !== void 0) params.limit = String(limit);
194
+ if (opts?.autonomous) params.autonomous = "true";
195
+ const res = await get("/api/work-queue", params);
196
+ return jsonBody(res, "pollWorkQueue");
197
+ },
198
+ async updateDeliverableStatus(deliverableId, status) {
199
+ const res = await patch(`/api/backlog/${deliverableId}`, { status });
200
+ await assertOk(res, "updateDeliverableStatus");
201
+ },
202
+ async getDeliverableDetail(deliverableId) {
203
+ const res = await get(`/api/backlog/${deliverableId}`);
204
+ if (res.status === 404) return null;
205
+ const data = await jsonBody(res, "getDeliverableDetail");
206
+ return {
207
+ prompt: data.deliverable?.prompt ?? null,
208
+ type: data.deliverable?.type ?? null
209
+ };
210
+ },
211
+ async getInitiativeForDeliverable(deliverableId) {
212
+ const res = await get(
213
+ `/api/pipeline/deliverables/${deliverableId}`
214
+ );
215
+ if (res.status === 404) return null;
216
+ const data = await jsonBody(res, "getInitiativeForDeliverable");
217
+ const d = data.parent;
218
+ if (!d?.initiativeId || !d.initiativeTitle) return null;
219
+ return {
220
+ initiativeId: d.initiativeId,
221
+ initiativeTitle: d.initiativeTitle
222
+ };
223
+ },
224
+ async appendOutputLine(runId, opts) {
225
+ const res = await post(`/api/daemon/runs/${runId}/output`, opts);
226
+ await assertOk(res, "appendOutputLine");
227
+ },
228
+ async hasPendingDecisions(projectId, deliverableId) {
229
+ const res = await get("/api/decisions", {
230
+ status: "pending",
231
+ scope: "deliverable",
232
+ deliverableId,
233
+ projectId
234
+ });
235
+ const { pending } = await jsonBody(res, "hasPendingDecisions");
236
+ return pending.length > 0;
237
+ },
238
+ async ingestAutonomousDeliverables(projectId, projectRoot) {
239
+ const res = await post("/api/daemon/ingest", {
240
+ projectId,
241
+ projectRoot
242
+ });
243
+ const { created } = await jsonBody(
244
+ res,
245
+ "ingestAutonomousDeliverables"
246
+ );
247
+ return created;
248
+ },
249
+ async routeUnassignedTasks(projectId) {
250
+ const res = await post(
251
+ "/api/daemon/tasks/route-unassigned",
252
+ { projectId }
253
+ );
254
+ const { assignments } = await jsonBody(res, "routeUnassignedTasks");
255
+ return assignments;
256
+ },
257
+ async routePendingTasks(projectId, projectRoot) {
258
+ const res = await post("/api/daemon/route", {
259
+ projectId,
260
+ projectRoot
261
+ });
262
+ return jsonBody(
263
+ res,
264
+ "routePendingTasks"
265
+ );
266
+ },
267
+ // ── Agent backlog methods ───────────────────────────────────────
268
+ async listEnabledAgents(projectId) {
269
+ const res = await get("/api/daemon/agents", {
270
+ projectId,
271
+ enabledOnly: "true"
272
+ });
273
+ const { agents } = await jsonBody(
274
+ res,
275
+ "listEnabledAgents"
276
+ );
277
+ return agents;
278
+ },
279
+ async getNextTasksForAgent(agentId, limit) {
280
+ const params = { agentId };
281
+ if (limit !== void 0) params.limit = String(limit);
282
+ const res = await get("/api/daemon/tasks/next", params);
283
+ const { tasks } = await jsonBody(
284
+ res,
285
+ "getNextTasksForAgent"
286
+ );
287
+ return tasks;
288
+ },
289
+ async claimTask(taskId) {
290
+ const res = await post(`/api/daemon/tasks/${taskId}/claim`, {});
291
+ if (res.status === 409) return null;
292
+ const { task } = await jsonBody(
293
+ res,
294
+ "claimTask"
295
+ );
296
+ return task;
297
+ },
298
+ async updateTask(taskId, updates) {
299
+ const res = await patch(`/api/daemon/tasks/${taskId}`, updates);
300
+ if (res.status === 404) return null;
301
+ const { task } = await jsonBody(
302
+ res,
303
+ "updateTask"
304
+ );
305
+ return task;
306
+ }
307
+ };
308
+ }
309
+ export {
310
+ DaemonClientError,
311
+ createDaemonClient
312
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getProjectPathById
4
- } from "./chunk-VYINBHPQ.js";
4
+ } from "./chunk-6VMREHG4.js";
5
5
  import {
6
6
  runGit
7
7
  } from "./chunk-FAZ7FCZQ.js";
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assembleAutonomousPrompt,
4
+ assembleTaskPrompt
5
+ } from "./chunk-6AA726KG.js";
6
+ export {
7
+ assembleAutonomousPrompt,
8
+ assembleTaskPrompt
9
+ };
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConflictBranchesForWorkQueue
4
- } from "./chunk-IHARLSA6.js";
5
- import "./chunk-VYINBHPQ.js";
4
+ } from "./chunk-7S5HKGS5.js";
5
+ import "./chunk-3MJBQK2F.js";
6
+ import "./chunk-6VMREHG4.js";
6
7
  import {
7
8
  getGitBranchInfo
8
9
  } from "./chunk-FAZ7FCZQ.js";
@@ -444,7 +445,7 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
444
445
  try {
445
446
  const activeRows = await query(
446
447
  `
447
- SELECT d.id, d.title, d.assigned_role, i.title AS initiative_title,
448
+ SELECT d.id, d.title, d.assigned_role, d.autonomous, i.title AS initiative_title,
448
449
  i.rice_score::text AS rice_score
449
450
  FROM deliverables d
450
451
  JOIN outcomes o ON o.id = d.outcome_id
@@ -469,7 +470,8 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
469
470
  entityId: row.id,
470
471
  entityType: "deliverable",
471
472
  initiativeTitle: row.initiative_title,
472
- riceScore: rice
473
+ riceScore: rice,
474
+ autonomous: row.autonomous
473
475
  });
474
476
  }
475
477
  } catch {
@@ -477,7 +479,7 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
477
479
  try {
478
480
  const readyRows = await query(
479
481
  `
480
- SELECT d.id, d.title, d.assigned_role, i.title AS initiative_title,
482
+ SELECT d.id, d.title, d.assigned_role, d.autonomous, i.title AS initiative_title,
481
483
  i.rice_score::text AS rice_score, i.current_stage
482
484
  FROM deliverables d
483
485
  JOIN outcomes o ON o.id = d.outcome_id
@@ -506,7 +508,8 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
506
508
  entityId: row.id,
507
509
  entityType: "deliverable",
508
510
  initiativeTitle: row.initiative_title,
509
- riceScore: rice
511
+ riceScore: rice,
512
+ autonomous: row.autonomous
510
513
  });
511
514
  }
512
515
  } catch {
@@ -555,16 +558,7 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
555
558
  ON prev.outcome_id = cur.outcome_id AND prev.rn = cur.rn - 1
556
559
  WHERE cur.status = 'todo'
557
560
  AND prev.status IN ('done', 'review')
558
- AND NOT EXISTS (
559
- SELECT 1 FROM prompts p
560
- WHERE p.deliverable_id = cur.id
561
- )
562
- AND NOT EXISTS (
563
- SELECT 1 FROM prompts p
564
- WHERE p.project_id = cur.project_id
565
- AND p.initiative_id IS NULL
566
- AND p.deliverable_id IS NULL
567
- )
561
+ AND (cur.prompt IS NULL OR cur.prompt = '')
568
562
  `,
569
563
  [projectId]
570
564
  );
@@ -612,7 +606,7 @@ async function computeWorkQueue(projectRoot, limit = 20, options) {
612
606
  );
613
607
  for (const row of blockedRows) {
614
608
  const rice = parseRice(row.rice_score);
615
- const isUnblocked = row.blocker_status === "done";
609
+ const isUnblocked = row.blocker_status === "done" || row.blocker_status === "review";
616
610
  items.push({
617
611
  priority: isUnblocked ? 0 : 50,
618
612
  tier: isUnblocked ? "ready" : "pipeline",
@@ -11,7 +11,8 @@ import {
11
11
  removeWorktree,
12
12
  scanWorktrees,
13
13
  slugifyForBranch
14
- } from "./chunk-JZT526HU.js";
14
+ } from "./chunk-KB2DTST2.js";
15
+ import "./chunk-3MJBQK2F.js";
15
16
  import "./chunk-FAZ7FCZQ.js";
16
17
  import "./chunk-PANC6BTV.js";
17
18
  import "./chunk-4YEHSYVN.js";
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@matthugh1/conductor-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Conductor CLI — session management, health checks, and autonomous runner for the Conductor pipeline",
5
5
  "type": "module",
6
6
  "bin": {
7
- "conductor": "./dist/agent.js"
7
+ "conductor": "dist/agent.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../../src/core/runner-prompt.ts
4
- var AUTONOMOUS_WORKFLOW = `
5
- ## Identity
6
-
7
- You are an autonomous implementation engineer spawned by Conductor's runner.
8
- You have no human in the loop \u2014 every decision you cannot make yourself must
9
- be parked for morning review. Work carefully and methodically.
10
-
11
- ## Workflow (follow in order)
12
-
13
- ### 1. Start your session
14
-
15
- Call conductor_session_brief with:
16
- - projectRoot: the project name given in your assignment below
17
- - deliverableId: the deliverable ID given in your assignment below
18
- - agentRole: "implementation-engineer"
19
- - agentType: "claude-code"
20
-
21
- Save the returned sessionId \u2014 pass it to every subsequent Conductor call.
22
-
23
- ### 2. Read the brief and understand your assignment
24
-
25
- The session brief contains your deliverable title, description, and
26
- acceptance criteria. Read them carefully before writing any code.
27
-
28
- ### 3. Branch from main
29
-
30
- Run: git checkout main && git pull && git checkout -b <branch-name>
31
-
32
- Name the branch after the deliverable (e.g., feat/prompt-template).
33
- Never branch from another feature branch.
34
-
35
- ### 4. Mark your deliverable in-progress
36
-
37
- Call conductor_update_deliverable with status "in_progress".
38
-
39
- ### 5. Do the work
40
-
41
- Implement the deliverable according to its acceptance criteria.
42
- After each meaningful chunk of work, call conductor_log_built with
43
- a plain English summary and the sessionId.
44
-
45
- ### 6. Self-check before submitting
46
-
47
- Before creating a PR, run ALL of these checks:
48
-
49
- 1. npx tsc --noEmit (type check)
50
- 2. npm run build (build)
51
- 3. npm test (tests)
52
- 4. git status (working tree clean?)
53
- 5. Review your own code for:
54
- - No \`any\` type assertions
55
- - No console.log in production code
56
- - No large files (>200 lines of code)
57
- - No large functions (>50 lines)
58
- - No unused imports
59
- - No secrets or .env files staged
60
-
61
- ### 7. Retry on failure (up to 3 attempts)
62
-
63
- If any check fails:
64
- - Read the error output carefully
65
- - Fix the issue
66
- - Re-run the failing check
67
- - Repeat up to 3 total attempts per check
68
-
69
- If a check still fails after 3 attempts, go to the parking procedure (step 10).
70
-
71
- ### 8. Create a PR
72
-
73
- When all checks pass:
74
- - git add the relevant files (never use git add -A)
75
- - git commit with a descriptive message
76
- - git push -u origin <branch-name>
77
- - gh pr create --title "<deliverable title>" --body "<summary>"
78
- - Save the PR URL \u2014 you need it for the completion report
79
-
80
- Never merge your own PR. Only create it.
81
-
82
- ### 9. Submit for review
83
-
84
- Call conductor_update_deliverable with:
85
- - status: "review"
86
- - completionReport with all check results and the prUrl
87
-
88
- Then call conductor_end_session with the sessionId.
89
-
90
- ### 10. Parking procedure (when stuck)
91
-
92
- If you cannot complete the work:
93
- - If it is a judgment call or ambiguous requirement:
94
- Call conductor_log_decision with the options and reasoning.
95
- - Call conductor_update_deliverable with status "parked".
96
- - Call conductor_log_next describing what remains and why you stopped.
97
- - Call conductor_end_session with the sessionId.
98
- - Exit cleanly.
99
-
100
- ## Rules
101
-
102
- - Follow all rules in CLAUDE.md (included above if present).
103
- - Never merge your own PR.
104
- - Never make silent decisions \u2014 log every judgment call via conductor_log_decision.
105
- - If a subsequent deliverable exists in the same outcome, create a handoff
106
- prompt via conductor_create_prompt before submitting for review.
107
- - Keep commits atomic and messages descriptive.
108
- - Do not install new dependencies without logging a decision.
109
- - Do not modify files outside the scope of your deliverable.
110
- - If you encounter merge conflicts, resolve them yourself.
111
- - Work within the existing code style and patterns.
112
- `.trim();
113
- function assembleAutonomousPrompt(ctx) {
114
- const parts = [];
115
- if (ctx.claudeMd.length > 0) {
116
- parts.push("# Project Rules (CLAUDE.md)\n\n" + ctx.claudeMd);
117
- }
118
- parts.push("# Autonomous Runner Instructions\n\n" + AUTONOMOUS_WORKFLOW);
119
- parts.push(
120
- `# Your Assignment
121
-
122
- Project: ${ctx.projectName}
123
- Deliverable: ${ctx.item.title}
124
- Deliverable ID: ${ctx.item.entityId}
125
- Initiative: ${ctx.item.initiativeTitle ?? "unknown"}
126
- Priority: P${ctx.item.priority}
127
- Action: ${ctx.item.action}
128
-
129
- Start by calling conductor_session_brief with:
130
- - projectRoot: "${ctx.projectName}"
131
- - deliverableId: "${ctx.item.entityId}"
132
- - agentRole: "implementation-engineer"
133
- - agentType: "claude-code"`
134
- );
135
- return parts.join("\n\n---\n\n");
136
- }
137
- export {
138
- assembleAutonomousPrompt
139
- };
File without changes