Merge branch 'main' into cw/account-ui-conflict-1772803526476

# Conflicts:
#	apps/server/trpc/router.test.ts
#	docs/server-api.md
This commit is contained in:
Lukas May
2026-03-06 14:27:38 +01:00
117 changed files with 10348 additions and 862 deletions

View File

@@ -11,7 +11,7 @@
| `process-manager.ts` | `AgentProcessManager` — worktree creation, command building, detached spawn |
| `output-handler.ts` | `OutputHandler` — JSONL stream parsing, completion detection, proposal creation, task dedup, task dependency persistence |
| `file-tailer.ts` | `FileTailer` — watches output files, fires parser + raw content callbacks |
| `file-io.ts` | Input/output file I/O: frontmatter writing, signal.json reading, tiptap conversion. Output files support `action` field (create/update/delete) for chat mode CRUD. |
| `file-io.ts` | Input/output file I/O: frontmatter writing, signal.json reading, tiptap conversion. Output files support `action` field (create/update/delete) for chat mode CRUD. Includes `writeErrandManifest()` for errand agent input files. |
| `markdown-to-tiptap.ts` | Markdown to Tiptap JSON conversion using MarkdownManager |
| `index.ts` | Public exports, `ClaudeAgentManager` deprecated alias |
@@ -24,15 +24,15 @@
| `accounts/` | Account discovery, config dir setup, credential management, usage API |
| `credentials/` | `AccountCredentialManager` — credential injection per account |
| `lifecycle/` | `LifecycleController` — retry policy, signal recovery, missing signal instructions |
| `prompts/` | Mode-specific prompt builders (execute, discuss, plan, detail, refine, chat) + shared blocks (test integrity, deviation rules, git workflow, session startup, progress tracking) + inter-agent communication instructions |
| `prompts/` | Mode-specific prompt builders (execute, discuss, plan, detail, refine, chat, conflict-resolution, errand) + shared blocks (test integrity, deviation rules, git workflow, session startup, progress tracking) + inter-agent communication instructions. Conflict-resolution uses a minimal inline startup (pwd, git status, CLAUDE.md) instead of the full `SESSION_STARTUP`/`CONTEXT_MANAGEMENT` blocks. |
## Key Flows
### Spawning an Agent
1. **tRPC procedure** calls `agentManager.spawn(options)`
2. Manager generates alias (adjective-animal), creates DB record
3. `AgentProcessManager.createWorktree()` — creates git worktree at `.cw-worktrees/agent/<alias>/`
2. Manager generates alias (adjective-animal), creates DB record. Appends inter-agent communication and preview instructions unless `skipPromptExtras: true` (used by conflict-resolution agents to keep prompts lean).
3. `AgentProcessManager.createProjectWorktrees()` — creates git worktrees at `agent-workdirs/<alias>/<project>/`. After creation, each project subdirectory is verified to exist; missing worktrees throw immediately to prevent agents running in the wrong directory.
4. `file-io.writeInputFiles()` — writes `.cw/input/` with assignment files (initiative, pages, phase, task) and read-only context dirs (`context/phases/`, `context/tasks/`)
5. Provider config builds spawn command via `buildSpawnCommand()`
6. `spawnDetached()` — launches detached child process with file output redirection
@@ -115,6 +115,30 @@ cw account add --token <token> --email user@example.com
Stored as `credentials: {"claudeAiOauth":{"accessToken":"<token>"}}` and `configJson: {"hasCompletedOnboarding":true}`.
## Errand Agent Support
### `sendUserMessage(agentId, message)`
Delivers a user message directly to a running or idle errand agent without going through the conversations table. Used by the `errand.sendMessage` tRPC procedure.
**Steps**: look up agent → validate status (`running`|`idle`) → validate `sessionId` → clear signal.json → update status to `running` → build resume command → stop active tailer/poll → spawn detached → start polling.
**Key difference from `resumeForConversation`**: no `conversationResumeLocks`, no conversations table entry, raw message passed as resume prompt.
### `writeErrandManifest(options)`
Writes errand input files to `<agentWorkdir>/.cw/input/`:
- `errand.md` — YAML frontmatter with `id`, `description`, `branch`, `project`
- `manifest.json``{ errandId, agentId, agentName, mode: "errand" }` (no `files`/`contextFiles` arrays)
- `expected-pwd.txt` — the agent workdir path
Written in order: `errand.md` first, `manifest.json` last (same discipline as `writeInputFiles`).
### `buildErrandPrompt(description)`
Builds the initial prompt for errand agents. Exported from `prompts/errand.ts` and re-exported from `prompts/index.ts`. The prompt instructs the agent to make only the changes needed for the description and write `signal.json` when done.
## Auto-Resume for Conversations
When Agent A asks Agent B a question via `cw ask` and Agent B is idle, the conversation router automatically resumes Agent B's session. This mirrors the `resumeForCommit()` pattern.
@@ -153,9 +177,10 @@ Agent output is persisted to `agent_log_chunks` table and drives all live stream
- DB insert → `agent:output` event emission (single source of truth for UI)
- No FK to agents — survives agent deletion
- Session tracking: spawn=1, resume=previousMax+1
- Read path (`getAgentOutput` tRPC): concatenates all DB chunks (no file fallback)
- Live path (`onAgentOutput` subscription): listens for `agent:output` events
- Frontend: initial query loads from DB, subscription accumulates raw JSONL, both parsed via `parseAgentOutput()`
- Read path (`getAgentOutput` tRPC): returns timestamped chunks `{ content, createdAt }[]` from DB
- Live path (`onAgentOutput` subscription): listens for `agent:output` events (client stamps with `Date.now()`)
- Frontend: initial query loads timestamped chunks, subscription accumulates live chunks, both parsed via `parseAgentOutput()` which accepts `TimestampedChunk[]`
- Timestamps displayed inline (HH:MM:SS) on text, tool_call, system, and session_end messages
## Inter-Agent Communication
@@ -236,7 +261,7 @@ All prompts follow a consistent tag ordering:
|------|------|--------------------|
| **execute** | `execute.ts` | `<task>`, `<execution_protocol>`, `<anti_patterns>`, `<scope_rules>` |
| **plan** | `plan.ts` | `<phase_design>`, `<dependencies>`, `<file_ownership>`, `<specificity>`, `<existing_context>` |
| **detail** | `detail.ts` | `<task_body_requirements>`, `<file_ownership>`, `<task_sizing>`, `<checkpoint_tasks>`, `<existing_context>` |
| **detail** | `detail.ts` | `<task_body_requirements>`, `<file_ownership>`, `<task_sizing>`, `<existing_context>` |
| **discuss** | `discuss.ts` | `<analysis_method>`, `<question_quality>`, `<decision_quality>`, `<question_categories>`, `<rules>` |
| **refine** | `refine.ts` | `<improvement_priorities>`, `<rules>` |
| **chat** | `chat.ts` | `<chat_history>`, `<instruction>` — iterative refinement loop, uses action field (create/update/delete) in output files, signals "questions" after each change to stay alive |

View File

@@ -123,6 +123,7 @@ All three commands output JSON for programmatic agent consumption.
| `list` | Show accounts with exhaustion status |
| `remove <id>` | Remove account |
| `refresh` | Clear expired exhaustion markers |
| `extract [--email <email>]` | Extract current Claude credentials as JSON (no server required) |
## Server Wiring

View File

@@ -5,7 +5,7 @@ This project uses [drizzle-kit](https://orm.drizzle.team/kit-docs/overview) for
## Overview
- **Schema definition:** `apps/server/db/schema.ts` (drizzle-orm table definitions)
- **Migration output:** `apps/server/drizzle/` directory (SQL files + meta journal)
- **Migration output:** `apps/server/drizzle/` directory (SQL files + `meta/_journal.json` + `meta/NNNN_snapshot.json`)
- **Config:** `drizzle.config.ts`
- **Runtime migrator:** `apps/server/db/ensure-schema.ts` (calls `drizzle-orm/better-sqlite3/migrator`)
@@ -13,6 +13,8 @@ This project uses [drizzle-kit](https://orm.drizzle.team/kit-docs/overview) for
On every server startup, `ensureSchema(db)` runs all pending migrations from the `apps/server/drizzle/` folder. Drizzle tracks applied migrations in a `__drizzle_migrations` table so only new migrations are applied. This is safe to call repeatedly.
The migrator discovers migrations via `apps/server/drizzle/meta/_journal.json`**not** by scanning the filesystem.
## Workflow
### Making schema changes
@@ -23,7 +25,17 @@ On every server startup, `ensureSchema(db)` runs all pending migrations from the
npx drizzle-kit generate
```
3. Review the generated SQL in `apps/server/drizzle/NNNN_*.sql`
4. Commit the migration file along with your schema change
4. Verify multi-statement migrations use `--> statement-breakpoint` between statements (required by better-sqlite3 which only allows one statement per `prepare()` call)
5. Commit the migration file, snapshot, and journal update together
### Important: statement breakpoints
better-sqlite3 rejects SQL with multiple statements in a single `prepare()` call. Drizzle-kit splits on `--> statement-breakpoint`. If you hand-write or edit a migration with multiple statements, append `--> statement-breakpoint` to the end of each statement line (before the next statement):
```sql
ALTER TABLE foo ADD COLUMN bar TEXT;--> statement-breakpoint
CREATE INDEX foo_bar_idx ON foo(bar);
```
### Applying migrations
@@ -31,20 +43,15 @@ Migrations are applied automatically on server startup. No manual step needed.
For tests, the same `ensureSchema()` function is called on in-memory SQLite databases in `apps/server/db/repositories/drizzle/test-helpers.ts`.
### Checking migration status
## History
```bash
# See what drizzle-kit would generate (dry run)
npx drizzle-kit generate --dry-run
# Open drizzle studio to inspect the database
npx drizzle-kit studio
```
Migrations 00000007 were generated by `drizzle-kit generate`. Migrations 00080032 were hand-written (the snapshots fell out of sync). A schema-derived snapshot was restored at 0032, so `drizzle-kit generate` works normally from that point forward.
## Rules
- **Never hand-write migration SQL.** Always use `drizzle-kit generate` from the schema.
- **Use `drizzle-kit generate`** for new migrations. It reads schema.ts, diffs against the last snapshot, and generates both SQL + snapshot automatically.
- **Never use raw CREATE TABLE statements** for schema initialization. The migration system handles this.
- **Always commit migration files.** They are the source of truth for database evolution.
- **Migration files are immutable.** Once committed, never edit them. Make a new migration instead.
- **Test with `npx vitest run`** after generating migrations to verify they work with in-memory databases.
- **Keep schema.ts in sync.** The schema file is the source of truth for TypeScript types; migrations are the source of truth for database DDL. Both must reflect the same structure.
- **Test with `npm test`** after generating migrations to verify they work with in-memory databases.

View File

@@ -5,8 +5,8 @@
## Architecture
- **Schema**: `apps/server/db/schema.ts` — all tables, columns, relations
- **Ports** (interfaces): `apps/server/db/repositories/*.ts` — 13 repository interfaces
- **Adapters** (implementations): `apps/server/db/repositories/drizzle/*.ts` — 13 Drizzle adapters
- **Ports** (interfaces): `apps/server/db/repositories/*.ts` — 14 repository interfaces
- **Adapters** (implementations): `apps/server/db/repositories/drizzle/*.ts` — 14 Drizzle adapters
- **Barrel exports**: `apps/server/db/index.ts` re-exports everything
All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.returning()` for atomic reads after writes.
@@ -29,7 +29,8 @@ All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.r
| initiativeId | text FK → initiatives (cascade) | |
| name | text NOT NULL | |
| content | text nullable | Tiptap JSON |
| status | text enum | 'pending' \| 'approved' \| 'in_progress' \| 'completed' \| 'blocked' |
| status | text enum | 'pending' \| 'approved' \| 'in_progress' \| 'completed' \| 'blocked' \| 'pending_review' |
| mergeBase | text nullable | git merge-base hash stored before phase merge, enables diff reconstruction for completed phases |
| createdAt, updatedAt | integer/timestamp | |
### phase_dependencies
@@ -44,12 +45,13 @@ All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.r
| parentTaskId | text nullable self-ref FK (cascade) | decomposition hierarchy |
| name | text NOT NULL | |
| description | text nullable | |
| type | text enum | 'auto' \| 'checkpoint:human-verify' \| 'checkpoint:decision' \| 'checkpoint:human-action' |
| type | text enum | 'auto' |
| category | text enum | 'execute' \| 'research' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' \| 'verify' \| 'merge' \| 'review' |
| priority | text enum | 'low' \| 'medium' \| 'high' |
| status | text enum | 'pending' \| 'in_progress' \| 'completed' \| 'blocked' |
| order | integer | default 0 |
| summary | text nullable | Agent result summary — propagated to dependent tasks as context |
| retryCount | integer NOT NULL | default 0, incremented on agent crash auto-retry, reset on manual retry |
| createdAt, updatedAt | integer/timestamp | |
### task_dependencies
@@ -70,6 +72,7 @@ All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.r
| mode | text enum | 'execute' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' |
| pid | integer nullable | OS process ID |
| exitCode | integer nullable | |
| prompt | text nullable | Full assembled prompt passed to agent at spawn; persisted for durability after log cleanup |
| outputFilePath | text nullable | |
| result | text nullable | JSON |
| pendingQuestions | text nullable | JSON |
@@ -194,6 +197,21 @@ Messages within a chat session.
Index: `(chatSessionId)`.
### errands
Tracks errand work items linked to a project branch, optionally assigned to an agent.
| Column | Type | Notes |
|--------|------|-------|
| id | text PK | caller-supplied |
| description | text NOT NULL | human-readable description |
| branch | text NOT NULL | working branch name |
| baseBranch | text NOT NULL | default 'main' |
| agentId | text FK → agents (set null) | assigned agent; null if unassigned |
| projectId | text FK → projects (cascade) | owning project |
| status | text enum | active, pending_review, conflict, merged, abandoned; default 'active' |
| createdAt, updatedAt | integer/timestamp | |
### review_comments
Inline review comments on phase diffs, persisted across page reloads.
@@ -214,7 +232,7 @@ Index: `(phaseId)`.
## Repository Interfaces
13 repositories, each with standard CRUD plus domain-specific methods:
14 repositories, each with standard CRUD plus domain-specific methods:
| Repository | Key Methods |
|-----------|-------------|
@@ -231,6 +249,7 @@ Index: `(phaseId)`.
| ConversationRepository | create, findById, findPendingForAgent, answer |
| ChatSessionRepository | createSession, findActiveSession, findActiveSessionByAgentId, updateSession, createMessage, findMessagesBySessionId |
| ReviewCommentRepository | create, findByPhaseId, resolve, unresolve, delete |
| ErrandRepository | create, findById, findAll (filter by projectId/status), update, delete |
## Migrations
@@ -242,4 +261,4 @@ Key rules:
- See [database-migrations.md](database-migrations.md) for full workflow
- Snapshots stale after 0008; migrations 0008+ are hand-written
Current migrations: 0000 through 0030 (31 total).
Current migrations: 0000 through 0035 (36 total).

View File

@@ -11,7 +11,7 @@
- **Adapter**: `TypedEventBus` using Node.js `EventEmitter`
- All events implement `BaseEvent { type, timestamp, payload }`
### Event Types (57)
### Event Types (58)
| Category | Events | Count |
|----------|--------|-------|
@@ -27,7 +27,7 @@
| **Preview** | `preview:building`, `preview:ready`, `preview:stopped`, `preview:failed` | 4 |
| **Conversation** | `conversation:created`, `conversation:answered` | 2 | `conversation:created` triggers auto-resume of idle target agents via `resumeForConversation()` |
| **Chat** | `chat:message_created`, `chat:session_closed` | 2 | Chat session lifecycle events |
| **Initiative** | `initiative:pending_review`, `initiative:review_approved` | 2 | Initiative-level review gate events |
| **Initiative** | `initiative:pending_review`, `initiative:review_approved`, `initiative:changes_requested` | 3 | Initiative-level review gate events |
| **Project** | `project:synced`, `project:sync_failed` | 2 | Remote sync results from `ProjectSyncManager` |
| **Log** | `log:entry` | 1 |
@@ -48,6 +48,7 @@ PhaseChangesRequestedEvent { phaseId, initiativeId, taskId, commentCount }
AccountCredentialsRefreshedEvent { accountId, expiresAt, previousExpiresAt? }
InitiativePendingReviewEvent { initiativeId, branch }
InitiativeReviewApprovedEvent { initiativeId, branch, strategy: 'push_branch' | 'merge_and_push' }
InitiativeChangesRequestedEvent { initiativeId, phaseId, taskId }
```
## Task Dispatch
@@ -64,10 +65,11 @@ InitiativeReviewApprovedEvent { initiativeId, branch, strategy: 'push_branch' |
2. **Dispatch**`dispatchNext()` finds highest-priority task with all deps complete
3. **Context gathering** — Before spawn, `dispatchNext()` gathers initiative context (initiative, phase, tasks, pages) and passes as `inputContext` to the agent. Agents receive `.cw/input/task.md`, `initiative.md`, `phase.md`, `context/tasks/`, `context/phases/`, and `pages/`.
4. **Priority order**: high > medium > low, then oldest first (FIFO within priority)
5. **Checkpoint skip**Tasks with type starting with `checkpoint:` skip auto-dispatch
6. **Planning skip** — Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow
5. **Planning skip**Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow
7. **Summary propagation**`completeTask()` reads the completing agent's `result.message` and stores it on the task's `summary` column. Dependent tasks see this summary in `context/tasks/<id>.md` frontmatter.
8. **Spawn failure** — If `agentManager.spawn()` throws, the task is blocked via `blockTask()` with the error message. The dispatch cycle continues instead of crashing.
9. **Retry blocked**`retryBlockedTask(taskId)` resets a blocked task to pending and re-queues it. Exposed via tRPC `retryBlockedTask` mutation. The UI shows a Retry button in the task slide-over when status is `blocked`.
10. **Branch validation** — Branch computation and `ensureBranch` errors are fatal for execution tasks (execute, verify, merge, review) but non-fatal for planning tasks. This prevents agents from spawning without proper branch isolation.
### DispatchManager Methods
@@ -78,6 +80,7 @@ InitiativeReviewApprovedEvent { initiativeId, branch, strategy: 'push_branch' |
| `getNextDispatchable()` | Get next task without dispatching |
| `completeTask(taskId, agentId?)` | Complete task |
| `blockTask(taskId, reason)` | Block task with reason |
| `retryBlockedTask(taskId)` | Reset blocked task to pending and re-queue |
| `getQueueState()` | Return queued, ready, blocked tasks |
## Phase Dispatch
@@ -110,8 +113,21 @@ InitiativeReviewApprovedEvent { initiativeId, branch, strategy: 'push_branch' |
| Event | Action |
|-------|--------|
| `phase:queued` | Dispatch ready phases → dispatch their tasks to idle agents |
| `agent:stopped` | Re-dispatch queued tasks (freed agent slot) |
| `task:completed` | Merge task branch, then dispatch next queued task |
| `agent:stopped` | Auto-complete task (unless user_requested), re-dispatch queued tasks (freed agent slot) |
| `agent:crashed` | Auto-retry crashed task up to `MAX_TASK_RETRIES` (3). Increments `retryCount`, resets status to `pending`, re-queues. Exceeding retries leaves task `in_progress` for manual intervention. |
| `task:completed` | Merge task branch (if branch exists), check phase completion, dispatch next queued task |
### Crash Recovery
When an agent crashes (`agent:crashed` event), the orchestrator automatically retries the task:
1. Finds the task associated with the crashed agent
2. Checks `task.retryCount` against `MAX_TASK_RETRIES` (3)
3. If under limit: increments `retryCount`, resets task to `pending`, re-queues for dispatch
4. If over limit: logs warning, task stays `in_progress` for manual intervention
On server restart, `recoverDispatchQueues()` also recovers stuck `in_progress` tasks whose agents are dead (status is not `running` or `waiting_for_input`). These are reset to `pending` and re-queued.
Manual retry via `retryBlockedTask()` resets `retryCount` to 0, giving the task a fresh set of automatic retries.
### Coalesced Scheduling
@@ -119,6 +135,8 @@ Multiple rapid events (e.g. several `phase:queued` from `queueAllPhases`) are co
### Execution Mode Behavior
- **YOLO**: phase completes → auto-merge → auto-dispatch next phase → auto-dispatch tasks
- **YOLO**: phase completes → auto-merge (if branch exists, skipped otherwise) → auto-dispatch next phase → auto-dispatch tasks
- **review_per_phase**: phase completes → set `pending_review` → STOP. User approves → `approveAndMergePhase()` → merge → dispatch next phase → dispatch tasks
- **request-changes**: phase `pending_review` → user requests changes → creates revision task (category=`'review'`) → phase reset to `in_progress` → agent fixes → phase returns to `pending_review`
- **No branch**: Phase completion check runs regardless of branch existence. Merge steps are skipped; status transitions still fire. `updateTaskStatus` tRPC routes completions through `dispatchManager.completeTask()` to emit `task:completed`.
- **request-changes (phase)**: phase `pending_review` → user requests changes → creates revision task (category=`'review'`, dedup guard skips if active review exists) with threaded comments (`[comment:ID]` tags + reply threads) → phase reset to `in_progress` → agent reads comments, fixes code, writes `.cw/output/comment-responses.json` → OutputHandler creates reply comments and optionally resolves threads → phase returns to `pending_review`
- **request-changes (initiative)**: initiative `pending_review` → user requests changes → creates/reuses "Finalization" phase → creates review task → initiative reset to `active` → agent fixes → initiative returns to `pending_review`

View File

@@ -44,6 +44,7 @@ Use `mapEntityStatus(rawStatus)` from `StatusDot.tsx` to convert raw entity stat
|-------|-----------|---------|
| `/` | `routes/index.tsx` | Dashboard / initiative list |
| `/initiatives/$id` | `routes/initiatives/$initiativeId.tsx` | Initiative detail (tabbed) |
| `/agents` | `routes/agents.tsx` | Agent list with Output / Details tab panel |
| `/settings` | `routes/settings/index.tsx` | Settings page |
## Initiative Detail Tabs
@@ -54,7 +55,7 @@ The initiative detail page has three tabs managed via local state (not URL param
2. **Execution Tab** — Pipeline visualization, phase management, task dispatch
3. **Review Tab** — Pending proposals from agents
## Component Inventory (73 components)
## Component Inventory (74 components)
### Core Components (`src/components/`)
| Component | Purpose |
@@ -66,6 +67,7 @@ The initiative detail page has three tabs managed via local state (not URL param
| `StatusBadge` | Colored badge using status tokens |
| `TaskRow` | Task list item with status, priority, category |
| `QuestionForm` | Agent question form with options |
| `AgentDetailsPanel` | Details tab for agent right-panel: metadata, input files, effective prompt |
| `InboxDetailPanel` | Agent message detail + response form |
| `ProjectPicker` | Checkbox list for project selection |
| `RegisterProjectDialog` | Dialog to register new git project |
@@ -111,10 +113,12 @@ The initiative detail page has three tabs managed via local state (not URL param
### Review Components (`src/components/review/`)
| Component | Purpose |
|-----------|---------|
| `ReviewTab` | Review tab container — orchestrates header, diff, sidebar, and preview |
| `ReviewTab` | Review tab container — orchestrates header, diff, sidebar, and preview. Phase-level review has threaded inline comments (with reply support) + Request Changes; initiative-level review has Request Changes (summary prompt) + Push Branch / Merge & Push |
| `ReviewHeader` | Consolidated toolbar: phase selector pills, branch info, stats, preview controls, approve/reject actions |
| `ReviewSidebar` | VSCode-style icon strip (Files/Commits views) with file list, comment counts, and commit navigation |
| `DiffViewer` | Unified diff renderer with inline comments |
| `ReviewSidebar` | VSCode-style icon strip (Files/Commits views) with file list, root-only comment counts, and commit navigation |
| `DiffViewer` | Unified diff renderer with threaded inline comments (root + reply threads) |
| `CommentThread` | Renders root comment with resolve/reopen + nested reply threads (agent replies styled with primary border). Inline reply form |
| `ConflictResolutionPanel` | Merge conflict detection + agent resolution in initiative review. Shows conflict files, spawns conflict agent, inline questions, re-check on completion |
| `PreviewPanel` | Docker preview status: building/running/failed with start/stop (legacy, now integrated into ReviewHeader) |
| `ProposalCard` | Individual proposal display |
@@ -126,6 +130,7 @@ shadcn/ui components: badge (6 status variants + xs size), button, card, dialog,
| Hook | Purpose |
|------|---------|
| `useRefineAgent` | Manages refine agent lifecycle for initiative |
| `useConflictAgent` | Manages conflict resolution agent lifecycle for initiative review |
| `useDetailAgent` | Manages detail agent for phase planning |
| `useAgentOutput` | Subscribes to live agent output stream |
| `useChatSession` | Manages chat session for phase/task refinement |
@@ -195,4 +200,4 @@ Components: `ChatSlideOver`, `ChatBubble`, `ChatInput`, `ChangeSetInline` in `sr
`listInitiatives` returns an `activity` field on each initiative, computed server-side from phase statuses via `deriveInitiativeActivity()` in `apps/server/trpc/routers/initiative-activity.ts`. This eliminates per-card N+1 `listPhases` queries.
Activity states (priority order): active architect agents > `pending_review` > `executing` > `blocked` > `complete` > `ready` > `planning` > `idle` > `archived`. Each state maps to a `StatusVariant` + pulse animation in `InitiativeCard`'s `activityVisual()` function. Active architect agents (modes: discuss, plan, detail, refine) are checked first — mapping to `discussing`, `detailing`, `detailing`, `refining` states respectively — so auto-spawned agents surface activity even when no phases exist yet. `PhaseSidebarItem` also shows a spinner when a detail agent is active for its phase.
Activity states (priority order): conflict agent > `archived` > active architect agents > `pending_review` > `executing` > `blocked` > `complete` > `ready` > `planning` > `idle`. Each state maps to a `StatusVariant` + pulse animation in `InitiativeCard`'s `activityVisual()` function. Active conflict agents (name starts with `conflict-`) are checked first — returning `resolving_conflict` (urgent variant, pulsing). Active architect agents (modes: discuss, plan, detail, refine) are checked next — mapping to `discussing`, `detailing`, `detailing`, `refining` states respectively — so auto-spawned agents surface activity even when no phases exist yet. `PhaseSidebarItem` also shows a spinner when a detail agent is active for its phase.

View File

@@ -45,6 +45,9 @@ Worktrees stored in `.cw-worktrees/` subdirectory of the repo. Each agent gets a
| `remoteBranchExists(repoPath, branch)` | Check remote tracking branches (`origin/<branch>`) |
| `listCommits(repoPath, base, head)` | List commits head has that base doesn't (with stats) |
| `diffCommit(repoPath, commitHash)` | Get unified diff for a single commit |
| `getMergeBase(repoPath, branch1, branch2)` | Get common ancestor commit hash |
| `pushBranch(repoPath, branch, remote?)` | Push branch to remote (default: 'origin') |
| `checkMergeability(repoPath, source, target)` | Dry-run merge check via `git merge-tree --write-tree` (git 2.38+). Returns `{ mergeable, conflicts? }` with no side effects |
`remoteBranchExists` is used by `registerProject` and `updateProject` to validate that a project's default branch actually exists in the cloned repository before saving.

View File

@@ -59,11 +59,15 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
| dismissAgent | mutation | Dismiss agent (set userDismissedAt) |
| resumeAgent | mutation | Resume with answers |
| listAgents | query | All agents |
| getAgent | query | Single agent by name or ID |
| getAgent | query | Single agent by name or ID; also returns `taskName`, `initiativeName`, `exitCode` |
| getAgentResult | query | Execution result |
| getAgentQuestions | query | Pending questions |
| getAgentOutput | query | Full output from DB log chunks |
| getAgentOutput | query | Timestamped log chunks from DB (`{ content, createdAt }[]`) |
| getTaskAgent | query | Most recent agent assigned to a task (by taskId) |
| getAgentInputFiles | query | Files written to agent's `.cw/input/` dir (text only, sorted, 500 KB cap) |
| getAgentPrompt | query | Assembled prompt — reads from DB (`agents.prompt`) first; falls back to `.cw/agent-logs/<name>/PROMPT.md` for pre-persistence agents (1 MB cap) |
| getActiveRefineAgent | query | Active refine agent for initiative |
| getActiveConflictAgent | query | Active conflict resolution agent for initiative (name starts with `conflict-`) |
| listWaitingAgents | query | Agents waiting for input |
| onAgentOutput | subscription | Live raw JSONL output stream via EventBus |
@@ -95,6 +99,9 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
| getInitiativeReviewCommits | query | Commits on initiative branch not on default branch |
| getInitiativeCommitDiff | query | Single commit diff for initiative review |
| approveInitiativeReview | mutation | Approve initiative review: `{initiativeId, strategy: 'push_branch' \| 'merge_and_push'}` |
| requestInitiativeChanges | mutation | Request changes on initiative: `{initiativeId, summary}` → creates review task in Finalization phase, resets initiative to active |
| checkInitiativeMergeability | query | Dry-run merge check: `{initiativeId}``{mergeable, conflictFiles[], targetBranch}` |
| spawnConflictResolutionAgent | mutation | Spawn agent to resolve merge conflicts: `{initiativeId, provider?}` → auto-dismisses stale conflict agents, creates merge task |
### Phases
| Procedure | Type | Description |
@@ -115,11 +122,12 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
| getPhaseReviewCommits | query | List commits between initiative and phase branch |
| getCommitDiff | query | Diff for a single commit (by hash) in a phase |
| approvePhaseReview | mutation | Approve and merge phase branch |
| requestPhaseChanges | mutation | Request changes: creates revision task from unresolved comments, resets phase to in_progress |
| listReviewComments | query | List review comments by phaseId |
| requestPhaseChanges | mutation | Request changes: creates revision task from unresolved threaded comments (with `[comment:ID]` tags and reply threads), resets phase to in_progress |
| listReviewComments | query | List review comments by phaseId (flat list including replies, frontend groups by parentCommentId) |
| createReviewComment | mutation | Create inline review comment on diff |
| resolveReviewComment | mutation | Mark review comment as resolved |
| unresolveReviewComment | mutation | Mark review comment as unresolved |
| replyToReviewComment | mutation | Create a threaded reply to an existing review comment (copies parent's phaseId/filePath/lineNumber) |
### Phase Dispatch
| Procedure | Type | Description |
@@ -205,13 +213,13 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
| Procedure | Type | Events |
|-----------|------|--------|
| onEvent | subscription | All event types |
| onAgentUpdate | subscription | agent:* events (8 types) |
| onAgentUpdate | subscription | agent:* events (7 types, excludes agent:output) |
| onTaskUpdate | subscription | task:* + phase:* events (8 types) |
| onPageUpdate | subscription | page:created/updated/deleted |
| onPreviewUpdate | subscription | preview:building/ready/stopped/failed |
| onConversationUpdate | subscription | conversation:created/answered |
Subscriptions use `eventBusIterable()` — queue-based async generator, max 1000 events, 30s heartbeat.
Subscriptions use `eventBusIterable()` — queue-based async generator, max 1000 events, 30s heartbeat. `agent:output` is excluded from all general subscriptions (it's high-frequency streaming data); use the dedicated `onAgentOutput` subscription instead.
## Coordination Module