kanbot 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +112 -2
- package/package.json +75 -5
- package/schema/config.schema.json +206 -0
- package/src/agents/load.ts +105 -0
- package/src/agents/sync.ts +219 -0
- package/src/cli.ts +27 -0
- package/src/commands/board.ts +140 -0
- package/src/commands/completion.ts +30 -0
- package/src/commands/doctor.ts +260 -0
- package/src/commands/init.ts +119 -0
- package/src/commands/run.ts +32 -0
- package/src/commands/skills.ts +63 -0
- package/src/commands/status.ts +70 -0
- package/src/commands/sync.ts +31 -0
- package/src/commands/upgrade.ts +70 -0
- package/src/completion.ts +217 -0
- package/src/config.ts +240 -0
- package/src/daemon/derive.ts +413 -0
- package/src/daemon/git.ts +187 -0
- package/src/daemon/lock.ts +29 -0
- package/src/daemon/loop.ts +212 -0
- package/src/daemon/notify.ts +44 -0
- package/src/daemon/snapshot.ts +109 -0
- package/src/daemon/state.ts +60 -0
- package/src/daemon/types.ts +73 -0
- package/src/errors.ts +171 -0
- package/src/github/api.ts +332 -0
- package/src/github/gh.ts +110 -0
- package/src/github/markers.ts +82 -0
- package/src/github/project.ts +288 -0
- package/src/github/trust.ts +38 -0
- package/src/jobs/prompts.ts +205 -0
- package/src/jobs/run.ts +569 -0
- package/src/known-hashes.json +132 -0
- package/src/log.ts +11 -0
- package/src/main.ts +21 -0
- package/src/providers/claude.ts +122 -0
- package/src/providers/codex.ts +98 -0
- package/src/providers/types.ts +94 -0
- package/src/schemas/results.ts +116 -0
- package/src/templates.ts +192 -0
- package/templates/.agents/skills/accessibility/SKILL.md +76 -0
- package/templates/.agents/skills/approach-designer/SKILL.md +79 -0
- package/templates/.agents/skills/code-review/SKILL.md +83 -0
- package/templates/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/templates/.agents/skills/frontend-design/SKILL.md +76 -0
- package/templates/.agents/skills/issue-splitting/SKILL.md +78 -0
- package/templates/.agents/skills/kanbot-admin/SKILL.md +57 -0
- package/templates/.agents/skills/kanbot-idea/SKILL.md +56 -0
- package/templates/.agents/skills/plan-template/SKILL.md +85 -0
- package/templates/.agents/skills/pr-description/SKILL.md +44 -0
- package/templates/.agents/skills/project-conventions/SKILL.md +79 -0
- package/templates/.agents/skills/project-memory/SKILL.md +39 -0
- package/templates/.agents/skills/project-memory/facts/.gitkeep +0 -0
- package/templates/.agents/skills/spec-review/SKILL.md +73 -0
- package/templates/.agents/skills/tdd/SKILL.md +51 -0
- package/templates/.agents/skills/user-stories/SKILL.md +82 -0
- package/templates/.agents/skills/verify-ui/SKILL.md +104 -0
- package/templates/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/templates/.github/ISSUE_TEMPLATE/technical.yml +15 -0
- package/templates/.github/ISSUE_TEMPLATE/user-story.yml +15 -0
- package/templates/.github/pull_request_template.md +15 -0
- package/templates/.github/workflows/ci.yml +23 -0
- package/templates/.github/workflows/kanbot-merged.yml +30 -0
- package/templates/.kanbot/agents/approach-designer.md +31 -0
- package/templates/.kanbot/agents/explorer.md +35 -0
- package/templates/.kanbot/agents/fixer.md +54 -0
- package/templates/.kanbot/agents/implementer-backend.md +57 -0
- package/templates/.kanbot/agents/implementer-docs.md +55 -0
- package/templates/.kanbot/agents/implementer-frontend.md +54 -0
- package/templates/.kanbot/agents/implementer-technical.md +53 -0
- package/templates/.kanbot/agents/plan-critic.md +36 -0
- package/templates/.kanbot/agents/planner.md +52 -0
- package/templates/.kanbot/agents/reviewer-code.md +46 -0
- package/templates/.kanbot/agents/reviewer-spec.md +49 -0
- package/templates/.kanbot/agents/test-writer.md +35 -0
- package/templates/.kanbot/agents/ui-verifier.md +35 -0
- package/templates/AGENTS.md +39 -0
- package/templates/CLAUDE.md +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikael Brevik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
<img src="docs/public/logo.svg#gh-light-mode-only" alt="kanbot logo" width="96">
|
|
2
|
+
<img src="docs/public/logo-dark.svg#gh-dark-mode-only" alt="kanbot logo" width="96">
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# kanbot
|
|
5
|
+
|
|
6
|
+
Run an autonomous dev team from a GitHub Project board. You define, specify and prioritize work while Claude or Codex agents work as your team.
|
|
7
|
+
Uses subscriptions for Claude or Codex, and does not require API usage. The agents are fully autonomous, but you can comment on their work and steer them as needed.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
Ideas["Ideas<br/><i>you write</i>"] --> Planning["Planning<br/><i>agent plans, you comment</i>"]
|
|
12
|
+
Planning --> Ready["Ready<br/><i>you approve</i>"]
|
|
13
|
+
Ready --> InProgress["In Progress<br/><i>agents build</i>"]
|
|
14
|
+
InProgress --> Review["Review<br/><i>agents review, you comment</i>"]
|
|
15
|
+
Review --> Done["Done<br/><i>you merge</i>"]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Full docs: [docs/](./docs/00-index.md). Demo video: [docs/assets/demo.mp4](./docs/assets/demo.mp4).
|
|
19
|
+
|
|
20
|
+
## How it works
|
|
21
|
+
|
|
22
|
+
| Column | What happens |
|
|
23
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
24
|
+
| **Ideas** | Backlog of ideas and tasks. Not touched by any agents. |
|
|
25
|
+
| **Planning** | An agent writes a plan into the issue. Comment to revise it. |
|
|
26
|
+
| **Ready** | Moving a card here approves the plan. Cards are picked top-down. |
|
|
27
|
+
| **In Progress** | An agent implements it in its own worktree and opens a PR. |
|
|
28
|
+
| **Review** | Two agent reviews, with fixes. Then you're notified. Comment on the PR and an agent addresses it. CI failures and merge conflicts are fixed too. |
|
|
29
|
+
| **Done** | You merge. |
|
|
30
|
+
|
|
31
|
+
A Bun daemon polls the board and runs one `claude -p` / `codex exec` process per job. GitHub is the only state, so you can restart it any time. Agents never get a GitHub token; the daemon makes every comment, push and PR. Only collaborators with write access can steer agents.
|
|
32
|
+
|
|
33
|
+
## Setup
|
|
34
|
+
|
|
35
|
+
Requires [Bun](https://bun.sh) ≥ 1.3, `gh` logged in, and `claude` or `codex` logged in.
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
gh auth refresh -s project
|
|
39
|
+
cd your-repo
|
|
40
|
+
bunx kanbot init --provider claude # or codex
|
|
41
|
+
kanbot board create # then enable the built-in workflows it prints
|
|
42
|
+
git add -A && git commit -m "chore: add kanbot" && git push
|
|
43
|
+
kanbot doctor
|
|
44
|
+
kanbot run
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use `kanbot run --once --dry-run` to see what it would do. Run one daemon per repo.
|
|
48
|
+
|
|
49
|
+
## Labels
|
|
50
|
+
|
|
51
|
+
| Label | Meaning |
|
|
52
|
+
| ------------------------------------- | ------------------------------------------------- |
|
|
53
|
+
| `technical` | Plan as a technical goal, not a user story |
|
|
54
|
+
| `type:frontend\|backend\|docs\|infra` | Picks the implementer agent (the planner sets it) |
|
|
55
|
+
| `explore:2..4` | Build N alternative approaches as N PRs |
|
|
56
|
+
| `agent-reviewed` | Passed agent review; ready for you |
|
|
57
|
+
| `needs-human` | Agents gave up. Remove it to retry. |
|
|
58
|
+
| `kanbot-review` | Agent review for your own PR, without a card |
|
|
59
|
+
|
|
60
|
+
## Beyond the board
|
|
61
|
+
|
|
62
|
+
- **Capture ideas by chatting.** Ask your agent to file an idea; the `kanbot-idea` skill shapes it and puts it in Ideas. [More](./docs/03-capturing-ideas.md)
|
|
63
|
+
- **Your own PRs.** Label a PR `kanbot-review` for agent review without a card. Commits to `main` that break kanbot PRs get merged in and fixed. [More](./docs/04-outside-the-board.md)
|
|
64
|
+
- **Project memory.** Corrections you give on PRs become facts agents read on every job. [More](./docs/06-skills.md#project-memory)
|
|
65
|
+
- **Copilot review.** Optionally add GitHub Copilot as a cloud reviewer. [More](./docs/09-copilot-review.md)
|
|
66
|
+
|
|
67
|
+
## Customizing
|
|
68
|
+
|
|
69
|
+
- `.kanbot/config.yml`: concurrency, timeouts, notifications, column names.
|
|
70
|
+
- `.kanbot/agents/*.md`: agent definitions. Run `kanbot sync` after editing; don't edit the generated `.claude/` or `.codex/` files.
|
|
71
|
+
- `.agents/skills/`: skills shared by agents. Fill in `project-conventions` first.
|
|
72
|
+
|
|
73
|
+
## Upgrading
|
|
74
|
+
|
|
75
|
+
`bunx kanbot@latest upgrade` updates agents and skills you haven't edited. For edited ones, it writes the new version next to yours as `<file>.kanbot-new` to merge. See [Upgrading](./docs/10-upgrading.md).
|
|
76
|
+
|
|
77
|
+
## Shell completion
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
echo 'source <(kanbot completion)' >> ~/.zshrc # or ~/.bashrc
|
|
81
|
+
kanbot completion --shell fish > ~/.config/fish/completions/kanbot.fish
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The shell is detected from `$SHELL`; pass `--shell zsh|bash|fish` to override. For zsh, `compinit` must run first.
|
|
85
|
+
|
|
86
|
+
## Operating
|
|
87
|
+
|
|
88
|
+
- `kanbot status` shows the board, daemon and failures.
|
|
89
|
+
- `.kanbot/logs/*.jsonl` has every agent transcript.
|
|
90
|
+
|
|
91
|
+
## Limitations
|
|
92
|
+
|
|
93
|
+
- Ready-column order is the project's manual item order, not a view's sort.
|
|
94
|
+
- Workers run as your user. `gh` and `git push` are blocked by command patterns, not missing credentials, so a determined prompt injection could get around them.
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
pnpm install
|
|
100
|
+
bun test
|
|
101
|
+
pnpm check
|
|
102
|
+
pnpm typecheck
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Commits follow [Conventional Commits](https://www.conventionalcommits.org). A `commit-msg` hook and CI lint them.
|
|
106
|
+
|
|
107
|
+
### Releasing
|
|
108
|
+
|
|
109
|
+
1. Merge or push conventional commits to `main`.
|
|
110
|
+
2. A draft release updates with the next version and notes. `feat` bumps minor, `fix` patch. On 0.x, breaking changes bump minor.
|
|
111
|
+
3. Publish the draft on GitHub. This tags the commit, publishes to npm with provenance, and commits the version to `package.json` on `main`.
|
|
112
|
+
|
|
113
|
+
Preview the next release with `pnpm release:preview`. Mark the draft as a pre-release to publish under the `next` tag.
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "kanbot",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Run an autonomous dev team (Claude Code or Codex) from a GitHub Project board",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Mikael Brevik",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mikaelbr/kanbot.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/mikaelbr/kanbot#readme",
|
|
13
|
+
"bugs": "https://github.com/mikaelbr/kanbot/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"github",
|
|
16
|
+
"kanban",
|
|
17
|
+
"agents",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"codex",
|
|
20
|
+
"automation",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"kanbot": "src/cli.ts"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"templates",
|
|
29
|
+
"schema"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"bun": ">=1.3"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"kanbot": "bun src/cli.ts",
|
|
36
|
+
"test": "bun test",
|
|
37
|
+
"check": "biome check .",
|
|
38
|
+
"fix": "biome check --write .",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"schema": "bun -e \"await Bun.write('schema/config.schema.json', (await import('./src/config.ts')).renderConfigSchema())\"",
|
|
41
|
+
"docs:dev": "pnpm --dir site dev",
|
|
42
|
+
"docs:build": "pnpm --dir site build",
|
|
43
|
+
"hashes": "bun scripts/known-hashes.ts",
|
|
44
|
+
"prepublishOnly": "bun run check && bun run typecheck && bun test",
|
|
45
|
+
"prepare": "lefthook install",
|
|
46
|
+
"release:preview": "git-cliff --unreleased --bump --strip all && git-cliff --bumped-version"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"citty": "^0.2.2",
|
|
50
|
+
"node-notifier": "^12.1.0",
|
|
51
|
+
"yaml": "^2.9.1",
|
|
52
|
+
"zod": "^4.6.7"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "^2.6.4",
|
|
56
|
+
"@commitlint/cli": "^21.2.4",
|
|
57
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
58
|
+
"@types/bun": "^1.4.4",
|
|
59
|
+
"git-cliff": "^2.14.2",
|
|
60
|
+
"lefthook": "^2.2.2",
|
|
61
|
+
"typescript": "^7.0.2"
|
|
62
|
+
},
|
|
63
|
+
"commitlint": {
|
|
64
|
+
"extends": [
|
|
65
|
+
"@commitlint/config-conventional"
|
|
66
|
+
],
|
|
67
|
+
"rules": {
|
|
68
|
+
"body-max-line-length": [
|
|
69
|
+
0
|
|
70
|
+
],
|
|
71
|
+
"footer-max-line-length": [
|
|
72
|
+
0
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"provider": {
|
|
6
|
+
"default": "claude",
|
|
7
|
+
"description": "Agent CLI that runs workers.",
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": ["claude", "codex"]
|
|
10
|
+
},
|
|
11
|
+
"repo": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[^/]+\\/[^/]+$",
|
|
14
|
+
"description": "GitHub repo as owner/name."
|
|
15
|
+
},
|
|
16
|
+
"baseBranch": {
|
|
17
|
+
"default": "main",
|
|
18
|
+
"description": "Branch agents work from.",
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"project": {
|
|
22
|
+
"default": null,
|
|
23
|
+
"description": "GitHub Project board. Set by `kanbot board create`.",
|
|
24
|
+
"anyOf": [
|
|
25
|
+
{
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"owner": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"number": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"minimum": -9007199254740991,
|
|
34
|
+
"maximum": 9007199254740991
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["owner", "number"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "null"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"columns": {
|
|
45
|
+
"description": "Names of the board's Status options.",
|
|
46
|
+
"default": {},
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"ideas": {
|
|
50
|
+
"default": "Ideas",
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"planning": {
|
|
54
|
+
"default": "Planning",
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"ready": {
|
|
58
|
+
"default": "Ready",
|
|
59
|
+
"type": "string"
|
|
60
|
+
},
|
|
61
|
+
"inProgress": {
|
|
62
|
+
"default": "In Progress",
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"review": {
|
|
66
|
+
"default": "Review",
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
"done": {
|
|
70
|
+
"default": "Done",
|
|
71
|
+
"type": "string"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"concurrency": {
|
|
76
|
+
"default": 3,
|
|
77
|
+
"description": "Max concurrent agent workers. Each exploratory variant is one worker.",
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"minimum": 1,
|
|
80
|
+
"maximum": 9007199254740991
|
|
81
|
+
},
|
|
82
|
+
"pollSeconds": {
|
|
83
|
+
"default": 60,
|
|
84
|
+
"description": "Seconds between board polls.",
|
|
85
|
+
"type": "integer",
|
|
86
|
+
"minimum": 10,
|
|
87
|
+
"maximum": 9007199254740991
|
|
88
|
+
},
|
|
89
|
+
"priority": {
|
|
90
|
+
"default": ["fix", "review", "plan", "implement"],
|
|
91
|
+
"description": "Order in which waiting jobs get free slots.",
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"enum": ["fix", "review", "plan", "implement"]
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"maxAttempts": {
|
|
99
|
+
"default": 2,
|
|
100
|
+
"description": "Attempts per job before the issue is labeled needs-human.",
|
|
101
|
+
"type": "integer",
|
|
102
|
+
"minimum": 1,
|
|
103
|
+
"maximum": 9007199254740991
|
|
104
|
+
},
|
|
105
|
+
"timeoutMinutes": {
|
|
106
|
+
"description": "Per-job timeout in minutes.",
|
|
107
|
+
"default": {},
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {
|
|
110
|
+
"plan": {
|
|
111
|
+
"default": 20,
|
|
112
|
+
"type": "number"
|
|
113
|
+
},
|
|
114
|
+
"implement": {
|
|
115
|
+
"default": 90,
|
|
116
|
+
"type": "number"
|
|
117
|
+
},
|
|
118
|
+
"review": {
|
|
119
|
+
"default": 30,
|
|
120
|
+
"type": "number"
|
|
121
|
+
},
|
|
122
|
+
"fix": {
|
|
123
|
+
"default": 45,
|
|
124
|
+
"type": "number"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"implementers": {
|
|
129
|
+
"default": {
|
|
130
|
+
"frontend": "implementer-frontend",
|
|
131
|
+
"backend": "implementer-backend",
|
|
132
|
+
"docs": "implementer-docs",
|
|
133
|
+
"infra": "implementer-technical",
|
|
134
|
+
"default": "implementer-backend"
|
|
135
|
+
},
|
|
136
|
+
"description": "Maps a type:<key> label to an agent. `default` is the fallback.",
|
|
137
|
+
"type": "object",
|
|
138
|
+
"propertyNames": {
|
|
139
|
+
"type": "string"
|
|
140
|
+
},
|
|
141
|
+
"additionalProperties": {
|
|
142
|
+
"type": "string"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"review": {
|
|
146
|
+
"default": {},
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"copilot": {
|
|
150
|
+
"default": false,
|
|
151
|
+
"description": "Wait for GitHub Copilot's code review and have the fixer address its comments. Needs a paid Copilot plan; each review uses credits.",
|
|
152
|
+
"type": "boolean"
|
|
153
|
+
},
|
|
154
|
+
"copilotOnPush": {
|
|
155
|
+
"default": false,
|
|
156
|
+
"description": "Have Copilot review again after every push. One review per push.",
|
|
157
|
+
"type": "boolean"
|
|
158
|
+
},
|
|
159
|
+
"copilotWaitMinutes": {
|
|
160
|
+
"default": 15,
|
|
161
|
+
"description": "Minutes to wait for Copilot's review before going on without it.",
|
|
162
|
+
"type": "integer",
|
|
163
|
+
"minimum": 1,
|
|
164
|
+
"maximum": 9007199254740991
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"codex": {
|
|
169
|
+
"default": {},
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"networkAccess": {
|
|
173
|
+
"default": false,
|
|
174
|
+
"description": "Let codex workers reach the network (package installs).",
|
|
175
|
+
"type": "boolean"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"notify": {
|
|
180
|
+
"default": {},
|
|
181
|
+
"type": "object",
|
|
182
|
+
"properties": {
|
|
183
|
+
"desktop": {
|
|
184
|
+
"default": true,
|
|
185
|
+
"description": "Show desktop notifications.",
|
|
186
|
+
"type": "boolean"
|
|
187
|
+
},
|
|
188
|
+
"webhook": {
|
|
189
|
+
"default": null,
|
|
190
|
+
"description": "POSTs {title, message, url} as JSON, e.g. an ntfy.sh topic URL.",
|
|
191
|
+
"anyOf": [
|
|
192
|
+
{
|
|
193
|
+
"type": "string",
|
|
194
|
+
"format": "uri"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"type": "null"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"required": ["repo"],
|
|
205
|
+
"title": "kanbot config"
|
|
206
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { UserError } from "../errors.ts";
|
|
7
|
+
import { RESULT_SCHEMAS } from "../schemas/results.ts";
|
|
8
|
+
|
|
9
|
+
export const TOOL_CAPABILITIES = ["read", "edit", "bash", "web"] as const;
|
|
10
|
+
|
|
11
|
+
const Frontmatter = z.object({
|
|
12
|
+
name: z.string().regex(/^[a-z][a-z0-9-]*$/),
|
|
13
|
+
tier: z.enum(["column", "sub"]),
|
|
14
|
+
description: z.string(),
|
|
15
|
+
models: z
|
|
16
|
+
.object({ claude: z.string().optional(), codex: z.string().optional() })
|
|
17
|
+
.default({}),
|
|
18
|
+
effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
|
|
19
|
+
tools: z.array(z.enum(TOOL_CAPABILITIES)).default(["read"]),
|
|
20
|
+
skills: z.array(z.string()).default([]),
|
|
21
|
+
subagents: z.array(z.string()).default([]),
|
|
22
|
+
output: z
|
|
23
|
+
.enum(Object.keys(RESULT_SCHEMAS) as [keyof typeof RESULT_SCHEMAS])
|
|
24
|
+
.optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type AgentDef = z.infer<typeof Frontmatter> & {
|
|
28
|
+
/** Markdown instructions after the frontmatter. */
|
|
29
|
+
body: string;
|
|
30
|
+
file: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function agentsDir(root: string) {
|
|
34
|
+
return join(root, ".kanbot", "agents");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseAgent(text: string, file: string): AgentDef {
|
|
38
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
39
|
+
if (!m) throw invalid(file, "Missing YAML frontmatter between `---` lines.");
|
|
40
|
+
const fm = Frontmatter.safeParse(parse(m[1] as string));
|
|
41
|
+
if (!fm.success) {
|
|
42
|
+
throw invalid(file, z.prettifyError(fm.error));
|
|
43
|
+
}
|
|
44
|
+
return { ...fm.data, body: (m[2] as string).trim(), file };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function loadAgents(root: string): Promise<AgentDef[]> {
|
|
48
|
+
const dir = agentsDir(root);
|
|
49
|
+
if (!existsSync(dir)) {
|
|
50
|
+
throw new UserError("kanbot agents are missing.", {
|
|
51
|
+
why: "No .kanbot/agents directory in this repo.",
|
|
52
|
+
fix: "kanbot init (or kanbot upgrade to restore missing files)",
|
|
53
|
+
docs: "getting-started#set-up-a-repository",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".md")).sort();
|
|
57
|
+
const agents = await Promise.all(
|
|
58
|
+
files.map(async (f) =>
|
|
59
|
+
parseAgent(await Bun.file(join(dir, f)).text(), join(dir, f)),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
validate(agents);
|
|
63
|
+
return agents;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validate(agents: AgentDef[]) {
|
|
67
|
+
const names = new Set(agents.map((a) => a.name));
|
|
68
|
+
if (names.size !== agents.length) {
|
|
69
|
+
throw new UserError("Duplicate agent names in .kanbot/agents.", {
|
|
70
|
+
why: "Two agent files declare the same `name`.",
|
|
71
|
+
fix: "Give each agent a unique name matching its filename.",
|
|
72
|
+
docs: "agents#frontmatter",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
for (const a of agents) {
|
|
76
|
+
if (!a.file.endsWith(`${a.name}.md`)) {
|
|
77
|
+
throw invalid(a.file, `name "${a.name}" must match the filename.`);
|
|
78
|
+
}
|
|
79
|
+
if (a.tier === "column" && !a.output) {
|
|
80
|
+
throw invalid(a.file, "Column agents must declare an `output` schema.");
|
|
81
|
+
}
|
|
82
|
+
for (const s of a.subagents) {
|
|
83
|
+
const sub = agents.find((x) => x.name === s);
|
|
84
|
+
if (!sub)
|
|
85
|
+
throw invalid(a.file, `Unknown subagent "${s}" in \`subagents\`.`);
|
|
86
|
+
if (sub.tier !== "sub") {
|
|
87
|
+
throw invalid(a.file, `Subagent "${s}" must have \`tier: sub\`.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function invalid(file: string, why: string) {
|
|
94
|
+
return new UserError(`Invalid agent file ${file}`, {
|
|
95
|
+
why,
|
|
96
|
+
fix: "Fix the file, then run `kanbot sync`.",
|
|
97
|
+
docs: "agents#frontmatter",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getAgent(agents: AgentDef[], name: string): AgentDef {
|
|
102
|
+
const a = agents.find((x) => x.name === name);
|
|
103
|
+
if (!a) throw new Error(`No agent named "${name}" in .kanbot/agents`);
|
|
104
|
+
return a;
|
|
105
|
+
}
|