14 KiB
14 KiB
Plan — Improve Kandev
Implementation plan for the
improve-kandevspec. Workflow: TDD (Red → Green → Refactor) per.claude/skills/tdd/SKILL.md.
1. Overview
Deliver an in-app entry point for filing bug reports and feature suggestions that produces a real kandev task running on the user's own agent. The task follows a hidden three-step workflow (improve → test → pr) and optionally attaches a logs bundle written to a temp folder.
Goals:
- Capture recent backend and frontend logs in bounded ring buffers.
- Add a hidden workflow
improve-kandevwith three manually-advanced steps. - Add an Improve Kandev settings entry that opens a tailored task-create
dialog with the kandev repo,
mainbranch, and a starter description pre-filled. - Filter hidden workflows out of management UI and the workflow picker.
Out of scope (deferred):
- Automatic step transitions, rate limiting, log redaction, fork management.
- A generic feedback inbox.
2. Prerequisites
- No external services. Uses existing
repoclone.Cloner,worktree.Manager,task.Service,orchestrator.Service, and the workflow YAML loader. - New temp directory layout:
os.MkdirTemp("", "kandev-improve-*"). - Workflows YAML loader at
apps/backend/config/workflows/loader.goalready supports loading additional templates fromapps/backend/config/workflows/. - The
Workflowmodel gains ahiddenboolean column. SQLite migration required.
3. Implementation Steps
Step 1 — Backend log ring buffer (RED → GREEN)
- File (new):
apps/backend/internal/common/logger/buffer/buffer.goRingBufferstruct: thread-safe, FIFO, capped at 2000 entries.Entrystruct:{Timestamp, Level, Logger, Message, Fields, Caller}.Snapshot()returns a copy of current entries.WriteSynceradapter (implementszapcore.WriteSyncer) that decodes the line emitted by the configured zap encoder back into anEntry. Prefer using a small customzapcore.Corethat hooks into entry creation rather than parsing strings —zap.Hooksreturns a wrapped core.
- File (modify):
apps/backend/internal/common/logger/logger.go- In
NewLogger, wrap the core withzap.Hooks(...)(or a small customzapcore.Coretee) that pushes each entry into the ring buffer. - Expose
(l *Logger) BufferSnapshot() []buffer.Entry.
- In
- File (new):
apps/backend/internal/common/logger/buffer/buffer_test.go- FIFO eviction at capacity.
- Concurrent writes are serialized.
Snapshot()returns an isolated copy.
Step 2 — Frontend log ring buffer + console interceptor (RED → GREEN)
- File (new):
apps/web/lib/logger/buffer.ts- In-memory ring buffer (cap 500).
Entryshape mirrors backend (timestamp,level,source,message,args). snapshot(),clear()exports.
- In-memory ring buffer (cap 500).
- File (new):
apps/web/lib/logger/intercept.tsinstallConsoleInterceptor()wrapsconsole.debug/info/warn/errorso the original behavior is preserved and each call also pushes to the ring.- Hooks
window.onerrorandwindow.addEventListener("unhandledrejection"). - Idempotent (no-op on repeat calls).
- File (modify):
apps/web/app/layout.tsx(or the closest top-level client boot) — callinstallConsoleInterceptor()once on the client. - Files (new tests):
apps/web/lib/logger/buffer.test.ts,apps/web/lib/logger/intercept.test.ts.
Step 3 — hidden flag on Workflow (RED → GREEN)
- File (modify):
apps/backend/internal/task/models/models.go— addHidden booltoWorkflow. - File (new):
apps/backend/internal/task/repository/migrations/NNNN_workflow_hidden.sql(use the existing migrations pattern; addALTER TABLE workflows ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0). - Files (modify): workflow repository read/write paths — include
hiddeninSELECT/INSERT/UPDATE. - File (modify): workflow service
ListWorkflows— accept anIncludeHidden booloption (default false). Existing callers pass false. - File (modify): workflow API DTOs and the OpenAPI/types generation if
applicable; ensure the
hiddenfield flows to the frontend. - Files (modify, frontend):
apps/web/lib/state/slices/settings/...(or wherever workflows hydrate) — preservehiddenfield on the type.apps/web/app/settings/workspace/workspace-workflows-client.tsx— filterworkflow.hidden !== truebefore renderingWorkflowList.apps/web/components/workflow-selector-row.tsx— same filter on the picker list.
- Tests: repository test for
hiddenround-trip; one frontend store test ensuring hidden workflows are excluded from the picker selector.
Step 4 — improve-kandev workflow YAML (GREEN)
- File (new):
apps/backend/config/workflows/improve-kandev.yml- Three steps with
is_start_stepon improve,allow_manual_move: trueon all,on_enter: auto_start_agent, and per-stepprompttext matching the spec. Top-level YAML does not yet supporthidden; add support:
- Three steps with
- File (modify):
apps/backend/config/workflows/loader.go— extendtemplateYAMLwithHidden booland propagate when materializing the template. When the loader instantiates a workflow from a template marked hidden, the resulting workflow is created withHidden: true. - File (modify): workflow template materialization path (the place that
creates a
Workflowrow from aWorkflowTemplate) — copyIsSystem/Hiddeninto the resulting workflow. - Test: a loader test that parses
improve-kandev.ymland asserts thehiddenflag is propagated.
Step 5 — Bootstrap endpoint (RED → GREEN)
- File (new):
apps/backend/internal/system/handlers/improve_kandev.goPOST /api/v1/system/improve-kandev/bootstrap- Ensures kandev repo is cloned via
repoclone.Cloner.EnsureCloned("https://github.com/kdlbs/kandev", "kdlbs", "kandev"). - Ensures a
Repositoryrow exists in the user's active workspace pointing at the local path; creates one on first call, reuses thereafter (idempotent). - Ensures the hidden
improve-kandevworkflow exists in the workspace (creates it from the template on first call). - Creates a temp directory via
os.MkdirTemp("", "kandev-improve-*")and writesmetadata.json(version, OS/arch, health snapshot, captured_at) andbackend.log(zap ring buffer snapshot, formatted as plain text, one entry per line). - Response:
{repository_id, workflow_id, branch: "main", bundle_dir, bundle_files: {metadata, backend_log, frontend_log_target}}.
- Ensures kandev repo is cloned via
POST /api/v1/system/improve-kandev/bundle/frontend-log- Body:
{bundle_dir, entries: FrontendLogEntry[]}. - Writes plain-text
frontend.loginto the bundle dir. - Validates
bundle_dirmatches the temp pattern to refuse arbitrary writes.
- Body:
- File (modify):
cmd/kandev/helpers.go— register the new routes. - File (new tests):
improve_kandev_test.go- First call clones + creates repo row + workflow row; second call is idempotent.
- Bundle dir validation rejects paths outside the temp pattern.
Step 6 — TaskCreateDialog extension props (RED → GREEN)
- File (modify):
apps/web/components/task-create-dialog.tsx- Add two optional props to
TaskCreateDialogProps:extraFormSlot?: React.ReactNode— rendered inside the form, directly below the description textarea.transformDescriptionBeforeSubmit?: (desc: string) => Promise<string> | string— invoked once with the current description text just before the create-task API call; the returned string is used as the description.
- Submit handler awaits the transform, swaps the description, then proceeds.
- Add two optional props to
- File (new test):
task-create-dialog-extra-slot.test.tsx- Rendering with a slot mounts the node.
- The transform is awaited and its result is sent in the API payload.
Step 7 — ImproveKandevDialog wrapper (GREEN)
- File (new):
apps/web/components/improve-kandev-dialog.tsx- Props:
{open, onOpenChange, onSuccess?}. - On mount: calls bootstrap endpoint to obtain
repository_id,workflow_id,bundle_dir,bundle_files. - Owns local state:
includeLogs: boolean(default true),logsPreviewOpen: boolean. - Renders
<TaskCreateDialog>with:workspaceId= active workspace id from store.workflowId= bootstrap response.initialValues = { title: "", description: STARTER_TEMPLATE, repositoryId, branch: "main" }.extraFormSlot= aCardcontaining the dialog's explanatory header copy ("Help improve kandev — flow: Improve → Test → PR — contribute…") plus the "Include recent logs" checkbox with an expandable preview.transformDescriptionBeforeSubmit= whenincludeLogsis true, sends the current ring-buffer snapshot to the frontend-log endpoint to writefrontend.log, then appends:--- Context bundle for the agent: - {metadata path} - {backend.log path} - {frontend.log path}
- Props:
- File (new test):
improve-kandev-dialog.test.tsx- Bootstrap is called once on open.
- With logs on, the description sent to create-task contains all three bundle paths.
- With logs off, no bundle reference is appended; the frontend-log endpoint is not called.
Step 8 — Top-bar entry point (GREEN)
- File (modify):
apps/web/components/kanban/kanban-header.tsx- Add a new
ImproveKandevTopbarButtoncomponent next toGitHubTopbarButtonandJiraTopbarButton(inside the sameTooltipProvider), following the same icon-onlyTooltip+Button variant="outline" size="icon"pattern. - Icon:
IconBug(or similar, e.g.IconSparkles) from@tabler/icons-react. - Tooltip text: "Improve kandev".
- Click handler toggles a
useStateboolean that mounts<ImproveKandevDialog open={…} onOpenChange={…} onSuccess={…} />. - On
onSuccess, navigate to/t/{taskId}so the user lands on the running task.
- Add a new
- File (modify):
apps/web/components/kanban/kanban-header-mobile.tsx- Add the same button to the mobile header for parity (icon-only, visible alongside the existing icon controls).
- File (new test):
apps/web/components/kanban/kanban-header.test.tsx(or extend an existing test) — assert the button renders with its tooltip and that clicking it opens the dialog.
Step 9 — Pre-flight gh auth check (GREEN)
- File (modify):
improve-kandev-dialog.tsx- On mount, call
/api/v1/system/health; if the GitHub-auth checker reports failure, render a blocking banner inside the dialog (replacing the form) explaining the issue and link to the relevant settings. - Disable submit while the banner is visible.
- On mount, call
- File (new test): extends the dialog test to assert that an unhealthy
gh authresult blocks submission.
Step 10 — Verify
- From
apps/:pnpm --filter @kandev/web test -- loggerpnpm --filter @kandev/web test -- task-create-dialog-extra-slotpnpm --filter @kandev/web test -- improve-kandev-dialog
- From
apps/backend:make test
- From repo root:
make fmtthenmake typecheck test lintper AGENTS.md.
4. File Changes Summary
Created (backend):
internal/common/logger/buffer/buffer.go(+ test).internal/system/handlers/improve_kandev.go(+ test).config/workflows/improve-kandev.yml.- Migration file for
workflows.hiddencolumn.
Modified (backend):
internal/common/logger/logger.go— wire ring buffer.internal/task/models/models.go—Hiddenfield onWorkflow.internal/task/repository/...— read/writehidden.internal/workflow/service/...— filter hidden in list calls.config/workflows/loader.go— propagatehiddenfrom YAML.cmd/kandev/helpers.go— register new routes.
Created (frontend):
lib/logger/buffer.ts+intercept.ts(+ tests).lib/api/domains/improve-kandev-api.ts.components/improve-kandev-dialog.tsx(+ test).
Modified (frontend):
app/layout.tsx— install console interceptor.components/task-create-dialog.tsx—extraFormSlot+transformDescriptionBeforeSubmitprops.components/workflow-selector-row.tsx— filter hidden workflows.app/settings/workspace/workspace-workflows-client.tsx— filter hidden.components/kanban/kanban-header.tsx—ImproveKandevTopbarButtonwith tooltip; mounts<ImproveKandevDialog>and routes to/t/{taskId}on success.components/kanban/kanban-header-mobile.tsx— same button for mobile.- workflow store types/hydration to keep the
hiddenfield.
Deleted: none.
5. Testing Strategy
Backend (Go, *_test.go):
buffer_test.go: FIFO eviction, concurrency, snapshot isolation.- Workflow loader test:
improve-kandev.ymlparses withhidden: true. - Workflow repository test:
hiddenround-trips through SQLite. - Workflow service test:
ListWorkflowsfilters hidden by default. - Improve-kandev handler test: bootstrap is idempotent; bundle-dir validation rejects paths outside the temp pattern.
Frontend (vitest):
- Logger buffer + interceptor tests.
- TaskCreateDialog extra-slot + transform-before-submit tests.
- ImproveKandevDialog dialog tests (bootstrap, logs on/off, gh auth gating).
Manual smoke:
- Start kandev. Open Settings → General → Improve kandev.
- Submit with logs on; verify the new task contains a bundle reference.
- Watch the agent run on Improve; move to Test; verify URLs appear.
- Move to PR; verify a PR is opened against
main.
6. Rollback Plan
- Frontend changes are additive (a new page, a new dialog, two optional dialog props). Reverting the merge restores the previous behavior.
- Backend
workflows.hiddencolumn isDEFAULT 0; existing rows are unaffected. The migration is reversible byALTER TABLE … DROP COLUMN. - The
improve-kandevworkflow YAML is only instantiated on first use of the feature; reverting before any user invokes it leaves no rows behind.
7. Estimated Effort
- Complexity: medium-large (cross-cutting: backend log infra, schema change, workflow loader change, dialog extension, new settings page).
- Time: ~2 days including TDD cycles, manual smoke, and CI fixups.