Commit Graph

600 Commits

Author SHA1 Message Date
Lukas May
3a328d2b1c feat: Add errands schema, repository, and wire into tRPC context/container
Creates the errands table (with conflictFiles column), errand-repository
port interface, DrizzleErrandRepository adapter, and wires the repository
into TRPCContext, the DI container, _helpers.ts requireErrandRepository guard,
and the test harness. Also fixes pre-existing TS error in controller.test.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:49:26 +01:00
Lukas May
67658fb717 fix: require signal.json for all errand agent exit scenarios
Option A ("ask inline, session stays open") described a path where the
errand agent could ask questions without writing signal.json, which broke
the server's completion detection (checkAgentCompletionResult polls for
done|questions|error status). Remove the Option A/B distinction and make
signal.json with questions status the single mechanism for all user-input
requests, consistent with how other agents handle blocking questions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:35:11 +01:00
Lukas May
41c5d292bb fix: allow errand agent to end session with questions and resume
The errand agent can now write { "status": "questions", ... } to
signal.json to pause mid-task and ask the user for clarification.
The session ends cleanly; the user answers via UI or CLI; the system
resumes the agent with their answers via sendUserMessage.

Two changes:
- buildErrandPrompt: adds "Option B" explaining the questions signal
  format and the resume-on-answer lifecycle, alongside the existing
  inline-question approach.
- sendUserMessage: extends allowed statuses from running|idle to also
  include waiting_for_input, so agents paused on a questions signal
  can be resumed when the user replies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:54:53 +01:00
Lukas May
e2c489dc48 feat: teach errand agent how to ask questions interactively
Add a dedicated "Asking questions" section to the errand prompt so the
agent knows it can pause, ask for clarification, and wait for the user
to reply via the UI chat input or `cw errand chat`. Previously the
prompt said "work interactively" with no guidance on the mechanism,
leaving the agent to guess.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:31:03 +01:00
Lukas May
343c6a83a8 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 14:05:26 +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
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
49970eb1d7 fix: Use overflow-clip instead of overflow-hidden on FileCard
overflow-hidden creates a scroll container that breaks sticky positioning
for file headers. overflow-clip provides the same visual clipping for
rounded corners without affecting the scroll/sticky context.
2026-03-06 11:44:45 +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
f428ec027e fix: Sticky file headers sit below review header using CSS variable
Sets --review-header-h on the card wrapper from measured header height.
FileCard reads it for sticky top offset so file headers dock just below
the review header instead of overlapping it.
2026-03-06 11:36:28 +01:00
Lukas May
c87aac44cc fix: Cover transparent gap above sticky header with upward box-shadow
Uses a 50px upward box-shadow in bg-background color to paint over the
main padding gap that shows above the stuck review header.
2026-03-06 11:33:31 +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
5b497b84a0 fix: Restore sticky header and sidebar by simplifying layout
Removed wrapper divs that broke sticky positioning. ReviewHeader now
accepts a ref prop directly, with sticky top-0 on its own root element.
Card wrapper restored as the tall containing block so both header and
sidebar have room to stick within it.
2026-03-06 11:26:43 +01:00
Lukas May
4664644cda fix: Use pseudo-element to cover all transparent space above sticky header
Replaces negative margin hack with a ::before that extends upward from
the sticky header to paint bg-background over the main padding gap.
2026-03-06 11:22:30 +01:00
Lukas May
19cd0a2cb0 fix: Cover transparent gap above sticky review header
Use negative margin to pull sticky header into the parent space-y-3 gap,
with matching padding and bg-background to paint over it when stuck.
2026-03-06 11:21:08 +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
01f2279735 fix: Eliminate whitespace above sticky review header
Moved card border/rounding onto the sticky header wrapper itself so it
scrolls flush to top-0 with no gap. The body grid gets its own border-x
and border-b to preserve the card appearance. ResizeObserver now measures
border-box size for accurate sidebar offset.
2026-03-06 11:17:29 +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
3a01b9e9ca fix: Restore sticky positioning on header wrapper div 2026-03-06 11:15:25 +01:00
Lukas May
09624e9cb7 fix: Sidebar accounts for sticky header height via ResizeObserver
Measures the review header dynamically and offsets the sidebar's sticky
top and max-height accordingly, eliminating the gap when scrolled.
2026-03-06 11:15:02 +01:00
Lukas May
d4b466ce6d fix: Make review sidebar sticky to viewport with internal scrolling
Removed overflow-hidden from grid container and changed sidebar to
sticky positioning with viewport-relative max-height. Sidebar content
scrolls independently while staying visible during diff navigation.
2026-03-06 11:11:46 +01:00
Lukas May
50043f4c61 fix: Use instant scroll for discussion navigation 2026-03-06 11:10:50 +01:00
Lukas May
3fcfa61914 fix: Scroll to exact comment location when clicking sidebar discussions
Adds data-comment-id attributes to comment thread rows so clicking a
discussion in the sidebar scrolls directly to the comment, not just the
file card. Includes a brief ring highlight on the target row.
2026-03-06 11:09:07 +01:00
Lukas May
b1233dd013 docs: Update dispatch-events for branchless phase completion 2026-03-06 11:07:28 +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
157fa445c5 fix: Show individual discussion threads in review sidebar with navigation
Discussions section was showing only aggregate counts (total/resolved/unresolved)
with no way to see or navigate to individual threads. Now lists each root comment
with file:line location, body preview, resolved status, and reply count. Clicking
a discussion scrolls to its file in the diff viewer.
2026-03-06 11:03:27 +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
1e2819eeff fix: Request changes uses in-app confirmation, requires review comments
- Replace window.prompt with in-app dropdown confirmation (matches merge dialog pattern)
- Disable button when no unresolved comments exist (comments-only, no summary)
- Remove initiative-level request changes button (no comment system there)
2026-03-06 10:43:36 +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
03c2abbd8f docs: Fix migration workflow — document hand-written SQL + journal registration
drizzle-kit generate has been broken since migration 0008 (stale snapshots).
The actual workflow is hand-written SQL + manual _journal.json registration.
Updated CLAUDE.md and database-migrations.md to reflect reality and prevent
future migrations from silently failing to apply.
2026-03-06 10:29:49 +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
2da6632298 docs: Update dispatch-events, server-api, frontend for request-changes fixes 2026-03-06 09:42:28 +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
cdb3de353d fix: Use SSE events instead of polling for preview status updates
httpBatchLink batches polling queries behind the long-running
startPreview mutation, so refetchInterval never fires independently.
Replace polling with preview: event invalidation via the existing
useLiveUpdates SSE subscription — preview:building/ready/stopped/failed
events now trigger listPreviews and getPreviewStatus invalidation.
2026-03-05 22:01:16 +01:00
Lukas May
df84a877f2 fix: Prioritize polled preview status over mutation pending state
The startPreview mutation blocks server-side through the entire Docker
build + health check + seed cycle. isPending was checked first, so even
after polling detected containers running, the UI stayed on "Building..."
until the full mutation resolved. Now polled status (running/failed)
takes priority, falling back to isPending only when no status exists.
2026-03-05 21:58:02 +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
13e009a82d feat: Add preview controls to initiative-level review
Extract PreviewControls into shared component and wire up preview
start/stop to InitiativeReview header alongside Push Branch and
Merge & Push to Default buttons.
2026-03-05 21:47:06 +01:00
Lukas May
39d92d5de9 fix: Add changeset reconciliation to deleteTask and fix missing invalidation
Same orphaned-changeset pattern as deletePhase: manually deleting all
tasks from a detail changeset now marks it reverted. Also added
deleteTask to the invalidation map (was missing entirely).
2026-03-05 21:35:14 +01:00
Lukas May
ff398f84ac fix: Move useMemo above early returns in ReviewTab to fix hooks ordering crash
The allFiles useMemo was declared after two early-return branches. Approving
the last phase empties pendingReviewPhases, triggering the early return and
causing React to see fewer hooks than the previous render.
2026-03-05 21:35:10 +01:00
Lukas May
7b93cfe7d7 feat: Remove checkpoint task types — per-phase review is sufficient
Checkpoint tasks (human-verify, decision, human-action) silently blocked
auto-dispatch with no UI to resolve them. Per-phase review + initiative
review already cover human verification, making checkpoints redundant.

Removed from: schema, dispatch manager, tRPC validators, detail prompt,
frontend types, tests, and docs.
2026-03-05 21:30:22 +01:00
Lukas May
39bb03e30b fix: Reconcile orphaned changesets when phases are manually deleted
Manually deleting phases left their parent changeset as "applied",
causing the Plan tab to show a stale "Created N phases" banner with
no phases visible.

- deletePhase now checks if all phases from a changeset are gone and
  marks it reverted
- PlanSection filters out dismissed agents so dismissed banners stay
  hidden
- revertChangeSet marks reverted before entity deletion to prevent
  ghost state on partial failure
- deletePhase invalidation now includes listChangeSets
2026-03-05 21:29:38 +01:00
Lukas May
7bc1e7f25b perf: Remove preview polling in favour of SSE-driven invalidation
refetchInterval: 3000 on listPreviews and getPreviewStatus is now
redundant. Phase oMHtTekCDgdnBG0kkk25A wired useLiveUpdates in
$id.tsx to invalidate both queries on every preview:* SSE event,
so queries refetch exactly once per state change instead of on
a fixed 3-second timer.

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