- Add a focused GitHub badge poller with shared rate-limit, freshness, and cleanup handling - Wire a dedicated badge websocket server and routes to stream PR and issue badge snapshots - Add a shared useBadgeWebSocket hook and update TaskCard to subscribe only when visible while preserving partial badge state - Cover polling, websocket, and task card flows with tests and document the realtime badge channel
240 lines
10 KiB
Markdown
240 lines
10 KiB
Markdown
# Project Guidelines
|
||
|
||
## Finalizing changes
|
||
|
||
When making changes that affect published packages, create a changeset file:
|
||
|
||
```bash
|
||
cat > .changeset/<short-description>.md << 'EOF'
|
||
---
|
||
"@dustinbyrne/kb": patch
|
||
---
|
||
|
||
Short description of the change.
|
||
EOF
|
||
```
|
||
|
||
Bump types:
|
||
|
||
- **patch**: bug fixes, internal changes
|
||
- **minor**: new features, new CLI commands, new tools
|
||
- **major**: breaking changes
|
||
|
||
Include the changeset file in the same commit as the code change. The filename should be a short kebab-case description (e.g. `fix-merge-conflict.md`, `add-retry-button.md`).
|
||
|
||
Only create changesets for changes that affect the published `@dustinbyrne/kb` package — user-facing features, bug fixes, CLI changes, tool changes. Do NOT create changesets for internal docs (AGENTS.md, README), CI config, or refactors that don't change behavior.
|
||
|
||
## Package Structure
|
||
|
||
- `@kb/core` — domain model, task store (private, not published)
|
||
- `@kb/dashboard` — web UI + API server (private, not published)
|
||
- `@kb/engine` — AI agents: triage, executor, reviewer, merger, scheduler (private, not published)
|
||
- `@dustinbyrne/kb` — CLI + pi extension (published to npm)
|
||
|
||
Only `@dustinbyrne/kb` is published. The others are internal workspace packages.
|
||
|
||
## Testing
|
||
|
||
```bash
|
||
pnpm test # run all tests
|
||
pnpm build # build all packages
|
||
```
|
||
|
||
Tests are required. Typechecks and manual verification are not substitutes for real tests with assertions.
|
||
|
||
## Pi Extension (`packages/cli/src/extension.ts`)
|
||
|
||
The pi extension provides tools and a `/kb` command for interacting with kb from within a pi session. It ships as part of `@dustinbyrne/kb` — one `pi install` gives you both the CLI and the extension.
|
||
|
||
Update it when:
|
||
|
||
- **CLI commands change** — if `kb task create`, `kb task list`, `kb task show`, `kb task attach`, `kb task pause`, or `kb task unpause` change their behavior, flags, or output, update the corresponding tool in `packages/cli/src/extension.ts`.
|
||
- **Task store API changes** — the extension calls `TaskStore` directly (`createTask`, `listTasks`, `getTask`, `addAttachment`, `pauseTask`). If these methods change signature or behavior, update the extension.
|
||
- **New user-facing features** — if a new CLI command is added that the chat agent should be able to use (task creation, status checking, automation control), add a tool for it.
|
||
|
||
**Don't** add tools for engine-internal operations (move, step updates, logging, merge) — those are handled by the engine's own agents.
|
||
|
||
The extension has no skills — tool descriptions, `promptSnippet`, and `promptGuidelines` give the LLM everything it needs.
|
||
|
||
## Dashboard badge WebSockets
|
||
|
||
GitHub PR and issue badges in the dashboard now have a dedicated real-time WebSocket channel at `/api/ws`.
|
||
|
||
### Frontend hook: `packages/dashboard/app/hooks/useBadgeWebSocket.ts`
|
||
|
||
Use `useBadgeWebSocket()` when a UI surface needs live badge snapshots for specific tasks.
|
||
|
||
- The hook uses a **shared singleton socket** so multiple `TaskCard` instances do not open duplicate WebSocket connections.
|
||
- Subscribe with `subscribeToBadge(taskId)` only when the card is visible and already has `prInfo` and/or `issueInfo`.
|
||
- Always pair subscriptions with `unsubscribeFromBadge(taskId)` on unmount or when the card leaves the viewport.
|
||
- Treat websocket payloads as **timestamped badge snapshots**. Merge them with task data using freshness comparisons so stale cached websocket data does not override newer SSE/task state.
|
||
- Preserve omitted fields on partial updates; only treat explicit `null` payloads as badge clears.
|
||
|
||
### Server-side expectations
|
||
|
||
- `/api/ws` is badge-specific; do **not** reuse it for general task updates.
|
||
- Badge broadcasts should contain only `prInfo` / `issueInfo` snapshot data, never full task objects.
|
||
- WebSocket subscription changes drive the focused GitHub poller so only actively viewed badge-linked tasks are polled.
|
||
- Keep the existing 5-minute refresh endpoints as a fallback path when websocket delivery is unavailable.
|
||
|
||
## Git
|
||
|
||
- Commit messages: `feat(KB-XXX):`, `fix(KB-XXX):`, `test(KB-XXX):`
|
||
- One commit per step (not per file change)
|
||
- Always include the task ID prefix
|
||
|
||
## Settings
|
||
|
||
The following settings are available in the kb configuration (stored in `.kb/config.json`):
|
||
|
||
### `autoResolveConflicts` (default: `true`)
|
||
|
||
When enabled, the auto-merge system will intelligently resolve common merge conflict patterns without requiring manual intervention:
|
||
|
||
- **Lock files** (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `Gemfile.lock`, etc.) — automatically resolved using the current branch's version ("ours")
|
||
- **Generated files** (`*.gen.ts`, `dist/*`, `coverage/*`, etc.) — automatically resolved using the branch's fresh generation ("theirs")
|
||
- **Trivial conflicts** (whitespace-only differences) — automatically resolved
|
||
|
||
When a merge encounters conflicts and the AI agent fails to resolve them on the first attempt, the system implements a 3-attempt retry logic with escalating strategies:
|
||
|
||
1. **Attempt 1**: Standard merge with AI agent using full context
|
||
2. **Attempt 2**: Auto-resolve lock/generated files, then retry AI with simplified context
|
||
3. **Attempt 3**: Use `git merge -X theirs` strategy for remaining conflicts
|
||
|
||
If all 3 attempts fail, the task remains in "in-review" for manual resolution. The per-task retry counter (`mergeRetries`) tracks how many attempts have been made.
|
||
|
||
To disable automatic conflict resolution and require manual intervention for all conflicts:
|
||
|
||
```json
|
||
{
|
||
"settings": {
|
||
"autoResolveConflicts": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### `smartConflictResolution` (default: `true`)
|
||
|
||
Alias for `autoResolveConflicts`. When enabled, enables automatic resolution of:
|
||
- Lock files using "ours" strategy (keep current branch's version)
|
||
- Generated files using "theirs" strategy (keep branch's fresh generation)
|
||
- Trivial whitespace conflicts via `git diff-tree -w`
|
||
|
||
This setting is preferred for new configurations. If both settings are present, `smartConflictResolution` takes precedence.
|
||
|
||
### `requirePlanApproval` (default: `false`)
|
||
|
||
When enabled, AI-generated task specifications require manual approval before the task can move from "triage" to "todo".
|
||
|
||
**How it works:**
|
||
- After the AI reviewer approves a specification, instead of automatically moving to "todo", the task remains in "triage" with status `"awaiting-approval"`
|
||
- Users can review the PROMPT.md in the task detail modal
|
||
- Users can click "Approve Plan" to move the task to "todo" and begin execution
|
||
- Users can click "Reject Plan" to discard the specification and trigger regeneration
|
||
|
||
To enable manual plan approval:
|
||
|
||
```json
|
||
{
|
||
"settings": {
|
||
"requirePlanApproval": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### `ntfyEnabled` (default: `false`)
|
||
|
||
When true, enables ntfy.sh push notifications for task completion and failures.
|
||
|
||
**Notification events:**
|
||
- Task moves to "in-review" — "Task completed — ready for review"
|
||
- Task moves to "done" — "Task merged to main"
|
||
- Task status becomes "failed" — "Task failed" (high priority)
|
||
|
||
**Configuration:**
|
||
1. Go to https://ntfy.sh and pick a unique topic name (or self-host ntfy)
|
||
2. Open dashboard Settings → Notifications
|
||
3. Enable notifications and enter your topic name
|
||
4. Install the ntfy app on your phone/desktop and subscribe to your topic
|
||
|
||
```json
|
||
{
|
||
"settings": {
|
||
"ntfyEnabled": true,
|
||
"ntfyTopic": "my-kb-notifications"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Notes:**
|
||
- No authentication required for public topics (keep it simple)
|
||
- Topic must be 1–64 alphanumeric/hyphen/underscore characters
|
||
- Notifications are best-effort: failures are logged but don't block task execution
|
||
|
||
### `worktreeNaming` (default: `"random"`)
|
||
|
||
Controls how worktree directory names are generated when `recycleWorktrees` is NOT enabled. This setting only affects fresh worktrees (not pooled/recycled ones).
|
||
|
||
**Valid values:**
|
||
- `"random"` — Human-friendly random names like `swift-falcon`, `calm-river` (default)
|
||
- `"task-id"` — Use the task ID as the directory name, e.g., `kb-042`
|
||
- `"task-title"` — Use a slugified version of the task title, e.g., `fix-login-bug`
|
||
|
||
**Example:**
|
||
```json
|
||
{
|
||
"settings": {
|
||
"worktreeNaming": "task-id"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Notes:**
|
||
- This setting has no effect when `recycleWorktrees` is enabled (pooled worktrees retain their existing names)
|
||
- Task branches are always named `kb/{task-id}` regardless of this setting
|
||
- When using `"task-title"` mode, special characters are replaced with hyphens and the result is lowercased
|
||
|
||
## Per-Task Model Overrides
|
||
|
||
The kb dashboard allows overriding the global AI model selection on a per-task basis. This enables using different models for different types of work without changing global settings.
|
||
|
||
### How It Works
|
||
|
||
Each task can optionally specify:
|
||
- **Executor Model**: The AI model used to implement the task (executor agent)
|
||
- **Validator Model**: The AI model used to review code and plans (reviewer agent)
|
||
|
||
When not specified, tasks use the global default settings (`defaultProvider`/`defaultModelId`).
|
||
|
||
### Setting Per-Task Models
|
||
|
||
In the dashboard, open any task's detail modal and click the **Model** tab. Select models from the dropdown:
|
||
- **Use default**: Uses the global default model (shown when no override is set)
|
||
- **Specific model**: Override with a chosen provider/model combination
|
||
|
||
Both provider and model ID must be selected together — selecting only one is treated as "not set" and falls back to defaults.
|
||
|
||
### Storage
|
||
|
||
Per-task model overrides are stored in the task's `task.json`:
|
||
```json
|
||
{
|
||
"modelProvider": "anthropic",
|
||
"modelId": "claude-sonnet-4-5",
|
||
"validatorModelProvider": "openai",
|
||
"validatorModelId": "gpt-4o"
|
||
}
|
||
```
|
||
|
||
To clear overrides, select "Use default" for both fields and save.
|
||
|
||
### Engine Behavior
|
||
|
||
- **Executor**: When both `modelProvider` and `modelId` are set on a task, the executor uses those instead of global settings when creating the agent session.
|
||
- **Reviewer**: When both `validatorModelProvider` and `validatorModelId` are set, the reviewer uses those instead of global settings. The validator model is passed via `ReviewOptions` to `reviewStep()`.
|
||
|
||
### Limitations
|
||
|
||
- Triage (task specification) always uses global defaults — per-task overrides apply only to execution and review
|
||
- Both provider and modelId must be set together; partial configuration falls back to defaults
|