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.
Files changed (62) hide show
  1. package/README.md +15 -9
  2. package/package.json +1 -1
  3. package/schema/config.schema.json +92 -8
  4. package/src/agents/load.ts +11 -0
  5. package/src/agents/sync.ts +8 -8
  6. package/src/capabilities.ts +157 -0
  7. package/src/commands/app.ts +169 -0
  8. package/src/commands/board.ts +14 -7
  9. package/src/commands/doctor.ts +115 -6
  10. package/src/commands/init.ts +15 -3
  11. package/src/commands/skills.ts +1 -1
  12. package/src/commands/sync.ts +1 -1
  13. package/src/commands/upgrade.ts +20 -1
  14. package/src/config.ts +72 -8
  15. package/src/daemon/derive.ts +79 -1
  16. package/src/daemon/loop.ts +10 -1
  17. package/src/daemon/snapshot.ts +1 -0
  18. package/src/daemon/types.ts +17 -0
  19. package/src/github/api.ts +47 -2
  20. package/src/github/app.ts +153 -0
  21. package/src/github/gh.ts +29 -3
  22. package/src/jobs/prompts.ts +30 -6
  23. package/src/jobs/run.ts +135 -56
  24. package/src/known-hashes.json +47 -57
  25. package/src/main.ts +1 -0
  26. package/src/providers/claude.ts +40 -30
  27. package/src/providers/codex.ts +60 -30
  28. package/src/providers/mcp.ts +71 -0
  29. package/src/schemas/results.ts +2 -5
  30. package/src/templates.ts +1 -0
  31. package/templates/.agents/skills/approach-designer/SKILL.md +1 -1
  32. package/templates/.agents/skills/kanbot-admin/SKILL.md +39 -7
  33. package/templates/.agents/skills/kanbot-guide/SKILL.md +110 -0
  34. package/templates/.agents/skills/kanbot-idea/SKILL.md +1 -1
  35. package/templates/.agents/skills/plan-template/SKILL.md +14 -6
  36. package/templates/.agents/skills/pr-description/SKILL.md +7 -3
  37. package/templates/.agents/skills/project-conventions/SKILL.md +23 -35
  38. package/templates/.agents/skills/review-findings/SKILL.md +47 -0
  39. package/templates/.agents/skills/spec-review/SKILL.md +10 -27
  40. package/templates/.agents/skills/user-stories/SKILL.md +1 -1
  41. package/templates/.github/pull_request_template.md +6 -0
  42. package/templates/.kanbot/agents/acceptance-reviewer.md +43 -0
  43. package/templates/.kanbot/agents/fixer.md +7 -5
  44. package/templates/.kanbot/agents/implementer.md +49 -0
  45. package/templates/.kanbot/agents/plan-critic.md +3 -3
  46. package/templates/.kanbot/agents/planner.md +10 -9
  47. package/templates/.kanbot/agents/reviewer.md +42 -0
  48. package/templates/AGENTS.md +3 -4
  49. package/templates/.agents/skills/accessibility/SKILL.md +0 -76
  50. package/templates/.agents/skills/code-review/SKILL.md +0 -83
  51. package/templates/.agents/skills/frontend-design/LICENSE.txt +0 -177
  52. package/templates/.agents/skills/frontend-design/SKILL.md +0 -76
  53. package/templates/.agents/skills/tdd/SKILL.md +0 -51
  54. package/templates/.agents/skills/verify-ui/SKILL.md +0 -104
  55. package/templates/.kanbot/agents/implementer-backend.md +0 -57
  56. package/templates/.kanbot/agents/implementer-docs.md +0 -55
  57. package/templates/.kanbot/agents/implementer-frontend.md +0 -54
  58. package/templates/.kanbot/agents/implementer-technical.md +0 -53
  59. package/templates/.kanbot/agents/reviewer-code.md +0 -46
  60. package/templates/.kanbot/agents/reviewer-spec.md +0 -49
  61. package/templates/.kanbot/agents/test-writer.md +0 -35
  62. 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(gh, c.id, "EYES")));
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(gh, issue.number, withPlan(fresh.body, result.plan));
165
-
166
- const allowed = result.labels.filter(
167
- (l) => l === LABELS.technical || l.startsWith(LABELS.typePrefix),
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
- gh,
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
- gh,
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(gh, issue.id, res.node_id);
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
- "_Comment to refine. Drag the card to **Ready** when the plan is approved._",
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(gh, issue.number, lines.join("\n\n"));
210
- await Promise.all(job.comments.map((c) => react(gh, c.id, "THUMBS_UP")));
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(gh, issue.number, [LABELS.working]);
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 typeLabel = issue.labels
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(gh, issue.number, [LABELS.needsHuman]);
271
+ await addLabels(bot, issue.number, [LABELS.needsHuman]);
244
272
  await postComment(
245
- gh,
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(gh, {
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(gh, pr.number, labels);
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
- const PASS_AGENT = { 1: "reviewer-code", 2: "reviewer-spec" } as const;
333
- const PASS_NAME = { 1: "code", 2: "spec" } as 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
- PASS_AGENT[job.pass],
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.gh, job.pr.number, body);
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(gh, pr.number, withDescription(pr.body, desc));
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
- gh,
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
- gh,
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
- gh,
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(gh, t.id);
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
- gh,
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
- gh,
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 { gh, worktrees, config } = deps;
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(job, config.baseBranch, files.map((f) => `- ${f}`).join("\n")),
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
- gh,
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(gh, action.number, action.add);
522
- for (const l of action.remove) await removeLabel(gh, action.number, l);
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(gh, action.pr.number, [LABELS.agentReviewed]);
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
- gh,
571
+ bot,
528
572
  action.pr.number,
529
- `**Agent review complete.** Ready for you to test and merge.\n\n${marker("ready")}`,
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
- gh,
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(gh, action.number, [LABELS.needsHuman]);
634
+ await addLabels(bot, action.number, [LABELS.needsHuman]);
556
635
  await postComment(
557
- gh,
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
  );
@@ -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-backend.md": [
89
- "5d454e29260ba8df0ef6e21f1ece9d02a472d4ed",
90
- "6fefffb02bb5551631839abd0863379e58bc7dcc"
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
- "bdb111213cced70583157edad658247972dcbe45"
108
- ],
109
- ".kanbot/agents/reviewer-code.md": [
110
- "398035214b63ceb3469a3b3384b07209e4cd63dd",
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),