Commit Graph
97 Commits
Author SHA1 Message Date
mika kuns 44d94caabc feat(planning): combined diff of a unit is base..head, no integration branch checkout 2026-10-01 11:25:48 +02:00
mika kuns 1f373a5a67 feat(worker): approve a unit parent like a single task, orchestrator only for legacy units 2026-10-01 11:25:48 +02:00
mika kuns 9da522b0c9 feat(worker): fast-forward finished children into the parent's unit branch
Planning parents get their own worktree on Queue plan, children fork from the
unit head and are absorbed via merge --ff-only before Done (ff failure -> Failed,
interactive child approve -> absorb instead of merge to the target).
2026-10-01 11:25:47 +02:00
mika kuns cd5b27c063 fix(planning): return to the list base branch after integration cleanup, not a guessed task branch 2026-10-01 10:55:12 +02:00
CubeGameLP e0748ba351 fix(worktree): auto-bootstrap commit-less repos before creating worktrees
Covers autonomous runs, interactive ConPTY sessions (GetInteractiveLaunchSpec),
planning sessions, and improvement/planning children — every path that needs a
base commit now self-heals on a fresh 'git init' repo instead of surfacing
"ambiguous argument 'HEAD'" as a HubException.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit ec16a85c495ceceaa22f922e077a27dcda54f035)
2026-08-28 20:33:45 +02:00
Mika Kuns 1affb39716 fix(worker): survive a worker restart when cancelling a mid-merge unit-merge parent
CancelAsync only checked PlanningMergeOrchestrator's in-memory HasActiveMerge, which is
empty after a restart. IActiveMergeState gains an async fallback that checks on-disk
(GitService.IsMidMergeAsync) for a WaitingForReview parent with children, so the guard
still blocks cancel until the merge is continued or aborted.
2026-08-28 14:17:38 +02:00
mika kuns 168ab1cd2f feat(merge): verify once per batch via skipVerify + verify_merges
Per-merge verify made an N-task batch pay N x ~7 min, each run testing
the same moving main. review_task/merge_task/continue_merge now take
skipVerify: the merge lands but the gate AND the Done transition are
deferred (status merged_verify_pending, task stays WaitingForReview).
The new verify_merges tool runs the list verify command once for the
explicitly listed tasks and promotes them to Done on success - explicit
ids so the handler own submitted task and parked verify_failed tasks
are never swept up; an Active worktree is refused per entry.

PlanningMergeOrchestrator threads the flag through the unit merge (no
more per-child verify) and skips FinalizeParentDoneAsync when verifies
were deferred. The merge-helper Merge prompt approves with
skipVerify=true and calls verify_merges once after the last merge. UI
approve is unchanged; the no-Done-without-green-verify invariant stays
server-enforced.
2026-08-26 15:29:20 +02:00
Mika Kuns c4928b4def chore: move appdata folder to .claudeDo 2026-08-26 14:26:19 +02:00
mika kuns 0f007c5367 refactor: drop the remaining single-implementation interfaces
IBaseDirtyChecker, IInteractiveLaunchSpecService and the LaunchSpec wrapper
had one implementation and one caller each; ProcessRunnerAdapter existed only
to give a static class an interface, and InstallArtifactLocator used
inheritance for two constructor arguments. The external MCP container now
shares its singletons through a Share<T> helper instead of 17 near-identical
registrations.
2026-08-26 13:55:51 +02:00
mika kuns 296251e27e refactor: collapse single-implementation interfaces
Nine interfaces had exactly one implementation and no test double — they existed
only to be named twice in a DI registration: IFindingsStore, IFindingsStoreLocator,
IPrimeScheduleSignal, IRefineRunner, IWeekReportService, IMergeCoordinator,
IMissionControlPane, IOnlineLoginService, ITaskListFilter. Consumers now depend on
the concrete type; the DTO records that shared those files moved next to their
implementation. IInteractiveLaunchSpecService stays — it carries 54 lines of
contract documentation, which is not ceremony.

IMergeCoordinator in particular had a redundant null object: MergeCoordinator with
a null Handler already no-ops, and every test used the real class with Handler set.

Filtering/ collapses from 8 files to 1. ITaskListFilter and TaskListFilterBase were
a double abstraction over four predicates, with MatchesAsContext => false declared
in both. SmartFlagFilter also compiled its expression twice (its own _flag plus the
inherited Matches cache) — it now uses the cache.

StaticTokenAuthProvider was in src but production uses ZitadelAuthProvider; it is
a test double, so it moves to the test project. Its own test goes away with it.
2026-08-26 10:12:23 +02:00
mika kuns f205843020 fix(worker): propagate unit-merge failures instead of reporting success
A child merge that came back blocked/verify_failed/untracked_collision during a
parent/children unit merge used to vanish: DrainAsync only logged it server-side,
PlanningMergeAborted carried no reason, and ApproveReview/review_task always
reported StatusMerged for a task with children regardless of the real outcome,
so a failed unit merge left the parent stuck with no visible error.

- PlanningMergeOrchestrator.StartAsync/ContinueAsync/DrainAsync now return a
  PlanningMergeResult(Status, Reason) instead of void, and PlanningMergeAborted
  carries that reason to the UI.
- WorkerHub.ApproveReview and ExternalMcpService.ReviewTask's approve branch
  propagate the real status/reason for a parent with children instead of
  hardcoding "merged" (or masking a non-conflict failure as "conflict").
- StartAsync now requires the parent to already be WaitingForReview for
  improvement parents too, not just planning ones, so a stale caller can no
  longer trigger a partial child merge.
- HasActiveMerge now also covers the window between the last child merging and
  FinalizeParentDoneAsync completing, closing a gap where a concurrent Cancel
  could race the parent's own approve-to-Done transition.
- IslandsShellViewModel.OnPlanningMergeAborted flashes the reason via
  FlashFooterError instead of only clearing the external-merge banner.
2026-08-20 15:02:17 +02:00
Mika Kuns 23aab26daf feat(worker): report progress for continue_merge and unit-merge verify gate
continue_merge and the parent/children unit-merge drain (PlanningMergeOrchestrator)
re-run the post-merge verify gate but never forwarded their IProgress token into it,
so a slow verify command on either path went silent past Claude Code's 300s MCP
idle-abort even though D1-D3 already fixed this for merge_task/review_task's
childless path. list_worktrees also gets elapsed-time progress: many tracked
worktrees means many concurrent git subprocess spawns.

Worker CLAUDE.md's existing progress rule now points at ProgressReporter as the
one implementation instead of a fresh polling loop.
2026-08-17 09:56:59 +02:00
mika kuns cad0582b37 fix(merge): conventional merge-commit default and live verify progress
The merge commit message was hand-rolled per caller ("Merge task: <title>",
"Merge <branch>", "Merge subtask") and ignored the task's commit type. Every
caller now passes a blank message and TaskMergeService fills in
CommitMessageBuilder.BuildMerge -> {commitType}(list-slug): merge <title> plus the
ClaudeDo-Task trailer; the merge modal prefills it from GetMergeTargets.

A merge whose list has a verify command holds the MergeTask call for minutes (5m46s
on this repo), during which the modal only disabled its button - no spinner, no
message, so a landed merge looked like a dead app. TaskMergeService now broadcasts
MergeProgress(taskId, phase, elapsedSeconds) for the merging and verifying phases
(re-reported every 30s) plus a WorkerLog line when verify starts; the modal shows a
spinner and the localized phase.
2026-08-11 19:14:36 +02:00
mika kuns 21c9c3b3b9 Merge claudedo/107e62d083e44436b9074c16d14e2edc 2026-08-10 15:02:52 +02:00
mika kuns c715c96af9 feat(mcp): paths filter for get_task_diff, subset hint for preview_merge_set
get_task_diff gained an optional paths param (git pathspec, both stat and
full-diff modes) so a large multi-file task can be narrowed to the files
actually in question instead of shipping the whole diff.

preview_merge_set now also reports Subsets: pairs where one task's changed
files are a proper subset of another's in the same set, the strongest
available post-hoc signal that a task may be redundant with another.
2026-08-10 13:39:55 +02:00
mika kuns 5d362b6973 fix(worker): stop sibling worktrees from landing inside the repo tree
Path.GetDirectoryName returned workingDir itself instead of its parent
whenever list.WorkingDir ended in a directory separator, placing
.claudedo-worktrees inside the target repo's own working tree. Add
WorktreeRootResolver to normalize the trailing separator before deriving
the sibling root and to guard that the resulting worktree path never
lands inside workingDir, used by both WorktreeManager and
PlanningSessionManager. Add a startup sweep (LegacyWorktreeFolderRecovery)
that warns when a list's working dir already contains a leftover
.claudedo-worktrees folder from before the fix.
2026-08-10 11:58:34 +02:00
mika kuns 79f90a9a8e fix(worker,ui): block cancelling a task while its unit merge is draining
TaskStateService.CancelAsync allowed cancelling a WaitingForReview task
even while PlanningMergeOrchestrator was mid-drain on it: ApproveReview
awaits the whole multi-subtask merge synchronously, so a concurrent
CancelReview (UI or MCP) could flip the parent to Cancelled while the
orchestrator kept merging children onto the target branch, then
FinalizeParentDoneAsync would find the parent no longer WaitingForReview
and give up - leaving the merged diffs stranded with no rollback.

CancelAsync now rejects with a clear reason when HasActiveMerge(taskId)
is true. TaskStateService can't take PlanningMergeOrchestrator as a
direct constructor dependency (circular back to ITaskStateService), so
it takes a lazily-resolved Func<IActiveMergeState> instead, mirroring
the existing Func<ITaskStateService> cycle-break already used for
PlanningChainCoordinator.

UI polish: DetailsIslandViewModel.IsMergeDraining gates
CancelReviewCommand's CanExecute (same shape as
WorktreesOverviewModalViewModel.IsMerging), and the command's catch now
raises ErrorReported instead of swallowing the rejection silently.
2026-08-06 13:38:02 +02:00
mika kuns 5d1d2d89d0 fix(ui): suppress auto-open conflict resolver during MCP-driven merges
review_task/continue_merge on a planning parent always leaves conflicts in
the tree, and the UI auto-opened the resolver on every PlanningMergeConflict
broadcast regardless of who started the merge -- so a running Claude session
resolving a unit-merge conflict could race a human editing the same shared
checkout in a resolver window neither of them asked for.

PlanningMergeOrchestrator.StartAsync now takes an externallyDriven flag (set
by ExternalMcpService's MCP-driven review_task path, left false for the UI's
ApproveReview) that rides along on the PlanningMergeConflict broadcast. The
UI only auto-opens the resolver when it's false; otherwise it shows a
persistent banner with a manual "Open resolver" button, cleared on
PlanningMergeAborted/PlanningCompleted. A new GetActiveExternalConflictsAsync
query (checked against GitService.IsMidMergeAsync rather than the in-memory
flag alone) lets the UI resync the banner on reconnect instead of trusting a
one-shot broadcast that isn't replayed after a restart.

The childless single-task conflict path was checked and needed no change --
it only broadcasts the generic TaskUpdated, never PlanningMergeConflict.
2026-08-06 12:04:52 +02:00
mika kuns b6791265d1 fix(planning): recover the planning session id from the on-disk transcript
The interactive planning TUI never reports its claude session id back, so
planning_session_id stayed NULL and UpdatePlanningSessionIdAsync had no caller
at all -- ResumeAsync always threw "No Claude session ID captured yet".

Resume now looks the id up in the transcript Claude Code writes to
~/.claude/projects/<encoded cwd>/<sessionId>.jsonl for the planning worktree
(newest file wins; a worktree hosts exactly one session) and persists it, so
the next resume is a plain DB read. No transcript -> a clear "cannot resume"
instead of resuming the wrong session.
2026-08-06 09:10:50 +02:00
mika kuns 65db1cdefa feat(planning): let CreateChildTask set maxTurns on child tasks
Planning sessions could already steer a subtask's model but not its turn
budget, so a visibly large subtask would still die at the global default
turn limit. maxTurns is optional (default null = inherit list/global
default, matching model); 0/negative values are rejected as invalid input,
consistent with the existing model-alias validation.
2026-08-05 11:33:16 +02:00
mika kuns 87de53e052 refactor(ui): remove pick-up-in-terminal, keep ConPTY as the single session entry
Two context-menu entries opened a Claude session for the same task via different
mechanisms (embedded ConPTY vs. an external wt terminal). Drop the external-terminal
path entirely, including its worker hub method, launcher plumbing, and localization
keys, since the embedded ConPTY session already covers every case it did.
2026-08-05 09:05:46 +02:00
mika kuns 7517f2a9b3 feat(worker): add continue_merge and abort_merge MCP tools 2026-07-24 14:21:26 +02:00
mika kuns 624ec7a668 fix(planning): use default permission mode so MCP planning tools don't prompt
Interactive planning sessions launched with --permission-mode plan, which
gates EVERY MCP tool call regardless of --allowedTools (verified: even a
read-only mcp__claudedo__list_task_lists is denied under plan mode). So the
session prompted the user on the first CreateChildTask -- the whole point of
a planning session.

Switch BuildPlanningStartArgs/BuildPlanningResumeArgs to --permission-mode
default, which honours the allowlist. File edits stay blocked via the planning
system prompt + AllowedTools omitting Write/Edit/Bash. Resume also re-passes
--allowedTools, since the CLI does not restore it across --resume.

The earlier 'glob does not match' hypothesis was empirically falsified:
mcp__claudedo__*, the bare server name, and the explicit tool name all allow
the tool with zero permission_denials in default mode.
2026-07-24 12:38:45 +02:00
mika kuns ef285b21fd feat(planning): run interactive planning sessions via embedded ConPTY
Planning start/resume opened an external Windows Terminal (wt) window.
Route them through the embedded ConPTY Command Center pane instead, matching
the existing interactive-session UX (no external window).

- Extract bare planning arg builders (BuildPlanningStartArgs/ResumeArgs) from
  WindowsTerminalLauncher; the wt path still uses them (kept, not removed).
- InteractiveLaunchSpecService.BuildPlanningStart/Resume map a planning
  context into a LaunchSpec (planning args + env: MAX_THINKING_TOKENS,
  CLAUDEDO_PLANNING_TOKEN). Hub GetPlanningStart/ResumeLaunchSpec run
  StartAsync/ResumeAsync then return the spec.
- UI: OpenPlanningSession + the resume branch raise OpenPlanningConPtyRequested;
  the shell opens Mission Control and hosts a planning ConPTY pane.

Env is process-global by design (sequential human-paced sessions). wt planning
code retained. Tests added for the arg/env mapping.
2026-07-24 12:12:23 +02:00
mika kuns 14e4c086e2 fix(data): merge preflights ignore untracked files in target working tree
HasChangesAsync counted untracked files, so a stray file in the shared
target working dir (e.g. left by a concurrent session) blocked merge
preflights even though nothing tracked changed. Add an includeUntracked
overload defaulting to true, and pass includeUntracked: false only from
the two target-working-tree merge preflights (TaskMergeService.MergeAsync,
PlanningMergeOrchestrator.StartAsync). Auto-commit and the worktree
cleanup data-loss guard keep counting untracked files, since those
callers need to know about them.
2026-07-23 18:14:46 +02:00
mika kuns 816f247d90 fix(worker): keep planning-chain cascade moving past an Idle middle link
OnChildFinishedAsync ignored CancelAsync's result. If a chain successor
sat in a non-cancellable state (e.g. parked to Idle out of band) when
its predecessor failed/was cancelled, CancelAsync was a silent no-op and
the cascade stopped there, leaving the rest of the chain Queued+blocked
forever. Now it walks past any link CancelAsync can't touch and keeps
cancelling downstream.
2026-07-23 17:09:55 +02:00
mika kuns c412a84fdf refactor(interactive): remove streaming interactive stack (superseded by ConPTY)
The embedded ConPTY terminal replaced the in-app streaming interactive session, so
delete the dead stack: StreamingClaudeSession, InteractiveSessionService,
ProcessClaudeStreamTransport, IClaudeStreamTransport, ILiveSession, LiveSessionRegistry,
IdleSessionReaper (+ WorkerConfig.InteractiveIdleTimeoutMinutes), the WorkerHub
interactive methods + HubBroadcaster events, IWorkerClient interactive members, the
TaskMonitorViewModel composer + SessionTerminalView composer markup, and the old
'Run interactively' entry. AskUser/PendingQuestionRegistry, the autonomous path,
planning, ResumeTaskInTerminal, and all ConPTY code are kept. Localization pruned.
2026-07-23 16:47:16 +02:00
mika kuns 1245e75902 feat(interactive): worker launch-spec for embedded ConPTY sessions
Adds InteractiveLaunchSpecService + GetInteractiveLaunchSpec hub method that
prepares a task worktree (session-skills seeding, run env) and returns a
LaunchSpec {cwd,exe,args,env} for a UI-hosted ConPTY terminal. Reuses
ISessionSkillSeeder, TaskRunner.UnionSkillNames, and WindowsTerminalLauncher
resume-arg/resolve logic. Guards mirror ResumeTaskInTerminal; a never-run task
yields a fresh-start spec instead of an error.
2026-07-23 16:47:15 +02:00
Mika Kuns 140ae2fda1 feat(worker): resume a task's claude session in a terminal 2026-07-23 16:47:13 +02:00
Mika Kuns 865e12c0de fix(worker): seed planning brief via file to avoid newline truncation 2026-07-23 16:47:13 +02:00
Mika Kuns 711374e858 fix(worker): reap idle interactive sessions so they don't pile up
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 51s
Interactive/streaming sessions are persistent claude.exe processes that
wait on stdin and never exit on their own. The only teardown was an
explicit StopInteractiveSession from the UI — there is no client-disconnect
or shutdown sweep — so an abandoned chat (UI closed, navigated away,
crashed) kept its claude.exe (+ conhost) alive for the worker's whole
lifetime. Under a long-running autostart worker these accumulate to dozens
of orphaned child processes.

LiveSessionRegistry now tracks per-session activity (Touch on every output
line and user action) and exposes ReapIdleAsync, which stops sessions idle
past a timeout while skipping any with a turn in flight. IdleSessionReaper
(BackgroundService) sweeps every 5 min; idle timeout defaults to 30 min,
configurable via interactive_idle_timeout_minutes (0 disables).
2026-06-26 16:11:53 +02:00
Mika Kuns fd1e38fb7f feat(worker): remove a queued interactive message
StreamingClaudeSession.RemoveQueuedAsync drops the first occurrence of a queued
message from _pending and re-broadcasts the updated queue. Wired through
InteractiveSessionService + WorkerHub.RemoveQueuedInteractiveMessage +
IWorkerClient.RemoveQueuedInteractiveMessageAsync. Removal by text (first match)
is robust to a turn flushing mid-click. Fakes + ILiveSession impls updated.
2026-06-26 16:11:53 +02:00
Mika Kuns 84034e8395 feat(worker): broadcast interactive message queue + delivery
StreamingClaudeSession raises onQueueChanged (pending snapshot) and onUserMessageSent
(on delivery, incl. the seeded first prompt); InteractiveSessionService forwards these
as InteractiveQueueChanged/InteractiveMessageSent broadcasts. Lets the UI show queued
messages above the input and move a message into the transcript only when actually
delivered to Claude. Client events + fakes updated.
2026-06-26 16:11:52 +02:00
Mika Kuns bdda98eccd feat(worker): queue interactive messages by default, interrupt opt-in
StreamingClaudeSession now buffers a mid-turn user message in a FIFO queue and
flushes one when the turn's result arrives (no implicit interrupt). InterruptAsync
only writes the control_request (no-op when idle); the resulting turn-end then
flushes any queued message. New InteractiveSessionService.InterruptAsync +
WorkerHub.InterruptInteractiveSession + IWorkerClient.InterruptInteractiveSessionAsync.
2026-06-26 16:11:52 +02:00
Mika Kuns 30e87e698e feat(worker): in-app interactive session service, replacing the wt terminal launch
InteractiveSessionService resolves a task's list working dir + seeded prompt,
spawns a StreamingClaudeSession (claude stream-json in the list dir, model+auto
as before), registers it in LiveSessionRegistry, streams output over TaskMessage,
and broadcasts InteractiveSessionStarted/Ended (an exit watcher fires Ended). The
hub's OpenInteractiveTerminalAsync now starts this in-app session; SendInteractiveMessage
and StopInteractiveSession route to it. The external Windows-Terminal interactive
launch (LaunchInteractiveAsync / InteractiveLaunchContext / OpenInteractiveAsync) is
removed; planning sessions keep their terminal launch.
2026-06-26 16:11:52 +02:00
Mika Kuns ea16da2756 fix(worker): keep interactive & planning prompts intact past Windows Terminal
wt.exe treats ';' as a command/tab delimiter in every argument, with no escape
that survives quoting (microsoft/terminal#13264), so a task description
containing ';' spawned extra terminals on "Run interactively" and planning start.
Route the launch as wt -> powershell -> claude and pass the free-text prompt via
$env:CLAUDEDO_LAUNCH_PROMPT so it never reaches the wt command line; PowerShell
binds the variable as a single argument (embedded quotes escaped for PS 5.1).

Also clarify the launcher, which serves interactive runs too (not just planning):
IPlanningTerminalLauncher -> ITerminalLauncher, WindowsTerminalPlanningLauncher ->
WindowsTerminalLauncher, LaunchStart/Resume -> LaunchPlanning{Start,Resume}Async.
2026-06-26 16:11:49 +02:00
mika kunsandClaude Sonnet 4.6 fb1d799b82 fix(worker): stateless AbortPlanningMerge after worker restart mid-merge
PlanningMergeOrchestrator._states is in-memory. A worker restart during a
conflict pause left the list repo mid-merge with no recovery path: both
ContinuePlanningMerge and AbortPlanningMerge threw "no in-progress merge",
and re-Approving failed on the IsMidMergeAsync guard.

AbortAsync now falls through to a stateless path when no _states entry exists:
it looks up the parent's list WorkingDir and, if the repo is mid-merge, runs
git merge --abort there directly, then broadcasts PlanningMergeAborted.
Parent remains WaitingForReview — the next Approve restarts the unit merge
(already-Merged child worktrees are skipped as before).

ContinueAsync error message now points to AbortPlanningMerge as the recovery
action. StartAsync mid-merge guard also carries an actionable hint.

Tests: AbortAsync stateless + mid-merge (restart recovery), AbortAsync
stateless + clean repo (clear error).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:35:08 +02:00
mika kunsandClaude Sonnet 4.6 e9e4ad8fbc fix(worker): route FinalizeParentDoneAsync through TaskStateService
Replaces the direct EF Status write in PlanningMergeOrchestrator with
_state.ApproveReviewAsync, enforcing the TaskStateService invariant as
sole owner of Status writes. Handles the improvement-parent path where
TaskMergeService already approved the parent's own worktree during the
drain (status == Done on entry → still success). If the parent was
concurrently cancelled, the transition guard rejects the approve,
PlanningCompleted is not broadcast, and the cancelled status is
preserved. ApproveReviewAsync now also sets FinishedAt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:13:30 +02:00
mika kunsandClaude Opus 4.8 c27a179d2b feat(worker): let Claude set the cheapest model per generated task via MCP
AddTask, planning CreateChildTask, and SuggestImprovement now accept an
optional alias-validated model (haiku/sonnet/opus; blank = inherit) so the
model is chosen at creation time instead of a follow-up set_task_config call.
The planning, system, and improvement prompts instruct Claude to pick the
cheapest capable model (haiku < sonnet < opus).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:22:17 +02:00
mika kunsandClaude Opus 4.8 f21c65be18 feat(ui): richer diff viewer + surface child roadblocks on parents
Changelog / changelog (push) Successful in 1s
Release / release (push) Successful in 38s
- UnifiedDiffParser detects added/deleted/renamed/binary files; diff
  modal shows a file list, binary/empty placeholders, and can diff a
  merged task by commit range after its worktree is gone
- DetailsIslandViewModel flags children needing attention (failed,
  cancelled, awaiting review, or with roadblocks) on the parent
- GitService gains worktree head-commit/range support; planning chain,
  merge orchestration, and session manager tweaks with updated tests
- refresh app/installer/worker icons

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:40:59 +02:00
mika kunsandClaude Sonnet 4.6 33bdff8a6e fix(worker): harden CLI injection, stuck-Running, chain wedge, and Fail guard
1. ArgumentList (fix injection): ClaudeArgsBuilder.Build() now returns
   IReadOnlyList<string>; ClaudeProcess populates ProcessStartInfo.ArgumentList
   instead of Arguments, so values like system prompts are never shell-split.
   DailyPrepPrompt, RefinePrompt, and WeekReportService migrated similarly.
   All IClaudeProcess fakes updated.

2. ContinueAsync exception guard: wrap RunOnceAsync in try/catch matching
   the RunAsync pattern so an unexpected exception never leaves the task
   stuck in Running status.

3. Planning chain cascade: OnChildFinishedAsync now calls CancelAsync on
   the immediate blocked successor when a child fails or is cancelled,
   triggering a recursive cascade that clears the entire remaining chain
   instead of leaving it wedged.

4. FailAsync guard: restrict valid source states to Running and Queued;
   WaitingForReview -> Failed is now rejected, preventing an invalid
   transition that could corrupt the review workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:05:40 +02:00
mika kuns 469e68bbc8 feat(merge): fold parent branch into combined-diff for improvement parents 2026-06-04 16:53:42 +02:00
mika kunsandClaude Sonnet 4.6 519bfbe6b3 feat(merge): fold parent branch into tree-merge for improvement parents
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 16:09:44 +02:00
mika kunsandClaude Sonnet 4.6 f3052dc5fc feat(mcp): resolve per-run tokens in MCP auth + register TaskRunMcpService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 15:57:12 +02:00
mika kuns 6fdf04d6a0 feat(children): generalize CreateChildAsync for any parent + CreatedBy stamp 2026-06-04 15:32:18 +02:00
mika kuns 1b3c6bdbb4 refactor(prompts): planning prompts read from editable files 2026-06-04 14:09:45 +02:00
mika kunsandClaude Opus 4.7 ce9fadc0b5 refactor: fold single-consumer helper types into their owners
Consolidate small single-purpose types into the files that own them:
StreamResult into StreamAnalyzer, the Planning context records into
PlanningSessionContext, PrimeClock/PrimeSchedulerOptions into PrimeScheduler,
AgentMcpTools into LifecycleMcpTools, the locator subclasses into
InstallArtifactLocator, LogLineViewModel into DetailsIslandViewModel,
RepoImportItemViewModel into its modal, and StepViewModel into InstallPageViewModel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:47:46 +02:00
mika kunsandClaude Opus 4.7 25ee623c42 refactor: remove dead PlanningMergeEvents records and unused RunNowRequestedEvent
The PlanningMergeEvents record types were never instantiated (the broadcaster
uses identically-named methods), and RunNowRequestedEvent had no subscribers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:42:25 +02:00
mika kunsandClaude Opus 4.7 41da124a31 refactor: extract interfaces to Interfaces folders and consolidate filters
Move interface declarations into per-area Interfaces/ subfolders, merge the
small task-list filter classes into StatusFilter/SmartFlagFilter, and simplify
related services, converters and hub DTO handling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:41:10 +02:00
mika kunsandClaude Opus 4.7 ce79a2d0fe feat(planning): gate subtask queueing behind plan finalization
Planning subtasks are now "Draft" until their parent plan is finalized,
then "Planned" (queueable). Finalizing a plan no longer auto-queues the
child chain; the user sends the plan to the queue explicitly.

- TaskStateService rejects a child entering Queued/Running unless its parent
  is Finalized; this single invariant covers UI, queue, RunNow and MCP paths
- WorkerHub.SetTaskStatus routes Queued through the gated EnqueueAsync
- Finalize call sites pass queueAgentTasks: false
- PlanningChainCoordinator.QueuePlanAsync guards the chain build on Finalized
- TaskRowViewModel derives Draft/Planned from ParentFinalized; gates
  CanSendToQueue / CanQueuePlan; view shows a PLANNED badge

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:41:48 +02:00