@matthugh1/conductor-cli 0.2.2 → 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 +166 -90
- package/dist/{chunk-7S5HKGS5.js → branch-overview-RRHX3XGY.js} +114 -9
- package/dist/{chunk-FAZ7FCZQ.js → chunk-N2KKNG4C.js} +15 -3
- package/dist/{chunk-B2WDTKD7.js → cli-config-LEERSU5N.js} +6 -7
- package/dist/{daemon-GGOJDZDB.js → daemon-ZJDZIP3R.js} +24 -15
- package/dist/{daemon-client-BE64H437.js → daemon-client-CTYOJMJP.js} +124 -1
- package/dist/{git-hooks-UZJ6AER4.js → git-hooks-RQ6WJQS4.js} +1 -2
- package/dist/{git-wrapper-DVJ46TMA.js → git-wrapper-QRZYTYCZ.js} +1 -2
- package/package.json +2 -2
- package/dist/branch-overview-DSSCUE5F.js +0 -18
- package/dist/chunk-3MJBQK2F.js +0 -75
- package/dist/chunk-4YEHSYVN.js +0 -17
- package/dist/chunk-6VMREHG4.js +0 -22
- package/dist/chunk-KB2DTST2.js +0 -482
- package/dist/chunk-PANC6BTV.js +0 -151
- package/dist/cli-config-2ZDXUUQN.js +0 -21
- package/dist/cli-tasks-NM5D5PIZ.js +0 -180
- package/dist/db-U6Y3QJDD.js +0 -16
- package/dist/git-snapshots-N3FBS7T3.js +0 -90
- package/dist/health-UFK7YCKQ.js +0 -147
- package/dist/health-snapshots-6MUVHE3G.js +0 -39
- package/dist/work-queue-U3JYHLX2.js +0 -758
- package/dist/worktree-manager-2ZUJEL3L.js +0 -31
|
@@ -1,758 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
getConflictBranchesForWorkQueue
|
|
4
|
-
} from "./chunk-7S5HKGS5.js";
|
|
5
|
-
import "./chunk-3MJBQK2F.js";
|
|
6
|
-
import "./chunk-6VMREHG4.js";
|
|
7
|
-
import {
|
|
8
|
-
getGitBranchInfo
|
|
9
|
-
} from "./chunk-FAZ7FCZQ.js";
|
|
10
|
-
import {
|
|
11
|
-
query
|
|
12
|
-
} from "./chunk-PANC6BTV.js";
|
|
13
|
-
import "./chunk-4YEHSYVN.js";
|
|
14
|
-
|
|
15
|
-
// ../../src/core/work-queue.ts
|
|
16
|
-
import { basename } from "path";
|
|
17
|
-
|
|
18
|
-
// ../../src/core/pipeline.ts
|
|
19
|
-
import path2 from "path";
|
|
20
|
-
|
|
21
|
-
// ../../src/core/project-lookup.ts
|
|
22
|
-
import { realpath } from "fs/promises";
|
|
23
|
-
import path from "path";
|
|
24
|
-
function normalizeProjectPath(projectRoot) {
|
|
25
|
-
return path.resolve(projectRoot.trim());
|
|
26
|
-
}
|
|
27
|
-
async function pathVariantsForLookup(projectRoot) {
|
|
28
|
-
const resolved = normalizeProjectPath(projectRoot);
|
|
29
|
-
const variants = /* @__PURE__ */ new Set([resolved]);
|
|
30
|
-
try {
|
|
31
|
-
const canon = await realpath(resolved);
|
|
32
|
-
variants.add(canon);
|
|
33
|
-
} catch {
|
|
34
|
-
}
|
|
35
|
-
return [...variants];
|
|
36
|
-
}
|
|
37
|
-
async function findProjectByPath(projectRoot) {
|
|
38
|
-
const variants = await pathVariantsForLookup(projectRoot);
|
|
39
|
-
const rows = await query(
|
|
40
|
-
`
|
|
41
|
-
SELECT id, name, path, repo_url
|
|
42
|
-
FROM projects
|
|
43
|
-
WHERE path = ANY($1::text[])
|
|
44
|
-
LIMIT 1
|
|
45
|
-
`,
|
|
46
|
-
[variants]
|
|
47
|
-
);
|
|
48
|
-
return rows.length > 0 ? rows[0] : null;
|
|
49
|
-
}
|
|
50
|
-
async function findProjectByName(name) {
|
|
51
|
-
const rows = await query(
|
|
52
|
-
`SELECT id, name, path, repo_url
|
|
53
|
-
FROM projects
|
|
54
|
-
WHERE lower(name) = lower($1)
|
|
55
|
-
LIMIT 1`,
|
|
56
|
-
[name.trim()]
|
|
57
|
-
);
|
|
58
|
-
return rows.length > 0 ? rows[0] : null;
|
|
59
|
-
}
|
|
60
|
-
async function getRegisteredProjectIdOrNull(projectRoot) {
|
|
61
|
-
try {
|
|
62
|
-
const byPath = await findProjectByPath(projectRoot);
|
|
63
|
-
if (byPath) return byPath.id;
|
|
64
|
-
const trimmed = projectRoot.trim();
|
|
65
|
-
const byName = await findProjectByName(trimmed);
|
|
66
|
-
if (byName) return byName.id;
|
|
67
|
-
const basename2 = trimmed.split("/").pop() ?? trimmed;
|
|
68
|
-
if (basename2 !== trimmed) {
|
|
69
|
-
const byBasename = await findProjectByName(basename2);
|
|
70
|
-
if (byBasename) return byBasename.id;
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
} catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ../../src/core/db-decisions.ts
|
|
79
|
-
function buildPipelineScopeBadge(row) {
|
|
80
|
-
if (row.deliverable_id !== null && row.deliverable_title !== null && row.deliverable_title.trim().length > 0) {
|
|
81
|
-
return `Deliverable: ${row.deliverable_title.trim()}`;
|
|
82
|
-
}
|
|
83
|
-
if (row.outcome_id !== null && row.outcome_title !== null && row.outcome_title.trim().length > 0) {
|
|
84
|
-
return `Outcome: ${row.outcome_title.trim()}`;
|
|
85
|
-
}
|
|
86
|
-
if (row.initiative_id !== null && row.initiative_title !== null && row.initiative_title.trim().length > 0) {
|
|
87
|
-
return `Initiative: ${row.initiative_title.trim()}`;
|
|
88
|
-
}
|
|
89
|
-
return void 0;
|
|
90
|
-
}
|
|
91
|
-
function rowToDecisionRecord(row, options) {
|
|
92
|
-
const source = row.source;
|
|
93
|
-
if (source !== "claude" && source !== "cursor" && source !== "manual") {
|
|
94
|
-
throw new Error("Stored decision has an unexpected source value.");
|
|
95
|
-
}
|
|
96
|
-
const status = row.status;
|
|
97
|
-
if (status !== "pending" && status !== "confirmed" && status !== "delegated" && status !== "dismissed") {
|
|
98
|
-
throw new Error("Stored decision has an unexpected status value.");
|
|
99
|
-
}
|
|
100
|
-
const scopeBadge = buildPipelineScopeBadge(row);
|
|
101
|
-
const record = {
|
|
102
|
-
id: row.id,
|
|
103
|
-
summary: row.summary,
|
|
104
|
-
options,
|
|
105
|
-
status,
|
|
106
|
-
timestamp: row.created_at,
|
|
107
|
-
source,
|
|
108
|
-
...row.chosen_option !== null && row.chosen_option.length > 0 ? { chosenOption: row.chosen_option } : {},
|
|
109
|
-
...row.resolved_at !== null && row.resolved_at.length > 0 ? { resolvedAt: row.resolved_at } : {},
|
|
110
|
-
...row.initiative_id !== null ? { initiativeId: row.initiative_id } : {},
|
|
111
|
-
...row.outcome_id !== null ? { outcomeId: row.outcome_id } : {},
|
|
112
|
-
...row.deliverable_id !== null ? { deliverableId: row.deliverable_id } : {},
|
|
113
|
-
...row.rationale !== null && row.rationale.trim().length > 0 ? { rationale: row.rationale.trim() } : {},
|
|
114
|
-
...scopeBadge !== void 0 ? { pipelineScopeBadge: scopeBadge } : {}
|
|
115
|
-
};
|
|
116
|
-
return record;
|
|
117
|
-
}
|
|
118
|
-
async function loadOptionsForDecisions(decisionIds) {
|
|
119
|
-
const map = /* @__PURE__ */ new Map();
|
|
120
|
-
if (decisionIds.length === 0) {
|
|
121
|
-
return map;
|
|
122
|
-
}
|
|
123
|
-
const optRows = await query(
|
|
124
|
-
`
|
|
125
|
-
SELECT decision_id, label, description
|
|
126
|
-
FROM decision_options
|
|
127
|
-
WHERE decision_id = ANY($1::uuid[])
|
|
128
|
-
ORDER BY id ASC
|
|
129
|
-
`,
|
|
130
|
-
[decisionIds]
|
|
131
|
-
);
|
|
132
|
-
for (const o of optRows) {
|
|
133
|
-
const list = map.get(o.decision_id) ?? [];
|
|
134
|
-
list.push(
|
|
135
|
-
o.description !== null && o.description.length > 0 ? { label: o.label, description: o.description } : { label: o.label }
|
|
136
|
-
);
|
|
137
|
-
map.set(o.decision_id, list);
|
|
138
|
-
}
|
|
139
|
-
return map;
|
|
140
|
-
}
|
|
141
|
-
async function listPendingDecisionsFromDb(projectId) {
|
|
142
|
-
const decisionRows = await query(
|
|
143
|
-
`
|
|
144
|
-
SELECT
|
|
145
|
-
d.id,
|
|
146
|
-
d.summary,
|
|
147
|
-
d.status,
|
|
148
|
-
d.chosen_option,
|
|
149
|
-
d.rationale,
|
|
150
|
-
d.created_at::text AS created_at,
|
|
151
|
-
d.resolved_at::text AS resolved_at,
|
|
152
|
-
d.source,
|
|
153
|
-
d.initiative_id,
|
|
154
|
-
d.outcome_id,
|
|
155
|
-
d.deliverable_id,
|
|
156
|
-
i.title AS initiative_title,
|
|
157
|
-
o.title AS outcome_title,
|
|
158
|
-
del.title AS deliverable_title
|
|
159
|
-
FROM decisions d
|
|
160
|
-
LEFT JOIN initiatives i ON i.id = d.initiative_id
|
|
161
|
-
LEFT JOIN outcomes o ON o.id = d.outcome_id
|
|
162
|
-
LEFT JOIN deliverables del ON del.id = d.deliverable_id
|
|
163
|
-
WHERE d.project_id = $1 AND d.status = 'pending'
|
|
164
|
-
ORDER BY d.created_at DESC
|
|
165
|
-
`,
|
|
166
|
-
[projectId]
|
|
167
|
-
);
|
|
168
|
-
const ids = decisionRows.map((r) => r.id);
|
|
169
|
-
const optionsById = await loadOptionsForDecisions(ids);
|
|
170
|
-
return decisionRows.map(
|
|
171
|
-
(row) => rowToDecisionRecord(row, optionsById.get(row.id) ?? [])
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ../../src/core/decision-queue.ts
|
|
176
|
-
async function getPendingDecisions(projectRoot) {
|
|
177
|
-
const projectId = await getRegisteredProjectIdOrNull(projectRoot);
|
|
178
|
-
if (projectId === null) {
|
|
179
|
-
return [];
|
|
180
|
-
}
|
|
181
|
-
return listPendingDecisionsFromDb(projectId);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ../../src/core/git-ops-queue.ts
|
|
185
|
-
import path3 from "path";
|
|
186
|
-
function rowToPending(row) {
|
|
187
|
-
return {
|
|
188
|
-
id: row.id,
|
|
189
|
-
projectId: row.project_id,
|
|
190
|
-
gitArgs: row.git_args,
|
|
191
|
-
tier: row.tier === "red" ? "red" : "yellow",
|
|
192
|
-
summary: row.summary,
|
|
193
|
-
plainEnglish: row.plain_english,
|
|
194
|
-
whatChanges: row.what_changes,
|
|
195
|
-
whatCouldGoWrong: row.what_could_go_wrong,
|
|
196
|
-
needsSnapshot: row.needs_snapshot,
|
|
197
|
-
status: row.status,
|
|
198
|
-
output: row.output,
|
|
199
|
-
error: row.error,
|
|
200
|
-
createdAt: row.created_at.toISOString(),
|
|
201
|
-
resolvedAt: row.resolved_at !== null ? row.resolved_at.toISOString() : null
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
async function listPendingGitOps(projectId) {
|
|
205
|
-
const rows = await query(
|
|
206
|
-
`
|
|
207
|
-
SELECT * FROM pending_git_ops
|
|
208
|
-
WHERE project_id = $1 AND status = 'pending'
|
|
209
|
-
ORDER BY created_at ASC
|
|
210
|
-
`,
|
|
211
|
-
[projectId]
|
|
212
|
-
);
|
|
213
|
-
return rows.map(rowToPending);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ../../src/core/work-queue.ts
|
|
217
|
-
var tierOrder = {
|
|
218
|
-
blocker: 0,
|
|
219
|
-
human_action: 1,
|
|
220
|
-
stage_gap: 2,
|
|
221
|
-
active: 3,
|
|
222
|
-
ready: 4,
|
|
223
|
-
pipeline: 5
|
|
224
|
-
};
|
|
225
|
-
function parseRice(raw) {
|
|
226
|
-
if (raw === null || raw === void 0) {
|
|
227
|
-
return void 0;
|
|
228
|
-
}
|
|
229
|
-
if (typeof raw === "number") {
|
|
230
|
-
return Number.isFinite(raw) ? raw : void 0;
|
|
231
|
-
}
|
|
232
|
-
const n = Number.parseFloat(raw);
|
|
233
|
-
return Number.isFinite(n) ? n : void 0;
|
|
234
|
-
}
|
|
235
|
-
function dedupeByEntity(items) {
|
|
236
|
-
const best = /* @__PURE__ */ new Map();
|
|
237
|
-
for (const item of items) {
|
|
238
|
-
const key = `${item.entityType}:${item.entityId}`;
|
|
239
|
-
const prev = best.get(key);
|
|
240
|
-
if (prev === void 0) {
|
|
241
|
-
best.set(key, item);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (tierOrder[item.tier] < tierOrder[prev.tier]) {
|
|
245
|
-
best.set(key, item);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return [...best.values()];
|
|
249
|
-
}
|
|
250
|
-
async function computeWorkQueue(projectRoot, limit = 20, options) {
|
|
251
|
-
const items = [];
|
|
252
|
-
const autoFilter = options?.autonomous === true;
|
|
253
|
-
const projectId = await getRegisteredProjectIdOrNull(projectRoot);
|
|
254
|
-
try {
|
|
255
|
-
let currentBranch = null;
|
|
256
|
-
try {
|
|
257
|
-
const branchInfo = await getGitBranchInfo(projectRoot);
|
|
258
|
-
currentBranch = branchInfo.branchName;
|
|
259
|
-
if (currentBranch !== null && currentBranch.startsWith("detached (")) {
|
|
260
|
-
currentBranch = null;
|
|
261
|
-
}
|
|
262
|
-
} catch {
|
|
263
|
-
currentBranch = null;
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
const { defaultBranch: mainLine, conflicts } = await getConflictBranchesForWorkQueue(projectRoot, {
|
|
267
|
-
projectId,
|
|
268
|
-
currentBranch
|
|
269
|
-
});
|
|
270
|
-
for (const branch of conflicts) {
|
|
271
|
-
items.push({
|
|
272
|
-
priority: 0,
|
|
273
|
-
tier: "blocker",
|
|
274
|
-
type: "conflict",
|
|
275
|
-
title: `Resolve merge conflicts: ${branch.name}`,
|
|
276
|
-
reason: "Blocks merging into main",
|
|
277
|
-
action: `Spin up a repo-ops agent to rebase ${branch.name} onto ${mainLine}`,
|
|
278
|
-
agentRole: "repo-ops",
|
|
279
|
-
entityId: branch.name,
|
|
280
|
-
entityType: "branch",
|
|
281
|
-
initiativeTitle: branch.deliverable?.title,
|
|
282
|
-
conflictFiles: branch.conflictFiles.length > 0 ? branch.conflictFiles : void 0,
|
|
283
|
-
defaultBranch: mainLine
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
} catch {
|
|
287
|
-
}
|
|
288
|
-
} catch {
|
|
289
|
-
}
|
|
290
|
-
if (projectId !== null) {
|
|
291
|
-
try {
|
|
292
|
-
const reviewRows = await query(
|
|
293
|
-
`
|
|
294
|
-
SELECT d.id, d.title, i.title AS initiative_title, i.rice_score::text AS rice_score
|
|
295
|
-
FROM deliverables d
|
|
296
|
-
JOIN outcomes o ON o.id = d.outcome_id
|
|
297
|
-
JOIN initiatives i ON i.id = o.initiative_id
|
|
298
|
-
WHERE i.project_id = $1 AND d.status = 'review'
|
|
299
|
-
ORDER BY d.updated_at DESC
|
|
300
|
-
`,
|
|
301
|
-
[projectId]
|
|
302
|
-
);
|
|
303
|
-
for (const row of reviewRows) {
|
|
304
|
-
items.push({
|
|
305
|
-
priority: 0,
|
|
306
|
-
tier: "human_action",
|
|
307
|
-
type: "review",
|
|
308
|
-
title: `Review: ${row.title}`,
|
|
309
|
-
reason: "Waiting for your approval",
|
|
310
|
-
action: "Approve or return the deliverable in the app when ready.",
|
|
311
|
-
agentRole: "delivery-manager",
|
|
312
|
-
entityId: row.id,
|
|
313
|
-
entityType: "deliverable",
|
|
314
|
-
initiativeTitle: row.initiative_title,
|
|
315
|
-
riceScore: parseRice(row.rice_score)
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
} catch {
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
const gitOps = await listPendingGitOps(projectId);
|
|
322
|
-
for (const op of gitOps) {
|
|
323
|
-
items.push({
|
|
324
|
-
priority: 0,
|
|
325
|
-
tier: "human_action",
|
|
326
|
-
type: "git_op",
|
|
327
|
-
title: op.summary,
|
|
328
|
-
reason: "Git operation awaiting confirmation",
|
|
329
|
-
action: "Confirm or reject this operation in the Git tab.",
|
|
330
|
-
agentRole: "delivery-manager",
|
|
331
|
-
entityId: op.id,
|
|
332
|
-
entityType: "git_op"
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
} catch {
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
try {
|
|
339
|
-
const pendingDecisions = await getPendingDecisions(projectRoot);
|
|
340
|
-
for (const d of pendingDecisions) {
|
|
341
|
-
if (d.status !== "pending") {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
items.push({
|
|
345
|
-
priority: 0,
|
|
346
|
-
tier: "human_action",
|
|
347
|
-
type: "decision",
|
|
348
|
-
title: d.summary,
|
|
349
|
-
reason: "Decision awaiting your input",
|
|
350
|
-
action: "Choose an option, delegate, or dismiss in the Decisions tab.",
|
|
351
|
-
agentRole: "delivery-manager",
|
|
352
|
-
entityId: d.id,
|
|
353
|
-
entityType: "decision"
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
}
|
|
358
|
-
if (projectId !== null) {
|
|
359
|
-
try {
|
|
360
|
-
const gapRows = await query(
|
|
361
|
-
`
|
|
362
|
-
SELECT
|
|
363
|
-
i.id,
|
|
364
|
-
i.title,
|
|
365
|
-
i.current_stage,
|
|
366
|
-
i.rice_score::text AS rice_score,
|
|
367
|
-
(
|
|
368
|
-
SELECT COUNT(*)::text
|
|
369
|
-
FROM design_notes dn
|
|
370
|
-
INNER JOIN outcomes o2 ON o2.id = dn.outcome_id
|
|
371
|
-
WHERE o2.initiative_id = i.id
|
|
372
|
-
) AS design_note_count,
|
|
373
|
-
(
|
|
374
|
-
SELECT COUNT(*)::text
|
|
375
|
-
FROM deliverables d2
|
|
376
|
-
INNER JOIN outcomes o3 ON o3.id = d2.outcome_id
|
|
377
|
-
WHERE o3.initiative_id = i.id
|
|
378
|
-
) AS deliverable_count,
|
|
379
|
-
(
|
|
380
|
-
SELECT COUNT(*)::text
|
|
381
|
-
FROM outcomes o4
|
|
382
|
-
WHERE o4.initiative_id = i.id
|
|
383
|
-
) AS outcome_count
|
|
384
|
-
FROM initiatives i
|
|
385
|
-
WHERE i.project_id = $1
|
|
386
|
-
AND i.current_stage NOT IN ('done', 'discovery')
|
|
387
|
-
`,
|
|
388
|
-
[projectId]
|
|
389
|
-
);
|
|
390
|
-
for (const row of gapRows) {
|
|
391
|
-
const rice = parseRice(row.rice_score);
|
|
392
|
-
const designNotes = Number.parseInt(row.design_note_count, 10) || 0;
|
|
393
|
-
const deliverables = Number.parseInt(row.deliverable_count, 10) || 0;
|
|
394
|
-
const outcomes = Number.parseInt(row.outcome_count, 10) || 0;
|
|
395
|
-
if (row.current_stage === "outcomes_defined" && outcomes === 0) {
|
|
396
|
-
items.push({
|
|
397
|
-
priority: 0,
|
|
398
|
-
tier: "stage_gap",
|
|
399
|
-
type: "stage_warning",
|
|
400
|
-
title: `Outcomes missing: ${row.title}`,
|
|
401
|
-
reason: "Outcomes need to be defined before the pipeline can move forward",
|
|
402
|
-
action: "Product manager should define outcomes for this initiative.",
|
|
403
|
-
agentRole: "product-manager",
|
|
404
|
-
entityId: row.id,
|
|
405
|
-
entityType: "initiative",
|
|
406
|
-
initiativeTitle: row.title,
|
|
407
|
-
riceScore: rice
|
|
408
|
-
});
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
if (row.current_stage === "tech_breakdown" && deliverables === 0) {
|
|
412
|
-
items.push({
|
|
413
|
-
priority: 0,
|
|
414
|
-
tier: "stage_gap",
|
|
415
|
-
type: "stage_warning",
|
|
416
|
-
title: `Deliverable breakdown needed: ${row.title}`,
|
|
417
|
-
reason: "Needs deliverable breakdown before implementation can start",
|
|
418
|
-
action: "Tech lead should split outcomes into deliverables.",
|
|
419
|
-
agentRole: "tech-lead",
|
|
420
|
-
entityId: row.id,
|
|
421
|
-
entityType: "initiative",
|
|
422
|
-
initiativeTitle: row.title,
|
|
423
|
-
riceScore: rice
|
|
424
|
-
});
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
if (deliverables > 0 && designNotes === 0) {
|
|
428
|
-
items.push({
|
|
429
|
-
priority: 0,
|
|
430
|
-
tier: "stage_gap",
|
|
431
|
-
type: "stage_warning",
|
|
432
|
-
title: `Design review needed: ${row.title}`,
|
|
433
|
-
reason: "Has deliverables but no design notes \u2014 design review needed before implementation",
|
|
434
|
-
action: "Product designer should add design notes to outcomes.",
|
|
435
|
-
agentRole: "product-designer",
|
|
436
|
-
entityId: row.id,
|
|
437
|
-
entityType: "initiative",
|
|
438
|
-
initiativeTitle: row.title,
|
|
439
|
-
riceScore: rice
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
} catch {
|
|
444
|
-
}
|
|
445
|
-
try {
|
|
446
|
-
const activeRows = await query(
|
|
447
|
-
`
|
|
448
|
-
SELECT d.id, d.title, d.assigned_role, d.autonomous, i.title AS initiative_title,
|
|
449
|
-
i.rice_score::text AS rice_score
|
|
450
|
-
FROM deliverables d
|
|
451
|
-
JOIN outcomes o ON o.id = d.outcome_id
|
|
452
|
-
JOIN initiatives i ON i.id = o.initiative_id
|
|
453
|
-
WHERE i.project_id = $1 AND d.status = 'in_progress'
|
|
454
|
-
${autoFilter ? "AND d.autonomous = true" : ""}
|
|
455
|
-
ORDER BY COALESCE(i.rice_score::numeric, 0) DESC, d.updated_at DESC
|
|
456
|
-
`,
|
|
457
|
-
[projectId]
|
|
458
|
-
);
|
|
459
|
-
for (const row of activeRows) {
|
|
460
|
-
const rice = parseRice(row.rice_score);
|
|
461
|
-
const r = rice !== void 0 ? String(rice) : "\u2014";
|
|
462
|
-
items.push({
|
|
463
|
-
priority: 0,
|
|
464
|
-
tier: "active",
|
|
465
|
-
type: "deliverable",
|
|
466
|
-
title: row.title,
|
|
467
|
-
reason: `In progress (RICE ${r})`,
|
|
468
|
-
action: "Continue implementation or move to review when checks pass.",
|
|
469
|
-
agentRole: row.assigned_role !== null && row.assigned_role.trim() !== "" ? row.assigned_role : "implementation-engineer",
|
|
470
|
-
entityId: row.id,
|
|
471
|
-
entityType: "deliverable",
|
|
472
|
-
initiativeTitle: row.initiative_title,
|
|
473
|
-
riceScore: rice,
|
|
474
|
-
autonomous: row.autonomous
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
} catch {
|
|
478
|
-
}
|
|
479
|
-
try {
|
|
480
|
-
const readyRows = await query(
|
|
481
|
-
`
|
|
482
|
-
SELECT d.id, d.title, d.assigned_role, d.autonomous, i.title AS initiative_title,
|
|
483
|
-
i.rice_score::text AS rice_score, i.current_stage
|
|
484
|
-
FROM deliverables d
|
|
485
|
-
JOIN outcomes o ON o.id = d.outcome_id
|
|
486
|
-
JOIN initiatives i ON i.id = o.initiative_id
|
|
487
|
-
WHERE i.project_id = $1
|
|
488
|
-
AND d.status = 'todo'
|
|
489
|
-
AND i.current_stage IN ('tech_breakdown', 'in_progress')
|
|
490
|
-
AND (d.blocked_by IS NULL
|
|
491
|
-
OR EXISTS (SELECT 1 FROM deliverables b WHERE b.id = d.blocked_by AND b.status IN ('done', 'review')))
|
|
492
|
-
${autoFilter ? "AND d.autonomous = true" : ""}
|
|
493
|
-
ORDER BY d.priority ASC, COALESCE(i.rice_score::numeric, 0) DESC, d.created_at ASC
|
|
494
|
-
`,
|
|
495
|
-
[projectId]
|
|
496
|
-
);
|
|
497
|
-
for (const row of readyRows) {
|
|
498
|
-
const rice = parseRice(row.rice_score);
|
|
499
|
-
const r = rice !== void 0 ? String(rice) : "\u2014";
|
|
500
|
-
items.push({
|
|
501
|
-
priority: 0,
|
|
502
|
-
tier: "ready",
|
|
503
|
-
type: "deliverable",
|
|
504
|
-
title: row.title,
|
|
505
|
-
reason: `Ready to start (RICE ${r})`,
|
|
506
|
-
action: "Pick up this deliverable and set it to in progress when you start.",
|
|
507
|
-
agentRole: row.assigned_role !== null && row.assigned_role.trim() !== "" ? row.assigned_role : "implementation-engineer",
|
|
508
|
-
entityId: row.id,
|
|
509
|
-
entityType: "deliverable",
|
|
510
|
-
initiativeTitle: row.initiative_title,
|
|
511
|
-
riceScore: rice,
|
|
512
|
-
autonomous: row.autonomous
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
} catch {
|
|
516
|
-
}
|
|
517
|
-
try {
|
|
518
|
-
const handoffRows = await query(
|
|
519
|
-
`
|
|
520
|
-
WITH ordered_deliverables AS (
|
|
521
|
-
SELECT
|
|
522
|
-
d.id,
|
|
523
|
-
d.title,
|
|
524
|
-
d.status,
|
|
525
|
-
d.outcome_id,
|
|
526
|
-
d.assigned_role,
|
|
527
|
-
d.created_at,
|
|
528
|
-
o.title AS outcome_title,
|
|
529
|
-
i.title AS initiative_title,
|
|
530
|
-
i.rice_score,
|
|
531
|
-
i.project_id,
|
|
532
|
-
ROW_NUMBER() OVER (PARTITION BY d.outcome_id ORDER BY d.created_at ASC) AS rn
|
|
533
|
-
FROM deliverables d
|
|
534
|
-
JOIN outcomes o ON o.id = d.outcome_id
|
|
535
|
-
JOIN initiatives i ON i.id = o.initiative_id
|
|
536
|
-
WHERE i.project_id = $1
|
|
537
|
-
)
|
|
538
|
-
SELECT
|
|
539
|
-
cur.id,
|
|
540
|
-
cur.title,
|
|
541
|
-
cur.outcome_id,
|
|
542
|
-
cur.outcome_title,
|
|
543
|
-
cur.initiative_title,
|
|
544
|
-
cur.rice_score::text AS rice_score,
|
|
545
|
-
cur.assigned_role,
|
|
546
|
-
prev.id AS prev_id,
|
|
547
|
-
prev.title AS prev_title,
|
|
548
|
-
prev.status AS prev_status,
|
|
549
|
-
(
|
|
550
|
-
SELECT cr.summary
|
|
551
|
-
FROM completion_reports cr
|
|
552
|
-
WHERE cr.deliverable_id = prev.id
|
|
553
|
-
ORDER BY cr.created_at DESC
|
|
554
|
-
LIMIT 1
|
|
555
|
-
) AS prev_summary
|
|
556
|
-
FROM ordered_deliverables cur
|
|
557
|
-
JOIN ordered_deliverables prev
|
|
558
|
-
ON prev.outcome_id = cur.outcome_id AND prev.rn = cur.rn - 1
|
|
559
|
-
WHERE cur.status = 'todo'
|
|
560
|
-
AND prev.status IN ('done', 'review')
|
|
561
|
-
AND (cur.prompt IS NULL OR cur.prompt = '')
|
|
562
|
-
`,
|
|
563
|
-
[projectId]
|
|
564
|
-
);
|
|
565
|
-
for (const row of handoffRows) {
|
|
566
|
-
const rice = parseRice(row.rice_score);
|
|
567
|
-
const prevContext = row.prev_summary !== null && row.prev_summary.trim().length > 0 ? ` Previous work: ${row.prev_summary.trim()}` : "";
|
|
568
|
-
items.push({
|
|
569
|
-
priority: 0,
|
|
570
|
-
tier: "blocker",
|
|
571
|
-
type: "handoff_needed",
|
|
572
|
-
title: `Handoff prompt needed for "${row.title}"`,
|
|
573
|
-
reason: `Waiting for handoff prompt from "${row.prev_title}" (${row.prev_status}).${prevContext}`,
|
|
574
|
-
action: `Create a prompt scoped to this deliverable: conductor_create_prompt with deliverableId ${row.id}`,
|
|
575
|
-
agentRole: row.assigned_role !== null && row.assigned_role.trim() !== "" ? row.assigned_role : "implementation-engineer",
|
|
576
|
-
entityId: row.id,
|
|
577
|
-
entityType: "deliverable",
|
|
578
|
-
initiativeTitle: row.initiative_title,
|
|
579
|
-
riceScore: rice,
|
|
580
|
-
blockedBy: row.prev_id,
|
|
581
|
-
blockedByTitle: row.prev_title,
|
|
582
|
-
outcomeName: row.outcome_title
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
} catch {
|
|
586
|
-
}
|
|
587
|
-
try {
|
|
588
|
-
const blockedRows = await query(
|
|
589
|
-
`
|
|
590
|
-
SELECT
|
|
591
|
-
d.id, d.title, d.status, d.priority, d.assigned_role,
|
|
592
|
-
b.id AS blocker_id, b.title AS blocker_title, b.status AS blocker_status,
|
|
593
|
-
i.title AS initiative_title,
|
|
594
|
-
o.title AS outcome_title,
|
|
595
|
-
i.rice_score::text AS rice_score
|
|
596
|
-
FROM deliverables d
|
|
597
|
-
INNER JOIN deliverables b ON b.id = d.blocked_by
|
|
598
|
-
INNER JOIN outcomes o ON o.id = d.outcome_id
|
|
599
|
-
INNER JOIN initiatives i ON i.id = o.initiative_id
|
|
600
|
-
WHERE i.project_id = $1
|
|
601
|
-
AND d.status IN ('todo', 'in_progress')
|
|
602
|
-
AND d.blocked_by IS NOT NULL
|
|
603
|
-
ORDER BY d.priority ASC, i.rice_score DESC NULLS LAST, d.created_at ASC
|
|
604
|
-
`,
|
|
605
|
-
[projectId]
|
|
606
|
-
);
|
|
607
|
-
for (const row of blockedRows) {
|
|
608
|
-
const rice = parseRice(row.rice_score);
|
|
609
|
-
const isUnblocked = row.blocker_status === "done" || row.blocker_status === "review";
|
|
610
|
-
items.push({
|
|
611
|
-
priority: isUnblocked ? 0 : 50,
|
|
612
|
-
tier: isUnblocked ? "ready" : "pipeline",
|
|
613
|
-
type: "deliverable",
|
|
614
|
-
title: isUnblocked ? `Blocker resolved \u2014 "${row.title}" is now unblocked` : `"${row.title}" waiting on "${row.blocker_title}" (${row.blocker_status})`,
|
|
615
|
-
reason: isUnblocked ? `"${row.blocker_title}" is done. This P${row.priority} deliverable can now start.` : `Blocked by "${row.blocker_title}" which is ${row.blocker_status}.`,
|
|
616
|
-
action: isUnblocked ? `Start work on this deliverable: conductor_update_deliverable with status 'in_progress'` : `Wait for "${row.blocker_title}" to be completed first.`,
|
|
617
|
-
agentRole: row.assigned_role !== null && row.assigned_role.trim() !== "" ? row.assigned_role : "implementation-engineer",
|
|
618
|
-
entityId: row.id,
|
|
619
|
-
entityType: "deliverable",
|
|
620
|
-
initiativeTitle: row.initiative_title,
|
|
621
|
-
riceScore: rice,
|
|
622
|
-
blockedBy: row.blocker_id,
|
|
623
|
-
blockedByTitle: row.blocker_title,
|
|
624
|
-
outcomeName: row.outcome_title
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
} catch {
|
|
628
|
-
}
|
|
629
|
-
try {
|
|
630
|
-
const pipeRows = await query(
|
|
631
|
-
`
|
|
632
|
-
SELECT i.id, i.title, i.current_stage, i.rice_score::text AS rice_score
|
|
633
|
-
FROM initiatives i
|
|
634
|
-
WHERE i.project_id = $1
|
|
635
|
-
AND i.current_stage IN (
|
|
636
|
-
'discovery', 'prd', 'rice_scored', 'outcomes_defined', 'design_reviewed'
|
|
637
|
-
)
|
|
638
|
-
AND i.current_stage != 'done'
|
|
639
|
-
ORDER BY COALESCE(i.rice_score::numeric, 0) DESC, i.created_at ASC
|
|
640
|
-
`,
|
|
641
|
-
[projectId]
|
|
642
|
-
);
|
|
643
|
-
for (const row of pipeRows) {
|
|
644
|
-
const rice = parseRice(row.rice_score);
|
|
645
|
-
const r = rice !== void 0 ? String(rice) : "\u2014";
|
|
646
|
-
let agentRole = "product-manager";
|
|
647
|
-
let reason = `Advance this initiative (RICE ${r})`;
|
|
648
|
-
let action = "Product manager should move discovery, PRD, RICE, and outcomes forward.";
|
|
649
|
-
if (row.current_stage === "discovery" || row.current_stage === "prd" || row.current_stage === "rice_scored") {
|
|
650
|
-
agentRole = "product-manager";
|
|
651
|
-
reason = `Pipeline needs product input \u2014 stage ${row.current_stage} (RICE ${r})`;
|
|
652
|
-
action = "Product manager should advance discovery, PRD, or RICE as appropriate.";
|
|
653
|
-
} else if (row.current_stage === "outcomes_defined") {
|
|
654
|
-
agentRole = "product-designer";
|
|
655
|
-
reason = `Design work needed after outcomes (RICE ${r})`;
|
|
656
|
-
action = "Product designer should add design notes and design review.";
|
|
657
|
-
} else if (row.current_stage === "design_reviewed") {
|
|
658
|
-
agentRole = "tech-lead";
|
|
659
|
-
reason = `Technical breakdown needed after design review (RICE ${r})`;
|
|
660
|
-
action = "Tech lead should break outcomes into deliverables.";
|
|
661
|
-
}
|
|
662
|
-
items.push({
|
|
663
|
-
priority: 0,
|
|
664
|
-
tier: "pipeline",
|
|
665
|
-
type: "initiative_stage",
|
|
666
|
-
title: row.title,
|
|
667
|
-
reason,
|
|
668
|
-
action,
|
|
669
|
-
agentRole,
|
|
670
|
-
entityId: row.id,
|
|
671
|
-
entityType: "initiative",
|
|
672
|
-
initiativeTitle: row.title,
|
|
673
|
-
riceScore: rice
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
} catch {
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
const merged = dedupeByEntity(items);
|
|
680
|
-
merged.sort((a, b) => {
|
|
681
|
-
const tierDiff = tierOrder[a.tier] - tierOrder[b.tier];
|
|
682
|
-
if (tierDiff !== 0) {
|
|
683
|
-
return tierDiff;
|
|
684
|
-
}
|
|
685
|
-
const riceA = a.riceScore ?? 0;
|
|
686
|
-
const riceB = b.riceScore ?? 0;
|
|
687
|
-
if (riceB !== riceA) {
|
|
688
|
-
return riceB - riceA;
|
|
689
|
-
}
|
|
690
|
-
return a.title.localeCompare(b.title);
|
|
691
|
-
});
|
|
692
|
-
const queue = merged.slice(0, limit).map((item, i) => ({
|
|
693
|
-
...item,
|
|
694
|
-
priority: i + 1
|
|
695
|
-
}));
|
|
696
|
-
return { queue, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
697
|
-
}
|
|
698
|
-
function actionLine(item) {
|
|
699
|
-
const rice = item.riceScore !== void 0 ? `, RICE ${item.riceScore}` : "";
|
|
700
|
-
const initiative = item.initiativeTitle ? ` (${item.initiativeTitle}${rice})` : "";
|
|
701
|
-
switch (item.type) {
|
|
702
|
-
case "conflict":
|
|
703
|
-
return `Resolve merge conflicts on \`${item.entityId}\` \u2014 rebase onto ${item.defaultBranch ?? "main"}`;
|
|
704
|
-
case "review":
|
|
705
|
-
return `Review and approve "${item.title.replace(/^Review:\s*/, "")}"${initiative}`;
|
|
706
|
-
case "git_op":
|
|
707
|
-
return item.title;
|
|
708
|
-
case "decision":
|
|
709
|
-
return `Decide: ${item.title}`;
|
|
710
|
-
case "stage_warning":
|
|
711
|
-
return item.title;
|
|
712
|
-
case "handoff_needed":
|
|
713
|
-
return `Create handoff prompt for "${item.title.replace(/^Handoff prompt needed for "/, "").replace(/"$/, "")}"${initiative}`;
|
|
714
|
-
case "deliverable":
|
|
715
|
-
return `Implement "${item.title}"${initiative}`;
|
|
716
|
-
case "initiative_stage": {
|
|
717
|
-
const hints = {
|
|
718
|
-
discovery: "write PRD and score RICE",
|
|
719
|
-
prd: "write PRD and score RICE",
|
|
720
|
-
rice_scored: "define outcomes",
|
|
721
|
-
outcomes_defined: "add design notes",
|
|
722
|
-
design_reviewed: "break into deliverables"
|
|
723
|
-
};
|
|
724
|
-
const stageMatch = item.reason.match(/stage (\w+)/);
|
|
725
|
-
const hint = stageMatch ? hints[stageMatch[1]] ?? "advance to next stage" : "advance to next stage";
|
|
726
|
-
return `Advance "${item.title}" \u2014 ${hint}`;
|
|
727
|
-
}
|
|
728
|
-
default:
|
|
729
|
-
return item.title;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function formatWorkQueueBrief(projectRoot, workQueue) {
|
|
733
|
-
const name = basename(projectRoot);
|
|
734
|
-
const time = new Date(workQueue.generatedAt).toLocaleTimeString();
|
|
735
|
-
if (workQueue.queue.length === 0) {
|
|
736
|
-
return `# What's next \u2014 ${name}
|
|
737
|
-
|
|
738
|
-
Nothing queued \u2014 all clear.`;
|
|
739
|
-
}
|
|
740
|
-
const top = workQueue.queue.slice(0, 3);
|
|
741
|
-
const rows = top.map(
|
|
742
|
-
(item, i) => `| ${i + 1} | ${item.agentRole} | ${actionLine(item)} |`
|
|
743
|
-
);
|
|
744
|
-
return [
|
|
745
|
-
`# What's next \u2014 ${name}`,
|
|
746
|
-
"",
|
|
747
|
-
`_${time} \xB7 ${workQueue.queue.length} items in queue_`,
|
|
748
|
-
"",
|
|
749
|
-
"| # | Agent | Action |",
|
|
750
|
-
"|---|-------|--------|",
|
|
751
|
-
...rows,
|
|
752
|
-
""
|
|
753
|
-
].join("\n");
|
|
754
|
-
}
|
|
755
|
-
export {
|
|
756
|
-
computeWorkQueue,
|
|
757
|
-
formatWorkQueueBrief
|
|
758
|
-
};
|