Commit Graph

114 Commits

Author SHA1 Message Date
Lukas May
2bef0fa682 Merge branch 'cw/project-management-operations-to-ui' into cw-merge-1772805244951 2026-03-06 14:54:05 +01:00
Lukas May
986cae880d chore: Remove unused realpathSync import 2026-03-06 14:52:42 +01:00
Lukas May
4a7105eb8f fix: Worktree get() matches wrong agent due to ambiguous endsWith lookup
The `get(id)` method on SimpleGitWorktreeManager used `path.endsWith(id)`
to find worktrees. Since all agents working on the same project create
worktrees with the same project name suffix (e.g., "codewalk-district"),
cleanup for one agent could match and delete another agent's worktree.

Fix: match on `basename(worktreesDir)/id` so each manager's lookups are
scoped to its own worktree base directory.
2026-03-06 14:52:28 +01:00
Lukas May
7a4d0d2582 feat: Add Sync All button to projects settings page with tests
- Adds syncAllMutation calling trpc.syncAllProjects once for all cards
- Shows Sync All button in header when ≥1 project exists (hidden otherwise)
- Propagates isPending to ProjectCard.syncAllPending to disable per-card sync
- On success: toasts all-success or failure count; invalidates listProjects + getProjectSyncStatus per project
- On network error: toasts Sync failed with err.message
- Adds unit tests for ProjectSyncManager.syncAllProjects: empty list, all succeed, partial failure, result shape, and failure counting logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:39:58 +01:00
Lukas May
8f4fa2a233 fix: disambiguate CONFLICT errors in registerProject by inspecting the SQLite UNIQUE constraint column
Previously a single "that name or URL" message was thrown regardless of which
column violated uniqueness. Now the catch block inspects the error string from
SQLite to emit a name-specific or url-specific message, with a generic fallback
when neither column can be identified.

Adds vitest tests covering all four scenarios: name conflict, url conflict,
unknown column conflict, and non-UNIQUE error passthrough.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:37:21 +01:00
Lukas May
23964374d1 Merge branch 'cw/agent-details' into cw-merge-1772802959182 2026-03-06 14:15:59 +01:00
Lukas May
6f38ae21ed Merge branch 'main' into cw/agent-details-conflict-1772802863659
# Conflicts:
#	docs/server-api.md
2026-03-06 14:15:30 +01:00
Lukas May
0f1c578269 fix: Fail fast when agent worktree creation or branch setup fails
Previously, branch computation errors and ensureBranch failures were
silently swallowed for all tasks, allowing execution agents to spawn
without proper git isolation. This caused alert-pony to commit directly
to main instead of its task branch.

- manager.ts: Verify each project worktree subdirectory exists after
  createProjectWorktrees; throw if any are missing. Convert passive
  cwdVerified log to a hard guard.
- dispatch/manager.ts: Make branch computation and ensureBranch errors
  fatal for execution tasks (execute, verify, merge, review) while
  keeping them non-fatal for planning tasks.
2026-03-06 14:08:59 +01:00
Lukas May
b419981924 perf: Speed up conflict resolution agents by trimming prompt bloat
Replace SESSION_STARTUP (full test suite run) and CONTEXT_MANAGEMENT
(progress file refs) with a minimal startup block (pwd, git status,
CLAUDE.md). Add skipPromptExtras option to SpawnAgentOptions to skip
inter-agent communication and preview deployment instructions. Conflict
agents now go straight to the resolution protocol — one post-resolution
test run instead of two.
2026-03-06 14:05:23 +01:00
Lukas May
a0574a1ae9 feat: Add subagent usage guidance to refine and plan prompts
Instruct architect agents to leverage subagents for parallel work —
page analysis, codebase verification, dependency mapping, and pattern
discovery — instead of doing everything sequentially.
2026-03-06 13:39:19 +01:00
Lukas May
1629285778 Merge branch 'refs/heads/main' into cw/agent-details-conflict-1772799979862
# Conflicts:
#	apps/server/drizzle/meta/_journal.json
2026-03-06 13:34:28 +01:00
Lukas May
e3246baf51 feat: Show resolving_conflict activity state on initiative cards
Add 'resolving_conflict' to InitiativeActivityState and detect active
conflict agents (name starts with conflict-) in deriveInitiativeActivity.
Conflict resolution takes priority over pending_review since the agent
is actively working.

- Add resolving_conflict to shared types and activity derivation
- Include conflict agents in listInitiatives agent filter (name + mode)
- Map resolving_conflict to urgent variant with pulse in InitiativeCard
- Add merge: prefix to INITIATIVE_LIST_RULES for merge event routing
- Add spawnConflictResolutionAgent to INVALIDATION_MAP
- Add getActiveConflictAgent to detail page agent: SSE invalidation
2026-03-06 13:32:37 +01:00
Lukas May
ebef093d3f fix: Add missing event routing for initiative status real-time refresh
7 of 12 initiative activity state transitions were broken due to missing
event routing at three layers: SSE event arrays, live-update prefix rules,
and mutation invalidation map.

- Add initiative:changes_requested to ALL_EVENT_TYPES and TASK_EVENT_TYPES
- Add initiative:/agent: prefix rules to initiatives list and detail pages
- Add approveInitiativeReview, requestInitiativeChanges, requestPhaseChanges
  to INVALIDATION_MAP; add listInitiatives to approvePhase
- Extract INITIATIVE_LIST_RULES constant for reuse
2026-03-06 13:25:31 +01:00
Lukas May
52e238924c feat: Add agent spawn infrastructure for errand mode
Implements three primitives needed before errand tRPC procedures can be wired up:

- agentManager.sendUserMessage(agentId, message): resumes an errand agent with a
  raw user message, bypassing the conversations table and conversationResumeLocks.
  Throws on missing agent, invalid status, or absent sessionId.

- writeErrandManifest(options): writes .cw/input/errand.md (YAML frontmatter),
  .cw/input/manifest.json (errandId/agentId/agentName/mode, no files/contextFiles),
  and .cw/expected-pwd.txt to an agent workdir.

- buildErrandPrompt(description): minimal prompt for errand agents; exported from
  prompts/errand.ts and re-exported from prompts/index.ts.

Also fixes a pre-existing TypeScript error in lifecycle/controller.test.ts (missing
backoffMs property in RetryPolicy mock introduced by a concurrent agent commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:22:15 +01:00
Lukas May
d52317ac5d feat: Add timestamps to agent logs and fix horizontal scroll
- getAgentOutput now returns timestamped chunks ({ content, createdAt }[])
  instead of a flat string, preserving DB chunk timestamps
- parseAgentOutput accepts TimestampedChunk[] and propagates timestamps
  to each ParsedMessage
- AgentOutputViewer displays HH:MM:SS timestamps on text, tool_call,
  system, and session_end messages
- Live subscription chunks get client-side Date.now() timestamps
- Fix horizontal scroll: overflow-x-hidden + break-words on content areas
- AgentLogsTab polls getTaskAgent every 5s until an agent is found,
  then stops polling for live subscription to take over
- Fix slide-over layout: details tab scrolls independently, logs tab
  fills remaining flex space for proper AgentOutputViewer sizing
2026-03-06 13:18:42 +01:00
Lukas May
4ee71d45f4 test: Add regression tests for agent:account_switched emission in lifecycle controller
Adds controller.test.ts with three test cases asserting that handleAccountExhaustion
correctly emits agent:account_switched with previousAccountId, newAccountId, and
reason fields — and that the emit is skipped when no new account is available or
the agent has no accountId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:17:32 +01:00
Lukas May
b2f4004191 feat: Persist agent prompt in DB so getAgentPrompt survives log cleanup
The `getAgentPrompt` tRPC procedure previously read exclusively from
`.cw/agent-logs/<name>/PROMPT.md`. Once the cleanup-manager removes
that directory, the prompt is gone forever.

Adds a `prompt` text column to the `agents` table and writes the fully
assembled prompt (including workspace layout, inter-agent comms, and
preview sections) to the DB in the same `repository.update()` call
that saves pid/outputFilePath after spawn.

`getAgentPrompt` now reads from DB first (`agent.prompt`) and falls
back to the filesystem only for agents spawned before this change.

Addresses review comment [MMcmVlEK16bBfkJuXvG6h].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:13:01 +01:00
Lukas May
3e2a570447 feat: Emit account_switched event on account exhaustion in lifecycle controller
Passes EventBus through LifecycleFactory and AgentLifecycleController so that
when an account is marked exhausted, an agent:account_switched event is emitted
with the previous and new account IDs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:12:25 +01:00
Lukas May
3f3954672e feat: Add Agent Logs tab to task slide-over
Add getTaskAgent tRPC procedure to find the most recent agent for a task.
TaskSlideOver now has Details/Agent Logs tabs — logs tab renders
AgentOutputViewer when an agent exists, or an empty state otherwise.
2026-03-06 13:10:46 +01:00
Lukas May
b6218584ee feat: Show project pills on initiative cards in list view
Add projects to the listInitiatives tRPC response and render them as
outline badge pills between the initiative name and activity row.
2026-03-06 13:06:33 +01:00
Lukas May
08c1bed465 Merge branch 'cw/unified-event-flow' into cw-merge-1772797070237 2026-03-06 12:37:50 +01:00
Lukas May
a86a373d42 fix: Handle push to checked-out branch in local non-bare repos
git refuses to push to a branch that is currently checked out in a
non-bare repository. When the clone's origin is the user's local repo,
this blocks merge_and_push entirely.

On "branch is currently checked out" error, temporarily set
receive.denyCurrentBranch=updateInstead on the remote and retry.
This uses git's built-in mechanism to update the working tree safely
(rejects if dirty).
2026-03-06 12:37:21 +01:00
Lukas May
940b0f8ed2 feat: Add errands persistence layer — repository port, Drizzle adapter, migration, and tests
- Add errand-repository.ts port with ErrandRepository, ErrandWithAlias, ErrandStatus types
- Add DrizzleErrandRepository adapter with create, findById (left-joins agents for alias),
  findAll (optional projectId/status filters, desc by createdAt), update, delete
- Wire exports into repositories/index.ts and repositories/drizzle/index.ts
- Add migration 0035_faulty_human_fly.sql (CREATE TABLE errands) and drizzle snapshot
- Add 13 tests covering CRUD, filtering, ordering, agentAlias join, cascade/set-null FK behaviour
- Update docs/database.md to document the errands table and ErrandRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:35:06 +01:00
Lukas May
5d1292c7ad fix: Use update-ref for fast-forward to avoid dirty working tree failures
fastForwardBranch used git merge --ff-only which fails when the clone
has uncommitted files. This caused the ff to be silently skipped, the
merge to proceed on stale main, and the push to fail (non-fast-forward).

Switched to update-ref which only moves the branch pointer without
touching the working tree.
2026-03-06 12:34:21 +01:00
Lukas May
6a76e17cef feat: Add errands table, errand agent mode, and push rollback on merge failure
- Add `errands` table to schema with status enum and relations to agents/projects
- Add `errand` mode to agents.mode enum
- Add push rollback in orchestrator: if push fails after merge, reset to previousRef to preserve the review diff
- Extend MergeResult with previousRef for rollback support; update branch-manager and simple-git-branch-manager
- Add orchestrator tests for push rollback behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:41 +01:00
Lukas May
00e426ac00 fix: Roll back merge when push fails in initiative approval
When merge_and_push failed at the push step, the local defaultBranch ref
was left pointing at the merge commit. This made the three-dot diff
(defaultBranch...initiativeBranch) return empty because main already
contained all changes — causing the review tab to show "no changes."

Now mergeBranch returns the previous ref, and approveInitiative restores
it on push failure. Also repaired the corrupted clone state.
2026-03-06 12:31:35 +01:00
Lukas May
eac03862e3 fix: Prevent lost task completions after server restart
Three bugs causing empty phase diffs when server restarts during agent
execution:

1. Startup ordering race: reconcileAfterRestart() emitted agent:stopped
   before orchestrator registered listeners — events lost. Moved
   reconciliation to after orchestrator.start().

2. Stuck in_progress tasks: recoverDispatchQueues() only re-queued
   pending tasks. Added recovery for in_progress tasks whose agents
   are dead (not running/waiting_for_input).

3. Branch force-reset destroys work: git branch -f wiped commits when
   a second agent was dispatched for the same task. Now checks if the
   branch has commits beyond baseBranch before resetting.

Also adds:
- agent:crashed handler with auto-retry (MAX_TASK_RETRIES=3)
- retryCount column on tasks table + migration
- retryCount reset on manual retryBlockedTask()
2026-03-06 12:19:59 +01:00
Lukas May
6034f6d854 Merge branch 'main' into cw/unified-event-flow-conflict-1772795597661
# Conflicts:
#	apps/web/src/components/review/ReviewTab.tsx
#	apps/web/src/routes/initiatives/$id.tsx
2026-03-06 12:16:07 +01:00
Lukas May
17f92040c7 fix: Ensure agents write signal.json to the correct directory
Two additional fixes to prevent agents from writing .cw/output/ in the
wrong location:

1. Always create .cw/output/ at the agent workdir root during spawn,
   even when no inputContext is provided. This gives the agent a visible
   anchor directory so it doesn't create one inside a project subdir.

2. Add absolute output path instruction to the workspace layout prompt
   for multi-project agents, explicitly telling them to write .cw/output/
   relative to the workdir root, not their current cd location.
2026-03-06 12:14:37 +01:00
Lukas May
f4dbaae0e3 fix: Guard worktree creation against branch === baseBranch
Throws if branch and baseBranch are identical, preventing
git branch -f from force-resetting shared branches (like
the initiative branch) when accidentally passed as both.
2026-03-06 12:12:32 +01:00
Lukas May
b853b28751 fix: Resolve agent workdir probing for initiative project subdirectories
Conflict-resolution agents (and any initiative-based agent) can write
.cw/output/signal.json inside a project subdirectory (e.g.
agent-workdirs/<name>/codewalk-district/.cw/output/) rather than the
parent agent workdir. This caused two failures:

1. spawnInternal wrote spawn-diagnostic.json before registering the
   agent in activeAgents and starting pollForCompletion. If the .cw/
   directory didn't exist (no inputContext provided), the write threw
   ENOENT, orphaning the running process with no completion monitoring.

2. resolveAgentCwd in cleanup-manager and output-handler only probed
   for a workspace/ subdirectory (standalone agents) but not project
   subdirectories, so reconciliation and completion handling couldn't
   find signal.json and marked the agent as crashed.

Fixes:
- Move activeAgents registration and pollForCompletion setup before
  the diagnostic write; make the write non-fatal with mkdir -p
- Add project subdirectory probing to resolveAgentCwd in both
  cleanup-manager.ts and output-handler.ts
2026-03-06 12:03:20 +01:00
Lukas May
2814c2d3b2 fix: Fetch remote before merge/push in initiative approval
approveInitiative was merging and pushing with a stale local
defaultBranch, causing "rejected (fetch first)" when origin/main
had advanced since the last project sync. Now fetches remote and
fast-forwards the target branch before merging.
2026-03-06 11:59:16 +01:00
Lukas May
1e723611e7 feat: Allow editing review comments
Add update method to ReviewCommentRepository, updateReviewComment tRPC
procedure, and inline edit UI in CommentThread. Edit button appears on
user-authored comments (not agent comments) when unresolved. Uses the
existing CommentForm with a new initialValue prop.
2026-03-06 11:58:08 +01:00
Lukas May
0c04a1d273 fix: Prevent conflict resolution agent from destroying initiative branch
spawnConflictResolutionAgent was passing the initiative branch as branchName,
causing SimpleGitWorktreeManager.create() to force-reset it to the target
branch. Now spawns on a unique temp branch based off the initiative branch,
with the agent using git update-ref to advance the initiative branch after
resolving conflicts. Also fixes stale diff/commits cache after resolution.
2026-03-06 11:40:22 +01:00
Lukas May
cc181ee6ba fix: Parse merge-tree output from stdout instead of catch block
simple-git's .raw() resolves successfully even on exit code 1,
returning stdout content. git merge-tree --write-tree outputs
CONFLICT markers to stdout (not stderr), so the catch block
never fired and conflicts were reported as clean merges.
2026-03-06 11:27:32 +01:00
Lukas May
6a2f9c6d57 migration: Drop orphaned approval columns from initiatives and tasks
merge_requires_approval (initiatives) and requires_approval (tasks)
were removed from schema.ts in the task-approval removal but left in
the DB because 0030 assumed SQLite couldn't DROP COLUMN. SQLite 3.35+
supports it. These orphaned columns caused the old stale-build approval
code path to silently set detail tasks to pending_approval, stranding
them and blocking phase completion.
2026-03-06 11:20:05 +01:00
Lukas May
6cf6bd076f feat: Add merge conflict detection and agent resolution in initiative review
Pre-merge mergeability check via `git merge-tree --write-tree` (dry-run, no
side effects). When conflicts exist the "Merge & Push" button is disabled and
a ConflictResolutionPanel shows conflict files with options to resolve manually
or spawn a conflict-resolution agent. Agent questions appear inline via
QuestionForm; on completion the mergeability re-checks automatically.

New server-side: MergeabilityResult type, BranchManager.checkMergeability,
conflict-resolution prompt, checkInitiativeMergeability query,
spawnConflictResolutionAgent mutation, getActiveConflictAgent query.

New frontend: useConflictAgent hook, ConflictResolutionPanel component,
mergeability badge + panel integration in InitiativeReview.
2026-03-06 11:17:25 +01:00
Lukas May
14d09b16df fix: Phase completion check runs regardless of branch/merge status
handleTaskCompleted and handlePhaseAllTasksDone both bailed early when
initiative had no branch, silently skipping phase status transitions.
Also, merge failures would skip the phase completion check entirely.

- Decouple phase completion check from branch existence
- Wrap merge in try/catch so phase check runs even if merge fails
- Route updateTaskStatus through dispatchManager.completeTask when
  completing, so the task:completed event fires for orchestration
2026-03-06 11:07:01 +01:00
Lukas May
1bc3f85d6a fix: Merge worktree conflict when target branch already checked out
Use a temp branch + update-ref to avoid "already checked out" error
when merging into the default branch. Also show actual branch name
in the Merge & Push button instead of "Default".
2026-03-06 10:54:42 +01:00
Lukas May
bdc95bcb26 fix: Handle existing branch in worktree creation
When re-dispatching tasks, the branch from a previous run may still
exist. Instead of failing with "a branch named X already exists",
reset the existing branch to the base and check it out.
2026-03-06 10:54:33 +01:00
Lukas May
4656627a59 fix: Restore drizzle-kit generate by syncing snapshot baseline
Snapshots were stale since migration 0008. Generated a schema-derived
snapshot at 0032 so drizzle-kit generate works again (zero diff on
current schema.ts). Also fixed migration 0032 to use statement-breakpoint
separator required by better-sqlite3.

- Added 0032_snapshot.json derived from current schema.ts
- Fixed 0032 SQL to use --> statement-breakpoint between statements
- Updated CLAUDE.md and database-migrations.md with correct workflow
2026-03-06 10:36:07 +01:00
Lukas May
eb667dd3d7 fix: Register migration 0032 in drizzle journal
The migration file existed but wasn't in _journal.json, so drizzle-kit's
migrator never applied it. Adds the journal entry for 0032_add_comment_threading.
2026-03-06 10:27:44 +01:00
Lukas May
7695604da2 feat: Add threaded review comments + agent comment responses
Introduces GitHub-style threaded comments via parentCommentId self-reference.
Users and agents can reply within comment threads, and review agents receive
comment IDs so they can post targeted responses via comment-responses.json.

- Migration 0032: parentCommentId column + index on review_comments
- Repository: createReply() copies parent context, default author 'you' → 'user'
- tRPC: replyToReviewComment procedure, requestPhaseChanges passes threaded comments
- Orchestrator: formats [comment:ID] tags with full reply threads in task description
- Agent IO: readCommentResponses() reads .cw/output/comment-responses.json
- OutputHandler: processes agent comment responses (creates replies, resolves threads)
- Execute prompt: conditional <review_comments> block when task has [comment:] markers
- Frontend: CommentThread renders root+replies with agent-specific styling + reply form
- Sidebar/ReviewTab: root-only comment counts, reply mutation plumbing through DiffViewer chain
2026-03-06 10:21:22 +01:00
Lukas May
65bcbf1a35 fix: Fix review task completion bug + add initiative-level Request Changes
Critical: review/merge tasks hit an early return in handleTaskCompleted()
that skipped the phase completion check, leaving phases stuck in
in_progress forever. Changed to an if-block wrapping only the merge step.

Also adds requestChangesOnInitiative() which creates/reuses a
"Finalization" phase for initiative-level review feedback, with dedup
guards for both phase and initiative request-changes flows.
2026-03-06 09:41:28 +01:00
Lukas May
1b8e496d39 fix: Switch preview gateway from path-prefix to subdomain routing
Path-prefix routing (`localhost:9100/<id>/`) broke SPAs because absolute
asset paths (`/assets/index.js`) didn't match the `handle_path /<id>/*`
route. Subdomain routing (`<id>.localhost:9100/`) resolves this since
all paths are relative to the root. Chrome/Firefox resolve *.localhost
to 127.0.0.1 natively — no DNS setup needed.
2026-03-05 22:38:00 +01:00
Lukas May
aedf149471 fix: Use container-internal port 80 in gateway Caddyfile
The Caddyfile was using the host port (e.g., 9100) as the Caddy listen
address, but Docker maps host:9100 → container:80. Caddy inside the
container was listening on 9100 while Docker only forwarded to port 80,
causing all health checks to fail with "connection reset by peer".
2026-03-05 22:16:19 +01:00
Lukas May
84250955d1 fix: Show completed phase diffs in review tab
Completed phases showed "No phases pending review" because:
1. Frontend filtered only pending_review phases
2. Server rejected non-pending_review phases
3. After merge, three-dot diff returned empty (merge base moved)

Fix: store pre-merge merge base hash on phase, use it to reconstruct
diffs for completed phases. Frontend now shows both pending_review and
completed phases with read-only mode (Merged badge) for completed ones.
2026-03-05 22:05:28 +01:00
Lukas May
4958b6624d fix: Refetch previews on start and switch to path-based routing
Two fixes:
- Call previewsQuery.refetch() in startPreview.onSuccess so the UI
  transitions from "building" to the preview link without a page refresh.
- Switch from subdomain routing (*.localhost) to path-based routing
  (localhost:<port>/<id>/) since macOS doesn't resolve wildcard
  localhost subdomains.
2026-03-05 21:56:05 +01:00
Lukas May
0e61c48c86 fix: Use lowercase alphanumeric nanoid for Docker compose project names
Docker compose requires project names to be lowercase alphanumeric
with hyphens/underscores only. The default nanoid alphabet includes
uppercase and special characters, causing build failures.
2026-03-05 21:49:01 +01:00
Lukas May
269a2d2616 feat: Extend AgentInfo with exitCode + add getAgentInputFiles/getAgentPrompt tRPC procedures
Adds exitCode to AgentInfo type and propagates it through all toAgentInfo()
implementations. Enhances getAgent to also return taskName and initiativeName
from their respective repositories. Adds two new filesystem-reading tRPC
procedures for the Agent Details tab: getAgentInputFiles (reads .cw/input/
files with binary detection, 500 KB cap, sorted) and getAgentPrompt (reads
.cw/agent-logs/<name>/PROMPT.md with 1 MB cap and structured ENOENT handling).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 21:39:29 +01:00