kanbot 0.2.0 → 0.4.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/README.md +15 -9
- package/package.json +1 -1
- package/schema/config.schema.json +92 -8
- package/src/agents/load.ts +11 -0
- package/src/agents/sync.ts +8 -8
- package/src/capabilities.ts +157 -0
- package/src/commands/app.ts +169 -0
- package/src/commands/board.ts +14 -7
- package/src/commands/doctor.ts +115 -6
- package/src/commands/init.ts +15 -3
- package/src/commands/skills.ts +1 -1
- package/src/commands/sync.ts +1 -1
- package/src/commands/upgrade.ts +20 -1
- package/src/config.ts +72 -8
- package/src/daemon/derive.ts +79 -1
- package/src/daemon/loop.ts +10 -1
- package/src/daemon/snapshot.ts +1 -0
- package/src/daemon/types.ts +17 -0
- package/src/github/api.ts +47 -2
- package/src/github/app.ts +153 -0
- package/src/github/gh.ts +29 -3
- package/src/jobs/prompts.ts +30 -6
- package/src/jobs/run.ts +135 -56
- package/src/known-hashes.json +47 -57
- package/src/main.ts +1 -0
- package/src/providers/claude.ts +40 -30
- package/src/providers/codex.ts +60 -30
- package/src/providers/mcp.ts +71 -0
- package/src/schemas/results.ts +2 -5
- package/src/templates.ts +1 -0
- package/templates/.agents/skills/approach-designer/SKILL.md +1 -1
- package/templates/.agents/skills/kanbot-admin/SKILL.md +39 -7
- package/templates/.agents/skills/kanbot-guide/SKILL.md +110 -0
- package/templates/.agents/skills/kanbot-idea/SKILL.md +1 -1
- package/templates/.agents/skills/plan-template/SKILL.md +14 -6
- package/templates/.agents/skills/pr-description/SKILL.md +7 -3
- package/templates/.agents/skills/project-conventions/SKILL.md +23 -35
- package/templates/.agents/skills/review-findings/SKILL.md +47 -0
- package/templates/.agents/skills/spec-review/SKILL.md +10 -27
- package/templates/.agents/skills/user-stories/SKILL.md +1 -1
- package/templates/.github/pull_request_template.md +6 -0
- package/templates/.kanbot/agents/acceptance-reviewer.md +43 -0
- package/templates/.kanbot/agents/fixer.md +7 -5
- package/templates/.kanbot/agents/implementer.md +49 -0
- package/templates/.kanbot/agents/plan-critic.md +3 -3
- package/templates/.kanbot/agents/planner.md +10 -9
- package/templates/.kanbot/agents/reviewer.md +42 -0
- package/templates/AGENTS.md +3 -4
- package/templates/.agents/skills/accessibility/SKILL.md +0 -76
- package/templates/.agents/skills/code-review/SKILL.md +0 -83
- package/templates/.agents/skills/frontend-design/LICENSE.txt +0 -177
- package/templates/.agents/skills/frontend-design/SKILL.md +0 -76
- package/templates/.agents/skills/tdd/SKILL.md +0 -51
- package/templates/.agents/skills/verify-ui/SKILL.md +0 -104
- package/templates/.kanbot/agents/implementer-backend.md +0 -57
- package/templates/.kanbot/agents/implementer-docs.md +0 -55
- package/templates/.kanbot/agents/implementer-frontend.md +0 -54
- package/templates/.kanbot/agents/implementer-technical.md +0 -53
- package/templates/.kanbot/agents/reviewer-code.md +0 -46
- package/templates/.kanbot/agents/reviewer-spec.md +0 -49
- package/templates/.kanbot/agents/test-writer.md +0 -35
- package/templates/.kanbot/agents/ui-verifier.md +0 -35
package/src/jobs/run.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ReviewJob,
|
|
14
14
|
reviewPasses,
|
|
15
15
|
variantOf,
|
|
16
|
+
withOpenQuestions,
|
|
16
17
|
} from "../daemon/derive.ts";
|
|
17
18
|
import type { Worktrees } from "../daemon/git.ts";
|
|
18
19
|
import type { Notifier } from "../daemon/notify.ts";
|
|
@@ -24,10 +25,12 @@ import {
|
|
|
24
25
|
createPR,
|
|
25
26
|
failedCheckLogs,
|
|
26
27
|
fetchIssue,
|
|
28
|
+
mergePR,
|
|
27
29
|
postComment,
|
|
28
30
|
react,
|
|
29
31
|
removeLabel,
|
|
30
32
|
replyToThread,
|
|
33
|
+
requestReview,
|
|
31
34
|
resolveThread,
|
|
32
35
|
updateIssueBody,
|
|
33
36
|
updatePRBody,
|
|
@@ -62,7 +65,12 @@ import {
|
|
|
62
65
|
export type JobDeps = {
|
|
63
66
|
root: string;
|
|
64
67
|
config: Config;
|
|
68
|
+
/** The user's own client: reads, board moves, merges. */
|
|
65
69
|
gh: Gh;
|
|
70
|
+
/** Who kanbot talks as: comments, labels, PRs. Same as `gh` in user mode. */
|
|
71
|
+
bot: Gh;
|
|
72
|
+
/** Login of the user running the daemon. */
|
|
73
|
+
me: string;
|
|
66
74
|
board: Board;
|
|
67
75
|
agents: AgentDef[];
|
|
68
76
|
provider: Provider;
|
|
@@ -145,9 +153,9 @@ export function runJob(job: Job, deps: JobDeps): Promise<void> {
|
|
|
145
153
|
// ---- plan -------------------------------------------------------------------
|
|
146
154
|
|
|
147
155
|
async function plan(job: PlanJob, deps: JobDeps) {
|
|
148
|
-
const { gh } = deps;
|
|
156
|
+
const { gh, bot } = deps;
|
|
149
157
|
const { issue } = job.item;
|
|
150
|
-
await Promise.all(job.comments.map((c) => react(
|
|
158
|
+
await Promise.all(job.comments.map((c) => react(bot, c.id, "EYES")));
|
|
151
159
|
const cwd = await deps.worktrees.detached(worktreeName(job));
|
|
152
160
|
const result = await runAgent(
|
|
153
161
|
deps,
|
|
@@ -161,13 +169,15 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
161
169
|
|
|
162
170
|
// Re-read so concurrent human edits to the description are kept.
|
|
163
171
|
const fresh = await fetchIssue(gh, issue.number);
|
|
164
|
-
await updateIssueBody(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
(
|
|
172
|
+
await updateIssueBody(
|
|
173
|
+
bot,
|
|
174
|
+
issue.number,
|
|
175
|
+
withPlan(fresh.body, withOpenQuestions(result.plan, result.questions)),
|
|
168
176
|
);
|
|
177
|
+
|
|
178
|
+
const allowed = result.labels.filter((l) => l === LABELS.technical);
|
|
169
179
|
await addLabels(
|
|
170
|
-
|
|
180
|
+
bot,
|
|
171
181
|
issue.number,
|
|
172
182
|
allowed.filter((l) => !fresh.labels.includes(l)),
|
|
173
183
|
);
|
|
@@ -175,12 +185,12 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
175
185
|
const created: string[] = [];
|
|
176
186
|
for (const ni of result.newIssues) {
|
|
177
187
|
const res = await createIssue(
|
|
178
|
-
|
|
188
|
+
bot,
|
|
179
189
|
ni.title,
|
|
180
190
|
`${ni.body}\n\n_Split from #${issue.number} by kanbot._\n${marker("created", issue.number)}`,
|
|
181
191
|
);
|
|
182
192
|
if (!res) continue;
|
|
183
|
-
if (ni.subIssue) await addSubIssue(
|
|
193
|
+
if (ni.subIssue) await addSubIssue(bot, issue.id, res.node_id);
|
|
184
194
|
await addToBoard(gh, deps.board, res.node_id, ni.column);
|
|
185
195
|
created.push(`#${res.number}`);
|
|
186
196
|
}
|
|
@@ -201,48 +211,66 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
201
211
|
);
|
|
202
212
|
}
|
|
203
213
|
lines.push(
|
|
204
|
-
|
|
214
|
+
result.questions.length
|
|
215
|
+
? "_Answer the questions to refine. kanbot won't start while the plan has open questions._"
|
|
216
|
+
: "_Comment to refine. Drag the card to **Ready** when the plan is approved._",
|
|
205
217
|
);
|
|
206
218
|
lines.push(
|
|
207
219
|
[marker("plan"), ...job.comments.map((c) => marker("re", c.id))].join("\n"),
|
|
208
220
|
);
|
|
209
|
-
await postComment(
|
|
210
|
-
await Promise.all(job.comments.map((c) => react(
|
|
221
|
+
await postComment(bot, issue.number, lines.join("\n\n"));
|
|
222
|
+
await Promise.all(job.comments.map((c) => react(bot, c.id, "THUMBS_UP")));
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
// ---- implement ----------------------------------------------------------------
|
|
214
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Always `implementer`. The deprecated `implementers` map still routes by
|
|
229
|
+
* type:<key> label, and `implementer-backend` serves installs not yet upgraded.
|
|
230
|
+
*/
|
|
231
|
+
export function implementerFor(
|
|
232
|
+
config: Config,
|
|
233
|
+
agents: AgentDef[],
|
|
234
|
+
labels: string[],
|
|
235
|
+
): string {
|
|
236
|
+
if (config.implementers) {
|
|
237
|
+
const type = labels
|
|
238
|
+
.find((l) => l.startsWith(LABELS.typePrefix))
|
|
239
|
+
?.slice(LABELS.typePrefix.length);
|
|
240
|
+
const name =
|
|
241
|
+
config.implementers[type ?? "default"] ?? config.implementers.default;
|
|
242
|
+
if (name) return name;
|
|
243
|
+
}
|
|
244
|
+
return agents.some((a) => a.name === "implementer")
|
|
245
|
+
? "implementer"
|
|
246
|
+
: "implementer-backend";
|
|
247
|
+
}
|
|
248
|
+
|
|
215
249
|
async function implement(job: ImplementJob, deps: JobDeps) {
|
|
216
|
-
const { gh, config, worktrees } = deps;
|
|
250
|
+
const { gh, bot, config, worktrees } = deps;
|
|
217
251
|
const { issue } = job.item;
|
|
218
252
|
if (job.item.column === "ready") {
|
|
219
253
|
await moveItem(gh, deps.board, job.item.itemId, "inProgress");
|
|
220
|
-
await addLabels(
|
|
254
|
+
await addLabels(bot, issue.number, [LABELS.working]);
|
|
221
255
|
}
|
|
222
256
|
|
|
223
257
|
const resumed = await worktrees.remoteHas(job.branch);
|
|
224
258
|
const cwd = await worktrees.checkout(worktreeName(job), job.branch);
|
|
225
259
|
let result: z.infer<typeof ImplementResult> | null = null;
|
|
226
260
|
if (!resumed) {
|
|
227
|
-
const
|
|
228
|
-
.find((l) => l.startsWith(LABELS.typePrefix))
|
|
229
|
-
?.slice(LABELS.typePrefix.length);
|
|
230
|
-
const agentName =
|
|
231
|
-
config.implementers[typeLabel ?? "default"] ??
|
|
232
|
-
config.implementers.default ??
|
|
233
|
-
"implementer-backend";
|
|
261
|
+
const agentName = implementerFor(config, deps.agents, issue.labels);
|
|
234
262
|
result = await runAgent(
|
|
235
263
|
deps,
|
|
236
264
|
job,
|
|
237
265
|
agentName,
|
|
238
266
|
"implement-result",
|
|
239
267
|
cwd,
|
|
240
|
-
implementPrompt(job, config.baseBranch),
|
|
268
|
+
implementPrompt(job, config.baseBranch, config.capabilities),
|
|
241
269
|
);
|
|
242
270
|
if (result.status === "blocked") {
|
|
243
|
-
await addLabels(
|
|
271
|
+
await addLabels(bot, issue.number, [LABELS.needsHuman]);
|
|
244
272
|
await postComment(
|
|
245
|
-
|
|
273
|
+
bot,
|
|
246
274
|
issue.number,
|
|
247
275
|
`**Implementation blocked${job.variant ? ` (approach ${job.variant})` : ""}.** ${result.blockedReason ?? ""}\n\nRemove \`${LABELS.needsHuman}\` once resolved and kanbot will retry.\n${marker("blocked", job.branch)}`,
|
|
248
276
|
);
|
|
@@ -292,19 +320,17 @@ async function implement(job: ImplementJob, deps: JobDeps) {
|
|
|
292
320
|
]
|
|
293
321
|
.filter(Boolean)
|
|
294
322
|
.join("\n\n");
|
|
295
|
-
const pr = await createPR(
|
|
323
|
+
const pr = await createPR(bot, {
|
|
296
324
|
title: `${result?.prTitle ?? issue.title}${variant}`,
|
|
297
325
|
body,
|
|
298
326
|
head: job.branch,
|
|
299
327
|
base: config.baseBranch,
|
|
300
328
|
});
|
|
301
329
|
if (pr) {
|
|
302
|
-
const labels = issue.labels.filter(
|
|
303
|
-
(l) => l.startsWith(LABELS.typePrefix) || l === LABELS.technical,
|
|
304
|
-
);
|
|
330
|
+
const labels: string[] = issue.labels.filter((l) => l === LABELS.technical);
|
|
305
331
|
if (job.variant !== null)
|
|
306
332
|
labels.push(`${LABELS.variantPrefix}${job.variant}`);
|
|
307
|
-
await addLabels(
|
|
333
|
+
await addLabels(bot, pr.number, labels);
|
|
308
334
|
log.info(`✔ opened ${pr.html_url}`);
|
|
309
335
|
}
|
|
310
336
|
}
|
|
@@ -329,8 +355,17 @@ export function absoluteImages(md: string, repo: string, branch: string) {
|
|
|
329
355
|
|
|
330
356
|
// ---- review -------------------------------------------------------------------
|
|
331
357
|
|
|
332
|
-
|
|
333
|
-
const
|
|
358
|
+
/** Agent per review pass. The second name serves installs not yet upgraded. */
|
|
359
|
+
export const PASS_AGENTS = {
|
|
360
|
+
1: ["reviewer", "reviewer-code"],
|
|
361
|
+
2: ["acceptance-reviewer", "reviewer-spec"],
|
|
362
|
+
} as const;
|
|
363
|
+
const PASS_NAME = { 1: "technical", 2: "acceptance" } as const;
|
|
364
|
+
|
|
365
|
+
export function passAgent(agents: AgentDef[], pass: 1 | 2): string {
|
|
366
|
+
const [name, legacy] = PASS_AGENTS[pass];
|
|
367
|
+
return agents.some((a) => a.name === name) ? name : legacy;
|
|
368
|
+
}
|
|
334
369
|
|
|
335
370
|
async function review(job: ReviewJob, deps: JobDeps) {
|
|
336
371
|
const cwd = await deps.worktrees.checkout(
|
|
@@ -340,10 +375,10 @@ async function review(job: ReviewJob, deps: JobDeps) {
|
|
|
340
375
|
const result = await runAgent(
|
|
341
376
|
deps,
|
|
342
377
|
job,
|
|
343
|
-
|
|
378
|
+
passAgent(deps.agents, job.pass),
|
|
344
379
|
"review-result",
|
|
345
380
|
cwd,
|
|
346
|
-
reviewPrompt(job, deps.config.baseBranch),
|
|
381
|
+
reviewPrompt(job, deps.config.baseBranch, deps.config.capabilities),
|
|
347
382
|
);
|
|
348
383
|
ReviewResult.parse(result);
|
|
349
384
|
const icon = result.verdict === "approve" ? "✅" : "🛠️";
|
|
@@ -354,13 +389,13 @@ async function review(job: ReviewJob, deps: JobDeps) {
|
|
|
354
389
|
marker("review", job.pass),
|
|
355
390
|
embedJson("findings", result.findings),
|
|
356
391
|
].join("\n\n");
|
|
357
|
-
await postComment(deps.
|
|
392
|
+
await postComment(deps.bot, job.pr.number, body);
|
|
358
393
|
}
|
|
359
394
|
|
|
360
395
|
// ---- fix ----------------------------------------------------------------------
|
|
361
396
|
|
|
362
397
|
async function fix(job: FixJob, deps: JobDeps) {
|
|
363
|
-
const { gh, worktrees, config } = deps;
|
|
398
|
+
const { gh, bot, worktrees, config } = deps;
|
|
364
399
|
const { pr } = job;
|
|
365
400
|
const cwd = await worktrees.checkout(worktreeName(job), pr.headRefName);
|
|
366
401
|
if (job.reason.type === "conflict") return mergeBase(job, deps, cwd);
|
|
@@ -375,7 +410,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
375
410
|
"fixer",
|
|
376
411
|
"fix-result",
|
|
377
412
|
cwd,
|
|
378
|
-
fixPrompt(job, config.baseBranch, logs),
|
|
413
|
+
fixPrompt(job, config.baseBranch, logs, config.capabilities),
|
|
379
414
|
);
|
|
380
415
|
FixResult.parse(result);
|
|
381
416
|
|
|
@@ -387,7 +422,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
387
422
|
if (changed) await worktrees.push(cwd, pr.headRefName);
|
|
388
423
|
if (result.prBody) {
|
|
389
424
|
const desc = absoluteImages(result.prBody, config.repo, pr.headRefName);
|
|
390
|
-
await updatePRBody(
|
|
425
|
+
await updatePRBody(bot, pr.number, withDescription(pr.body, desc));
|
|
391
426
|
}
|
|
392
427
|
const by = new Map(result.addressed.map((a) => [a.ref, a]));
|
|
393
428
|
const pushed = changed ? "Pushed fixes." : "No code changes.";
|
|
@@ -399,7 +434,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
399
434
|
return `- ${a?.resolved ? "✅" : "⏭️"} **${f.id}** ${a?.reply ?? "Not addressed."}`;
|
|
400
435
|
});
|
|
401
436
|
await postComment(
|
|
402
|
-
|
|
437
|
+
bot,
|
|
403
438
|
pr.number,
|
|
404
439
|
[
|
|
405
440
|
`**Addressed agent review ${r.pass}/${reviewPasses(job.item.issue).length}.** ${result.summary} ${pushed}`,
|
|
@@ -411,7 +446,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
411
446
|
}
|
|
412
447
|
if (r.type === "ci") {
|
|
413
448
|
await postComment(
|
|
414
|
-
|
|
449
|
+
bot,
|
|
415
450
|
pr.number,
|
|
416
451
|
`**CI fix attempt.** ${result.summary} ${pushed}\n\n${marker("ci", pr.headSha)}`,
|
|
417
452
|
);
|
|
@@ -423,11 +458,11 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
423
458
|
for (const t of r.threads) {
|
|
424
459
|
const a = by.get(t.id);
|
|
425
460
|
await replyToThread(
|
|
426
|
-
|
|
461
|
+
bot,
|
|
427
462
|
t.id,
|
|
428
463
|
`${a?.reply ?? "Looked at this but made no change."}\n\n${marker("re", t.id)}`,
|
|
429
464
|
);
|
|
430
|
-
if (a?.resolved) await resolveThread(
|
|
465
|
+
if (a?.resolved) await resolveThread(bot, t.id);
|
|
431
466
|
else open++;
|
|
432
467
|
}
|
|
433
468
|
if (r.type === "copilot") return;
|
|
@@ -438,7 +473,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
438
473
|
return `> ${excerpt(c.body)}\n\n${a?.reply ?? "Noted."}`;
|
|
439
474
|
});
|
|
440
475
|
await postComment(
|
|
441
|
-
|
|
476
|
+
bot,
|
|
442
477
|
pr.number,
|
|
443
478
|
[
|
|
444
479
|
`**Feedback round.** ${result.summary} ${pushed}`,
|
|
@@ -452,7 +487,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
452
487
|
: "All feedback addressed — ready for another look.";
|
|
453
488
|
if (!r.comments.length)
|
|
454
489
|
await postComment(
|
|
455
|
-
|
|
490
|
+
bot,
|
|
456
491
|
pr.number,
|
|
457
492
|
`${msg} ${pushed}\n\n${marker("feedback-round")}`,
|
|
458
493
|
);
|
|
@@ -469,7 +504,7 @@ const CONFLICT_MARKER = /^(<{7}|>{7})( |$)/m;
|
|
|
469
504
|
|
|
470
505
|
/** Merge the base branch into a conflicting PR; the fixer resolves conflicts. */
|
|
471
506
|
async function mergeBase(job: FixJob, deps: JobDeps, cwd: string) {
|
|
472
|
-
const {
|
|
507
|
+
const { bot, worktrees, config } = deps;
|
|
473
508
|
const { pr } = job;
|
|
474
509
|
const base = `origin/${config.baseBranch}`;
|
|
475
510
|
const files = await worktrees.merge(cwd, base);
|
|
@@ -481,7 +516,12 @@ async function mergeBase(job: FixJob, deps: JobDeps, cwd: string) {
|
|
|
481
516
|
"fixer",
|
|
482
517
|
"fix-result",
|
|
483
518
|
cwd,
|
|
484
|
-
fixPrompt(
|
|
519
|
+
fixPrompt(
|
|
520
|
+
job,
|
|
521
|
+
config.baseBranch,
|
|
522
|
+
files.map((f) => `- ${f}`).join("\n"),
|
|
523
|
+
config.capabilities,
|
|
524
|
+
),
|
|
485
525
|
);
|
|
486
526
|
FixResult.parse(result);
|
|
487
527
|
const left: string[] = [];
|
|
@@ -503,7 +543,7 @@ async function mergeBase(job: FixJob, deps: JobDeps, cwd: string) {
|
|
|
503
543
|
throw new Error(`${base} was not merged`);
|
|
504
544
|
await worktrees.push(cwd, pr.headRefName);
|
|
505
545
|
await postComment(
|
|
506
|
-
|
|
546
|
+
bot,
|
|
507
547
|
pr.number,
|
|
508
548
|
`**Merged \`${config.baseBranch}\`.** ${summary}\n\n${marker("conflict", pr.headSha)}`,
|
|
509
549
|
);
|
|
@@ -512,35 +552,74 @@ async function mergeBase(job: FixJob, deps: JobDeps, cwd: string) {
|
|
|
512
552
|
// ---- deterministic actions ----------------------------------------------------
|
|
513
553
|
|
|
514
554
|
export async function applyAction(action: Action, deps: JobDeps) {
|
|
515
|
-
const { gh } = deps;
|
|
555
|
+
const { gh, bot, config } = deps;
|
|
516
556
|
switch (action.kind) {
|
|
517
557
|
case "move":
|
|
518
558
|
log.info(`#${action.item.issue.number} → ${action.to} (${action.why})`);
|
|
519
559
|
return moveItem(gh, deps.board, action.item.itemId, action.to);
|
|
520
560
|
case "label":
|
|
521
|
-
await addLabels(
|
|
522
|
-
for (const l of action.remove) await removeLabel(
|
|
561
|
+
await addLabels(bot, action.number, action.add);
|
|
562
|
+
for (const l of action.remove) await removeLabel(bot, action.number, l);
|
|
523
563
|
return;
|
|
524
|
-
case "agentReviewed":
|
|
525
|
-
await addLabels(
|
|
564
|
+
case "agentReviewed": {
|
|
565
|
+
await addLabels(bot, action.pr.number, [LABELS.agentReviewed]);
|
|
566
|
+
const next =
|
|
567
|
+
config.merge.onApproval && action.pr.headRefName.startsWith("kanbot/")
|
|
568
|
+
? "Approve to merge."
|
|
569
|
+
: "Ready for you to test and merge.";
|
|
526
570
|
await postComment(
|
|
527
|
-
|
|
571
|
+
bot,
|
|
528
572
|
action.pr.number,
|
|
529
|
-
`**Agent review complete.**
|
|
573
|
+
`**Agent review complete.** ${next}\n\n${marker("ready")}`,
|
|
530
574
|
);
|
|
575
|
+
// The bot opened the PR, so the user can be asked to review it.
|
|
576
|
+
if (bot.isBot)
|
|
577
|
+
await requestReview(bot, action.pr.number, [deps.me]).catch((e) =>
|
|
578
|
+
log.warn(`request review on #${action.pr.number}: ${e}`),
|
|
579
|
+
);
|
|
531
580
|
deps.notify.send(
|
|
532
581
|
`Ready to test: PR #${action.pr.number}`,
|
|
533
582
|
action.pr.title,
|
|
534
583
|
action.pr.url,
|
|
535
584
|
);
|
|
536
585
|
return;
|
|
586
|
+
}
|
|
587
|
+
case "openQuestions":
|
|
588
|
+
return postComment(
|
|
589
|
+
bot,
|
|
590
|
+
action.item.issue.number,
|
|
591
|
+
`**Not started: the plan has open questions.**\n\n${action.questions.map((q) => `- ${q}`).join("\n")}\n\nMove the card to **Planning** and answer them, or remove them from the plan. Then move it back to **Ready**.\n${marker("questions")}`,
|
|
592
|
+
);
|
|
593
|
+
case "merge": {
|
|
594
|
+
const { pr } = action;
|
|
595
|
+
log.info(`merging #${pr.number}, approved by ${action.by}`);
|
|
596
|
+
try {
|
|
597
|
+
await mergePR(gh, pr.number, pr.headSha, config.merge.method);
|
|
598
|
+
} catch (e) {
|
|
599
|
+
return applyAction(
|
|
600
|
+
{
|
|
601
|
+
kind: "needsHuman",
|
|
602
|
+
number: action.item.issue.number,
|
|
603
|
+
why: `Approved, but merging #${pr.number} failed:\n\n\`\`\`\n${String(e).slice(0, 1500)}\n\`\`\``,
|
|
604
|
+
},
|
|
605
|
+
deps,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
await postComment(
|
|
609
|
+
bot,
|
|
610
|
+
pr.number,
|
|
611
|
+
`**Merged** after approval by @${action.by}.\n\n${marker("merged")}`,
|
|
612
|
+
);
|
|
613
|
+
deps.notify.send(`Merged PR #${pr.number}`, pr.title, pr.url);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
537
616
|
case "compare": {
|
|
538
617
|
const rows = action.prs.map(
|
|
539
618
|
(p) =>
|
|
540
619
|
`| ${p.labels.find((l) => l.startsWith(LABELS.variantPrefix)) ?? "?"} | #${p.number} | ${p.title} |`,
|
|
541
620
|
);
|
|
542
621
|
await postComment(
|
|
543
|
-
|
|
622
|
+
bot,
|
|
544
623
|
action.item.issue.number,
|
|
545
624
|
`**All ${action.prs.length} approaches are agent-reviewed.** Try them and merge the one you want; the others are closed automatically.\n\n| Variant | PR | Title |\n|---|---|---|\n${rows.join("\n")}\n\n${marker("compare")}`,
|
|
546
625
|
);
|
|
@@ -552,9 +631,9 @@ export async function applyAction(action: Action, deps: JobDeps) {
|
|
|
552
631
|
return;
|
|
553
632
|
}
|
|
554
633
|
case "needsHuman":
|
|
555
|
-
await addLabels(
|
|
634
|
+
await addLabels(bot, action.number, [LABELS.needsHuman]);
|
|
556
635
|
await postComment(
|
|
557
|
-
|
|
636
|
+
bot,
|
|
558
637
|
action.number,
|
|
559
638
|
`**kanbot needs a human.** ${action.why}\n\nRemove \`${LABELS.needsHuman}\` to let agents retry.\n${marker("needs-human")}`,
|
|
560
639
|
);
|
package/src/known-hashes.json
CHANGED
|
@@ -1,40 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
".agents/skills/accessibility/SKILL.md": [
|
|
3
|
-
"bb8c5148899685168324b8fda3524937e3943b32"
|
|
4
|
-
],
|
|
5
2
|
".agents/skills/approach-designer/SKILL.md": [
|
|
6
|
-
"7fbf6e80562b59e94c8b9615d777afca8a769d29"
|
|
7
|
-
|
|
8
|
-
".agents/skills/code-review/SKILL.md": [
|
|
9
|
-
"a35bdecaeb1b6a2469a6330d2cef42a9d61409fe"
|
|
10
|
-
],
|
|
11
|
-
".agents/skills/frontend-design/LICENSE.txt": [
|
|
12
|
-
"f433b1a53f5b830a205fd2df78e2b34974656c7b"
|
|
13
|
-
],
|
|
14
|
-
".agents/skills/frontend-design/SKILL.md": [
|
|
15
|
-
"8ac4bf9b37f7b42ead4b65c35e0e9890fb5354e8"
|
|
3
|
+
"7fbf6e80562b59e94c8b9615d777afca8a769d29",
|
|
4
|
+
"d932ed30a58cb32dd91ec03ddd2b1c6c59f77714"
|
|
16
5
|
],
|
|
17
6
|
".agents/skills/issue-splitting/SKILL.md": [
|
|
18
7
|
"01625372d68a81347bf401e8603084420922d04e"
|
|
19
8
|
],
|
|
20
9
|
".agents/skills/kanbot-admin/SKILL.md": [
|
|
21
10
|
"3789c559be9fb6a78f82708ae9857395a5556f6b",
|
|
11
|
+
"47b16ee8a6c6847450589bbb954d5a08c154f52a",
|
|
12
|
+
"557532f241c0fc1e04f03ce8edb69efaa7b5165b",
|
|
13
|
+
"a7a7b2f55be7235ea17ffd092755af8493c8ef76",
|
|
14
|
+
"b09b9ccc5ca90585418a6645919c10d9a487bad9",
|
|
15
|
+
"c29ed25fa7f825e64b58c54ed2240823fb22ef6c",
|
|
22
16
|
"e427c711b532963c0916f07ecc6b11e9b47cb9fa"
|
|
23
17
|
],
|
|
18
|
+
".agents/skills/kanbot-guide/SKILL.md": [
|
|
19
|
+
"1251194ba34a8b6eae8508037b0a76c60d7a26b8",
|
|
20
|
+
"2c6c77a225c5b880eff023d4b3c1305bfc91bfb3",
|
|
21
|
+
"2ccb86b12e5af121b79ef9f53c2a35dbbdb2ddeb",
|
|
22
|
+
"a24c0a655d6485e65bd5dea2a71f3afe67f04404",
|
|
23
|
+
"cf3e95d137a5a58a273262bca6ba25fbc95fa037"
|
|
24
|
+
],
|
|
24
25
|
".agents/skills/kanbot-idea/SKILL.md": [
|
|
25
|
-
"8ab28cec8d3f30250ffe080106d0de8e3c2eaa8a"
|
|
26
|
+
"8ab28cec8d3f30250ffe080106d0de8e3c2eaa8a",
|
|
27
|
+
"9d47113a299a75514a00993060bf6afebb049c6c"
|
|
26
28
|
],
|
|
27
29
|
".agents/skills/plan-template/SKILL.md": [
|
|
28
30
|
"47a5dbbd2870883ca42dd9410d224ef9c0c9979f",
|
|
29
31
|
"79cfd0bf36110b5d6f452e6400161609b11df0d6",
|
|
30
|
-
"7bfde45995efaf515e45ccec09c204178203fdb7"
|
|
32
|
+
"7bfde45995efaf515e45ccec09c204178203fdb7",
|
|
33
|
+
"841446c304ffe9783c79f293e050a28ceb540b3d",
|
|
34
|
+
"e6b6e55a42bfc42d0c9ff1a5cdf3a0573ab665c0"
|
|
31
35
|
],
|
|
32
36
|
".agents/skills/pr-description/SKILL.md": [
|
|
33
37
|
"1db6eb751e0bb1ea0014a2b414e04f6af59b1b2d",
|
|
34
38
|
"6b0cca1be3abe8d031159c94e5d3b4f20307d600",
|
|
35
|
-
"7e477762c9c5f09ce6d5d51ccd9138ce9743baa8"
|
|
39
|
+
"7e477762c9c5f09ce6d5d51ccd9138ce9743baa8",
|
|
40
|
+
"830c94888620de0a5bb7030672c017c5fafa63e5"
|
|
36
41
|
],
|
|
37
42
|
".agents/skills/project-conventions/SKILL.md": [
|
|
43
|
+
"668d904ff8fdf33edbe9768cd6bf223813380c9d",
|
|
38
44
|
"81d9ff980a15a6f901010646b800b3e6e379e4b0",
|
|
39
45
|
"dabf1dadc148c020496d8f2f9bb8de6269f56479"
|
|
40
46
|
],
|
|
@@ -44,18 +50,18 @@
|
|
|
44
50
|
".agents/skills/project-memory/SKILL.md": [
|
|
45
51
|
"d354e11310db01362ec2bf5eec1080db77ac053d"
|
|
46
52
|
],
|
|
53
|
+
".agents/skills/review-findings/SKILL.md": [
|
|
54
|
+
"4ba188751544038ca5ab3fdeaf61f40455a3bcec"
|
|
55
|
+
],
|
|
47
56
|
".agents/skills/spec-review/SKILL.md": [
|
|
48
57
|
"03e44fe4f23b04f1b11c907936ee9f6c7c8131f0",
|
|
58
|
+
"84fbc57a8b8f4fe473258402772185944a54a81c",
|
|
49
59
|
"f455d1007d589602e5afce0dac4f00d78aa7c67a"
|
|
50
60
|
],
|
|
51
|
-
".agents/skills/tdd/SKILL.md": ["5e6b20868e02ae24f6d25824bac431143bd43021"],
|
|
52
61
|
".agents/skills/user-stories/SKILL.md": [
|
|
53
62
|
"1b37f8e59c15b934d555070a63d4f0d3939a007f",
|
|
54
|
-
"1b5d229d043c1bfbd84aecd9ee6191b1d473f1d8"
|
|
55
|
-
|
|
56
|
-
".agents/skills/verify-ui/SKILL.md": [
|
|
57
|
-
"c67a0e1d6a1c1d9adf59dd154160ccf58519ea72",
|
|
58
|
-
"f4d3d07c0ec28a9961d919356d0b4e572b724b43"
|
|
63
|
+
"1b5d229d043c1bfbd84aecd9ee6191b1d473f1d8",
|
|
64
|
+
"73455bec36bd5677cdf3c081677ad2856c434954"
|
|
59
65
|
],
|
|
60
66
|
".github/ISSUE_TEMPLATE/config.yml": [
|
|
61
67
|
"0086358db1eb971c0cfa8739c27518bbc18a5ff4"
|
|
@@ -67,12 +73,16 @@
|
|
|
67
73
|
"d26a148970ba6323d9cfdd86924cb2e82e68b705"
|
|
68
74
|
],
|
|
69
75
|
".github/pull_request_template.md": [
|
|
70
|
-
"57cfbc21472a94ccccb19ef6f19e87a473925649"
|
|
76
|
+
"57cfbc21472a94ccccb19ef6f19e87a473925649",
|
|
77
|
+
"b8060c2599769dd12477081b6ec51733a2a82c7b"
|
|
71
78
|
],
|
|
72
79
|
".github/workflows/ci.yml": ["dce7bcf80cef91c10923829eb5893d2b311b58e5"],
|
|
73
80
|
".github/workflows/kanbot-merged.yml": [
|
|
74
81
|
"8daaea63e23cf1e0d37821bfadaf60018a448de2"
|
|
75
82
|
],
|
|
83
|
+
".kanbot/agents/acceptance-reviewer.md": [
|
|
84
|
+
"e6b65950345c686b4d219026d8d2d185e9735c12"
|
|
85
|
+
],
|
|
76
86
|
".kanbot/agents/approach-designer.md": [
|
|
77
87
|
"7e5f8c16c4d4a4e261bccc5dc8b4fff2d3046a72"
|
|
78
88
|
],
|
|
@@ -82,51 +92,31 @@
|
|
|
82
92
|
],
|
|
83
93
|
".kanbot/agents/fixer.md": [
|
|
84
94
|
"109c8974fe2d70f5f514f33d97761740f589a6c4",
|
|
95
|
+
"16c16692f6d2ee29bc0e4d0ca4a1b3cfc4f1ba72",
|
|
85
96
|
"7c98cf1dc3e798e0e5856fd95a3d78d378884def",
|
|
86
97
|
"810871ec49fd7be56d111c7df176d22c66748a0f"
|
|
87
98
|
],
|
|
88
|
-
".kanbot/agents/implementer
|
|
89
|
-
|
|
90
|
-
"
|
|
99
|
+
".kanbot/agents/implementer.md": ["f6000b529b1a8bed0def57af9bfa55c1ca5cd3b6"],
|
|
100
|
+
".kanbot/agents/plan-critic.md": [
|
|
101
|
+
"3d1c38922a94712d0f3a725902e3df9cee87bcda",
|
|
102
|
+
"cd6dd133f4611bd86fe27f8539a62a805e4df6c9"
|
|
91
103
|
],
|
|
92
|
-
".kanbot/agents/implementer-docs.md": [
|
|
93
|
-
"082da045a20bc914f29839e026027b6715c184a9",
|
|
94
|
-
"c5f01ac3d377133caab5b13422f1e73608d6357e"
|
|
95
|
-
],
|
|
96
|
-
".kanbot/agents/implementer-frontend.md": [
|
|
97
|
-
"0aa9b0e70c3057138b4255ee3d270ac04fc8ab0e",
|
|
98
|
-
"45a6e3b8f6e6f5a95dac865126c5649dab115fc5"
|
|
99
|
-
],
|
|
100
|
-
".kanbot/agents/implementer-technical.md": [
|
|
101
|
-
"c95dff48eed5db4aadf10d0a47e7ec1285937ebf",
|
|
102
|
-
"e4bbddb0a810313fb3a6ed3983b3b9335279feef"
|
|
103
|
-
],
|
|
104
|
-
".kanbot/agents/plan-critic.md": ["cd6dd133f4611bd86fe27f8539a62a805e4df6c9"],
|
|
105
104
|
".kanbot/agents/planner.md": [
|
|
106
105
|
"2283cf8bf6d27b8bbf721f97bb45cd40eaa6d39d",
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"
|
|
111
|
-
"61f3a45642ca954c6d4a5f41083f0a25f78d2b51"
|
|
112
|
-
],
|
|
113
|
-
".kanbot/agents/reviewer-spec.md": [
|
|
114
|
-
"11c86a654a0b715777b23bfb699a4f0d6cfe91de",
|
|
115
|
-
"ab8951ab296e96b01f419243d8acfc99c7e64b1c"
|
|
116
|
-
],
|
|
117
|
-
".kanbot/agents/test-writer.md": [
|
|
118
|
-
"dcef7f611a307229974fef4098d53816d7db0555",
|
|
119
|
-
"ec81c44c021053fae0612683a61e8414634359a0"
|
|
120
|
-
],
|
|
121
|
-
".kanbot/agents/ui-verifier.md": [
|
|
122
|
-
"38615159a8fe7b1e99cacedc49c0be53c69b90d4",
|
|
123
|
-
"a73abf7e72c654fde41df51b43a5879170978cb0"
|
|
106
|
+
"6500bea6691685d2b74ea73095f11643e0fcc937",
|
|
107
|
+
"a4a65d240ab4b23737f4d68d8765cca89cf08864",
|
|
108
|
+
"bdb111213cced70583157edad658247972dcbe45",
|
|
109
|
+
"dc50d048164ade2926b58dee48c3919c6b7d9558"
|
|
124
110
|
],
|
|
111
|
+
".kanbot/agents/reviewer.md": ["7ff1fdd21df4442d3b82784d3e6077195b32ba5b"],
|
|
125
112
|
"AGENTS.md": [
|
|
113
|
+
"0dd205961831f0082e74a3c03b79036e2114c072",
|
|
126
114
|
"3da9115cbf52b7b7d0feb8325503a8caf43ff2c7",
|
|
127
115
|
"b821013b5be7051327b349eda603218590a41f33",
|
|
116
|
+
"c688ed7f42f7a6f6a5ac600de857b42fb04aa613",
|
|
128
117
|
"cf7ef593ccfda953055dfb76b3e9c267fc9637f6",
|
|
129
|
-
"e5b76bcbaf155b79a8ff3f2e70977cc0c589566b"
|
|
118
|
+
"e5b76bcbaf155b79a8ff3f2e70977cc0c589566b",
|
|
119
|
+
"f820c2f5a9d3bc299eed35a6012bbc5d19615aef"
|
|
130
120
|
],
|
|
131
121
|
"CLAUDE.md": ["43c994c2d3617f947bcb5adf1933e21dabe46bb5"]
|
|
132
122
|
}
|
package/src/main.ts
CHANGED
|
@@ -10,6 +10,7 @@ export const main = defineCommand({
|
|
|
10
10
|
subCommands: {
|
|
11
11
|
init: () => import("./commands/init.ts").then((m) => m.default),
|
|
12
12
|
board: () => import("./commands/board.ts").then((m) => m.default),
|
|
13
|
+
app: () => import("./commands/app.ts").then((m) => m.default),
|
|
13
14
|
upgrade: () => import("./commands/upgrade.ts").then((m) => m.default),
|
|
14
15
|
skills: () => import("./commands/skills.ts").then((m) => m.default),
|
|
15
16
|
sync: () => import("./commands/sync.ts").then((m) => m.default),
|