Commit Graph
270 Commits
Author SHA1 Message Date
mika kuns bd5aa265d1 feat(mission-control): Handler-Run per Knopf abbrechen
Ein "Abandon run"-Knopf an Handler-Panes fragt HandlerRoundState.Get(handlerTaskId)
nach den noch offenen (Queued/Running) Tasks des Laufs, bestaetigt mit Anzahl,
cancelt jeden ueber den bestehenden CancelTaskAsync-Pfad und schliesst danach die
Session -- ein Schritt statt Tile schliessen + Queue separat abbrechen. Ohne
offene Tasks (oder ohne Round-Eintrag) schliesst die Aktion direkt, ohne Dialog.
Nur an den beiden Handler-Panes sichtbar (IsListHandler), normale Task-/Ad-hoc-
Panes bleiben unveraendert.
2026-09-02 22:39:12 +02:00
mika kuns 69898be5c9 feat(usage): PowerUse-Zeitfenster + Handoff-Schwellwert als Settings
Drei neue app_settings-Spalten (Migration AddPowerUseWindow):
power_use_window_start/end (HH:mm, lokal, nullable) und
power_use_handoff_pct (NOT NULL DEFAULT 95, geclampt 50-99).

Die Fensterlogik liegt als reine Funktion in PowerUseWindow
(Contains/Normalize/IsOpen): beide Grenzen leer oder nur eine gesetzt =
rund um die Uhr, Ende vor Start wrappt ueber Mitternacht, Grenzen
halboffen. Geprueft wird in ComposeHandlerPromptAsync, also pro Phase
zum Zeitpunkt des Prompt-Baus - die Uhr ist per Func<DateTime>
injizierbar. Bedienung im Usage-Monitor neben dem PowerUse-Toggle
(Commit auf Blur/Enter, ein Write fuer alle vier Felder); in
Settings->General unsichtbar, aber load->save durchgereicht.

Nebenbefund, gleicher Pfad: AppSettingsRepository.UpdateAsync kopiert
Feld fuer Feld und hat ModelPresets, PowerUseEnabled und
ListHandlerMaxHandoffs nie geschrieben - jeder Save dieser drei war
still wirkungslos. Mit ergaenzt und per Round-trip-Test gepinnt.
2026-09-02 19:09:47 +02:00
mika kuns 20b3b4ce2b docs(claude-do): Handler-Prompt praezisieren (Wave-Plan, Task-Split, review_task-Aufruf) 2026-09-02 18:43:15 +02:00
Mika Kuns 675b36e923 Merge branch 'main' into feat/poweruse-list-handler
Kollision mit #264 (Handoff-Budget im Brief sichtbar machen): beide Seiten haben
HandoffRoundTracker, HandoffMcpTools und InteractiveLaunchSpecService angefasst.

Der Konflikt war inhaltlich, nicht nur textuell. #264 liest den Cap an zwei
Stellen aus der Konstante (GetStatus und das roundInfo-Rendering), waehrend
dieser Branch ihn zum Parameter aus app_settings gemacht hat. Ein reiner
Text-Merge haette kompiliert und trotzdem gelogen: Register haette den
konfigurierten Cap durchgesetzt, waehrend der Brief dem Modell weiter "2 Runden
uebrig" gemeldet haette, obwohl es 39 hat.

Aufgeloest so, dass der Cap eine einzige Quelle hat:
- GetStatus(taskId, maxHandoffs) nimmt den Cap als Parameter, mit demselben
  Clamp wie Register. Die Rechnerei liegt damit an einer Stelle.
- GetHandoffStatus liest das Setting und gibt es weiter.
- Das roundInfo-Rendering benutzt GetStatus statt eigener Arithmetik.

Zwei Tests nageln genau diesen Drift fest, damit er beim naechsten Merge nicht
unbemerkt zurueckkommt: einer auf GetHandoffStatus, einer auf den Brief.

MaxHandoffs -> DefaultMaxHandoffs in #264s vier Test-Referenzen; die laufen alle
mit Default-Settings, die Semantik bleibt.
2026-09-02 15:48:43 +02:00
Mika Kuns 453d61ca58 feat(worker): PowerUse-Addendum an die Handler-Prompts, Cap aus den Settings
HandoffRoundTracker.Register nimmt den Cap jetzt als Parameter statt aus einer
Konstante; HandoffMcpTools liest ihn pro Aufruf aus app_settings. Werte unter 1
werden auf 1 geklemmt (in WorkerHub UND in Register): der Cap ist das Einzige,
was eine entlaufene Handoff-Kette begrenzt, "0 = unbegrenzt" darf kein Aufrufer
waehlen koennen.

InteractiveLaunchSpecService.ComposeHandlerPromptAsync haengt das PowerUse-
Addendum an den Phasen-Prompt, aber nur wenn globaler Schalter und Listen-Opt-in
beide gesetzt sind. Bewusst ein Addendum statt einer zweiten Prompt-Garnitur:
die gemeinsamen 95% bleiben an einer Stelle, und ein Run ohne PowerUse bekommt
einen byte-identischen Prompt wie vorher. Das Addendum ist als Override der
interaktiven Regeln formuliert (nie fragen, entscheiden und protokollieren;
Konflikte loesen oder abbrechen, nie im Tree liegen lassen; Arbeit ernten, wenn
eine Runde sonst leer laeuft).

Drei Tests pinnen alle vier Ecken des UND, weil die Sicherheitsaussage des
Features genau daran haengt.
2026-09-02 15:43:48 +02:00
Mika Kuns 3cf9628f46 feat(data): PowerUse-Flags und konfigurierbares Handoff-Budget
app_settings bekommt power_use_enabled (Master-Schalter, wird im Usage-Monitor
gesetzt) und list_handler_max_handoffs (Default 4 = zwei Merge-Runden).
list_config bekommt power_use_allowed als Opt-in pro Liste.

Ein Handler-Run laeuft nur unbeaufsichtigt, wenn BEIDE Schalter gesetzt sind.
Damit kann der globale Schalter ueber Nacht an bleiben, ohne dass der Handler
einer nicht zugestimmten Liste sein Verhalten aendert.

UpdateListConfigDto.PowerUseAllowed ist bewusst bool? (tri-state, null =
gespeicherten Wert behalten), analog zu TicketProjectId: SetConfigAsync kopiert
verbatim, ein non-nullable bool haette jedem fremden Writer erlaubt, das Opt-in
per Auslassung zu widerrufen. Der Test dazu nagelt genau das fest.

Migration ist drei reine ALTER TABLE ADD COLUMN, kein Table-Rebuild.
2026-09-02 15:43:32 +02:00
Mika Kuns 91a37984c6 feat(claude-do): merge Handoff-Budget im Brief sichtbar machen und pro Task mitzähl
ClaudeDo-Task: bdcd601a-2fed-44d8-9164-6d2d008e7f31
2026-09-02 15:28:59 +02:00
Mika Kuns c25732cff8 feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte
tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies
ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das
alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder
pickbar). BlockedByTaskId bleibt unberuehrt.

- Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id
  verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird
- QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten
  wird blockiert, alle Kanten muessen done sein, eine Kante auf einen
  verschwundenen Task blockiert weiter
- SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine
  BFS ueber den Graphen, faengt also auch A->B->C->A
- blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von
  ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt)
- add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt
  als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig
- UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label,
  die lineare Kette rendert unveraendert
2026-09-02 15:08:05 +02:00
Mika Kuns 81a5fc5d33 feat(worker): surface the handoff round budget in the merge-helper handoff brief
A handler session only learned the chain was capped when handoff_list_handler
threw at the end of its work, stranding open tasks. The round count already
lived in HandoffRoundTracker the whole time -- it just never reached the
brief. InteractiveLaunchSpecService now reads HandoffRoundTracker.GetState
(read-only, doesn't advance the round) and renders round N of MaxHandoffs
plus the remaining count into the handoff brief, replacing the old one-line
finalNote. The rejection error also names the next action (start a new
handler run) instead of just failing.
2026-09-02 14:26:56 +02:00
Mika Kuns 6c1ea1ae25 feat(claude-do): Merge-Session soll die Queue selbst beobachten statt fertige
## WO — verifiziert am 2026-09-02, nicht neu suchen

- Merge-Phasen-Prompt: `src/ClaudeDo.Data/PromptFiles.cs`, Konstante `MergeHelperMergeDefault` (ab Z. 495). Der neue Warte-Abschnitt gehört zwischen "## Verify once — after the last merge" (Z. 526) und "## Handoff / end" (Z. 542) — also nach dem Verify, vor der Handoff-Entscheidung.
- Brief-Template: `MergeHelperInitialDefault` (Z. 555) und `Mer

ClaudeDo-Task: 1788adc4-c671-4942-ab78-1ef1812da494
2026-09-02 13:49:58 +02:00
Mika Kuns 244ba9b72f chore: merge main into task branch 2026-09-02 13:13:59 +02:00
Mika Kuns 0b506f529a feat(data): add ExpectsNoDiff flag for verification/audit tasks
An empty diff from a review/audit/verification task looks identical to
one that silently failed to commit. ExpectsNoDiff (TaskEntity, add_task/
update_task, preview_merge_set, review_task via TaskRefDto) lets the
merge helper tell them apart instead of treating every 0-changed-files
result as suspicious.
2026-09-02 13:13:13 +02:00
Mika Kuns 651ddbc31c chore(claude-do): merge feat(worktrees): warn about git worktrees ClaudeDo doesn't k
ClaudeDo-Task: 765fed13-db66-44e4-9285-c4b2d415b443
2026-09-02 13:01:57 +02:00
Mika Kuns 9e80564665 feat(data): add dependency audit and diff-review threshold to list-handler prompts
Triage phase now audits dependsOnTaskId chains before handoff: distinguishes real
from artificial (plan-numbered) dependencies, resolves artificial ones via
update_task, groups survivors into a wave plan, and checks the wave count against
the 4-handoff cap (2 merge rounds) so a chain too long to finish says so explicitly
instead of stranding mid-run.

Merge phase now reads a task's own diff when preview_merge_set's changedFileCount
is below 5, reserving the sonnet review subagent for larger diffs where the
context-protection cost is actually worth it.

Updates DefaultFor_merge_helper_merge_never_reads_diffs_itself, renamed to
DefaultFor_merge_helper_merge_gates_the_diff_subagent_on_a_file_count_threshold,
to match the new threshold behavior.
2026-09-02 12:16:44 +02:00
Mika Kuns 25ead4c8dd chore(claude-do): feat(worktrees): warn about git worktrees ClaudeDo doesn't k
From `docs/open.md` (re-verified against the code 2026-09-02, still open): nothing reconciles `git worktree list` against the `worktrees` table. `ExternalMcpService.ListWorktrees` and the Worktrees overview read DB rows only; `WorktreeManager` calls git solely inside its self-heal path (`ListWorktreePathsForBranchAsync`). Observed once: 22 git-registered worktrees + 26 `claudedo/*` branches in `C:

ClaudeDo-Task: 765fed13-db66-44e4-9285-c4b2d415b443
2026-09-02 12:09:12 +02:00
mika kuns 6be62f02cc refactor(ui): remove the stale-worker banner
The banner compared the running worker's build SHA against the
selected list's HEAD via a git ancestor check on every list switch
and reconnect. It added a git-rev-parse MSBuild target, a hub call,
and a poll loop for a corner case that hasn't come up in practice —
not worth the moving parts.
2026-09-02 09:59:39 +02:00
CubeGameLPandClaude Opus 5 ae0c7d3771 feat(notes): add_note MCP tool with a per-day agent cap
Agents can write into the user's daily notes, but only within limits:
max 10 agent notes a day, max 500 characters, add only — no edit, no
delete. A new from_agent column separates their lines from the user's
so the cap counts the right ones and the UI can mark them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:02:37 +02:00
CubeGameLP cf093e3178 docs: empty-repo hardening notes + verification item
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit d771dbf03575bb60e692ede44110ff17c3043f39)
2026-08-28 20:34:24 +02:00
CubeGameLP de44196b9a fix(git): bootstrap commit-less repos and survive missing git identity
A fresh 'git init' repo (unborn HEAD) failed every worktree-based flow with raw
git stderr ('ambiguous argument HEAD'). EnsureHeadCommitAsync now creates an
empty initial commit via plumbing only (mktree/commit-tree/update-ref) so the
user's index and working tree are untouched; broken HEADs get a clear error.
CommitAsync retries once with a ClaudeDo identity when the machine has none,
and RevParseHeadAsync reports 'no commits yet' instead of raw git output.
Also: git stdin is now BOM-less UTF-8 (git rejects BOM-prefixed input).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 21af231f3e7b2cf056efe475916627f0cfeeb5c6)
2026-08-28 20:33:45 +02:00
mika kuns c31f79d3d4 feat(ui): wählbares Listen-Icon und aufgeräumter Task-Detail-Header
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 41s
Listen bekommen eine Icon-Spalte (Migration AddListIcon) und einen Icon-Picker
in den Listeneinstellungen; 34 kuratierte Geometrien aus der Icon-Bibliothek,
Fallback auf "Folder" bei unbekanntem Key.

Der Metadata-Footer im Task-Detail entfällt; created-at wandert in die
Header-Zeile neben das ID-Badge, der Close-Button (und CloseDetailsCommand)
fällt weg.
2026-08-28 14:17:38 +02:00
Mika Kuns 8d597df448 fix(worker): make re-approve after revert_merge do a real merge, not a silent no-op
ApproveAndMergeAsync treated any non-Active worktree (including Kept, the
state RevertMergeAsync leaves behind) the same as "never had a worktree",
so re-approving a reverted task ran only the verify gate and marked the
task Done without merging anything back.

Now a Kept worktree is distinguished: if its branch is gone (the common
case, since approve normally deletes it), approve returns Blocked instead
of a fake "merged". If the branch survives, its tip is already an ancestor
of the target (from the original merge), so a plain `git merge` would
silently no-op ("Already up to date."); cherry-picking the original merge
commit's own diff (-m 1) is what actually restores the content, recorded
as a fresh MergeCommit.
2026-08-28 14:17:38 +02:00
mika kuns 84219a9f88 refactor: Datei-Scope-Serialisierung entfernen
ScopeGlobs auf Tasks und SerializeOnFileOverlap auf der Listen-Config waren
ungenutzt: der Scope wurde nie befuellt, also hat der Queue-Picker nie
serialisiert. ScopeOverlap, das Picker-Gate, die DTO-Felder, die UI-Option und
die Spalten fallen weg (Migration DropFileScopeSerialization).
2026-08-27 16:43:14 +02:00
mika kuns fb1ceac192 docs: Ticketsystem-Anbindung in den CLAUDE.md-Dateien
Tickets/-Ordner, TaskStateService.NotifyAsync, ticket_api_base_url und TicketRef/
TicketProjectId dokumentiert. Verbatim-Copy-Warnung um TicketProjectId ergänzt und
den PermissionMode-Vorbestandsbug (SetConfigAsync liess das Feld im Update-Zweig
aus, gefixt in f0a3a186) vermerkt. docs/open.md um die fuenf offenen Punkte aus der
manuellen Verifikation des Specs ergänzt, inkl. Hinweis dass die Bandel-Response-
Shapes nie live verifiziert wurden.
2026-08-27 13:40:29 +02:00
mika kuns f0a3a186dd feat(hub): Ticket-Settings, Projektliste und Import ueber den Hub 2026-08-27 13:22:54 +02:00
mika kuns 3171690bae feat(data): ticket_ref auf tasks, ticket_project_id auf list_config 2026-08-27 12:17:59 +02:00
mika kuns d474fd13ea docs: neue Config-Felder + Migration-Fixture-Falle in den CLAUDE.md-Dateien 2026-08-27 10:40:26 +02:00
mika kuns 13b81cac68 feat(settings): claude_bin im Dateien-Tab setzbar, mit Auflösungs-Check
Bisher nur per Hand in worker.config.json. Neuer Abschnitt CLAUDE CLI mit
eigenem Save-Button (die Datei gehoert dem Worker, nicht app_settings) und einer
Zeile, wohin der Wert per ExecutableResolver tatsaechlich aufloest — 'nicht im
PATH gefunden' war bisher erst am fehlgeschlagenen Run zu sehen. WorkerConfig ist
DI-Singleton und ClaudeProcess loest pro Spawn auf, also greift die Aenderung ab
dem naechsten Run ohne Neustart.

SaveOnlineInbox/SaveClaudeBin teilen jetzt ein SaveKey(), damit beide dieselbe
read-modify-write-Semantik haben (alle anderen Keys bleiben unberuehrt).
Die zugehoerigen Locale-Keys sind im vorigen Commit mitgelaufen.
2026-08-27 10:39:25 +02:00
mika kuns 4464dc6ff1 feat(config): Permission-Modus pro Liste und pro Task ueberschreibbar
Bisher gab es nur AppSettings.DefaultPermissionMode global — ein Task, der plan
oder acceptEdits braucht, erzwang das Umstellen der globalen Einstellung. Neue
Spalten tasks.permission_mode und list_config.permission_mode, Auflösung
task -> list -> global im EffectiveRunConfigResolver (den TaskRunner und
get_effective_run_config gemeinsam nutzen), ComboBox mit Inherited-Badge im
geteilten Agent-Editor.

MigrationBaselineTests: das Fixture baute per EnsureCreated das heutige Schema
und stempelte Legacy-History darauf — jede Migration nach dem Squash lief damit
in 'duplicate column name'. Es migriert jetzt gezielt bis InitialCreate und
prueft 'nichts pending' statt 'InitialCreate ist die einzige Zeile'.
2026-08-27 10:28:57 +02:00
mika kuns 5cc99bfec6 feat(ui): Listen-Einstellungen — Checkbox fuer serialize_on_file_overlap
Das Flag war bisher nur ueber MCP set_list_config erreichbar. UpdateListConfigDto
fuehrt es tri-state (null = gespeicherten Wert behalten), damit nur das
Listen-Modal es setzen/loeschen kann und kein anderer Aufrufer es per Omission
verliert.
2026-08-27 10:15:17 +02:00
Mika Kuns 89f0371279 fix(claude-do): merge Modify/Delete- und Rename-Konflikte im Merge sichtbar machen
ClaudeDo-Task: 059bcf98-34d5-4ca0-822d-317980fadf8d
2026-08-26 15:46:51 +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 533a287eb9 fix(worker): make modify/delete and rename/delete merge conflicts visible instead of silently staging them
ConflictMarkerParser only recognizes text-marker conflicts, so a modify/delete or
rename/delete conflict (git never writes markers for those) was invisible to the
resolver: GetConflictDocumentsAsync read it as an already-resolved stable file, and
ContinueMergeAsync's marker scan let it through, so `git add` on the unmerged path
silently kept whichever side wasn't deleted.

GetConflictDocumentsAsync now tells a modify/delete-style conflict (one index stage
missing) apart from an ordinary already-resolved file (both stages present) and
synthesizes a real whole-file conflict block from the index stages so it shows up in
the resolver. ContinueMergeAsync compares such a path against git's own default
checkout content before treating it as resolved, so an untouched file blocks the
merge instead of being silently staged. Resolving to the deleted side (empty content)
now runs `git rm` instead of staging a tracked, zero-byte file.
2026-08-26 15:25:42 +02:00
Mika Kuns c4928b4def chore: move appdata folder to .claudeDo 2026-08-26 14:26:19 +02:00
mika kuns 8930b564e4 refactor(ipc): declare the SignalR wire DTOs once in ClaudeDo.Data
WorkerHub and IWorkerClient each carried their own copy of every record and
had already drifted — MergePreviewDto lost its verify fields on the client
side, two records disagreed on their name. They now live in Data/Wire.cs and
reach both sides via a <Using> item, so a hub signature change is a compile
error instead of a silently dropped JSON field.
2026-08-26 13:55:40 +02:00
mika kuns 39d8241360 refactor(data): squash the 40 migrations into one InitialCreate
The incremental chain was ~31k lines of generated Designer snapshots for a
schema no shipped database steps through anymore. MigrateAndConfigure now
baselines an existing DB onto the squashed id (pre-EF, or history ending at
AddPrimeActionKind) and throws with a reinstall message for a mid-chain DB.
MigrationBaselineTests covers all three paths with a real Migrate().
2026-08-26 13:55:30 +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 81994aadca refactor: delete unreachable members and redundant package refs
Eight public members had no caller anywhere in src: GitService.GetFileDiffAsync,
SubtaskRepository.DeleteByTaskIdAsync, TaskRepository.GetByListAsync (a
backwards-compat alias for GetByListIdAsync) and .GetByCreatorAsync,
WorktreeRepository.GetByStatesAsync, TaskMonitorViewModel.SetPendingQuestion
(a duplicate of the live-event lambda), PrimeClaudeTabViewModel.ApplyFiredEvent,
and StreamLineFormatter.FormatFile. TaskAttachmentRepository.DeleteAllForTaskAsync
was reachable only from its own test; the ON DELETE CASCADE on task_attachments
already covers it. Tests for the deleted members go with them.

Dropped two package refs the platform already provides: EntityFrameworkCore.Design
in Worker (the design-time factory and the migrations live in Data, which has its
own ref) and System.IO.FileSystem.AccessControl in Installer.Tests (net8.0-windows
ships the ACL APIs in the shared framework).
2026-08-26 10:11:46 +02:00
mika kuns a1d74ab45d docs(prime): document the schedule action kinds 2026-08-25 09:12:10 +02:00
mika kuns 71d2972a97 feat(prime): migrate prime_schedules with action_kind defaulting to ping 2026-08-25 08:33:17 +02:00
mika kuns 022eaf385c feat(prime): add PrimeActionKind to the schedule entity 2026-08-25 08:29:40 +02:00
Mika Kuns 79438c00d2 feat(Data): Improve App Prompts 2026-08-24 09:25:43 +02:00
mika kuns 3dfae30fff fix(worker): Usage-Limit-Auto-Continue verbrennt nicht mehr alle Kandidaten außer dem ersten
OverrideSlotService hält genau einen Slot, und ein Usage-Limit stoppt
naturgemäß alle laufenden Tasks gleichzeitig — "mehr Kandidaten als
Kapazität" ist also der Normalfall. Der Coordinator stempelte
UsageLimitAutoContinuedAt aber VOR dem ContinueTask (der Marker ist die
Dedupe-Guard), sodass Kandidat 2..n den Marker bekamen, deren
ContinueTask mit "override slot busy" in den catch flog und sie danach
dauerhaft aus GetUsageLimitAutoContinueCandidatesAsync ausgeschlossen
waren — ein Continue, der nie lief.

- Vorab-Check auf CurrentSlot: bei belegtem Slot bricht der Tick ab,
  statt die restlichen Kandidaten zu verbrennen.
- Neuer TaskRepository.ReleaseUsageLimitAutoContinueClaimAsync gibt den
  Claim zurück, wenn ContinueTask wirft; danach return, der 30s-Backstop
  holt den Rest im nächsten Tick.
- Regressionstest mit zwei Kandidaten, deterministisch über eine
  TaskCompletionSource im FakeClaudeProcess (StartInSlot setzt _slot
  synchron unter dem Lock, bevor die Arbeit startet).
2026-08-24 09:05:20 +02:00
mika kuns 07dd75700d feat(worker): Toggle "Continue on session limit reset" für Usage-Limit-Abbrüche
Klassifiziert einen echten Usage-Limit-Abbruch als eigene FailureReason
"usage_limit" (TaskRunner.ClassifyFailureReason: nur bei terminal_reason
"api_error" plus einem Limit-Muster im gerenderten Fehlertext, nicht an
Status==Failed allein). Neuer Toggle AutoContinueOnUsageLimit (app_settings,
Default aus) unter Settings → General → "Usage limit stop":

- UsageLimitAutoContinueCoordinator feuert pro Task genau einmal ContinueTask
  über OverrideSlotService, sobald das 5h-Fenster (UsageState.Snapshot.FiveHour
  .ResetsAt) tatsächlich zurückgesetzt ist; ein persistenter Marker
  (TaskEntity.UsageLimitAutoContinuedAt) verhindert einen zweiten Anlauf bei
  einem erneuten Limit-Treffer.
- QueueService schedult zusätzlich einen exakten Wake-Timer auf den
  Reset-Zeitpunkt, statt nur auf den 30s-Backstop zu warten.
- Fail-open durchgängig: kein Snapshot/keine Reset-Zeit → kein Timer, kein
  Continue, kein Throw. Toggle aus ändert das heutige Verhalten nicht.

Migration AddUsageLimitAutoContinue fügt beide Spalten hinzu; die von
`dotnet ef migrations add` mitgescaffoldete leere UpdateData auf app_settings
(columns/values: []) erzeugte ungültiges SQL ("near WHERE") und wurde entfernt
— TaskNumberMigrationTests deckte das über den vollen Migrate()-Pfad auf.
2026-08-21 18:43:02 +02:00
mika kuns 9c4a63d2c5 fix(worker): thread handler task id into list-handler kickoff prompts
Neither the initial nor the handoff kickoff ever told a list-handler
session its own handler task id, so handoff_list_handler(taskId, ...)
was unrenderable -- the handoff chain broke exactly where it was needed
(#200/#201 on 2026-08-21).

Add {handlerTaskId} to both MergeHelperInitialDefault and
MergeHelperHandoffDefault, thread a handlerTaskId parameter through
BuildForMergeHelperAsync (interface, WorkerHub.GetMergeHelperLaunchSpec,
IWorkerClient/WorkerClient, and the MissionControlViewModel call site,
which already had the id from CreateMergeHelperTaskAsync but never
passed it on), and render it in BuildForMergeHelperHandoffAsync from the
taskId parameter it already receives.

RenderTemplate leaves unknown/missing tokens untouched, so a
user-edited override without the new token still renders fine -- no
forced migration for override users.
2026-08-21 18:00:16 +02:00
mika kuns ea2271cc3e feat(mcp): update_task kann isManual setzen + List-Handler stellt seinen Task auf Review
Teil A: update_task bekommt einen optionalen isManual-Parameter (null =
unveraendert); TaskDto/TaskRefDto spiegeln IsManual zurueck.

Teil B: neuer submit_task_for_review MCP-Tool (LifecycleMcpTools) laesst
einen Handler-Task selbst auf WaitingForReview gehen -- fuer einen
worktree-losen Task wird HandlerHeadCommit vom aktuellen HEAD gestempelt,
sonst werden offene Aenderungen committet. Die Submit-Logik ist aus
WorkerHub.SubmitTaskForReview in InteractiveReviewSubmissionService
extrahiert; der Hub ruft sie jetzt nur noch auf. Der Merge-Prompt-Default
weist die Endzweige (merge ohne Rerun, merge_final) an, den eigenen
Handler-Task nach der Summary einzureichen.
2026-08-21 16:51:18 +02:00
mika kuns b656a241a2 fix(worker,data): honor wait_for_task_change's NotFound contract, harden TaskNumberAllocator
wait_for_task_change resolved #<number>/bare-number ids up front via TaskIdResolver, which
throws for an unknown number -- breaking the tool's own documented promise that an unknown
id reports status "NotFound" instead of failing the whole call. Resolve per id and fall back
to the original id on a resolution failure so CheckOnceAsync can still report it.

TaskNumberAllocator.AddWithNumberAsync indexed into the app_settings UPDATE...RETURNING result
without checking for an empty result, throwing on a missing singleton row; it also caught any
DbUpdateException as a number collision, burning up to 5 numbers on an unrelated failure (e.g.
FK violation) before the real error surfaced. Now recreates the missing row and only retries on
the actual unique-index collision (SQLite error 19 on tasks.number), rethrowing everything else
immediately.

Audited the other TaskIdResolver.ResolveAsync/ResolveManyAsync call sites (ExternalMcpService,
HandoffMcpTools, ConfigMcpTools, RunHistoryMcpTools, AttachmentMcpTools, LifecycleMcpTools,
BatchMcpTools): none of their tool descriptions promise a found/NotFound flag for the id itself
(BatchMcpTools.BatchGetTasks already handles this correctly via its own per-id try/catch;
PreviewMergeSet promises a per-task "error" field, not a found/NotFound flag; the rest are
single-id tools that already throw on a missing task downstream) -- left throwing behavior as-is.
2026-08-11 16:57:23 +02:00
mika kuns e8e2fcec3f docs: update for task-numbers features (Slice 5)
Document task-number allocation in Data layer (TaskEntity.Number,
next_task_number counter, invariants, insert paths).

Update Worker docs to clarify TaskIdResolver wiring (#123 → GUID lookup),
Number in MCP payloads, and correct the 'Two hard conventions' statement
(only the first is test-enforced).

Bump external-mcp.md verified-against commit to 38af549 (Slice 4 merge)
and add new sections on task ID resolution and numbering.

Add Slice 4 visual verification items to open.md (row/detail number display,
worker-log messages).

Verified against:
- src/ClaudeDo.Data/TaskNumberAllocator.cs
- src/ClaudeDo.Data/Repositories/TaskRepository.cs (AddAsync line 20, CreateChildAsync line 276)
- src/ClaudeDo.Worker/External/TaskIdResolver.cs
- src/ClaudeDo.Worker/External/ExternalMcpService.cs (TaskDto/TaskRefDto DTOs)
- tests/ClaudeDo.Worker.Tests/External/ExternalMcpToolSchemaTests.cs (only first convention test-enforced)
2026-08-11 14:26:22 +02:00
mika kuns 9e46c96b24 feat(data): add task numbers schema, allocator, and backfill migration
TaskEntity.Number is a global, monotonically increasing, never-reused
integer (displayed as #123), allocated from AppSettingsEntity.NextTaskNumber
via a single UPDATE...RETURNING statement rather than MAX(number)+1, which
would reissue a deleted task's number. Both insert paths (TaskRepository.
AddAsync and CreateChildAsync) route through the new TaskNumberAllocator,
with a bounded retry on a unique-index collision. One migration adds the
columns, backfills existing rows in creation order, and creates the unique
index afterwards. Data-layer only; MCP/UI wiring is later slices.
2026-08-11 10:49:06 +02:00
mika kuns f2609d186a feat(worker): wire list-handler phase parameter through handoff chain
Threads a nextPhase parameter (wait/merge/wait_final/merge_final, validated
by the new MergeHelperPhase) from handoff_list_handler through
HubBroadcaster/WorkerHub into InteractiveLaunchSpecService, which now picks
the next session's system prompt (MergeHelperWait/MergeHelperMerge) and
model (HandlerWaitAlias/HandlerMergeAlias) from it instead of hardcoding the
old two-phase Execute prompt -- this also fixes a build break left by the
prior prompt-split task, which removed PromptKind.MergeHelperExecute without
updating its only caller.

Also sets --model/--effort/--permission-mode explicitly for every list-handler
session (Triage included) via PermissionModeResolver instead of inheriting the
CLI's ambient model and hardcoding "auto", and adds Task to the merge-helper
allowlist so the Merge phase can delegate diff reviews to subagents.
2026-08-11 09:27:16 +02:00
mika kuns d3b91b5214 feat(data): split list-handler prompt into triage/wait/merge roles
Adds HandlerTriageAlias/HandlerWaitAlias/HandlerMergeAlias to ModelRegistry and
replaces PromptKind.MergeHelperExecute with MergeHelperWait (phase 3 only) and
MergeHelperMerge (phases 4-5), so the list handler can run as three
cost-scoped sessions instead of two. Triage's Phase 2 now checks/sets a wide
verify command via get_list_config/set_list_config once per run. Merge now
merges before reruns, delegates diff review to a sonnet subagent, rejects
0-file diffs, tracks the merged-but-not-Done verify-gate outcome, and caps
reruns at one per task via handoff_list_handler's new nextPhase parameter.

InteractiveLaunchSpecService.cs still references the removed
PromptKind.MergeHelperExecute and fails to build -- wiring the Worker/UI side
onto the new roles is a follow-up task.
2026-08-11 08:52:28 +02:00