kanbot 0.3.0 → 0.4.1
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 +8 -8
- package/package.json +1 -1
- package/schema/config.schema.json +38 -8
- package/src/agents/load.ts +11 -0
- package/src/agents/sync.ts +8 -8
- package/src/capabilities.ts +157 -0
- package/src/color.ts +16 -0
- package/src/commands/board.ts +14 -7
- package/src/commands/doctor.ts +206 -19
- package/src/commands/init.ts +15 -3
- package/src/commands/sync.ts +1 -1
- package/src/commands/upgrade.ts +20 -1
- package/src/config.ts +38 -8
- package/src/daemon/derive.ts +41 -0
- package/src/daemon/loop.ts +2 -0
- package/src/jobs/prompts.ts +25 -5
- package/src/jobs/run.ts +61 -22
- package/src/known-hashes.json +42 -57
- 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/templates/.agents/skills/approach-designer/SKILL.md +1 -1
- package/templates/.agents/skills/kanbot-admin/SKILL.md +21 -4
- package/templates/.agents/skills/kanbot-guide/SKILL.md +26 -7
- 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 +2 -3
- 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/prompts.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Capabilities, capabilitiesSection } from "../capabilities.ts";
|
|
1
2
|
import {
|
|
2
3
|
type FixJob,
|
|
3
4
|
type ImplementJob,
|
|
@@ -21,6 +22,8 @@ const RULES = `## Ground rules
|
|
|
21
22
|
- Your final answer must be the structured JSON result. Put everything the human should see in it.
|
|
22
23
|
- Write short and plain: only what the reader needs to act or understand. Don't restate the input or narrate your process.`;
|
|
23
24
|
|
|
25
|
+
const VALIDATION = "List what you used under Validation in the PR body.";
|
|
26
|
+
|
|
24
27
|
function quote(text: string) {
|
|
25
28
|
return text.trim() ? text.trim() : "_(empty)_";
|
|
26
29
|
}
|
|
@@ -109,7 +112,11 @@ export function planPrompt(job: PlanJob, explore: number | null) {
|
|
|
109
112
|
return parts.join("\n\n");
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
export function implementPrompt(
|
|
115
|
+
export function implementPrompt(
|
|
116
|
+
job: ImplementJob,
|
|
117
|
+
base: string,
|
|
118
|
+
capabilities: Capabilities = {},
|
|
119
|
+
) {
|
|
113
120
|
const { issue } = job.item;
|
|
114
121
|
const parts = [
|
|
115
122
|
issueHeader(issue),
|
|
@@ -126,13 +133,18 @@ export function implementPrompt(job: ImplementJob, base: string) {
|
|
|
126
133
|
`## Recent human comments on the issue\n${conversation(humanNotes)}`,
|
|
127
134
|
);
|
|
128
135
|
parts.push(
|
|
136
|
+
capabilitiesSection(capabilities, VALIDATION),
|
|
129
137
|
`## Workspace\n- Branch \`${job.branch}\`, created from \`origin/${base}\`.\n- Commit your work with conventional commit messages. Uncommitted changes are committed for you, but prefer small commits.\n- Run the project's tests and linters before finishing and report them in \`testsRun\`.`,
|
|
130
138
|
RULES,
|
|
131
139
|
);
|
|
132
|
-
return parts.join("\n\n");
|
|
140
|
+
return parts.filter(Boolean).join("\n\n");
|
|
133
141
|
}
|
|
134
142
|
|
|
135
|
-
export function reviewPrompt(
|
|
143
|
+
export function reviewPrompt(
|
|
144
|
+
job: ReviewJob,
|
|
145
|
+
base: string,
|
|
146
|
+
capabilities: Capabilities = {},
|
|
147
|
+
) {
|
|
136
148
|
const { issue } = job.item;
|
|
137
149
|
const { pr } = job;
|
|
138
150
|
return [
|
|
@@ -140,7 +152,8 @@ export function reviewPrompt(job: ReviewJob, base: string) {
|
|
|
140
152
|
`## Plan / acceptance criteria\n${extractPlan(issue.body) ?? stripPlan(issue.body)}`,
|
|
141
153
|
`## Pull request #${pr.number}: ${pr.title}\nBranch \`${pr.headRefName}\` (checked out). Diff with: \`git diff origin/${base}...HEAD\`. CI status: ${pr.checks ?? "none"}.\n\n### PR description\n${quote(stripMarkers(pr.body))}`,
|
|
142
154
|
humanDecisions(pr),
|
|
143
|
-
|
|
155
|
+
capabilitiesSection(capabilities),
|
|
156
|
+
`## Review pass ${job.pass} of ${reviewPasses(issue).length}: ${job.pass === 1 ? "technical" : "acceptance"}\nYou only review. Do not edit files or commit.${job.pass === 2 && Object.values(capabilities).some((c) => c.required) ? " A required capability that applies but is missing from the PR body's Validation section is a blocker." : ""}`,
|
|
144
157
|
RULES,
|
|
145
158
|
]
|
|
146
159
|
.filter(Boolean)
|
|
@@ -148,7 +161,12 @@ export function reviewPrompt(job: ReviewJob, base: string) {
|
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
/** `details`: CI logs for a CI fix, the conflicted files for a conflict fix. */
|
|
151
|
-
export function fixPrompt(
|
|
164
|
+
export function fixPrompt(
|
|
165
|
+
job: FixJob,
|
|
166
|
+
base: string,
|
|
167
|
+
details: string | null,
|
|
168
|
+
capabilities: Capabilities = {},
|
|
169
|
+
) {
|
|
152
170
|
const { issue } = job.item;
|
|
153
171
|
const { pr } = job;
|
|
154
172
|
const parts = [
|
|
@@ -156,6 +174,8 @@ export function fixPrompt(job: FixJob, base: string, details: string | null) {
|
|
|
156
174
|
`## Plan / acceptance criteria\n${extractPlan(issue.body) ?? stripPlan(issue.body)}`,
|
|
157
175
|
`## Pull request #${pr.number}: ${pr.title}\nBranch \`${pr.headRefName}\` (checked out). Diff with: \`git diff origin/${base}...HEAD\`.\n\n### Current PR description\n${quote(stripMarkers(pr.body))}`,
|
|
158
176
|
];
|
|
177
|
+
const caps = capabilitiesSection(capabilities, VALIDATION);
|
|
178
|
+
if (caps) parts.push(caps);
|
|
159
179
|
const r = job.reason;
|
|
160
180
|
const decisions = r.type === "feedback" ? null : humanDecisions(pr);
|
|
161
181
|
if (decisions) parts.push(decisions);
|
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";
|
|
@@ -168,11 +169,13 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
168
169
|
|
|
169
170
|
// Re-read so concurrent human edits to the description are kept.
|
|
170
171
|
const fresh = await fetchIssue(gh, issue.number);
|
|
171
|
-
await updateIssueBody(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
(
|
|
172
|
+
await updateIssueBody(
|
|
173
|
+
bot,
|
|
174
|
+
issue.number,
|
|
175
|
+
withPlan(fresh.body, withOpenQuestions(result.plan, result.questions)),
|
|
175
176
|
);
|
|
177
|
+
|
|
178
|
+
const allowed = result.labels.filter((l) => l === LABELS.technical);
|
|
176
179
|
await addLabels(
|
|
177
180
|
bot,
|
|
178
181
|
issue.number,
|
|
@@ -208,7 +211,9 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
208
211
|
);
|
|
209
212
|
}
|
|
210
213
|
lines.push(
|
|
211
|
-
|
|
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._",
|
|
212
217
|
);
|
|
213
218
|
lines.push(
|
|
214
219
|
[marker("plan"), ...job.comments.map((c) => marker("re", c.id))].join("\n"),
|
|
@@ -219,6 +224,28 @@ async function plan(job: PlanJob, deps: JobDeps) {
|
|
|
219
224
|
|
|
220
225
|
// ---- implement ----------------------------------------------------------------
|
|
221
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
|
+
|
|
222
249
|
async function implement(job: ImplementJob, deps: JobDeps) {
|
|
223
250
|
const { gh, bot, config, worktrees } = deps;
|
|
224
251
|
const { issue } = job.item;
|
|
@@ -231,20 +258,14 @@ async function implement(job: ImplementJob, deps: JobDeps) {
|
|
|
231
258
|
const cwd = await worktrees.checkout(worktreeName(job), job.branch);
|
|
232
259
|
let result: z.infer<typeof ImplementResult> | null = null;
|
|
233
260
|
if (!resumed) {
|
|
234
|
-
const
|
|
235
|
-
.find((l) => l.startsWith(LABELS.typePrefix))
|
|
236
|
-
?.slice(LABELS.typePrefix.length);
|
|
237
|
-
const agentName =
|
|
238
|
-
config.implementers[typeLabel ?? "default"] ??
|
|
239
|
-
config.implementers.default ??
|
|
240
|
-
"implementer-backend";
|
|
261
|
+
const agentName = implementerFor(config, deps.agents, issue.labels);
|
|
241
262
|
result = await runAgent(
|
|
242
263
|
deps,
|
|
243
264
|
job,
|
|
244
265
|
agentName,
|
|
245
266
|
"implement-result",
|
|
246
267
|
cwd,
|
|
247
|
-
implementPrompt(job, config.baseBranch),
|
|
268
|
+
implementPrompt(job, config.baseBranch, config.capabilities),
|
|
248
269
|
);
|
|
249
270
|
if (result.status === "blocked") {
|
|
250
271
|
await addLabels(bot, issue.number, [LABELS.needsHuman]);
|
|
@@ -306,9 +327,7 @@ async function implement(job: ImplementJob, deps: JobDeps) {
|
|
|
306
327
|
base: config.baseBranch,
|
|
307
328
|
});
|
|
308
329
|
if (pr) {
|
|
309
|
-
const labels = issue.labels.filter(
|
|
310
|
-
(l) => l.startsWith(LABELS.typePrefix) || l === LABELS.technical,
|
|
311
|
-
);
|
|
330
|
+
const labels: string[] = issue.labels.filter((l) => l === LABELS.technical);
|
|
312
331
|
if (job.variant !== null)
|
|
313
332
|
labels.push(`${LABELS.variantPrefix}${job.variant}`);
|
|
314
333
|
await addLabels(bot, pr.number, labels);
|
|
@@ -336,8 +355,17 @@ export function absoluteImages(md: string, repo: string, branch: string) {
|
|
|
336
355
|
|
|
337
356
|
// ---- review -------------------------------------------------------------------
|
|
338
357
|
|
|
339
|
-
|
|
340
|
-
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
|
+
}
|
|
341
369
|
|
|
342
370
|
async function review(job: ReviewJob, deps: JobDeps) {
|
|
343
371
|
const cwd = await deps.worktrees.checkout(
|
|
@@ -347,10 +375,10 @@ async function review(job: ReviewJob, deps: JobDeps) {
|
|
|
347
375
|
const result = await runAgent(
|
|
348
376
|
deps,
|
|
349
377
|
job,
|
|
350
|
-
|
|
378
|
+
passAgent(deps.agents, job.pass),
|
|
351
379
|
"review-result",
|
|
352
380
|
cwd,
|
|
353
|
-
reviewPrompt(job, deps.config.baseBranch),
|
|
381
|
+
reviewPrompt(job, deps.config.baseBranch, deps.config.capabilities),
|
|
354
382
|
);
|
|
355
383
|
ReviewResult.parse(result);
|
|
356
384
|
const icon = result.verdict === "approve" ? "✅" : "🛠️";
|
|
@@ -382,7 +410,7 @@ async function fix(job: FixJob, deps: JobDeps) {
|
|
|
382
410
|
"fixer",
|
|
383
411
|
"fix-result",
|
|
384
412
|
cwd,
|
|
385
|
-
fixPrompt(job, config.baseBranch, logs),
|
|
413
|
+
fixPrompt(job, config.baseBranch, logs, config.capabilities),
|
|
386
414
|
);
|
|
387
415
|
FixResult.parse(result);
|
|
388
416
|
|
|
@@ -488,7 +516,12 @@ async function mergeBase(job: FixJob, deps: JobDeps, cwd: string) {
|
|
|
488
516
|
"fixer",
|
|
489
517
|
"fix-result",
|
|
490
518
|
cwd,
|
|
491
|
-
fixPrompt(
|
|
519
|
+
fixPrompt(
|
|
520
|
+
job,
|
|
521
|
+
config.baseBranch,
|
|
522
|
+
files.map((f) => `- ${f}`).join("\n"),
|
|
523
|
+
config.capabilities,
|
|
524
|
+
),
|
|
492
525
|
);
|
|
493
526
|
FixResult.parse(result);
|
|
494
527
|
const left: string[] = [];
|
|
@@ -551,6 +584,12 @@ export async function applyAction(action: Action, deps: JobDeps) {
|
|
|
551
584
|
);
|
|
552
585
|
return;
|
|
553
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
|
+
);
|
|
554
593
|
case "merge": {
|
|
555
594
|
const { pr } = action;
|
|
556
595
|
log.info(`merging #${pr.number}, approved by ${action.by}`);
|
package/src/known-hashes.json
CHANGED
|
@@ -1,44 +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",
|
|
22
12
|
"557532f241c0fc1e04f03ce8edb69efaa7b5165b",
|
|
13
|
+
"a7a7b2f55be7235ea17ffd092755af8493c8ef76",
|
|
14
|
+
"b09b9ccc5ca90585418a6645919c10d9a487bad9",
|
|
15
|
+
"c29ed25fa7f825e64b58c54ed2240823fb22ef6c",
|
|
23
16
|
"e427c711b532963c0916f07ecc6b11e9b47cb9fa"
|
|
24
17
|
],
|
|
25
18
|
".agents/skills/kanbot-guide/SKILL.md": [
|
|
19
|
+
"1251194ba34a8b6eae8508037b0a76c60d7a26b8",
|
|
20
|
+
"2c6c77a225c5b880eff023d4b3c1305bfc91bfb3",
|
|
21
|
+
"2ccb86b12e5af121b79ef9f53c2a35dbbdb2ddeb",
|
|
22
|
+
"a24c0a655d6485e65bd5dea2a71f3afe67f04404",
|
|
26
23
|
"cf3e95d137a5a58a273262bca6ba25fbc95fa037"
|
|
27
24
|
],
|
|
28
25
|
".agents/skills/kanbot-idea/SKILL.md": [
|
|
29
|
-
"8ab28cec8d3f30250ffe080106d0de8e3c2eaa8a"
|
|
26
|
+
"8ab28cec8d3f30250ffe080106d0de8e3c2eaa8a",
|
|
27
|
+
"9d47113a299a75514a00993060bf6afebb049c6c"
|
|
30
28
|
],
|
|
31
29
|
".agents/skills/plan-template/SKILL.md": [
|
|
32
30
|
"47a5dbbd2870883ca42dd9410d224ef9c0c9979f",
|
|
33
31
|
"79cfd0bf36110b5d6f452e6400161609b11df0d6",
|
|
34
|
-
"7bfde45995efaf515e45ccec09c204178203fdb7"
|
|
32
|
+
"7bfde45995efaf515e45ccec09c204178203fdb7",
|
|
33
|
+
"841446c304ffe9783c79f293e050a28ceb540b3d",
|
|
34
|
+
"e6b6e55a42bfc42d0c9ff1a5cdf3a0573ab665c0"
|
|
35
35
|
],
|
|
36
36
|
".agents/skills/pr-description/SKILL.md": [
|
|
37
37
|
"1db6eb751e0bb1ea0014a2b414e04f6af59b1b2d",
|
|
38
38
|
"6b0cca1be3abe8d031159c94e5d3b4f20307d600",
|
|
39
|
-
"7e477762c9c5f09ce6d5d51ccd9138ce9743baa8"
|
|
39
|
+
"7e477762c9c5f09ce6d5d51ccd9138ce9743baa8",
|
|
40
|
+
"830c94888620de0a5bb7030672c017c5fafa63e5"
|
|
40
41
|
],
|
|
41
42
|
".agents/skills/project-conventions/SKILL.md": [
|
|
43
|
+
"668d904ff8fdf33edbe9768cd6bf223813380c9d",
|
|
42
44
|
"81d9ff980a15a6f901010646b800b3e6e379e4b0",
|
|
43
45
|
"dabf1dadc148c020496d8f2f9bb8de6269f56479"
|
|
44
46
|
],
|
|
@@ -48,18 +50,18 @@
|
|
|
48
50
|
".agents/skills/project-memory/SKILL.md": [
|
|
49
51
|
"d354e11310db01362ec2bf5eec1080db77ac053d"
|
|
50
52
|
],
|
|
53
|
+
".agents/skills/review-findings/SKILL.md": [
|
|
54
|
+
"4ba188751544038ca5ab3fdeaf61f40455a3bcec"
|
|
55
|
+
],
|
|
51
56
|
".agents/skills/spec-review/SKILL.md": [
|
|
52
57
|
"03e44fe4f23b04f1b11c907936ee9f6c7c8131f0",
|
|
58
|
+
"84fbc57a8b8f4fe473258402772185944a54a81c",
|
|
53
59
|
"f455d1007d589602e5afce0dac4f00d78aa7c67a"
|
|
54
60
|
],
|
|
55
|
-
".agents/skills/tdd/SKILL.md": ["5e6b20868e02ae24f6d25824bac431143bd43021"],
|
|
56
61
|
".agents/skills/user-stories/SKILL.md": [
|
|
57
62
|
"1b37f8e59c15b934d555070a63d4f0d3939a007f",
|
|
58
|
-
"1b5d229d043c1bfbd84aecd9ee6191b1d473f1d8"
|
|
59
|
-
|
|
60
|
-
".agents/skills/verify-ui/SKILL.md": [
|
|
61
|
-
"c67a0e1d6a1c1d9adf59dd154160ccf58519ea72",
|
|
62
|
-
"f4d3d07c0ec28a9961d919356d0b4e572b724b43"
|
|
63
|
+
"1b5d229d043c1bfbd84aecd9ee6191b1d473f1d8",
|
|
64
|
+
"73455bec36bd5677cdf3c081677ad2856c434954"
|
|
63
65
|
],
|
|
64
66
|
".github/ISSUE_TEMPLATE/config.yml": [
|
|
65
67
|
"0086358db1eb971c0cfa8739c27518bbc18a5ff4"
|
|
@@ -71,12 +73,16 @@
|
|
|
71
73
|
"d26a148970ba6323d9cfdd86924cb2e82e68b705"
|
|
72
74
|
],
|
|
73
75
|
".github/pull_request_template.md": [
|
|
74
|
-
"57cfbc21472a94ccccb19ef6f19e87a473925649"
|
|
76
|
+
"57cfbc21472a94ccccb19ef6f19e87a473925649",
|
|
77
|
+
"b8060c2599769dd12477081b6ec51733a2a82c7b"
|
|
75
78
|
],
|
|
76
79
|
".github/workflows/ci.yml": ["dce7bcf80cef91c10923829eb5893d2b311b58e5"],
|
|
77
80
|
".github/workflows/kanbot-merged.yml": [
|
|
78
81
|
"8daaea63e23cf1e0d37821bfadaf60018a448de2"
|
|
79
82
|
],
|
|
83
|
+
".kanbot/agents/acceptance-reviewer.md": [
|
|
84
|
+
"e6b65950345c686b4d219026d8d2d185e9735c12"
|
|
85
|
+
],
|
|
80
86
|
".kanbot/agents/approach-designer.md": [
|
|
81
87
|
"7e5f8c16c4d4a4e261bccc5dc8b4fff2d3046a72"
|
|
82
88
|
],
|
|
@@ -86,52 +92,31 @@
|
|
|
86
92
|
],
|
|
87
93
|
".kanbot/agents/fixer.md": [
|
|
88
94
|
"109c8974fe2d70f5f514f33d97761740f589a6c4",
|
|
95
|
+
"16c16692f6d2ee29bc0e4d0ca4a1b3cfc4f1ba72",
|
|
89
96
|
"7c98cf1dc3e798e0e5856fd95a3d78d378884def",
|
|
90
97
|
"810871ec49fd7be56d111c7df176d22c66748a0f"
|
|
91
98
|
],
|
|
92
|
-
".kanbot/agents/implementer
|
|
93
|
-
|
|
94
|
-
"
|
|
99
|
+
".kanbot/agents/implementer.md": ["f6000b529b1a8bed0def57af9bfa55c1ca5cd3b6"],
|
|
100
|
+
".kanbot/agents/plan-critic.md": [
|
|
101
|
+
"3d1c38922a94712d0f3a725902e3df9cee87bcda",
|
|
102
|
+
"cd6dd133f4611bd86fe27f8539a62a805e4df6c9"
|
|
95
103
|
],
|
|
96
|
-
".kanbot/agents/implementer-docs.md": [
|
|
97
|
-
"082da045a20bc914f29839e026027b6715c184a9",
|
|
98
|
-
"c5f01ac3d377133caab5b13422f1e73608d6357e"
|
|
99
|
-
],
|
|
100
|
-
".kanbot/agents/implementer-frontend.md": [
|
|
101
|
-
"0aa9b0e70c3057138b4255ee3d270ac04fc8ab0e",
|
|
102
|
-
"45a6e3b8f6e6f5a95dac865126c5649dab115fc5"
|
|
103
|
-
],
|
|
104
|
-
".kanbot/agents/implementer-technical.md": [
|
|
105
|
-
"c95dff48eed5db4aadf10d0a47e7ec1285937ebf",
|
|
106
|
-
"e4bbddb0a810313fb3a6ed3983b3b9335279feef"
|
|
107
|
-
],
|
|
108
|
-
".kanbot/agents/plan-critic.md": ["cd6dd133f4611bd86fe27f8539a62a805e4df6c9"],
|
|
109
104
|
".kanbot/agents/planner.md": [
|
|
110
105
|
"2283cf8bf6d27b8bbf721f97bb45cd40eaa6d39d",
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
"61f3a45642ca954c6d4a5f41083f0a25f78d2b51"
|
|
116
|
-
],
|
|
117
|
-
".kanbot/agents/reviewer-spec.md": [
|
|
118
|
-
"11c86a654a0b715777b23bfb699a4f0d6cfe91de",
|
|
119
|
-
"ab8951ab296e96b01f419243d8acfc99c7e64b1c"
|
|
120
|
-
],
|
|
121
|
-
".kanbot/agents/test-writer.md": [
|
|
122
|
-
"dcef7f611a307229974fef4098d53816d7db0555",
|
|
123
|
-
"ec81c44c021053fae0612683a61e8414634359a0"
|
|
124
|
-
],
|
|
125
|
-
".kanbot/agents/ui-verifier.md": [
|
|
126
|
-
"38615159a8fe7b1e99cacedc49c0be53c69b90d4",
|
|
127
|
-
"a73abf7e72c654fde41df51b43a5879170978cb0"
|
|
106
|
+
"6500bea6691685d2b74ea73095f11643e0fcc937",
|
|
107
|
+
"a4a65d240ab4b23737f4d68d8765cca89cf08864",
|
|
108
|
+
"bdb111213cced70583157edad658247972dcbe45",
|
|
109
|
+
"dc50d048164ade2926b58dee48c3919c6b7d9558"
|
|
128
110
|
],
|
|
111
|
+
".kanbot/agents/reviewer.md": ["7ff1fdd21df4442d3b82784d3e6077195b32ba5b"],
|
|
129
112
|
"AGENTS.md": [
|
|
113
|
+
"0dd205961831f0082e74a3c03b79036e2114c072",
|
|
130
114
|
"3da9115cbf52b7b7d0feb8325503a8caf43ff2c7",
|
|
131
115
|
"b821013b5be7051327b349eda603218590a41f33",
|
|
132
116
|
"c688ed7f42f7a6f6a5ac600de857b42fb04aa613",
|
|
133
117
|
"cf7ef593ccfda953055dfb76b3e9c267fc9637f6",
|
|
134
|
-
"e5b76bcbaf155b79a8ff3f2e70977cc0c589566b"
|
|
118
|
+
"e5b76bcbaf155b79a8ff3f2e70977cc0c589566b",
|
|
119
|
+
"f820c2f5a9d3bc299eed35a6012bbc5d19615aef"
|
|
135
120
|
],
|
|
136
121
|
"CLAUDE.md": ["43c994c2d3617f947bcb5adf1933e21dabe46bb5"]
|
|
137
122
|
}
|
package/src/providers/claude.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CLAUDE_DENY,
|
|
9
9
|
claudeTools,
|
|
10
10
|
} from "../agents/sync.ts";
|
|
11
|
+
import { mcpConfigPath } from "./mcp.ts";
|
|
11
12
|
import {
|
|
12
13
|
looksRateLimited,
|
|
13
14
|
type Provider,
|
|
@@ -25,45 +26,54 @@ type ResultEvent = {
|
|
|
25
26
|
api_error_status?: number | null;
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
/** CLI args for a headless run. */
|
|
30
|
+
export function claudeArgs(input: RunInput, agentsFile: string): string[] {
|
|
31
|
+
const { agent } = input;
|
|
32
|
+
const cmd = [
|
|
33
|
+
"claude",
|
|
34
|
+
"-p",
|
|
35
|
+
"--agents",
|
|
36
|
+
agentsFile,
|
|
37
|
+
"--agent",
|
|
38
|
+
agent.name,
|
|
39
|
+
"--output-format",
|
|
40
|
+
"stream-json",
|
|
41
|
+
"--verbose",
|
|
42
|
+
"--json-schema",
|
|
43
|
+
JSON.stringify(input.schema),
|
|
44
|
+
"--permission-mode",
|
|
45
|
+
"acceptEdits",
|
|
46
|
+
"--allowedTools",
|
|
47
|
+
...headlessTools(agent),
|
|
48
|
+
"--disallowedTools",
|
|
49
|
+
...CLAUDE_DENY,
|
|
50
|
+
"--no-session-persistence",
|
|
51
|
+
// Isolate from the user's own setup: no user-level plugins, skills or
|
|
52
|
+
// MCP servers, so workers only see the repo's agents and skills.
|
|
53
|
+
"--setting-sources",
|
|
54
|
+
"project,local",
|
|
55
|
+
"--strict-mcp-config",
|
|
56
|
+
"--settings",
|
|
57
|
+
JSON.stringify(CLAUDE_ATTRIBUTION),
|
|
58
|
+
];
|
|
59
|
+
// The repo's committed MCP servers only.
|
|
60
|
+
const mcp = mcpConfigPath(input.cwd);
|
|
61
|
+
if (mcp) cmd.push("--mcp-config", mcp);
|
|
62
|
+
if (agent.models.claude) cmd.push("--model", agent.models.claude);
|
|
63
|
+
if (agent.effort) cmd.push("--effort", agent.effort);
|
|
64
|
+
return cmd;
|
|
65
|
+
}
|
|
66
|
+
|
|
28
67
|
/** Claude Code headless (`claude -p`), using the logged-in subscription. */
|
|
29
68
|
export const claude: Provider = {
|
|
30
69
|
name: "claude",
|
|
31
70
|
async run(input: RunInput): Promise<RunResult> {
|
|
32
|
-
const { agent } = input;
|
|
33
71
|
// Pass agent definitions explicitly so runs don't depend on what is
|
|
34
72
|
// committed in the worktree's .claude/agents.
|
|
35
73
|
const tmp = await mkdtemp(join(tmpdir(), "kanbot-claude-"));
|
|
36
74
|
const agentsFile = join(tmp, "agents.json");
|
|
37
75
|
await Bun.write(agentsFile, JSON.stringify(claudeAgentsJson(input.agents)));
|
|
38
|
-
const cmd =
|
|
39
|
-
"claude",
|
|
40
|
-
"-p",
|
|
41
|
-
"--agents",
|
|
42
|
-
agentsFile,
|
|
43
|
-
"--agent",
|
|
44
|
-
agent.name,
|
|
45
|
-
"--output-format",
|
|
46
|
-
"stream-json",
|
|
47
|
-
"--verbose",
|
|
48
|
-
"--json-schema",
|
|
49
|
-
JSON.stringify(input.schema),
|
|
50
|
-
"--permission-mode",
|
|
51
|
-
"acceptEdits",
|
|
52
|
-
"--allowedTools",
|
|
53
|
-
...headlessTools(agent),
|
|
54
|
-
"--disallowedTools",
|
|
55
|
-
...CLAUDE_DENY,
|
|
56
|
-
"--no-session-persistence",
|
|
57
|
-
// Isolate from the user's own setup: no user-level plugins, skills or
|
|
58
|
-
// MCP servers, so workers only see the repo's agents and skills.
|
|
59
|
-
"--setting-sources",
|
|
60
|
-
"project,local",
|
|
61
|
-
"--strict-mcp-config",
|
|
62
|
-
"--settings",
|
|
63
|
-
JSON.stringify(CLAUDE_ATTRIBUTION),
|
|
64
|
-
];
|
|
65
|
-
if (agent.models.claude) cmd.push("--model", agent.models.claude);
|
|
66
|
-
if (agent.effort) cmd.push("--effort", agent.effort);
|
|
76
|
+
const cmd = claudeArgs(input, agentsFile);
|
|
67
77
|
|
|
68
78
|
const res = await spawnWorker(cmd, {
|
|
69
79
|
cwd: input.cwd,
|
package/src/providers/codex.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import type { AgentDef } from "../agents/load.ts";
|
|
4
5
|
import { agentInstructions } from "../agents/sync.ts";
|
|
5
6
|
import { git } from "../daemon/git.ts";
|
|
7
|
+
import { codexMcpArgs, type McpServer, readMcpServers } from "./mcp.ts";
|
|
6
8
|
import {
|
|
7
9
|
looksRateLimited,
|
|
8
10
|
type Provider,
|
|
@@ -11,6 +13,58 @@ import {
|
|
|
11
13
|
spawnWorker,
|
|
12
14
|
} from "./types.ts";
|
|
13
15
|
|
|
16
|
+
/** CLI args for a headless run. */
|
|
17
|
+
export function codexArgs(o: {
|
|
18
|
+
agent: AgentDef;
|
|
19
|
+
cwd: string;
|
|
20
|
+
gitDir: string;
|
|
21
|
+
schemaFile: string;
|
|
22
|
+
lastFile: string;
|
|
23
|
+
networkAccess: boolean;
|
|
24
|
+
mcpServers: Record<string, McpServer>;
|
|
25
|
+
}): string[] {
|
|
26
|
+
const { agent } = o;
|
|
27
|
+
const sandbox =
|
|
28
|
+
agent.tools.includes("edit") || agent.tools.includes("bash")
|
|
29
|
+
? "workspace-write"
|
|
30
|
+
: "read-only";
|
|
31
|
+
const cmd = [
|
|
32
|
+
"codex",
|
|
33
|
+
"exec",
|
|
34
|
+
"--json",
|
|
35
|
+
"--ephemeral",
|
|
36
|
+
// Isolate from the user's own setup: no user-level plugins or MCP
|
|
37
|
+
// servers. Auth still comes from CODEX_HOME.
|
|
38
|
+
"--ignore-user-config",
|
|
39
|
+
"--sandbox",
|
|
40
|
+
sandbox,
|
|
41
|
+
"--cd",
|
|
42
|
+
o.cwd,
|
|
43
|
+
"--add-dir",
|
|
44
|
+
o.gitDir,
|
|
45
|
+
"--output-schema",
|
|
46
|
+
o.schemaFile,
|
|
47
|
+
"--output-last-message",
|
|
48
|
+
o.lastFile,
|
|
49
|
+
"-c",
|
|
50
|
+
`developer_instructions=${JSON.stringify(agentInstructions(agent))}`,
|
|
51
|
+
// The repo's committed MCP servers only.
|
|
52
|
+
...codexMcpArgs(o.mcpServers),
|
|
53
|
+
];
|
|
54
|
+
if (o.networkAccess)
|
|
55
|
+
cmd.push("-c", "sandbox_workspace_write.network_access=true");
|
|
56
|
+
if (agent.models.codex && agent.models.codex !== "default")
|
|
57
|
+
cmd.push("--model", agent.models.codex);
|
|
58
|
+
if (agent.effort) {
|
|
59
|
+
cmd.push(
|
|
60
|
+
"-c",
|
|
61
|
+
`model_reasoning_effort="${agent.effort === "max" ? "xhigh" : agent.effort}"`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
cmd.push("-");
|
|
65
|
+
return cmd;
|
|
66
|
+
}
|
|
67
|
+
|
|
14
68
|
/** Codex CLI headless (`codex exec`), using the logged-in ChatGPT plan. */
|
|
15
69
|
export function codex({ networkAccess }: { networkAccess: boolean }): Provider {
|
|
16
70
|
return {
|
|
@@ -29,39 +83,15 @@ export function codex({ networkAccess }: { networkAccess: boolean }): Provider {
|
|
|
29
83
|
"--path-format=absolute",
|
|
30
84
|
"--git-common-dir",
|
|
31
85
|
);
|
|
32
|
-
const
|
|
33
|
-
agent
|
|
34
|
-
|
|
35
|
-
: "read-only";
|
|
36
|
-
const cmd = [
|
|
37
|
-
"codex",
|
|
38
|
-
"exec",
|
|
39
|
-
"--json",
|
|
40
|
-
"--ephemeral",
|
|
41
|
-
"--sandbox",
|
|
42
|
-
sandbox,
|
|
43
|
-
"--cd",
|
|
44
|
-
input.cwd,
|
|
45
|
-
"--add-dir",
|
|
86
|
+
const cmd = codexArgs({
|
|
87
|
+
agent,
|
|
88
|
+
cwd: input.cwd,
|
|
46
89
|
gitDir,
|
|
47
|
-
"--output-schema",
|
|
48
90
|
schemaFile,
|
|
49
|
-
"--output-last-message",
|
|
50
91
|
lastFile,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (networkAccess)
|
|
55
|
-
cmd.push("-c", "sandbox_workspace_write.network_access=true");
|
|
56
|
-
if (agent.models.codex && agent.models.codex !== "default")
|
|
57
|
-
cmd.push("--model", agent.models.codex);
|
|
58
|
-
if (agent.effort) {
|
|
59
|
-
cmd.push(
|
|
60
|
-
"-c",
|
|
61
|
-
`model_reasoning_effort="${agent.effort === "max" ? "xhigh" : agent.effort}"`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
cmd.push("-");
|
|
92
|
+
networkAccess,
|
|
93
|
+
mcpServers: await readMcpServers(input.cwd),
|
|
94
|
+
});
|
|
65
95
|
|
|
66
96
|
const res = await spawnWorker(cmd, {
|
|
67
97
|
cwd: input.cwd,
|