Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3ecb99492 | ||
|
|
a2496e056a | ||
|
|
44ec89b0e6 | ||
|
|
a7c3d18533 | ||
|
|
0d029717f8 | ||
|
|
2be8ba5d66 | ||
|
|
928e3905d9 | ||
|
|
60358100be | ||
|
|
2a41f7e641 | ||
|
|
bb92dc279f | ||
|
|
8a31a67c74 | ||
|
|
f2ff865dca | ||
|
|
5da8036b9a | ||
|
|
cf4b5c507c | ||
|
|
3e2d5bab6a | ||
|
|
27d2b1d8ef | ||
|
|
ccd11a8033 | ||
|
|
9991f1fc1f | ||
|
|
75b78cb2c0 | ||
|
|
05deda8343 | ||
|
|
52ba538339 | ||
|
|
2558e02ba3 | ||
|
|
81dcaebfba | ||
|
|
1396649519 | ||
|
|
f88d2a898e | ||
|
|
87dbb117d7 | ||
|
|
0f82fa67e1 | ||
|
|
9f2e63d337 | ||
|
|
3009ff0f8f | ||
|
|
0dac047c46 | ||
|
|
57dd676672 | ||
|
|
352aaa525b | ||
|
|
c8b2ad046a | ||
|
|
d3db4455fa | ||
|
|
216620c52e | ||
|
|
08d1797ead | ||
|
|
e0a1448f44 | ||
|
|
c9dd885fe0 | ||
|
|
31dfaffc8b | ||
|
|
09bcd696dd | ||
|
|
86cc309ba9 | ||
|
|
684b0115e5 | ||
|
|
1325ab9438 | ||
|
|
56161e4e9b | ||
|
|
4ff5383b35 | ||
|
|
5cdfb14ff1 | ||
|
|
7452f6cfe4 | ||
|
|
c874bf7e52 | ||
|
|
50b6b56f87 | ||
|
|
9a41bed0f5 | ||
|
|
103eb9a687 | ||
|
|
7ecb1fa0b5 | ||
|
|
18394dd6b2 | ||
|
|
251b70ce83 | ||
|
|
8e2d37e63e | ||
|
|
edcaee77dc | ||
|
|
bfb1c6523c | ||
|
|
1770b67713 | ||
|
|
3d09323ee1 | ||
|
|
7c399b26ba | ||
|
|
d6696c3317 | ||
|
|
73e038edd9 | ||
|
|
9d22c775f2 | ||
|
|
407bc30554 | ||
|
|
5ecf6f750a | ||
|
|
eb62371430 | ||
|
|
395874d834 | ||
|
|
29ea18c427 | ||
|
|
1bdf20272d | ||
|
|
7eba0affbe | ||
|
|
d2648f965c | ||
|
|
897f287ee1 | ||
|
|
1a5728749b | ||
|
|
e628d97d29 | ||
|
|
0cec0a77ee | ||
|
|
acff815706 | ||
|
|
8e6f0d1605 | ||
|
|
bd393a0599 | ||
|
|
b4b6225432 | ||
|
|
46a0a27620 | ||
|
|
423d39e9fb | ||
|
|
951c2fe92a | ||
|
|
6f1fbe5b93 | ||
|
|
cfda3e9044 | ||
|
|
2a34d2453f | ||
|
|
451c01848c | ||
|
|
0235b43bad | ||
|
|
3348736e9d | ||
|
|
752cf0926b | ||
|
|
4cf601e423 | ||
|
|
c1b133ff90 | ||
|
|
256bea5ffa | ||
|
|
2b0562b2ed | ||
|
|
741f66fe14 | ||
|
|
2d67ca3171 | ||
|
|
0fcfe7c524 | ||
|
|
5be972564a | ||
|
|
1c76852957 | ||
|
|
c9d4823f06 | ||
|
|
dbee4a0de9 | ||
|
|
1177463a82 | ||
|
|
5477ae5302 | ||
|
|
a0a85c5c24 | ||
|
|
25c9a619dc | ||
|
|
13e860f06b | ||
|
|
ec1e60be1d | ||
|
|
f231f08a27 | ||
|
|
23491d3b49 | ||
|
|
0db9fadee9 | ||
|
|
7c7c33d677 | ||
|
|
c7392e085f | ||
|
|
c16c7b97ef | ||
|
|
cccfe2eca1 | ||
|
|
a8ec550740 | ||
|
|
40d2537486 | ||
|
|
f70503d038 | ||
|
|
9360cd2fe6 | ||
|
|
ee502883f8 | ||
|
|
e64d570554 | ||
|
|
fc25ee45d1 | ||
|
|
51ee505057 | ||
|
|
06bc3d2105 | ||
|
|
bd5aa265d1 | ||
|
|
b298594a2c | ||
|
|
62855ac575 | ||
|
|
18b4dcd56f | ||
|
|
b02e0f80f0 | ||
|
|
df3e7ba0c7 | ||
|
|
a1b20d9f27 | ||
|
|
e0c6b670cb | ||
|
|
2bd48ea1c7 | ||
|
|
77c70d74cb | ||
|
|
f24654dfb8 | ||
|
|
b05b6fbaec | ||
|
|
eb3aee41a4 | ||
|
|
2b8733227d | ||
|
|
d258ab4164 | ||
|
|
574edf6c1a | ||
|
|
fe8186514f | ||
|
|
c2166a0d4f | ||
|
|
c3a418f077 | ||
|
|
2ec3af96d5 | ||
|
|
016b9203f8 | ||
|
|
69898be5c9 | ||
|
|
20b3b4ce2b | ||
|
|
42356241e3 | ||
|
|
675b36e923 | ||
|
|
416997d7e0 | ||
|
|
193752540b | ||
|
|
453d61ca58 | ||
|
|
3cf9628f46 | ||
|
|
b00bdf28ae | ||
|
|
91a37984c6 | ||
|
|
7ee7e33654 | ||
|
|
c25732cff8 | ||
|
|
2330b426c6 | ||
|
|
81a5fc5d33 | ||
|
|
84d955dfc7 | ||
|
|
20685827fb | ||
|
|
4ee1d05f06 | ||
|
|
6c1ea1ae25 | ||
|
|
7e847638cf | ||
|
|
aadc926ffe | ||
|
|
c2ef446fe1 | ||
|
|
7bcca3647e | ||
|
|
244ba9b72f | ||
|
|
0b506f529a | ||
|
|
7a0b97df59 | ||
|
|
a0c277100e | ||
|
|
651ddbc31c | ||
|
|
477a216567 | ||
|
|
6a4fa15372 | ||
|
|
411eb7487b | ||
|
|
ce70ba1c46 | ||
|
|
21769eb928 | ||
|
|
9b3f7ac5c8 | ||
|
|
25f6691aa0 | ||
|
|
dc1e67d07b | ||
|
|
912201770f | ||
|
|
9f90e75a85 | ||
|
|
9e80564665 | ||
|
|
b24ff614ff | ||
|
|
25ead4c8dd | ||
|
|
e099d4f6cf | ||
|
|
4c78a5d6fd | ||
|
|
56bc1651a8 | ||
|
|
80fe188a48 | ||
|
|
c347e4fc5f | ||
|
|
6be62f02cc | ||
|
|
b6e5876b07 | ||
|
|
30d0611b8e | ||
|
|
f640580463 | ||
|
|
b90c5855f4 | ||
|
|
0f04b796f4 | ||
|
|
ae0c7d3771 | ||
|
|
cf093e3178 | ||
|
|
e0748ba351 | ||
|
|
de44196b9a | ||
|
|
c259254921 | ||
|
|
fea8236e8a | ||
|
|
1d6cd49beb | ||
|
|
c31f79d3d4 | ||
|
|
22e70ffcb4 | ||
|
|
8d597df448 | ||
|
|
1affb39716 | ||
|
|
bbdb022327 | ||
|
|
ecb1cdbb06 |
+356
@@ -1,5 +1,361 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.17.0 — 2026-09-28
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- allow nested sidebar folders via drag and drop (0dac047c)
|
||||||
|
- UI-Prozess loggt auf Platte (ui-.log) + fehlgeschlagene Hub- (57dd6766)
|
||||||
|
- Mission Control Recent Sessions mit verschachtelten Handoffs (d3db4455)
|
||||||
|
- Mission-Control-Kacheln behalten Position und Identitaetsfarbe ueber Handoffs (e0a1448f)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Interactive sessions no longer Instantly start working (87dbb117)
|
||||||
|
- snake_case Serilog tokens + no Warning-log on cancelled hub calls (3009ff0f)
|
||||||
|
- DeleteTask aus der UI schlug immer mit generischem Serverfehler fehl (352aaa52)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.16.0 (c9dd885f)
|
||||||
|
|
||||||
|
## v2.16.0 — 2026-09-16
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- dirty Ziel-Verzeichnis beim Oeffnen des Merge-Dialogs melden statt nach dem Fehlschlag (09bcd696)
|
||||||
|
- preview_merge_set flags removed symbols and locale-key overlaps (684b0115)
|
||||||
|
- Bilder aus der Zwischenablage ins interaktive Terminal einfuegen (56161e4e)
|
||||||
|
- poll the OAuth usage endpoint as fast as its measured rate limit allows (50b6b56f)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- duplizierter Confirm-Dialog mit "Delete"-Knopf – zusammenfuehren und lokalisieren (86cc309b)
|
||||||
|
- Worktrees-Uebersicht zeigt Task-Nummern, DIFF-Spalte trimmt statt abzuschneiden (1325ab94)
|
||||||
|
- Diff-Viewer scrollt jetzt auch horizontal synchron (4ff5383b)
|
||||||
|
- Ordner-Toggle in der Sidebar schluckt schnelle Klicks (5cdfb14f)
|
||||||
|
- preview_merge braucht einen runVerify-Parameter wie preview_merge_set (7452f6cf)
|
||||||
|
- depends_on-Nachfolger schon beim gelandeten Merge freigeben (c874bf7e)
|
||||||
|
- Worker-Neustart trifft nun den tatsaechlich laufenden Prozess (9a41bed0)
|
||||||
|
- Shutdown terminates the app even with secondary windows open (103eb9a6)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.16.0 (31dfaffc)
|
||||||
|
- update for v2.15.0 (18394dd6)
|
||||||
|
|
||||||
|
## v2.15.0 — 2026-09-11
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Stash-Option fuer uncommittete Aenderungen im Ziel-Tree (251b70ce)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.14.0 (8e2d37e6)
|
||||||
|
|
||||||
|
## v2.14.0 — 2026-09-10
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Listen und Ordner in einer gemischten Sidebar-Reihenfolge (bfb1c652)
|
||||||
|
- confirm before closing a running ConPTY session (eb623714)
|
||||||
|
- worker-menu Shutdown ends worker + app after confirm (1bdf2027)
|
||||||
|
- chain a normal parent's children on WaitingForChildren (d2648f96)
|
||||||
|
- localize WorkConsole, DescriptionStepsCard and the ROADBLOCK label (8e6f0d16)
|
||||||
|
- surface depends_on blocking in status chip, chain label, and detail pane (4cf601e4)
|
||||||
|
- Worker-Menü "Update Claude CLI" führt claude update aus (dbee4a0d)
|
||||||
|
- triage model picker (opus/fable) in the merge helper start modal (1177463a)
|
||||||
|
- collapsible folders for the list sidebar (25c9a619)
|
||||||
|
- list_folders table plus lists.folder_id (13e860f0)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Import broadcastet TaskUpdated fuer neue Tasks (1770b677)
|
||||||
|
- ConnectedClientTracker in den externen MCP-Container teilen (3d09323e)
|
||||||
|
- de.json-Konsistenz-Pass und restliche harte EN-Strings (d6696c33)
|
||||||
|
- force English git output for locale-fragile stderr matches (5ecf6f75)
|
||||||
|
- make diff-stat parsing locale-safe and dedupe the two parsers (29ea18c4)
|
||||||
|
- report failed paste/drop in the interactive terminal (0cec0a77)
|
||||||
|
- surface worker connect/restart failures instead of no-opting (b4b62254)
|
||||||
|
- validate PowerUse window, explain effort and Prime Claude in Settings (951c2fe9)
|
||||||
|
- start_list_handler ehrlich machen, wenn keine App verbunden ist (451c0184)
|
||||||
|
- warn and block save when a list's working dir isn't a git repo (3348736e)
|
||||||
|
- show a banner when a ConPTY session dies after startup (256bea5f)
|
||||||
|
- merge duplicate top-level "checks" block in en/de.json (741f66fe)
|
||||||
|
- translate max_turns failure text to English (0fcfe7c5)
|
||||||
|
- route WeekReportService permission-mode through PermissionModeResolver (1c768529)
|
||||||
|
- report startup port conflicts instead of crashing silently (c9d4823f)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- record list folders in the Data and Ui CLAUDE.md (5477ae53)
|
||||||
|
- update for v2.13.6 (ec1e60be)
|
||||||
|
|
||||||
|
## v2.13.6 — 2026-09-07
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Home-Ordner als Sitzungsziel in Mission Control (f231f08a)
|
||||||
|
- Einfuegen und Datei-Drop im interaktiven Terminal (23491d3b)
|
||||||
|
- Cheat-Sheet-Fenster im Hilfe-Menue (0db9fade)
|
||||||
|
- PowerUse-Einstellungen nach Settings -> Ausfuehrung verschieben (7c7c33d6)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.13.5 (c7392e08)
|
||||||
|
|
||||||
|
## v2.13.5 — 2026-09-03
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Version im About-Dialog aus der FileVersion lesen (cccfe2ec)
|
||||||
|
|
||||||
|
### Refactoring
|
||||||
|
|
||||||
|
- "Nach Updates suchen" ins Hilfe-Menue verschieben (c16c7b97)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.13.4 (a8ec5507)
|
||||||
|
|
||||||
|
## v2.13.4 — 2026-09-03
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- interaktive Session ohne Arbeitsverzeichnis im User-Verzeichnis starten (f70503d0)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.13.3 (9360cd2f)
|
||||||
|
|
||||||
|
## v2.13.3 — 2026-09-03
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- raise SignalR MaximumParallelInvocationsPerClient (e64d5705)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.13.2 (fc25ee45)
|
||||||
|
|
||||||
|
## v2.13.2 — 2026-09-03
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Handler-Run per Knopf abbrechen (bd5aa265)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- strip CLAUDE_CODE_CHILD_SESSION/CLAUDECODE before every ConPTY launch (51ee5050)
|
||||||
|
- Dequeue-X auf blockierten Queue-Zeilen anzeigen und Kette konsistent halten (df3e7ba0)
|
||||||
|
- report and gate ListSettingsModal load failures instead of swallowing them (a1b20d9f)
|
||||||
|
- move ScrollViewer padding to inner content in modal bodies (e0c6b670)
|
||||||
|
- Mission Control oeffnen, wenn ein Handler-Run per MCP startet oder ein Handoff eintrifft (b05b6fba)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- show list settings modal before its seven-round-trip load finishes (f24654df)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- remove stale session-skills empty-state entry (2bd48ea1)
|
||||||
|
- update for v2.13.1 (eb3aee41)
|
||||||
|
- update for v2.13.0 (2b873322)
|
||||||
|
|
||||||
|
## v2.13.1 — 2026-09-02
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Mission Control oeffnen, wenn ein Handler-Run per MCP startet oder ein Handoff eintrifft (9f64ba50)
|
||||||
|
|
||||||
|
## v2.13.0 — 2026-09-02
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Handler-Session vor dem Usage-Limit warnen und Handoff bis zum Reset zurueckhalten (fe818651)
|
||||||
|
- start_list_handler MCP-Tool startet einen List-Handler-Run wie der Button (c2166a0d)
|
||||||
|
- Handler-Session nach Usage-Limit-Reset automatisch neu starten (016b9203)
|
||||||
|
- PowerUse-Zeitfenster + Handoff-Schwellwert als Settings (69898be5)
|
||||||
|
- PowerUse-Toggle im Usage-Monitor, Opt-in pro Liste (19375254)
|
||||||
|
- PowerUse-Addendum an die Handler-Prompts, Cap aus den Settings (453d61ca)
|
||||||
|
- PowerUse-Flags und konfigurierbares Handoff-Budget (3cf9628f)
|
||||||
|
- depends_on als Menge statt Einzelfeld (c25732cf)
|
||||||
|
- surface the handoff round budget in the merge-helper handoff brief (81a5fc5d)
|
||||||
|
- add ExpectsNoDiff flag for verification/audit tasks (0b506f52)
|
||||||
|
- add dependency audit and diff-review threshold to list-handler prompts (9e805646)
|
||||||
|
- add "move to list" to the task row context menu (80fe188a)
|
||||||
|
- notes get their own list, a day log and an Enter chain (0f04b796)
|
||||||
|
- add_note MCP tool with a per-day agent cap (ae0c7d37)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- carry ExpectsNoDiff into the relocated merge MCP tools (4ee1d05f)
|
||||||
|
- stop Ui.Tests order-dependent flakiness on shared Loc.Current (dc1e67d0)
|
||||||
|
- add failureReason field to batch_get_tasks (91220177)
|
||||||
|
- make locale-parity test able to detect a malformed locale file (4c78a5d6)
|
||||||
|
- don't leave an orphaned list behind on cancel (56bc1651)
|
||||||
|
- stop treating git worktrees and submodules as repos (b6e5876b)
|
||||||
|
- fix Bandel ticket sync and reactivate the integration (30d0611b)
|
||||||
|
- Shift+Enter in the capture box, not just in the note rows (f6405804)
|
||||||
|
- let Shift+Enter break the line, and clear the leftovers around the editor (b90c5855)
|
||||||
|
- auto-bootstrap commit-less repos before creating worktrees (e0748ba3)
|
||||||
|
- bootstrap commit-less repos and survive missing git identity (de44196b)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- switch to workstation GC to cut idle memory (c347e4fc)
|
||||||
|
|
||||||
|
### Refactoring
|
||||||
|
|
||||||
|
- share StubWorkerClient between Ui.Tests and Worker.Tests (9f90e75a)
|
||||||
|
- remove the stale-worker banner (6be62f02)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Handler-Prompt praezisieren (Wave-Plan, Task-Split, review_task-Aufruf) (20b3b4ce)
|
||||||
|
- Test-Scoping fuer Implementer-Tasks, PowerUse in den Explore-Notes (416997d7)
|
||||||
|
- plan for multi-dependency depends_on relation table (84d955df)
|
||||||
|
- empty-repo hardening notes + verification item (cf093e31)
|
||||||
|
- spec + plan for empty-repo (unborn HEAD) hardening (fea8236e)
|
||||||
|
- update for v2.12.0 (1d6cd49b)
|
||||||
|
|
||||||
|
## v2.12.0 — 2026-08-28
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- wählbares Listen-Icon und aufgeräumter Task-Detail-Header (c31f79d3)
|
||||||
|
- Mission Control als Session-Hub (22e70ffc)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- make re-approve after revert_merge do a real merge, not a silent no-op (8d597df4)
|
||||||
|
- survive a worker restart when cancelling a mid-merge unit-merge parent (1affb397)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update for v2.11.0 (bbdb0223)
|
||||||
|
|
||||||
|
## v2.11.0 — 2026-08-27
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Listeneinstellungen mit Kategorie-Sidebar (4e55f916)
|
||||||
|
- Ticketsystem-Anbindung hinter einen Aus-Schalter legen (8329c925)
|
||||||
|
- Ticket-Projekt pro Liste und Import ueber das Kontextmenue (e91ade9c)
|
||||||
|
- Settings-Tab fuer die Ticketsystem-Anbindung (020f93d5)
|
||||||
|
- Ticket-Settings, Projektliste und Import ueber den Hub (f0a3a186)
|
||||||
|
- Ticket-Import pro Liste (2e236736)
|
||||||
|
- Ticket-Status-Rueckmeldung ueber TaskStateService (5aafeb6c)
|
||||||
|
- BandelTicketClient fuer die Ticketsystem-REST-API (10652780)
|
||||||
|
- TicketSystemConfig + ticket_api_base_url in worker.config.json (2a3c937f)
|
||||||
|
- ticket_ref auf tasks, ticket_project_id auf list_config (3171690b)
|
||||||
|
- claude_bin im Dateien-Tab setzbar, mit Auflösungs-Check (13b81cac)
|
||||||
|
- Throttle-Stufen im Ausfuehrung-Tab sichtbar machen (31d53b00)
|
||||||
|
- Permission-Modus pro Liste und pro Task ueberschreibbar (4464dc6f)
|
||||||
|
- Listen-Einstellungen — Checkbox fuer serialize_on_file_overlap (5cc99bfe)
|
||||||
|
- Review-Merge-Button — Claude nimmt Waiting-for-Review pro Liste ab (741c7ac5)
|
||||||
|
- verify once per batch via skipVerify + verify_merges (168ab1cd)
|
||||||
|
- run both MCP servers stateless (69fcceb2)
|
||||||
|
- add the Prime action radio group to settings (f3cb34b9)
|
||||||
|
- add the action kind to the settings viewmodels (57be4961)
|
||||||
|
- add Prime action-kind strings in en and de (514f142a)
|
||||||
|
- carry the action kind through the hub and validate custom prompts (4e8e0334)
|
||||||
|
- branch PrimeRunner on the schedule action kind (cf6a6b62)
|
||||||
|
- migrate prime_schedules with action_kind defaulting to ping (71d2972a)
|
||||||
|
- add PrimeActionKind to the schedule entity (022eaf38)
|
||||||
|
- open the TokenTracker dashboard from the usage monitor (3003cfa5)
|
||||||
|
- show cost columns and the TokenTracker hint card in the usage monitor (074c0e4d)
|
||||||
|
- surface TokenTracker status and cost in the usage monitor (bac0d69a)
|
||||||
|
- add TokenTracker analytics strings in en and de (66f743dd)
|
||||||
|
- add TokenTracker status, refresh and install to the worker client (3c3bb306)
|
||||||
|
- serve model and task usage from the TokenTracker export (44337d4b)
|
||||||
|
- cache the TokenTracker export with single-flight refresh (3f3638c1)
|
||||||
|
- run the TokenTracker CLI through the shared executable resolver (a9218045)
|
||||||
|
- pin the TokenTracker CLI invocations (d6b22ea5)
|
||||||
|
- hold the last good TokenTracker export fail-open (580d4e07)
|
||||||
|
- aggregate TokenTracker sessions by model and per task (2fca0cc4)
|
||||||
|
- parse the TokenTracker sessions export defensively (106f9a37)
|
||||||
|
- add TokenTracker export models and test fixture (16952b64)
|
||||||
|
- reproduce TokenTracker session_hash for task attribution (1cb574bd)
|
||||||
|
- Improve App Prompts (79438c00)
|
||||||
|
- Toggle "Continue on session limit reset" für Usage-Limit-Abbrüche (07dd7570)
|
||||||
|
- confirm main-window close when Mission Control sessions are open (0892883a)
|
||||||
|
- "Worktree öffnen" im Kontextmenü der Task-Zeile (ac9b0dab)
|
||||||
|
- update_task kann isManual setzen + List-Handler stellt seinen Task auf Review (ea2271cc)
|
||||||
|
- Usage-Monitor zeigt Reset-Uhrzeit hinter der Restzeit (8f983851)
|
||||||
|
- Erststart-Banner und Textkette Repo→Task→Queue→Review (UX-Struktur C) (111dd0fb)
|
||||||
|
- Settings-Modal auf Sidebar-Kategorien umbauen (56c3719e)
|
||||||
|
- Task-Zeile entdichten und Kontextmenü gruppieren (UX-Struktur A) (51dc7bd6)
|
||||||
|
- surface startup recovery via OperationProgress channel (6ec81f65)
|
||||||
|
- report worktree-creation phase on OperationProgress (eba0d842)
|
||||||
|
- surface rebase and worktree-maintenance progress on OperationProgress (42de97e1)
|
||||||
|
- Empty States für Detail-Pane und Task-Liste (UX-Audit #4) (9159f1b5)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Base-URL ohne Schema laesst die Ticket-Anbindung scheitern (3322152b)
|
||||||
|
- Import-Erfolgsmeldung nicht mehr als Fehler einfaerben (2fe2ac83)
|
||||||
|
- MergeHelperSelectionModal — Task-Nummer anzeigen, Titel wrappen, Status-Spalte verbreitern (24e1d648)
|
||||||
|
- Branch-Zeile im Detail-Header — Icon stroked rendern, ohne Task ausblenden (18755910)
|
||||||
|
- Review-Merge-Button-Icon stroked rendern statt gefuellt — Linien waren unsichtbar (d7a3aa80)
|
||||||
|
- block delete on interactive-session tasks, clear ghost selection (c1ab042d)
|
||||||
|
- store ScheduledFor as real UTC instead of mislabeled local time (f70aec49)
|
||||||
|
- make modify/delete and rename/delete merge conflicts visible instead of silently staging them (533a287e)
|
||||||
|
- close status-force bypasses on Running tasks (299c7b74)
|
||||||
|
- allow the done toggle from any non-Running status (f80af122)
|
||||||
|
- restore the Settings modal body and tidy the Prime schedule row (d71c0cea)
|
||||||
|
- close the typographic quote that made de.json unparseable (9ab17d82)
|
||||||
|
- failureReason "usage_limit" nachziehen + Worktree-Gate prüft die Platte (29171b10)
|
||||||
|
- Usage-Limit-Auto-Continue verbrennt nicht mehr alle Kandidaten außer dem ersten (3dfae30f)
|
||||||
|
- thread handler task id into list-handler kickoff prompts (9c4a63d2)
|
||||||
|
- UsagePillViewModel loads snapshot on connection restore (7f94bf4a)
|
||||||
|
- Description-Editbox nutzt volle Kartenhoehe statt 320px-Deckel (a6d165a1)
|
||||||
|
- route done-toggle and dequeue through guarded TaskStateService transitions (7f337b36)
|
||||||
|
- Reactivität — Description-Save-Race, UsageMonitor-Leak, Listen-Live-Refresh (52efe3ec)
|
||||||
|
- notify IsTasksEmptyRepoHintVisible on every list switch (b997af44)
|
||||||
|
- Aktions- und Bestätigungs-Konsistenz (UX-Audit #3) (2935eef7)
|
||||||
|
- surface precondition hints for silently-disabled review buttons (UX-Audit #2) (dbb73bae)
|
||||||
|
- silent no-ops raise ErrorReported instead of swallowing failures (UX-Audit #1) (99bb5be5)
|
||||||
|
- propagate unit-merge failures instead of reporting success (f2058430)
|
||||||
|
- Stuck-Running-Fenster in Continue- und Stop-Pfad schließen (389c9126)
|
||||||
|
|
||||||
|
### Refactoring
|
||||||
|
|
||||||
|
- Datei-Scope-Serialisierung entfernen (84219a9f)
|
||||||
|
- OnlineTokenStore zu DpapiTokenStore verallgemeinert (41bfc242)
|
||||||
|
- Icon-Faerbung zentral im ICON-PAINTING-Block (1ef67760)
|
||||||
|
- drop the remaining single-implementation interfaces (0f007c53)
|
||||||
|
- merge the one-expression converters into SimpleConverters.cs (c593be2f)
|
||||||
|
- declare the SignalR wire DTOs once in ClaudeDo.Data (8930b564)
|
||||||
|
- squash the 40 migrations into one InitialCreate (39d82413)
|
||||||
|
- collapse single-implementation interfaces (296251e2)
|
||||||
|
- delete unreachable members and redundant package refs (81994aad)
|
||||||
|
- drop the transcript aggregation path in favour of TokenTracker (40879705)
|
||||||
|
- dedupe path-open helper across five view models (b2a1b790)
|
||||||
|
- derive TaskRowViewModel CanX/DisabledReason pairs from one gate method (baba9216)
|
||||||
|
- unify list-linked-repo predicate into RepoLinkage (9578c950)
|
||||||
|
- dedupe FormatRemaining between UsagePill and UsageMonitorModal (e164f1e3)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Ticketsystem-Anbindung als pausiert vermerken (cb69e2a7)
|
||||||
|
- Ticketsystem-Anbindung in den CLAUDE.md-Dateien (fb1ceac1)
|
||||||
|
- IsConfigured-Kosten praezise beschreiben (e9dfea90)
|
||||||
|
- Implementierungsplan, Base-URL in worker.config.json (5a13ad44)
|
||||||
|
- Design fuer die Ticketsystem-Anbindung (63184ea3)
|
||||||
|
- neue Config-Felder + Migration-Fixture-Falle in den CLAUDE.md-Dateien (d474fd13)
|
||||||
|
- bump review-merge verified-against to 168ab1cd (187f5fc5)
|
||||||
|
- document the schedule action kinds (a1d74ab4)
|
||||||
|
- implementation plan for schedule action kinds (2dec590d)
|
||||||
|
- add the TokenTracker analytics implementation plan (884b990e)
|
||||||
|
- document TokenTracker as the analytics backend (c0bf7db6)
|
||||||
|
- spec per-schedule action kind (Ping/FillMyDay/Custom) (a873eaa9)
|
||||||
|
- spec for TokenTracker as the token-analytics backend (0e74650d)
|
||||||
|
- C5 periodische Dienste abgesagt — kein Mehrwert über bestehende Broadcasts (29868962)
|
||||||
|
- design and plan for the four structural UX themes (4cf08f81)
|
||||||
|
- update for v2.10.0 (925ba655)
|
||||||
|
|
||||||
## v2.10.0 — 2026-08-17
|
## v2.10.0 — 2026-08-17
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -66,14 +66,38 @@ Commit freely (per task + the spec/plan docs). Never push without asking.
|
|||||||
|
|
||||||
## Building & Testing
|
## Building & Testing
|
||||||
|
|
||||||
`dotnet build ClaudeDo.slnx` requires .NET 9; on .NET 8 build individual projects with `-c Release` (a running Worker locks the `Debug` output).
|
**Build the whole solution, test only what you touched.** Always `-c Release` — a running Worker locks the `Debug` output.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release # pulls in Ui + Data
|
dotnet build ClaudeDo.slnx -c Release # ~40s, catches cross-project breaks
|
||||||
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release
|
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release --no-build \
|
||||||
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release # also: Data.Tests, Ui.Tests, Localization.Tests, Installer.Tests, Releases.Tests
|
--filter "FullyQualifiedName~Handoff" # seconds, not minutes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`.slnx` needs SDK 9+ (this machine is on 10). On an older SDK build the projects individually:
|
||||||
|
`src/ClaudeDo.App` (pulls in Ui + Data) and `src/ClaudeDo.Worker`.
|
||||||
|
|
||||||
|
### Do NOT run the full test suite to finish a task
|
||||||
|
|
||||||
|
Measured 2026-09-02, sequentially after one build: **Worker.Tests 507s**, Ui.Tests 33s, Data.Tests 8s,
|
||||||
|
Localization/Installer/Releases 3s each, build 42s — ~10 min for all six. Run in parallel across
|
||||||
|
projects it is *worse*, not better (Worker.Tests 801s, Ui.Tests 82s): these tests create real git
|
||||||
|
worktrees and spawn processes, so they thrash rather than share.
|
||||||
|
|
||||||
|
With up to 5 implementer tasks running at once, a full suite per task costs hours — and **cannot find
|
||||||
|
what it costs that for.** Each task works in its own worktree containing only its own change, so a
|
||||||
|
collision between two parallel tasks is invisible to both. Collisions surface after the merge, which
|
||||||
|
is exactly where `verify_merges` runs the list's verify command **once per batch**. The full suite is
|
||||||
|
the merge gate's job, not the task's.
|
||||||
|
|
||||||
|
So, per task:
|
||||||
|
- **Build:** the whole solution, always. It's cheap and it catches the compile break your change
|
||||||
|
caused three projects over.
|
||||||
|
- **Tests:** `--no-build --filter` scoped to what you touched. Add a filter for the classes covering
|
||||||
|
your change; if you genuinely can't scope it, run the one relevant test project, never all six.
|
||||||
|
- **Report** what you ran and what you did not, so the merge session knows what is still unverified.
|
||||||
|
- Never report "all tests pass" when you ran a filtered subset. Say which filter you used.
|
||||||
|
|
||||||
### Gotchas
|
### Gotchas
|
||||||
- **Subagents:** use the `sonnet` model; stage files explicitly by path — never `git add -A` (parallel sessions often leave unrelated WIP in the tree).
|
- **Subagents:** use the `sonnet` model; stage files explicitly by path — never `git add -A` (parallel sessions often leave unrelated WIP in the tree).
|
||||||
- **Icons:** `PathIcon` *fills* its geometry — line-art icons must be filled geometry or a `Path Classes="icon-stroke"`, else they render invisible. Colour comes from the `ICON PAINTING` block in `IslandStyles.axaml` (class + state); setting `Foreground`/`Stroke` inline kills the hover state.
|
- **Icons:** `PathIcon` *fills* its geometry — line-art icons must be filled geometry or a `Path Classes="icon-stroke"`, else they render invisible. Colour comes from the `ICON PAINTING` block in `IslandStyles.axaml` (class + state); setting `Foreground`/`Stroke` inline kills the hover state.
|
||||||
|
|||||||
@@ -97,6 +97,48 @@ Net effect: reopening an interactive session for a task (pane closed, process ki
|
|||||||
resumes the same claude conversation, because the id was committed to the DB before the previous
|
resumes the same claude conversation, because the id was committed to the DB before the previous
|
||||||
launch even started.
|
launch even started.
|
||||||
|
|
||||||
|
## Session handoff (`handoff_session`)
|
||||||
|
|
||||||
|
Task and ad-hoc tiles can hand off to a fresh session in the same tile. The **paneId is the pane's
|
||||||
|
`IdentityKey`**: the task id for a task tile, a GUID minted by `OpenAdHocConPtySessionAsync` for an
|
||||||
|
ad-hoc one.
|
||||||
|
|
||||||
|
**The model never passes an id** (it guesses GUIDs). Instead, every such spec gets
|
||||||
|
`--mcp-config ~/.claudeDo/pane-sessions/<paneId>/mcp.json`, which adds a second entry,
|
||||||
|
`claudedo_pane`. It points at the same external MCP, with headers `X-ClaudeDo-Pane` and
|
||||||
|
`X-ClaudeDo-Session` (plus `X-ClaudeDo-Key` when one is configured). An `AddListToolsFilter`
|
||||||
|
(`SessionHandoffMcpTools.FilterByPane`) splits the tool list by that header:
|
||||||
|
- `claudedo_pane` lists only `handoff_session`;
|
||||||
|
- the plain `claudedo` entry lists everything except `handoff_session`.
|
||||||
|
|
||||||
|
The hint (`handoff-hint.md`, via `--append-system-prompt-file`) says when to call
|
||||||
|
`mcp__claudedo_pane__handoff_session(brief)` and what goes into the brief, and contains no ids. Ad-hoc
|
||||||
|
tiles get a pre-assigned `--session-id` so the header has one. With `ExternalMcpPort = 0` the tile
|
||||||
|
gets neither the entry nor the hint.
|
||||||
|
|
||||||
|
`handoff_session(brief)` writes `pane-sessions/<paneId>/handoffs/<guid>/brief.md` (first line:
|
||||||
|
`Handed off from session <id>`) and broadcasts `SessionHandoffRequested(paneId, briefPath)`. The
|
||||||
|
worker has no pane registry: the UI looks up the pane by `IdentityKey`, takes its `Cwd` (from the
|
||||||
|
resolved launch spec) and its `TaskId`, and closes it. It then reopens the tile at the same slot
|
||||||
|
with the same `IdentityKey` via `GetSessionHandoffLaunchSpec` → `BuildHandoffAsync`. For a task
|
||||||
|
tile, the new session id replaces `InteractiveSessionId`, so reopening the task resumes the
|
||||||
|
handed-off session. The old transcript remains under `~/.claude/projects/`.
|
||||||
|
|
||||||
|
## Recent sessions
|
||||||
|
|
||||||
|
Every task, ad-hoc and handoff tile launch (only those — `paneId != null`) records its session in
|
||||||
|
`mission_control_sessions` from inside `InteractiveLaunchSpecService`, which is where the session id
|
||||||
|
is known. A handoff's predecessor comes from the brief's first line
|
||||||
|
(`SessionHandoffMcpTools.ReadHandedOffFrom`), and it inherits the predecessor's title and task.
|
||||||
|
The clock button in the Mission Control header opens a flyout (`RefreshRecentSessionsAsync`, last 14
|
||||||
|
days / 50 rows, read straight from the DB). `NestHandoffs` groups each chain under its origin.
|
||||||
|
- **Resume**: a task's *current* `InteractiveSessionId` reopens the task tile (deduped by TaskId).
|
||||||
|
Any other session gets a fresh ad-hoc tile via `GetResumeSessionLaunchSpec` → `BuildResumeAsync`
|
||||||
|
(`--resume <id>` in the recorded cwd, handoff hint included).
|
||||||
|
- **Brief**: `ShellOpen.Path(brief.md)`, which opens the file in the default editor.
|
||||||
|
- Not guarded: resuming a session that is still live in another ad-hoc tile, since panes don't
|
||||||
|
track their session id.
|
||||||
|
|
||||||
## List handler ("Let Claude handle it")
|
## List handler ("Let Claude handle it")
|
||||||
|
|
||||||
`BuildForMergeHelperAsync`/`BuildForMergeHelperHandoffAsync` resolve `--permission-mode` via
|
`BuildForMergeHelperAsync`/`BuildForMergeHelperHandoffAsync` resolve `--permission-mode` via
|
||||||
@@ -115,6 +157,22 @@ share the same prompt/model as their non-final counterpart and differ only in on
|
|||||||
rendered into the handoff kickoff file (`PromptKind.MergeHelperHandoff`) marking the final round
|
rendered into the handoff kickoff file (`PromptKind.MergeHelperHandoff`) marking the final round
|
||||||
and forbidding further reruns.
|
and forbidding further reruns.
|
||||||
|
|
||||||
|
The chain length is bounded server-side by `HandoffRoundTracker`, whose cap comes from
|
||||||
|
`AppSettingsEntity.ListHandlerMaxHandoffs` (default 4 = two merge rounds; `HandoffMcpTools` reads it
|
||||||
|
per call and `Register` clamps to min 1, so 0 can never mean "unbounded"). Past the cap a
|
||||||
|
`wait`/`merge` request is silently coerced to its `_final` counterpart — check the result's
|
||||||
|
`coerced` flag — and any handoff after a `merge_final` round throws.
|
||||||
|
|
||||||
|
**PowerUse.** When `AppSettingsEntity.PowerUseEnabled` (Settings → Execution) **and** the
|
||||||
|
list's own `ListConfigEntity.PowerUseAllowed` are both set, `InteractiveLaunchSpecService.
|
||||||
|
ComposeHandlerPromptAsync` appends `PromptKind.PowerUseAddendum` to whichever handler prompt the
|
||||||
|
phase resolved to. The addendum is written as an *override* of the base prompt's interactive rules
|
||||||
|
(never ask, decide and record instead; resolve or abort conflicts rather than leaving them in the
|
||||||
|
tree; harvest new tasks when a round would starve). Both keys are required on purpose: the global
|
||||||
|
switch can stay on overnight without turning an unrelated list's handler run unattended. A run that
|
||||||
|
is not in PowerUse gets a byte-identical prompt to the pre-PowerUse behaviour — there is deliberately
|
||||||
|
no second set of handler prompts to keep in sync.
|
||||||
|
|
||||||
Each handoff hands the SAME handler task id to a NEW tile, and `MissionControlViewModel.
|
Each handoff hands the SAME handler task id to a NEW tile, and `MissionControlViewModel.
|
||||||
OpenMergeHelperHandoffConPtySessionAsync` closes the outgoing phase's pane (`CloseConPtySession`,
|
OpenMergeHelperHandoffConPtySessionAsync` closes the outgoing phase's pane (`CloseConPtySession`,
|
||||||
which disposes its `PtyTerminalSession` and kills the underlying `claude` process) before opening
|
which disposes its `PtyTerminalSession` and kills the underlying `claude` process) before opening
|
||||||
@@ -127,6 +185,43 @@ outgoing session is only ever told to end its turn, never to exit. This keeps th
|
|||||||
`MCP_TOOL_TIMEOUT` is 200 s here — `TaskWaitMcpTools` clamps its own timeout to 170 s to stay
|
`MCP_TOOL_TIMEOUT` is 200 s here — `TaskWaitMcpTools` clamps its own timeout to 170 s to stay
|
||||||
comfortably under it (see [external-mcp.md](external-mcp.md)).
|
comfortably under it (see [external-mcp.md](external-mcp.md)).
|
||||||
|
|
||||||
|
### Auto-restart after the usage limit
|
||||||
|
|
||||||
|
A handler session that dies mid-phase used to be a silent dead tile: `InteractiveTerminalViewModel.
|
||||||
|
OnSessionProcessExited` only raised a visible banner for a death **inside** its 5 s
|
||||||
|
`StartupGraceWindow`. It now raises `ExitedAfterStartup` for a later non-zero exit; the pane
|
||||||
|
forwards it as `SessionDiedAfterStartup(taskId, exitCode)` and `MissionControlViewModel` reports it
|
||||||
|
via `WorkerHub.ReportInteractiveSessionExited`. Every task-based pane reports — the UI does no
|
||||||
|
filtering, because the exit code is the *only* thing it knows.
|
||||||
|
|
||||||
|
`HandlerRestartCoordinator` (Worker) decides the rest and **fails closed** — unlike the usage gate,
|
||||||
|
every missing signal means "don't relaunch":
|
||||||
|
|
||||||
|
- **Which round was running** comes from `HandlerRoundState`, a JSON file at
|
||||||
|
`~/.claudeDo/handler-rounds.json` (one `HandlerRound` per handler task id, deliberately not a DB
|
||||||
|
column). `BuildForMergeHelperAsync` writes the opening round as phase `"triage"`;
|
||||||
|
`BuildForMergeHelperHandoffAsync` writes each subsequent `MergeHelperPhase`. Without it the phase
|
||||||
|
and task set exist *only* in the transient `HandoffRequested` broadcast, so a dead tile could not
|
||||||
|
be reopened. A `"triage"` round is never reopened — there is no handoff spec for it.
|
||||||
|
- **Was it really the usage limit?** A ConPTY session has no captured stdout, so `TaskRunner.
|
||||||
|
IsUsageLimitMessage` (which matches the CLI's own "session limit" text) has nothing to read. The
|
||||||
|
corroborating witness is `UsageState.Snapshot.FiveHour.Utilization ≥ 95`
|
||||||
|
(`HandlerRestartCoordinator.UsageLimitProofPct`) at the moment of death. A crash at 40 % is not
|
||||||
|
restarted; no snapshot at all is not restarted.
|
||||||
|
- **PowerUse only** — both keys, exactly like the prompt addendum above.
|
||||||
|
- **One restart per round**, claimed via `HandlerRoundState.TryClaimAutoRestart` before the wait, so
|
||||||
|
a restart that dies again stays dead. `HandlerRoundState.Save` carries `AutoRestartUsed` over when
|
||||||
|
the phase is unchanged, because the restart's own launch-spec build writes the round again — a
|
||||||
|
naive overwrite would hand out a fresh budget every time and loop all night.
|
||||||
|
- The restart waits out `FiveHour.ResetsAt`, then re-broadcasts `HandoffRequested` with the
|
||||||
|
persisted phase + task set, which the UI already handles by reopening the tile. It goes **around**
|
||||||
|
`handoff_list_handler`, so `HandoffRoundTracker.Register` is never called and the run loses no
|
||||||
|
handoff budget.
|
||||||
|
|
||||||
|
Sibling to `UsageLimitAutoContinueCoordinator`, not an extension of it: that one only ever sees
|
||||||
|
headless runs (`FailureReason == "usage_limit"`, resumed through the override slot), and a ConPTY
|
||||||
|
session has no `task_runs` row to classify.
|
||||||
|
|
||||||
### The host task and its commit range
|
### The host task and its commit range
|
||||||
|
|
||||||
The handler run **owns a real ClaudeDo task**, created by `CreateMergeHelperTask` (hub) →
|
The handler run **owns a real ClaudeDo task**, created by `CreateMergeHelperTask` (hub) →
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ is wired through `TaskIdResolver`, which resolves:
|
|||||||
This is **not** ambiguous: a GUID is never all-digits, so a pure-integer parameter is always a
|
This is **not** ambiguous: a GUID is never all-digits, so a pure-integer parameter is always a
|
||||||
number, not a partial GUID. All task-returning tools (`GetTask`, `ListTasks`, `AddTask`,
|
number, not a partial GUID. All task-returning tools (`GetTask`, `ListTasks`, `AddTask`,
|
||||||
`BatchGetTasks`, etc.) stamp the resolved task's `Number` in the DTO — both `TaskDto` and the
|
`BatchGetTasks`, etc.) stamp the resolved task's `Number` in the DTO — both `TaskDto` and the
|
||||||
lean `TaskRefDto` carry an `int Number` field. **Branch names and worktree paths** (`claudedo/{id}`)
|
lean `TaskRefDto` carry an `int Number` field. **Task worktree branches and folders** are `claudedo/{number}-{title-slug}`;
|
||||||
continue to use the GUID; the number is a display alias, never the identity.
|
otherwise the number is a display alias, never the identity.
|
||||||
|
|
||||||
Every tool description carries a shared boilerplate clause (defined in `McpToolDocs.TaskNumberHint`)
|
Every tool description carries a shared boilerplate clause (defined in `McpToolDocs.TaskNumberHint`)
|
||||||
instructing the agent to **refer to tasks as `#<number>` when reporting results to the user** —
|
instructing the agent to **refer to tasks as `#<number>` when reporting results to the user** —
|
||||||
@@ -98,6 +98,7 @@ Daily prep: `GetDailyPrepCandidates`, `SetMyDay`.
|
|||||||
| `TaskWaitMcpTools` | `WaitForTaskChange` |
|
| `TaskWaitMcpTools` | `WaitForTaskChange` |
|
||||||
| `QueueStateMcpTools` | `GetQueueState` |
|
| `QueueStateMcpTools` | `GetQueueState` |
|
||||||
| `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` |
|
| `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` |
|
||||||
|
| `ListHandlerMcpTools` | `StartListHandler` |
|
||||||
|
|
||||||
## Per-tool behaviour worth knowing
|
## Per-tool behaviour worth knowing
|
||||||
|
|
||||||
@@ -235,6 +236,13 @@ task already outside those two (unknown ids reported as status `"NotFound"`, als
|
|||||||
**`AttachmentMcpTools`** — re-attaching the same `fileName` overwrites. Add/remove refuse on a
|
**`AttachmentMcpTools`** — re-attaching the same `fileName` overwrites. Add/remove refuse on a
|
||||||
`Running` task.
|
`Running` task.
|
||||||
|
|
||||||
|
**`StartListHandler`** — the worker only broadcasts `ListHandlerStartRequested`; the connected
|
||||||
|
ClaudeDo App does the actual work (creates the handler task, opens the triage ConPTY tile). Before
|
||||||
|
broadcasting it checks `ConnectedClientTracker` (a `WorkerHub.OnConnectedAsync`/`OnDisconnectedAsync`-driven
|
||||||
|
singleton count) — with zero connected clients the broadcast would reach nobody, so the tool
|
||||||
|
returns `Requested: false` plus a `Reason` instead of claiming success. Does not queue or retry the
|
||||||
|
request for a later connect.
|
||||||
|
|
||||||
**`GetDailyPrepCandidates`** — Idle, non-blocked tasks in a git repo **not** excluded by
|
**`GetDailyPrepCandidates`** — Idle, non-blocked tasks in a git repo **not** excluded by
|
||||||
`AppSettings.ReportExcludedPaths` and not already `IsMyDay`, plus the current Idle MyDay tasks
|
`AppSettings.ReportExcludedPaths` and not already `IsMyDay`, plus the current Idle MyDay tasks
|
||||||
and `maxTasks` (= `DailyPrepMaxTasks`). Repo-exclusion logic lives in the `DailyPrepFilter`
|
and `maxTasks` (= `DailyPrepMaxTasks`). Repo-exclusion logic lives in the `DailyPrepFilter`
|
||||||
|
|||||||
@@ -184,9 +184,9 @@ detached-HEAD scratch worktree under the OS temp dir (`GitService.WorktreeAddDet
|
|||||||
is always removed afterward (`finally`, non-cancellable cleanup). No verify command configured, or
|
is always removed afterward (`finally`, non-cancellable cleanup). No verify command configured, or
|
||||||
`runVerify=false`, reproduces the pre-existing preview exactly — no delay, no scratch worktree.
|
`runVerify=false`, reproduces the pre-existing preview exactly — no delay, no scratch worktree.
|
||||||
|
|
||||||
Wired into `ExternalMcpService`: `preview_merge` (single task) always requests a verify run when the
|
Wired into `ExternalMcpService`: both `preview_merge` (single task) and `preview_merge_set` only
|
||||||
list has a command configured; `preview_merge_set` only does when its `runVerify` parameter is
|
request a verify run when their `runVerify` parameter is explicitly set (default `false`) — a
|
||||||
explicitly set (default `false`) — a set preview never starts N builds unasked. The Hub's
|
preview never starts a build unasked. The Hub's
|
||||||
`PreviewMerge` (the UI's live mergeability indicator) always passes `runVerify: false`, since
|
`PreviewMerge` (the UI's live mergeability indicator) always passes `runVerify: false`, since
|
||||||
running a build on every poll would be a bad regression; `MergePreviewDto` carries the
|
running a build on every poll would be a bad regression; `MergePreviewDto` carries the
|
||||||
verify fields anyway so a future UI entry point can opt in. Result fields: `VerifyExitCode` (null =
|
verify fields anyway so a future UI entry point can opt in. Result fields: `VerifyExitCode` (null =
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ authenticated with the Bearer access token Claude Code itself keeps fresh at
|
|||||||
Because the endpoint is undocumented and can change without notice, **every consumer
|
Because the endpoint is undocumented and can change without notice, **every consumer
|
||||||
fails open**. That is the single most important invariant here.
|
fails open**. That is the single most important invariant here.
|
||||||
|
|
||||||
|
**Measured 2026-09-16** (clean burst against the live endpoint, Worker otherwise idle): exactly
|
||||||
|
5 requests succeed, the 6th 429s with `Retry-After: 300` — a flat 5-requests-per-5-minute quota,
|
||||||
|
no `ratelimit-*`/`x-ratelimit-*` headers on either the 200s or the 429. The quota is per-account,
|
||||||
|
not per-process, so a manual refresh or another consumer of the same OAuth token shares it.
|
||||||
|
|
||||||
## Components (`Usage/`)
|
## Components (`Usage/`)
|
||||||
|
|
||||||
| Type | Role |
|
| Type | Role |
|
||||||
@@ -28,7 +33,7 @@ fails open**. That is the single most important invariant here.
|
|||||||
| `UsageModels` | `UsageBucket` / `UsageLimitRow` / `UsageSnapshot`. `UsageBucket.Utilization` is already a 0–100 percent — compare directly against thresholds, don't rescale. |
|
| `UsageModels` | `UsageBucket` / `UsageLimitRow` / `UsageSnapshot`. `UsageBucket.Utilization` is already a 0–100 percent — compare directly against thresholds, don't rescale. |
|
||||||
| `ClaudeOAuthUsageClient` | Reads the token, calls the endpoint. Defensive parsing: missing/null buckets → null, missing `limits` → empty list. |
|
| `ClaudeOAuthUsageClient` | Reads the token, calls the endpoint. Defensive parsing: missing/null buckets → null, missing `limits` → empty list. |
|
||||||
| `UsageState` | Threadsafe singleton. A failed poll **never** overwrites the last good snapshot — it only sets `LastError`. |
|
| `UsageState` | Threadsafe singleton. A failed poll **never** overwrites the last good snapshot — it only sets `LastError`. |
|
||||||
| `UsageMonitorService` | `BackgroundService`; polls on `usage_poll_interval_seconds` (default 60, clamped to min 15 on config load), one poll at startup. Logs a failure at most once per distinct error message. Broadcasts `HubBroadcaster.UsageUpdated` after **every** tick, success or failure. |
|
| `UsageMonitorService` | `BackgroundService`; polls on `usage_poll_interval_active_seconds` / `usage_poll_interval_idle_seconds` (default 120 each, clamped to min 90 on config load), one poll at startup. Logs a failure at most once per distinct error message. Broadcasts `HubBroadcaster.UsageUpdated` after **every** tick, success or failure. |
|
||||||
| `UsageSnapshotBuilder` | Builds the Hub-facing `UsageSnapshotDto` from `UsageState` + `IUsageGate` + `AppSettings`. The one shared place for stale/threshold/gate logic — `WorkerHub.GetUsageSnapshot` and `UsageMonitorService` must not diverge. |
|
| `UsageSnapshotBuilder` | Builds the Hub-facing `UsageSnapshotDto` from `UsageState` + `IUsageGate` + `AppSettings`. The one shared place for stale/threshold/gate logic — `WorkerHub.GetUsageSnapshot` and `UsageMonitorService` must not diverge. |
|
||||||
| `UsageGate` | Hard pause decision → `UsageGateDecision(IsBlocked, Reason)`. |
|
| `UsageGate` | Hard pause decision → `UsageGateDecision(IsBlocked, Reason)`. |
|
||||||
| `UsageThrottle` | Pure static staging of parallelism ahead of the gate. |
|
| `UsageThrottle` | Pure static staging of parallelism ahead of the gate. |
|
||||||
@@ -254,6 +259,7 @@ degrades instead of throwing. Worker config: `usage_poll_interval_active_seconds
|
|||||||
`usage_poll_interval_idle_seconds`.
|
`usage_poll_interval_idle_seconds`.
|
||||||
|
|
||||||
The gate percentages are editable in **two** places that both write the same `app_settings` row:
|
The gate percentages are editable in **two** places that both write the same `app_settings` row:
|
||||||
Settings → General (typed) and the usage-monitor gauges (dragged). The throttle stages are
|
Settings → Execution (typed) and the usage-monitor gauges (dragged). The throttle stages are
|
||||||
gauge-only — `SettingsModalViewModel` therefore carries them load→save verbatim so saving Settings
|
gauge-only — `SettingsModalViewModel` therefore carries them load→save verbatim so saving Settings
|
||||||
can't reset a dragged value.
|
can't reset a dragged value. PowerUse (switch, time window, handoff %) used to be usage-monitor-only
|
||||||
|
and is now typed on the same Settings tab, so it needs no such round-trip.
|
||||||
|
|||||||
+33
-26
@@ -15,9 +15,7 @@ Checkliste lebt in `docs/verification-handoff.md`.
|
|||||||
|
|
||||||
- **Planning-aktiver Parent zeigt weiter „Idle":** Parent in `planning_phase=active` hat `Status=Idle` (korrekt im Modell), aber `TaskRowViewModel.StatusLabel` (Zeile 132) kennt nur `HasInteractiveSession`/`IsParked` als Overrides — der `PlanningBadge` ersetzt den Chip nicht sichtbar → liest sich wie ein normaler Idle-Task. Wunsch: klarer „Planning/Draft aktiv"-Zustand, der Idle überschreibt. (Kinder zeigen korrekt „Draft".)
|
- **Planning-aktiver Parent zeigt weiter „Idle":** Parent in `planning_phase=active` hat `Status=Idle` (korrekt im Modell), aber `TaskRowViewModel.StatusLabel` (Zeile 132) kennt nur `HasInteractiveSession`/`IsParked` als Overrides — der `PlanningBadge` ersetzt den Chip nicht sichtbar → liest sich wie ein normaler Idle-Task. Wunsch: klarer „Planning/Draft aktiv"-Zustand, der Idle überschreibt. (Kinder zeigen korrekt „Draft".)
|
||||||
- **Blocked-by-Kette nicht sichtbar:** Nach Finalize ist die sequentielle Kette korrekt gesetzt (child[i] blocked-by child[i-1]), aber die UI stellt Reihenfolge/Abhängigkeit nicht dar — es gibt nur den „waiting"-Chip. Wunsch: Kette visualisieren (z.B. „wartet auf <Vorgänger>").
|
- **Blocked-by-Kette nicht sichtbar:** Nach Finalize ist die sequentielle Kette korrekt gesetzt (child[i] blocked-by child[i-1]), aber die UI stellt Reihenfolge/Abhängigkeit nicht dar — es gibt nur den „waiting"-Chip. Wunsch: Kette visualisieren (z.B. „wartet auf <Vorgänger>").
|
||||||
- **Dequeue-„X" fehlt auf wartenden (blockierten) Kettengliedern:** `CanRemoveFromQueue = IsQueued || HasQueuedSubtasks` (TaskRowViewModel.cs:99), `IsQueued` verlangt leeres `blocked_by`. Ein gequeuetes, aber blockiertes Kind (`IsWaiting`) bekommt daher kein Remove-from-queue-X — nur Parent + erstes (entsperrtes) Kind.
|
- **Modal-Bodies unten abgeschnitten — gefixt, Sichtprüfung offen** (Ursache 2026-09-02 verifiziert: `ScrollViewer.Padding` zählt in Avalonia 12 nicht zum Extent; Fix ist das Padding auf ein `Margin` des inneren Inhalts umzuziehen). Betraf `AboutModalView`, `MergeHelperSelectionModal`, `MergeModalView`, `RepoImportModalView`, `WorktreesOverviewModalView` — alle fünf umgestellt. `ListSettingsModalView`, `LogVisualizerView` und `WorkConsole` hatten zu diesem Zeitpunkt bereits kein ScrollViewer-Padding mehr.
|
||||||
- **Session-Skills-Tab hat keinen Empty-State:** Bei 0 installierten Skills zeigt der Skills-Tab (Settings) nur eine nackte leere Fläche unter der Install-Zeile — kein erklärender Hinweis (z.B. „Noch keine Skills installiert — GitHub-URL oben einfügen"). Im `sessionSkillsTab`-Locale-Namespace (en.json:674) gibt es keinen `empty`-Key.
|
|
||||||
- **Modal-Bodies sind unten abgeschnitten** (Sichtprüfung 2026-08-06, beobachtet in den Listen-Settings: der Hinweistext unter „Verify command" ist mitten in der Zeile vom Footer weggeschnitten und lässt sich nicht herunterscrollen; laut Mika „fast überall" in den Settings-Modals). Gemeinsames Muster: `<ScrollViewer Padding="20,16">` als Modal-Body — in `ListSettingsModalView:36`, `MergeModalView:33`, `WorktreesOverviewModalView:131`, `AboutModalView:20`, `MergeHelperSelectionModal:47`, `RepoImportModalView:45`, `LogVisualizerView:45` sowie `WorkConsole:295/395`. **Vermutete** Ursache (nicht verifiziert): das untere `Padding` des ScrollViewers zählt nicht zum scrollbaren Extent, die letzten Pixel sind also unerreichbar. Erst am echten Fall nachmessen, dann ggf. einheitlich das Padding vom ScrollViewer auf ein `Margin` des inneren Inhalts umziehen.
|
|
||||||
- **Usage-Monitor-Modal: Analyse-Tabellen ausbaufähig** (Sichtprüfung 2026-08-06; Gauges, Info-Bänder, Presets/Custom-Range und Refresh-Button sind in Ordnung). Models-Tab: die Spaltenköpfe „OTHER CACHE" und „SHARE" kollidieren zu `OTHER CACHISHARE`; `claude-haiku-4-5-20251001` läuft in die IN-Spalte; alle Zahlen linksbündig und ohne Tausendertrenner (`506830708`). Tasks-Tab: Task-Titel wird ohne Ellipse hart an der LIST-Spalte abgeschnitten, der LIST-Text ebenfalls; MODEL ist bei Runs von vor der `task_runs.model`-Spalte leer (besser „—"). Wunsch: Zahlen rechtsbündig + `#,##0` (oder k/M), Spaltenbreiten/Truncation fixen.
|
- **Usage-Monitor-Modal: Analyse-Tabellen ausbaufähig** (Sichtprüfung 2026-08-06; Gauges, Info-Bänder, Presets/Custom-Range und Refresh-Button sind in Ordnung). Models-Tab: die Spaltenköpfe „OTHER CACHE" und „SHARE" kollidieren zu `OTHER CACHISHARE`; `claude-haiku-4-5-20251001` läuft in die IN-Spalte; alle Zahlen linksbündig und ohne Tausendertrenner (`506830708`). Tasks-Tab: Task-Titel wird ohne Ellipse hart an der LIST-Spalte abgeschnitten, der LIST-Text ebenfalls; MODEL ist bei Runs von vor der `task_runs.model`-Spalte leer (besser „—"). Wunsch: Zahlen rechtsbündig + `#,##0` (oder k/M), Spaltenbreiten/Truncation fixen.
|
||||||
- **Conflict-Resolver: mehrere Konfliktdateien schlecht erkennbar:** Beim 2-Datei-Konflikt schwer zu sehen, dass zwei Dateien betroffen sind (File-Switcher/Anzahl zu unauffällig). Prominentere Datei-Liste / „x von y Dateien".
|
- **Conflict-Resolver: mehrere Konfliktdateien schlecht erkennbar:** Beim 2-Datei-Konflikt schwer zu sehen, dass zwei Dateien betroffen sind (File-Switcher/Anzahl zu unauffällig). Prominentere Datei-Liste / „x von y Dateien".
|
||||||
- **Attachments: erster Drag&Drop der Session schlug einmalig fehl („An error occurred"):** Beim allerersten Drop-to-attach einer UI-Session zeigte die `DropStatus`-Zeile inline einen generischen Fehler und es wurde nichts persistiert (kein File, keine DB-Row); alle folgenden Drops derselben Session + der „Add file…"-Picker + Remove funktionierten fehlerfrei. Nicht reproduzierbar nach dem ersten Mal. Kandidaten: transiente SQLite-Contention (der UI-Prozess schreibt `todo.db` direkt via `new TaskAttachmentRepository`, während der Worker dieselbe DB hält) ODER ein Fehler im Drop-Stream-Pfad (`IStorageFile.OpenReadAsync` im Code-Behind, außerhalb des `try` in `AddFilesAsync`). Falls es erneut auftritt: `AddFilesAsync`/`OnDrop` mit robusterem Error-Logging versehen.
|
- **Attachments: erster Drag&Drop der Session schlug einmalig fehl („An error occurred"):** Beim allerersten Drop-to-attach einer UI-Session zeigte die `DropStatus`-Zeile inline einen generischen Fehler und es wurde nichts persistiert (kein File, keine DB-Row); alle folgenden Drops derselben Session + der „Add file…"-Picker + Remove funktionierten fehlerfrei. Nicht reproduzierbar nach dem ersten Mal. Kandidaten: transiente SQLite-Contention (der UI-Prozess schreibt `todo.db` direkt via `new TaskAttachmentRepository`, während der Worker dieselbe DB hält) ODER ein Fehler im Drop-Stream-Pfad (`IStorageFile.OpenReadAsync` im Code-Behind, außerhalb des `try` in `AddFilesAsync`). Falls es erneut auftritt: `AddFilesAsync`/`OnDrop` mit robusterem Error-Logging versehen.
|
||||||
@@ -46,36 +44,45 @@ verifiziert und deshalb hier entfernt. Es bleiben die Entscheidungen, die daran
|
|||||||
|
|
||||||
## Offene Verifikation (2026-08-27, Ticketsystem-Anbindung)
|
## Offene Verifikation (2026-08-27, Ticketsystem-Anbindung)
|
||||||
|
|
||||||
> **PAUSIERT.** `TicketSettingsTabViewModel.FeatureEnabled = false` versteckt Settings-Tab,
|
> **Reaktiviert am 2026-09-01.** `FeatureEnabled = true`. Die API läuft unter
|
||||||
> Projekt-Dropdown und Kontextmenü-Eintrag; zum Reaktivieren die Konstante umlegen. Grund: die
|
> `http://192.168.45.161` — der Hostname `bandelapis.fb-tuning.local` lehnt die Verbindung
|
||||||
> API ist unter `http://bandelapis.fb-tuning.local` nicht erreichbar (*connection actively
|
> weiterhin ab, die Base-URL muss also die **IP** sein.
|
||||||
> refused* — nichts lauscht dort). Kein ClaudeDo-Bug und kein CORS; wahrscheinlich fehlt eine
|
|
||||||
> IIS-Hostname-Bindung oder der Port stimmt nicht (`deploy/deploy-iis-winrm.ps1` der API notiert,
|
|
||||||
> dass srv-04b diese Sites *by IP:port rather than by hostname* erreicht). Wird mit dem
|
|
||||||
> API-Kollegen geklärt. Die Punkte unten bleiben stehen, bis die Erreichbarkeit steht.
|
|
||||||
|
|
||||||
Tasks 1–9 sind implementiert und gemergt (Build + Tests grün, `_broadcaster.TaskUpdated` genau
|
Alle vier Routen sind jetzt live gegen die echte API geprüft. Die aus dem `Bandel.APIs`-Quellcode
|
||||||
einmal in `TaskStateService.cs`), aber **kein Smoke-Test gegen die echte Bandel-API ist
|
abgeleiteten Shapes stimmten bis auf zwei Punkte, beide gefixt und mit Tests belegt:
|
||||||
gelaufen**. Die fünf Punkte aus der „Manuellen Verifikation" des Specs
|
|
||||||
(`docs/superpowers/specs/2026-08-27-ticketsystem-integration-design.md`):
|
|
||||||
|
|
||||||
- [ ] PAT in den Settings hinterlegen, „Verbindung testen" → korrekter Benutzername + Scopes.
|
- `PATCH /api/Ticket/{id}/status` braucht **`statusId` UND `statusName`** — ohne den Namen 400
|
||||||
- [ ] Liste mit Projekt `393 Bandel.LagerApp` verknüpfen, „Update from Ticketsystem" → nur
|
(„The StatusName field is required."), also hätte *jede* Write-Back fehlgeschlagen. Der Name
|
||||||
eigene, offene Tickets erscheinen als Tasks.
|
wird nicht gegen die Id validiert, muss aber nicht-leer sein (`TicketStatusMap.NameOf`).
|
||||||
- [ ] Einen Task laufen lassen → Ticket steht im Ticketsystem auf `InBearbeitung`.
|
- Das flache `assigneeName` im Board-Item ist auf **jedem** Ticket null (alle 63 in Abteilung
|
||||||
|
Entwicklung geprüft). Der Assignee-Filter des Imports hätte damit immer 0 Tasks erzeugt. Echte
|
||||||
|
Quelle: `ticketAssignees[].assigneeName`, das exakt das Format von `/pat/me`s `userName` hat.
|
||||||
|
`personInChargeName` ist die falsche Quelle (mal Login, mal Anzeigename).
|
||||||
|
|
||||||
|
Import zieht jetzt `Offen` **und** `InBearbeitung`, weiterhin nur eigene Tickets.
|
||||||
|
|
||||||
|
Bleibt manuell zu prüfen (UI-Pfad, nie sichtbar gelaufen):
|
||||||
|
|
||||||
|
- [ ] PAT in Settings → Ticketsystem hinterlegen (Base-URL `http://192.168.45.161`),
|
||||||
|
„Verbindung testen" → `mika.kuns` + 9 Scopes.
|
||||||
|
- [ ] Liste mit einem Projekt verknüpfen (Rechtsklick → Listeneinstellungen → Ticket-Projekt),
|
||||||
|
„Update from Ticketsystem" → nur eigene offene/laufende Tickets erscheinen als Tasks.
|
||||||
|
Achtung: aktuell ist **kein** offenes Ticket in Entwicklung dir zugewiesen — zum Testen
|
||||||
|
erst im Ticketsystem zuweisen, sonst ist 0 importiert das korrekte Ergebnis.
|
||||||
|
- [ ] Einen Task laufen lassen → Ticket steht auf `InBearbeitung`.
|
||||||
- [ ] Approve/Merge → Ticket steht auf `Fertig`.
|
- [ ] Approve/Merge → Ticket steht auf `Fertig`.
|
||||||
- [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr.
|
- [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr.
|
||||||
|
- [ ] Sichtprüfung des Settings-Tabs + des Ticket-Projekt-Dropdowns (nie gerendert gesehen).
|
||||||
**Wichtig:** die Response-Shapes der Bandel-API (`BandelEnvelope<T>`, `TicketIdentity`,
|
|
||||||
`TicketBoardItem`, `SidebarDepartment`/`SidebarProject`, `TicketDtos.cs`) wurden aus dem
|
|
||||||
**Quellcode** von `Bandel.APIs` abgeleitet, **nie live gegen die echte API verifiziert** — es gab
|
|
||||||
keinen einzigen echten Netzwerkaufruf während der Implementierung. Die Punkte 1–4 oben sind
|
|
||||||
zugleich der erste Test dieser Annahme: schlägt einer davon mit einem Deserialisierungs- oder
|
|
||||||
404/Formatfehler fehl, ist das erwartbar und kein Regressions-Bug — dann müssen die DTOs in
|
|
||||||
`src/ClaudeDo.Worker/Tickets/TicketDtos.cs` an die tatsächliche Antwort angepasst werden.
|
|
||||||
|
|
||||||
## Offene Verifikation (2026-07-27)
|
## Offene Verifikation (2026-07-27)
|
||||||
|
|
||||||
|
- **Empty-Repo-Hardening (2026-07-30)** — Worker/App neu starten (laufende Instanz hat den
|
||||||
|
Fix noch nicht), dann auf dem Task „HEy this is a Test" (Test List → `C:\Dev\TestRepo`,
|
||||||
|
aktuell 0 Commits) „Open ConPTY session" klicken: muss jetzt starten und in `C:\Dev\TestRepo`
|
||||||
|
genau einen leeren Commit „chore: initialize repository (ClaudeDo)" hinterlassen. Idealerweise
|
||||||
|
auch einmal auf dem Zweit-PC gegentesten (dort zusätzlich: Task-Run auf einem PC ohne
|
||||||
|
`git config user.name/email` — Auto-Commit darf nicht mehr scheitern).
|
||||||
|
|
||||||
- **List handler (2026-07-27)** — visual pass: the Broom button is gone from the lists footer,
|
- **List handler (2026-07-27)** — visual pass: the Broom button is gone from the lists footer,
|
||||||
the context-menu item appears only on lists with a working dir, and the selection dialog has no
|
the context-menu item appears only on lists with a working dir, and the selection dialog has no
|
||||||
LIST column. (Der real-Claude-Smoke-Run der fünf Phasen ist am 2026-07-29 gelaufen — 6/6 sauber
|
LIST column. (Der real-Claude-Smoke-Run der fünf Phasen ist am 2026-07-29 gelaufen — 6/6 sauber
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Empty-repo hardening — implementation plan
|
||||||
|
|
||||||
|
Spec: `docs/superpowers/specs/2026-07-30-empty-repo-hardening-design.md`
|
||||||
|
|
||||||
|
## Task 1 — GitService: HasHeadCommitAsync + EnsureHeadCommitAsync + friendly unborn error
|
||||||
|
|
||||||
|
**Files:** `src/ClaudeDo.Data/Git/GitService.cs`,
|
||||||
|
`tests/ClaudeDo.Worker.Tests/Runner/GitServiceBootstrapTests.cs` (new)
|
||||||
|
|
||||||
|
1. Tests first (skip when git unavailable, mirroring `WorktreeManagerTests`):
|
||||||
|
- fresh `git init` repo (no commit): `HasHeadCommitAsync` false; after a commit true.
|
||||||
|
- `EnsureHeadCommitAsync` on empty repo → true; `RevParseHeadAsync` then succeeds;
|
||||||
|
`git ls-tree HEAD` empty; second call → false.
|
||||||
|
- staged file in empty repo survives bootstrap: still staged, not in the commit.
|
||||||
|
- corrupt HEAD (write a garbage SHA into `.git/HEAD` as a detached head):
|
||||||
|
`EnsureHeadCommitAsync` throws `InvalidOperationException` mentioning the dir.
|
||||||
|
- `RevParseHeadAsync` on empty repo: message says "has no commits yet" (no raw
|
||||||
|
"ambiguous argument").
|
||||||
|
2. Implement: `HasHeadCommitAsync` (`rev-parse --verify --quiet HEAD`),
|
||||||
|
`EnsureHeadCommitAsync` (symbolic-ref guard → hash-object/commit-tree/update-ref
|
||||||
|
plumbing behind a static `SemaphoreSlim` with post-acquire re-check), unborn-HEAD
|
||||||
|
message in `RevParseHeadAsync`.
|
||||||
|
3. Build Data + run new tests. Commit `fix(git): bootstrap empty repos with an initial commit`.
|
||||||
|
|
||||||
|
## Task 2 — CommitAsync identity fallback
|
||||||
|
|
||||||
|
**Files:** same two files.
|
||||||
|
|
||||||
|
1. Test: repo with local `user.name`/`user.email` set to empty strings → `CommitAsync`
|
||||||
|
succeeds (retry path), commit exists.
|
||||||
|
2. Implement: on identity-signature failure, retry once with
|
||||||
|
`-c user.name=ClaudeDo -c user.email=claudedo@local`.
|
||||||
|
3. Commit `fix(git): fall back to a ClaudeDo identity when git has none`.
|
||||||
|
|
||||||
|
## Task 3 — call sites
|
||||||
|
|
||||||
|
**Files:** `src/ClaudeDo.Worker/Runner/WorktreeManager.cs`,
|
||||||
|
`src/ClaudeDo.Worker/Planning/PlanningSessionManager.cs`,
|
||||||
|
`tests/ClaudeDo.Worker.Tests/Runner/WorktreeManagerTests.cs`
|
||||||
|
|
||||||
|
1. Test: `CreateAsync` on a commit-less repo succeeds; worktree on disk; DB row Active.
|
||||||
|
2. Wire `EnsureHeadCommitAsync` into `WorktreeManager.CreateAsync` (log info on
|
||||||
|
bootstrap) and `PlanningSessionManager.StartAsync`.
|
||||||
|
3. Full Worker.Tests run. Commit `fix(worktree): auto-bootstrap commit-less repos before creating worktrees`.
|
||||||
|
|
||||||
|
## Task 4 — docs
|
||||||
|
|
||||||
|
- `src/ClaudeDo.Data/CLAUDE.md`: GitService bullet gains bootstrap/identity-fallback.
|
||||||
|
- `docs/open.md`: verification item — open a ConPTY session on a fresh `git init` repo
|
||||||
|
on a second PC.
|
||||||
|
- Commit `docs: empty-repo hardening notes`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `dotnet test tests/ClaudeDo.Worker.Tests -c Release` green.
|
||||||
|
- Manual: task 61609f79… in "Test List" (`C:\Dev\TestRepo`, currently commit-less) —
|
||||||
|
opening the ConPTY session must now succeed and leave `C:\Dev\TestRepo` with exactly
|
||||||
|
one empty commit by ClaudeDo.
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# Mehrfach-Abhängigkeiten (`depends_on` als Menge) — Implementation Plan
|
||||||
|
|
||||||
|
**Goal:** A task can declare *n* predecessors instead of exactly one. The queue picker
|
||||||
|
claims it only once **all** of them are `Done`.
|
||||||
|
|
||||||
|
**Architecture:** Replace the single `tasks.depends_on_task_id` column with a
|
||||||
|
`task_dependencies(task_id, depends_on_task_id)` relation table (composite PK, both FKs to
|
||||||
|
`tasks.id`, `ON DELETE CASCADE`). Cascade reproduces today's `ON DELETE SET NULL` semantics
|
||||||
|
on a join table: deleting a predecessor drops the edge, so the dependent becomes pickable
|
||||||
|
instead of blocked forever. `BlockedByTaskId` (the internal planning chain) is untouched —
|
||||||
|
the split stays deliberate.
|
||||||
|
|
||||||
|
**Tech Stack:** .NET 8, EF Core (SQLite), SignalR, Avalonia 12, xUnit.
|
||||||
|
|
||||||
|
**Not in scope:** `BlockedByTaskId`; rewriting the list-handler prompt to *use* multi-deps;
|
||||||
|
a graph visualizer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
**Create:**
|
||||||
|
- `src/ClaudeDo.Data/Models/TaskDependencyEntity.cs`
|
||||||
|
- `src/ClaudeDo.Data/Configuration/TaskDependencyEntityConfiguration.cs`
|
||||||
|
- `src/ClaudeDo.Data/Migrations/<ts>_AddTaskDependencies.cs` — scaffolded
|
||||||
|
- `src/ClaudeDo.Worker/External/TaskBlocking.cs` — the shared `blockedReason` builder
|
||||||
|
(two consumers: `ExternalMcpService`, `TaskWaitMcpTools`)
|
||||||
|
- `tests/ClaudeDo.Data.Tests/TaskDependencyMigrationTests.cs` — real `Migrate()` on a DB
|
||||||
|
seeded with legacy `depends_on_task_id` rows
|
||||||
|
|
||||||
|
**Modify:**
|
||||||
|
- `src/ClaudeDo.Data/Models/TaskEntity.cs` — `DependsOnTaskId` → `Dependencies` nav +
|
||||||
|
`[NotMapped] DependsOnTaskIds`
|
||||||
|
- `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs` — drop column/FK/index
|
||||||
|
- `src/ClaudeDo.Data/ClaudeDoDbContext.cs` — `DbSet<TaskDependencyEntity>`
|
||||||
|
- `src/ClaudeDo.Data/Repositories/TaskRepository.cs` — `.Include(t => t.Dependencies)` on
|
||||||
|
the three read paths; `UpdateAsync` must not push the nav graph
|
||||||
|
- `src/ClaudeDo.Worker/Queue/QueuePicker.cs` — `NOT EXISTS` over the relation table
|
||||||
|
- `src/ClaudeDo.Worker/State/TaskStateService.cs` — `SetDependsOnAsync` takes a set,
|
||||||
|
graph-wide cycle check
|
||||||
|
- `src/ClaudeDo.Worker/State/Interfaces/ITaskStateService.cs`
|
||||||
|
- `src/ClaudeDo.Worker/External/ExternalMcpService.cs` — DTOs, `add_task`, `update_task`,
|
||||||
|
`ComputeBlockedInfoAsync`
|
||||||
|
- `src/ClaudeDo.Worker/External/TaskWaitMcpTools.cs`
|
||||||
|
- `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs`,
|
||||||
|
`TasksIslandViewModel.cs` — display of *n* predecessors
|
||||||
|
- `src/ClaudeDo.Data/CLAUDE.md`, `src/ClaudeDo.Worker/CLAUDE.md`
|
||||||
|
- test files listed per task below
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 — Schema + migration
|
||||||
|
|
||||||
|
- [ ] `TaskDependencyEntity { string TaskId; string DependsOnTaskId; }`, table
|
||||||
|
`task_dependencies`, composite PK, both FKs cascade, index on `depends_on_task_id`.
|
||||||
|
- [ ] `TaskEntity.Dependencies` (`ICollection<TaskDependencyEntity>`), `[NotMapped]
|
||||||
|
DependsOnTaskIds` projection. `DependsOnTaskId` and its FK/index are removed.
|
||||||
|
- [ ] Scaffold the migration, then **hand-edit the Up body** so the data copy sits between
|
||||||
|
create and drop:
|
||||||
|
`CreateTable` → `INSERT INTO task_dependencies (task_id, depends_on_task_id) SELECT
|
||||||
|
id, depends_on_task_id FROM tasks WHERE depends_on_task_id IS NOT NULL` →
|
||||||
|
`DropColumn`. Down reverses it (add column, `UPDATE … FROM` the first edge, drop
|
||||||
|
table) — lossy for >1 edge, which is inherent and documented in the migration.
|
||||||
|
- [ ] Verify: `dotnet ef database update` against a temp DB seeded with legacy rows;
|
||||||
|
`pragma_table_info` diff. **`DropColumn` forces a SQLite table rebuild** — the
|
||||||
|
`ef-migration-parallel-designer-collision` trap applies: if another migration lands
|
||||||
|
in parallel, hand-check both `.Designer.cs` snapshots.
|
||||||
|
|
||||||
|
**Test:** `TaskDependencyMigrationTests` — migrate to the previous migration, seed a task
|
||||||
|
with `depends_on_task_id` via raw SQL, migrate to head, assert the edge is in
|
||||||
|
`task_dependencies` and nothing else changed. (Acceptance #2.)
|
||||||
|
|
||||||
|
## Task 2 — Queue gating
|
||||||
|
|
||||||
|
- [ ] `QueuePicker` replaces the `depends_on_task_id IS NULL OR EXISTS(...)` clause with
|
||||||
|
a doubly-negated `NOT EXISTS` over `task_dependencies`, so a dangling edge keeps
|
||||||
|
blocking exactly as today:
|
||||||
|
```sql
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM task_dependencies td
|
||||||
|
WHERE td.task_id = t.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM tasks d
|
||||||
|
WHERE d.id = td.depends_on_task_id AND d.status = 'done')
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** (`QueuePickerTests`, real SQLite): n=2 both `Done` → claimed; one `Done` one
|
||||||
|
`Running` → skipped; zero deps → claimed (regression); one dep `Failed` → still skipped,
|
||||||
|
no cascade (acceptance #6); one dep `Cancelled` → still skipped.
|
||||||
|
|
||||||
|
## Task 3 — Write path + cycle detection
|
||||||
|
|
||||||
|
- [ ] `ITaskStateService.SetDependsOnAsync(string taskId, IReadOnlyList<string>
|
||||||
|
dependsOnTaskIds, CancellationToken)` — empty list clears. Rejects self-reference,
|
||||||
|
unknown id, duplicate handling by de-dup.
|
||||||
|
- [ ] Cycle check walks the **graph**: load the whole edge set once (it only ever holds
|
||||||
|
*declared* dependencies, so it stays small — `ponytail:` note, switch to a recursive
|
||||||
|
CTE if it ever isn't), BFS from every proposed predecessor; reaching `taskId` is a
|
||||||
|
cycle. Visited set guards pre-existing bad data.
|
||||||
|
- [ ] Replace rows transactionally (`ExecuteDelete` for `taskId` + `AddRange`), wake the
|
||||||
|
queue when the set shrank.
|
||||||
|
|
||||||
|
**Tests** (`TaskStateServiceTests`): sets n edges; empty list clears + wakes; self-ref
|
||||||
|
rejected; unknown id rejected; **A→B→C→A rejected** (acceptance #3); adding a second edge
|
||||||
|
leaves the first intact.
|
||||||
|
|
||||||
|
## Task 4 — MCP surface
|
||||||
|
|
||||||
|
- [ ] `add_task` / `update_task` keep `dependsOnTaskId` (string) **and** gain
|
||||||
|
`dependsOnTaskIds` (`string[]?`). Precedence: `dependsOnTaskIds` non-null wins;
|
||||||
|
otherwise the legacy scalar is treated as a one-element set, with `""` still the
|
||||||
|
clear sentinel. Both run through `TaskIdResolver` so `#123` keeps working.
|
||||||
|
- [ ] `TaskDto`/`TaskRefDto`: `DependsOnTaskId` → `DependsOnTaskIds`
|
||||||
|
(`IReadOnlyList<string>`). Only the *input* form is contractually frozen.
|
||||||
|
- [ ] `TaskBlocking.Describe` builds one reason naming **every** unmet predecessor
|
||||||
|
(acceptance #4); `ComputeBlockedInfoAsync` and `TaskWaitMcpTools.CheckOnceAsync`
|
||||||
|
both use it.
|
||||||
|
|
||||||
|
**Tests** (`ExternalMcpServiceTests`, `TaskWaitMcpToolsTests`, `BatchMcpToolsTests`):
|
||||||
|
legacy scalar still sets + clears the link (acceptance #5, explicit back-compat test);
|
||||||
|
`dependsOnTaskIds` sets n; cycle via the list form throws; `blockedReason` names both
|
||||||
|
open predecessors.
|
||||||
|
|
||||||
|
## Task 5 — UI
|
||||||
|
|
||||||
|
- [ ] `TaskRowViewModel.DependsOnTaskIds`; `DependsOnTaskId` becomes a computed
|
||||||
|
"single predecessor or null" so the existing linear-chain rail is unchanged for the
|
||||||
|
1-dependency case.
|
||||||
|
- [ ] `BuildAfterLabel` joins all resolvable predecessors (`#3, #7`); a row with >1
|
||||||
|
predecessor gets the label, never a rail (a rail can only render a line).
|
||||||
|
- [ ] `GroupingKey` folds the joined ids so the reconcile tick still detects a change.
|
||||||
|
- [ ] `.Include(t => t.Dependencies)` on the `TasksIslandViewModel` query sites that
|
||||||
|
hydrate rows.
|
||||||
|
|
||||||
|
**Tests** (`TasksIslandChainTests`, `TasksIslandClassifyItemsTests`,
|
||||||
|
`TasksIslandReconcileTickTests`): existing chain tests keep passing via the 1-dep path;
|
||||||
|
a 2-dep row shows a joined after-label and no rail.
|
||||||
|
|
||||||
|
## Task 6 — Docs + verify
|
||||||
|
|
||||||
|
- [ ] `src/ClaudeDo.Data/CLAUDE.md` (Models, Schema) and
|
||||||
|
`src/ClaudeDo.Worker/CLAUDE.md` (Status Model table, picker bullet,
|
||||||
|
`TaskStateService` bullet) describe the relation table (acceptance #7).
|
||||||
|
- [ ] VERIFY (acceptance #8): `dotnet build src/ClaudeDo.App -c Release`,
|
||||||
|
`dotnet build src/ClaudeDo.Worker -c Release`, `dotnet test` for
|
||||||
|
`ClaudeDo.Data.Tests`, `ClaudeDo.Worker.Tests`, `ClaudeDo.Ui.Tests` (`-c Release`),
|
||||||
|
report counts.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Empty-repo hardening (unborn HEAD) — design
|
||||||
|
|
||||||
|
**Date:** 2026-07-30
|
||||||
|
**Status:** approved (option "Auto-Bootstrap" chosen by Mika)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
`GetInteractiveLaunchSpec` (embedded ConPTY session) fails with a raw git error when the
|
||||||
|
task's list points at a git repository that has **no commits yet** (fresh `git init`,
|
||||||
|
unborn HEAD):
|
||||||
|
|
||||||
|
```
|
||||||
|
HubException: git rev-parse HEAD failed (exit 128): fatal: ambiguous argument 'HEAD': unknown revision ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Root cause chain: `InteractiveLaunchSpecService.BuildForTaskAsync` → `WorktreeManager.CreateAsync`
|
||||||
|
→ `IsGitRepoAsync` (passes — `rev-parse --git-dir` works on an empty repo) →
|
||||||
|
`ResolveBaseCommitAsync` → `RevParseHeadAsync` (fatals — HEAD is unborn). The same gap
|
||||||
|
exists in `PlanningSessionManager.StartAsync` and hits autonomous runs (`TaskRunner` →
|
||||||
|
`CreateAsync` → task `Failed` with the same cryptic message).
|
||||||
|
|
||||||
|
This is a "works on my laptop" class of bug: on a fresh PC the first thing a user tries is
|
||||||
|
a freshly init-ed test repo, which breaks instantly. A second member of the same class:
|
||||||
|
a PC without `git config user.name/email` breaks every auto-commit
|
||||||
|
("Please tell me who you are").
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**Auto-bootstrap:** when a worktree (task run / ConPTY / planning) needs a base commit and
|
||||||
|
the repo has zero commits, ClaudeDo creates an empty initial commit automatically and
|
||||||
|
proceeds. Nothing can be lost — the repo is empty. Genuinely broken states (HEAD not a
|
||||||
|
symbolic ref and not resolvable) still produce a clear, actionable error instead of raw
|
||||||
|
git stderr.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### 1. `GitService.HasHeadCommitAsync(dir)`
|
||||||
|
|
||||||
|
`git rev-parse --verify --quiet HEAD` → exit 0 means HEAD resolves to a commit.
|
||||||
|
|
||||||
|
### 2. `GitService.EnsureHeadCommitAsync(dir)` → `bool` (true = bootstrapped)
|
||||||
|
|
||||||
|
- HEAD resolves → return `false` (no-op).
|
||||||
|
- `git symbolic-ref -q HEAD` fails → repo is corrupt (detached HEAD onto a missing
|
||||||
|
commit, mangled `.git/HEAD`) → throw `InvalidOperationException` with a clear message
|
||||||
|
naming the directory. Auto-fixing a corrupt repo is out of scope by design.
|
||||||
|
- Otherwise HEAD is an unborn branch → bootstrap **via plumbing only** (never touches the
|
||||||
|
user's index or working tree — staged/untracked files stay exactly as they were):
|
||||||
|
1. `git hash-object -w -t tree --stdin` with empty stdin → empty tree object.
|
||||||
|
2. `git -c user.name=ClaudeDo -c user.email=claudedo@local commit-tree <tree> -m
|
||||||
|
"chore: initialize repository (ClaudeDo)"` — inline identity so this works on a PC
|
||||||
|
with no git identity configured.
|
||||||
|
3. `git update-ref <unborn-branch-ref> <commit>`.
|
||||||
|
- Serialized behind a process-wide gate with a re-check after acquiring, so two parallel
|
||||||
|
task starts on the same empty repo bootstrap exactly once.
|
||||||
|
|
||||||
|
### 3. Defense-in-depth: friendly `RevParseHeadAsync` error
|
||||||
|
|
||||||
|
When `rev-parse HEAD` fails with the unborn-HEAD signature ("ambiguous argument 'HEAD'"),
|
||||||
|
throw "The repository at {dir} has no commits yet" instead of raw stderr — covers any
|
||||||
|
future call site that forgets the preflight.
|
||||||
|
|
||||||
|
### 4. Call sites
|
||||||
|
|
||||||
|
- `WorktreeManager.CreateAsync` — after the `IsGitRepoAsync` guard, call
|
||||||
|
`EnsureHeadCommitAsync`; log when a bootstrap happened. Covers autonomous runs,
|
||||||
|
interactive ConPTY sessions, improvement/planning children.
|
||||||
|
- `PlanningSessionManager.StartAsync` — same, before its own `RevParseHeadAsync`.
|
||||||
|
|
||||||
|
### 5. Git identity fallback for auto-commits
|
||||||
|
|
||||||
|
`GitService.CommitAsync`: if the commit fails with the missing/empty-identity signature,
|
||||||
|
retry once with inline `-c user.name="ClaudeDo" -c user.email="claudedo@local"`.
|
||||||
|
Happy path costs nothing (no preflight call); a fresh PC without git identity no longer
|
||||||
|
fails every task auto-commit.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- UI validation when configuring a list's WorkingDir (nice-to-have; the worktree paths now
|
||||||
|
either self-heal or produce clear errors).
|
||||||
|
- Repairing corrupt repos.
|
||||||
|
|
||||||
|
## Test plan (real git, Worker.Tests)
|
||||||
|
|
||||||
|
- `HasHeadCommitAsync`: false on fresh `git init`, true after a commit.
|
||||||
|
- `EnsureHeadCommitAsync` on an empty repo: returns true, `rev-parse HEAD` then works,
|
||||||
|
tree of the bootstrap commit is empty, second call returns false.
|
||||||
|
- Staged-file preservation: file staged in empty repo → after bootstrap it is still
|
||||||
|
staged and NOT part of the bootstrap commit.
|
||||||
|
- Corrupt HEAD (point `.git/HEAD` at a bogus ref? no — detach onto a garbage SHA by
|
||||||
|
writing `.git/HEAD` directly): clear error, no bootstrap.
|
||||||
|
- `CommitAsync` identity fallback: local `user.name`/`user.email` set to empty strings
|
||||||
|
(forces the identity failure deterministically regardless of global config) → commit
|
||||||
|
succeeds via fallback, author is ClaudeDo.
|
||||||
|
- `WorktreeManager.CreateAsync` on an empty repo: succeeds; worktree exists; base commit
|
||||||
|
= bootstrap commit.
|
||||||
|
- `PlanningSessionManager.StartAsync` on an empty repo: succeeds (if existing test infra
|
||||||
|
makes this cheap; otherwise covered by the shared code path).
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
# Sichtprüfungs-Session 2026-09-16
|
||||||
|
|
||||||
|
Build: Debug 2.15.0 (App 08:05, Worker 09:40 — enthält den uncommitteten `WorktreeUpdated`-Fix).
|
||||||
|
Sandkasten: Liste **ClaudeDoTests** → `C:\TestRepos\ClaudeDoTests` (sauber, alle Alt-Tasks Done).
|
||||||
|
Vorgeseedet: **#334** (README.md) und **#335** (docs/notes.md) — laufen, landen in `WaitingForReview`.
|
||||||
|
|
||||||
|
Abgedeckt wird alles seit dem letzten Sichtprüfungs-Pass (06.08.), also v2.13.x–v2.15.
|
||||||
|
**Bewusst draußen:** Installer/SystemCheckPage (braucht echte Installation) und die
|
||||||
|
Ticketsystem-Anbindung (braucht ein zugewiesenes Ticket) — beide aus `docs/open.md`.
|
||||||
|
|
||||||
|
Notation: `[ ]` offen · `[x]` ok · `[!]` Fehler (Text dahinter = Fix-Kandidat)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pass 1 — Sidebar: Listen & Ordner (~8 min) · **ERLEDIGT** — 1.1–1.8 ok bis auf 1.2 (→ F1)
|
||||||
|
|
||||||
|
> 1.7 verhält sich gut: Ordner löschen schiebt die enthaltene Liste raus statt sie mitzunehmen.
|
||||||
|
|
||||||
|
- [ ] **1.1** Rechtsklick in die Listen-Sidebar → „Neue Liste": im Modal den Ordner-Toggle
|
||||||
|
einschalten → es entsteht ein **Ordner**, nicht eine Liste.
|
||||||
|
- [ ] **1.2** Ordner anklicken → klappt auf/zu, Pfeil dreht sich, Zustand bleibt nach
|
||||||
|
Listenwechsel erhalten.
|
||||||
|
- [ ] **1.3** Eine bestehende Liste per Drag in den Ordner ziehen → landet drin, Ordner zeigt sie.
|
||||||
|
- [ ] **1.4** Liste wieder aus dem Ordner heraus auf die oberste Ebene ziehen → geht zurück.
|
||||||
|
- [ ] **1.5** **Gemischte Reihenfolge** (das ist der Kern von 2.14): eine Liste **über** einen
|
||||||
|
Ordner ziehen → Reihenfolge hält. Ordner per Drag zwischen zwei Listen schieben → hält auch.
|
||||||
|
- [ ] **1.6** App neu starten (oder Worker-Menü → Reconnect) → die gemischte Reihenfolge ist
|
||||||
|
noch dieselbe. *Das ist der Test, der `ReorderSidebarAsync` wirklich prüft.*
|
||||||
|
- [ ] **1.7** Ordner löschen, während eine Liste drin liegt → was passiert mit der Liste?
|
||||||
|
(Erwartung unklar — notieren, was es tut.)
|
||||||
|
- [ ] **1.8** Rechtsklick auf einen Task → „In Liste verschieben" → Ziel wählen → Task ist weg
|
||||||
|
aus der Quelle, da in der Zielliste.
|
||||||
|
|
||||||
|
## Pass 2 — Review, Merge, Stash (~15 min) · der wichtigste Pass
|
||||||
|
|
||||||
|
Braucht #334/#335 in `WaitingForReview`. Sag mir Bescheid, falls sie noch laufen.
|
||||||
|
|
||||||
|
- [ ] **2.1** ⚠️ **Der uncommittete Fix.** Auf einem Task „Open ConPTY session" → im Terminal
|
||||||
|
irgendeine Kleinigkeit ändern → in der Mission-Control-Kachel „Submit for review".
|
||||||
|
**Erwartung:** Der Task landet in `WaitingForReview` und die REVIEW-Karte im Detail-Pane
|
||||||
|
zeigt **sofort** „Open diff" + Änderungs-Zusammenfassung — **ohne** dass du den Task neu
|
||||||
|
anklicken musst. (Vorher fehlte der ganze Block bis zur Neuauswahl.)
|
||||||
|
- [ ] **2.2** #334 anklicken → „Open diff" → Side-by-side-Diff zeigt die README-Änderung,
|
||||||
|
Zeilen/Farben stimmen, kein Abschneiden unten.
|
||||||
|
- [ ] **2.3** Approve-Gate: **ohne** geöffneten Diff ist Approve gesperrt, nach dem Diff aktiv.
|
||||||
|
- [ ] **2.4** Approve auf #334 → merged, Task geht auf **Done**, Chip wechselt, Worker-Log-Zeile
|
||||||
|
im Footer erscheint (mit `#334` im Text — deckt gleich Pass 5.1 mit ab).
|
||||||
|
- [ ] **2.5** 🔥 **Stash-Option (neuester Commit, komplett ungetestet).** Ich mache dir das
|
||||||
|
Ziel-Verzeichnis absichtlich dirty (sag „mach dirty"), dann auf #335 **Approve & Merge**:
|
||||||
|
Erwartung = **Bestätigungsdialog** „Änderungen stashen und mergen?" statt der alten
|
||||||
|
`blocked`-Sackgasse. Bestätigen → Merge läuft, und deine uncommitteten Änderungen in
|
||||||
|
`C:\TestRepos\ClaudeDoTests` sind danach **wieder da**.
|
||||||
|
- [ ] **2.6** Derselbe Fall über das **Merge-Modal** (statt Approve): Button
|
||||||
|
„Änderungen stashen und mergen" ist da und tut dasselbe.
|
||||||
|
- [ ] **2.7** Abbrechen im Stash-Dialog → nichts passiert, Task bleibt `WaitingForReview`,
|
||||||
|
Arbeitsverzeichnis unverändert.
|
||||||
|
- [ ] **2.8** Worktrees-Übersicht öffnen → die gemergten Worktrees stehen als `Merged` drin,
|
||||||
|
Cleanup funktioniert, keine WARN-Flut im Footer.
|
||||||
|
|
||||||
|
## Pass 3 — Interaktive Sessions & Mission Control (~12 min)
|
||||||
|
|
||||||
|
- [ ] **3.1** ConPTY-Session läuft → auf das **X** klicken → **Rückfrage** erscheint
|
||||||
|
(„Session läuft wirklich schließen?"), Abbrechen lässt sie laufen.
|
||||||
|
- [ ] **3.2** Im interaktiven Terminal **Text einfügen** (Ctrl+V) → landet im Prompt.
|
||||||
|
- [ ] **3.3** Eine **Datei ins Terminal droppen** → Pfad wird eingefügt.
|
||||||
|
Und: ein Drop, der scheitern muss (z.B. Ordner statt Datei) → Fehler wird **sichtbar**
|
||||||
|
gemeldet, nicht still geschluckt.
|
||||||
|
- [ ] **3.4** **Session stirbt nach dem Start** (im Terminal `exit` tippen) → Banner erscheint
|
||||||
|
in der Kachel, nicht einfach eine tote schwarze Fläche.
|
||||||
|
- [ ] **3.5** Mission Control: **Home-Ordner als Sitzungsziel** — neue Session ohne Liste/Task
|
||||||
|
starten → läuft im User-Verzeichnis, nicht irgendwo.
|
||||||
|
- [ ] **3.6** Mission Control ist **minimiert** → Handler-Run/Session startet → Fenster wird
|
||||||
|
aus dem Minimiert-Zustand geholt, nicht nur in der Taskleiste blinkend.
|
||||||
|
- [ ] **3.7** Handler-Run per Knopf **abbrechen** → Run endet sauber, Kachel verschwindet/
|
||||||
|
wechselt, kein Geister-Pane.
|
||||||
|
|
||||||
|
## Pass 4 — Menüs, Settings, Fenster-Layout (~12 min)
|
||||||
|
|
||||||
|
- [ ] **4.1** Hilfe-Menü → **Cheat-Sheets** → Fenster öffnet, sechs Sheets (git, Conventional
|
||||||
|
Commits, Claude CLI, Claude-Config, dotnet CLI, PowerShell).
|
||||||
|
- [ ] **4.2** Im Cheat-Sheet: Sektionen sind Expander, Befehle sind Mono-Chips mit
|
||||||
|
**Copy-Button** → Klick kopiert wirklich (irgendwo einfügen und prüfen).
|
||||||
|
- [ ] **4.3** Cheat-Sheet-Fenster **anpinnen** (Topmost) → bleibt über der App, App bleibt
|
||||||
|
bedienbar (modelos).
|
||||||
|
- [ ] **4.4** Hilfe-Menü → „Nach Updates suchen" und „Über" → Version zeigt **2.15.0**
|
||||||
|
(aus der FileVersion, nicht hartkodiert).
|
||||||
|
- [ ] **4.5** Worker-Menü → **„Update Claude CLI"** → führt sichtbar `claude update` aus,
|
||||||
|
Ausgabe landet irgendwo lesbar. *(Achtung: ändert wirklich deine CLI — überspring das,
|
||||||
|
wenn du gerade keine CLI-Version riskieren willst.)*
|
||||||
|
- [ ] **4.6** Worker-Menü → **Shutdown** → Rückfrage → beendet Worker **und** App.
|
||||||
|
*Ganz am Ende machen, sonst ist die Session vorbei.*
|
||||||
|
- [ ] **4.7** Settings → **Ausführung**: die PowerUse-Einstellungen sind hier (nicht mehr im
|
||||||
|
Usage-Monitor), Erklärtexte zu Effort und „Prime Claude" sind da und verständlich.
|
||||||
|
Ungültiges PowerUse-Zeitfenster eingeben → wird abgelehnt/erklärt, nicht still gespeichert.
|
||||||
|
- [ ] **4.8** **Modal-Layout** (Fix vom 02.09., Sichtprüfung offen): Über-Dialog,
|
||||||
|
Merge-Modal, Repo-Import, Worktrees-Übersicht, Merge-Helper-Auswahl **einmal öffnen** und
|
||||||
|
prüfen, dass unten **nichts abgeschnitten** ist (letzte Zeile/Button voll sichtbar).
|
||||||
|
- [ ] **4.9** Listeneinstellungen einer Liste öffnen → Modal erscheint **sofort** (lädt sichtbar
|
||||||
|
nach), und: Arbeitsverzeichnis auf einen Nicht-Git-Ordner setzen → **Warnung + Speichern
|
||||||
|
blockiert**, nicht stillschweigend akzeptiert.
|
||||||
|
- [ ] **4.10** Merge-Helper starten → Start-Modal hat den **Triage-Modell-Picker (opus/fable)**.
|
||||||
|
*(Nur das Modal ansehen, Lauf nicht starten — kostet sonst die halbe Stunde.)*
|
||||||
|
|
||||||
|
## Pass 5 — Reste & bekannte Nits (~8 min)
|
||||||
|
|
||||||
|
- [ ] **5.1** **Task-Nummern:** `#334` steht dimmed vor dem Titel in der Zeile, der Detail-Chip
|
||||||
|
zeigt `#334` (nicht mehr GUID), Footer-Worker-Log schreibt `#334 …`.
|
||||||
|
- [ ] **5.2** **depends_on sichtbar:** zwei Tasks verketten → der blockierte zeigt einen
|
||||||
|
Blocking-Chip, ein Ketten-Label („wartet auf …") und im Detail-Pane den Grund.
|
||||||
|
In der Queue-Zeile hat auch der **blockierte** Task ein Dequeue-**X**.
|
||||||
|
- [ ] **5.3** **Usage-Monitor-Modal → Models-Tab:** die bekannten Nits — kollidierende
|
||||||
|
Spaltenköpfe (`OTHER CACHISHARE`), Zahlen linksbündig ohne Tausendertrenner, Task-Titel
|
||||||
|
hart abgeschnitten. Nur bestätigen, dass sie noch da sind → geht auf die Fix-Liste.
|
||||||
|
- [ ] **5.4** **Deutsch:** einmal die Sprache auf DE stellen und durch Sidebar, Detail-Pane,
|
||||||
|
WorkConsole, ROADBLOCK-Karte und die Merge-Dialoge gehen → keine englischen Reste.
|
||||||
|
*(Der de.json-Konsistenz-Pass vom 10.09. ist nie angeschaut worden.)*
|
||||||
|
- [ ] **5.5** **Worker-Verbindung:** Worker-Menü → Reconnect bei laufendem Worker → Erfolg
|
||||||
|
sichtbar. Und einmal Worker beenden, dann Reconnect → **Fehlschlag wird gemeldet**
|
||||||
|
(Footer-Strip), nicht still no-op.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ergebnis
|
||||||
|
|
||||||
|
Alle fünf Pässe durchlaufen. **11 Befunde** aus ~45 Checks; Pass 4 (Menüs/Settings/Modal-Layout)
|
||||||
|
war komplett sauber. Die drei schweren (F9/F10/F11) sind alle erst ganz am Ende aufgetaucht —
|
||||||
|
Lebenszyklus der App, nicht Features.
|
||||||
|
|
||||||
|
Nicht getestet: Installer/SystemCheckPage, Ticketsystem-Anbindung (beide brauchen echtes Setup),
|
||||||
|
3.7 Handler-Abbruch nur oberflächlich.
|
||||||
|
|
||||||
|
## Delegiert als Tasks (Liste „Claude do", alle Idle)
|
||||||
|
|
||||||
|
| Task | Deckt ab |
|
||||||
|
|---|---|
|
||||||
|
| **#339** Shutdown beendet den Prozess nicht, Worker-Neustart tut nichts | F9 + F11 |
|
||||||
|
| **#340** App-Speicherverbrauch messen — nur Diagnose | F10 |
|
||||||
|
| **#341** duplizierter Confirm-Dialog mit „Delete"-Knopf | F4 + F5 |
|
||||||
|
| **#342** Ordner-Toggle schluckt schnelle Klicks | F1 |
|
||||||
|
| **#343** Diff-Viewer horizontal nicht synchron | F3 |
|
||||||
|
| **#344** dirty Ziel früh melden statt nach dem Fehlschlag | F6 |
|
||||||
|
| **#345** Bilder aus der Zwischenablage ins Terminal | F8 |
|
||||||
|
| **#346** Worktrees-Übersicht: Nummern + Truncation | F7 |
|
||||||
|
|
||||||
|
Nicht delegiert: **F2** (Modell für interaktive Sessions erzwingen) — Entscheidung Mika offen.
|
||||||
|
|
||||||
|
## Fix-Liste
|
||||||
|
|
||||||
|
| # | Pass | Befund | Schwere |
|
||||||
|
|---|------|--------|---------|
|
||||||
|
| F9 | 4.6 | **„Shutdown" beendet die App nicht — der Prozess überlebt.** `IslandsShellViewModel.cs:705` `ShutdownWorkerAndCloseApp` killt den Worker und ruft dann nur `CloseWindow` für das Hauptfenster. Mission Control, Cheat-Sheets und der Diff-Viewer sind eigene `Window`-Instanzen, bleiben offen und halten den Avalonia-Prozess am Leben (bestätigt: alle Nebenfenster standen nach dem „Shutdown" noch offen, `ClaudeDo.App` weiterhin im Task-Manager). Fix: alle Fenster schließen bzw. `IClassicDesktopStyleApplicationLifetime.Shutdown()` statt `CloseWindow`. | **hoch** |
|
||||||
|
| F10 | — | **App-Speicherverbrauch ~1,2 GB.** Gemessen 2026-09-16 am laufenden Debug-Build: `ClaudeDo.App` 1227 MB, `ClaudeDo.Worker` 209 MB. Der Worker wurde 2026-09-02 (`c347e4fc`) bewusst auf Workstation-GC umgestellt, die App offenbar nicht — erster Verdacht, aber ungeprüft. Braucht eine echte Messung (Server-GC? gehaltene Log-/Diff-Puffer? ConPTY-Scrollback?), keinen Blindfix. | **hoch** |
|
||||||
|
| F11 | 5.5 | **Worker-Neustart über den Button funktioniert nicht.** `IslandsShellViewModel.cs:656` `RestartWorkerService` beginnt mit `_workerLocator.Find()` und wirft „Worker executable not found", wenn nichts passt. **Hypothese, nicht verifiziert:** der Locator findet den *installierten* Worker (`C:\Program Files\ClaudeDo`), während der Debug-Build aus dem Repo läuft — dann wird der falsche oder gar kein Prozess gekillt/gestartet. Erst reproduzieren und die Footer-Meldung lesen, dann fixen. | **hoch** |
|
||||||
|
| F8 | 3.2 | **Bilder aus der Zwischenablage lassen sich nicht ins interaktive Terminal einfügen** (Text geht). `InteractiveTerminalViewModel.PasteAsync` → `PtyTerminalSession.PasteAsync` liest nur Text. Lazy Fix: Bild-Clipboard in eine temporäre PNG schreiben und deren Pfad über den bereits funktionierenden Drop-Pfad (`SendTextAsync`) einfügen — Claude liest die Datei dann selbst. Kein neuer Mechanismus nötig. | mittel |
|
||||||
|
| F6 | 2.6 | **Dirty-Target wird zu spät gemeldet.** `dirty_target` kommt erst als Fehlerergebnis *nachdem* der Merge gedrückt und abgelehnt wurde — der Zustand des Ziel-Trees ist aber schon beim Öffnen des Merge-Modals bekannt. Wunsch (Mika): beim Laden des Modals prüfen, Hinweis + „Änderungen stashen und mergen" direkt anbieten; ggf. schon im Diff-Viewer signalisieren. Betrifft beide Pfade (Approve + Merge-Modal). | mittel |
|
||||||
|
| F7 | 2.8 | **Worktrees-Übersicht zeigt keine Task-Nummern.** TASK-Spalte listet nur den Titel; die Task-Numbers-Feature hat Zeile, Detail-Chip und Worker-Log abgedeckt, diese Tabelle nicht. Zusätzlich ist die DIFF-Spalte hart abgeschnitten ohne Ellipse (`docs/notes.m`, `1 file chan`) — gleiches Truncation-Muster wie im Usage-Monitor (F-Nit 5.3). | niedrig |
|
||||||
|
| F4 | 2.5 | **Stash-Bestätigung trägt „Delete" als Bestätigungsknopf.** `DetailsIslandView.axaml.cs:262-263` ist eine zweite, kopierte Confirm-Dialog-Implementierung mit hartkodiertem `Content = "Delete"` (+ `Classes = { "danger" }`); `DetailsIslandViewModel.ConfirmAsync` hängt daran statt am gemeinsamen `WindowDialogService.ConfirmAsync:209` (dort korrekt „Confirm"). Der Stash-Dialog erbt damit die Beschriftung eines Lösch-Dialogs — irreführend bis gefährlich. Fix: Duplikat löschen, VM an den Service-Dialog hängen. | **hoch** |
|
||||||
|
| F5 | 2.5 | **Confirm-/Error-Dialoge sind nicht lokalisiert.** Beide Implementierungen setzen `"Cancel"`/`"Confirm"`/`"Delete"`/`"OK"` und den Fenstertitel `"Confirm"`/`"Error"` als String-Literal, kein `Loc.T`. Fällt mit F4 zusammen erledigen. | mittel |
|
||||||
|
| F3 | 2.2 | **Diff-Viewer scrollt nur vertikal synchron.** `DiffEditorSetup.cs` — die Sync-Klasse heißt `VerticalScrollSync`, Zeile 147 schreibt `new Vector(viewer.Offset.X, y)` und behält das X der Zielspalte bei; horizontal wurde nie verdrahtet, beide Seiten scrollen seitwärts unabhängig. Fix: X 1:1 mitspiegeln (gleiche Schrift/Breite in beiden Panes), Klasse entsprechend umbenennen. | mittel |
|
||||||
|
| F2 | 2.1 | **Feature-Wunsch (kein Bug):** interaktive ConPTY-Sessions starten unter dem Modell aus Mikas Claude-Config, nicht unter dem im Task gewählten. Bewusste Entscheidung, siehe `docs/open.md:35` (`--effort` ja, `--model` nein) — dort schon als möglicher Folge-Task vermerkt. Entscheidung Mika offen. | Wunsch |
|
||||||
|
| F1 | 1.2 | **Ordner-Toggle schluckt schnelle Klicks.** `ListsIslandViewModel.cs:626` `ToggleFolderAsync` ist ein async `[RelayCommand]` → `AsyncRelayCommand` setzt `CanExecute=false` für die Dauer des DB-Writes (`SetFolderCollapsedAsync`); jeder Klick in diesem Fenster wird verworfen. Fix: Toggle synchron (UI-State sofort), Persistierung fire-and-forget. Gleiches Muster in der Sidebar auf andere async RelayCommands gegenprüfen. | mittel |
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.418",
|
"version": "8.0.418",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestMajor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public partial class App : Application
|
|||||||
Dispatcher.UIThread.UnhandledException += (_, e) =>
|
Dispatcher.UIThread.UnhandledException += (_, e) =>
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
Serilog.Log.Error(e.Exception, "Unhandled exception on the UI dispatcher");
|
||||||
shell.FlashFooterError(Loc.T("vm.shell.unexpectedError", e.Exception.Message));
|
shell.FlashFooterError(Loc.T("vm.shell.unexpectedError", e.Exception.Message));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using ClaudeDo.Ui.ViewModels.Modals;
|
|||||||
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -20,6 +21,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ClaudeDo.App;
|
namespace ClaudeDo.App;
|
||||||
|
|
||||||
@@ -34,6 +36,35 @@ sealed class Program
|
|||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
SetCurrentProcessExplicitAppUserModelID("ClaudeDo.App");
|
SetCurrentProcessExplicitAppUserModelID("ClaudeDo.App");
|
||||||
|
|
||||||
|
var logRoot = Path.Combine(Paths.AppDataRoot(), "logs");
|
||||||
|
Directory.CreateDirectory(logRoot);
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
.WriteTo.File(
|
||||||
|
Path.Combine(logRoot, "ui-.log"),
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
retainedFileCountLimit: 7,
|
||||||
|
shared: true)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
var version = Assembly.GetEntryAssembly()?
|
||||||
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
|
||||||
|
?? "0.0.0";
|
||||||
|
Log.Information("ClaudeDo.App starting (version: {version})", version);
|
||||||
|
|
||||||
|
// Last-resort net: any exception that misses every other handler still gets one
|
||||||
|
// Fatal line in the log before the process dies.
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
||||||
|
{
|
||||||
|
Log.Fatal(e.ExceptionObject as Exception, "Unhandled exception, terminating (is_terminating: {is_terminating})", e.IsTerminating);
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
};
|
||||||
|
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||||
|
{
|
||||||
|
Log.Error(e.Exception, "Unobserved task exception");
|
||||||
|
e.SetObserved();
|
||||||
|
};
|
||||||
|
|
||||||
var services = BuildServices();
|
var services = BuildServices();
|
||||||
|
|
||||||
using (var scope = services.CreateScope())
|
using (var scope = services.CreateScope())
|
||||||
@@ -42,6 +73,8 @@ sealed class Program
|
|||||||
ClaudeDoDbContext.MigrateAndConfigure(db);
|
ClaudeDoDbContext.MigrateAndConfigure(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClipboardImageTempStore.DeleteOlderThan(TimeSpan.FromHours(24), DateTime.UtcNow);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ConfigureAppBuilder(AppBuilder.Configure(() => new App(services)))
|
ConfigureAppBuilder(AppBuilder.Configure(() => new App(services)))
|
||||||
@@ -60,6 +93,8 @@ sealed class Program
|
|||||||
{
|
{
|
||||||
/* best effort on shutdown */
|
/* best effort on shutdown */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.CloseAndFlush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +200,13 @@ sealed class Program
|
|||||||
sc.AddSingleton<TasksIslandViewModel>(sp =>
|
sc.AddSingleton<TasksIslandViewModel>(sp =>
|
||||||
new TasksIslandViewModel(
|
new TasksIslandViewModel(
|
||||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||||
sp.GetRequiredService<IWorkerClient>()));
|
sp.GetRequiredService<IWorkerClient>(),
|
||||||
|
sp.GetRequiredService<INotesApi>()));
|
||||||
sc.AddSingleton<DetailsIslandViewModel>(sp =>
|
sc.AddSingleton<DetailsIslandViewModel>(sp =>
|
||||||
new DetailsIslandViewModel(
|
new DetailsIslandViewModel(
|
||||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||||
sp.GetRequiredService<IWorkerClient>(),
|
sp.GetRequiredService<IWorkerClient>(),
|
||||||
sp,
|
sp,
|
||||||
sp.GetRequiredService<INotesApi>(),
|
|
||||||
sp.GetRequiredService<MergeCoordinator>()));
|
sp.GetRequiredService<MergeCoordinator>()));
|
||||||
sc.AddSingleton<UsagePillViewModel>(sp =>
|
sc.AddSingleton<UsagePillViewModel>(sp =>
|
||||||
new UsagePillViewModel(sp.GetRequiredService<IWorkerClient>()));
|
new UsagePillViewModel(sp.GetRequiredService<IWorkerClient>()));
|
||||||
@@ -179,7 +214,8 @@ sealed class Program
|
|||||||
new MissionControlViewModel(
|
new MissionControlViewModel(
|
||||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||||
sp.GetRequiredService<IWorkerClient>(),
|
sp.GetRequiredService<IWorkerClient>(),
|
||||||
sp.GetRequiredService<UsagePillViewModel>()));
|
sp.GetRequiredService<UsagePillViewModel>(),
|
||||||
|
sp.GetRequiredService<AppSettings>()));
|
||||||
sc.AddSingleton<IslandsShellViewModel>(sp =>
|
sc.AddSingleton<IslandsShellViewModel>(sp =>
|
||||||
{
|
{
|
||||||
var shell = ActivatorUtilities.CreateInstance<IslandsShellViewModel>(sp);
|
var shell = ActivatorUtilities.CreateInstance<IslandsShellViewModel>(sp);
|
||||||
|
|||||||
+37
-10
@@ -4,14 +4,17 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio
|
|||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
- **TaskEntity** — Id, ListId, Title, Description, Status, PlanningPhase, BlockedByTaskId (FK to predecessor in a chain), DependsOnTaskId (FK to a user/MCP-declared predecessor, distinct from BlockedByTaskId), ScheduledFor, Result, ReviewFeedback, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns / PermissionMode (nullable overrides), IsStarred, IsMyDay, IsManual, Notes, ParentTaskId, PlanningSessionId / PlanningSessionToken / PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit, InteractiveSessionId, TicketRef, Number (global immutable human-readable alias).
|
- **TaskEntity** — Id, ListId, Title, Description, Status, PlanningPhase, BlockedByTaskId (FK to predecessor in a chain), Dependencies (n user/MCP-declared predecessors via `task_dependencies`, distinct from BlockedByTaskId), ScheduledFor, Result, ReviewFeedback, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath / MaxTurns / PermissionMode (nullable overrides), IsStarred, IsMyDay, IsManual, Notes, ParentTaskId, PlanningSessionId / PlanningSessionToken / PlanningFinalizedAt, CreatedBy, HandlerBaseCommit / HandlerHeadCommit, InteractiveSessionId, TicketRef, Number (global immutable human-readable alias).
|
||||||
- `TicketRef` (`ticket_ref`, nullable string) — link to an external ticket, format `<provider>:<id>` (currently always `bandel:<ticketId>`). Set by the ticket import, otherwise null. → `ClaudeDo.Worker/CLAUDE.md` Tickets section.
|
- `TicketRef` (`ticket_ref`, nullable string) — link to an external ticket, format `<provider>:<id>` (currently always `bandel:<ticketId>`). Set by the ticket import, otherwise null. → `ClaudeDo.Worker/CLAUDE.md` Tickets section.
|
||||||
- Status / PlanningPhase / BlockedByTaskId / DependsOnTaskId semantics + allowed transitions: `ClaudeDo.Worker/CLAUDE.md` → Status Model.
|
- Status / PlanningPhase / BlockedByTaskId / dependency-set semantics + allowed transitions: `ClaudeDo.Worker/CLAUDE.md` → Status Model.
|
||||||
|
- `Dependencies` (`ICollection<TaskDependencyEntity>`) + the `[NotMapped] DependsOnTaskIds` id projection. **Only populated where the query `Include`s it** — `TaskRepository.GetByIdAsync`/`GetByNumberAsync`/`GetByListIdAsync` do, and so do the `TasksIslandViewModel` row-hydration queries. The queue gate itself is raw SQL in `QueuePicker` and never reads the nav, so a forgotten Include misreports the UI/MCP `blocked` flag but can never let a blocked task run. ⚠️ `TaskRepository.UpdateAsync` marks **only the root entity** Modified (`Entry().State`, not `Update()`) — `Update()` marks the loaded graph, and an all-key `task_dependencies` row has nothing to SET.
|
||||||
- `HandlerBaseCommit`/`HandlerHeadCommit` = the review range for a **worktree-less "list handler" host task** ("Let Claude handle it"), which commits straight into the list's working dir instead of a per-task worktree. Everything that reads a task's diff falls back to this pair whenever `Worktree` is null → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
- `HandlerBaseCommit`/`HandlerHeadCommit` = the review range for a **worktree-less "list handler" host task** ("Let Claude handle it"), which commits straight into the list's working dir instead of a per-task worktree. Everything that reads a task's diff falls back to this pair whenever `Worktree` is null → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
||||||
- `InteractiveSessionId` = the claude session id an embedded ConPTY interactive task session runs under, persisted by `InteractiveLaunchSpecService` before launch so a closed/aborted session can be resumed → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
- `InteractiveSessionId` = the claude session id an embedded ConPTY interactive task session runs under, persisted by `InteractiveLaunchSpecService` before launch so a closed/aborted session can be resumed → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
||||||
- `Number` (INTEGER NOT NULL, unique index `idx_tasks_number`) — a global, monotonically increasing display alias for tasks. **Never reused**: deleting a task leaves a gap in numbering, ensuring that a task number that appears in a log or report always points to the same task (if it exists). Allocated by `TaskNumberAllocator.AddWithNumberAsync` on every insert via a persistent counter (`app_settings.next_task_number`). **Why not `MAX(number) + 1`:** deleting the highest-numbered task would free its number for reuse, breaking the immutability guarantee. The allocator uses an `UPDATE…RETURNING` statement to claim numbers atomically, retrying up to 5 times if a uniqueness collision occurs (the counter advances on each attempt, so retries always allocate fresh numbers). The one-time backfill for pre-existing rows lived in the `AddTaskNumbers` migration, squashed away 2026-08-26.
|
- `Number` (INTEGER NOT NULL, unique index `idx_tasks_number`) — a global, monotonically increasing display alias for tasks. **Never reused**: deleting a task leaves a gap in numbering, ensuring that a task number that appears in a log or report always points to the same task (if it exists). Allocated by `TaskNumberAllocator.AddWithNumberAsync` on every insert via a persistent counter (`app_settings.next_task_number`). **Why not `MAX(number) + 1`:** deleting the highest-numbered task would free its number for reuse, breaking the immutability guarantee. The allocator uses an `UPDATE…RETURNING` statement to claim numbers atomically, retrying up to 5 times if a uniqueness collision occurs (the counter advances on each attempt, so retries always allocate fresh numbers). The one-time backfill for pre-existing rows lived in the `AddTaskNumbers` migration, squashed away 2026-08-26.
|
||||||
- Legacy status values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired. The backfill migration was squashed away (2026-08-26) — a database still holding those values can't be upgraded, see Schema below.
|
- Legacy status values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired. The backfill migration was squashed away (2026-08-26) — a database still holding those values can't be upgraded, see Schema below.
|
||||||
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to `IsManual`)
|
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to `IsManual`), Icon, FolderId
|
||||||
|
- `FolderId` (`folder_id`, nullable FK → `list_folders.id`, `ON DELETE SET NULL`) — the sidebar folder the list sits in; null = top level. Written only by the UI (`ListsIslandViewModel` drag/context menu), never by MCP.
|
||||||
|
- **ListFolderEntity** — Id, Name, Icon, SortOrder, IsCollapsed, ParentFolderId, CreatedAt → `list_folders`. A pure sidebar grouping: no tasks, no config, no working dir. `ParentFolderId` (self-FK, `ON DELETE SET NULL`) lets folders nest arbitrarily deep — drag-and-drop only, no UI to pick a parent otherwise. Deliberately its own table rather than a flag on `lists`, so every caller that enumerates lists (MCP `list_task_lists`, "move to list", the queue picker, repo import) needs no folder filtering. Folder CRUD lives on `ListRepository`.
|
||||||
- **ListConfigEntity** — ListId (PK, 1:1), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand, PermissionMode, TicketProjectId (all nullable). `VerifyCommand` is an optional post-merge gate; null/blank = no gate → [review-merge](../../docs/explore-notes/review-merge.md). `PermissionMode` is null = inherit the global default; resolution order task → list → global lives in `EffectiveRunConfigResolver`. `TicketProjectId` (`ticket_project_id`) links the list to a project in the external ticket system; null = not linked → `ClaudeDo.Worker/CLAUDE.md` Tickets section.
|
- **ListConfigEntity** — ListId (PK, 1:1), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand, PermissionMode, TicketProjectId (all nullable). `VerifyCommand` is an optional post-merge gate; null/blank = no gate → [review-merge](../../docs/explore-notes/review-merge.md). `PermissionMode` is null = inherit the global default; resolution order task → list → global lives in `EffectiveRunConfigResolver`. `TicketProjectId` (`ticket_project_id`) links the list to a project in the external ticket system; null = not linked → `ClaudeDo.Worker/CLAUDE.md` Tickets section.
|
||||||
- ⚠️ `ListRepository.SetConfigAsync` copies the entity **verbatim**, so every writer must carry the fields it doesn't own (`SessionSkills`, `PermissionMode`, `TicketProjectId`) or they silently reset. `UpdateListConfigDto.TicketProjectId` is tri-state (`null` = keep stored) for exactly that reason — only the list-settings modal sends an explicit value. This bit `PermissionMode` for real: its update-branch copy was missing from `SetConfigAsync` until `f0a3a186`, so a list's permission-mode override only ever stuck on the list's *first* save, never on a later one.
|
- ⚠️ `ListRepository.SetConfigAsync` copies the entity **verbatim**, so every writer must carry the fields it doesn't own (`SessionSkills`, `PermissionMode`, `TicketProjectId`) or they silently reset. `UpdateListConfigDto.TicketProjectId` is tri-state (`null` = keep stored) for exactly that reason — only the list-settings modal sends an explicit value. This bit `PermissionMode` for real: its update-branch copy was missing from `SetConfigAsync` until `f0a3a186`, so a list's permission-mode override only ever stuck on the list's *first* save, never on a later one.
|
||||||
- **WorktreeEntity** — TaskId (PK, 1:1), Path, BranchName, BaseCommit, HeadCommit, DiffStat, MergeCommit (nullable — SHA of the merge commit this branch produced; the only thing making `revert_merge` possible without searching `git log`), State (`Active|Merged|Discarded|Kept`)
|
- **WorktreeEntity** — TaskId (PK, 1:1), Path, BranchName, BaseCommit, HeadCommit, DiffStat, MergeCommit (nullable — SHA of the merge commit this branch produced; the only thing making `revert_merge` possible without searching `git log`), State (`Active|Merged|Discarded|Kept`)
|
||||||
@@ -20,6 +23,8 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio
|
|||||||
- **DailyNoteEntity** — Id, Date (DateOnly), Text, SortOrder, CreatedAt → `daily_notes`
|
- **DailyNoteEntity** — Id, Date (DateOnly), Text, SortOrder, CreatedAt → `daily_notes`
|
||||||
- **WeekReportEntity** — Id, StartDate/EndDate (DateOnly), Markdown, GeneratedAt → `week_reports`, unique index on (start_date, end_date)
|
- **WeekReportEntity** — Id, StartDate/EndDate (DateOnly), Markdown, GeneratedAt → `week_reports`, unique index on (start_date, end_date)
|
||||||
- **TaskAttachmentEntity** — Id, TaskId (FK, ON DELETE CASCADE), FileName, ByteSize, CreatedAt → `task_attachments`
|
- **TaskAttachmentEntity** — Id, TaskId (FK, ON DELETE CASCADE), FileName, ByteSize, CreatedAt → `task_attachments`
|
||||||
|
- **TaskDependencyEntity** — one declared "run after" edge: TaskId + DependsOnTaskId, composite PK, table `task_dependencies`. Both FKs are `ON DELETE CASCADE`, which is what the old single `depends_on_task_id` column got from `ON DELETE SET NULL`: deleting a predecessor drops the edge so the dependent becomes pickable instead of blocked on a task that's gone. Written **only** by `TaskStateService.SetDependsOnAsync`.
|
||||||
|
- **MissionControlSessionEntity** — SessionId (PK), Cwd, TaskId (no FK), Title, StartedAt, PredecessorSessionId, BriefPath → `mission_control_sessions`. Index behind Mission Control's "Recent sessions" flyout; written only by `InteractiveLaunchSpecService` (task/ad-hoc/handoff tile launches), insert-if-absent → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
||||||
- **SubtaskEntity**, **AgentInfo** — subtasks / record for scanned agent files
|
- **SubtaskEntity**, **AgentInfo** — subtasks / record for scanned agent files
|
||||||
|
|
||||||
### AppSettingsEntity
|
### AppSettingsEntity
|
||||||
@@ -37,10 +42,20 @@ Beyond the basics it carries:
|
|||||||
| `ReportExcludedPaths` | `report_excluded_paths` | null | JSON array of excluded path prefixes. |
|
| `ReportExcludedPaths` | `report_excluded_paths` | null | JSON array of excluded path prefixes. |
|
||||||
| `StandupWeekday` | `standup_weekday` | Wednesday | int `DayOfWeek`. |
|
| `StandupWeekday` | `standup_weekday` | Wednesday | int `DayOfWeek`. |
|
||||||
| `NextTaskNumber` | `next_task_number` | 1 | Persistent counter for `TaskEntity.Number` allocation. Incremented atomically in `TaskNumberAllocator.AddWithNumberAsync` on every task insert. **Monotonic only:** the counter itself advances across all tasks (no per-list numbering), and it never resets. A deleted task leaves a gap. |
|
| `NextTaskNumber` | `next_task_number` | 1 | Persistent counter for `TaskEntity.Number` allocation. Incremented atomically in `TaskNumberAllocator.AddWithNumberAsync` on every task insert. **Monotonic only:** the counter itself advances across all tasks (no per-list numbering), and it never resets. A deleted task leaves a gap. |
|
||||||
|
| `PowerUseEnabled` | `power_use_enabled` | false | PowerUse master switch, toggled in **Settings → Execution**. ⚠️ Never sufficient alone — a handler run is in PowerUse only when `list_config.power_use_allowed` is also set. |
|
||||||
|
| `ListHandlerMaxHandoffs` | `list_handler_max_handoffs` | 4 | Cap on a list-handler run's wait↔merge handoff chain (`HandoffRoundTracker`). 4 = two merge rounds. Independent of PowerUse — applies to interactive runs too. Clamped to min 1 in `WorkerHub.UpdateAppSettings` and again in `Register`, because 0 would remove the only bound on a runaway chain. |
|
||||||
|
| `PowerUseWindowStart` / `PowerUseWindowEnd` | `power_use_window_*` | null | Optional local-time window (`HH:mm`) narrowing when PowerUse applies. Both null (or only one set) = round the clock, i.e. byte-identical to the pre-window behaviour; end before start wraps over midnight. Semantics live in the pure `PowerUseWindow`; checked in `InteractiveLaunchSpecService.ComposeHandlerPromptAsync` **per phase** at prompt-build time, so a chain handing off past the end time drops the addendum from there on. Unparseable input is stored as null, not rejected. |
|
||||||
|
| `PowerUseHandoffPct` | `power_use_handoff_pct` | 95 | 5h-usage percentage at which a PowerUse handler session should hand off. Clamped 50..99. Typed in Settings → Execution; the consumer is the handoff prompt. |
|
||||||
|
|
||||||
All four usage percentages are clamped 0..100 by `AppSettingsRepository.UpdateAsync`.
|
All four usage percentages are clamped 0..100 by `AppSettingsRepository.UpdateAsync`.
|
||||||
Gate/throttle semantics → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md).
|
Gate/throttle semantics → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md).
|
||||||
|
|
||||||
|
⚠️ **`AppSettingsRepository.UpdateAsync` copies the incoming entity field by field** onto the
|
||||||
|
singleton row — a property missing from that block never persists, however correctly the caller
|
||||||
|
filled the entity, and nothing fails. `ModelPresets`, `PowerUseEnabled` and
|
||||||
|
`ListHandlerMaxHandoffs` were all silently unwritable that way until 2026-09-02. Add a new column
|
||||||
|
to that block, not just to the entity + the hub.
|
||||||
|
|
||||||
### Model / effort registries
|
### Model / effort registries
|
||||||
|
|
||||||
- **ModelPresets / ModelPreset** — per-model run defaults (`Model`, `Effort`, `MaxTurns`), one row per `ModelRegistry.Aliases` entry, supplying the **global** effort and max-turns defaults. Ship defaults: haiku medium/20, sonnet high/30, opus high/40, fable high/25. `Parse`/`Serialize` normalize (unknown models dropped, missing aliases filled from `Defaults`, effort validated, turns clamped 1–200) and **never throw** — a malformed settings row must not stop a run. `For(presets, model, fallbackMaxTurns)` always returns a usable row; resolution order and the fallback trap → [worker-task-pipeline](../../docs/explore-notes/worker-task-pipeline.md).
|
- **ModelPresets / ModelPreset** — per-model run defaults (`Model`, `Effort`, `MaxTurns`), one row per `ModelRegistry.Aliases` entry, supplying the **global** effort and max-turns defaults. Ship defaults: haiku medium/20, sonnet high/30, opus high/40, fable high/25. `Parse`/`Serialize` normalize (unknown models dropped, missing aliases filled from `Defaults`, effort validated, turns clamped 1–200) and **never throw** — a malformed settings row must not stop a run. `For(presets, model, fallbackMaxTurns)` always returns a usable row; resolution order and the fallback trap → [worker-task-pipeline](../../docs/explore-notes/worker-task-pipeline.md).
|
||||||
@@ -53,7 +68,8 @@ All use EF Core LINQ via `ClaudeDoDbContext`. The atomic `Queued → Running` cl
|
|||||||
Worker's `QueuePicker` (`FromSqlRaw`), **not** here.
|
Worker's `QueuePicker` (`FromSqlRaw`), **not** here.
|
||||||
|
|
||||||
- **TaskRepository** — CRUD, planning helpers (`CreateChildAsync`, `SetPlanningStartedAsync`, `DiscardPlanningAsync`, `UpdateChildAsync`), `UpdateAgentSettingsAsync`. ⚠️ Status-mutation primitives (`MarkRunningAsync`/`MarkDoneAsync`/`MarkFailedAsync`/`FlipAllRunningToFailedAsync`) are **`internal`** — only the Worker's `TaskStateService` may call them. `CreateChildAsync` produces children with `Status=Idle, PlanningPhase=None`. **Task-number allocation:** `AddAsync` (line 20) and `CreateChildAsync` (line 276) are the two sole task-creation paths; both call `TaskNumberAllocator.AddWithNumberAsync` to allocate a number atomically during the same transaction as the insert. Every other creation path (UI, MCP `add_task`, `batch_add_tasks`, Online Inbox sync, merge-helper handler tasks, planning children) routes through one of these two.
|
- **TaskRepository** — CRUD, planning helpers (`CreateChildAsync`, `SetPlanningStartedAsync`, `DiscardPlanningAsync`, `UpdateChildAsync`), `UpdateAgentSettingsAsync`. ⚠️ Status-mutation primitives (`MarkRunningAsync`/`MarkDoneAsync`/`MarkFailedAsync`/`FlipAllRunningToFailedAsync`) are **`internal`** — only the Worker's `TaskStateService` may call them. `CreateChildAsync` produces children with `Status=Idle, PlanningPhase=None`. **Task-number allocation:** `AddAsync` (line 20) and `CreateChildAsync` (line 276) are the two sole task-creation paths; both call `TaskNumberAllocator.AddWithNumberAsync` to allocate a number atomically during the same transaction as the insert. Every other creation path (UI, MCP `add_task`, `batch_add_tasks`, Online Inbox sync, merge-helper handler tasks, planning children) routes through one of these two.
|
||||||
- **ListRepository** — CRUD, `GetConfigAsync`/`SetConfigAsync` (upsert)/`DeleteConfigAsync` for `list_config`
|
- **ListRepository** — CRUD, `GetConfigAsync`/`SetConfigAsync` (upsert)/`DeleteConfigAsync` for `list_config`, plus the `list_folders` set: `GetFoldersAsync`, `AddFolderAsync` (appends), `RenameFolderAsync`, `DeleteFolderAsync` (folder only — the FK releases its lists), `SetFolderCollapsedAsync`, `SetListFolderAsync`. The folder writers are `ExecuteUpdate` patches, not entity `Update()`s, so a rename can't reset `SortOrder`/`IsCollapsed` the way `SetConfigAsync`'s verbatim copy resets forgotten columns.
|
||||||
|
- ⚠️ **`lists.sort_order` and `list_folders.sort_order` share one number space** — the sidebar interleaves folders and top-level lists in a single order, so `ReorderSidebarAsync` numbers the whole visual sequence (each folder followed by its children) across both tables, and `AddAsync`/`AddFolderAsync` append via `MAX(sort_order)+1` over **both**. Never renumber one table on its own.
|
||||||
- **WorktreeRepository** — CRUD, `UpdateHeadAsync`, `SetStateAsync`, `SetMergedAsync` (atomically sets State=Merged **and** stamps MergeCommit in one update — the only writer of MergeCommit)
|
- **WorktreeRepository** — CRUD, `UpdateHeadAsync`, `SetStateAsync`, `SetMergedAsync` (atomically sets State=Merged **and** stamps MergeCommit in one update — the only writer of MergeCommit)
|
||||||
- **TaskAttachmentRepository** — `AddAsync`, `UpdateAsync`, `GetAsync(taskId, fileName)`, `ListByTaskIdAsync`, `DeleteAsync(taskId, fileName)`. Bulk delete-per-task is the `ON DELETE CASCADE` on `task_attachments`, not a repository method.
|
- **TaskAttachmentRepository** — `AddAsync`, `UpdateAsync`, `GetAsync(taskId, fileName)`, `ListByTaskIdAsync`, `DeleteAsync(taskId, fileName)`. Bulk delete-per-task is the `ON DELETE CASCADE` on `task_attachments`, not a repository method.
|
||||||
- **DailyNoteRepository**, **WeekReportRepository**, **TaskRunRepository**, **SubtaskRepository**, **AppSettingsRepository**
|
- **DailyNoteRepository**, **WeekReportRepository**, **TaskRunRepository**, **SubtaskRepository**, **AppSettingsRepository**
|
||||||
@@ -80,10 +96,15 @@ dir(s) via an optional `AttachmentStore` ctor param (defaults to the production
|
|||||||
- Merge: ff-only, no-ff, abort, mid-merge detection (`MERGE_HEAD`), conflicted files
|
- Merge: ff-only, no-ff, abort, mid-merge detection (`MERGE_HEAD`), conflicted files
|
||||||
- Revert: `RevertMergeCommitAsync` (`git revert --no-edit -m 1 <sha>`), `RevertAbortAsync`, `IsMidRevertAsync` (`REVERT_HEAD`, mirrors `IsMidMergeAsync`)
|
- Revert: `RevertMergeCommitAsync` (`git revert --no-edit -m 1 <sha>`), `RevertAbortAsync`, `IsMidRevertAsync` (`REVERT_HEAD`, mirrors `IsMidMergeAsync`)
|
||||||
- `PreviewMergeAsync` (non-destructive check via `git merge-tree --write-tree`), `CountChangedFilesAsync`, rev-parse, is-git-repo
|
- `PreviewMergeAsync` (non-destructive check via `git merge-tree --write-tree`), `CountChangedFilesAsync`, rev-parse, is-git-repo
|
||||||
|
- Empty repos: `HasHeadCommitAsync` (HEAD resolves to a real commit) and `EnsureHeadCommitAsync` — a fresh `git init` repo (unborn HEAD) gets an empty bootstrap commit via plumbing only (mktree/commit-tree/update-ref), so the user's index and working tree stay untouched; identity is passed inline, so it also works with no git config, and a broken HEAD throws instead of guessing
|
||||||
|
- Identity fallback: `CommitAsync` retries once as the ClaudeDo identity when the machine has none configured
|
||||||
|
|
||||||
⚠️ **Revert never resets or rewrites** — it always produces a new commit, because the working
|
⚠️ **Revert never resets or rewrites** — it always produces a new commit, because the working
|
||||||
directory it operates on is shared with other concurrent sessions.
|
directory it operates on is shared with other concurrent sessions.
|
||||||
|
|
||||||
|
⚠️ **Git stdin is BOM-less UTF-8** — `Encoding.UTF8` writes a BOM preamble, which git rejects
|
||||||
|
as input (`mktree` fails with "input format error", and commit messages would carry it).
|
||||||
|
|
||||||
## Schema
|
## Schema
|
||||||
|
|
||||||
Tables (one per line so parallel migrations don't collide on the same line):
|
Tables (one per line so parallel migrations don't collide on the same line):
|
||||||
@@ -98,6 +119,9 @@ Tables (one per line so parallel migrations don't collide on the same line):
|
|||||||
- `daily_notes`
|
- `daily_notes`
|
||||||
- `week_reports`
|
- `week_reports`
|
||||||
- `task_attachments`
|
- `task_attachments`
|
||||||
|
- `task_dependencies`
|
||||||
|
- `list_folders`
|
||||||
|
- `mission_control_sessions`
|
||||||
|
|
||||||
Managed by EF Core migrations in `Migrations/`. **The 40 incremental migrations were squashed into
|
Managed by EF Core migrations in `Migrations/`. **The 40 incremental migrations were squashed into
|
||||||
a single `20260826094154_InitialCreate` on 2026-08-26** — the schema history is git history now,
|
a single `20260826094154_InitialCreate` on 2026-08-26** — the schema history is git history now,
|
||||||
@@ -107,8 +131,9 @@ not a migration chain. Consequences:
|
|||||||
(pre-EF databases without a history, and databases whose history still lists the old chain ending
|
(pre-EF databases without a history, and databases whose history still lists the old chain ending
|
||||||
at `20260825063230_AddPrimeActionKind`). A database that stopped *mid*-chain can no longer be
|
at `20260825063230_AddPrimeActionKind`). A database that stopped *mid*-chain can no longer be
|
||||||
upgraded and throws with a reinstall message instead of being stamped onto a schema it lacks.
|
upgraded and throws with a reinstall message instead of being stamped onto a schema it lacks.
|
||||||
Covered by `MigrationBaselineTests` — the only test that runs a real `Migrate()` (every other
|
Covered by `MigrationBaselineTests` — one of only two tests that run a real `Migrate()`
|
||||||
Data.Tests fixture uses `EnsureCreated`, which skips migrations entirely). Its fixture builds the
|
(`TaskDependencyMigrationTests` is the other; every other Data.Tests fixture uses
|
||||||
|
`EnsureCreated`, which skips migrations entirely). Its fixture builds the
|
||||||
legacy database by migrating **to** `InitialCreate` and seeding rows with raw SQL: `EnsureCreated`
|
legacy database by migrating **to** `InitialCreate` and seeding rows with raw SQL: `EnsureCreated`
|
||||||
would build *today's* schema, so every post-squash migration would then re-add an existing column
|
would build *today's* schema, so every post-squash migration would then re-add an existing column
|
||||||
("duplicate column name"). Assert "nothing pending", never "InitialCreate is the only history row".
|
("duplicate column name"). Assert "nothing pending", never "InitialCreate is the only history row".
|
||||||
@@ -117,10 +142,12 @@ not a migration chain. Consequences:
|
|||||||
Release` — `Data` is its own startup project (it holds the `Design` package and a design-time
|
Release` — `Data` is its own startup project (it holds the `Design` package and a design-time
|
||||||
factory), and `--configuration Release` is needed because a running Worker locks `Debug`.
|
factory), and `--configuration Release` is needed because a running Worker locks `Debug`.
|
||||||
|
|
||||||
`tasks` holds `status`, `planning_phase` (default `none`),
|
`tasks` holds `status`, `planning_phase` (default `none`) and `blocked_by_task_id` (FK to
|
||||||
`blocked_by_task_id` (FK to `tasks.id`, `ON DELETE SET NULL`), and `depends_on_task_id` (same FK
|
`tasks.id`, `ON DELETE SET NULL`). The user/MCP-declared predecessors live in `task_dependencies`
|
||||||
shape, but a separate column — see Worker/CLAUDE.md → Status Model for why it isn't unified with
|
instead — a **set**, so a task can wait on n of them; see Worker/CLAUDE.md → Status Model for why
|
||||||
`blocked_by_task_id`).
|
it isn't unified with `blocked_by_task_id`. `AddTaskDependencies` (2026-09-02) migrated the former
|
||||||
|
single `tasks.depends_on_task_id` column into that table (each value became a one-element set) and
|
||||||
|
dropped it; the `Down` direction is inherently lossy for more than one edge.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ public class ClaudeDoDbContext : DbContext
|
|||||||
=> optionsBuilder.AddInterceptors(SqliteForeignKeyInterceptor.Instance);
|
=> optionsBuilder.AddInterceptors(SqliteForeignKeyInterceptor.Instance);
|
||||||
|
|
||||||
public DbSet<TaskEntity> Tasks => Set<TaskEntity>();
|
public DbSet<TaskEntity> Tasks => Set<TaskEntity>();
|
||||||
|
public DbSet<TaskDependencyEntity> TaskDependencies => Set<TaskDependencyEntity>();
|
||||||
public DbSet<ListEntity> Lists => Set<ListEntity>();
|
public DbSet<ListEntity> Lists => Set<ListEntity>();
|
||||||
|
public DbSet<ListFolderEntity> ListFolders => Set<ListFolderEntity>();
|
||||||
public DbSet<ListConfigEntity> ListConfigs => Set<ListConfigEntity>();
|
public DbSet<ListConfigEntity> ListConfigs => Set<ListConfigEntity>();
|
||||||
public DbSet<WorktreeEntity> Worktrees => Set<WorktreeEntity>();
|
public DbSet<WorktreeEntity> Worktrees => Set<WorktreeEntity>();
|
||||||
public DbSet<TaskRunEntity> TaskRuns => Set<TaskRunEntity>();
|
public DbSet<TaskRunEntity> TaskRuns => Set<TaskRunEntity>();
|
||||||
@@ -52,6 +54,7 @@ public class ClaudeDoDbContext : DbContext
|
|||||||
public DbSet<DailyNoteEntity> DailyNotes => Set<DailyNoteEntity>();
|
public DbSet<DailyNoteEntity> DailyNotes => Set<DailyNoteEntity>();
|
||||||
public DbSet<WeekReportEntity> WeekReports => Set<WeekReportEntity>();
|
public DbSet<WeekReportEntity> WeekReports => Set<WeekReportEntity>();
|
||||||
public DbSet<SessionSkillEntity> SessionSkills => Set<SessionSkillEntity>();
|
public DbSet<SessionSkillEntity> SessionSkills => Set<SessionSkillEntity>();
|
||||||
|
public DbSet<MissionControlSessionEntity> MissionControlSessions => Set<MissionControlSessionEntity>();
|
||||||
|
|
||||||
private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
|
private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
|
||||||
new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
||||||
|
|||||||
@@ -70,6 +70,17 @@ public class AppSettingsEntityConfiguration : IEntityTypeConfiguration<AppSettin
|
|||||||
builder.Property(s => s.AutoContinueOnUsageLimit)
|
builder.Property(s => s.AutoContinueOnUsageLimit)
|
||||||
.HasColumnName("auto_continue_on_usage_limit").IsRequired().HasDefaultValue(false);
|
.HasColumnName("auto_continue_on_usage_limit").IsRequired().HasDefaultValue(false);
|
||||||
|
|
||||||
|
builder.Property(s => s.PowerUseEnabled)
|
||||||
|
.HasColumnName("power_use_enabled").IsRequired().HasDefaultValue(false);
|
||||||
|
builder.Property(s => s.PowerUseWindowStart)
|
||||||
|
.HasColumnName("power_use_window_start");
|
||||||
|
builder.Property(s => s.PowerUseWindowEnd)
|
||||||
|
.HasColumnName("power_use_window_end");
|
||||||
|
builder.Property(s => s.PowerUseHandoffPct)
|
||||||
|
.HasColumnName("power_use_handoff_pct").IsRequired().HasDefaultValue(95);
|
||||||
|
builder.Property(s => s.ListHandlerMaxHandoffs)
|
||||||
|
.HasColumnName("list_handler_max_handoffs").IsRequired().HasDefaultValue(4);
|
||||||
|
|
||||||
builder.HasData(new AppSettingsEntity { Id = AppSettingsEntity.SingletonId });
|
builder.HasData(new AppSettingsEntity { Id = AppSettingsEntity.SingletonId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class DailyNoteEntityConfiguration : IEntityTypeConfiguration<DailyNoteEn
|
|||||||
builder.Property(n => n.Date).HasColumnName("note_date").IsRequired();
|
builder.Property(n => n.Date).HasColumnName("note_date").IsRequired();
|
||||||
builder.Property(n => n.Text).HasColumnName("text").IsRequired();
|
builder.Property(n => n.Text).HasColumnName("text").IsRequired();
|
||||||
builder.Property(n => n.SortOrder).HasColumnName("sort_order").IsRequired();
|
builder.Property(n => n.SortOrder).HasColumnName("sort_order").IsRequired();
|
||||||
|
builder.Property(n => n.FromAgent).HasColumnName("from_agent").IsRequired();
|
||||||
builder.Property(n => n.CreatedAt).HasColumnName("created_at").IsRequired();
|
builder.Property(n => n.CreatedAt).HasColumnName("created_at").IsRequired();
|
||||||
builder.HasIndex(n => n.Date);
|
builder.HasIndex(n => n.Date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ public class ListConfigEntityConfiguration : IEntityTypeConfiguration<ListConfig
|
|||||||
builder.Property(c => c.VerifyCommand).HasColumnName("verify_command");
|
builder.Property(c => c.VerifyCommand).HasColumnName("verify_command");
|
||||||
builder.Property(c => c.PermissionMode).HasColumnName("permission_mode");
|
builder.Property(c => c.PermissionMode).HasColumnName("permission_mode");
|
||||||
builder.Property(c => c.TicketProjectId).HasColumnName("ticket_project_id");
|
builder.Property(c => c.TicketProjectId).HasColumnName("ticket_project_id");
|
||||||
|
builder.Property(c => c.PowerUseAllowed).HasColumnName("power_use_allowed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,18 @@ public class ListEntityConfiguration : IEntityTypeConfiguration<ListEntity>
|
|||||||
builder.Property(l => l.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
builder.Property(l => l.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
||||||
builder.Property(l => l.IsManual).HasColumnName("is_manual").IsRequired().HasDefaultValue(false);
|
builder.Property(l => l.IsManual).HasColumnName("is_manual").IsRequired().HasDefaultValue(false);
|
||||||
builder.Property(l => l.FindingsTracked).HasColumnName("findings_tracked").IsRequired().HasDefaultValue(false);
|
builder.Property(l => l.FindingsTracked).HasColumnName("findings_tracked").IsRequired().HasDefaultValue(false);
|
||||||
|
builder.Property(l => l.Icon).HasColumnName("icon");
|
||||||
|
builder.Property(l => l.FolderId).HasColumnName("folder_id");
|
||||||
|
|
||||||
builder.HasIndex(l => l.SortOrder).HasDatabaseName("idx_lists_sort");
|
builder.HasIndex(l => l.SortOrder).HasDatabaseName("idx_lists_sort");
|
||||||
|
builder.HasIndex(l => l.FolderId).HasDatabaseName("idx_lists_folder");
|
||||||
|
|
||||||
|
// No navigation property on either side: nothing needs the graph, and a nav would have to
|
||||||
|
// be Include'd (or silently come back empty) in every list query.
|
||||||
|
builder.HasOne<ListFolderEntity>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(l => l.FolderId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
builder.HasOne(l => l.Config)
|
builder.HasOne(l => l.Config)
|
||||||
.WithOne(c => c.List)
|
.WithOne(c => c.List)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Configuration;
|
||||||
|
|
||||||
|
public class ListFolderEntityConfiguration : IEntityTypeConfiguration<ListFolderEntity>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ListFolderEntity> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("list_folders");
|
||||||
|
|
||||||
|
builder.HasKey(f => f.Id);
|
||||||
|
builder.Property(f => f.Id).HasColumnName("id");
|
||||||
|
builder.Property(f => f.Name).HasColumnName("name").IsRequired();
|
||||||
|
builder.Property(f => f.CreatedAt).HasColumnName("created_at").IsRequired();
|
||||||
|
builder.Property(f => f.Icon).HasColumnName("icon");
|
||||||
|
builder.Property(f => f.Color).HasColumnName("color");
|
||||||
|
builder.Property(f => f.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
||||||
|
builder.Property(f => f.IsCollapsed).HasColumnName("is_collapsed").IsRequired().HasDefaultValue(false);
|
||||||
|
builder.Property(f => f.ParentFolderId).HasColumnName("parent_folder_id");
|
||||||
|
|
||||||
|
builder.HasIndex(f => f.SortOrder).HasDatabaseName("idx_list_folders_sort");
|
||||||
|
builder.HasIndex(f => f.ParentFolderId).HasDatabaseName("idx_list_folders_parent");
|
||||||
|
|
||||||
|
builder.HasOne<ListFolderEntity>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(f => f.ParentFolderId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Configuration;
|
||||||
|
|
||||||
|
public class MissionControlSessionEntityConfiguration : IEntityTypeConfiguration<MissionControlSessionEntity>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<MissionControlSessionEntity> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("mission_control_sessions");
|
||||||
|
builder.HasKey(s => s.SessionId);
|
||||||
|
builder.Property(s => s.SessionId).HasColumnName("session_id").ValueGeneratedNever();
|
||||||
|
builder.Property(s => s.Cwd).HasColumnName("cwd").IsRequired();
|
||||||
|
builder.Property(s => s.TaskId).HasColumnName("task_id");
|
||||||
|
builder.Property(s => s.Title).HasColumnName("title").IsRequired();
|
||||||
|
builder.Property(s => s.StartedAt).HasColumnName("started_at").IsRequired();
|
||||||
|
builder.Property(s => s.PredecessorSessionId).HasColumnName("predecessor_session_id");
|
||||||
|
builder.Property(s => s.BriefPath).HasColumnName("brief_path");
|
||||||
|
builder.HasIndex(s => s.StartedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Configuration;
|
||||||
|
|
||||||
|
public class TaskDependencyEntityConfiguration : IEntityTypeConfiguration<TaskDependencyEntity>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<TaskDependencyEntity> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("task_dependencies");
|
||||||
|
|
||||||
|
builder.HasKey(d => new { d.TaskId, d.DependsOnTaskId });
|
||||||
|
builder.Property(d => d.TaskId).HasColumnName("task_id");
|
||||||
|
builder.Property(d => d.DependsOnTaskId).HasColumnName("depends_on_task_id");
|
||||||
|
|
||||||
|
// Cascade on both sides reproduces what the old single depends_on_task_id column got from
|
||||||
|
// ON DELETE SET NULL: deleting the predecessor drops the edge, so the dependent becomes
|
||||||
|
// pickable rather than blocked forever on a task that no longer exists.
|
||||||
|
builder.HasOne<TaskEntity>()
|
||||||
|
.WithMany(t => t.Dependencies)
|
||||||
|
.HasForeignKey(d => d.TaskId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasOne<TaskEntity>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(d => d.DependsOnTaskId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasIndex(d => d.DependsOnTaskId).HasDatabaseName("idx_task_dependencies_depends_on");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,7 +75,6 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
builder.Property(t => t.PlanningPhase).HasColumnName("planning_phase").IsRequired()
|
builder.Property(t => t.PlanningPhase).HasColumnName("planning_phase").IsRequired()
|
||||||
.HasConversion(PhaseConverter).HasDefaultValue(PlanningPhase.None);
|
.HasConversion(PhaseConverter).HasDefaultValue(PlanningPhase.None);
|
||||||
builder.Property(t => t.BlockedByTaskId).HasColumnName("blocked_by_task_id");
|
builder.Property(t => t.BlockedByTaskId).HasColumnName("blocked_by_task_id");
|
||||||
builder.Property(t => t.DependsOnTaskId).HasColumnName("depends_on_task_id");
|
|
||||||
builder.Property(t => t.ScheduledFor).HasColumnName("scheduled_for");
|
builder.Property(t => t.ScheduledFor).HasColumnName("scheduled_for");
|
||||||
builder.Property(t => t.Result).HasColumnName("result");
|
builder.Property(t => t.Result).HasColumnName("result");
|
||||||
builder.Property(t => t.ReviewFeedback).HasColumnName("review_feedback");
|
builder.Property(t => t.ReviewFeedback).HasColumnName("review_feedback");
|
||||||
@@ -97,6 +96,7 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
builder.Property(t => t.IsStarred).HasColumnName("is_starred").HasDefaultValue(false);
|
builder.Property(t => t.IsStarred).HasColumnName("is_starred").HasDefaultValue(false);
|
||||||
builder.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false);
|
builder.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false);
|
||||||
builder.Property(t => t.IsManual).HasColumnName("is_manual").HasDefaultValue(false);
|
builder.Property(t => t.IsManual).HasColumnName("is_manual").HasDefaultValue(false);
|
||||||
|
builder.Property(t => t.ExpectsNoDiff).HasColumnName("expects_no_diff").HasDefaultValue(false);
|
||||||
builder.Property(t => t.Notes).HasColumnName("notes");
|
builder.Property(t => t.Notes).HasColumnName("notes");
|
||||||
builder.Property(t => t.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
builder.Property(t => t.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
||||||
builder.Property(t => t.SessionSkills).HasColumnName("session_skills");
|
builder.Property(t => t.SessionSkills).HasColumnName("session_skills");
|
||||||
@@ -123,12 +123,8 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
.HasForeignKey(t => t.BlockedByTaskId)
|
.HasForeignKey(t => t.BlockedByTaskId)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
// DependsOn: user/MCP-declared predecessor. SetNull on delete so the dependent becomes
|
// DependsOn edges live in task_dependencies (a task can declare n predecessors) —
|
||||||
// pickable rather than blocked forever on a task that no longer exists.
|
// configured in TaskDependencyEntityConfiguration, which also owns the Dependencies nav.
|
||||||
builder.HasOne<TaskEntity>()
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(t => t.DependsOnTaskId)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
builder.HasOne(t => t.List)
|
builder.HasOne(t => t.List)
|
||||||
.WithMany(l => l.Tasks)
|
.WithMany(l => l.Tasks)
|
||||||
@@ -145,6 +141,5 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
builder.HasIndex(t => new { t.ListId, t.SortOrder }).HasDatabaseName("idx_tasks_list_sort");
|
builder.HasIndex(t => new { t.ListId, t.SortOrder }).HasDatabaseName("idx_tasks_list_sort");
|
||||||
builder.HasIndex(t => t.ParentTaskId).HasDatabaseName("idx_tasks_parent_task_id");
|
builder.HasIndex(t => t.ParentTaskId).HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
builder.HasIndex(t => t.BlockedByTaskId).HasDatabaseName("idx_tasks_blocked_by");
|
builder.HasIndex(t => t.BlockedByTaskId).HasDatabaseName("idx_tasks_blocked_by");
|
||||||
builder.HasIndex(t => t.DependsOnTaskId).HasDatabaseName("idx_tasks_depends_on");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,16 @@ public sealed class GitService
|
|||||||
{
|
{
|
||||||
var (exitCode, stdout, stderr) = await RunGitAsync(dir, ["rev-parse", "HEAD"], ct);
|
var (exitCode, stdout, stderr) = await RunGitAsync(dir, ["rev-parse", "HEAD"], ct);
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
// Unborn HEAD (fresh `git init`, no commits) — the raw git stderr ("ambiguous
|
||||||
|
// argument 'HEAD'") is useless to a user; call sites should have run
|
||||||
|
// EnsureHeadCommitAsync, this message is the safety net for any that didn't.
|
||||||
|
if (stderr.Contains("ambiguous argument 'HEAD'", StringComparison.Ordinal) ||
|
||||||
|
stderr.Contains("unknown revision", StringComparison.Ordinal))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The repository at {dir} has no commits yet (unborn or broken HEAD) — ClaudeDo needs at least one commit to work in it.");
|
||||||
throw new InvalidOperationException($"git rev-parse HEAD failed (exit {exitCode}): {stderr}");
|
throw new InvalidOperationException($"git rev-parse HEAD failed (exit {exitCode}): {stderr}");
|
||||||
|
}
|
||||||
return stdout.Trim();
|
return stdout.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +64,67 @@ public sealed class GitService
|
|||||||
return exitCode == 0 ? stdout.Trim() : null;
|
return exitCode == 0 ? stdout.Trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>HEAD resolves to a commit (false for a fresh `git init` repo or a broken HEAD).</summary>
|
||||||
|
public async Task<bool> HasHeadCommitAsync(string dir, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// ^{commit} forces the object to actually exist and be a commit — a bare
|
||||||
|
// `--verify HEAD` happily accepts a detached HEAD pointing at a missing object.
|
||||||
|
var (exitCode, _, _) = await RunGitAsync(dir, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], ct);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string FallbackUserName = "ClaudeDo";
|
||||||
|
private const string FallbackUserEmail = "claudedo@local";
|
||||||
|
|
||||||
|
// Two parallel task starts on the same empty repo must create exactly one initial commit.
|
||||||
|
private static readonly SemaphoreSlim BootstrapGate = new(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guarantees HEAD resolves to a commit so worktrees can be created. A repo with an
|
||||||
|
/// unborn HEAD (fresh `git init`) gets an empty initial commit built from plumbing only
|
||||||
|
/// (hash-object/commit-tree/update-ref) — the user's index and working tree are never
|
||||||
|
/// touched, so staged and untracked files stay exactly as they were. Identity is passed
|
||||||
|
/// inline, so this also works on machines with no git identity configured. A repo whose
|
||||||
|
/// HEAD is broken (not a symbolic ref and not resolvable) throws instead of guessing.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if a bootstrap commit was created; false if HEAD already resolved.</returns>
|
||||||
|
public async Task<bool> EnsureHeadCommitAsync(string dir, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (await HasHeadCommitAsync(dir, ct)) return false;
|
||||||
|
|
||||||
|
var (symExit, symRef, _) = await RunGitAsync(dir, ["symbolic-ref", "--quiet", "HEAD"], ct);
|
||||||
|
if (symExit != 0)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The repository at {dir} has a broken HEAD (detached onto a missing commit, or a corrupt .git/HEAD). Repair the repository manually.");
|
||||||
|
|
||||||
|
await BootstrapGate.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await HasHeadCommitAsync(dir, ct)) return false;
|
||||||
|
|
||||||
|
var (treeExit, treeSha, treeErr) = await RunGitAsync(dir, ["mktree"], ct, stdinData: "");
|
||||||
|
if (treeExit != 0)
|
||||||
|
throw new InvalidOperationException($"git mktree failed (exit {treeExit}): {treeErr}");
|
||||||
|
|
||||||
|
var (commitExit, commitSha, commitErr) = await RunGitAsync(dir,
|
||||||
|
["-c", $"user.name={FallbackUserName}", "-c", $"user.email={FallbackUserEmail}",
|
||||||
|
"commit-tree", treeSha.Trim(), "-m", "chore: initialize repository (ClaudeDo)"], ct);
|
||||||
|
if (commitExit != 0)
|
||||||
|
throw new InvalidOperationException($"git commit-tree failed (exit {commitExit}): {commitErr}");
|
||||||
|
|
||||||
|
var (refExit, _, refErr) = await RunGitAsync(dir,
|
||||||
|
["update-ref", symRef.Trim(), commitSha.Trim()], ct);
|
||||||
|
if (refExit != 0)
|
||||||
|
throw new InvalidOperationException($"git update-ref failed (exit {refExit}): {refErr}");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BootstrapGate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task WorktreeAddAsync(string repoDir, string branchName, string worktreePath, string baseCommit, CancellationToken ct = default)
|
public async Task WorktreeAddAsync(string repoDir, string branchName, string worktreePath, string baseCommit, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await WorktreeAddGate.WaitAsync(ct);
|
await WorktreeAddGate.WaitAsync(ct);
|
||||||
@@ -135,10 +205,24 @@ public sealed class GitService
|
|||||||
{
|
{
|
||||||
// Use -F - (read message from stdin) to handle multi-line messages safely.
|
// Use -F - (read message from stdin) to handle multi-line messages safely.
|
||||||
var (exitCode, _, stderr) = await RunGitAsync(worktreePath, ["commit", "-F", "-"], ct, stdinData: message);
|
var (exitCode, _, stderr) = await RunGitAsync(worktreePath, ["commit", "-F", "-"], ct, stdinData: message);
|
||||||
|
if (exitCode != 0 && IsMissingIdentityError(stderr))
|
||||||
|
{
|
||||||
|
// A machine without git identity configured must not fail every auto-commit —
|
||||||
|
// retry once as ClaudeDo. The happy path pays no preflight cost.
|
||||||
|
(exitCode, _, stderr) = await RunGitAsync(worktreePath,
|
||||||
|
["-c", $"user.name={FallbackUserName}", "-c", $"user.email={FallbackUserEmail}",
|
||||||
|
"commit", "-F", "-"], ct, stdinData: message);
|
||||||
|
}
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
throw new InvalidOperationException($"git commit failed (exit {exitCode}): {stderr}");
|
throw new InvalidOperationException($"git commit failed (exit {exitCode}): {stderr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsMissingIdentityError(string stderr) =>
|
||||||
|
stderr.Contains("tell me who you are", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
stderr.Contains("empty ident", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
stderr.Contains("no email was given", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
stderr.Contains("unable to auto-detect email address", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public async Task<string> GetDiffAsync(
|
public async Task<string> GetDiffAsync(
|
||||||
string worktreePath, IReadOnlyList<string>? paths = null, CancellationToken ct = default)
|
string worktreePath, IReadOnlyList<string>? paths = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -254,6 +338,38 @@ public sealed class GitService
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>All git-registered worktrees for <paramref name="repoDir"/>, with their branch (short name, null if detached).</summary>
|
||||||
|
public async Task<List<(string Path, string? Branch)>> ListWorktreesAsync(string repoDir, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["worktree", "list", "--porcelain"], ct);
|
||||||
|
if (exitCode != 0) return new();
|
||||||
|
|
||||||
|
var result = new List<(string Path, string? Branch)>();
|
||||||
|
string? currentPath = null;
|
||||||
|
string? currentBranch = null;
|
||||||
|
foreach (var raw in stdout.Split('\n'))
|
||||||
|
{
|
||||||
|
var line = raw.TrimEnd('\r');
|
||||||
|
if (line.StartsWith("worktree ", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
currentPath = line["worktree ".Length..].Trim();
|
||||||
|
}
|
||||||
|
else if (line.StartsWith("branch ", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var b = line["branch ".Length..].Trim();
|
||||||
|
currentBranch = b.StartsWith("refs/heads/", StringComparison.Ordinal) ? b["refs/heads/".Length..] : b;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrWhiteSpace(line))
|
||||||
|
{
|
||||||
|
if (currentPath is not null) result.Add((currentPath, currentBranch));
|
||||||
|
currentPath = null;
|
||||||
|
currentBranch = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentPath is not null) result.Add((currentPath, currentBranch));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task WorktreePruneAsync(string repoDir, CancellationToken ct = default)
|
public async Task WorktreePruneAsync(string repoDir, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["worktree", "prune"], ct);
|
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["worktree", "prune"], ct);
|
||||||
@@ -308,14 +424,22 @@ public sealed class GitService
|
|||||||
return File.Exists(Path.Combine(gitDir, "MERGE_HEAD"));
|
return File.Exists(Path.Combine(gitDir, "MERGE_HEAD"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="autostash">Stash the target working tree's uncommitted changes before the
|
||||||
|
/// merge and restore them after it. Git owns the bookkeeping: on a conflict the stash stays
|
||||||
|
/// pending and is re-applied by `merge --continue`/`--abort`, so a conflicted merge left in
|
||||||
|
/// the tree doesn't lose it. A restore that itself conflicts leaves the stash in
|
||||||
|
/// `git stash list` rather than dropping it.</param>
|
||||||
public async Task<(int ExitCode, string Stderr)> MergeNoFfAsync(
|
public async Task<(int ExitCode, string Stderr)> MergeNoFfAsync(
|
||||||
string repoDir, string sourceBranch, string message, CancellationToken ct = default)
|
string repoDir, string sourceBranch, string message, CancellationToken ct = default,
|
||||||
|
bool autostash = false)
|
||||||
{
|
{
|
||||||
// diff3 conflict style writes the merge base (|||||||) into conflict markers so the
|
// diff3 conflict style writes the merge base (|||||||) into conflict markers so the
|
||||||
// in-app resolver can show a true three-way view. It only enriches conflicted hunks;
|
// in-app resolver can show a true three-way view. It only enriches conflicted hunks;
|
||||||
// clean merges are unaffected.
|
// clean merges are unaffected.
|
||||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir,
|
string[] args = autostash
|
||||||
["-c", "merge.conflictStyle=diff3", "merge", "--no-ff", "-m", message, sourceBranch], ct);
|
? ["-c", "merge.conflictStyle=diff3", "merge", "--no-ff", "--autostash", "-m", message, sourceBranch]
|
||||||
|
: ["-c", "merge.conflictStyle=diff3", "merge", "--no-ff", "-m", message, sourceBranch];
|
||||||
|
var (exitCode, _, stderr) = await RunGitAsync(repoDir, args, ct);
|
||||||
return (exitCode, stderr);
|
return (exitCode, stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +480,38 @@ public sealed class GitService
|
|||||||
throw new InvalidOperationException($"git revert --abort failed (exit {exitCode}): {stderr}");
|
throw new InvalidOperationException($"git revert --abort failed (exit {exitCode}): {stderr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Re-applies a merge commit's own diff (against its first parent, `-m 1`) as a new commit on
|
||||||
|
/// top of HEAD. Needed to redo a merge that was previously reverted: once a branch tip is an
|
||||||
|
/// ancestor of HEAD, `git merge` on that branch again is a silent "Already up to date." no-op
|
||||||
|
/// (ancestry, not content, is what merge checks) -- cherry-picking the original merge commit's
|
||||||
|
/// diff is what actually restores the content.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(int ExitCode, string Stderr)> CherryPickMergeCommitAsync(
|
||||||
|
string repoDir, string mergeCommitSha, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (exitCode, _, stderr) = await RunGitAsync(repoDir,
|
||||||
|
["-c", "merge.conflictStyle=diff3", "cherry-pick", "-m", "1", "--no-edit", mergeCommitSha], ct);
|
||||||
|
return (exitCode, stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsMidCherryPickAsync(string repoDir, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["rev-parse", "--git-dir"], ct);
|
||||||
|
if (exitCode != 0) return false;
|
||||||
|
var gitDir = stdout.Trim();
|
||||||
|
if (!Path.IsPathRooted(gitDir))
|
||||||
|
gitDir = Path.Combine(repoDir, gitDir);
|
||||||
|
return File.Exists(Path.Combine(gitDir, "CHERRY_PICK_HEAD"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CherryPickAbortAsync(string repoDir, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["cherry-pick", "--abort"], ct);
|
||||||
|
if (exitCode != 0)
|
||||||
|
throw new InvalidOperationException($"git cherry-pick --abort failed (exit {exitCode}): {stderr}");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<string>> ListConflictedFilesAsync(string repoDir, CancellationToken ct = default)
|
public async Task<List<string>> ListConflictedFilesAsync(string repoDir, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var (exitCode, stdout, stderr) = await RunGitAsync(repoDir,
|
var (exitCode, stdout, stderr) = await RunGitAsync(repoDir,
|
||||||
@@ -402,6 +558,16 @@ public sealed class GitService
|
|||||||
return exitCode == 0 ? stdout : null;
|
return exitCode == 0 ? stdout : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content of <paramref name="path"/> at <paramref name="revision"/> via `git show revision:path`.
|
||||||
|
/// Null when the path doesn't exist at that revision (new, deleted, or renamed file).
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string?> ShowFileAsync(string repoDir, string revision, string path, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["show", $"{revision}:{path}"], ct, trimOutput: false);
|
||||||
|
return exitCode == 0 ? stdout : null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Non-destructive mergeability probe via `git merge-tree --write-tree`. Writes only
|
/// Non-destructive mergeability probe via `git merge-tree --write-tree`. Writes only
|
||||||
/// loose objects — the working tree, index, and refs are left untouched.
|
/// loose objects — the working tree, index, and refs are left untouched.
|
||||||
@@ -552,8 +718,15 @@ public sealed class GitService
|
|||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
StandardOutputEncoding = Encoding.UTF8,
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
StandardErrorEncoding = Encoding.UTF8,
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
StandardInputEncoding = stdinData is not null ? Encoding.UTF8 : null,
|
// BOM-less: Encoding.UTF8 writes a BOM preamble, which git rejects as input
|
||||||
|
// (mktree "input format error") and would prepend to stdin commit messages.
|
||||||
|
StandardInputEncoding = stdinData is not null ? new UTF8Encoding(false) : null,
|
||||||
};
|
};
|
||||||
|
// Force English stderr/stdout so the English-only substring matches below
|
||||||
|
// (e.g. "ambiguous argument 'HEAD'", "tell me who you are") work regardless
|
||||||
|
// of the OS locale git for Windows picks up.
|
||||||
|
psi.Environment["LC_ALL"] = "C";
|
||||||
|
psi.Environment["LANGUAGE"] = "C";
|
||||||
psi.ArgumentList.Add("-C");
|
psi.ArgumentList.Add("-C");
|
||||||
psi.ArgumentList.Add(workDir);
|
psi.ArgumentList.Add(workDir);
|
||||||
foreach (var a in args) psi.ArgumentList.Add(a);
|
foreach (var a in args) psi.ArgumentList.Add(a);
|
||||||
|
|||||||
@@ -0,0 +1,978 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ClaudeDoDbContext))]
|
||||||
|
[Migration("20260828084750_AddListIcon")]
|
||||||
|
partial class AddListIcon
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.AppSettingsEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoContinueOnUsageLimit")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("auto_continue_on_usage_limit");
|
||||||
|
|
||||||
|
b.Property<string>("CentralWorktreeRoot")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("central_worktree_root");
|
||||||
|
|
||||||
|
b.Property<int>("DailyPrepMaxTasks")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(5)
|
||||||
|
.HasColumnName("daily_prep_max_tasks");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultClaudeInstructions")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("")
|
||||||
|
.HasColumnName("default_claude_instructions");
|
||||||
|
|
||||||
|
b.Property<int>("DefaultMaxTurns")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(40)
|
||||||
|
.HasColumnName("default_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultModel")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sonnet")
|
||||||
|
.HasColumnName("default_model");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultPermissionMode")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("bypassPermissions")
|
||||||
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTurnsCeiling")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("max_turns_ceiling");
|
||||||
|
|
||||||
|
b.Property<string>("ModelPresets")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model_presets");
|
||||||
|
|
||||||
|
b.Property<int>("NextTaskNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("next_task_number");
|
||||||
|
|
||||||
|
b.Property<string>("RepoImportFolders")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("repo_import_folders");
|
||||||
|
|
||||||
|
b.Property<string>("ReportExcludedPaths")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("report_excluded_paths");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("StandupWeekday")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(3)
|
||||||
|
.HasColumnName("standup_weekday");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateFiveHourPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("usage_gate_five_hour_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateSevenDayPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(90)
|
||||||
|
.HasColumnName("usage_gate_seven_day_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourSoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDayHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDaySoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("WorktreeAutoCleanupDays")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(7)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_days");
|
||||||
|
|
||||||
|
b.Property<bool>("WorktreeAutoCleanupEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_enabled");
|
||||||
|
|
||||||
|
b.Property<string>("WorktreeStrategy")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sibling")
|
||||||
|
.HasColumnName("worktree_strategy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("app_settings", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AutoContinueOnUsageLimit = false,
|
||||||
|
DailyPrepMaxTasks = 5,
|
||||||
|
DefaultClaudeInstructions = "",
|
||||||
|
DefaultMaxTurns = 40,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "auto",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
MaxTurnsCeiling = 80,
|
||||||
|
NextTaskNumber = 1,
|
||||||
|
StandupWeekday = 3,
|
||||||
|
UsageGateFiveHourPct = 80,
|
||||||
|
UsageGateSevenDayPct = 90,
|
||||||
|
UsageThrottleFiveHourHardPct = 65,
|
||||||
|
UsageThrottleFiveHourSoftPct = 50,
|
||||||
|
UsageThrottleSevenDayHardPct = 65,
|
||||||
|
UsageThrottleSevenDaySoftPct = 50,
|
||||||
|
WorktreeAutoCleanupDays = 7,
|
||||||
|
WorktreeAutoCleanupEnabled = false,
|
||||||
|
WorktreeStrategy = "sibling"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.DailyNoteEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("note_date");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Date");
|
||||||
|
|
||||||
|
b.ToTable("daily_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<int?>("TicketProjectId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ticket_project_id");
|
||||||
|
|
||||||
|
b.Property<string>("VerifyCommand")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("verify_command");
|
||||||
|
|
||||||
|
b.HasKey("ListId");
|
||||||
|
|
||||||
|
b.ToTable("list_config", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultCommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("default_commit_type");
|
||||||
|
|
||||||
|
b.Property<bool>("FindingsTracked")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("findings_tracked");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("icon");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
|
b.ToTable("lists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<string>("Kind")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("ping")
|
||||||
|
.HasColumnName("action_kind");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("prime_schedules", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SessionSkillEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("AddedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("added_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("PinnedRef")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("pinned_ref");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("source_url");
|
||||||
|
|
||||||
|
b.Property<string>("Subpath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("subpath");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("session_skills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("Completed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("OrderNum")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("order_num");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_subtasks_task_id");
|
||||||
|
|
||||||
|
b.ToTable("subtasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("ByteSize")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("byte_size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file_name");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_attachments_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_attachments", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("BlockedByTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("blocked_by_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
|
b.Property<string>("DependsOnTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("depends_on_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureMaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("FailureReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("failure_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureTurnsUsed")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_turns_used");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerBaseCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerHeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("InteractiveSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("interactive_session_id");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMyDay")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_my_day");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStarred")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_starred");
|
||||||
|
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("notes");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("number");
|
||||||
|
|
||||||
|
b.Property<string>("ParentTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlanningFinalizedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_finalized_at");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningPhase")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("none")
|
||||||
|
.HasColumnName("planning_phase");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_id");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionToken")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_token");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
b.Property<int>("RoadblockCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("roadblock_count");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<string>("TicketRef")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ticket_ref");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsageLimitAutoContinuedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("usage_limit_auto_continued_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockedByTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
|
b.HasIndex("DependsOnTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_depends_on");
|
||||||
|
|
||||||
|
b.HasIndex("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Number")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx_tasks_number");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
b.HasIndex("ListId", "SortOrder")
|
||||||
|
.HasDatabaseName("idx_tasks_list_sort");
|
||||||
|
|
||||||
|
b.ToTable("tasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheReadTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_read_tokens");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheWriteTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_write_tokens");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("error_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("Errors")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("errors");
|
||||||
|
|
||||||
|
b.Property<int?>("ExitCode")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exit_code");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsRetry")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_retry");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Prompt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt");
|
||||||
|
|
||||||
|
b.Property<string>("ResultMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("ResultSubtype")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_subtype");
|
||||||
|
|
||||||
|
b.Property<int>("RunNumber")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("run_number");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("StructuredOutputJson")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("structured_output");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("TerminalReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("terminal_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensIn")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_in");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensOut")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_out");
|
||||||
|
|
||||||
|
b.Property<int?>("TurnCount")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("turn_count");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_runs_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_runs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WeekReportEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("EndDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("end_date");
|
||||||
|
|
||||||
|
b.Property<DateTime>("GeneratedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("generated_at");
|
||||||
|
|
||||||
|
b.Property<string>("Markdown")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("markdown");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("StartDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("start_date");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StartDate", "EndDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("week_reports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("BaseCommit")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("BranchName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("branch_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DiffStat")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("diff_stat");
|
||||||
|
|
||||||
|
b.Property<string>("HeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("MergeCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("merge_commit");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("active")
|
||||||
|
.HasColumnName("state");
|
||||||
|
|
||||||
|
b.HasKey("TaskId");
|
||||||
|
|
||||||
|
b.ToTable("worktrees", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithOne("Config")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.ListConfigEntity", "ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Subtasks")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BlockedByTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DependsOnTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Runs")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithOne("Worktree")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.WorktreeEntity", "TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddListIcon : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "icon",
|
||||||
|
table: "lists",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "icon",
|
||||||
|
table: "lists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+982
@@ -0,0 +1,982 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ClaudeDoDbContext))]
|
||||||
|
[Migration("20260828185839_AddDailyNoteFromAgent")]
|
||||||
|
partial class AddDailyNoteFromAgent
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.AppSettingsEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoContinueOnUsageLimit")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("auto_continue_on_usage_limit");
|
||||||
|
|
||||||
|
b.Property<string>("CentralWorktreeRoot")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("central_worktree_root");
|
||||||
|
|
||||||
|
b.Property<int>("DailyPrepMaxTasks")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(5)
|
||||||
|
.HasColumnName("daily_prep_max_tasks");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultClaudeInstructions")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("")
|
||||||
|
.HasColumnName("default_claude_instructions");
|
||||||
|
|
||||||
|
b.Property<int>("DefaultMaxTurns")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(40)
|
||||||
|
.HasColumnName("default_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultModel")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sonnet")
|
||||||
|
.HasColumnName("default_model");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultPermissionMode")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("bypassPermissions")
|
||||||
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTurnsCeiling")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("max_turns_ceiling");
|
||||||
|
|
||||||
|
b.Property<string>("ModelPresets")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model_presets");
|
||||||
|
|
||||||
|
b.Property<int>("NextTaskNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("next_task_number");
|
||||||
|
|
||||||
|
b.Property<string>("RepoImportFolders")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("repo_import_folders");
|
||||||
|
|
||||||
|
b.Property<string>("ReportExcludedPaths")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("report_excluded_paths");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("StandupWeekday")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(3)
|
||||||
|
.HasColumnName("standup_weekday");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateFiveHourPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("usage_gate_five_hour_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateSevenDayPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(90)
|
||||||
|
.HasColumnName("usage_gate_seven_day_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourSoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDayHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDaySoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("WorktreeAutoCleanupDays")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(7)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_days");
|
||||||
|
|
||||||
|
b.Property<bool>("WorktreeAutoCleanupEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_enabled");
|
||||||
|
|
||||||
|
b.Property<string>("WorktreeStrategy")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sibling")
|
||||||
|
.HasColumnName("worktree_strategy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("app_settings", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AutoContinueOnUsageLimit = false,
|
||||||
|
DailyPrepMaxTasks = 5,
|
||||||
|
DefaultClaudeInstructions = "",
|
||||||
|
DefaultMaxTurns = 40,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "auto",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
MaxTurnsCeiling = 80,
|
||||||
|
NextTaskNumber = 1,
|
||||||
|
StandupWeekday = 3,
|
||||||
|
UsageGateFiveHourPct = 80,
|
||||||
|
UsageGateSevenDayPct = 90,
|
||||||
|
UsageThrottleFiveHourHardPct = 65,
|
||||||
|
UsageThrottleFiveHourSoftPct = 50,
|
||||||
|
UsageThrottleSevenDayHardPct = 65,
|
||||||
|
UsageThrottleSevenDaySoftPct = 50,
|
||||||
|
WorktreeAutoCleanupDays = 7,
|
||||||
|
WorktreeAutoCleanupEnabled = false,
|
||||||
|
WorktreeStrategy = "sibling"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.DailyNoteEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("note_date");
|
||||||
|
|
||||||
|
b.Property<bool>("FromAgent")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("from_agent");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Date");
|
||||||
|
|
||||||
|
b.ToTable("daily_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<int?>("TicketProjectId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ticket_project_id");
|
||||||
|
|
||||||
|
b.Property<string>("VerifyCommand")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("verify_command");
|
||||||
|
|
||||||
|
b.HasKey("ListId");
|
||||||
|
|
||||||
|
b.ToTable("list_config", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultCommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("default_commit_type");
|
||||||
|
|
||||||
|
b.Property<bool>("FindingsTracked")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("findings_tracked");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("icon");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
|
b.ToTable("lists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<string>("Kind")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("ping")
|
||||||
|
.HasColumnName("action_kind");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("prime_schedules", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SessionSkillEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("AddedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("added_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("PinnedRef")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("pinned_ref");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("source_url");
|
||||||
|
|
||||||
|
b.Property<string>("Subpath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("subpath");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("session_skills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("Completed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("OrderNum")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("order_num");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_subtasks_task_id");
|
||||||
|
|
||||||
|
b.ToTable("subtasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("ByteSize")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("byte_size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file_name");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_attachments_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_attachments", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("BlockedByTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("blocked_by_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
|
b.Property<string>("DependsOnTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("depends_on_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureMaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("FailureReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("failure_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureTurnsUsed")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_turns_used");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerBaseCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerHeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("InteractiveSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("interactive_session_id");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMyDay")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_my_day");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStarred")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_starred");
|
||||||
|
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("notes");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("number");
|
||||||
|
|
||||||
|
b.Property<string>("ParentTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlanningFinalizedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_finalized_at");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningPhase")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("none")
|
||||||
|
.HasColumnName("planning_phase");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_id");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionToken")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_token");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
b.Property<int>("RoadblockCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("roadblock_count");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<string>("TicketRef")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ticket_ref");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsageLimitAutoContinuedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("usage_limit_auto_continued_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockedByTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
|
b.HasIndex("DependsOnTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_depends_on");
|
||||||
|
|
||||||
|
b.HasIndex("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Number")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx_tasks_number");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
b.HasIndex("ListId", "SortOrder")
|
||||||
|
.HasDatabaseName("idx_tasks_list_sort");
|
||||||
|
|
||||||
|
b.ToTable("tasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheReadTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_read_tokens");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheWriteTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_write_tokens");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("error_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("Errors")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("errors");
|
||||||
|
|
||||||
|
b.Property<int?>("ExitCode")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exit_code");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsRetry")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_retry");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Prompt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt");
|
||||||
|
|
||||||
|
b.Property<string>("ResultMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("ResultSubtype")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_subtype");
|
||||||
|
|
||||||
|
b.Property<int>("RunNumber")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("run_number");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("StructuredOutputJson")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("structured_output");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("TerminalReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("terminal_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensIn")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_in");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensOut")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_out");
|
||||||
|
|
||||||
|
b.Property<int?>("TurnCount")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("turn_count");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_runs_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_runs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WeekReportEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("EndDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("end_date");
|
||||||
|
|
||||||
|
b.Property<DateTime>("GeneratedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("generated_at");
|
||||||
|
|
||||||
|
b.Property<string>("Markdown")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("markdown");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("StartDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("start_date");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StartDate", "EndDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("week_reports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("BaseCommit")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("BranchName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("branch_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DiffStat")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("diff_stat");
|
||||||
|
|
||||||
|
b.Property<string>("HeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("MergeCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("merge_commit");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("active")
|
||||||
|
.HasColumnName("state");
|
||||||
|
|
||||||
|
b.HasKey("TaskId");
|
||||||
|
|
||||||
|
b.ToTable("worktrees", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithOne("Config")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.ListConfigEntity", "ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Subtasks")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BlockedByTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DependsOnTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Runs")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithOne("Worktree")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.WorktreeEntity", "TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDailyNoteFromAgent : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "from_agent",
|
||||||
|
table: "daily_notes",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "from_agent",
|
||||||
|
table: "daily_notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,988 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ClaudeDoDbContext))]
|
||||||
|
[Migration("20260902102003_AddExpectsNoDiff")]
|
||||||
|
partial class AddExpectsNoDiff
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.AppSettingsEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoContinueOnUsageLimit")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("auto_continue_on_usage_limit");
|
||||||
|
|
||||||
|
b.Property<string>("CentralWorktreeRoot")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("central_worktree_root");
|
||||||
|
|
||||||
|
b.Property<int>("DailyPrepMaxTasks")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(5)
|
||||||
|
.HasColumnName("daily_prep_max_tasks");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultClaudeInstructions")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("")
|
||||||
|
.HasColumnName("default_claude_instructions");
|
||||||
|
|
||||||
|
b.Property<int>("DefaultMaxTurns")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(40)
|
||||||
|
.HasColumnName("default_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultModel")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sonnet")
|
||||||
|
.HasColumnName("default_model");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultPermissionMode")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("bypassPermissions")
|
||||||
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTurnsCeiling")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("max_turns_ceiling");
|
||||||
|
|
||||||
|
b.Property<string>("ModelPresets")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model_presets");
|
||||||
|
|
||||||
|
b.Property<int>("NextTaskNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("next_task_number");
|
||||||
|
|
||||||
|
b.Property<string>("RepoImportFolders")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("repo_import_folders");
|
||||||
|
|
||||||
|
b.Property<string>("ReportExcludedPaths")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("report_excluded_paths");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("StandupWeekday")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(3)
|
||||||
|
.HasColumnName("standup_weekday");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateFiveHourPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(80)
|
||||||
|
.HasColumnName("usage_gate_five_hour_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageGateSevenDayPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(90)
|
||||||
|
.HasColumnName("usage_gate_seven_day_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleFiveHourSoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_five_hour_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDayHardPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(65)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_hard_pct");
|
||||||
|
|
||||||
|
b.Property<int>("UsageThrottleSevenDaySoftPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(50)
|
||||||
|
.HasColumnName("usage_throttle_seven_day_soft_pct");
|
||||||
|
|
||||||
|
b.Property<int>("WorktreeAutoCleanupDays")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(7)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_days");
|
||||||
|
|
||||||
|
b.Property<bool>("WorktreeAutoCleanupEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_enabled");
|
||||||
|
|
||||||
|
b.Property<string>("WorktreeStrategy")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sibling")
|
||||||
|
.HasColumnName("worktree_strategy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("app_settings", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AutoContinueOnUsageLimit = false,
|
||||||
|
DailyPrepMaxTasks = 5,
|
||||||
|
DefaultClaudeInstructions = "",
|
||||||
|
DefaultMaxTurns = 40,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "auto",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
MaxTurnsCeiling = 80,
|
||||||
|
NextTaskNumber = 1,
|
||||||
|
StandupWeekday = 3,
|
||||||
|
UsageGateFiveHourPct = 80,
|
||||||
|
UsageGateSevenDayPct = 90,
|
||||||
|
UsageThrottleFiveHourHardPct = 65,
|
||||||
|
UsageThrottleFiveHourSoftPct = 50,
|
||||||
|
UsageThrottleSevenDayHardPct = 65,
|
||||||
|
UsageThrottleSevenDaySoftPct = 50,
|
||||||
|
WorktreeAutoCleanupDays = 7,
|
||||||
|
WorktreeAutoCleanupEnabled = false,
|
||||||
|
WorktreeStrategy = "sibling"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.DailyNoteEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("note_date");
|
||||||
|
|
||||||
|
b.Property<bool>("FromAgent")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("from_agent");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Date");
|
||||||
|
|
||||||
|
b.ToTable("daily_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<int?>("TicketProjectId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ticket_project_id");
|
||||||
|
|
||||||
|
b.Property<string>("VerifyCommand")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("verify_command");
|
||||||
|
|
||||||
|
b.HasKey("ListId");
|
||||||
|
|
||||||
|
b.ToTable("list_config", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultCommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("default_commit_type");
|
||||||
|
|
||||||
|
b.Property<bool>("FindingsTracked")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("findings_tracked");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("icon");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
|
b.ToTable("lists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<string>("Kind")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("ping")
|
||||||
|
.HasColumnName("action_kind");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("prime_schedules", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SessionSkillEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("AddedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("added_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("PinnedRef")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("pinned_ref");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("source_url");
|
||||||
|
|
||||||
|
b.Property<string>("Subpath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("subpath");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("session_skills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("Completed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("OrderNum")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("order_num");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_subtasks_task_id");
|
||||||
|
|
||||||
|
b.ToTable("subtasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("ByteSize")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("byte_size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file_name");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_attachments_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_attachments", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("BlockedByTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("blocked_by_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
|
b.Property<string>("DependsOnTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("depends_on_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<bool>("ExpectsNoDiff")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("expects_no_diff");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureMaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("FailureReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("failure_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("FailureTurnsUsed")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("failure_turns_used");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerBaseCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("HandlerHeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("handler_head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("InteractiveSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("interactive_session_id");
|
||||||
|
|
||||||
|
b.Property<bool>("IsManual")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_manual");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMyDay")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_my_day");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStarred")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_starred");
|
||||||
|
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxTurns")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("notes");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("number");
|
||||||
|
|
||||||
|
b.Property<string>("ParentTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionMode")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlanningFinalizedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_finalized_at");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningPhase")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("none")
|
||||||
|
.HasColumnName("planning_phase");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_id");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionToken")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_token");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
b.Property<int>("RoadblockCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("roadblock_count");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<string>("SessionSkills")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_skills");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<string>("TicketRef")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ticket_ref");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsageLimitAutoContinuedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("usage_limit_auto_continued_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockedByTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
|
b.HasIndex("DependsOnTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_depends_on");
|
||||||
|
|
||||||
|
b.HasIndex("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Number")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx_tasks_number");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
b.HasIndex("ListId", "SortOrder")
|
||||||
|
.HasDatabaseName("idx_tasks_list_sort");
|
||||||
|
|
||||||
|
b.ToTable("tasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheReadTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_read_tokens");
|
||||||
|
|
||||||
|
b.Property<int?>("CacheWriteTokens")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("cache_write_tokens");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("error_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("Errors")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("errors");
|
||||||
|
|
||||||
|
b.Property<int?>("ExitCode")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exit_code");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsRetry")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_retry");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Prompt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt");
|
||||||
|
|
||||||
|
b.Property<string>("ResultMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_markdown");
|
||||||
|
|
||||||
|
b.Property<string>("ResultSubtype")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_subtype");
|
||||||
|
|
||||||
|
b.Property<int>("RunNumber")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("run_number");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("StructuredOutputJson")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("structured_output");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("TerminalReason")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("terminal_reason");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensIn")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_in");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensOut")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_out");
|
||||||
|
|
||||||
|
b.Property<int?>("TurnCount")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("turn_count");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_runs_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_runs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WeekReportEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("EndDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("end_date");
|
||||||
|
|
||||||
|
b.Property<DateTime>("GeneratedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("generated_at");
|
||||||
|
|
||||||
|
b.Property<string>("Markdown")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("markdown");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("StartDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("start_date");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StartDate", "EndDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("week_reports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("BaseCommit")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("BranchName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("branch_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DiffStat")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("diff_stat");
|
||||||
|
|
||||||
|
b.Property<string>("HeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("MergeCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("merge_commit");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("active")
|
||||||
|
.HasColumnName("state");
|
||||||
|
|
||||||
|
b.HasKey("TaskId");
|
||||||
|
|
||||||
|
b.ToTable("worktrees", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithOne("Config")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.ListConfigEntity", "ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Subtasks")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskAttachmentEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BlockedByTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DependsOnTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Runs")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithOne("Worktree")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.WorktreeEntity", "TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddExpectsNoDiff : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "expects_no_diff",
|
||||||
|
table: "tasks",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "expects_no_diff",
|
||||||
|
table: "tasks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1011
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Turns the single <c>tasks.depends_on_task_id</c> column into a set: one row per declared
|
||||||
|
/// "run after" edge in <c>task_dependencies</c>. Hand-edited after scaffolding so the data copy
|
||||||
|
/// sits between CreateTable and DropColumn — the scaffolded order dropped the column first and
|
||||||
|
/// would have discarded every existing link.
|
||||||
|
/// </summary>
|
||||||
|
public partial class AddTaskDependencies : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "task_dependencies",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
task_id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
depends_on_task_id = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_task_dependencies", x => new { x.task_id, x.depends_on_task_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_task_dependencies_tasks_depends_on_task_id",
|
||||||
|
column: x => x.depends_on_task_id,
|
||||||
|
principalTable: "tasks",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_task_dependencies_tasks_task_id",
|
||||||
|
column: x => x.task_id,
|
||||||
|
principalTable: "tasks",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_task_dependencies_depends_on",
|
||||||
|
table: "task_dependencies",
|
||||||
|
column: "depends_on_task_id");
|
||||||
|
|
||||||
|
// Every existing link becomes a one-element set. The old FK was ON DELETE SET NULL, so
|
||||||
|
// a surviving value always points at an existing task and can't violate the new FK.
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
INSERT INTO task_dependencies (task_id, depends_on_task_id)
|
||||||
|
SELECT id, depends_on_task_id FROM tasks WHERE depends_on_task_id IS NOT NULL
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_tasks_tasks_depends_on_task_id",
|
||||||
|
table: "tasks");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx_tasks_depends_on",
|
||||||
|
table: "tasks");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "depends_on_task_id",
|
||||||
|
table: "tasks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "depends_on_task_id",
|
||||||
|
table: "tasks",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
// Inherently lossy: a task with several predecessors keeps only one. There is no
|
||||||
|
// single-column shape that could hold the rest.
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
UPDATE tasks SET depends_on_task_id = (
|
||||||
|
SELECT td.depends_on_task_id FROM task_dependencies td
|
||||||
|
WHERE td.task_id = tasks.id
|
||||||
|
ORDER BY td.depends_on_task_id
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "task_dependencies");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_tasks_depends_on",
|
||||||
|
table: "tasks",
|
||||||
|
column: "depends_on_task_id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_tasks_tasks_depends_on_task_id",
|
||||||
|
table: "tasks",
|
||||||
|
column: "depends_on_task_id",
|
||||||
|
principalTable: "tasks",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPowerUse : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "power_use_allowed",
|
||||||
|
table: "list_config",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "list_handler_max_handoffs",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 4);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "power_use_enabled",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "app_settings",
|
||||||
|
keyColumn: "id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "list_handler_max_handoffs",
|
||||||
|
value: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "power_use_allowed",
|
||||||
|
table: "list_config");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "list_handler_max_handoffs",
|
||||||
|
table: "app_settings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "power_use_enabled",
|
||||||
|
table: "app_settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPowerUseWindow : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "power_use_handoff_pct",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 95);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "power_use_window_end",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "power_use_window_start",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "app_settings",
|
||||||
|
keyColumn: "id",
|
||||||
|
keyValue: 1,
|
||||||
|
columns: new[] { "power_use_handoff_pct", "power_use_window_end", "power_use_window_start" },
|
||||||
|
values: new object[] { 95, null, null });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "power_use_handoff_pct",
|
||||||
|
table: "app_settings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "power_use_window_end",
|
||||||
|
table: "app_settings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "power_use_window_start",
|
||||||
|
table: "app_settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddListFolders : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "folder_id",
|
||||||
|
table: "lists",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "list_folders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
created_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
icon = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
sort_order = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
|
||||||
|
is_collapsed = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_list_folders", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_lists_folder",
|
||||||
|
table: "lists",
|
||||||
|
column: "folder_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_list_folders_sort",
|
||||||
|
table: "list_folders",
|
||||||
|
column: "sort_order");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_lists_list_folders_folder_id",
|
||||||
|
table: "lists",
|
||||||
|
column: "folder_id",
|
||||||
|
principalTable: "list_folders",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_lists_list_folders_folder_id",
|
||||||
|
table: "lists");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "list_folders");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx_lists_folder",
|
||||||
|
table: "lists");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "folder_id",
|
||||||
|
table: "lists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1137
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMissionControlSessions : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mission_control_sessions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
session_id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
cwd = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
task_id = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
title = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
started_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
predecessor_session_id = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
brief_path = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_mission_control_sessions", x => x.session_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mission_control_sessions_started_at",
|
||||||
|
table: "mission_control_sessions",
|
||||||
|
column: "started_at");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mission_control_sessions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1152
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddNestedListFolders : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "parent_folder_id",
|
||||||
|
table: "list_folders",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_list_folders_parent",
|
||||||
|
table: "list_folders",
|
||||||
|
column: "parent_folder_id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_list_folders_list_folders_parent_folder_id",
|
||||||
|
table: "list_folders",
|
||||||
|
column: "parent_folder_id",
|
||||||
|
principalTable: "list_folders",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_list_folders_list_folders_parent_folder_id",
|
||||||
|
table: "list_folders");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx_list_folders_parent",
|
||||||
|
table: "list_folders");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "parent_folder_id",
|
||||||
|
table: "list_folders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1156
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddListFolderColor : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "color",
|
||||||
|
table: "list_folders",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "color",
|
||||||
|
table: "list_folders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,12 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasDefaultValue("bypassPermissions")
|
.HasDefaultValue("bypassPermissions")
|
||||||
.HasColumnName("default_permission_mode");
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("ListHandlerMaxHandoffs")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(4)
|
||||||
|
.HasColumnName("list_handler_max_handoffs");
|
||||||
|
|
||||||
b.Property<int>("MaxParallelExecutions")
|
b.Property<int>("MaxParallelExecutions")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
@@ -88,6 +94,26 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasDefaultValue(1)
|
.HasDefaultValue(1)
|
||||||
.HasColumnName("next_task_number");
|
.HasColumnName("next_task_number");
|
||||||
|
|
||||||
|
b.Property<bool>("PowerUseEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("power_use_enabled");
|
||||||
|
|
||||||
|
b.Property<int>("PowerUseHandoffPct")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(95)
|
||||||
|
.HasColumnName("power_use_handoff_pct");
|
||||||
|
|
||||||
|
b.Property<string>("PowerUseWindowEnd")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("power_use_window_end");
|
||||||
|
|
||||||
|
b.Property<string>("PowerUseWindowStart")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("power_use_window_start");
|
||||||
|
|
||||||
b.Property<string>("RepoImportFolders")
|
b.Property<string>("RepoImportFolders")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("repo_import_folders");
|
.HasColumnName("repo_import_folders");
|
||||||
@@ -175,9 +201,12 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
DefaultMaxTurns = 40,
|
DefaultMaxTurns = 40,
|
||||||
DefaultModel = "sonnet",
|
DefaultModel = "sonnet",
|
||||||
DefaultPermissionMode = "auto",
|
DefaultPermissionMode = "auto",
|
||||||
|
ListHandlerMaxHandoffs = 4,
|
||||||
MaxParallelExecutions = 1,
|
MaxParallelExecutions = 1,
|
||||||
MaxTurnsCeiling = 80,
|
MaxTurnsCeiling = 80,
|
||||||
NextTaskNumber = 1,
|
NextTaskNumber = 1,
|
||||||
|
PowerUseEnabled = false,
|
||||||
|
PowerUseHandoffPct = 95,
|
||||||
StandupWeekday = 3,
|
StandupWeekday = 3,
|
||||||
UsageGateFiveHourPct = 80,
|
UsageGateFiveHourPct = 80,
|
||||||
UsageGateSevenDayPct = 90,
|
UsageGateSevenDayPct = 90,
|
||||||
@@ -205,6 +234,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("note_date");
|
.HasColumnName("note_date");
|
||||||
|
|
||||||
|
b.Property<bool>("FromAgent")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("from_agent");
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
b.Property<int>("SortOrder")
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("sort_order");
|
.HasColumnName("sort_order");
|
||||||
@@ -243,6 +276,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("permission_mode");
|
.HasColumnName("permission_mode");
|
||||||
|
|
||||||
|
b.Property<bool>("PowerUseAllowed")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("power_use_allowed");
|
||||||
|
|
||||||
b.Property<string>("SessionSkills")
|
b.Property<string>("SessionSkills")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("session_skills");
|
.HasColumnName("session_skills");
|
||||||
@@ -287,6 +324,14 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("findings_tracked");
|
.HasColumnName("findings_tracked");
|
||||||
|
|
||||||
|
b.Property<string>("FolderId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("folder_id");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("icon");
|
||||||
|
|
||||||
b.Property<bool>("IsManual")
|
b.Property<bool>("IsManual")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
@@ -310,12 +355,104 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FolderId")
|
||||||
|
.HasDatabaseName("idx_lists_folder");
|
||||||
|
|
||||||
b.HasIndex("SortOrder")
|
b.HasIndex("SortOrder")
|
||||||
.HasDatabaseName("idx_lists_sort");
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
b.ToTable("lists", (string)null);
|
b.ToTable("lists", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListFolderEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("color");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("icon");
|
||||||
|
|
||||||
|
b.Property<bool>("IsCollapsed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_collapsed");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<string>("ParentFolderId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_folder_id");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ParentFolderId")
|
||||||
|
.HasDatabaseName("idx_list_folders_parent");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_list_folders_sort");
|
||||||
|
|
||||||
|
b.ToTable("list_folders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.MissionControlSessionEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<string>("BriefPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("brief_path");
|
||||||
|
|
||||||
|
b.Property<string>("Cwd")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("cwd");
|
||||||
|
|
||||||
|
b.Property<string>("PredecessorSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("predecessor_session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("SessionId");
|
||||||
|
|
||||||
|
b.HasIndex("StartedAt");
|
||||||
|
|
||||||
|
b.ToTable("mission_control_sessions", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@@ -467,6 +604,24 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
b.ToTable("task_attachments", (string)null);
|
b.ToTable("task_attachments", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskDependencyEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("DependsOnTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("depends_on_task_id");
|
||||||
|
|
||||||
|
b.HasKey("TaskId", "DependsOnTaskId");
|
||||||
|
|
||||||
|
b.HasIndex("DependsOnTaskId")
|
||||||
|
.HasDatabaseName("idx_task_dependencies_depends_on");
|
||||||
|
|
||||||
|
b.ToTable("task_dependencies", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
@@ -496,14 +651,16 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("created_by");
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
b.Property<string>("DependsOnTaskId")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("depends_on_task_id");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("description");
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<bool>("ExpectsNoDiff")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("expects_no_diff");
|
||||||
|
|
||||||
b.Property<int?>("FailureMaxTurns")
|
b.Property<int?>("FailureMaxTurns")
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("failure_max_turns");
|
.HasColumnName("failure_max_turns");
|
||||||
@@ -661,9 +818,6 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
b.HasIndex("BlockedByTaskId")
|
b.HasIndex("BlockedByTaskId")
|
||||||
.HasDatabaseName("idx_tasks_blocked_by");
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
b.HasIndex("DependsOnTaskId")
|
|
||||||
.HasDatabaseName("idx_tasks_depends_on");
|
|
||||||
|
|
||||||
b.HasIndex("ListId")
|
b.HasIndex("ListId")
|
||||||
.HasDatabaseName("idx_tasks_list_id");
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
@@ -876,6 +1030,22 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
b.Navigation("List");
|
b.Navigation("List");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListFolderEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FolderId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListFolderEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListFolderEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ParentFolderId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
@@ -898,6 +1068,21 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
b.Navigation("Task");
|
b.Navigation("Task");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskDependencyEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DependsOnTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany("Dependencies")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
@@ -905,11 +1090,6 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasForeignKey("BlockedByTaskId")
|
.HasForeignKey("BlockedByTaskId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("DependsOnTaskId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
.WithMany("Tasks")
|
.WithMany("Tasks")
|
||||||
.HasForeignKey("ListId")
|
.HasForeignKey("ListId")
|
||||||
@@ -959,6 +1139,8 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Children");
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Dependencies");
|
||||||
|
|
||||||
b.Navigation("Runs");
|
b.Navigation("Runs");
|
||||||
|
|
||||||
b.Navigation("Subtasks");
|
b.Navigation("Subtasks");
|
||||||
|
|||||||
@@ -60,4 +60,27 @@ public sealed class AppSettingsEntity
|
|||||||
// automatic ContinueTask once the 5h window resets, and the gate-blocked queue wakes exactly
|
// automatic ContinueTask once the 5h window resets, and the gate-blocked queue wakes exactly
|
||||||
// at that reset time instead of only on the 30s backstop.
|
// at that reset time instead of only on the 30s backstop.
|
||||||
public bool AutoContinueOnUsageLimit { get; set; }
|
public bool AutoContinueOnUsageLimit { get; set; }
|
||||||
|
|
||||||
|
// Master switch for PowerUse: list-handler runs go unattended (never ask the user) and harvest
|
||||||
|
// new work when a round would otherwise starve. Toggled in Settings → Execution. Off by default,
|
||||||
|
// and on its own it changes nothing — a run is only in PowerUse when the LIST also opted in via
|
||||||
|
// ListConfigEntity.PowerUseAllowed, so leaving this on cannot make an unrelated list's handler
|
||||||
|
// run unattended. Composition lives in InteractiveLaunchSpecService.
|
||||||
|
public bool PowerUseEnabled { get; set; }
|
||||||
|
|
||||||
|
// Optional local-time window (both `HH:mm`) narrowing when PowerUse applies, checked per phase
|
||||||
|
// at prompt-build time. Both null (the default) = round the clock, byte-identical to the
|
||||||
|
// pre-window behaviour; only one set counts as unset too. End before start wraps over midnight.
|
||||||
|
// Semantics live in PowerUseWindow.
|
||||||
|
public string? PowerUseWindowStart { get; set; }
|
||||||
|
public string? PowerUseWindowEnd { get; set; }
|
||||||
|
|
||||||
|
// Percentage of the 5h usage window at which a PowerUse handler session is told to hand off
|
||||||
|
// rather than start more work. Stored and displayed here; the consumer is the handoff prompt.
|
||||||
|
public int PowerUseHandoffPct { get; set; } = 95;
|
||||||
|
|
||||||
|
// Server-side cap on a list-handler run's wait<->merge handoff chain (HandoffRoundTracker).
|
||||||
|
// 4 = two full wait/merge cycles, the long-standing default. Raise it for a run meant to keep
|
||||||
|
// going for hours. It exists to bound runaway handoff loops, so it must stay >= 1.
|
||||||
|
public int ListHandlerMaxHandoffs { get; set; } = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,9 @@ public sealed class DailyNoteEntity
|
|||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>True when an agent wrote this note through the MCP tool rather than the user.
|
||||||
|
/// Drives the UI marker and the per-day cap in <c>NotesMcpTools</c>.</summary>
|
||||||
|
public bool FromAgent { get; set; }
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ public sealed class ListConfigEntity
|
|||||||
// Feld mitführen, sonst setzt es sich still zurück.
|
// Feld mitführen, sonst setzt es sich still zurück.
|
||||||
public int? TicketProjectId { get; set; }
|
public int? TicketProjectId { get; set; }
|
||||||
|
|
||||||
|
// Per-Liste-Opt-in für PowerUse. Ein Handler-Run läuft nur unattended, wenn BEIDES gesetzt ist:
|
||||||
|
// dieses Flag und AppSettingsEntity.PowerUseEnabled. Der globale Toggle bleibt damit gefahrlos
|
||||||
|
// an — eine Liste, die hier nicht zugestimmt hat, behält das interaktive Verhalten. Default
|
||||||
|
// false, also opt-in. ⚠️ Gleiche Falle wie TicketProjectId: SetConfigAsync kopiert verbatim.
|
||||||
|
public bool PowerUseAllowed { get; set; }
|
||||||
|
|
||||||
// Navigation property
|
// Navigation property
|
||||||
public ListEntity List { get; set; } = null!;
|
public ListEntity List { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ public sealed class ListEntity
|
|||||||
// When true the project's .claudedo/ findings store is committed with the repo; when false it
|
// When true the project's .claudedo/ findings store is committed with the repo; when false it
|
||||||
// is kept out of git via .git/info/exclude (see FindingsStore).
|
// is kept out of git via .git/info/exclude (see FindingsStore).
|
||||||
public bool FindingsTracked { get; set; }
|
public bool FindingsTracked { get; set; }
|
||||||
|
// Icon key into the UI's icon library (e.g. "Star"); null = the default folder icon.
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
// Sidebar folder this list sits in (ListFolderEntity.Id); null = top level. ON DELETE SET NULL,
|
||||||
|
// so deleting a folder moves its lists back to the top level instead of deleting them.
|
||||||
|
public string? FolderId { get; set; }
|
||||||
|
|
||||||
// Navigation properties
|
// Navigation properties
|
||||||
public ListConfigEntity? Config { get; set; }
|
public ListConfigEntity? Config { get; set; }
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sidebar folder grouping user lists. Deliberately its own table rather than a flag on
|
||||||
|
/// <see cref="ListEntity"/>: every place that enumerates lists (MCP list_task_lists, "move to
|
||||||
|
/// list", the queue picker, repo import) would otherwise have to filter folders out.
|
||||||
|
/// Folders nest arbitrarily deep via <see cref="ParentFolderId"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ListFolderEntity
|
||||||
|
{
|
||||||
|
public required string Id { get; init; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required DateTime CreatedAt { get; init; }
|
||||||
|
// Icon key into the UI's icon library, same set as ListEntity.Icon; null = default folder icon.
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
/// <summary>Hex colour (#RRGGBB) for the sidebar name and guide line; null = theme default.</summary>
|
||||||
|
public string? Color { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
// Sidebar expand/collapse state. Lives here rather than in ui.config.json because the row is
|
||||||
|
// already being read for the folder itself.
|
||||||
|
public bool IsCollapsed { get; set; }
|
||||||
|
// Parent folder, or null for a top-level folder. Self-FK, ON DELETE SET NULL — deleting a
|
||||||
|
// folder releases its subfolders to the top level, same as it does for its lists.
|
||||||
|
public string? ParentFolderId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
/// <summary>One claude session started in a Mission Control tile (ad-hoc, task or handoff) — the
|
||||||
|
/// index behind "Recent sessions". The transcript itself stays with Claude Code.</summary>
|
||||||
|
public sealed class MissionControlSessionEntity
|
||||||
|
{
|
||||||
|
/// <summary>The claude session id (`--session-id` / `--resume`).</summary>
|
||||||
|
public string SessionId { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Working directory the session ran in — a resume must start there.</summary>
|
||||||
|
public string Cwd { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Task the tile belonged to; null for an ad-hoc tile. No FK: the row outlives the task.</summary>
|
||||||
|
public string? TaskId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tile title at start (task title or folder name; a handoff inherits its predecessor's).</summary>
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>UTC time the session was first launched.</summary>
|
||||||
|
public DateTime StartedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>Session this one was handed off from; null unless it is a handoff session.</summary>
|
||||||
|
public string? PredecessorSessionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>brief.md the session started from; non-null exactly for handoff sessions.</summary>
|
||||||
|
public string? BriefPath { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The optional local-time window restricting when PowerUse applies
|
||||||
|
/// (<see cref="AppSettingsEntity.PowerUseWindowStart"/> / <c>End</c>, both <c>HH:mm</c>).
|
||||||
|
/// Pure on purpose — the caller supplies "now", so the window logic is testable without a clock.
|
||||||
|
/// </summary>
|
||||||
|
public static class PowerUseWindow
|
||||||
|
{
|
||||||
|
/// <summary>Parses an <c>HH:mm</c> string; anything else (including null/blank) is null.</summary>
|
||||||
|
public static TimeOnly? Parse(string? value)
|
||||||
|
=> TimeOnly.TryParseExact(value?.Trim(), "HH:mm", out var t) ? t : null;
|
||||||
|
|
||||||
|
/// <summary>Round-trips a stored/typed value through <see cref="Parse"/>, so an unparseable
|
||||||
|
/// string is stored as null instead of sitting in the column pretending to be a window.</summary>
|
||||||
|
public static string? Normalize(string? value) => Parse(value)?.ToString("HH:mm");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when <paramref name="now"/> falls inside the window. Half-open: start is included,
|
||||||
|
/// end is not. Either bound missing = no window at all = always true (the pre-window
|
||||||
|
/// behaviour). A window whose end is before its start wraps over midnight.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Contains(TimeOnly now, TimeOnly? start, TimeOnly? end)
|
||||||
|
{
|
||||||
|
if (start is not { } from || end is not { } to) return true;
|
||||||
|
return from < to ? now >= from && now < to : now >= from || now < to;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Convenience overload for the two stored strings and a wall clock.</summary>
|
||||||
|
public static bool IsOpen(DateTime now, string? start, string? end)
|
||||||
|
=> Contains(TimeOnly.FromDateTime(now), Parse(start), Parse(end));
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One declared "task must run after task" edge. A task can carry n of them; the queue picker
|
||||||
|
/// claims it only once every predecessor reached <see cref="TaskStatus.Done"/>.
|
||||||
|
/// Deliberately separate from <see cref="TaskEntity.BlockedByTaskId"/>, the planning chain's own
|
||||||
|
/// internal single link — see ClaudeDo.Worker/CLAUDE.md → Status Model.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TaskDependencyEntity
|
||||||
|
{
|
||||||
|
public required string TaskId { get; init; }
|
||||||
|
public required string DependsOnTaskId { get; init; }
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace ClaudeDo.Data.Models;
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
public enum TaskStatus
|
public enum TaskStatus
|
||||||
@@ -33,12 +35,17 @@ public sealed class TaskEntity
|
|||||||
public PlanningPhase PlanningPhase { get; set; } = PlanningPhase.None;
|
public PlanningPhase PlanningPhase { get; set; } = PlanningPhase.None;
|
||||||
public string? BlockedByTaskId { get; set; }
|
public string? BlockedByTaskId { get; set; }
|
||||||
|
|
||||||
// A user/MCP-declared predecessor, distinct from BlockedByTaskId (the planning chain's own
|
// User/MCP-declared predecessors, distinct from BlockedByTaskId (the planning chain's own
|
||||||
// internal link): the picker also skips a Queued task while this is set and the referenced
|
// internal link): the picker skips a Queued task until EVERY predecessor reached Done.
|
||||||
// task's Status isn't Done. Unlike the chain, a Failed/Cancelled dependency does NOT cascade
|
// Unlike the chain, a Failed/Cancelled dependency does NOT cascade or auto-resolve -- the
|
||||||
// or auto-resolve -- the dependent just stays blocked and reports why (see TaskStateService.
|
// dependent just stays blocked and reports why (see TaskStateService.SetDependsOnAsync and
|
||||||
// SetDependsOnAsync and QueuePicker).
|
// QueuePicker). Only loaded where a read path Includes it; the picker gates in raw SQL and
|
||||||
public string? DependsOnTaskId { get; set; }
|
// never relies on this collection.
|
||||||
|
public ICollection<TaskDependencyEntity> Dependencies { get; set; } = new List<TaskDependencyEntity>();
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public IReadOnlyList<string> DependsOnTaskIds =>
|
||||||
|
Dependencies.Select(d => d.DependsOnTaskId).ToList();
|
||||||
public DateTime? ScheduledFor { get; set; }
|
public DateTime? ScheduledFor { get; set; }
|
||||||
public string? Result { get; set; }
|
public string? Result { get; set; }
|
||||||
public string? ReviewFeedback { get; set; }
|
public string? ReviewFeedback { get; set; }
|
||||||
@@ -71,6 +78,10 @@ public sealed class TaskEntity
|
|||||||
// list handler) and the Claude affordances are hidden; a hand-driven ConPTY session is still
|
// list handler) and the Claude affordances are hidden; a hand-driven ConPTY session is still
|
||||||
// allowed. New tasks in a manual list default to true.
|
// allowed. New tasks in a manual list default to true.
|
||||||
public bool IsManual { get; set; }
|
public bool IsManual { get; set; }
|
||||||
|
// Set on verification/audit/research tasks whose correct outcome is an empty diff (e.g. "build
|
||||||
|
// and test, don't change code"). Lets the merge-helper session tell that apart from a task that
|
||||||
|
// silently failed to commit -- both look identical to preview_merge_set otherwise.
|
||||||
|
public bool ExpectsNoDiff { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
public string? SessionSkills { get; set; }
|
public string? SessionSkills { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace ClaudeDo.Data;
|
namespace ClaudeDo.Data;
|
||||||
|
|
||||||
public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport, ImprovementChild, Refine, MergeHelperTriage, MergeHelperWait, MergeHelperMerge, MergeHelperInitial, MergeHelperHandoff }
|
public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport, ImprovementChild, Refine, MergeHelperTriage, MergeHelperWait, MergeHelperMerge, MergeHelperInitial, MergeHelperHandoff, PowerUseAddendum }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How a prompt kind's on-disk override (if any) relates to the bundled default.
|
/// How a prompt kind's on-disk override (if any) relates to the bundled default.
|
||||||
@@ -44,6 +44,7 @@ public static class PromptFiles
|
|||||||
PromptKind.MergeHelperMerge => "merge-helper-merge.md",
|
PromptKind.MergeHelperMerge => "merge-helper-merge.md",
|
||||||
PromptKind.MergeHelperInitial => "merge-helper-initial.md",
|
PromptKind.MergeHelperInitial => "merge-helper-initial.md",
|
||||||
PromptKind.MergeHelperHandoff => "merge-helper-handoff.md",
|
PromptKind.MergeHelperHandoff => "merge-helper-handoff.md",
|
||||||
|
PromptKind.PowerUseAddendum => "power-use.md",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,6 +222,7 @@ public static class PromptFiles
|
|||||||
PromptKind.MergeHelperMerge => MergeHelperMergeDefault,
|
PromptKind.MergeHelperMerge => MergeHelperMergeDefault,
|
||||||
PromptKind.MergeHelperInitial => MergeHelperInitialDefault,
|
PromptKind.MergeHelperInitial => MergeHelperInitialDefault,
|
||||||
PromptKind.MergeHelperHandoff => MergeHelperHandoffDefault,
|
PromptKind.MergeHelperHandoff => MergeHelperHandoffDefault,
|
||||||
|
PromptKind.PowerUseAddendum => PowerUseAddendumDefault,
|
||||||
_ => ""
|
_ => ""
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,6 +450,15 @@ public static class PromptFiles
|
|||||||
|
|
||||||
Cancel nothing without an explicit answer.
|
Cancel nothing without an explicit answer.
|
||||||
|
|
||||||
|
## Phase 1.5 — Audit dependencies
|
||||||
|
For every surviving task that carries a dependsOnTaskId, decide whether the link is real or artificial before you hand off. Real: task B genuinely needs something task A produces — a symbol/type B's code references, a changed signature, a migration that must land before repository code assumes it. Artificial: the tasks just got numbered in sequence by the plan but touch disjoint files for unrelated reasons. Task descriptions usually name their target files explicitly — use that to check for real file/symbol overlap instead of trusting the order.
|
||||||
|
|
||||||
|
Resolve every artificial link with update_task(taskId, dependsOnTaskId="") — a chain numbered 1→2→3→... is the default shape to suspect, not the default shape to trust.
|
||||||
|
|
||||||
|
Group what's left into waves: batches that can run in parallel, where a later wave depends only on tasks in an earlier one. Report this as a wave plan (e.g. "[3] → [4,5,6] → [7]"), not a flat list — the Wait/Merge sessions after you need the batching, not just which ids survived.
|
||||||
|
|
||||||
|
Then check the wave count against the handoff cap. This list-handler chain allows a limited number of handoffs (wait, merge, wait_final, merge_final) — by default 4, i.e. exactly two merge rounds, but it is configurable and may be higher for this run. Assume two rounds unless the kickoff tells you otherwise. If your wave plan needs more rounds than that to clear, say so explicitly when you hand off — name which waves won't fit — so the Merge session ends by telling the user "the rest needs manual approval in the UI" instead of silently stranding mid-chain.
|
||||||
|
|
||||||
## Phase 2 — Enhance for execution
|
## Phase 2 — Enhance for execution
|
||||||
Before touching any task, check the list's verify command ONCE for this whole run (not per task): call get_list_config. If verifyCommand is already set, leave it alone — never overwrite an existing one — and move straight to sharpening tasks below.
|
Before touching any task, check the list's verify command ONCE for this whole run (not per task): call get_list_config. If verifyCommand is already set, leave it alone — never overwrite an existing one — and move straight to sharpening tasks below.
|
||||||
|
|
||||||
@@ -471,7 +482,7 @@ public static class PromptFiles
|
|||||||
Once every surviving task is enhanced, print your triage summary — one line per task from the brief:
|
Once every surviving task is enhanced, print your triage summary — one line per task from the brief:
|
||||||
title — dedupe action (kept / merged into X / cancelled as duplicate of X) — enhanced (yes/no).
|
title — dedupe action (kept / merged into X / cancelled as duplicate of X) — enhanced (yes/no).
|
||||||
|
|
||||||
Then call handoff_list_handler(taskId, survivingTaskIds, nextPhase: "wait"). That opens a fresh session to carry out the wait/merge phases with just that list, without dragging along this session's dedupe/rewrite context. Say a short goodbye line, then stop — do not continue into phase 3 yourself.
|
Then print the wave plan from Phase 1.5, including any "won't fit in two rounds" note, and call handoff_list_handler(taskId, survivingTaskIds, nextPhase: "wait"). That opens a fresh session to carry out the wait/merge phases with just that list, without dragging along this session's dedupe/rewrite context. Say a short goodbye line, then stop — do not continue into phase 3 yourself.
|
||||||
""";
|
""";
|
||||||
|
|
||||||
private const string MergeHelperWaitDefault = """
|
private const string MergeHelperWaitDefault = """
|
||||||
@@ -505,14 +516,14 @@ public static class PromptFiles
|
|||||||
|
|
||||||
One task at a time. Default to the order the brief lists them. If the overlap check shows two or more tasks touching the same file, tell the user which tasks collide and merge those in an order you can justify (e.g. the one making the smaller change first) — deviate from brief order only with that stated reason.
|
One task at a time. Default to the order the brief lists them. If the overlap check shows two or more tasks touching the same file, tell the user which tasks collide and merge those in an order you can justify (e.g. the one making the smaller change first) — deviate from brief order only with that stated reason.
|
||||||
|
|
||||||
1. Never read a task's diff yourself. Instead start a sonnet subagent (Task tool) per task to inspect get_task_diff and sanity-check it against the task's title and description; only its short verdict (clean / risky + reason) comes back into your own context. If preview_merge_set flagged this task in an overlap, tell the subagent to also skim the diff of the other task(s) sharing that file.
|
1. Check this task's changedFileCount from preview_merge_set first. Below 5 changed files, read get_task_diff yourself and sanity-check it against the task's title and description — a subagent hop is wasted context for a diff that small. At 5 or more, delegate instead: start a sonnet subagent (Task tool) to inspect get_task_diff and sanity-check it, so only its short verdict (clean / risky + reason) comes back into your own context — the whole point of never reading a large diff yourself. If preview_merge_set flagged this task in an overlap, also skim (or have the subagent skim) the diff of the other task(s) sharing that file.
|
||||||
2. Before approving, cross-check changedFileCount from preview_merge_set (or the length of get_task_diff's files list — the diff tool has no changedFileCount field). 0 changed files means an empty branch, not a clean success — do not approve it; treat it like a risky diff instead.
|
2. Before approving, cross-check changedFileCount from preview_merge_set (or the length of get_task_diff's files list — the diff tool has no changedFileCount field). 0 changed files means an empty branch, not a clean success — do not approve it; treat it like a risky diff instead. Exception: if preview_merge_set's expectsNoDiff is true for this task (a verification/audit/research task deliberately expected to change nothing), 0 changed files is the correct outcome — read the task's Result to confirm it reports what was checked and that it passed, then approve normally.
|
||||||
3. If the subagent's verdict is risky, or the change looks wrong or incomplete, STOP and ask the user before merging — offer reject_rerun (with feedback) or skip.
|
3. If the subagent's verdict is risky, or the change looks wrong or incomplete, STOP and ask the user before merging — offer reject_rerun (with feedback) or skip.
|
||||||
4. Otherwise merge with review_task(taskId, decision="approve", leaveConflictsInTree=true, skipVerify=true). skipVerify defers the list's verify command to ONE verify_merges call after the last merge (below) instead of paying it per merge.
|
4. Otherwise merge with review_task(taskId, decision="approve", leaveConflictsInTree=true, skipVerify=true). skipVerify defers the list's verify command to ONE verify_merges call after the last merge (below) instead of paying it per merge.
|
||||||
- Clean merge → mergeStatus "merged_verify_pending" (expected: the task deliberately stays WaitingForReview until verify_merges passes) or "merged" (list has no verify command; the task is Done). Either way, move on.
|
- Clean merge → mergeStatus "merged_verify_pending" (expected: the task deliberately stays WaitingForReview until verify_merges passes) or "merged" (list has no verify command; the task is Done). Either way, move on.
|
||||||
- Conflict (markers left in the working tree, repoPath returned) → resolve it below.
|
- Conflict (markers left in the working tree, repoPath returned) → resolve it below.
|
||||||
|
|
||||||
After each merge, main has moved — a clean preview_merge_set result from before this merge is now stale for the remaining tasks. If you are unsure whether an earlier overlap warning still matters, call preview_merge for the next task again before merging it.
|
After each merge, main has moved — a clean preview_merge_set result from before this merge is now stale for the remaining tasks. If you are unsure whether an earlier overlap warning still matters, call preview_merge for the next task again before merging it (default runVerify=false — this is just a conflict recheck, not a build; the verify command still runs once via verify_merges after the last merge).
|
||||||
|
|
||||||
Every branch in this run forked from the same base, so conflicts between them are the NORMAL case, not a failure. Resolve them and keep going; do not abandon the run because a merge conflicted.
|
Every branch in this run forked from the same base, so conflicts between them are the NORMAL case, not a failure. Resolve them and keep going; do not abandon the run because a merge conflicted.
|
||||||
|
|
||||||
@@ -539,6 +550,30 @@ public static class PromptFiles
|
|||||||
|
|
||||||
Allow at most ONE rerun per task in this session.
|
Allow at most ONE rerun per task in this session.
|
||||||
|
|
||||||
|
## Keep watching the queue
|
||||||
|
|
||||||
|
Do not assume the tasks you were handed are the only ones that matter. A prior Wait session
|
||||||
|
can already have finished before every one of its tasks actually reached a terminal status,
|
||||||
|
or new tasks can still be running when you start — merging only what happened to be ready the
|
||||||
|
moment you looked in and then stopping is how work silently falls through the cracks.
|
||||||
|
|
||||||
|
Before you go to Handoff / end, call get_queue_state and check whether any task id from your
|
||||||
|
brief still shows up in activeSlots or waitingTaskIds. If none do, your list is clear — go to
|
||||||
|
Handoff / end now.
|
||||||
|
|
||||||
|
If some do, call wait_for_task_change with exactly those still-busy task ids,
|
||||||
|
timeoutSeconds=900, treatWaitingForChildrenAsBusy=true. When it reports a change, go back to
|
||||||
|
Phase 4 and merge/review whatever it now says is WaitingForReview, then repeat this check.
|
||||||
|
|
||||||
|
This is NOT a `sleep`-and-poll loop — never write your own retry/sleep loop around get_task or
|
||||||
|
get_queue_state. wait_for_task_change already blocks server-side and reports progress so it
|
||||||
|
survives your own client's idle-silence abort; that is the only waiting mechanism allowed here.
|
||||||
|
|
||||||
|
Hard cap: stop after at most THREE consecutive wait_for_task_change calls made this way (three
|
||||||
|
900s timeouts ≈ 45 minutes total), even if tasks are still busy. When you stop — because the
|
||||||
|
queue cleared or because you hit the cap — report whatever is still Queued/Running in the
|
||||||
|
summary below and proceed to Handoff / end exactly as if this section didn't exist.
|
||||||
|
|
||||||
## Handoff / end
|
## Handoff / end
|
||||||
|
|
||||||
- If you started any reruns: call handoff_list_handler(taskId, <the rerun task ids>, nextPhase: "wait_final"), then stop your turn immediately — do not merge anything yet that you just restarted. Do NOT submit this run for review yet — the chain isn't finished.
|
- If you started any reruns: call handoff_list_handler(taskId, <the rerun task ids>, nextPhase: "wait_final"), then stop your turn immediately — do not merge anything yet that you just restarted. Do NOT submit this run for review yet — the chain isn't finished.
|
||||||
@@ -584,24 +619,99 @@ public static class PromptFiles
|
|||||||
|
|
||||||
Start with phase 3 (run), continuing through review/merge and the summary as your
|
Start with phase 3 (run), continuing through review/merge and the summary as your
|
||||||
instructions describe.
|
instructions describe.
|
||||||
{finalNote}
|
{roundInfo}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Appended to the triage/wait/merge system prompt when a run is in PowerUse (global switch AND
|
||||||
|
// the list's own opt-in). Deliberately an addendum rather than a second set of prompts: the
|
||||||
|
// shared 95% stays in one place, and a normal run's prompt is byte-identical to what it was.
|
||||||
|
// It therefore has to read as an OVERRIDE of the rules above it, not as standalone instructions.
|
||||||
|
private const string PowerUseAddendumDefault = """
|
||||||
|
# PowerUse mode — these rules supersede the ones above
|
||||||
|
|
||||||
|
PowerUse is ON for this run. Everything above was written for a session with a user watching it. That assumption no longer holds, and where the two conflict, this section wins.
|
||||||
|
|
||||||
|
## No user exists
|
||||||
|
|
||||||
|
Nobody is watching and nobody can answer a question. Every instruction above that says to ask the user, confirm with the user, or stop and let the user decide is replaced by: decide it yourself, act, and record the decision in your summary. A question asked here is a hung session and a wasted run.
|
||||||
|
|
||||||
|
This does not lower the bar for what you may do — it removes escalation as an option, so the decisions you make must be ones you can defend in writing afterwards. Where a rule above offered "ask the user" as the safe path, the safe path is now the conservative action plus a written record, never the bold one.
|
||||||
|
|
||||||
|
Specifically:
|
||||||
|
- **Duplicate pairs (Triage Phase 1):** decide them. Clear duplicate — same goal, or one fully contained in the other — fold the loser's unique content into the survivor and cancel the loser. Genuinely different intent that merely touches the same area — keep both and chain them with dependsOnTaskId so they cannot run against the same files at once.
|
||||||
|
- **A task too vague to sharpen (Triage Phase 2):** cancel it with a recorded reason rather than guessing a specification for someone else's half-sentence. Guessing burns a whole slot on work nobody asked for.
|
||||||
|
- **Wave plan vs. handoff cap (Triage Phase 1.5):** do not assume two rounds, and never trim the wave plan to fit a cap or defer waves to "manual approval in the UI" — nobody will be there. Call get_handoff_status with your own handler task id to read this run's real cap, then plan the waves the work actually needs and hand off the first one.
|
||||||
|
- **A task that bundles several independent features (Triage Phase 2):** split it yourself — create the pieces, chain them with dependsOnTaskId where one builds on another, cancel or narrow the original, and record what you did.
|
||||||
|
- **Verify command not set (Triage Phase 2):** determine it, run it once via Bash to prove it works, then set it. Never write a command you have not actually run.
|
||||||
|
- **A risky or wrong-looking diff (Merge Phase 4):** if it contradicts the task's own acceptance criteria, call review_task(taskId, decision="reject_rerun", feedback=...) with concrete feedback naming what was missing. If it merely looks unusual but meets the criteria, merge it and say why. If it looks actively destructive — deletes code no task asked to delete, drops a migration, rewrites unrelated files — leave it unmerged and record it as HELD FOR REVIEW. Never merge something you would describe as destructive.
|
||||||
|
- **A conflict you cannot resolve with confidence (Merge Phase 4):** call abort_merge(taskId) and record it as HELD FOR REVIEW. Aborting is always correct over guessing: the task keeps its status, and a rerun against the new main usually removes the conflict entirely.
|
||||||
|
|
||||||
|
## Conflicts are the normal case
|
||||||
|
|
||||||
|
Never end your turn with a conflicted merge left in the working tree. A half-merged tree makes the repo unbuildable for every session and every task after you, and any verify that runs against it reports a false red.
|
||||||
|
|
||||||
|
Prefer get_merge_conflicts + resolve_conflict_hunk over reading marker text by hand — for a large conflict that is the only tractable route; a 200k-character marker dump is not something to read linearly.
|
||||||
|
|
||||||
|
The common collision shape in a parallel run is one task MOVING code out of a file while another task ADDS to it. The resolution is almost always to keep the move and re-apply the addition in the file the code moved to — not to pick a side. Picking "ours" silently deletes the other task's work.
|
||||||
|
|
||||||
|
When ordering merges that overlap on a file, merge the task that moves or deletes code BEFORE the task that only adds to it. A move landing second turns every addition into a conflict.
|
||||||
|
|
||||||
|
## Rerun budget
|
||||||
|
|
||||||
|
At most TWO reruns per task across the whole run, replacing the single-rerun limit above. A task that has already been rerun twice is recorded as HELD FOR REVIEW instead of getting a third attempt — three failures on the same task is a specification problem, not bad luck, and further attempts starve the rest of the run.
|
||||||
|
|
||||||
|
## Harvest — so the run does not starve
|
||||||
|
|
||||||
|
This applies to the Merge phase only, after its merges and reruns are done.
|
||||||
|
|
||||||
|
If you have fewer than five tasks to hand to the next round, harvest new work for this list's repo before handing off. Only for the list named in the brief — never another list or repo.
|
||||||
|
|
||||||
|
Sources, in this order; prefer one you have not exhausted yet:
|
||||||
|
1. **Verify/build output** — real compiler warnings. One task per warning group.
|
||||||
|
2. **`ponytail:` comments** — deliberate shortcuts with a named ceiling and upgrade path. Take the ones whose upgrade is small.
|
||||||
|
3. **TODO / HACK / FIXME** — only the concretely formulated ones.
|
||||||
|
4. **Unfinished plan slices** — `docs/superpowers/plans/*.md` whose last slices never landed. Check against `git log` before believing a slice is open. One task per slice, naming the plan path and slice number.
|
||||||
|
5. **Test gaps at bug hotspots** — `git log --grep="^fix"` over recent commits; files with repeated fixes and no test.
|
||||||
|
6. **Over-engineering** — the ponytail-audit skill against individual subfolders, never the whole repo at once. Deletion candidates.
|
||||||
|
7. **Feature work** — improvements that follow clearly from what the code already does and from the plans and specs under `docs/`. Ground each one in something you actually read; a feature you cannot point at a file for is a guess, not a proposal.
|
||||||
|
|
||||||
|
Rules for every harvested task, without exception:
|
||||||
|
- Write it the way the Triage phase would: concrete acceptance criteria, real file paths found with Read/Grep/Glob, and what is out of scope. A vague harvested task wastes a whole slot.
|
||||||
|
- Keep it narrow — one concern, ideally under five changed files. Narrow tasks merge in parallel; broad ones collide with everything.
|
||||||
|
- Do not hand two new tasks to the same round if they touch the same file. Stagger them across rounds, or chain them with dependsOnTaskId.
|
||||||
|
- Tag every harvested task `createdBy: "power-use"` so a later review can tell your proposals from the ones a human asked for.
|
||||||
|
- A task that only verifies, audits or researches and is not meant to change code must be created with `expectsNoDiff: true`, or the next Merge session will read its empty diff as a failure.
|
||||||
|
|
||||||
|
## Verify red is the one thing you must not paper over
|
||||||
|
|
||||||
|
If verify_merges returns verify_failed, the target branch is now RED. Do not try to fix it inside this session. Create ONE task with add_task(queueImmediately: true, model: "sonnet") titled `fix: verify red after this round`, put the failing output tail in its description, and hand that task to the next round as its ONLY entry. Merge nothing further until it has landed.
|
||||||
|
|
||||||
|
## The summary is the only record
|
||||||
|
|
||||||
|
Nobody saw you work, so anything not written down did not happen. In addition to the per-task lines the Merge phase already asks for, your summary must carry these sections. An empty section is a valid answer; an omitted one is not:
|
||||||
|
|
||||||
|
- **HELD FOR REVIEW** — every task you did not merge, with the reason.
|
||||||
|
- **Conflicts resolved** — per conflict: the file, which two tasks collided, and which way you resolved it. Someone must be able to find your resolution without re-deriving it.
|
||||||
|
- **Harvested** — every task you created, grouped by source.
|
||||||
|
- **Round state** — which round this was and what you handed to the next one.
|
||||||
""";
|
""";
|
||||||
|
|
||||||
private const string WeeklyReportDefault = """
|
private const string WeeklyReportDefault = """
|
||||||
You are generating a concise weekly standup report for a software developer,
|
You are writing memory-jogger notes for a developer's weekly standup,
|
||||||
covering {start} to {end}.
|
covering {start} to {end}. They read them aloud in their own words, so write
|
||||||
|
keywords, not sentences.
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- Write the ENTIRE report in German.
|
- Write in German.
|
||||||
- Group by day. One "## <Wochentag>, <dd.MM.yyyy>" section per day that has
|
- Group by day. One "## <Wochentag>, <dd.MM.>" heading per day that has
|
||||||
activity (German weekday names). Omit days with no activity.
|
activity (German weekday names). Omit days with no activity.
|
||||||
- Within each day: 3–5 first-person, past-tense bullets ("- Habe X umgesetzt",
|
- Per day: 1–3 bullets, each at most ~6 words, noun style
|
||||||
"- Y behoben"). Merge related small work into one bullet.
|
("- LagerApp: Etikettendruck gefixt", "- ClaudeDo Listen-Ordner"). No "Habe …".
|
||||||
- Drop trivia: typo fixes, pure exploration, false starts, tooling/log noise.
|
- One bullet per topic — merge everything about the same feature/bug.
|
||||||
- Blend the developer's own notes and the derived activity into ONE deduplicated
|
- Drop trivia: typo fixes, exploration, false starts, tooling/log noise.
|
||||||
bullet list per day. The notes are authoritative — never omit or contradict them.
|
- Merge the developer's notes with the derived activity, deduplicated.
|
||||||
- Name the project/repo when it adds clarity.
|
The notes are authoritative — never omit or contradict them.
|
||||||
- Output ONLY the dated sections. No preamble, no intro, no closing remarks.
|
- Output ONLY the day sections. No preamble, no summary, no closing remarks.
|
||||||
|
|
||||||
Two sections follow below: an activity log derived from Claude session history,
|
Two sections follow below: an activity log derived from Claude session history,
|
||||||
and the developer's own notes. Base the report on both; the notes are
|
and the developer's own notes. Base the report on both; the notes are
|
||||||
|
|||||||
@@ -93,6 +93,16 @@ public sealed class AppSettingsRepository
|
|||||||
row.UsageThrottleSevenDaySoftPct = Math.Clamp(updated.UsageThrottleSevenDaySoftPct, 0, 100);
|
row.UsageThrottleSevenDaySoftPct = Math.Clamp(updated.UsageThrottleSevenDaySoftPct, 0, 100);
|
||||||
row.UsageThrottleSevenDayHardPct = Math.Clamp(updated.UsageThrottleSevenDayHardPct, 0, 100);
|
row.UsageThrottleSevenDayHardPct = Math.Clamp(updated.UsageThrottleSevenDayHardPct, 0, 100);
|
||||||
row.AutoContinueOnUsageLimit = updated.AutoContinueOnUsageLimit;
|
row.AutoContinueOnUsageLimit = updated.AutoContinueOnUsageLimit;
|
||||||
|
// ModelPresets / PowerUse* / ListHandlerMaxHandoffs were missing from this copy block, so
|
||||||
|
// every write of them was silently dropped (the row keeps whatever it already had). This
|
||||||
|
// block is the single write path for the singleton row — a field absent here does not
|
||||||
|
// persist, no matter how correctly the hub fills the entity.
|
||||||
|
row.ModelPresets = string.IsNullOrWhiteSpace(updated.ModelPresets) ? row.ModelPresets : updated.ModelPresets;
|
||||||
|
row.PowerUseEnabled = updated.PowerUseEnabled;
|
||||||
|
row.PowerUseWindowStart = PowerUseWindow.Normalize(updated.PowerUseWindowStart);
|
||||||
|
row.PowerUseWindowEnd = PowerUseWindow.Normalize(updated.PowerUseWindowEnd);
|
||||||
|
row.PowerUseHandoffPct = Math.Clamp(updated.PowerUseHandoffPct, 50, 99);
|
||||||
|
row.ListHandlerMaxHandoffs = Math.Max(1, updated.ListHandlerMaxHandoffs);
|
||||||
|
|
||||||
await _context.SaveChangesAsync(ct);
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ public sealed class DailyNoteRepository
|
|||||||
.OrderBy(n => n.Date).ThenBy(n => n.SortOrder)
|
.OrderBy(n => n.Date).ThenBy(n => n.SortOrder)
|
||||||
.ToListAsync(ct);
|
.ToListAsync(ct);
|
||||||
|
|
||||||
public async Task<DailyNoteEntity> AddAsync(DateOnly day, string text, CancellationToken ct = default)
|
/// <summary>Notes an agent wrote on <paramref name="day"/> — the number the MCP cap counts against.</summary>
|
||||||
|
public async Task<int> CountAgentNotesAsync(DateOnly day, CancellationToken ct = default) =>
|
||||||
|
await _context.DailyNotes.AsNoTracking()
|
||||||
|
.CountAsync(n => n.Date == day && n.FromAgent, ct);
|
||||||
|
|
||||||
|
public async Task<DailyNoteEntity> AddAsync(
|
||||||
|
DateOnly day, string text, bool fromAgent = false, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var nextOrder = await _context.DailyNotes
|
var nextOrder = await _context.DailyNotes
|
||||||
.Where(n => n.Date == day)
|
.Where(n => n.Date == day)
|
||||||
@@ -34,6 +40,7 @@ public sealed class DailyNoteRepository
|
|||||||
Date = day,
|
Date = day,
|
||||||
Text = text,
|
Text = text,
|
||||||
SortOrder = nextOrder + 1,
|
SortOrder = nextOrder + 1,
|
||||||
|
FromAgent = fromAgent,
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
};
|
};
|
||||||
_context.DailyNotes.Add(note);
|
_context.DailyNotes.Add(note);
|
||||||
|
|||||||
@@ -17,10 +17,20 @@ public sealed class ListRepository
|
|||||||
public async Task AddAsync(ListEntity entity, CancellationToken ct = default)
|
public async Task AddAsync(ListEntity entity, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
NormalizeWorkingDir(entity);
|
NormalizeWorkingDir(entity);
|
||||||
|
if (entity.SortOrder == 0) entity.SortOrder = await NextSortOrderAsync(ct);
|
||||||
_context.Lists.Add(entity);
|
_context.Lists.Add(entity);
|
||||||
await _context.SaveChangesAsync(ct);
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>One past the highest <c>sort_order</c> in use — across <b>both</b> tables, because
|
||||||
|
/// the sidebar interleaves folders and top-level lists in a single order sequence.</summary>
|
||||||
|
private async Task<int> NextSortOrderAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var maxList = await _context.Lists.MaxAsync(l => (int?)l.SortOrder, ct) ?? -1;
|
||||||
|
var maxFolder = await _context.ListFolders.MaxAsync(f => (int?)f.SortOrder, ct) ?? -1;
|
||||||
|
return Math.Max(maxList, maxFolder) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateAsync(ListEntity entity, CancellationToken ct = default)
|
public async Task UpdateAsync(ListEntity entity, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
NormalizeWorkingDir(entity);
|
NormalizeWorkingDir(entity);
|
||||||
@@ -56,18 +66,92 @@ public sealed class ListRepository
|
|||||||
return await _context.Lists.OrderBy(l => l.SortOrder).ThenBy(l => l.CreatedAt).ToListAsync(ct);
|
return await _context.Lists.OrderBy(l => l.SortOrder).ThenBy(l => l.CreatedAt).ToListAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReorderAsync(IReadOnlyList<string> orderedListIds, CancellationToken ct = default)
|
/// <summary>
|
||||||
|
/// Writes the sidebar's visual order as one sequence spanning both tables: folders and lists
|
||||||
|
/// share the <c>sort_order</c> number space, which is what lets a top-level list sit between
|
||||||
|
/// two folders. A folder's own children follow it in the sequence, so ordering folders by
|
||||||
|
/// <c>sort_order</c> and lists by <c>sort_order</c> independently still reproduces the sidebar.
|
||||||
|
/// </summary>
|
||||||
|
public async Task ReorderSidebarAsync(IReadOnlyList<(string Id, bool IsFolder)> ordered, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var idSet = orderedListIds.ToHashSet();
|
var listIds = ordered.Where(o => !o.IsFolder).Select(o => o.Id).ToHashSet();
|
||||||
var entities = await _context.Lists.Where(l => idSet.Contains(l.Id)).ToListAsync(ct);
|
var folderIds = ordered.Where(o => o.IsFolder).Select(o => o.Id).ToHashSet();
|
||||||
for (int i = 0; i < orderedListIds.Count; i++)
|
var lists = await _context.Lists.Where(l => listIds.Contains(l.Id)).ToListAsync(ct);
|
||||||
|
var folders = await _context.ListFolders.Where(f => folderIds.Contains(f.Id)).ToListAsync(ct);
|
||||||
|
|
||||||
|
for (int i = 0; i < ordered.Count; i++)
|
||||||
{
|
{
|
||||||
var e = entities.FirstOrDefault(x => x.Id == orderedListIds[i]);
|
var (id, isFolder) = ordered[i];
|
||||||
if (e is not null) e.SortOrder = i;
|
if (isFolder)
|
||||||
|
{
|
||||||
|
if (folders.FirstOrDefault(f => f.Id == id) is { } folder) folder.SortOrder = i;
|
||||||
|
}
|
||||||
|
else if (lists.FirstOrDefault(l => l.Id == id) is { } list) list.SortOrder = i;
|
||||||
}
|
}
|
||||||
await _context.SaveChangesAsync(ct);
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Sidebar folders (list_folders) ----
|
||||||
|
// Folders live here rather than in their own repository because they exist only as a grouping
|
||||||
|
// of these lists — every caller that touches one already holds a ListRepository.
|
||||||
|
|
||||||
|
public async Task<List<ListFolderEntity>> GetFoldersAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await _context.ListFolders
|
||||||
|
.OrderBy(f => f.SortOrder).ThenBy(f => f.CreatedAt)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddFolderAsync(ListFolderEntity folder, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
folder.SortOrder = await NextSortOrderAsync(ct);
|
||||||
|
_context.ListFolders.Add(folder);
|
||||||
|
await _context.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Patches just the editable fields — a whole-entity Update would need
|
||||||
|
/// <c>CreatedAt</c>/<c>SortOrder</c>/<c>IsCollapsed</c> carried along and silently reset
|
||||||
|
/// whichever the caller forgot (see the SetConfigAsync note in CLAUDE.md).</summary>
|
||||||
|
public async Task RenameFolderAsync(string folderId, string name, string? icon, string? color, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.ListFolders
|
||||||
|
.Where(f => f.Id == folderId)
|
||||||
|
.ExecuteUpdateAsync(s => s
|
||||||
|
.SetProperty(f => f.Name, name)
|
||||||
|
.SetProperty(f => f.Icon, icon)
|
||||||
|
.SetProperty(f => f.Color, color), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Deletes the folder only — its lists survive and fall back to the top level via
|
||||||
|
/// the ON DELETE SET NULL on <c>lists.folder_id</c>.</summary>
|
||||||
|
public async Task DeleteFolderAsync(string folderId, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.ListFolders.Where(f => f.Id == folderId).ExecuteDeleteAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetFolderCollapsedAsync(string folderId, bool collapsed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.ListFolders
|
||||||
|
.Where(f => f.Id == folderId)
|
||||||
|
.ExecuteUpdateAsync(s => s.SetProperty(f => f.IsCollapsed, collapsed), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Moves a folder under <paramref name="parentFolderId"/>, or to the top level when null.</summary>
|
||||||
|
public async Task SetFolderParentAsync(string folderId, string? parentFolderId, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.ListFolders
|
||||||
|
.Where(f => f.Id == folderId)
|
||||||
|
.ExecuteUpdateAsync(s => s.SetProperty(f => f.ParentFolderId, parentFolderId), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Moves a list into <paramref name="folderId"/>, or back to the top level when null.</summary>
|
||||||
|
public async Task SetListFolderAsync(string listId, string? folderId, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _context.Lists
|
||||||
|
.Where(l => l.Id == listId)
|
||||||
|
.ExecuteUpdateAsync(s => s.SetProperty(l => l.FolderId, folderId), ct);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ListConfigEntity?> GetConfigAsync(string listId, CancellationToken ct = default)
|
public async Task<ListConfigEntity?> GetConfigAsync(string listId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == listId, ct);
|
return await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == listId, ct);
|
||||||
@@ -90,6 +174,7 @@ public sealed class ListRepository
|
|||||||
existing.VerifyCommand = config.VerifyCommand;
|
existing.VerifyCommand = config.VerifyCommand;
|
||||||
existing.PermissionMode = config.PermissionMode;
|
existing.PermissionMode = config.PermissionMode;
|
||||||
existing.TicketProjectId = config.TicketProjectId;
|
existing.TicketProjectId = config.TicketProjectId;
|
||||||
|
existing.PowerUseAllowed = config.PowerUseAllowed;
|
||||||
}
|
}
|
||||||
await _context.SaveChangesAsync(ct);
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Repositories;
|
||||||
|
|
||||||
|
/// <summary>Reads and writes <c>mission_control_sessions</c>.</summary>
|
||||||
|
public sealed class MissionControlSessionRepository
|
||||||
|
{
|
||||||
|
private readonly ClaudeDoDbContext _context;
|
||||||
|
|
||||||
|
public MissionControlSessionRepository(ClaudeDoDbContext context) => _context = context;
|
||||||
|
|
||||||
|
/// <summary>Inserts the session unless it is already recorded — a resumed task session keeps
|
||||||
|
/// its original row and start time.</summary>
|
||||||
|
public async Task RecordAsync(MissionControlSessionEntity session, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (await _context.MissionControlSessions.AnyAsync(s => s.SessionId == session.SessionId, ct)) return;
|
||||||
|
_context.MissionControlSessions.Add(session);
|
||||||
|
await _context.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The recorded session, or null.</summary>
|
||||||
|
public async Task<MissionControlSessionEntity?> GetAsync(string sessionId, CancellationToken ct = default) =>
|
||||||
|
await _context.MissionControlSessions.AsNoTracking().FirstOrDefaultAsync(s => s.SessionId == sessionId, ct);
|
||||||
|
|
||||||
|
/// <summary>Sessions started since <paramref name="sinceUtc"/>, newest first, at most <paramref name="max"/>.</summary>
|
||||||
|
public async Task<IReadOnlyList<MissionControlSessionEntity>> ListRecentAsync(
|
||||||
|
DateTime sinceUtc, int max, CancellationToken ct = default) =>
|
||||||
|
await _context.MissionControlSessions.AsNoTracking()
|
||||||
|
.Where(s => s.StartedAt >= sinceUtc)
|
||||||
|
.OrderByDescending(s => s.StartedAt)
|
||||||
|
.Take(max)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
@@ -34,7 +34,10 @@ public sealed class TaskRepository
|
|||||||
var tracked = _context.Tasks.Local.FirstOrDefault(t => t.Id == entity.Id);
|
var tracked = _context.Tasks.Local.FirstOrDefault(t => t.Id == entity.Id);
|
||||||
if (tracked is not null && !ReferenceEquals(tracked, entity))
|
if (tracked is not null && !ReferenceEquals(tracked, entity))
|
||||||
_context.Entry(tracked).State = Microsoft.EntityFrameworkCore.EntityState.Detached;
|
_context.Entry(tracked).State = Microsoft.EntityFrameworkCore.EntityState.Detached;
|
||||||
_context.Tasks.Update(entity);
|
// Entry().State instead of Update(): Update marks the whole loaded graph Modified, which
|
||||||
|
// for the all-key task_dependencies rows means an UPDATE with nothing to set. Dependency
|
||||||
|
// edges are written only by TaskStateService.SetDependsOnAsync.
|
||||||
|
_context.Entry(entity).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||||
await _context.SaveChangesAsync(ct);
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,17 +49,20 @@ public sealed class TaskRepository
|
|||||||
|
|
||||||
public async Task<TaskEntity?> GetByIdAsync(string taskId, CancellationToken ct = default)
|
public async Task<TaskEntity?> GetByIdAsync(string taskId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await _context.Tasks.AsNoTracking().FirstOrDefaultAsync(t => t.Id == taskId, ct);
|
return await _context.Tasks.AsNoTracking().Include(t => t.Dependencies)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == taskId, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TaskEntity?> GetByNumberAsync(int number, CancellationToken ct = default)
|
public async Task<TaskEntity?> GetByNumberAsync(int number, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await _context.Tasks.AsNoTracking().FirstOrDefaultAsync(t => t.Number == number, ct);
|
return await _context.Tasks.AsNoTracking().Include(t => t.Dependencies)
|
||||||
|
.FirstOrDefaultAsync(t => t.Number == number, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TaskEntity>> GetByListIdAsync(string listId, CancellationToken ct = default)
|
public async Task<List<TaskEntity>> GetByListIdAsync(string listId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await _context.Tasks
|
return await _context.Tasks
|
||||||
|
.Include(t => t.Dependencies)
|
||||||
.Where(t => t.ListId == listId)
|
.Where(t => t.ListId == listId)
|
||||||
.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
|
.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
|
||||||
.ToListAsync(ct);
|
.ToListAsync(ct);
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace ClaudeDo.Data.Wire;
|
|||||||
|
|
||||||
public record ActiveTaskDto(string Slot, string TaskId, DateTime StartedAt);
|
public record ActiveTaskDto(string Slot, string TaskId, DateTime StartedAt);
|
||||||
|
|
||||||
public record WorkerBuildInfoDto(string? BuildSha);
|
|
||||||
|
|
||||||
public record AppSettingsDto(
|
public record AppSettingsDto(
|
||||||
string DefaultClaudeInstructions,
|
string DefaultClaudeInstructions,
|
||||||
string DefaultModel,
|
string DefaultModel,
|
||||||
@@ -38,7 +36,16 @@ public record AppSettingsDto(
|
|||||||
int UsageThrottleFiveHourHardPct = 65,
|
int UsageThrottleFiveHourHardPct = 65,
|
||||||
int UsageThrottleSevenDaySoftPct = 50,
|
int UsageThrottleSevenDaySoftPct = 50,
|
||||||
int UsageThrottleSevenDayHardPct = 65,
|
int UsageThrottleSevenDayHardPct = 65,
|
||||||
bool AutoContinueOnUsageLimit = false);
|
bool AutoContinueOnUsageLimit = false,
|
||||||
|
// PowerUse master switch — Settings → Execution. Only takes effect for a list that also set
|
||||||
|
// PowerUseAllowed.
|
||||||
|
bool PowerUseEnabled = false,
|
||||||
|
int ListHandlerMaxHandoffs = 4,
|
||||||
|
// PowerUse time window (`HH:mm`, local, null = round the clock) + the 5h-usage percentage at
|
||||||
|
// which a PowerUse handler session should hand off. Same Settings tab.
|
||||||
|
string? PowerUseWindowStart = null,
|
||||||
|
string? PowerUseWindowEnd = null,
|
||||||
|
int PowerUseHandoffPct = 95);
|
||||||
|
|
||||||
public record ModelPresetDto(string Model, string Effort, int MaxTurns);
|
public record ModelPresetDto(string Model, string Effort, int MaxTurns);
|
||||||
|
|
||||||
@@ -65,7 +72,8 @@ public record WorktreeOverviewDto(
|
|||||||
WorktreeState State,
|
WorktreeState State,
|
||||||
string? DiffStat,
|
string? DiffStat,
|
||||||
DateTime CreatedAt,
|
DateTime CreatedAt,
|
||||||
bool PathExistsOnDisk);
|
bool PathExistsOnDisk,
|
||||||
|
int TaskNumber = 0);
|
||||||
|
|
||||||
public record ForceRemoveResultDto(bool Removed, string? Reason);
|
public record ForceRemoveResultDto(bool Removed, string? Reason);
|
||||||
|
|
||||||
@@ -84,7 +92,8 @@ public record MergePreviewDto(
|
|||||||
int? VerifyExitCode = null, long? VerifyDurationMs = null, string? VerifyOutputTail = null);
|
int? VerifyExitCode = null, long? VerifyDurationMs = null, string? VerifyOutputTail = null);
|
||||||
|
|
||||||
public record MergeTargetsDto(
|
public record MergeTargetsDto(
|
||||||
string DefaultBranch, IReadOnlyList<string> LocalBranches, string DefaultCommitMessage);
|
string DefaultBranch, IReadOnlyList<string> LocalBranches, string DefaultCommitMessage,
|
||||||
|
bool TargetDirty = false);
|
||||||
|
|
||||||
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
||||||
|
|
||||||
@@ -92,16 +101,18 @@ public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<Merg
|
|||||||
|
|
||||||
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
||||||
|
|
||||||
public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false);
|
public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false, string? Icon = null);
|
||||||
|
|
||||||
// TicketProjectId is tri-state on purpose: a caller that doesn't own the field (anything but the
|
// TicketProjectId is tri-state on purpose: a caller that doesn't own the field (anything but the
|
||||||
// list-settings modal) must not be able to clear it by omission — SetConfigAsync copies the entity
|
// list-settings modal) must not be able to clear it by omission — SetConfigAsync copies the entity
|
||||||
// verbatim. Its scale: null = keep stored, <= 0 = clear the link, > 0 = set it.
|
// verbatim. Its scale: null = keep stored, <= 0 = clear the link, > 0 = set it.
|
||||||
public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? VerifyCommand = null, string? PermissionMode = null, int? TicketProjectId = null);
|
// PowerUseAllowed is nullable for the same reason: null = keep stored, false = revoke the opt-in.
|
||||||
|
// A non-nullable bool here would let any caller that omits it silently revoke PowerUse for the list.
|
||||||
|
public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? VerifyCommand = null, string? PermissionMode = null, int? TicketProjectId = null, bool? PowerUseAllowed = null);
|
||||||
|
|
||||||
public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? PermissionMode = null);
|
public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? PermissionMode = null);
|
||||||
|
|
||||||
public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? VerifyCommand = null, string? PermissionMode = null, int? TicketProjectId = null);
|
public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null, string? VerifyCommand = null, string? PermissionMode = null, int? TicketProjectId = null, bool PowerUseAllowed = false);
|
||||||
|
|
||||||
public record SeedResultDto(int Copied, int Skipped);
|
public record SeedResultDto(int Copied, int Skipped);
|
||||||
|
|
||||||
@@ -207,6 +218,10 @@ public sealed record LaunchSpec(
|
|||||||
IReadOnlyList<string> Args,
|
IReadOnlyList<string> Args,
|
||||||
IReadOnlyDictionary<string, string> Env);
|
IReadOnlyDictionary<string, string> Env);
|
||||||
|
|
||||||
|
// A handler round's still-open (Queued/Running) task, for the Mission Control "abandon run"
|
||||||
|
// action -- confirms with a count, then cancels each one before closing the pane.
|
||||||
|
public record HandlerOpenTaskDto(string Id, string Title);
|
||||||
|
|
||||||
public record TicketSettingsDto(string? ApiBaseUrl, bool TokenSet);
|
public record TicketSettingsDto(string? ApiBaseUrl, bool TokenSet);
|
||||||
public record TicketConnectionDto(bool Ok, string? UserName, IReadOnlyList<string> Scopes, string? Error);
|
public record TicketConnectionDto(bool Ok, string? UserName, IReadOnlyList<string> Scopes, string? Error);
|
||||||
public record TicketProjectDto(int Id, string Title, string DepartmentName);
|
public record TicketProjectDto(int Id, string Title, string DepartmentName);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -23,11 +24,12 @@ public sealed class ClaudeAuthCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
||||||
if (lookup.Resolved is null)
|
if (lookup.Resolved is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
||||||
"The claude CLI was not found — login could not be checked.");
|
loc?["checks.claudeAuth.notFound"] ?? "The claude CLI was not found — login could not be checked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "auth", "status", "--json");
|
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "auth", "status", "--json");
|
||||||
@@ -36,19 +38,19 @@ public sealed class ClaudeAuthCheck : IEnvironmentCheck
|
|||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
||||||
"Login status could not be determined.", output);
|
loc?["checks.claudeAuth.undetermined"] ?? "Login status could not be determined.", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
var loggedIn = TryParseLoggedIn(output);
|
var loggedIn = TryParseLoggedIn(output);
|
||||||
if (loggedIn is null)
|
if (loggedIn is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
|
||||||
"Login status could not be determined.", output);
|
loc?["checks.claudeAuth.undetermined"] ?? "Login status could not be determined.", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
return loggedIn.Value
|
return loggedIn.Value
|
||||||
? CheckResult.Ok(Id, Severity, "checks.claudeAuth.title", "Logged in.")
|
? CheckResult.Ok(Id, Severity, "checks.claudeAuth.title", loc?["checks.claudeAuth.loggedIn"] ?? "Logged in.")
|
||||||
: CheckResult.Fail(Id, Severity, "checks.claudeAuth.title", "Not logged in.", "checks.claudeAuth.hint");
|
: CheckResult.Fail(Id, Severity, "checks.claudeAuth.title", loc?["checks.claudeAuth.notLoggedIn"] ?? "Not logged in.", "checks.claudeAuth.hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool? TryParseLoggedIn(string output)
|
private static bool? TryParseLoggedIn(string output)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using ClaudeDo.Data.Environment;
|
using ClaudeDo.Data.Environment;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -21,20 +22,21 @@ public sealed class ClaudeCliCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
||||||
|
|
||||||
if (lookup.Resolved is null)
|
if (lookup.Resolved is null)
|
||||||
{
|
{
|
||||||
var searched = string.Join(", ", SearchedLocations());
|
var searched = string.Join(", ", SearchedLocations());
|
||||||
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
|
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
|
||||||
$"'{ctx.ClaudeBin}' was not found on PATH. Searched: {searched}.",
|
loc?.Get("checks.claudeCli.notFound", ctx.ClaudeBin, searched) ?? $"'{ctx.ClaudeBin}' was not found on PATH. Searched: {searched}.",
|
||||||
"checks.claudeCli.hint");
|
"checks.claudeCli.hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lookup.ExitCode != 0)
|
if (lookup.ExitCode != 0)
|
||||||
{
|
{
|
||||||
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
|
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
|
||||||
$"'{lookup.Resolved.Path}' exited with code {lookup.ExitCode}.",
|
loc?.Get("checks.claudeCli.exitCode", lookup.Resolved.Path, lookup.ExitCode) ?? $"'{lookup.Resolved.Path}' exited with code {lookup.ExitCode}.",
|
||||||
"checks.claudeCli.hint", detail: lookup.Output);
|
"checks.claudeCli.hint", detail: lookup.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +44,8 @@ public sealed class ClaudeCliCheck : IEnvironmentCheck
|
|||||||
var detail = lookup.Resolved.IsShim
|
var detail = lookup.Resolved.IsShim
|
||||||
? "Resolved via a shim (.cmd/.bat/.ps1), not a native .exe."
|
? "Resolved via a shim (.cmd/.bat/.ps1), not a native .exe."
|
||||||
: null;
|
: null;
|
||||||
return CheckResult.Ok(Id, Severity, "checks.claudeCli.title", $"{lookup.Resolved.Path} — {version}", detail);
|
return CheckResult.Ok(Id, Severity, "checks.claudeCli.title",
|
||||||
|
loc?.Get("checks.claudeCli.resolvedFormat", lookup.Resolved.Path, version) ?? $"{lookup.Resolved.Path} — {version}", detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> SearchedLocations()
|
private IEnumerable<string> SearchedLocations()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -27,28 +28,29 @@ public sealed class ClaudeVersionCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
||||||
|
|
||||||
if (lookup.Resolved is null)
|
if (lookup.Resolved is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
|
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
|
||||||
"The claude CLI was not found — version could not be checked.");
|
loc?["checks.claudeVersion.notFound"] ?? "The claude CLI was not found — version could not be checked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lookup.ExitCode != 0 || lookup.ParsedVersion is null)
|
if (lookup.ExitCode != 0 || lookup.ParsedVersion is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
|
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
|
||||||
$"Could not parse a version from '{lookup.Output.Trim()}'.");
|
loc?.Get("checks.claudeVersion.parseFailed", lookup.Output.Trim()) ?? $"Could not parse a version from '{lookup.Output.Trim()}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lookup.ParsedVersion < MinimumVersion)
|
if (lookup.ParsedVersion < MinimumVersion)
|
||||||
{
|
{
|
||||||
return CheckResult.Fail(Id, Severity, "checks.claudeVersion.title",
|
return CheckResult.Fail(Id, Severity, "checks.claudeVersion.title",
|
||||||
$"claude {lookup.ParsedVersion} is older than the required {MinimumVersion}.",
|
loc?.Get("checks.claudeVersion.tooOld", lookup.ParsedVersion, MinimumVersion) ?? $"claude {lookup.ParsedVersion} is older than the required {MinimumVersion}.",
|
||||||
"checks.claudeVersion.hint");
|
"checks.claudeVersion.hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.Ok(Id, Severity, "checks.claudeVersion.title",
|
return CheckResult.Ok(Id, Severity, "checks.claudeVersion.title",
|
||||||
$"claude {lookup.ParsedVersion} meets the minimum ({MinimumVersion}).");
|
loc?.Get("checks.claudeVersion.ok", lookup.ParsedVersion, MinimumVersion) ?? $"claude {lookup.ParsedVersion} meets the minimum ({MinimumVersion}).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Text.RegularExpressions;
|
|||||||
using ClaudeDo.Data.Environment;
|
using ClaudeDo.Data.Environment;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
using ClaudeDo.Installer.Core.Interfaces;
|
using ClaudeDo.Installer.Core.Interfaces;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -27,10 +28,12 @@ public sealed class GitCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
|
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
|
||||||
if (resolved is null)
|
if (resolved is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Fail(Id, Severity, "checks.git.title", "git was not found on PATH.",
|
return CheckResult.Fail(Id, Severity, "checks.git.title",
|
||||||
|
loc?["checks.git.notFound"] ?? "git was not found on PATH.",
|
||||||
"checks.git.hint", "https://git-scm.com/downloads");
|
"checks.git.hint", "https://git-scm.com/downloads");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +42,13 @@ public sealed class GitCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
{
|
{
|
||||||
return CheckResult.Fail(Id, Severity, "checks.git.title", $"'{resolved.Path}' exited with code {exitCode}.",
|
return CheckResult.Fail(Id, Severity, "checks.git.title",
|
||||||
|
loc?.Get("checks.git.exitCode", resolved.Path, exitCode) ?? $"'{resolved.Path}' exited with code {exitCode}.",
|
||||||
"checks.git.hint", "https://git-scm.com/downloads", output);
|
"checks.git.hint", "https://git-scm.com/downloads", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = VersionPattern.Match(output) is { Success: true } match ? match.Value : output.Trim();
|
var version = VersionPattern.Match(output) is { Success: true } match ? match.Value : output.Trim();
|
||||||
return CheckResult.Ok(Id, Severity, "checks.git.title", $"{resolved.Path} — {version}");
|
return CheckResult.Ok(Id, Severity, "checks.git.title",
|
||||||
|
loc?.Get("checks.git.resolvedFormat", resolved.Path, version) ?? $"{resolved.Path} — {version}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ClaudeDo.Data.Environment;
|
using ClaudeDo.Data.Environment;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
using ClaudeDo.Installer.Core.Interfaces;
|
using ClaudeDo.Installer.Core.Interfaces;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -25,10 +26,12 @@ public sealed class GitIdentityCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
|
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
|
||||||
if (resolved is null)
|
if (resolved is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.gitIdentity.title", "git was not found — identity could not be checked.");
|
return CheckResult.Unknown(Id, Severity, "checks.gitIdentity.title",
|
||||||
|
loc?["checks.gitIdentity.notFound"] ?? "git was not found — identity could not be checked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = await ReadConfigAsync(resolved, "user.name", ct);
|
var name = await ReadConfigAsync(resolved, "user.name", ct);
|
||||||
@@ -48,7 +51,8 @@ public sealed class GitIdentityCheck : IEnvironmentCheck
|
|||||||
_ => "user.email",
|
_ => "user.email",
|
||||||
};
|
};
|
||||||
|
|
||||||
return CheckResult.Fail(Id, Severity, "checks.gitIdentity.title", $"Missing git identity: {missing}.", "checks.gitIdentity.hint");
|
return CheckResult.Fail(Id, Severity, "checks.gitIdentity.title",
|
||||||
|
loc?.Get("checks.gitIdentity.missing", missing) ?? $"Missing git identity: {missing}.", "checks.gitIdentity.hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ReadConfigAsync(ResolvedExecutable resolved, string key, CancellationToken ct)
|
private async Task<string> ReadConfigAsync(ResolvedExecutable resolved, string key, CancellationToken ct)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -28,11 +29,12 @@ public sealed class PermissionModeAutoCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
var lookup = await _lookup.ResolveAsync(ctx, ct);
|
||||||
if (lookup.Resolved is null)
|
if (lookup.Resolved is null)
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
|
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
|
||||||
"The claude CLI was not found — permission-mode support could not be checked.");
|
loc?["checks.permissionModeAuto.notFound"] ?? "The claude CLI was not found — permission-mode support could not be checked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "--help");
|
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "--help");
|
||||||
@@ -41,17 +43,17 @@ public sealed class PermissionModeAutoCheck : IEnvironmentCheck
|
|||||||
if (exitCode != 0 || string.IsNullOrWhiteSpace(output))
|
if (exitCode != 0 || string.IsNullOrWhiteSpace(output))
|
||||||
{
|
{
|
||||||
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
|
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
|
||||||
"Could not determine whether --permission-mode auto is supported.");
|
loc?["checks.permissionModeAuto.undetermined"] ?? "Could not determine whether --permission-mode auto is supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AutoIsListed(output))
|
if (AutoIsListed(output))
|
||||||
{
|
{
|
||||||
return CheckResult.Ok(Id, Severity, "checks.permissionModeAuto.title",
|
return CheckResult.Ok(Id, Severity, "checks.permissionModeAuto.title",
|
||||||
"--permission-mode auto is recognized by this CLI.");
|
loc?["checks.permissionModeAuto.ok"] ?? "--permission-mode auto is recognized by this CLI.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.Fail(Id, Severity, "checks.permissionModeAuto.title",
|
return CheckResult.Fail(Id, Severity, "checks.permissionModeAuto.title",
|
||||||
"This claude CLI version does not list 'auto' as a --permission-mode choice.",
|
loc?["checks.permissionModeAuto.notListed"] ?? "This claude CLI version does not list 'auto' as a --permission-mode choice.",
|
||||||
"checks.permissionModeAuto.hint");
|
"checks.permissionModeAuto.hint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Net;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using ClaudeDo.Installer.Checks.Interfaces;
|
using ClaudeDo.Installer.Checks.Interfaces;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ public sealed class PortCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
var ports = new (string Label, int Port)[]
|
var ports = new (string Label, int Port)[]
|
||||||
{
|
{
|
||||||
("SignalR", ctx.SignalRPort),
|
("SignalR", ctx.SignalRPort),
|
||||||
@@ -39,7 +41,9 @@ public sealed class PortCheck : IEnvironmentCheck
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
blocked.Add(owner is null ? $"{label} port {port} is in use." : $"{label} port {port} is in use by '{owner}'.");
|
blocked.Add(owner is null
|
||||||
|
? loc?.Get("checks.ports.inUse", label, port) ?? $"{label} port {port} is in use."
|
||||||
|
: loc?.Get("checks.ports.inUseBy", label, port, owner) ?? $"{label} port {port} is in use by '{owner}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blocked.Count > 0)
|
if (blocked.Count > 0)
|
||||||
@@ -49,10 +53,12 @@ public sealed class PortCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
if (ownWorker.Count > 0)
|
if (ownWorker.Count > 0)
|
||||||
{
|
{
|
||||||
return CheckResult.Ok(Id, Severity, "checks.ports.title", $"In use by the running ClaudeDo Worker: {string.Join(", ", ownWorker)}.");
|
var ownWorkerList = string.Join(", ", ownWorker);
|
||||||
|
return CheckResult.Ok(Id, Severity, "checks.ports.title",
|
||||||
|
loc?.Get("checks.ports.inUseByOwnWorker", ownWorkerList) ?? $"In use by the running ClaudeDo Worker: {ownWorkerList}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.Ok(Id, Severity, "checks.ports.title", "Ports are available.");
|
return CheckResult.Ok(Id, Severity, "checks.ports.title", loc?["checks.ports.available"] ?? "Ports are available.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsFree(int port)
|
private static bool IsFree(int port)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using ClaudeDo.Data;
|
using ClaudeDo.Data;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
using ClaudeDo.Installer.Localization;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Checks;
|
namespace ClaudeDo.Installer.Checks;
|
||||||
|
|
||||||
@@ -14,17 +15,19 @@ public sealed class WriteAccessCheck : IEnvironmentCheck
|
|||||||
|
|
||||||
public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var loc = TrExtension.Localizer;
|
||||||
foreach (var target in new[] { ctx.InstallDirectory, Paths.AppDataRoot() })
|
foreach (var target in new[] { ctx.InstallDirectory, Paths.AppDataRoot() })
|
||||||
{
|
{
|
||||||
var error = TryWrite(target);
|
var error = TryWrite(target);
|
||||||
if (error is not null)
|
if (error is not null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(CheckResult.Fail(Id, Severity, "checks.writeAccess.title",
|
return Task.FromResult(CheckResult.Fail(Id, Severity, "checks.writeAccess.title",
|
||||||
$"Cannot write to '{target}': {error}", "checks.writeAccess.hint"));
|
loc?.Get("checks.writeAccess.cannotWrite", target, error) ?? $"Cannot write to '{target}': {error}", "checks.writeAccess.hint"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(CheckResult.Ok(Id, Severity, "checks.writeAccess.title", "Install directory and data directory are writable."));
|
return Task.FromResult(CheckResult.Ok(Id, Severity, "checks.writeAccess.title",
|
||||||
|
loc?["checks.writeAccess.writable"] ?? "Install directory and data directory are writable."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? TryWrite(string path)
|
private static string? TryWrite(string path)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"categoryGeneral": "Allgemein",
|
"categoryGeneral": "Allgemein",
|
||||||
"categoryExecution": "Ausführung",
|
"categoryExecution": "Ausführung",
|
||||||
"categoryPrime": "Prime Claude",
|
"categoryPrime": "Prime Claude",
|
||||||
|
"categoryPrimeHint": "Geplante Claude-Läufe: Ping, My Day füllen oder ein eigener Prompt, zu festen Zeiten.",
|
||||||
"categoryWorktrees": "Worktrees",
|
"categoryWorktrees": "Worktrees",
|
||||||
"categoryFiles": "Dateien & Prompts",
|
"categoryFiles": "Dateien & Prompts",
|
||||||
"categorySkills": "Session Skills",
|
"categorySkills": "Session Skills",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"model": "Modell",
|
"model": "Modell",
|
||||||
"maxTurns": "Max. Durchläufe",
|
"maxTurns": "Max. Durchläufe",
|
||||||
"effort": "Effort",
|
"effort": "Effort",
|
||||||
|
"effortHint": "Denkaufwand, den das Modell anwendet. Höher kostet mehr und denkt tiefer.",
|
||||||
"modelPresets": "Vorgaben pro Modell",
|
"modelPresets": "Vorgaben pro Modell",
|
||||||
"modelPresetsHint": "Effort und Durchlauf-Budget, mit denen eine Sitzung unter diesem Modell läuft. Overrides auf Listen- und Aufgabenebene gewinnen weiterhin.",
|
"modelPresetsHint": "Effort und Durchlauf-Budget, mit denen eine Sitzung unter diesem Modell läuft. Overrides auf Listen- und Aufgabenebene gewinnen weiterhin.",
|
||||||
"maxTurnsCeiling": "Max. Durchläufe – Obergrenze",
|
"maxTurnsCeiling": "Max. Durchläufe – Obergrenze",
|
||||||
@@ -36,13 +38,23 @@
|
|||||||
"maxParallelExecutions": "Max. parallele Ausführungen",
|
"maxParallelExecutions": "Max. parallele Ausführungen",
|
||||||
"maxParallelExecutionsHint": "Wie viele Aufgaben aus der Warteschlange der Worker gleichzeitig ausführt.",
|
"maxParallelExecutionsHint": "Wie viele Aufgaben aus der Warteschlange der Worker gleichzeitig ausführt.",
|
||||||
"usageGateHeading": "Usage-Limit-Stopp",
|
"usageGateHeading": "Usage-Limit-Stopp",
|
||||||
"usageGateHint": "Ab dieser Auslastung holt die Queue keine neuen Tasks mehr; laufende Tasks laufen zu Ende. 0 schaltet den Stopp ab.",
|
"usageGateHint": "Ab dieser Auslastung holt die Warteschlange keine neuen Aufgaben mehr; laufende Aufgaben laufen zu Ende. 0 schaltet den Stopp ab.",
|
||||||
"usageGateFiveHourPct": "5-Stunden-Fenster (%)",
|
"usageGateFiveHourPct": "5-Stunden-Fenster (%)",
|
||||||
"usageGateSevenDayPct": "7-Tage-Fenster (%)",
|
"usageGateSevenDayPct": "7-Tage-Fenster (%)",
|
||||||
"autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen",
|
"autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen",
|
||||||
"autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Queue genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.",
|
"autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Warteschlange genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.",
|
||||||
|
"listHandlerMaxHandoffs": "Handoff-Budget des List-Handlers",
|
||||||
|
"listHandlerMaxHandoffsHint": "Wie viele Wait/Merge-Handoffs ein „Let Claude handle it\"-Run verketten darf. 4 entspricht zwei Merge-Runden. Für Läufe, die stundenlang durchlaufen sollen, höher setzen; der Wert begrenzt eine entlaufene Kette und darf daher nicht 0 sein.",
|
||||||
|
"powerUseHeading": "PowerUse",
|
||||||
|
"powerUse": "PowerUse aktivieren",
|
||||||
|
"powerUseHint": "Das restliche Fenster ausnutzen: ein List-Handler-Run fragt nichts, löst Konflikte selbst, sammelt neue Arbeit und handoffet weiter, solange sein Budget reicht. Wirkt nur auf Listen, die PowerUse in ihren Listen-Einstellungen erlaubt haben.",
|
||||||
|
"powerUseWindow": "Nur zwischen",
|
||||||
|
"powerUseWindowPlaceholder": "HH:mm",
|
||||||
|
"powerUseHandoffPct": "Übergabe bei (% von 5h)",
|
||||||
|
"powerUseWindowHint": "Beide Zeiten leer = rund um die Uhr. Das Fenster wird bei jedem Start einer Handler-Phase geprüft — eine Kette, die über das Ende hinausläuft, macht ohne PowerUse weiter.",
|
||||||
|
"powerUseWindowInvalid": "PowerUse-Zeiten müssen im Format HH:mm sein oder leer bleiben.",
|
||||||
"throttleStagesHeading": "Throttle-Stufen",
|
"throttleStagesHeading": "Throttle-Stufen",
|
||||||
"throttleStagesHint": "Die Parallelität wird vor dem harten Gate begrenzt: soft auf 2 Slots, hard auf 1. Eingestellt wird das durch Ziehen der Marker an den Gauges im Usage-Monitor. Der Monitor schließt diese Einstellungen — vorher speichern, wenn du etwas geändert hast.",
|
"throttleStagesHint": "Die Parallelität wird vor dem harten Limit-Stopp begrenzt: soft auf 2 Slots, hard auf 1. Eingestellt wird das durch Ziehen der Marker an den Gauges im Usage-Monitor. Der Monitor schließt diese Einstellungen — vorher speichern, wenn du etwas geändert hast.",
|
||||||
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
|
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
|
||||||
"openUsageMonitor": "Usage-Monitor öffnen...",
|
"openUsageMonitor": "Usage-Monitor öffnen...",
|
||||||
"reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)",
|
"reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)",
|
||||||
@@ -85,6 +97,7 @@
|
|||||||
"retryPrompt": "Wiederholung",
|
"retryPrompt": "Wiederholung",
|
||||||
"dailyPrepPrompt": "Tagesplanung",
|
"dailyPrepPrompt": "Tagesplanung",
|
||||||
"weeklyReportPrompt": "Wochenbericht",
|
"weeklyReportPrompt": "Wochenbericht",
|
||||||
|
"powerUsePrompt": "PowerUse-Addendum",
|
||||||
"viewPrompt": "Ansehen",
|
"viewPrompt": "Ansehen",
|
||||||
"customizedSection": "ANGEPASST",
|
"customizedSection": "ANGEPASST",
|
||||||
"customizedHint": "Diese Prompts weichen vom mitgelieferten Standard ab und werden unverändert beibehalten. Zurücksetzen, um künftige Standard-Verbesserungen zu erhalten.",
|
"customizedHint": "Diese Prompts weichen vom mitgelieferten Standard ab und werden unverändert beibehalten. Zurücksetzen, um künftige Standard-Verbesserungen zu erhalten.",
|
||||||
@@ -177,7 +190,6 @@
|
|||||||
"listSettingsTip": "Listeneinstellungen",
|
"listSettingsTip": "Listeneinstellungen",
|
||||||
"addPlaceholder": "Aufgabe hinzufügen…",
|
"addPlaceholder": "Aufgabe hinzufügen…",
|
||||||
"enterKey": "ENTER",
|
"enterKey": "ENTER",
|
||||||
"notesPinnedRow": "Notizen (Tagesnotizen)",
|
|
||||||
"clearDayTip": "Tag leeren",
|
"clearDayTip": "Tag leeren",
|
||||||
"planMyDayTip": "Meinen Tag planen",
|
"planMyDayTip": "Meinen Tag planen",
|
||||||
"overdue": "ÜBERFÄLLIG",
|
"overdue": "ÜBERFÄLLIG",
|
||||||
@@ -209,6 +221,7 @@
|
|||||||
"ctxRefineTask": "Aufgabe verfeinern",
|
"ctxRefineTask": "Aufgabe verfeinern",
|
||||||
"ctxPlanning": "Planung",
|
"ctxPlanning": "Planung",
|
||||||
"ctxSchedule": "Zeitplan",
|
"ctxSchedule": "Zeitplan",
|
||||||
|
"ctxMoveToList": "In Liste verschieben",
|
||||||
"ctxDeleteTask": "Aufgabe löschen",
|
"ctxDeleteTask": "Aufgabe löschen",
|
||||||
"reasonAlreadyRunning": "Aufgabe läuft bereits",
|
"reasonAlreadyRunning": "Aufgabe läuft bereits",
|
||||||
"reasonAlreadyQueued": "Bereits in der Warteschlange",
|
"reasonAlreadyQueued": "Bereits in der Warteschlange",
|
||||||
@@ -223,12 +236,13 @@
|
|||||||
"reasonIsPlanningParent": "Teil eines Plans",
|
"reasonIsPlanningParent": "Teil eines Plans",
|
||||||
"reasonIsChildTask": "Teilaufgaben haben keine eigene Planungssitzung",
|
"reasonIsChildTask": "Teilaufgaben haben keine eigene Planungssitzung",
|
||||||
"reasonPlanAlreadyFinalized": "Plan ist bereits finalisiert",
|
"reasonPlanAlreadyFinalized": "Plan ist bereits finalisiert",
|
||||||
"reasonNoWorktree": "Kein Worktree für diesen Task",
|
"reasonNoWorktree": "Kein Worktree für diese Aufgabe",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"removeFromQueueTip": "Aus Warteschlange entfernen",
|
"removeFromQueueTip": "Aus Warteschlange entfernen",
|
||||||
"toggleSubtasksTip": "Unteraufgaben ein-/ausklappen",
|
"toggleSubtasksTip": "Unteraufgaben ein-/ausklappen",
|
||||||
"agentSuggestedTip": "Vom Agenten vorgeschlagen",
|
"agentSuggestedTip": "Vom Agenten vorgeschlagen",
|
||||||
"interactiveChipTip": "Interaktive Sitzung offen — klicken, um im Mission Control dorthin zu springen",
|
"interactiveChipTip": "Interaktive Sitzung offen — klicken, um im Mission Control dorthin zu springen",
|
||||||
|
"waitingOnTip": "Wartet auf {0}",
|
||||||
"scheduleTitle": "Aufgabe planen",
|
"scheduleTitle": "Aufgabe planen",
|
||||||
"scheduleWhen": "WANN",
|
"scheduleWhen": "WANN",
|
||||||
"scheduleConfirm": "Planen",
|
"scheduleConfirm": "Planen",
|
||||||
@@ -250,22 +264,24 @@
|
|||||||
"contextOpenFindings": "Findings-Ordner öffnen",
|
"contextOpenFindings": "Findings-Ordner öffnen",
|
||||||
"contextImportTickets": "Aus Ticketsystem aktualisieren",
|
"contextImportTickets": "Aus Ticketsystem aktualisieren",
|
||||||
"contextSettings": "Einstellungen…",
|
"contextSettings": "Einstellungen…",
|
||||||
|
"contextFolderSettings": "Ordner-Einstellungen…",
|
||||||
|
"contextRemoveFromFolder": "Aus Ordner herausnehmen",
|
||||||
"newList": "Neue Liste",
|
"newList": "Neue Liste",
|
||||||
"addReposTip": "Repos als Listen hinzufügen",
|
"addReposTip": "Repos als Listen hinzufügen",
|
||||||
"noRepoBannerText": "Noch kein Repo verknüpft. ClaudeDo führt Tasks in einem Git-Repo aus.",
|
"noRepoBannerText": "Noch kein Repo verknüpft. ClaudeDo führt Aufgaben in einem Git-Repo aus.",
|
||||||
"noRepoBannerButton": "Repo importieren",
|
"noRepoBannerButton": "Repo importieren",
|
||||||
"importTicketsResult": "{0} Tickets geprüft, {1} neue Tasks."
|
"importTicketsResult": "{0} Tickets geprüft, {1} neue Tasks."
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"deleteTaskTip": "Aufgabe löschen",
|
"deleteTaskTip": "Aufgabe löschen",
|
||||||
"killSessionTip": "Laufende Sitzung beenden",
|
"killSessionTip": "Laufende Sitzung hart beenden",
|
||||||
"sendToQueue": "In Warteschlange einreihen",
|
"sendToQueue": "In Warteschlange einreihen",
|
||||||
"sendToQueueTip": "Diese Aufgabe in die Warteschlange einreihen, damit Claude sie beim nächsten Lauf übernimmt",
|
"sendToQueueTip": "Diese Aufgabe in die Warteschlange einreihen, damit Claude sie beim nächsten Lauf übernimmt",
|
||||||
"closeTip": "Schließen",
|
|
||||||
"copyTaskIdTip": "Aufgaben-ID kopieren",
|
"copyTaskIdTip": "Aufgaben-ID kopieren",
|
||||||
"starTip": "Favorit",
|
"starTip": "Favorit",
|
||||||
"agentSettingsTip": "Agent-Einstellungen",
|
"agentSettingsTip": "Agent-Einstellungen",
|
||||||
"agentSettingsHeading": "Agent-Einstellungen (Überschreibungen)",
|
"agentSettingsHeading": "Agent-Einstellungen (Überschreibungen)",
|
||||||
|
"waitingOnPrefix": "Wartet auf",
|
||||||
"copyFormattedTip": "Titel, Beschreibung und offene Schritte kopieren",
|
"copyFormattedTip": "Titel, Beschreibung und offene Schritte kopieren",
|
||||||
"toggleEditPreviewTip": "Bearbeiten/Vorschau umschalten",
|
"toggleEditPreviewTip": "Bearbeiten/Vorschau umschalten",
|
||||||
"planDay": "Tag planen",
|
"planDay": "Tag planen",
|
||||||
@@ -291,7 +307,37 @@
|
|||||||
"steps": "Schritte",
|
"steps": "Schritte",
|
||||||
"files": "Dateien"
|
"files": "Dateien"
|
||||||
},
|
},
|
||||||
"emptyState": "Wähle eine Aufgabe aus, um Details zu sehen"
|
"emptyState": "Wähle eine Aufgabe aus, um Details zu sehen",
|
||||||
|
"roadblockLabel": "ROADBLOCK",
|
||||||
|
"stepsCard": {
|
||||||
|
"preview": "Vorschau",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"addStepPlaceholder": "Schritt hinzufügen…"
|
||||||
|
},
|
||||||
|
"workConsole": {
|
||||||
|
"continue": "Fortsetzen",
|
||||||
|
"resetAndRetry": "Zurücksetzen & erneut versuchen",
|
||||||
|
"tabOutput": "Ausgabe",
|
||||||
|
"tabGit": "Git",
|
||||||
|
"tabSession": "Sitzung",
|
||||||
|
"outcomeLabel": "ERGEBNIS",
|
||||||
|
"reviewLabel": "REVIEW",
|
||||||
|
"mergeLabel": "MERGE",
|
||||||
|
"targetBranch": "Zielbranch",
|
||||||
|
"openDiff": "Diff öffnen",
|
||||||
|
"worktree": "Worktree",
|
||||||
|
"reviewCombinedDiff": "Kombinierten Diff prüfen",
|
||||||
|
"openDiffHint": "Diff öffnen, um Mergen freizuschalten",
|
||||||
|
"targetDirty": "Ziel-Arbeitsverzeichnis hat uncommittete Änderungen — beim Mergen werden sie gestasht und danach wiederhergestellt.",
|
||||||
|
"approveMerge": "Genehmigen & Mergen",
|
||||||
|
"park": "Parken",
|
||||||
|
"parkTip": "Zurückstellen — zurück auf Leerlauf, Worktree bleibt erhalten",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"resetDiscardBranch": "Zurücksetzen (Branch verwerfen)…",
|
||||||
|
"submitForReviewHint": "Von Hand daran gearbeitet? Den Worktree zum Review einreichen, um ihn zu mergen.",
|
||||||
|
"submitForReview": "Zum Review einreichen",
|
||||||
|
"outcomesLabel": "ERGEBNISSE"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"agent": {
|
"agent": {
|
||||||
"openWorktreeTip": "Worktree im Datei-Explorer öffnen — benötigt einen aktiven Worktree",
|
"openWorktreeTip": "Worktree im Datei-Explorer öffnen — benötigt einen aktiven Worktree",
|
||||||
@@ -300,8 +346,11 @@
|
|||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"today": "Heute",
|
"today": "Heute",
|
||||||
"add": "Hinzufügen",
|
"yesterday": "Gestern",
|
||||||
"newNotePlaceholder": "Neue Notiz…"
|
"newNotePlaceholder": "Neue Notiz…",
|
||||||
|
"emptyHint": "Noch nichts notiert. Tippen, Enter, fertig.",
|
||||||
|
"deleteTip": "Notiz löschen",
|
||||||
|
"fromAgentTip": "Von Claude notiert"
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"chipLive": "LIVE",
|
"chipLive": "LIVE",
|
||||||
@@ -320,14 +369,33 @@
|
|||||||
"newSession": "Neue Sitzung",
|
"newSession": "Neue Sitzung",
|
||||||
"empty": "Keine laufenden Aufgaben",
|
"empty": "Keine laufenden Aufgaben",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"queue": "Warteschlange",
|
"activity": "Aktivität",
|
||||||
"blocked": "Blockiert",
|
"blocked": "Blockiert",
|
||||||
"running": "Läuft",
|
"running": "Läuft",
|
||||||
|
"review": "Im Review",
|
||||||
|
"failed": "Fehlgeschlagen",
|
||||||
|
"roadblock": "Blockade gemeldet",
|
||||||
|
"openSession": "Sitzung öffnen",
|
||||||
|
"homeFolder": "Home (Schnellsitzung)",
|
||||||
|
"browseForFolder": "Ordner wählen…",
|
||||||
|
"recentSessions": "Letzte Sitzungen",
|
||||||
|
"recentSessionsEmpty": "Keine Sitzungen in den letzten 14 Tagen",
|
||||||
|
"resumeSession": "Fortsetzen",
|
||||||
|
"showBrief": "Brief",
|
||||||
|
"handoffSession": "Übergabe",
|
||||||
|
"briefOpenFailed": "Brief konnte nicht geöffnet werden: {0}",
|
||||||
"focusMode": "Fokus",
|
"focusMode": "Fokus",
|
||||||
"overviewMode": "Übersicht",
|
"overviewMode": "Übersicht",
|
||||||
"closeSession": "Sitzung beenden",
|
"columns": "Spalten",
|
||||||
|
"columnsAuto": "Auto",
|
||||||
|
"closeSession": "Sitzung hart beenden",
|
||||||
|
"closeSessionConfirm": "Laufende Sitzung beenden? Der Claude-Prozess wird abgebrochen.",
|
||||||
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
|
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
|
||||||
"conptyStarting": "Sitzung wird gestartet…",
|
"conptyStarting": "Sitzung wird gestartet…",
|
||||||
|
"conptyExited": "Sitzung beendet (Code {0}).",
|
||||||
|
"usageWarningNotDelivered": "Die Listen-Handler-Sitzung konnte nicht vor dem Usage-Limit gewarnt werden.",
|
||||||
|
"terminalSendFailed": "Konnte nicht an die Terminal-Sitzung gesendet werden.",
|
||||||
|
"deferredHandoffTitleSuffix": "warte auf Limit-Zurücksetzen ({0})",
|
||||||
"mergeHelperTitle": "Merge-Helfer",
|
"mergeHelperTitle": "Merge-Helfer",
|
||||||
"mergeHelperHandoffTitleSuffix": " (Übergabe)",
|
"mergeHelperHandoffTitleSuffix": " (Übergabe)",
|
||||||
"mergeHelperTaskTitle": "Listen-Handler: {0}",
|
"mergeHelperTaskTitle": "Listen-Handler: {0}",
|
||||||
@@ -343,6 +411,10 @@
|
|||||||
"enqueueAlreadyOpen": "Kann nicht in die Warteschlange gestellt werden — dieser Task hat eine offene interaktive Sitzung.",
|
"enqueueAlreadyOpen": "Kann nicht in die Warteschlange gestellt werden — dieser Task hat eine offene interaktive Sitzung.",
|
||||||
"enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}",
|
"enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}",
|
||||||
"queueRefreshFailed": "Warteschlange konnte nicht aktualisiert werden: {0}",
|
"queueRefreshFailed": "Warteschlange konnte nicht aktualisiert werden: {0}",
|
||||||
|
"abandonRun": "Lauf abbrechen",
|
||||||
|
"abandonRunTip": "Die noch offenen Tasks dieses Laufs abbrechen und die Sitzung schließen",
|
||||||
|
"abandonRunConfirm": "{0} offene Aufgabe(n) abbrechen und diese Sitzung schließen? Aufgaben im Review oder bereits erledigt bleiben unangetastet.",
|
||||||
|
"abandonRunFailed": "Offene Tasks konnten nicht abgebrochen werden: {0}",
|
||||||
"question": {
|
"question": {
|
||||||
"title": "Claude fragt nach",
|
"title": "Claude fragt nach",
|
||||||
"placeholder": "Antwort eingeben…",
|
"placeholder": "Antwort eingeben…",
|
||||||
@@ -376,13 +448,22 @@
|
|||||||
"body": "ClaudeDo kann den Hintergrund-Worker nicht erreichen. Normalerweise wird er bei der Anmeldung automatisch gestartet. Du kannst ihn jetzt starten oder neu installieren, falls das Problem bestehen bleibt.",
|
"body": "ClaudeDo kann den Hintergrund-Worker nicht erreichen. Normalerweise wird er bei der Anmeldung automatisch gestartet. Du kannst ihn jetzt starten oder neu installieren, falls das Problem bestehen bleibt.",
|
||||||
"dismiss": "Ausblenden",
|
"dismiss": "Ausblenden",
|
||||||
"rerunInstaller": "Installer erneut ausführen",
|
"rerunInstaller": "Installer erneut ausführen",
|
||||||
"startWorker": "Worker starten"
|
"startWorker": "Worker starten",
|
||||||
|
"workerNotFound": "Worker-Programm nicht gefunden. Installation prüfen.",
|
||||||
|
"installerNotFound": "Installer nicht gefunden. Installation prüfen.",
|
||||||
|
"startFailed": "Start fehlgeschlagen: {0}"
|
||||||
},
|
},
|
||||||
"listSettings": {
|
"listSettings": {
|
||||||
"title": "LISTENEINSTELLUNGEN",
|
"title": "LISTENEINSTELLUNGEN",
|
||||||
|
"folderTitle": "ORDNER",
|
||||||
"deleteList": "Liste löschen",
|
"deleteList": "Liste löschen",
|
||||||
|
"deleteFolder": "Ordner löschen",
|
||||||
|
"createAsFolder": "Stattdessen einen Ordner anlegen",
|
||||||
|
"createAsFolderHint": "Ein Ordner gruppiert nur Listen in der Seitenleiste — er enthält keine Aufgaben und führt nichts aus. Listen per Drag-and-Drop hineinziehen.",
|
||||||
"sectionGeneral": "ALLGEMEIN",
|
"sectionGeneral": "ALLGEMEIN",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"folderColor": "Farbe",
|
||||||
|
"folderColorHint": "Färbt den Ordnernamen und seine Führungslinie in der Seitenleiste.",
|
||||||
"workingDirectory": "Arbeitsverzeichnis",
|
"workingDirectory": "Arbeitsverzeichnis",
|
||||||
"workingDirectoryPlaceholder": "(keines)",
|
"workingDirectoryPlaceholder": "(keines)",
|
||||||
"browse": "Durchsuchen...",
|
"browse": "Durchsuchen...",
|
||||||
@@ -391,6 +472,13 @@
|
|||||||
"manualListHint": "Neue Aufgaben in dieser Liste sind zunächst manuell: kein Einreihen, Ausführen oder Verfeinern, und die Automatik überspringt sie. Eine handgesteuerte Sitzung kannst du weiterhin öffnen.",
|
"manualListHint": "Neue Aufgaben in dieser Liste sind zunächst manuell: kein Einreihen, Ausführen oder Verfeinern, und die Automatik überspringt sie. Eine handgesteuerte Sitzung kannst du weiterhin öffnen.",
|
||||||
"findingsTracked": "Findings-Ordner .claudedo einchecken",
|
"findingsTracked": "Findings-Ordner .claudedo einchecken",
|
||||||
"findingsTrackedHint": "Aus: der Ordner bleibt über .git/info/exclude aus git heraus. An: Findings reisen mit dem Repo.",
|
"findingsTrackedHint": "Aus: der Ordner bleibt über .git/info/exclude aus git heraus. An: Findings reisen mit dem Repo.",
|
||||||
|
"powerUseAllowed": "PowerUse für diese Liste erlauben",
|
||||||
|
"powerUseAllowedHint": "Erlaubt einem List-Handler-Run auf dieser Liste, unbeaufsichtigt zu laufen, sobald PowerUse in Einstellungen → Ausführung eingeschaltet ist: er fragt nichts, löst Konflikte selbst und sammelt neue Arbeit. Beide Schalter sind nötig — ein global aktives PowerUse ändert an einer Liste ohne Opt-in also nichts.",
|
||||||
|
"sectionIcon": "ICON",
|
||||||
|
"icon": "Listen-Icon",
|
||||||
|
"iconHint": "Wird in der Seitenleiste neben dem Listennamen angezeigt.",
|
||||||
|
"iconSearchWatermark": "Alle Icons durchsuchen (Englisch, z.B. rocket, car, bug)",
|
||||||
|
"iconSearchTruncated": "Die ersten 200 Treffer werden angezeigt — Suche verfeinern.",
|
||||||
"sectionAgent": "AGENT",
|
"sectionAgent": "AGENT",
|
||||||
"resetAgentSettings": "Agent-Einstellungen zurücksetzen",
|
"resetAgentSettings": "Agent-Einstellungen zurücksetzen",
|
||||||
"sectionVerify": "VERIFIKATION",
|
"sectionVerify": "VERIFIKATION",
|
||||||
@@ -407,7 +495,8 @@
|
|||||||
"targetBranch": "Ziel-Branch",
|
"targetBranch": "Ziel-Branch",
|
||||||
"removeWorktree": "Worktree nach dem Mergen entfernen",
|
"removeWorktree": "Worktree nach dem Mergen entfernen",
|
||||||
"commitMessage": "Commit-Nachricht",
|
"commitMessage": "Commit-Nachricht",
|
||||||
"conflictedFiles": "Konfliktdateien:"
|
"conflictedFiles": "Konfliktdateien:",
|
||||||
|
"stashAndMerge": "Änderungen stashen und mergen"
|
||||||
},
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
"title": "DIFF",
|
"title": "DIFF",
|
||||||
@@ -463,6 +552,8 @@
|
|||||||
"windowTitle": "Claude machen lassen",
|
"windowTitle": "Claude machen lassen",
|
||||||
"scopeList": "Liste: {0}",
|
"scopeList": "Liste: {0}",
|
||||||
"scopeReview": "Alle Listen — Tasks im Review",
|
"scopeReview": "Alle Listen — Tasks im Review",
|
||||||
|
"modelLabel": "Modell",
|
||||||
|
"modelTip": "Modell für die Triage-Session (Dedupe & Verfeinern). Spätere Merge-Phasen laufen immer auf Opus.",
|
||||||
"confirm": "Claude machen lassen",
|
"confirm": "Claude machen lassen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"selectAll": "Alle auswählen",
|
"selectAll": "Alle auswählen",
|
||||||
@@ -490,21 +581,27 @@
|
|||||||
"regenerate": "Neu erstellen",
|
"regenerate": "Neu erstellen",
|
||||||
"emptyStateHint": "Noch kein Bericht für diesen Zeitraum. Klicke auf „Erstellen“."
|
"emptyStateHint": "Noch kein Bericht für diesen Zeitraum. Klicke auf „Erstellen“."
|
||||||
},
|
},
|
||||||
|
"cheatSheets": {
|
||||||
|
"title": "CHEAT SHEETS",
|
||||||
|
"windowTitle": "Cheat Sheets",
|
||||||
|
"pin": "Anpinnen",
|
||||||
|
"pinTooltip": "Fenster über den anderen halten",
|
||||||
|
"missing": "Cheat Sheet {0} nicht gefunden."
|
||||||
|
},
|
||||||
"usageMonitor": {
|
"usageMonitor": {
|
||||||
"title": "USAGE MONITOR",
|
"title": "USAGE MONITOR",
|
||||||
"windowTitle": "Usage Monitor",
|
"windowTitle": "Usage Monitor",
|
||||||
"noGauges": "Noch keine Nutzungslimits gemeldet.",
|
"noGauges": "Noch keine Nutzungslimits gemeldet.",
|
||||||
"refresh": "Jetzt aktualisieren",
|
"refreshHint": "Jetzt aktualisieren — Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.",
|
||||||
"refreshHint": "Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.",
|
|
||||||
"staleFormat": "Werte veraltet (Stand {0})",
|
"staleFormat": "Werte veraltet (Stand {0})",
|
||||||
"staleGateHint": "Das Gate greift in diesem Zustand nicht.",
|
"staleGateHint": "Der Limit-Stopp greift in diesem Zustand nicht.",
|
||||||
"gateBlockedFormat": "Warteschlange pausiert — {0}",
|
"gateBlockedFormat": "Warteschlange pausiert — {0}",
|
||||||
"throttleFormat": "Warteschlange gedrosselt: {0}/{1} Slots ({2})",
|
"throttleFormat": "Warteschlange gedrosselt: {0}/{1} Slots ({2})",
|
||||||
"resetIn": "Reset in {0} ({1})",
|
"resetIn": "Reset in {0} ({1})",
|
||||||
"dragHint": "Marker ziehen oder Wert unten eintragen.",
|
"dragHint": "Marker ziehen oder Wert unten eintragen.",
|
||||||
"legendSoft": "Soft · 2 Slots",
|
"legendSoft": "Soft · 2 Slots",
|
||||||
"legendHard": "Hard · 1 Slot",
|
"legendHard": "Hard · 1 Slot",
|
||||||
"legendGate": "Gate · Pause",
|
"legendGate": "Limit-Stopp · Pause",
|
||||||
"gaugeSession": "Session (5 Std.)",
|
"gaugeSession": "Session (5 Std.)",
|
||||||
"gaugeWeeklyAll": "Woche (alle Modelle)",
|
"gaugeWeeklyAll": "Woche (alle Modelle)",
|
||||||
"gaugeWeeklyScopedFormat": "Woche ({0})",
|
"gaugeWeeklyScopedFormat": "Woche ({0})",
|
||||||
@@ -524,7 +621,7 @@
|
|||||||
"columnOtherOut": "ÜBRIGES OUT",
|
"columnOtherOut": "ÜBRIGES OUT",
|
||||||
"columnOtherCache": "ÜBRIGES CACHE",
|
"columnOtherCache": "ÜBRIGES CACHE",
|
||||||
"columnShare": "ANTEIL",
|
"columnShare": "ANTEIL",
|
||||||
"columnTask": "TASK",
|
"columnTask": "AUFGABE",
|
||||||
"columnList": "LISTE",
|
"columnList": "LISTE",
|
||||||
"columnModelShort": "MODELL",
|
"columnModelShort": "MODELL",
|
||||||
"columnRuns": "RUNS",
|
"columnRuns": "RUNS",
|
||||||
@@ -628,35 +725,61 @@
|
|||||||
"checks": {
|
"checks": {
|
||||||
"git": {
|
"git": {
|
||||||
"title": "Git",
|
"title": "Git",
|
||||||
"hint": "Installiere Git und stelle sicher, dass es im PATH liegt."
|
"hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu.",
|
||||||
|
"notFound": "git wurde nicht im PATH gefunden.",
|
||||||
|
"exitCode": "'{0}' wurde mit Exitcode {1} beendet.",
|
||||||
|
"resolvedFormat": "{0} — {1}"
|
||||||
},
|
},
|
||||||
"gitIdentity": {
|
"gitIdentity": {
|
||||||
"title": "Git-Identität",
|
"title": "Git-Identität",
|
||||||
"hint": "Lege deine Git-Identität fest: git config --global user.name \"...\" und user.email \"...\"."
|
"hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\"",
|
||||||
|
"notFound": "git wurde nicht gefunden — Identität konnte nicht geprüft werden.",
|
||||||
|
"missing": "Fehlende Git-Identität: {0}."
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Ports",
|
"title": "Ports",
|
||||||
"hint": "Gib den Port frei oder ändere ihn nach der Installation auf der Worker-Seite."
|
"hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess.",
|
||||||
|
"inUse": "{0}-Port {1} ist belegt.",
|
||||||
|
"inUseBy": "{0}-Port {1} ist belegt von '{2}'.",
|
||||||
|
"inUseByOwnWorker": "Belegt vom laufenden ClaudeDo Worker: {0}.",
|
||||||
|
"available": "Ports sind verfügbar."
|
||||||
},
|
},
|
||||||
"writeAccess": {
|
"writeAccess": {
|
||||||
"title": "Schreibzugriff",
|
"title": "Schreibzugriff",
|
||||||
"hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer als Administrator."
|
"hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator).",
|
||||||
|
"cannotWrite": "Kein Schreibzugriff auf '{0}': {1}",
|
||||||
|
"writable": "Installations- und Datenverzeichnis sind beschreibbar."
|
||||||
},
|
},
|
||||||
"claudeCli": {
|
"claudeCli": {
|
||||||
"title": "Claude CLI",
|
"title": "Claude CLI",
|
||||||
"hint": "Installiere die Claude CLI und stelle sicher, dass sie im PATH liegt."
|
"hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift.",
|
||||||
|
"notFound": "'{0}' wurde nicht im PATH gefunden. Durchsucht: {1}.",
|
||||||
|
"exitCode": "'{0}' wurde mit Exitcode {1} beendet.",
|
||||||
|
"resolvedFormat": "{0} — {1}"
|
||||||
},
|
},
|
||||||
"claudeVersion": {
|
"claudeVersion": {
|
||||||
"title": "Claude-CLI-Version",
|
"title": "Claude-CLI-Version",
|
||||||
"hint": "Aktualisiere die Claude CLI auf eine neuere Version."
|
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code",
|
||||||
|
"notFound": "Die claude-CLI wurde nicht gefunden — Version konnte nicht geprüft werden.",
|
||||||
|
"parseFailed": "Konnte keine Version aus '{0}' auslesen.",
|
||||||
|
"tooOld": "claude {0} ist älter als die erforderliche Version {1}.",
|
||||||
|
"ok": "claude {0} erfüllt die Mindestanforderung ({1})."
|
||||||
},
|
},
|
||||||
"claudeAuth": {
|
"claudeAuth": {
|
||||||
"title": "Claude-Anmeldung",
|
"title": "Claude-CLI-Login",
|
||||||
"hint": "Führe 'claude auth login' aus, um dich anzumelden."
|
"hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab.",
|
||||||
|
"notFound": "Die claude-CLI wurde nicht gefunden — Login konnte nicht geprüft werden.",
|
||||||
|
"undetermined": "Login-Status konnte nicht ermittelt werden.",
|
||||||
|
"notLoggedIn": "Nicht angemeldet.",
|
||||||
|
"loggedIn": "Angemeldet."
|
||||||
},
|
},
|
||||||
"permissionModeAuto": {
|
"permissionModeAuto": {
|
||||||
"title": "Berechtigungsmodus",
|
"title": "Berechtigungsmodus",
|
||||||
"hint": "Aktualisiere die Claude CLI auf eine Version, die --permission-mode auto unterstützt."
|
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen.",
|
||||||
|
"notFound": "Die claude-CLI wurde nicht gefunden — Unterstützung für permission-mode konnte nicht geprüft werden.",
|
||||||
|
"undetermined": "Konnte nicht ermitteln, ob --permission-mode auto unterstützt wird.",
|
||||||
|
"ok": "--permission-mode auto wird von dieser CLI erkannt.",
|
||||||
|
"notListed": "Diese claude-CLI-Version listet 'auto' nicht als --permission-mode-Option."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"planning": {
|
"planning": {
|
||||||
@@ -684,6 +807,9 @@
|
|||||||
"abort": "Merge abbrechen"
|
"abort": "Merge abbrechen"
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
|
"markdown": {
|
||||||
|
"copy": "Befehl kopieren"
|
||||||
|
},
|
||||||
"datePicker": {
|
"datePicker": {
|
||||||
"today": "Heute",
|
"today": "Heute",
|
||||||
"tomorrow": "Morgen",
|
"tomorrow": "Morgen",
|
||||||
@@ -693,6 +819,14 @@
|
|||||||
"done": "Fertig"
|
"done": "Fertig"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dialog": {
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"ok": "OK",
|
||||||
|
"confirmTitle": "Bestätigen",
|
||||||
|
"errorTitle": "Fehler"
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"menu": {
|
"menu": {
|
||||||
"help": "Hilfe",
|
"help": "Hilfe",
|
||||||
@@ -700,9 +834,18 @@
|
|||||||
"repositories": "Repositories",
|
"repositories": "Repositories",
|
||||||
"checkForUpdates": "Nach Updates suchen",
|
"checkForUpdates": "Nach Updates suchen",
|
||||||
"restartWorker": "Worker neu starten",
|
"restartWorker": "Worker neu starten",
|
||||||
|
"updateClaudeCli": "Claude CLI aktualisieren",
|
||||||
|
"shutdown": "Beenden",
|
||||||
"worktrees": "Worktrees…",
|
"worktrees": "Worktrees…",
|
||||||
"weeklyReport": "Wochenbericht…",
|
"weeklyReport": "Wochenbericht…",
|
||||||
"usageMonitor": "Usage Monitor…",
|
"usageMonitor": "Usage Monitor…",
|
||||||
|
"cheatSheets": "Cheat Sheets",
|
||||||
|
"cheatSheetGit": "Git…",
|
||||||
|
"cheatSheetCommits": "Conventional Commits…",
|
||||||
|
"cheatSheetClaude": "Claude CLI…",
|
||||||
|
"cheatSheetClaudeConfig": "Claude-Konfiguration…",
|
||||||
|
"cheatSheetDotnet": "dotnet CLI…",
|
||||||
|
"cheatSheetPowershell": "PowerShell…",
|
||||||
"about": "Über…",
|
"about": "Über…",
|
||||||
"addRepos": "Repos als Listen hinzufügen…"
|
"addRepos": "Repos als Listen hinzufügen…"
|
||||||
},
|
},
|
||||||
@@ -711,9 +854,6 @@
|
|||||||
"updateNow": "Jetzt aktualisieren",
|
"updateNow": "Jetzt aktualisieren",
|
||||||
"dismiss": "Ausblenden"
|
"dismiss": "Ausblenden"
|
||||||
},
|
},
|
||||||
"staleWorker": {
|
|
||||||
"message": "Der Worker läuft auf einem älteren Stand als der gemergte main-Branch dieses Repos — Neustart nötig, damit gemergte Änderungen wirken."
|
|
||||||
},
|
|
||||||
"externalMerge": {
|
"externalMerge": {
|
||||||
"banner": "Eine Claude-Session löst gerade einen Merge-Konflikt auf — bitte keine Dateien im Repository bearbeiten.",
|
"banner": "Eine Claude-Session löst gerade einen Merge-Konflikt auf — bitte keine Dateien im Repository bearbeiten.",
|
||||||
"open": "Resolver öffnen"
|
"open": "Resolver öffnen"
|
||||||
@@ -721,19 +861,19 @@
|
|||||||
},
|
},
|
||||||
"vm": {
|
"vm": {
|
||||||
"connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" },
|
"connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" },
|
||||||
"shell": { "restartingWorker": "Worker wird neu gestartet…", "unexpectedError": "Unerwarteter Fehler: {0}" },
|
"shell": { "unexpectedError": "Unerwarteter Fehler: {0}", "shutdownConfirm": "Dadurch wird der ClaudeDo-Worker beendet (laufende Task-Runs und interaktive Sitzungen brechen ab) und die App geschlossen. Fortfahren?" },
|
||||||
"mainWindow": { "closeConfirm": { "message": "{0} Mission-Control-Sitzung(en) laufen noch. Beim Beenden werden sie abgebrochen. Trotzdem schließen?" } },
|
"mainWindow": { "closeConfirm": { "message": "{0} Mission-Control-Sitzung(en) laufen noch. Beim Beenden werden sie abgebrochen. Trotzdem schließen?" } },
|
||||||
"agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "review": "Prüfung", "children": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
|
"agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "review": "Prüfung", "children": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
|
||||||
"taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "waitingForChildren": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "parked": "Geparkt", "interactive": "Interaktiv" },
|
"taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "waitingForChildren": "Wartet auf Teilaufgaben", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "parked": "Geparkt", "interactive": "Interaktiv", "waiting": "Wartet" },
|
||||||
"failureReason": { "maxTurns": "Turn-Limit erreicht", "timeout": "Zeitüberschreitung", "usageLimit": "Nutzungslimit erreicht", "error": "Fehler", "cancelled": "Abgebrochen", "unknown": "Grund unbekannt" },
|
"failureReason": { "maxTurns": "Turn-Limit erreicht", "timeout": "Zeitüberschreitung", "usageLimit": "Nutzungslimit erreicht", "error": "Fehler", "cancelled": "Abgebrochen", "unknown": "Grund unbekannt" },
|
||||||
"failureReasonTooltip": { "maxTurns": "Turn-Limit erreicht ({0}/{1} Turns) — der Worktree ist meist brauchbar; Task fortsetzen statt zurücksetzen." },
|
"failureReasonTooltip": { "maxTurns": "Turn-Limit erreicht ({0}/{1} Turns) — der Worktree ist meist brauchbar; Task fortsetzen statt zurücksetzen." },
|
||||||
"planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" },
|
"planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" },
|
||||||
"taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" },
|
"taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" },
|
||||||
"queue": { "baseDirtyWarning": "Achtung: Das Repo dieser Liste hat uncommittete Änderungen ({0} geändert, {1} untracked) — ein neuer Worktree startet vom letzten Commit und enthält sie nicht." },
|
"queue": { "baseDirtyWarning": "Achtung: Das Repo dieser Liste hat uncommittete Änderungen ({0} geändert, {1} untracked) — ein neuer Worktree startet vom letzten Commit und enthält sie nicht." },
|
||||||
"tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "planningResumeFailed": "Planungssitzung konnte nicht fortgesetzt werden: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}", "cancelReviewFailed": "Prüfung abbrechen fehlgeschlagen: {0}", "sendToQueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "queuePlanBlockedInteractive": "Plan kann nicht in die Warteschlange gestellt werden — {0} hat eine offene interaktive Sitzung und muss zuerst geschlossen werden.", "moveRunningRejected": "Ein laufender Task kann nicht in eine andere Liste verschoben werden.", "moveWorktreeRejected": "Verschieben nicht möglich — dieser Task hat einen aktiven Worktree, der auf sein aktuelles Repo zeigt.", "moveRepoConfirm": "Unterschiedliche Repos — {0} → {1}. Task trotzdem verschieben?", "moveConfirmUnavailable": "Verschieben nicht möglich — der Bestätigungsdialog ist nicht verfügbar.", "quickClaudeNoWorkingDir": "Für diese Liste ist kein Arbeitsverzeichnis konfiguriert.", "quickClaudeDirMissing": "Arbeitsverzeichnis existiert nicht mehr: {0}", "setStatusFailed": "Status konnte nicht aktualisiert werden: {0}", "cancelFailed": "Abbrechen fehlgeschlagen: {0}", "rejectToQueueFailed": "Zurückweisen in die Warteschlange fehlgeschlagen: {0}", "rejectToIdleFailed": "Zurückweisen fehlgeschlagen: {0}", "deleteTaskConfirm": "\"{0}\" löschen? Dies kann nicht rückgängig gemacht werden.", "deleteTaskFailed": "Löschen fehlgeschlagen: {0}", "toggleDoneFailed": "Erledigt-Status konnte nicht aktualisiert werden: {0}", "removeFromQueueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
|
"tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "statusPill": "{0} laufend · {1} Prüfung", "openTaskOne": "1 offene Aufgabe", "openTasks": "{0} offene Aufgaben", "planningResumeFailed": "Planungssitzung konnte nicht fortgesetzt werden: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}", "cancelReviewFailed": "Prüfung abbrechen fehlgeschlagen: {0}", "sendToQueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "queuePlanBlockedInteractive": "Plan kann nicht in die Warteschlange gestellt werden — {0} hat eine offene interaktive Sitzung und muss zuerst geschlossen werden.", "moveRunningRejected": "Ein laufender Task kann nicht in eine andere Liste verschoben werden.", "moveWorktreeRejected": "Verschieben nicht möglich — dieser Task hat einen aktiven Worktree, der auf sein aktuelles Repo zeigt.", "moveRepoConfirm": "Unterschiedliche Repos — {0} → {1}. Task trotzdem verschieben?", "moveConfirmUnavailable": "Verschieben nicht möglich — der Bestätigungsdialog ist nicht verfügbar.", "quickClaudeNoWorkingDir": "Für diese Liste ist kein Arbeitsverzeichnis konfiguriert.", "quickClaudeDirMissing": "Arbeitsverzeichnis existiert nicht mehr: {0}", "setStatusFailed": "Status konnte nicht aktualisiert werden: {0}", "cancelFailed": "Abbrechen fehlgeschlagen: {0}", "rejectToQueueFailed": "Zurückweisen in die Warteschlange fehlgeschlagen: {0}", "rejectToIdleFailed": "Zurückweisen fehlgeschlagen: {0}", "deleteTaskConfirm": "\"{0}\" löschen? Dies kann nicht rückgängig gemacht werden.", "deleteTaskFailed": "Löschen fehlgeschlagen: {0}", "toggleDoneFailed": "Erledigt-Status konnte nicht aktualisiert werden: {0}", "removeFromQueueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
|
||||||
"diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen.", "unavailable": "Diff nicht mehr verfügbar — Commit-Bereich unvollständig." },
|
"diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen.", "unavailable": "Diff nicht mehr verfügbar — Commit-Bereich unvollständig." },
|
||||||
"planningDiff": { "hubError": "Kombinierte Vorschau konnte nicht erstellt werden (Hub-Fehler).", "conflict": "Kombinierte Vorschau nicht möglich: Teilaufgabe {0} steht im Konflikt mit einer früheren Teilaufgabe ({1} Dateien).", "buildFailed": "Kombinierte Vorschau konnte nicht erstellt werden: {0}" },
|
"planningDiff": { "hubError": "Kombinierte Vorschau konnte nicht erstellt werden (Hub-Fehler).", "conflict": "Kombinierte Vorschau nicht möglich: Teilaufgabe {0} steht im Konflikt mit einer früheren Teilaufgabe ({1} Dateien).", "buildFailed": "Kombinierte Vorschau konnte nicht erstellt werden: {0}" },
|
||||||
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Wird zusammengeführt…", "progressVerifying": "Verify-Kommando der Liste läuft… ({0})", "workerOfflineBranches": "Worker offline — Branches können nicht aufgelistet werden.", "loadBranchesFailed": "Branches konnten nicht geladen werden: {0}", "merged": "Zusammengeführt.", "blocked": "Blockiert: {0}", "verifyFailed": "Merge ist gelandet, aber das Verify-Kommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf Erledigt gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" },
|
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Wird zusammengeführt…", "progressVerifying": "Verify-Kommando der Liste läuft… ({0})", "workerOfflineBranches": "Worker offline — Branches können nicht aufgelistet werden.", "loadBranchesFailed": "Branches konnten nicht geladen werden: {0}", "merged": "Zusammengeführt.", "blocked": "Blockiert: {0}", "verifyFailed": "Merge ist gelandet, aber das Verify-Kommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf Erledigt gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "dirtyTarget": "Merge abgelehnt — das Ziel-Arbeitsverzeichnis hat uncommittete Änderungen.", "targetDirty": "Das Ziel-Arbeitsverzeichnis hat bereits uncommittete Änderungen — stashen und mergen?", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" },
|
||||||
"settingsModal": { "workerOffline": "Worker offline — Einstellungen schreibgeschützt.", "saveFailed": "Speichern fehlgeschlagen: {0}" },
|
"settingsModal": { "workerOffline": "Worker offline — Einstellungen schreibgeschützt.", "saveFailed": "Speichern fehlgeschlagen: {0}" },
|
||||||
"onlineInbox": { "workerOffline": "Worker offline — Konfiguration kann nicht geladen werden.", "saved": "Konfiguration gespeichert.", "saveFailed": "Speichern fehlgeschlagen: {0}", "signedIn": "Erfolgreich angemeldet.", "signedInNoRole": "Angemeldet, aber diesem Konto fehlt die Rolle 'user' in Zitadel — die Online-Synchronisierung wird abgelehnt, bis die Rolle im ClaudeDo-Projekt zugewiesen wird.", "signInFailed": "Anmeldung fehlgeschlagen: {0}", "signedOut": "Abgemeldet.", "signOutFailed": "Abmeldung fehlgeschlagen: {0}" },
|
"onlineInbox": { "workerOffline": "Worker offline — Konfiguration kann nicht geladen werden.", "saved": "Konfiguration gespeichert.", "saveFailed": "Speichern fehlgeschlagen: {0}", "signedIn": "Erfolgreich angemeldet.", "signedInNoRole": "Angemeldet, aber diesem Konto fehlt die Rolle 'user' in Zitadel — die Online-Synchronisierung wird abgelehnt, bis die Rolle im ClaudeDo-Projekt zugewiesen wird.", "signInFailed": "Anmeldung fehlgeschlagen: {0}", "signedOut": "Abgemeldet.", "signOutFailed": "Abmeldung fehlgeschlagen: {0}" },
|
||||||
"tickets": { "connectionOk": "Verbunden als {0}.", "connectionMissingScopes": "Verbunden als {0}, aber es fehlen Scopes: {1}", "tokenCleared": "Token entfernt.", "workerOffline": "Worker nicht erreichbar." },
|
"tickets": { "connectionOk": "Verbunden als {0}.", "connectionMissingScopes": "Verbunden als {0}, aber es fehlen Scopes: {1}", "tokenCleared": "Token entfernt.", "workerOffline": "Worker nicht erreichbar." },
|
||||||
@@ -752,12 +892,12 @@
|
|||||||
"sessionSkillsTab": { "installed": "Installiert: {0}", "installFailed": "Installation fehlgeschlagen: {0}", "updated": "Aktualisiert.", "updateFailed": "Aktualisierung fehlgeschlagen: {0}", "removed": "Entfernt.", "removeFailed": "Entfernen fehlgeschlagen: {0}" },
|
"sessionSkillsTab": { "installed": "Installiert: {0}", "installFailed": "Installation fehlgeschlagen: {0}", "updated": "Aktualisiert.", "updateFailed": "Aktualisierung fehlgeschlagen: {0}", "removed": "Entfernt.", "removeFailed": "Entfernen fehlgeschlagen: {0}" },
|
||||||
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "{0} Worktree(s) entfernt.", "blocked": "Zwangsentfernung nicht möglich: {0} Aufgabe(n) laufen noch. Brich sie zuerst ab.", "removedFrom": "{0} Worktree(s) von {1} Aufgabe(n) entfernt.", "cleanupFailed": "Aufräumen fehlgeschlagen: {0}", "resetFailed": "Zurücksetzen fehlgeschlagen: {0}" },
|
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "{0} Worktree(s) entfernt.", "blocked": "Zwangsentfernung nicht möglich: {0} Aufgabe(n) laufen noch. Brich sie zuerst ab.", "removedFrom": "{0} Worktree(s) von {1} Aufgabe(n) entfernt.", "cleanupFailed": "Aufräumen fehlgeschlagen: {0}", "resetFailed": "Zurücksetzen fehlgeschlagen: {0}" },
|
||||||
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "Liste", "cleanupFailed": "Aufräumen fehlgeschlagen.", "cleanupFailedDetailed": "Aufräumen fehlgeschlagen: {0}", "removed": "{0} Worktree(s) entfernt.", "discardConfirm": "Worktree für \"{0}\" verwerfen? Nicht committete Arbeit geht verloren.", "discardFailed": "Worktree konnte nicht verworfen werden.", "keepFailed": "Worktree konnte nicht behalten werden.", "cannotForceRunning": "Eine laufende Aufgabe kann nicht zwangsweise entfernt werden.", "forceRemoveFailed": "Zwangsentfernung fehlgeschlagen.", "forceRemoveFailedDetailed": "Zwangsentfernung fehlgeschlagen: {0}", "batchDone": "{0} gemergt, {1} zu lösen.", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "Liste", "cleanupFailed": "Aufräumen fehlgeschlagen.", "cleanupFailedDetailed": "Aufräumen fehlgeschlagen: {0}", "removed": "{0} Worktree(s) entfernt.", "discardConfirm": "Worktree für \"{0}\" verwerfen? Nicht committete Arbeit geht verloren.", "discardFailed": "Worktree konnte nicht verworfen werden.", "keepFailed": "Worktree konnte nicht behalten werden.", "cannotForceRunning": "Eine laufende Aufgabe kann nicht zwangsweise entfernt werden.", "forceRemoveFailed": "Zwangsentfernung fehlgeschlagen.", "forceRemoveFailedDetailed": "Zwangsentfernung fehlgeschlagen: {0}", "batchDone": "{0} gemergt, {1} zu lösen.", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
||||||
"listSettings": { "untitled": "Unbenannt", "ticketProjectNone": "— keins —" },
|
"listSettings": { "untitled": "Unbenannt", "ticketProjectNone": "— keins —", "loadFailed": "Listeneinstellungen konnten nicht geladen werden: {0}", "ticketProjectsLoadFailed": "Ticket-Projekte konnten nicht geladen werden: {0}", "confirmDeleteFolder": "Ordner \"{0}\" löschen? Die Listen und Unterordner darin bleiben erhalten und wandern auf die oberste Ebene.", "confirmDeleteList": "Liste \"{0}\" und alle ihre Aufgaben löschen? Das kann nicht rückgängig gemacht werden.", "deleteBlockedPlanning": "Diese Liste hat Planungssitzungen mit Kind-Aufgaben. Erst diese verwerfen, dann die Liste löschen.", "workingDirMissing": "Dieser Ordner existiert nicht.", "workingDirNotRepo": "Dieser Ordner ist noch kein Git-Repository. Führe darin zuerst `git init` aus, oder leere das Arbeitsverzeichnis, um dies als manuelle Liste zu speichern." },
|
||||||
"detailsIsland": { "verifyFailed": "Merge ist erfolgt, aber das Verifikationskommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf 'Erledigt' gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "stopOffline": "Worker offline — Task kann nicht gestoppt werden.", "stopFailed": "Stoppen fehlgeschlagen: {0}", "enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "dequeueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "resetAndRetryFailed": "Zurücksetzen & erneut versuchen fehlgeschlagen: {0}", "toggleDoneFailed": "Erledigt-Status konnte nicht aktualisiert werden: {0}" },
|
"detailsIsland": { "verifyFailed": "Merge ist erfolgt, aber das Verifikationskommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf 'Erledigt' gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "dirtyTarget": "Merge abgelehnt — das Ziel-Arbeitsverzeichnis hat uncommittete Änderungen.", "stashAndMergeConfirm": "Das Ziel-Arbeitsverzeichnis hat uncommittete Änderungen.\nStashen, mergen und danach wiederherstellen?", "stopOffline": "Worker offline — Task kann nicht gestoppt werden.", "stopFailed": "Stoppen fehlgeschlagen: {0}", "enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "dequeueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "resetAndRetryFailed": "Zurücksetzen & erneut versuchen fehlgeschlagen: {0}", "toggleDoneFailed": "Erledigt-Status konnte nicht aktualisiert werden: {0}" },
|
||||||
"mergeSection": { "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
|
"mergeSection": { "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
|
||||||
"about": { "openPathFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
"about": { "openPathFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
||||||
"lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
"lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "smartNotes": "Notizen", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
|
||||||
"repoImport": { "loadFailed": "Gespeicherte Ordner konnten nicht geladen werden: {0}", "saveFailed": "Ordner konnten nicht gespeichert werden: {0}" }
|
"repoImport": { "loadFailed": "Gespeicherte Ordner konnten nicht geladen werden: {0}", "saveFailed": "Ordner konnten nicht gespeichert werden: {0}", "createButton": "{0} Liste(n) erstellen" }
|
||||||
},
|
},
|
||||||
"ops": {
|
"ops": {
|
||||||
"stalledHint": "Läuft weiter — Worker antwortet noch",
|
"stalledHint": "Läuft weiter — Worker antwortet noch",
|
||||||
@@ -825,39 +965,5 @@
|
|||||||
"lastError": "Letzter Fehler: {0}",
|
"lastError": "Letzter Fehler: {0}",
|
||||||
"throttled": "Gedrosselt: {0}/{1} Slots ({2})"
|
"throttled": "Gedrosselt: {0}/{1} Slots ({2})"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"checks": {
|
|
||||||
"git": {
|
|
||||||
"title": "Git",
|
|
||||||
"hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu."
|
|
||||||
},
|
|
||||||
"gitIdentity": {
|
|
||||||
"title": "Git-Identität",
|
|
||||||
"hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\""
|
|
||||||
},
|
|
||||||
"ports": {
|
|
||||||
"title": "Ports",
|
|
||||||
"hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess."
|
|
||||||
},
|
|
||||||
"writeAccess": {
|
|
||||||
"title": "Schreibzugriff",
|
|
||||||
"hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator)."
|
|
||||||
},
|
|
||||||
"claudeCli": {
|
|
||||||
"title": "Claude CLI",
|
|
||||||
"hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift."
|
|
||||||
},
|
|
||||||
"claudeVersion": {
|
|
||||||
"title": "Claude-CLI-Version",
|
|
||||||
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code"
|
|
||||||
},
|
|
||||||
"claudeAuth": {
|
|
||||||
"title": "Claude-CLI-Login",
|
|
||||||
"hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab."
|
|
||||||
},
|
|
||||||
"permissionModeAuto": {
|
|
||||||
"title": "Unterstützung für --permission-mode auto",
|
|
||||||
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"categoryGeneral": "General",
|
"categoryGeneral": "General",
|
||||||
"categoryExecution": "Execution",
|
"categoryExecution": "Execution",
|
||||||
"categoryPrime": "Prime Claude",
|
"categoryPrime": "Prime Claude",
|
||||||
|
"categoryPrimeHint": "Scheduled Claude runs: Ping, Fill My Day, or a custom prompt, at fixed times.",
|
||||||
"categoryWorktrees": "Worktrees",
|
"categoryWorktrees": "Worktrees",
|
||||||
"categoryFiles": "Files & Prompts",
|
"categoryFiles": "Files & Prompts",
|
||||||
"categorySkills": "Session Skills",
|
"categorySkills": "Session Skills",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"model": "Model",
|
"model": "Model",
|
||||||
"maxTurns": "Max turns",
|
"maxTurns": "Max turns",
|
||||||
"effort": "Effort",
|
"effort": "Effort",
|
||||||
|
"effortHint": "Reasoning effort the model applies. Higher costs more and reasons deeper.",
|
||||||
"modelPresets": "Per-model defaults",
|
"modelPresets": "Per-model defaults",
|
||||||
"modelPresetsHint": "Reasoning effort and turn budget used when a session runs under that model. List- and task-level max-turn overrides still win.",
|
"modelPresetsHint": "Reasoning effort and turn budget used when a session runs under that model. List- and task-level max-turn overrides still win.",
|
||||||
"maxTurnsCeiling": "Max turns ceiling",
|
"maxTurnsCeiling": "Max turns ceiling",
|
||||||
@@ -41,6 +43,16 @@
|
|||||||
"usageGateSevenDayPct": "7-day window (%)",
|
"usageGateSevenDayPct": "7-day window (%)",
|
||||||
"autoContinueOnUsageLimit": "Continue on session limit reset",
|
"autoContinueOnUsageLimit": "Continue on session limit reset",
|
||||||
"autoContinueOnUsageLimitHint": "When a run stops because it hit the usage limit, automatically continue it once the 5-hour window resets. Also wakes the paused queue exactly at reset time instead of waiting for the next backstop check.",
|
"autoContinueOnUsageLimitHint": "When a run stops because it hit the usage limit, automatically continue it once the 5-hour window resets. Also wakes the paused queue exactly at reset time instead of waiting for the next backstop check.",
|
||||||
|
"listHandlerMaxHandoffs": "List-handler handoff budget",
|
||||||
|
"listHandlerMaxHandoffsHint": "How many wait/merge handoffs one \"Let Claude handle it\" run may chain. 4 means two merge rounds. Raise it for runs meant to keep going for hours; it bounds a runaway chain, so it cannot be 0.",
|
||||||
|
"powerUseHeading": "PowerUse",
|
||||||
|
"powerUse": "Enable PowerUse",
|
||||||
|
"powerUseHint": "Spend the rest of the window: a list-handler run asks nothing, resolves conflicts itself, harvests new work, and keeps handing off for as long as its budget allows. Only affects lists that allowed PowerUse in their list settings.",
|
||||||
|
"powerUseWindow": "Only between",
|
||||||
|
"powerUseWindowPlaceholder": "HH:mm",
|
||||||
|
"powerUseHandoffPct": "Hand off at (% of 5h)",
|
||||||
|
"powerUseWindowHint": "Leave both times empty for round the clock. A window is checked whenever a handler phase starts, so a chain that runs past the end time continues without PowerUse.",
|
||||||
|
"powerUseWindowInvalid": "PowerUse window times must be HH:mm, or left blank.",
|
||||||
"throttleStagesHeading": "Throttle stages",
|
"throttleStagesHeading": "Throttle stages",
|
||||||
"throttleStagesHint": "Parallelism is capped before the hard gate: soft caps at 2 slots, hard at 1. Edited by dragging the markers on the usage-monitor gauges. Opening the monitor closes these settings — save first if you changed anything.",
|
"throttleStagesHint": "Parallelism is capped before the hard gate: soft caps at 2 slots, hard at 1. Edited by dragging the markers on the usage-monitor gauges. Opening the monitor closes these settings — save first if you changed anything.",
|
||||||
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
|
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
|
||||||
@@ -85,6 +97,7 @@
|
|||||||
"retryPrompt": "Retry",
|
"retryPrompt": "Retry",
|
||||||
"dailyPrepPrompt": "Daily prep",
|
"dailyPrepPrompt": "Daily prep",
|
||||||
"weeklyReportPrompt": "Weekly report",
|
"weeklyReportPrompt": "Weekly report",
|
||||||
|
"powerUsePrompt": "PowerUse addendum",
|
||||||
"viewPrompt": "View",
|
"viewPrompt": "View",
|
||||||
"customizedSection": "CUSTOMIZED",
|
"customizedSection": "CUSTOMIZED",
|
||||||
"customizedHint": "These prompts differ from the bundled default and are kept as-is. Reset to pick up future default improvements.",
|
"customizedHint": "These prompts differ from the bundled default and are kept as-is. Reset to pick up future default improvements.",
|
||||||
@@ -177,7 +190,6 @@
|
|||||||
"listSettingsTip": "List settings",
|
"listSettingsTip": "List settings",
|
||||||
"addPlaceholder": "Add a task…",
|
"addPlaceholder": "Add a task…",
|
||||||
"enterKey": "ENTER",
|
"enterKey": "ENTER",
|
||||||
"notesPinnedRow": "Notes (daily notes)",
|
|
||||||
"clearDayTip": "Clear day",
|
"clearDayTip": "Clear day",
|
||||||
"planMyDayTip": "Plan My Day",
|
"planMyDayTip": "Plan My Day",
|
||||||
"overdue": "OVERDUE",
|
"overdue": "OVERDUE",
|
||||||
@@ -209,6 +221,7 @@
|
|||||||
"ctxRefineTask": "Refine task",
|
"ctxRefineTask": "Refine task",
|
||||||
"ctxPlanning": "Planning",
|
"ctxPlanning": "Planning",
|
||||||
"ctxSchedule": "Schedule",
|
"ctxSchedule": "Schedule",
|
||||||
|
"ctxMoveToList": "Move to list",
|
||||||
"ctxDeleteTask": "Delete task",
|
"ctxDeleteTask": "Delete task",
|
||||||
"reasonAlreadyRunning": "Task is already running",
|
"reasonAlreadyRunning": "Task is already running",
|
||||||
"reasonAlreadyQueued": "Already in the queue",
|
"reasonAlreadyQueued": "Already in the queue",
|
||||||
@@ -229,6 +242,7 @@
|
|||||||
"toggleSubtasksTip": "Expand / collapse subtasks",
|
"toggleSubtasksTip": "Expand / collapse subtasks",
|
||||||
"agentSuggestedTip": "Suggested by the agent",
|
"agentSuggestedTip": "Suggested by the agent",
|
||||||
"interactiveChipTip": "Interactive session open — click to jump to it in Mission Control",
|
"interactiveChipTip": "Interactive session open — click to jump to it in Mission Control",
|
||||||
|
"waitingOnTip": "Waiting on {0}",
|
||||||
"scheduleTitle": "Schedule task",
|
"scheduleTitle": "Schedule task",
|
||||||
"scheduleWhen": "WHEN",
|
"scheduleWhen": "WHEN",
|
||||||
"scheduleConfirm": "Schedule",
|
"scheduleConfirm": "Schedule",
|
||||||
@@ -250,6 +264,8 @@
|
|||||||
"contextOpenFindings": "Open findings folder",
|
"contextOpenFindings": "Open findings folder",
|
||||||
"contextImportTickets": "Update from ticket system",
|
"contextImportTickets": "Update from ticket system",
|
||||||
"contextSettings": "Settings…",
|
"contextSettings": "Settings…",
|
||||||
|
"contextFolderSettings": "Folder settings…",
|
||||||
|
"contextRemoveFromFolder": "Move out of folder",
|
||||||
"newList": "New list",
|
"newList": "New list",
|
||||||
"addReposTip": "Add repos as lists",
|
"addReposTip": "Add repos as lists",
|
||||||
"noRepoBannerText": "No repo linked yet. ClaudeDo runs tasks in a git repo.",
|
"noRepoBannerText": "No repo linked yet. ClaudeDo runs tasks in a git repo.",
|
||||||
@@ -261,11 +277,11 @@
|
|||||||
"killSessionTip": "Kill the running session",
|
"killSessionTip": "Kill the running session",
|
||||||
"sendToQueue": "Send to queue",
|
"sendToQueue": "Send to queue",
|
||||||
"sendToQueueTip": "Queue this task so Claude picks it up on the next run",
|
"sendToQueueTip": "Queue this task so Claude picks it up on the next run",
|
||||||
"closeTip": "Close",
|
|
||||||
"copyTaskIdTip": "Copy task ID",
|
"copyTaskIdTip": "Copy task ID",
|
||||||
"starTip": "Star",
|
"starTip": "Star",
|
||||||
"agentSettingsTip": "Agent settings",
|
"agentSettingsTip": "Agent settings",
|
||||||
"agentSettingsHeading": "Agent settings (overrides)",
|
"agentSettingsHeading": "Agent settings (overrides)",
|
||||||
|
"waitingOnPrefix": "Waiting on",
|
||||||
"copyFormattedTip": "Copy title, description and open steps",
|
"copyFormattedTip": "Copy title, description and open steps",
|
||||||
"toggleEditPreviewTip": "Toggle edit/preview",
|
"toggleEditPreviewTip": "Toggle edit/preview",
|
||||||
"planDay": "Plan day",
|
"planDay": "Plan day",
|
||||||
@@ -291,7 +307,37 @@
|
|||||||
"steps": "Steps",
|
"steps": "Steps",
|
||||||
"files": "Files"
|
"files": "Files"
|
||||||
},
|
},
|
||||||
"emptyState": "Select a task to see its details"
|
"emptyState": "Select a task to see its details",
|
||||||
|
"roadblockLabel": "ROADBLOCK",
|
||||||
|
"stepsCard": {
|
||||||
|
"preview": "Preview",
|
||||||
|
"edit": "Edit",
|
||||||
|
"addStepPlaceholder": "Add step…"
|
||||||
|
},
|
||||||
|
"workConsole": {
|
||||||
|
"continue": "Continue",
|
||||||
|
"resetAndRetry": "Reset & Retry",
|
||||||
|
"tabOutput": "Output",
|
||||||
|
"tabGit": "Git",
|
||||||
|
"tabSession": "Session",
|
||||||
|
"outcomeLabel": "OUTCOME",
|
||||||
|
"reviewLabel": "REVIEW",
|
||||||
|
"mergeLabel": "MERGE",
|
||||||
|
"targetBranch": "Target branch",
|
||||||
|
"openDiff": "Open Diff",
|
||||||
|
"worktree": "Worktree",
|
||||||
|
"reviewCombinedDiff": "Review Combined Diff",
|
||||||
|
"openDiffHint": "Open the diff to enable merge",
|
||||||
|
"targetDirty": "Target working directory has uncommitted changes — merging will stash and restore them.",
|
||||||
|
"approveMerge": "Approve & Merge",
|
||||||
|
"park": "Park",
|
||||||
|
"parkTip": "Set aside — back to Idle, keeps the worktree",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"resetDiscardBranch": "Reset (discard branch)…",
|
||||||
|
"submitForReviewHint": "Worked on this by hand? Submit the worktree for review to merge it.",
|
||||||
|
"submitForReview": "Submit for review",
|
||||||
|
"outcomesLabel": "OUTCOMES"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"agent": {
|
"agent": {
|
||||||
"openWorktreeTip": "Open worktree in file explorer — requires an active worktree",
|
"openWorktreeTip": "Open worktree in file explorer — requires an active worktree",
|
||||||
@@ -300,8 +346,11 @@
|
|||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"add": "Add",
|
"yesterday": "Yesterday",
|
||||||
"newNotePlaceholder": "New note…"
|
"newNotePlaceholder": "New note…",
|
||||||
|
"emptyHint": "Nothing noted yet. Type, hit Enter, done.",
|
||||||
|
"deleteTip": "Delete note",
|
||||||
|
"fromAgentTip": "Written by Claude"
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"chipLive": "LIVE",
|
"chipLive": "LIVE",
|
||||||
@@ -320,14 +369,33 @@
|
|||||||
"newSession": "New session",
|
"newSession": "New session",
|
||||||
"empty": "No running tasks",
|
"empty": "No running tasks",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"queue": "Queue",
|
"activity": "Activity",
|
||||||
"blocked": "Blocked",
|
"blocked": "Blocked",
|
||||||
"running": "Running",
|
"running": "Running",
|
||||||
|
"review": "In review",
|
||||||
|
"failed": "Failed",
|
||||||
|
"roadblock": "Roadblock reported",
|
||||||
|
"openSession": "Open session",
|
||||||
|
"homeFolder": "Home (quick session)",
|
||||||
|
"browseForFolder": "Choose folder…",
|
||||||
|
"recentSessions": "Recent sessions",
|
||||||
|
"recentSessionsEmpty": "No sessions in the last 14 days",
|
||||||
|
"resumeSession": "Resume",
|
||||||
|
"showBrief": "Brief",
|
||||||
|
"handoffSession": "Handoff",
|
||||||
|
"briefOpenFailed": "Couldn't open the brief: {0}",
|
||||||
"focusMode": "Focus",
|
"focusMode": "Focus",
|
||||||
"overviewMode": "Overview",
|
"overviewMode": "Overview",
|
||||||
|
"columns": "Columns",
|
||||||
|
"columnsAuto": "Auto",
|
||||||
"closeSession": "Kill session",
|
"closeSession": "Kill session",
|
||||||
|
"closeSessionConfirm": "End the running session? The Claude process will be aborted.",
|
||||||
"conptyLaunchFailed": "Couldn't open interactive session: {0}",
|
"conptyLaunchFailed": "Couldn't open interactive session: {0}",
|
||||||
"conptyStarting": "Starting session…",
|
"conptyStarting": "Starting session…",
|
||||||
|
"conptyExited": "Session ended (code {0}).",
|
||||||
|
"usageWarningNotDelivered": "Couldn't warn the list-handler session about the usage limit.",
|
||||||
|
"terminalSendFailed": "Couldn't send that to the terminal session.",
|
||||||
|
"deferredHandoffTitleSuffix": "waiting for limit reset ({0})",
|
||||||
"mergeHelperTitle": "Merge Helper",
|
"mergeHelperTitle": "Merge Helper",
|
||||||
"mergeHelperHandoffTitleSuffix": " (Handoff)",
|
"mergeHelperHandoffTitleSuffix": " (Handoff)",
|
||||||
"mergeHelperTaskTitle": "List handler: {0}",
|
"mergeHelperTaskTitle": "List handler: {0}",
|
||||||
@@ -343,6 +411,10 @@
|
|||||||
"enqueueAlreadyOpen": "Can't queue — this task has an open interactive session.",
|
"enqueueAlreadyOpen": "Can't queue — this task has an open interactive session.",
|
||||||
"enqueueFailed": "Queue failed: {0}",
|
"enqueueFailed": "Queue failed: {0}",
|
||||||
"queueRefreshFailed": "Couldn't refresh the queue: {0}",
|
"queueRefreshFailed": "Couldn't refresh the queue: {0}",
|
||||||
|
"abandonRun": "Abandon run",
|
||||||
|
"abandonRunTip": "Cancel this run's still-open tasks and close the session",
|
||||||
|
"abandonRunConfirm": "Cancel {0} open task(s) and close this session? Tasks already in review or done are not affected.",
|
||||||
|
"abandonRunFailed": "Couldn't cancel the open tasks: {0}",
|
||||||
"question": {
|
"question": {
|
||||||
"title": "Claude is asking",
|
"title": "Claude is asking",
|
||||||
"placeholder": "Type your answer…",
|
"placeholder": "Type your answer…",
|
||||||
@@ -376,13 +448,22 @@
|
|||||||
"body": "ClaudeDo can't reach the background worker. It is normally started automatically at logon. You can start it now, or reinstall if the problem persists.",
|
"body": "ClaudeDo can't reach the background worker. It is normally started automatically at logon. You can start it now, or reinstall if the problem persists.",
|
||||||
"dismiss": "Dismiss",
|
"dismiss": "Dismiss",
|
||||||
"rerunInstaller": "Rerun Installer",
|
"rerunInstaller": "Rerun Installer",
|
||||||
"startWorker": "Start Worker"
|
"startWorker": "Start Worker",
|
||||||
|
"workerNotFound": "Worker executable not found. Check the installation.",
|
||||||
|
"installerNotFound": "Installer not found. Check the installation.",
|
||||||
|
"startFailed": "Start failed: {0}"
|
||||||
},
|
},
|
||||||
"listSettings": {
|
"listSettings": {
|
||||||
"title": "LIST SETTINGS",
|
"title": "LIST SETTINGS",
|
||||||
|
"folderTitle": "FOLDER",
|
||||||
"deleteList": "Delete list",
|
"deleteList": "Delete list",
|
||||||
|
"deleteFolder": "Delete folder",
|
||||||
|
"createAsFolder": "Create a folder instead",
|
||||||
|
"createAsFolderHint": "A folder just groups lists in the sidebar — it holds no tasks and runs nothing. Drag lists onto it to fill it.",
|
||||||
"sectionGeneral": "GENERAL",
|
"sectionGeneral": "GENERAL",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"folderColor": "Colour",
|
||||||
|
"folderColorHint": "Tints the folder name and its guide line in the sidebar.",
|
||||||
"workingDirectory": "Working directory",
|
"workingDirectory": "Working directory",
|
||||||
"workingDirectoryPlaceholder": "(none)",
|
"workingDirectoryPlaceholder": "(none)",
|
||||||
"browse": "Browse...",
|
"browse": "Browse...",
|
||||||
@@ -391,6 +472,13 @@
|
|||||||
"manualListHint": "New tasks in this list start out manual: no queueing, running or refining, and automation skips them. You can still open a hand-driven session.",
|
"manualListHint": "New tasks in this list start out manual: no queueing, running or refining, and automation skips them. You can still open a hand-driven session.",
|
||||||
"findingsTracked": "Commit the .claudedo findings folder",
|
"findingsTracked": "Commit the .claudedo findings folder",
|
||||||
"findingsTrackedHint": "Off: the folder stays out of git via .git/info/exclude. On: findings travel with the repo.",
|
"findingsTrackedHint": "Off: the folder stays out of git via .git/info/exclude. On: findings travel with the repo.",
|
||||||
|
"powerUseAllowed": "Allow PowerUse for this list",
|
||||||
|
"powerUseAllowedHint": "Lets a list-handler run on this list go unattended when PowerUse is switched on in Settings → Execution: it asks nothing, resolves conflicts itself, and harvests new work. Both switches are needed, so leaving PowerUse on globally never changes a list that hasn't opted in.",
|
||||||
|
"sectionIcon": "ICON",
|
||||||
|
"icon": "List icon",
|
||||||
|
"iconHint": "Shown next to the list name in the sidebar.",
|
||||||
|
"iconSearchWatermark": "Search all icons (English, e.g. rocket, car, bug)",
|
||||||
|
"iconSearchTruncated": "Showing the first 200 matches — refine the search.",
|
||||||
"sectionAgent": "AGENT",
|
"sectionAgent": "AGENT",
|
||||||
"resetAgentSettings": "Reset agent settings",
|
"resetAgentSettings": "Reset agent settings",
|
||||||
"sectionVerify": "VERIFICATION",
|
"sectionVerify": "VERIFICATION",
|
||||||
@@ -407,7 +495,8 @@
|
|||||||
"targetBranch": "Target branch",
|
"targetBranch": "Target branch",
|
||||||
"removeWorktree": "Remove worktree after merge",
|
"removeWorktree": "Remove worktree after merge",
|
||||||
"commitMessage": "Commit message",
|
"commitMessage": "Commit message",
|
||||||
"conflictedFiles": "Conflicted files:"
|
"conflictedFiles": "Conflicted files:",
|
||||||
|
"stashAndMerge": "Stash changes and merge"
|
||||||
},
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
"title": "DIFF",
|
"title": "DIFF",
|
||||||
@@ -463,6 +552,8 @@
|
|||||||
"windowTitle": "Let Claude handle it",
|
"windowTitle": "Let Claude handle it",
|
||||||
"scopeList": "List: {0}",
|
"scopeList": "List: {0}",
|
||||||
"scopeReview": "All lists — tasks waiting for review",
|
"scopeReview": "All lists — tasks waiting for review",
|
||||||
|
"modelLabel": "Model",
|
||||||
|
"modelTip": "Model for the triage session (dedupe & refine). Later merge phases always run on opus.",
|
||||||
"confirm": "Let Claude handle it",
|
"confirm": "Let Claude handle it",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"selectAll": "Select all",
|
"selectAll": "Select all",
|
||||||
@@ -490,12 +581,18 @@
|
|||||||
"regenerate": "Regenerate",
|
"regenerate": "Regenerate",
|
||||||
"emptyStateHint": "No report for this range yet. Click “Generate”."
|
"emptyStateHint": "No report for this range yet. Click “Generate”."
|
||||||
},
|
},
|
||||||
|
"cheatSheets": {
|
||||||
|
"title": "CHEAT SHEETS",
|
||||||
|
"windowTitle": "Cheat Sheets",
|
||||||
|
"pin": "Pin",
|
||||||
|
"pinTooltip": "Keep this window on top of the others",
|
||||||
|
"missing": "Cheat sheet {0} not found."
|
||||||
|
},
|
||||||
"usageMonitor": {
|
"usageMonitor": {
|
||||||
"title": "USAGE MONITOR",
|
"title": "USAGE MONITOR",
|
||||||
"windowTitle": "Usage Monitor",
|
"windowTitle": "Usage Monitor",
|
||||||
"noGauges": "No usage limits reported yet.",
|
"noGauges": "No usage limits reported yet.",
|
||||||
"refresh": "Refresh now",
|
"refreshHint": "Refresh now — polled every 5 min while a task runs, otherwise every 15 min.",
|
||||||
"refreshHint": "Polled every 5 min while a task runs, otherwise every 15 min.",
|
|
||||||
"staleFormat": "Values stale (as of {0})",
|
"staleFormat": "Values stale (as of {0})",
|
||||||
"staleGateHint": "The gate does not apply while values are stale.",
|
"staleGateHint": "The gate does not apply while values are stale.",
|
||||||
"gateBlockedFormat": "Queue paused — {0}",
|
"gateBlockedFormat": "Queue paused — {0}",
|
||||||
@@ -628,35 +725,61 @@
|
|||||||
"checks": {
|
"checks": {
|
||||||
"git": {
|
"git": {
|
||||||
"title": "Git",
|
"title": "Git",
|
||||||
"hint": "Install Git and make sure it is on PATH."
|
"hint": "Install Git from https://git-scm.com/downloads, then restart the installer.",
|
||||||
|
"notFound": "git was not found on PATH.",
|
||||||
|
"exitCode": "'{0}' exited with code {1}.",
|
||||||
|
"resolvedFormat": "{0} — {1}"
|
||||||
},
|
},
|
||||||
"gitIdentity": {
|
"gitIdentity": {
|
||||||
"title": "Git Identity",
|
"title": "Git Identity",
|
||||||
"hint": "Set your git identity: git config --global user.name \"...\" and user.email \"...\"."
|
"hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\"",
|
||||||
|
"notFound": "git was not found — identity could not be checked.",
|
||||||
|
"missing": "Missing git identity: {0}."
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Ports",
|
"title": "Ports",
|
||||||
"hint": "Free the port, or change it on the Worker page after install."
|
"hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them.",
|
||||||
|
"inUse": "{0} port {1} is in use.",
|
||||||
|
"inUseBy": "{0} port {1} is in use by '{2}'.",
|
||||||
|
"inUseByOwnWorker": "In use by the running ClaudeDo Worker: {0}.",
|
||||||
|
"available": "Ports are available."
|
||||||
},
|
},
|
||||||
"writeAccess": {
|
"writeAccess": {
|
||||||
"title": "Write Access",
|
"title": "Write Access",
|
||||||
"hint": "Choose a different install directory, or run the installer as administrator."
|
"hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator).",
|
||||||
|
"cannotWrite": "Cannot write to '{0}': {1}",
|
||||||
|
"writable": "Install directory and data directory are writable."
|
||||||
},
|
},
|
||||||
"claudeCli": {
|
"claudeCli": {
|
||||||
"title": "Claude CLI",
|
"title": "Claude CLI",
|
||||||
"hint": "Install the Claude CLI and make sure it is on PATH."
|
"hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect.",
|
||||||
|
"notFound": "'{0}' was not found on PATH. Searched: {1}.",
|
||||||
|
"exitCode": "'{0}' exited with code {1}.",
|
||||||
|
"resolvedFormat": "{0} — {1}"
|
||||||
},
|
},
|
||||||
"claudeVersion": {
|
"claudeVersion": {
|
||||||
"title": "Claude CLI Version",
|
"title": "Claude CLI Version",
|
||||||
"hint": "Update the Claude CLI to a newer version."
|
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code",
|
||||||
|
"notFound": "The claude CLI was not found — version could not be checked.",
|
||||||
|
"parseFailed": "Could not parse a version from '{0}'.",
|
||||||
|
"tooOld": "claude {0} is older than the required {1}.",
|
||||||
|
"ok": "claude {0} meets the minimum ({1})."
|
||||||
},
|
},
|
||||||
"claudeAuth": {
|
"claudeAuth": {
|
||||||
"title": "Claude Login",
|
"title": "Claude CLI Login",
|
||||||
"hint": "Run 'claude auth login' to sign in."
|
"hint": "Run 'claude' once and complete the login flow.",
|
||||||
|
"notFound": "The claude CLI was not found — login could not be checked.",
|
||||||
|
"undetermined": "Login status could not be determined.",
|
||||||
|
"notLoggedIn": "Not logged in.",
|
||||||
|
"loggedIn": "Logged in."
|
||||||
},
|
},
|
||||||
"permissionModeAuto": {
|
"permissionModeAuto": {
|
||||||
"title": "Permission Mode",
|
"title": "Permission Mode",
|
||||||
"hint": "Update the Claude CLI to a version that supports --permission-mode auto."
|
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts.",
|
||||||
|
"notFound": "The claude CLI was not found — permission-mode support could not be checked.",
|
||||||
|
"undetermined": "Could not determine whether --permission-mode auto is supported.",
|
||||||
|
"ok": "--permission-mode auto is recognized by this CLI.",
|
||||||
|
"notListed": "This claude CLI version does not list 'auto' as a --permission-mode choice."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"planning": {
|
"planning": {
|
||||||
@@ -684,6 +807,9 @@
|
|||||||
"abort": "Abort merge"
|
"abort": "Abort merge"
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
|
"markdown": {
|
||||||
|
"copy": "Copy command"
|
||||||
|
},
|
||||||
"datePicker": {
|
"datePicker": {
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"tomorrow": "Tomorrow",
|
"tomorrow": "Tomorrow",
|
||||||
@@ -693,6 +819,14 @@
|
|||||||
"done": "Done"
|
"done": "Done"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dialog": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"delete": "Delete",
|
||||||
|
"ok": "OK",
|
||||||
|
"confirmTitle": "Confirm",
|
||||||
|
"errorTitle": "Error"
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"menu": {
|
"menu": {
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
@@ -700,9 +834,18 @@
|
|||||||
"repositories": "Repositories",
|
"repositories": "Repositories",
|
||||||
"checkForUpdates": "Check for updates",
|
"checkForUpdates": "Check for updates",
|
||||||
"restartWorker": "Restart worker",
|
"restartWorker": "Restart worker",
|
||||||
|
"updateClaudeCli": "Update Claude CLI",
|
||||||
|
"shutdown": "Shutdown",
|
||||||
"worktrees": "Worktrees…",
|
"worktrees": "Worktrees…",
|
||||||
"weeklyReport": "Weekly Report…",
|
"weeklyReport": "Weekly Report…",
|
||||||
"usageMonitor": "Usage Monitor…",
|
"usageMonitor": "Usage Monitor…",
|
||||||
|
"cheatSheets": "Cheat Sheets",
|
||||||
|
"cheatSheetGit": "Git…",
|
||||||
|
"cheatSheetCommits": "Conventional Commits…",
|
||||||
|
"cheatSheetClaude": "Claude CLI…",
|
||||||
|
"cheatSheetClaudeConfig": "Claude configuration…",
|
||||||
|
"cheatSheetDotnet": "dotnet CLI…",
|
||||||
|
"cheatSheetPowershell": "PowerShell…",
|
||||||
"about": "About…",
|
"about": "About…",
|
||||||
"addRepos": "Add repos as lists…"
|
"addRepos": "Add repos as lists…"
|
||||||
},
|
},
|
||||||
@@ -711,9 +854,6 @@
|
|||||||
"updateNow": "Update now",
|
"updateNow": "Update now",
|
||||||
"dismiss": "Dismiss"
|
"dismiss": "Dismiss"
|
||||||
},
|
},
|
||||||
"staleWorker": {
|
|
||||||
"message": "The worker is running an older build than this repo's merged main — restart it so your merged changes take effect."
|
|
||||||
},
|
|
||||||
"externalMerge": {
|
"externalMerge": {
|
||||||
"banner": "A Claude session is resolving a merge conflict — don't edit files in the repository.",
|
"banner": "A Claude session is resolving a merge conflict — don't edit files in the repository.",
|
||||||
"open": "Open resolver"
|
"open": "Open resolver"
|
||||||
@@ -721,19 +861,19 @@
|
|||||||
},
|
},
|
||||||
"vm": {
|
"vm": {
|
||||||
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
|
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
|
||||||
"shell": { "restartingWorker": "Restarting worker…", "unexpectedError": "Unexpected error: {0}" },
|
"shell": { "unexpectedError": "Unexpected error: {0}", "shutdownConfirm": "This stops the ClaudeDo worker (any running task or interactive session is interrupted) and closes the app. Continue?" },
|
||||||
"mainWindow": { "closeConfirm": { "message": "{0} Mission Control session(s) are still running. Closing the app will end them. Close anyway?" } },
|
"mainWindow": { "closeConfirm": { "message": "{0} Mission Control session(s) are still running. Closing the app will end them. Close anyway?" } },
|
||||||
"agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "review": "Review", "children": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
|
"agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "review": "Review", "children": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
|
||||||
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "waitingForChildren": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled", "parked": "Parked", "interactive": "Interactive" },
|
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "waitingForChildren": "Waiting for Subtasks", "done": "Done", "failed": "Failed", "cancelled": "Cancelled", "parked": "Parked", "interactive": "Interactive", "waiting": "Waiting" },
|
||||||
"failureReason": { "maxTurns": "Turn limit reached", "timeout": "Timed out", "usageLimit": "Usage limit reached", "error": "Error", "cancelled": "Cancelled", "unknown": "Unknown reason" },
|
"failureReason": { "maxTurns": "Turn limit reached", "timeout": "Timed out", "usageLimit": "Usage limit reached", "error": "Error", "cancelled": "Cancelled", "unknown": "Unknown reason" },
|
||||||
"failureReasonTooltip": { "maxTurns": "Turn limit reached ({0}/{1} turns) — the worktree is usually fine; continue the task instead of resetting it." },
|
"failureReasonTooltip": { "maxTurns": "Turn limit reached ({0}/{1} turns) — the worktree is usually fine; continue the task instead of resetting it." },
|
||||||
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
|
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
|
||||||
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
|
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
|
||||||
"queue": { "baseDirtyWarning": "Heads up: this list's repo has uncommitted changes ({0} modified, {1} untracked) — a new worktree starts from the last commit and won't include them." },
|
"queue": { "baseDirtyWarning": "Heads up: this list's repo has uncommitted changes ({0} modified, {1} untracked) — a new worktree starts from the last commit and won't include them." },
|
||||||
"tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "planningResumeFailed": "Couldn't resume planning session: {0}", "approveFailed": "Approve & merge failed: {0}", "cancelReviewFailed": "Cancel review failed: {0}", "sendToQueueFailed": "Send to queue failed: {0}", "queuePlanBlockedInteractive": "Can't queue the plan — {0} has an open interactive session and must be closed first.", "moveRunningRejected": "Can't move a running task to another list.", "moveWorktreeRejected": "Can't move — this task has an active worktree pointing at its current repo.", "moveRepoConfirm": "Different repos — {0} → {1}. Move the task anyway?", "moveConfirmUnavailable": "Can't move — the confirmation dialog isn't available.", "quickClaudeNoWorkingDir": "This list has no working directory configured.", "quickClaudeDirMissing": "Working directory no longer exists: {0}", "setStatusFailed": "Failed to update status: {0}", "cancelFailed": "Cancel failed: {0}", "rejectToQueueFailed": "Reject to queue failed: {0}", "rejectToIdleFailed": "Reject failed: {0}", "deleteTaskConfirm": "Delete \"{0}\"? This cannot be undone.", "deleteTaskFailed": "Delete failed: {0}", "toggleDoneFailed": "Couldn't update done state: {0}", "removeFromQueueFailed": "Remove from queue failed: {0}", "openWorktreeFailed": "Couldn't open worktree: {0}" },
|
"tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "statusPill": "{0} running · {1} review", "openTaskOne": "1 open task", "openTasks": "{0} open tasks", "planningResumeFailed": "Couldn't resume planning session: {0}", "approveFailed": "Approve & merge failed: {0}", "cancelReviewFailed": "Cancel review failed: {0}", "sendToQueueFailed": "Send to queue failed: {0}", "queuePlanBlockedInteractive": "Can't queue the plan — {0} has an open interactive session and must be closed first.", "moveRunningRejected": "Can't move a running task to another list.", "moveWorktreeRejected": "Can't move — this task has an active worktree pointing at its current repo.", "moveRepoConfirm": "Different repos — {0} → {1}. Move the task anyway?", "moveConfirmUnavailable": "Can't move — the confirmation dialog isn't available.", "quickClaudeNoWorkingDir": "This list has no working directory configured.", "quickClaudeDirMissing": "Working directory no longer exists: {0}", "setStatusFailed": "Failed to update status: {0}", "cancelFailed": "Cancel failed: {0}", "rejectToQueueFailed": "Reject to queue failed: {0}", "rejectToIdleFailed": "Reject failed: {0}", "deleteTaskConfirm": "Delete \"{0}\"? This cannot be undone.", "deleteTaskFailed": "Delete failed: {0}", "toggleDoneFailed": "Couldn't update done state: {0}", "removeFromQueueFailed": "Remove from queue failed: {0}", "openWorktreeFailed": "Couldn't open worktree: {0}" },
|
||||||
"diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show.", "unavailable": "Diff no longer available — commit range incomplete." },
|
"diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show.", "unavailable": "Diff no longer available — commit range incomplete." },
|
||||||
"planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files).", "buildFailed": "Could not build combined preview: {0}" },
|
"planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files).", "buildFailed": "Could not build combined preview: {0}" },
|
||||||
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Merging…", "progressVerifying": "Running the list's verify command… ({0})", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "blocked": "Blocked: {0}", "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" },
|
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Merging…", "progressVerifying": "Running the list's verify command… ({0})", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "blocked": "Blocked: {0}", "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "dirtyTarget": "Merge refused — the target working directory has uncommitted changes.", "targetDirty": "The target working directory already has uncommitted changes — stash them and merge?", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" },
|
||||||
"settingsModal": { "workerOffline": "Worker offline — settings read-only.", "saveFailed": "Save failed: {0}" },
|
"settingsModal": { "workerOffline": "Worker offline — settings read-only.", "saveFailed": "Save failed: {0}" },
|
||||||
"onlineInbox": { "workerOffline": "Worker offline — cannot load config.", "saved": "Config saved.", "saveFailed": "Save failed: {0}", "signedIn": "Signed in successfully.", "signedInNoRole": "Signed in, but this account is missing the 'user' role in Zitadel — online sync will be rejected until the role is granted in the ClaudeDo project.", "signInFailed": "Sign-in failed: {0}", "signedOut": "Signed out.", "signOutFailed": "Sign-out failed: {0}" },
|
"onlineInbox": { "workerOffline": "Worker offline — cannot load config.", "saved": "Config saved.", "saveFailed": "Save failed: {0}", "signedIn": "Signed in successfully.", "signedInNoRole": "Signed in, but this account is missing the 'user' role in Zitadel — online sync will be rejected until the role is granted in the ClaudeDo project.", "signInFailed": "Sign-in failed: {0}", "signedOut": "Signed out.", "signOutFailed": "Sign-out failed: {0}" },
|
||||||
"tickets": { "connectionOk": "Connected as {0}.", "connectionMissingScopes": "Connected as {0}, but missing scopes: {1}", "tokenCleared": "Token removed.", "workerOffline": "Worker not reachable." },
|
"tickets": { "connectionOk": "Connected as {0}.", "connectionMissingScopes": "Connected as {0}, but missing scopes: {1}", "tokenCleared": "Token removed.", "workerOffline": "Worker not reachable." },
|
||||||
@@ -752,12 +892,12 @@
|
|||||||
"sessionSkillsTab": { "installed": "Installed: {0}", "installFailed": "Install failed: {0}", "updated": "Updated.", "updateFailed": "Update failed: {0}", "removed": "Removed.", "removeFailed": "Remove failed: {0}" },
|
"sessionSkillsTab": { "installed": "Installed: {0}", "installFailed": "Install failed: {0}", "updated": "Updated.", "updateFailed": "Update failed: {0}", "removed": "Removed.", "removeFailed": "Remove failed: {0}" },
|
||||||
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s).", "cleanupFailed": "Cleanup failed: {0}", "resetFailed": "Reset failed: {0}" },
|
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s).", "cleanupFailed": "Cleanup failed: {0}", "resetFailed": "Reset failed: {0}" },
|
||||||
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "cleanupFailedDetailed": "Cleanup failed: {0}", "removed": "Removed {0} worktree(s).", "discardConfirm": "Discard the worktree for \"{0}\"? Uncommitted work will be lost.", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed.", "forceRemoveFailedDetailed": "Force remove failed: {0}", "batchDone": "Merged {0}, {1} need resolution.", "openInExplorerFailed": "Couldn't open path: {0}" },
|
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "cleanupFailedDetailed": "Cleanup failed: {0}", "removed": "Removed {0} worktree(s).", "discardConfirm": "Discard the worktree for \"{0}\"? Uncommitted work will be lost.", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed.", "forceRemoveFailedDetailed": "Force remove failed: {0}", "batchDone": "Merged {0}, {1} need resolution.", "openInExplorerFailed": "Couldn't open path: {0}" },
|
||||||
"listSettings": { "untitled": "Untitled", "ticketProjectNone": "— none —" },
|
"listSettings": { "untitled": "Untitled", "ticketProjectNone": "— none —", "loadFailed": "Couldn't load list settings: {0}", "ticketProjectsLoadFailed": "Couldn't load ticket projects: {0}", "confirmDeleteFolder": "Delete folder \"{0}\"? The lists and subfolders inside it are kept and move back to the top level.", "confirmDeleteList": "Delete list \"{0}\" and all its tasks? This cannot be undone.", "deleteBlockedPlanning": "This list has planning sessions with child tasks. Discard those first, then delete the list.", "workingDirMissing": "This folder doesn't exist.", "workingDirNotRepo": "This folder isn't a git repository yet. Run `git init` in it first, or clear the working directory to save this as a manual list." },
|
||||||
"detailsIsland": { "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "stopOffline": "Worker offline — can't stop the task.", "stopFailed": "Stop failed: {0}", "enqueueFailed": "Queue failed: {0}", "dequeueFailed": "Remove from queue failed: {0}", "resetAndRetryFailed": "Reset & retry failed: {0}", "toggleDoneFailed": "Couldn't update done state: {0}" },
|
"detailsIsland": { "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "dirtyTarget": "Merge refused — the target working directory has uncommitted changes.", "stashAndMergeConfirm": "The target working directory has uncommitted changes.\nStash them, merge, and restore them afterwards?", "stopOffline": "Worker offline — can't stop the task.", "stopFailed": "Stop failed: {0}", "enqueueFailed": "Queue failed: {0}", "dequeueFailed": "Remove from queue failed: {0}", "resetAndRetryFailed": "Reset & retry failed: {0}", "toggleDoneFailed": "Couldn't update done state: {0}" },
|
||||||
"mergeSection": { "openWorktreeFailed": "Couldn't open worktree: {0}" },
|
"mergeSection": { "openWorktreeFailed": "Couldn't open worktree: {0}" },
|
||||||
"about": { "openPathFailed": "Couldn't open path: {0}" },
|
"about": { "openPathFailed": "Couldn't open path: {0}" },
|
||||||
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}", "openInExplorerFailed": "Couldn't open path: {0}" },
|
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "smartNotes": "Notes", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}", "openInExplorerFailed": "Couldn't open path: {0}" },
|
||||||
"repoImport": { "loadFailed": "Couldn't load remembered folders: {0}", "saveFailed": "Couldn't save folders: {0}" }
|
"repoImport": { "loadFailed": "Couldn't load remembered folders: {0}", "saveFailed": "Couldn't save folders: {0}", "createButton": "Create {0} list(s)" }
|
||||||
},
|
},
|
||||||
"ops": {
|
"ops": {
|
||||||
"stalledHint": "Still running — worker hasn't responded in a while",
|
"stalledHint": "Still running — worker hasn't responded in a while",
|
||||||
@@ -825,39 +965,5 @@
|
|||||||
"lastError": "Last error: {0}",
|
"lastError": "Last error: {0}",
|
||||||
"throttled": "Throttled: {0}/{1} slots ({2})"
|
"throttled": "Throttled: {0}/{1} slots ({2})"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"checks": {
|
|
||||||
"git": {
|
|
||||||
"title": "Git",
|
|
||||||
"hint": "Install Git from https://git-scm.com/downloads, then restart the installer."
|
|
||||||
},
|
|
||||||
"gitIdentity": {
|
|
||||||
"title": "Git identity",
|
|
||||||
"hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\""
|
|
||||||
},
|
|
||||||
"ports": {
|
|
||||||
"title": "Ports",
|
|
||||||
"hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them."
|
|
||||||
},
|
|
||||||
"writeAccess": {
|
|
||||||
"title": "Write access",
|
|
||||||
"hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator)."
|
|
||||||
},
|
|
||||||
"claudeCli": {
|
|
||||||
"title": "Claude CLI",
|
|
||||||
"hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect."
|
|
||||||
},
|
|
||||||
"claudeVersion": {
|
|
||||||
"title": "Claude CLI version",
|
|
||||||
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code"
|
|
||||||
},
|
|
||||||
"claudeAuth": {
|
|
||||||
"title": "Claude CLI login",
|
|
||||||
"hint": "Run 'claude' once and complete the login flow."
|
|
||||||
},
|
|
||||||
"permissionModeAuto": {
|
|
||||||
"title": "--permission-mode auto support",
|
|
||||||
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,9 @@ public sealed class AppSettings
|
|||||||
public string DiffViewMode { get; set; } = "unified";
|
public string DiffViewMode { get; set; } = "unified";
|
||||||
public bool DiffWrapLines { get; set; }
|
public bool DiffWrapLines { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Mission Control overview column count; 0 = auto (derived from the pane count).</summary>
|
||||||
|
public int MissionControlColumns { get; set; }
|
||||||
|
|
||||||
private static readonly string DefaultConfigPath = Paths.Expand("~/.claudeDo/ui.config.json");
|
private static readonly string DefaultConfigPath = Paths.Expand("~/.claudeDo/ui.config.json");
|
||||||
|
|
||||||
/// Where this instance persists. Instance-level (not static) so tests can redirect it —
|
/// Where this instance persists. Instance-level (not static) so tests can redirect it —
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Claude CLI
|
||||||
|
|
||||||
|
## Session starten
|
||||||
|
- `claude` — interaktive Session im aktuellen Verzeichnis
|
||||||
|
- `claude "<prompt>"` — Session mit erstem Prompt starten
|
||||||
|
- `claude -c` — letzte Session in diesem Verzeichnis fortsetzen
|
||||||
|
- `claude -r` — Session aus einer Auswahlliste fortsetzen
|
||||||
|
- `claude -r <session-id>` — bestimmte Session fortsetzen
|
||||||
|
- `claude --fork-session -r <id>` — Session unter neuer ID fortsetzen
|
||||||
|
- `claude -n "<name>"` — Anzeigename für die Session setzen
|
||||||
|
|
||||||
|
## Modell und Aufwand
|
||||||
|
- `claude --model sonnet` — Alias oder voller Name (`fable`, `opus`, `sonnet`, `claude-opus-5`)
|
||||||
|
- `claude --effort high` — Denkaufwand: `low`, `medium`, `high`, `xhigh`, `max`
|
||||||
|
- `claude --fallback-model sonnet` — Ausweichmodell bei Überlast (nur mit `-p`)
|
||||||
|
|
||||||
|
## Nicht interaktiv
|
||||||
|
- `claude -p "<prompt>"` — antworten und beenden
|
||||||
|
- `claude -p --output-format json` — ein JSON-Ergebnis
|
||||||
|
- `claude -p --output-format stream-json` — Streaming-JSON für Skripte
|
||||||
|
- `claude -p --include-partial-messages` — Teil-Chunks mitstreamen
|
||||||
|
- `claude -p --max-budget-usd 5` — Kostendeckel für den Lauf
|
||||||
|
- `claude -p --json-schema '<schema>'` — strukturierte Ausgabe validieren
|
||||||
|
|
||||||
|
## Berechtigungen und Tools
|
||||||
|
- `claude --permission-mode <mode>` — `acceptEdits`, `auto`, `plan`, `manual`, `dontAsk`, `bypassPermissions`
|
||||||
|
- `claude --allowed-tools "Bash(git *)" Edit` — nur diese Tools erlauben
|
||||||
|
- `claude --disallowed-tools "Bash(rm *)"` — diese Tools sperren
|
||||||
|
- `claude --tools "Bash,Edit,Read"` — Built-in-Toolset festlegen
|
||||||
|
- `claude --add-dir <pfad>` — weiteres Arbeitsverzeichnis freigeben
|
||||||
|
- `claude --restricted` — ohne befehlsausführende Tools und ohne lokale Settings
|
||||||
|
|
||||||
|
## Kontext und Konfiguration
|
||||||
|
- `claude --append-system-prompt "<text>"` — Systemprompt ergänzen
|
||||||
|
- `claude --system-prompt "<text>"` — Systemprompt ersetzen
|
||||||
|
- `claude --settings <datei|json>` — zusätzliche Settings laden
|
||||||
|
- `claude --agents '<json>'` — eigene Subagents für diese Session
|
||||||
|
- `claude --mcp-config <datei>` — MCP-Server aus JSON laden
|
||||||
|
- `claude --strict-mcp-config` — nur diese MCP-Server, alle anderen ignorieren
|
||||||
|
- `claude --safe-mode` — alle Anpassungen aus, zum Fehlersuchen
|
||||||
|
- `claude --bare` — Minimalmodus ohne Hooks, Plugins, CLAUDE.md
|
||||||
|
|
||||||
|
## Hintergrund-Sessions
|
||||||
|
- `claude --bg "<prompt>"` — im Hintergrund starten, gibt die Kurz-ID aus
|
||||||
|
- `claude agents` — laufende Hintergrund-Sessions auflisten
|
||||||
|
- `claude attach <id>` — Hintergrund-Session in dieses Terminal holen
|
||||||
|
- `claude logs <id>` — letzte Ausgabe einer Hintergrund-Session
|
||||||
|
- `claude stop <id>` — Session stoppen, Verlauf bleibt
|
||||||
|
- `claude rm <id>` — Session löschen
|
||||||
|
|
||||||
|
## Worktree
|
||||||
|
- `claude -w` — neuen Git-Worktree für diese Session anlegen
|
||||||
|
- `claude -w <name>` — Worktree mit eigenem Namen
|
||||||
|
|
||||||
|
## Verwaltung
|
||||||
|
- `claude doctor` — Installation prüfen
|
||||||
|
- `claude update` — auf neue Version aktualisieren
|
||||||
|
- `claude mcp` — MCP-Server konfigurieren
|
||||||
|
- `claude plugin` — Plugins verwalten
|
||||||
|
- `claude auth` — Authentifizierung verwalten
|
||||||
|
- `claude setup-token` — Langzeit-Token einrichten
|
||||||
|
- `claude -d api,hooks` — Debugmodus mit Kategoriefilter
|
||||||
|
- `claude -v` — Version ausgeben
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Claude Konfiguration
|
||||||
|
|
||||||
|
## Dateien und Orte
|
||||||
|
- `~/.claude/settings.json` — globale Settings für alle Projekte
|
||||||
|
- `.claude/settings.json` — Projekt-Settings, eingecheckt fürs Team
|
||||||
|
- `.claude/settings.local.json` — persönliche Projekt-Settings, nicht eingecheckt
|
||||||
|
- `Rangfolge: managed > local > projekt > global` — spezifischer gewinnt
|
||||||
|
- `/config` — Settings interaktiv in der Session ansehen und ändern
|
||||||
|
- `claude doctor` — Konfiguration auf Fehler prüfen
|
||||||
|
|
||||||
|
## CLAUDE.md
|
||||||
|
- `~/.claude/CLAUDE.md` — globale Anweisungen für alle Projekte
|
||||||
|
- `CLAUDE.md` — im Projekt-Root, wird bei Start automatisch geladen
|
||||||
|
- `<unterordner>/CLAUDE.md` — wird geladen, sobald dort gearbeitet wird
|
||||||
|
- `@pfad/zur/datei.md` — Import einer weiteren Datei in eine CLAUDE.md
|
||||||
|
- `# <notiz>` — in der Session: Notiz in die CLAUDE.md übernehmen
|
||||||
|
- `/init` — CLAUDE.md für ein Projekt generieren lassen
|
||||||
|
|
||||||
|
## Berechtigungen
|
||||||
|
- `"permissions": { "allow": [...], "deny": [...], "ask": [...] }` — Struktur in settings.json
|
||||||
|
- `Bash(git *)` — Bash-Befehle per Muster erlauben oder sperren
|
||||||
|
- `Edit(src/**)` — Dateizugriff per Glob einschränken
|
||||||
|
- `WebFetch(domain:example.com)` — Webzugriff auf eine Domain begrenzen
|
||||||
|
- `mcp__<server>__<tool>` — Regel für ein einzelnes MCP-Tool
|
||||||
|
- `/permissions` — Regeln in der Session ansehen und ergänzen
|
||||||
|
|
||||||
|
## MCP-Server
|
||||||
|
- `claude mcp add <name> -- <befehl>` — lokalen stdio-Server anlegen
|
||||||
|
- `claude mcp add --transport http <name> <url>` — HTTP-Server anlegen
|
||||||
|
- `claude mcp add --scope project <name> ...` — in `.mcp.json` fürs Team
|
||||||
|
- `claude mcp list` — konfigurierte Server anzeigen
|
||||||
|
- `claude mcp remove <name>` — Server entfernen
|
||||||
|
- `/mcp` — Serverstatus in der Session, OAuth-Login
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
- `"hooks": { "PreToolUse": [...] }` — Shell-Befehle an Lifecycle-Events hängen
|
||||||
|
- `PreToolUse / PostToolUse` — vor und nach jedem Tool-Aufruf
|
||||||
|
- `UserPromptSubmit` — beim Absenden eines Prompts
|
||||||
|
- `SessionStart / Stop` — bei Sessionbeginn und -ende
|
||||||
|
- `Exit-Code 2` — blockiert die Aktion, stderr geht an Claude
|
||||||
|
- `/hooks` — Hooks interaktiv verwalten
|
||||||
|
|
||||||
|
## Erweiterungen
|
||||||
|
- `.claude/commands/<name>.md` — eigener Slash-Command `/name`
|
||||||
|
- `.claude/skills/<name>/SKILL.md` — Skill, wird bei passender Aufgabe geladen
|
||||||
|
- `.claude/agents/<name>.md` — eigener Subagent-Typ
|
||||||
|
- `~/.claude/...` — dieselben Ordner global statt pro Projekt
|
||||||
|
- `claude plugin` — Plugins installieren und verwalten
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Conventional Commits
|
||||||
|
|
||||||
|
## Format
|
||||||
|
- `<type>(<scope>): <beschreibung>` — Grundform, Scope optional
|
||||||
|
- `feat(auth): add login endpoint` — Beispiel mit Scope
|
||||||
|
- `fix: prevent crash on empty input` — Beispiel ohne Scope
|
||||||
|
- `imperativ, klein, kein Punkt am Ende` — Stil der Beschreibung
|
||||||
|
|
||||||
|
## Types
|
||||||
|
- `feat` — neues Feature
|
||||||
|
- `fix` — Bugfix
|
||||||
|
- `refactor` — Umbau ohne Verhaltensänderung
|
||||||
|
- `perf` — Performance-Verbesserung
|
||||||
|
- `docs` — nur Dokumentation
|
||||||
|
- `test` — Tests hinzufügen oder korrigieren
|
||||||
|
- `style` — Formatierung, kein Code-Effekt
|
||||||
|
- `build` — Build-System oder Dependencies
|
||||||
|
- `ci` — CI-Konfiguration
|
||||||
|
- `chore` — Sonstiges ohne src- oder Test-Änderung
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
- `feat(api)!: remove legacy endpoint` — `!` markiert einen Breaking Change
|
||||||
|
- `BREAKING CHANGE: <erklärung>` — alternativ als Footer im Commit-Body
|
||||||
|
|
||||||
|
## Body und Footer
|
||||||
|
- `Leerzeile nach der Betreffzeile` — dann optionaler Body mit dem Warum
|
||||||
|
- `Refs: #123` — Footer für Ticket-Referenzen
|
||||||
|
- `Reverts: <hash>` — Footer für Reverts
|
||||||
|
|
||||||
|
## Warum das Ganze
|
||||||
|
- `feat` — löst ein Minor-Release aus (SemVer)
|
||||||
|
- `fix` — löst ein Patch-Release aus (SemVer)
|
||||||
|
- `!` oder `BREAKING CHANGE` — löst ein Major-Release aus
|
||||||
|
- `git log --oneline --grep "^feat"` — Verlauf wird filterbar
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# dotnet CLI
|
||||||
|
|
||||||
|
## Projekt anlegen
|
||||||
|
- `dotnet new list` — verfügbare Vorlagen anzeigen
|
||||||
|
- `dotnet new console -n <name>` — Projekt aus Vorlage anlegen
|
||||||
|
- `dotnet new sln` — Solution anlegen
|
||||||
|
- `dotnet sln add <projekt>` — Projekt in die Solution aufnehmen
|
||||||
|
|
||||||
|
## Bauen
|
||||||
|
- `dotnet restore` — NuGet-Pakete auflösen
|
||||||
|
- `dotnet build <projekt> -c Release` — Release bauen
|
||||||
|
- `dotnet build --no-restore` — ohne Restore, wenn Pakete aktuell sind
|
||||||
|
- `dotnet build -v minimal` — knappere Ausgabe
|
||||||
|
- `dotnet clean -c Release` — Build-Ausgabe löschen
|
||||||
|
|
||||||
|
## Laufen lassen
|
||||||
|
- `dotnet run --project <projekt> -c Release` — starten
|
||||||
|
- `dotnet run --project <projekt> -- <args>` — Argumente an die App
|
||||||
|
- `dotnet watch --project <projekt> run` — bei Dateiänderung neu starten
|
||||||
|
|
||||||
|
## Testen
|
||||||
|
- `dotnet test <projekt> -c Release --no-build` — Tests ohne Neubau
|
||||||
|
- `dotnet test --filter "FullyQualifiedName~<name>"` — nur passende Tests
|
||||||
|
- `dotnet test --filter "FullyQualifiedName!~<name>"` — Tests ausschließen
|
||||||
|
- `dotnet test --logger "console;verbosity=detailed"` — Details pro Test
|
||||||
|
- `dotnet test --blame-hang-timeout 60s` — hängende Tests aufspüren
|
||||||
|
|
||||||
|
## Pakete
|
||||||
|
- `dotnet add <projekt> package <name>` — Paket hinzufügen
|
||||||
|
- `dotnet add <projekt> package <name> --version 1.2.3` — feste Version
|
||||||
|
- `dotnet remove <projekt> package <name>` — Paket entfernen
|
||||||
|
- `dotnet list <projekt> package --outdated` — veraltete Pakete
|
||||||
|
- `dotnet nuget locals all --clear` — NuGet-Caches leeren
|
||||||
|
|
||||||
|
## EF Core Migrationen
|
||||||
|
- `dotnet ef migrations add <name> -p <daten-projekt>` — Migration anlegen
|
||||||
|
- `dotnet ef migrations list -p <daten-projekt>` — Migrationen anzeigen
|
||||||
|
- `dotnet ef migrations remove -p <daten-projekt>` — letzte Migration zurücknehmen
|
||||||
|
- `dotnet ef database update -p <daten-projekt>` — Schema anwenden
|
||||||
|
|
||||||
|
## Veröffentlichen
|
||||||
|
- `dotnet publish <projekt> -c Release -r win-x64 --self-contained` — eigenständiges Build
|
||||||
|
- `dotnet publish <projekt> -c Release -p:PublishSingleFile=true` — eine Exe
|
||||||
|
|
||||||
|
## Diagnose
|
||||||
|
- `dotnet --info` — SDKs, Runtimes, RID
|
||||||
|
- `dotnet --list-sdks` — installierte SDKs
|
||||||
|
- `dotnet format <projekt>` — Formatierung anwenden
|
||||||
|
- `dotnet build -p:TreatWarningsAsErrors=true` — Warnungen als Fehler
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Git
|
||||||
|
|
||||||
|
## Start
|
||||||
|
- `git clone <url>` — Repository klonen
|
||||||
|
- `git init` — neues Repository anlegen
|
||||||
|
- `git remote add origin <url>` — Remote verbinden
|
||||||
|
|
||||||
|
## Stand ansehen
|
||||||
|
- `git status -sb` — Kurzstatus mit Branch-Zeile
|
||||||
|
- `git diff` — nicht gestagete Änderungen
|
||||||
|
- `git diff --staged` — gestagete Änderungen
|
||||||
|
- `git diff main...HEAD` — alles, was dieser Branch gegenüber main bringt
|
||||||
|
- `git diff --stat` — nur Datei- und Zeilenzähler
|
||||||
|
- `git log --oneline --graph --decorate -20` — kompakter Verlauf
|
||||||
|
- `git log -p -- <pfad>` — Verlauf einer Datei mit Diffs
|
||||||
|
- `git show <commit>` — ein Commit im Detail
|
||||||
|
|
||||||
|
## Branch
|
||||||
|
- `git switch <branch>` — Branch wechseln
|
||||||
|
- `git switch -c <name>` — Branch anlegen und wechseln
|
||||||
|
- `git switch -` — zurück zum vorherigen Branch
|
||||||
|
- `git branch -vv` — Branches mit Upstream und letztem Commit
|
||||||
|
- `git branch -d <name>` — gemergten Branch löschen (`-D` erzwingt)
|
||||||
|
|
||||||
|
## Committen
|
||||||
|
- `git add <pfad>` — Änderungen stagen
|
||||||
|
- `git add -p` — Änderungen hunk-weise stagen
|
||||||
|
- `git commit -m "<text>"` — gestagete Änderungen committen
|
||||||
|
- `git commit -- <pfad>` — nur diese Pfade committen
|
||||||
|
- `git commit --amend --no-edit` — letzten Commit um die Staging-Area erweitern
|
||||||
|
- `git commit --fixup <commit>` — Fixup für späteres Autosquash
|
||||||
|
|
||||||
|
## Remote
|
||||||
|
- `git fetch --all --prune` — alles holen, gelöschte Remote-Branches aufräumen
|
||||||
|
- `git pull --rebase` — holen und eigene Commits obendrauf setzen
|
||||||
|
- `git push -u origin <name>` — pushen und Upstream setzen
|
||||||
|
- `git push --force-with-lease` — sicheres Force-Push, nie plain `--force`
|
||||||
|
|
||||||
|
## Rebase und Merge
|
||||||
|
- `git rebase main` — Branch auf aktuellen main setzen
|
||||||
|
- `git rebase -i --autosquash main` — interaktiv aufräumen
|
||||||
|
- `git rebase --continue` — nach gelöstem Konflikt weiter
|
||||||
|
- `git rebase --abort` — Rebase abbrechen, Ausgangszustand zurück
|
||||||
|
- `git merge --no-ff <branch>` — Merge-Commit erzwingen
|
||||||
|
- `git merge --abort` — laufenden Merge abbrechen
|
||||||
|
- `git cherry-pick <commit>` — einzelnen Commit übernehmen
|
||||||
|
|
||||||
|
## Konflikte
|
||||||
|
- `git diff --name-only --diff-filter=U` — konfliktbehaftete Dateien
|
||||||
|
- `git checkout --ours <pfad>` — eigene Seite übernehmen
|
||||||
|
- `git checkout --theirs <pfad>` — fremde Seite übernehmen
|
||||||
|
- `git mergetool` — konfiguriertes Merge-Tool starten
|
||||||
|
|
||||||
|
## Rückgängig
|
||||||
|
- `git restore <pfad>` — Datei auf HEAD zurücksetzen, Änderungen weg
|
||||||
|
- `git restore --staged <pfad>` — unstagen, Änderungen behalten
|
||||||
|
- `git reset --soft HEAD~1` — letzten Commit auflösen, Änderungen gestaged behalten
|
||||||
|
- `git reset --hard <commit>` — alles verwerfen, nicht rückholbar
|
||||||
|
- `git revert <commit>` — Commit durch Gegen-Commit aufheben
|
||||||
|
- `git reflog` — Referenzverlauf, um verlorene Commits wiederzufinden
|
||||||
|
|
||||||
|
## Stash
|
||||||
|
- `git stash push -m "<text>"` — Änderungen wegpacken
|
||||||
|
- `git stash push -u` — inklusive unversionierter Dateien
|
||||||
|
- `git stash list` — Stashes anzeigen
|
||||||
|
- `git stash pop` — letzten Stash zurückholen und entfernen
|
||||||
|
- `git stash apply <stash>` — anwenden, aber behalten
|
||||||
|
|
||||||
|
## Worktrees
|
||||||
|
- `git worktree list` — alle Worktrees
|
||||||
|
- `git worktree add <pfad> <branch>` — Worktree für bestehenden Branch
|
||||||
|
- `git worktree add -b <name> <pfad>` — Worktree mit neuem Branch
|
||||||
|
- `git worktree remove <pfad>` — Worktree entfernen
|
||||||
|
- `git worktree prune` — verwaiste Einträge aufräumen
|
||||||
|
|
||||||
|
## Suchen
|
||||||
|
- `git grep -n "<text>"` — im Arbeitsbaum suchen
|
||||||
|
- `git log -S"<text>"` — Commits, die den Text einfügen oder entfernen
|
||||||
|
- `git blame -L 40,60 <pfad>` — Zeilenbereich zuordnen
|
||||||
|
- `git bisect start` — binäre Suche nach dem Fehler-Commit
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# PowerShell
|
||||||
|
|
||||||
|
## Navigieren und Dateien
|
||||||
|
- `Get-ChildItem` — Verzeichnis auflisten (`ls`, `-Force` zeigt versteckte)
|
||||||
|
- `Set-Location <pfad>` — Verzeichnis wechseln (`cd`)
|
||||||
|
- `New-Item -ItemType Directory <pfad>` — Ordner anlegen (`mkdir`)
|
||||||
|
- `Copy-Item <quelle> <ziel> -Recurse` — kopieren
|
||||||
|
- `Move-Item <quelle> <ziel>` — verschieben oder umbenennen
|
||||||
|
- `Remove-Item <pfad> -Recurse -Force` — löschen, endgültig
|
||||||
|
- `Test-Path <pfad>` — existiert die Datei oder der Ordner?
|
||||||
|
|
||||||
|
## Ansehen und Suchen
|
||||||
|
- `Get-Content <datei> -Tail 50` — letzte Zeilen (`-Wait` folgt wie tail -f)
|
||||||
|
- `Select-String "<text>" <dateien> -List` — in Dateien suchen (grep)
|
||||||
|
- `Get-ChildItem -Recurse -Filter *.log` — Dateien rekursiv finden
|
||||||
|
- `<befehl> | Select-Object -First 10` — Ausgabe begrenzen (head)
|
||||||
|
- `<befehl> | Where-Object Name -like "*test*"` — Ausgabe filtern
|
||||||
|
- `<befehl> | Measure-Object` — zählen (wc)
|
||||||
|
- `<befehl> | Out-GridView` — Ausgabe in sortierbarem Fenster
|
||||||
|
|
||||||
|
## Prozesse und Dienste
|
||||||
|
- `Get-Process | Sort-Object CPU -Descending | Select-Object -First 10` — Top-Prozesse
|
||||||
|
- `Stop-Process -Name <name>` — Prozess beenden (`-Id <pid>` per PID)
|
||||||
|
- `Get-Service <name>` — Dienststatus
|
||||||
|
- `Restart-Service <name>` — Dienst neu starten (Admin nötig)
|
||||||
|
|
||||||
|
## Netzwerk
|
||||||
|
- `Test-NetConnection <host> -Port 443` — Erreichbarkeit und Port testen
|
||||||
|
- `Get-NetTCPConnection -LocalPort <port>` — wer belegt den Port (OwningProcess = PID)
|
||||||
|
- `Invoke-RestMethod <url>` — HTTP-Aufruf, JSON kommt als Objekt zurück
|
||||||
|
- `Resolve-DnsName <host>` — DNS-Auflösung prüfen
|
||||||
|
|
||||||
|
## Umgebung
|
||||||
|
- `$env:PATH -split ";"` — PATH lesbar ausgeben
|
||||||
|
- `$env:NAME = "wert"` — Variable für diese Session setzen
|
||||||
|
- `[Environment]::SetEnvironmentVariable("NAME", "wert", "User")` — dauerhaft setzen
|
||||||
|
- `Get-Command <name>` — wo liegt ein Befehl (which)
|
||||||
|
- `Get-History` — bisherige Befehle dieser Session
|
||||||
|
|
||||||
|
## Windows-Spezifisches
|
||||||
|
- `explorer .` — aktuellen Ordner im Explorer öffnen
|
||||||
|
- `Start-Process <datei>` — mit Standardprogramm öffnen
|
||||||
|
- `Start-Process pwsh -Verb RunAs` — Terminal als Administrator
|
||||||
|
- `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` — lokale Skripte erlauben
|
||||||
|
- `winget search <name>` — Software im Paketmanager suchen
|
||||||
|
- `winget install <id>` — installieren
|
||||||
|
- `winget upgrade --all` — alles aktualisieren
|
||||||
|
|
||||||
|
## Hilfe
|
||||||
|
- `Get-Help <befehl> -Examples` — Beispiele zu einem Cmdlet
|
||||||
|
- `Get-Member` — Eigenschaften eines Pipeline-Objekts anzeigen
|
||||||
|
- `Get-Alias <kurzform>` — welches Cmdlet steckt hinter einem Alias
|
||||||
@@ -41,6 +41,7 @@ Design/ — Tokens.axaml (design tokens; merged before styles)
|
|||||||
|
|
||||||
- **IslandsShellViewModel** — root coordinator. Owns the three island VMs and the `WorkerClient`, wires cross-island events (selection, notes/prep mode, conflict resolution), connection state, the update banner, the inline worker-log strip (clickable → Log Visualizer overlay; `FlashFooterError` surfaces UI-action failures + the worker's Warn/Error there), responsive-layout flags (`ShowLists`/`ShowDetails` by window width), `PrimeStatus` flash, the modal openers, and `RestartWorkerAsync`/`CheckForUpdatesAsync`. Hosts `UpdateCheckService`.
|
- **IslandsShellViewModel** — root coordinator. Owns the three island VMs and the `WorkerClient`, wires cross-island events (selection, notes/prep mode, conflict resolution), connection state, the update banner, the inline worker-log strip (clickable → Log Visualizer overlay; `FlashFooterError` surfaces UI-action failures + the worker's Warn/Error there), responsive-layout flags (`ShowLists`/`ShowDetails` by window width), `PrimeStatus` flash, the modal openers, and `RestartWorkerAsync`/`CheckForUpdatesAsync`. Hosts `UpdateCheckService`.
|
||||||
- **ListsIslandViewModel** — smart lists (My Day, Important, Planned, virtual queued/running/review), user lists, selection, list CRUD, drag-reorder, badge counts, opens list settings / repo import / worktrees overview, `OpenInExplorer`/`OpenInTerminal`.
|
- **ListsIslandViewModel** — smart lists (My Day, Important, Planned, virtual queued/running/review), user lists, selection, list CRUD, drag-reorder, badge counts, opens list settings / repo import / worktrees overview, `OpenInExplorer`/`OpenInTerminal`.
|
||||||
|
- **Folders:** the sidebar's top level is **one mixed collection**, `TopLevel` (`ObservableCollection<object>`: `ListFolderNavItemViewModel` rows, each with its own `Children`, interleaved with folder-less `ListNavItemViewModel` rows) — that's what lets a list sit *above* a folder. Folders nest arbitrarily deep — a folder's `Children` mixes lists and subfolders the same way `TopLevel` mixes folders and folder-less lists, via `ParentFolderId`. `Folders` is a **recursive** `OfType` projection over the whole tree (not just the top level); `RootLists` stays a `TopLevel`-only projection. `UserLists` stays the **flat superset** every count refresh, row refresh and the no-repo banner work over — a row lives in both. `ContainerOf(row-or-folder)` resolves which collection renders it (non-generic `IList`, since every container is mixed). The view is a single `ItemsControl` over `TopLevel` with **implicit, type-matched `DataTemplate`s in `UserControl.DataTemplates`** — no keyed row template, and the folder's child `ItemsControl` resolves the same templates recursively by walking up to the UserControl (which is also what indents nested folders: each level adds its template's fixed margin). A folder is never selectable: tapping the header runs `ToggleFolderCommand` (persisted to `list_folders.is_collapsed`), and its badge is the sum of all descendant lists, recursive. `DropAsync(source, target, placeBelow)` handles list and folder sources against any target and doubles as the move: a drop position implies a container, so dropping a row onto a row in another folder — or next to *anything* at the top level, folder headers included — reassigns `FolderId`/`ParentFolderId`. A folder dropped onto or next to a target inside its own subtree is silently ignored (self/descendant guard), never nested into itself. `MoveToFolderAsync` is the drop-into-the-header path (reaches collapsed/empty folders, bound to the middle half of the header — the outer quarters insert *next to* the folder instead; also takes a folder source now); `RemoveFromFolderCommand` is its context-menu counterpart. Both end in `PersistOrderAsync` → `ListRepository.ReorderSidebarAsync`/`SetFolderParentAsync`, one `sort_order` sequence spanning **both tables**, walked depth-first. ⚠️ A **folder** drag starts from `PointerMoved` past a 4px threshold using the *stored* `PointerPressedEventArgs` (`DoDragDropAsync` only accepts those) — starting it on press would capture the pointer and swallow the `Tapped` that folds the folder.
|
||||||
- **TasksIslandViewModel** — open/overdue/completed groups for the selected list with hierarchy-aware regrouping; task CRUD, drag-reorder, toggle done/star, schedule, enqueue/dequeue, cancel; review actions; planning session lifecycle; `RefineTask`, `OpenConPtySessionRequested`, `ToggleManual`, `SyncInteractiveSessions`; MyDay extras (`IsMyDayList`, `ClearDayCommand`, `ShowPrepLogCommand`) and the pinned Notes pseudo-row. Raises `NotesRequested`/`PrepRequested` for the shell.
|
- **TasksIslandViewModel** — open/overdue/completed groups for the selected list with hierarchy-aware regrouping; task CRUD, drag-reorder, toggle done/star, schedule, enqueue/dequeue, cancel; review actions; planning session lifecycle; `RefineTask`, `OpenConPtySessionRequested`, `ToggleManual`, `SyncInteractiveSessions`; MyDay extras (`IsMyDayList`, `ClearDayCommand`, `ShowPrepLogCommand`) and the pinned Notes pseudo-row. Raises `NotesRequested`/`PrepRequested` for the shell.
|
||||||
- **DetailsIslandViewModel** — the detail pane for a bound `TaskRowViewModel`. Owns live-log streaming (`Log` via `StreamLineFormatter`), debounced title/description editing, subtasks, the session-outcome/roadblock split, the three-tab work console (`output`/`git`/`session`), child surfacing (`ChildOutcomes` + `ChildrenNeedingAttention` drive an attention band on the Session tab), attachments, and the modes `IsNotesMode`/`IsPrepMode`/computed `IsTaskDetailVisible`. Failures raise `ErrorReported`, wired by the shell into `FlashFooterError`.
|
- **DetailsIslandViewModel** — the detail pane for a bound `TaskRowViewModel`. Owns live-log streaming (`Log` via `StreamLineFormatter`), debounced title/description editing, subtasks, the session-outcome/roadblock split, the three-tab work console (`output`/`git`/`session`), child surfacing (`ChildOutcomes` + `ChildrenNeedingAttention` drive an attention band on the Session tab), attachments, and the modes `IsNotesMode`/`IsPrepMode`/computed `IsTaskDetailVisible`. Failures raise `ErrorReported`, wired by the shell into `FlashFooterError`.
|
||||||
- Three concerns are extracted into section VMs exposed as properties: `AgentSettings` (`AgentConfigEditorViewModel`, scope=Task), `MergeSectionViewModel`, `PrepPanelViewModel`. Helper rows live in the same file.
|
- Three concerns are extracted into section VMs exposed as properties: `AgentSettings` (`AgentConfigEditorViewModel`, scope=Task), `MergeSectionViewModel`, `PrepPanelViewModel`. Helper rows live in the same file.
|
||||||
@@ -55,16 +56,28 @@ Design/ — Tokens.axaml (design tokens; merged before styles)
|
|||||||
| VM | Notes |
|
| VM | Notes |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `SettingsModalViewModel` | Four tabs: General, Worktrees, Files (prompt paths), Prime Claude. General hosts the per-model preset table (`ModelPresetRowViewModel`: effort + max turns per alias) which **replaced** the single global "Max turns" field. The Prime Claude tab gives each schedule row a three-way action radio group (Ping / Fill My Day / Custom) — `RadioButton.GroupName` is bound to a **per-row** `RadioGroup` string, since a shared group name would make every schedule share one selection. `DailyPrepMaxTasks` is disabled unless some row is `FillMyDay`. |
|
| `SettingsModalViewModel` | Four tabs: General, Worktrees, Files (prompt paths), Prime Claude. General hosts the per-model preset table (`ModelPresetRowViewModel`: effort + max turns per alias) which **replaced** the single global "Max turns" field. The Prime Claude tab gives each schedule row a three-way action radio group (Ping / Fill My Day / Custom) — `RadioButton.GroupName` is bound to a **per-row** `RadioGroup` string, since a shared group name would make every schedule share one selection. `DailyPrepMaxTasks` is disabled unless some row is `FillMyDay`. |
|
||||||
| `ListSettingsModalViewModel` | Name, working dir, commit type, "manual list" flag, `VerifyCommand`, delete. Hosts the shared `AgentConfigEditorViewModel` as `Agent` (scope=List) — ⚠️ save delegates to `Agent.SaveAsync(verifyCommand)` because both land in the same `list_config` row via one `UpdateListConfig` call and would otherwise clobber each other. |
|
| `ListSettingsModalViewModel` | Name, working dir, commit type, "manual list" flag, `VerifyCommand`, delete. Hosts the shared `AgentConfigEditorViewModel` as `Agent` (scope=List) — ⚠️ save delegates to `Agent.SaveAsync(verifyCommand)` because both land in the same `list_config` row via one `UpdateListConfig` call and would otherwise clobber each other. **Doubles as the folder editor:** `IsFolder` collapses the body to name + icon, and it's reached either via the create-only "create a folder instead" toggle (`CanToggleFolder`, set by `BeginLoad`) or via `BeginLoadFolder` on an existing folder. There is no list↔folder conversion. Creating a folder writes a `list_folders` row and **deletes the list row the create flow pre-created**, reporting it as `Deleted` so the caller does a full reload. |
|
||||||
| `WeeklyReportModalViewModel` | Range pickers default "since last standup weekday → today", cached per range. |
|
| `WeeklyReportModalViewModel` | Range pickers default "since last standup weekday → today", cached per range. |
|
||||||
| `MergeHelperSelectionModalViewModel` | "Let Claude handle it" picker → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md). |
|
| `MergeHelperSelectionModalViewModel` | "Let Claude handle it" picker → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md). |
|
||||||
| `UsageMonitorModalViewModel` | Opened from the usage pill (shown **before** the data loads via `BeginLoad`); gauges are **dynamic** per `UsageSnapshotDto.Limits` row, and the 5h/7d ones carry three draggable stage markers (soft/hard/gate) via `UsageGaugeBar` + the pure `UsageThresholdDrag`, plus a colour-matched legend with a `NumericUpDown` per stage → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md). The **analytics** part (models/tasks/cost) comes from the external TokenTracker export, not from us: without the CLI the modal shows a hint card with an install button (gated on Node ≥ 20) instead of the totals header. Gauges, gate and throttle are unaffected by it. |
|
| `UsageMonitorModalViewModel` | Opened from the usage pill (shown **before** the data loads via `BeginLoad`); gauges are **dynamic** per `UsageSnapshotDto.Limits` row, and the 5h/7d ones carry three draggable stage markers (soft/hard/gate) via `UsageGaugeBar` + the pure `UsageThresholdDrag`, plus a colour-matched legend with a `NumericUpDown` per stage → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md). The **analytics** part (models/tasks/cost) comes from the external TokenTracker export, not from us: without the CLI the modal shows a hint card with an install button (gated on Node ≥ 20) instead of the totals header. Gauges, gate and throttle are unaffected by it. The modal is **display + threshold drag only** — the PowerUse switch/window/handoff % it used to host moved to Settings → Execution (`GeneralSettingsTabViewModel`), and "Refresh now" is an icon in the titlebar via `ModalShell.HeaderActions` rather than a body row. |
|
||||||
|
|
||||||
Self-explanatory: `RepoImportModalViewModel` (bulk-create lists from git repos; already-wired
|
Self-explanatory: `RepoImportModalViewModel` (bulk-create lists from git repos; already-wired
|
||||||
repos disabled), `MergeModalViewModel`, `WorktreesOverviewModalViewModel`,
|
repos disabled), `MergeModalViewModel`, `WorktreesOverviewModalViewModel`,
|
||||||
`UnfinishedPlanningModalViewModel`, `LogVisualizerViewModel` (last 30 min, all levels + a
|
`UnfinishedPlanningModalViewModel`, `LogVisualizerViewModel` (last 30 min, all levels + a
|
||||||
warn/error filter), `WorkerConnectionModalViewModel`, `AboutModalViewModel`.
|
warn/error filter), `WorkerConnectionModalViewModel`, `AboutModalViewModel`.
|
||||||
|
|
||||||
|
`CheatSheetsViewModel` backs the modeless `CheatSheetsWindow` (Help → Cheat Sheets): content is
|
||||||
|
markdown under `Assets/CheatSheets/*.md` as **EmbeddedResource** (not `AvaloniaResource` —
|
||||||
|
`AssetLoader` throws without an initialized Avalonia app, which would make the VM unconstructible
|
||||||
|
in tests), rendered through `MarkdownView`, with a `Topmost` pin so it can sit docked beside the
|
||||||
|
shell. A new sheet = a new `.md` file + its key in `CheatSheetsViewModel.Keys` (+ a menu entry if
|
||||||
|
it should be directly reachable). `MarkdownView` has no table/fenced-code support, so sheets are
|
||||||
|
bullet lists with inline code — rendered via its opt-in `CommandRows` property, which turns a
|
||||||
|
`` `command` — description `` bullet into a mono chip + copy button + description and wraps each
|
||||||
|
`##` section in a collapsible `Expander` (blocks are added to the open section's panel, not to the
|
||||||
|
host). The flag is opt-in because the other three `MarkdownView` call sites (prompt-file viewer,
|
||||||
|
weekly report, task-description preview) render prose where copy buttons would be noise.
|
||||||
|
|
||||||
## Diff & Conflicts
|
## Diff & Conflicts
|
||||||
|
|
||||||
`UnifiedDiffParser` (static) + `DiffModels.cs` shared types + `DiffViewerViewModel` (one unified
|
`UnifiedDiffParser` (static) + `DiffModels.cs` shared types + `DiffViewerViewModel` (one unified
|
||||||
@@ -79,7 +92,7 @@ single-task and planning unit-merge conflicts. Full detail →
|
|||||||
The two surfaces are **not** variants of one control (read-only 2-way renderer over aligned rows
|
The two surfaces are **not** variants of one control (read-only 2-way renderer over aligned rows
|
||||||
vs. 3-way editor over a writable document), but they share their AvaloniaEdit host plumbing via
|
vs. 3-way editor over a writable document), but they share their AvaloniaEdit host plumbing via
|
||||||
`Views/Controls/DiffEditorSetup.cs`: the process-wide TextMate `Registry`, `InstallHighlighting`,
|
`Views/Controls/DiffEditorSetup.cs`: the process-wide TextMate `Registry`, `InstallHighlighting`,
|
||||||
`ApplyGrammar`, the `Brush` resource fallback, the `Seg` segment, and `VerticalScrollSync`. Put
|
`ApplyGrammar`, the `Brush` resource fallback, the `Seg` segment, and `ScrollSync`. Put
|
||||||
new editor boilerplate there rather than copying it a third time.
|
new editor boilerplate there rather than copying it a third time.
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
@@ -106,6 +119,9 @@ There is no null→bool converter — bind
|
|||||||
Modals use `TaskCompletionSource` results behind the reusable `ModalShell` control — the dialog
|
Modals use `TaskCompletionSource` results behind the reusable `ModalShell` control — the dialog
|
||||||
sets the result on save/cancel, the caller awaits the TCS.
|
sets the result on save/cancel, the caller awaits the TCS.
|
||||||
|
|
||||||
|
`ModalShell.HeaderActions` is the titlebar slot left of maximise/close — for a window-level action
|
||||||
|
that should stay visible without costing a body row (currently only the usage monitor's refresh).
|
||||||
|
|
||||||
`ModalShell` also owns the window chrome for every modal: the titlebar drag goes through
|
`ModalShell` also owns the window chrome for every modal: the titlebar drag goes through
|
||||||
`Window.BeginMoveDrag` (so Windows snap works — drag to the top edge to maximise; the OS only
|
`Window.BeginMoveDrag` (so Windows snap works — drag to the top edge to maximise; the OS only
|
||||||
snaps `CanResize="True"` windows, which is the opt-in), and it insets itself by the window's
|
snaps `CanResize="True"` windows, which is the opt-in), and it insets itself by the window's
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<PackageReference Include="Avalonia" Version="12.0.4" />
|
<PackageReference Include="Avalonia" Version="12.0.4" />
|
||||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="12.0.0" />
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="12.0.0" />
|
||||||
|
<PackageReference Include="Material.Icons" Version="3.0.2" />
|
||||||
<PackageReference Include="TextMateSharp.Grammars" Version="2.0.3" />
|
<PackageReference Include="TextMateSharp.Grammars" Version="2.0.3" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.11" />
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="Duende.IdentityModel.OidcClient" Version="7.1.0" />
|
<PackageReference Include="Duende.IdentityModel.OidcClient" Version="7.1.0" />
|
||||||
<PackageReference Include="Iciclecreek.Avalonia.Terminal" Version="2.0.3" />
|
<PackageReference Include="Iciclecreek.Avalonia.Terminal" Version="2.0.3" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -36,6 +38,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<!-- EmbeddedResource, not AvaloniaResource: reading these needs no initialized Avalonia
|
||||||
|
app, so the VM stays constructible in tests. -->
|
||||||
|
<EmbeddedResource Include="Assets/CheatSheets/*.md" />
|
||||||
<AvaloniaResource Include="Assets/Fonts/*.ttf" />
|
<AvaloniaResource Include="Assets/Fonts/*.ttf" />
|
||||||
<AvaloniaResource Include="Assets/Fonts/OFL-InterTight.txt" />
|
<AvaloniaResource Include="Assets/Fonts/OFL-InterTight.txt" />
|
||||||
<AvaloniaResource Include="Assets/Fonts/OFL-JetBrainsMono.txt" />
|
<AvaloniaResource Include="Assets/Fonts/OFL-JetBrainsMono.txt" />
|
||||||
|
|||||||
@@ -2,27 +2,49 @@ using System.Globalization;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Material.Icons;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Converters;
|
namespace ClaudeDo.Ui.Converters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts an icon key string (e.g. "Sun") to the matching StreamGeometry resource "Icon.Sun".
|
/// Converts an icon key to its geometry: "Sun" → the "Icon.Sun" StreamGeometry resource,
|
||||||
|
/// "mdi:RocketLaunch" → the Material Design Icons path of that <see cref="MaterialIconKind"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IconKeyConverter : IValueConverter
|
public class IconKeyConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
/// <summary>Key prefix marking a Material Design Icons kind.</summary>
|
||||||
|
public const string MdiPrefix = "mdi:";
|
||||||
|
|
||||||
public static IconKeyConverter Instance { get; } = new();
|
public static IconKeyConverter Instance { get; } = new();
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, Geometry?> MdiCache = new();
|
||||||
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (value is not string key || string.IsNullOrEmpty(key))
|
if (value is not string key || string.IsNullOrEmpty(key))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
if (key.StartsWith(MdiPrefix, StringComparison.Ordinal))
|
||||||
|
return MdiGeometry(key[MdiPrefix.Length..]);
|
||||||
|
|
||||||
var resourceKey = $"Icon.{key}";
|
var resourceKey = $"Icon.{key}";
|
||||||
if (Application.Current?.TryGetResource(resourceKey, null, out var res) == true)
|
if (Application.Current?.TryGetResource(resourceKey, null, out var res) == true)
|
||||||
return res as StreamGeometry;
|
return res as StreamGeometry;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Parses (and caches) the geometry for an MDI kind name; null for unknown names.</summary>
|
||||||
|
private static Geometry? MdiGeometry(string name)
|
||||||
|
{
|
||||||
|
if (MdiCache.TryGetValue(name, out var cached))
|
||||||
|
return cached;
|
||||||
|
var geometry = Enum.TryParse<MaterialIconKind>(name, out var kind)
|
||||||
|
? StreamGeometry.Parse(MaterialIconDataProvider.GetData(kind))
|
||||||
|
: null;
|
||||||
|
MdiCache[name] = geometry;
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
=> throw new NotSupportedException();
|
=> throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,24 @@ namespace ClaudeDo.Ui.Converters;
|
|||||||
// instantiate a generic FuncValueConverter as a resource; they stay one per type so the
|
// instantiate a generic FuncValueConverter as a resource; they stay one per type so the
|
||||||
// x:Key names in App.axaml keep resolving. Anything with real logic gets its own file.
|
// x:Key names in App.axaml keep resolving. Anything with real logic gets its own file.
|
||||||
|
|
||||||
|
/// <summary>"#RRGGBB" to a brush; anything else falls back to the app resource named by the parameter.</summary>
|
||||||
|
public sealed class HexBrushConverter : OneWayConverter
|
||||||
|
{
|
||||||
|
public static HexBrushConverter Instance { get; } = new();
|
||||||
|
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> value is string s && Color.TryParse(s, out var c)
|
||||||
|
? new SolidColorBrush(c)
|
||||||
|
: Avalonia.Application.Current?.TryGetResource(parameter as string ?? "", null, out var res) == true ? res : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>True when the value's string form equals the parameter (segmented-switch active state).</summary>
|
||||||
|
public sealed class EqualsParameterConverter : OneWayConverter
|
||||||
|
{
|
||||||
|
public static EqualsParameterConverter Instance { get; } = new();
|
||||||
|
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> Equals(value?.ToString(), parameter as string);
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class UpperCaseConverter : OneWayConverter
|
public sealed class UpperCaseConverter : OneWayConverter
|
||||||
{
|
{
|
||||||
public static UpperCaseConverter Instance { get; } = new();
|
public static UpperCaseConverter Instance { get; } = new();
|
||||||
|
|||||||
@@ -33,8 +33,9 @@
|
|||||||
<!-- All d-strings sourced from icons.jsx -->
|
<!-- All d-strings sourced from icons.jsx -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
<!-- Icon.Sun -->
|
<!-- Icon.Sun — filled disc + 8 rays. The old stroke-only version rendered as a bare dot in a
|
||||||
<StreamGeometry x:Key="Icon.Sun">M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z M12 3v2M12 19v2M3 12h2M19 12h2M5.5 5.5l1.4 1.4M17.1 17.1l1.4 1.4M5.5 18.5l1.4-1.4M17.1 6.9l1.4-1.4</StreamGeometry>
|
PathIcon (verified by rendering it); rays are kept clear of the disc so EvenOdd is safe. -->
|
||||||
|
<StreamGeometry x:Key="Icon.Sun">M7.5 12 C7.5 9.51 9.51 7.5 12 7.5 C14.49 7.5 16.5 9.51 16.5 12 C16.5 14.49 14.49 16.5 12 16.5 C9.51 16.5 7.5 14.49 7.5 12 Z M11 2 H13 V5.5 H11 Z M11 18.5 H13 V22 H11 Z M2 11 H5.5 V13 H2 Z M18.5 11 H22 V13 H18.5 Z M4.22 5.64 L5.64 4.22 L8.11 6.69 L6.69 8.11 Z M18.36 4.22 L19.78 5.64 L17.31 8.11 L15.89 6.69 Z M5.64 19.78 L4.22 18.36 L6.69 15.89 L8.11 17.31 Z M19.78 18.36 L18.36 19.78 L15.89 17.31 L17.31 15.89 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Activity (pulse waveform) — filled outline of the polyline; PathIcon fills, a stroke-only pulse renders as blobs -->
|
<!-- Icon.Activity (pulse waveform) — filled outline of the polyline; PathIcon fills, a stroke-only pulse renders as blobs -->
|
||||||
<StreamGeometry x:Key="Icon.Activity">M3,11 L6.28,11 L9,2.84 L12.86,14.43 L14.46,8.04 L17.41,11 L21,11 L21,13 L16.59,13 L15.54,11.96 L13.14,21.57 L9,9.16 L7.72,13 L3,13 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Activity">M3,11 L6.28,11 L9,2.84 L12.86,14.43 L14.46,8.04 L17.41,11 L21,11 L21,13 L16.59,13 L15.54,11.96 L13.14,21.57 L9,9.16 L7.72,13 L3,13 Z</StreamGeometry>
|
||||||
@@ -42,20 +43,26 @@
|
|||||||
<!-- Icon.Star -->
|
<!-- Icon.Star -->
|
||||||
<StreamGeometry x:Key="Icon.Star">M12 3.5l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Star">M12 3.5l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Calendar -->
|
<!-- Icon.Calendar — solid header band, hollow body, two binder posts and a date block.
|
||||||
<StreamGeometry x:Key="Icon.Calendar">M3.5 5a2 2 0 0 1 2-2h13a2 2 0 0 1 2 2v15a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z M3.5 10h17M8 3v4M16 3v4</StreamGeometry>
|
The old stroke version filled into a plain black square. -->
|
||||||
|
<StreamGeometry x:Key="Icon.Calendar">F0 M3 5 H21 V21 H3 Z M5 11 H19 V19 H5 Z M7 2 H9 V5 H7 Z M15 2 H17 V5 H15 Z M7.5 13 H10 V15.5 H7.5 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Eye -->
|
<!-- Icon.Eye -->
|
||||||
<StreamGeometry x:Key="Icon.Eye">M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Eye">M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Inbox -->
|
<!-- Icon.Inbox — tray frame with an arrow dropping into the upper compartment.
|
||||||
<StreamGeometry x:Key="Icon.Inbox">M3 13h5l1 2h6l1-2h5M3 13l3-8h12l3 8v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z</StreamGeometry>
|
The old stroke version filled into a solid slab. -->
|
||||||
|
<StreamGeometry x:Key="Icon.Inbox">F0 M3 5 H21 V19 H3 Z M5 7 H19 V12 H5 Z M5 14 H19 V17 H5 Z M11 7.6 H13 V9.4 H15 L12 12 L9 9.4 H11 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Dot — picker preview for "no icon"; the sidebar renders it as a coloured Ellipse instead. -->
|
||||||
|
<StreamGeometry x:Key="Icon.Dot">M12 7a5 5 0 1 0 0 10a5 5 0 1 0 0-10z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Folder -->
|
<!-- Icon.Folder -->
|
||||||
<StreamGeometry x:Key="Icon.Folder">M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Folder">M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Search -->
|
<!-- Icon.Search — hollow ring + handle. The old stroke version filled into a solid disc with
|
||||||
<StreamGeometry x:Key="Icon.Search">M11 4a7 7 0 1 0 0 14A7 7 0 0 0 11 4z M20 20l-3.5-3.5</StreamGeometry>
|
no handle at all (this one is also the magnifier in the lists search box). -->
|
||||||
|
<StreamGeometry x:Key="Icon.Search">F0 M3 10.5 C3 6.36 6.36 3 10.5 3 C14.64 3 18 6.36 18 10.5 C18 14.64 14.64 18 10.5 18 C6.36 18 3 14.64 3 10.5 Z M5.2 10.5 C5.2 7.57 7.57 5.2 10.5 5.2 C13.43 5.2 15.8 7.57 15.8 10.5 C15.8 13.43 13.43 15.8 10.5 15.8 C7.57 15.8 5.2 13.43 5.2 10.5 Z M14.88 16.58 L16.58 14.88 L22.05 20.35 L20.35 22.05 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Plus — filled cross (PathIcon fills, so a stroke-only plus renders invisible) -->
|
<!-- Icon.Plus — filled cross (PathIcon fills, so a stroke-only plus renders invisible) -->
|
||||||
<StreamGeometry x:Key="Icon.Plus">M10.6 5 H13.4 V10.6 H19 V13.4 H13.4 V19 H10.6 V13.4 H5 V10.6 H10.6 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Plus">M10.6 5 H13.4 V10.6 H19 V13.4 H13.4 V19 H10.6 V13.4 H5 V10.6 H10.6 Z</StreamGeometry>
|
||||||
@@ -87,8 +94,8 @@
|
|||||||
<!-- Icon.Stop — filled square (stop / interrupt) -->
|
<!-- Icon.Stop — filled square (stop / interrupt) -->
|
||||||
<StreamGeometry x:Key="Icon.Stop">M4 4 H20 V20 H4 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Stop">M4 4 H20 V20 H4 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.Check -->
|
<!-- Icon.Check — filled tick. The old stroke version filled into a solid wedge. -->
|
||||||
<StreamGeometry x:Key="Icon.Check">M4 12l5 5 11-11</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Check">M9.6 18.2 L3.4 12 L5.5 9.9 L9.6 14 L18.5 5.1 L20.6 7.2 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Icon.ArrowOut — filled arrow for "open external" button -->
|
<!-- Icon.ArrowOut — filled arrow for "open external" button -->
|
||||||
<StreamGeometry x:Key="Icon.ArrowOut">M13 4 H20 V11 H18 V7.4 L11.4 14 L10 12.6 L16.6 6 H13 Z M4 6 H10 V8 H6 V18 H16 V14 H18 V20 H4 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.ArrowOut">M13 4 H20 V11 H18 V7.4 L11.4 14 L10 12.6 L16.6 6 H13 Z M4 6 H10 V8 H6 V18 H16 V14 H18 V20 H4 Z</StreamGeometry>
|
||||||
@@ -115,6 +122,75 @@
|
|||||||
<!-- Icon.Skull — filled silhouette: rounded cranium + eye holes (EvenOdd) + jaw -->
|
<!-- Icon.Skull — filled silhouette: rounded cranium + eye holes (EvenOdd) + jaw -->
|
||||||
<StreamGeometry x:Key="Icon.Skull">F0 M12 2 C7 2 4 5.5 4 10 C4 13.5 6 16 8 17.5 L8 19 C8 20 8.9 21 10 21 L10 18.5 L14 18.5 L14 21 C15.1 21 16 20 16 19 L16 17.5 C18 16 20 13.5 20 10 C20 5.5 17 2 12 2 Z M8.5 8 L8.5 12 L11 12 L11 8 Z M13 8 L13 12 L15.5 12 L15.5 8 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Skull">F0 M12 2 C7 2 4 5.5 4 10 C4 13.5 6 16 8 17.5 L8 19 C8 20 8.9 21 10 21 L10 18.5 L14 18.5 L14 21 C15.1 21 16 20 16 19 L16 17.5 C18 16 20 13.5 20 10 C20 5.5 17 2 12 2 Z M8.5 8 L8.5 12 L11 12 L11 8 Z M13 8 L13 12 L15.5 12 L15.5 8 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- List icons — pickable in list settings → Icon tab. -->
|
||||||
|
<!-- All filled outlines (PathIcon fills; a stroke-only path is -->
|
||||||
|
<!-- invisible). Holes rely on the EvenOdd default; the two that -->
|
||||||
|
<!-- want overlapping subpaths to merge declare F1 explicitly. -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
|
<!-- Icon.Box — carton: solid lid band, open body, seam -->
|
||||||
|
<StreamGeometry x:Key="Icon.Box">F0 M3 5 H21 V20 H3 Z M5 11 H19 V18 H5 Z M11 11 H13 V14 H11 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Truck — box body + cab + two wheels (F1: wheels overlap the body) -->
|
||||||
|
<StreamGeometry x:Key="Icon.Truck">F1 M2 6 H13.5 V16 H2 Z M13.5 9.5 H17.4 L21 13.1 V16 H13.5 Z M7 15.5 a2.5 2.5 0 1 0 0 5 a2.5 2.5 0 1 0 0-5 Z M17 15.5 a2.5 2.5 0 1 0 0 5 a2.5 2.5 0 1 0 0-5 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.MapPin — location pin with punched-out centre -->
|
||||||
|
<StreamGeometry x:Key="Icon.MapPin">F0 M12 2 C8.1 2 5 5.1 5 9 C5 14.2 12 22 12 22 C12 22 19 14.2 19 9 C19 5.1 15.9 2 12 2 Z M12 6.5 a2.5 2.5 0 1 0 0 5 a2.5 2.5 0 1 0 0-5 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Chart — three rising bars -->
|
||||||
|
<StreamGeometry x:Key="Icon.Chart">M4 13 H8 V20 H4 Z M10 8 H14 V20 H10 Z M16 4 H20 V20 H16 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Server — two rack units with punched-out status LEDs -->
|
||||||
|
<StreamGeometry x:Key="Icon.Server">F0 M3 4 H21 V10 H3 Z M3 14 H21 V20 H3 Z M5.5 6 H7.5 V8 H5.5 Z M5.5 16 H7.5 V18 H5.5 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Chip — CPU die with hollow core and eight pins -->
|
||||||
|
<StreamGeometry x:Key="Icon.Chip">F0 M7 7 H17 V17 H7 Z M9.5 9.5 H14.5 V14.5 H9.5 Z M9 3 H10.5 V6.5 H9 Z M13.5 3 H15 V6.5 H13.5 Z M9 17.5 H10.5 V21 H9 Z M13.5 17.5 H15 V21 H13.5 Z M3 9 H6.5 V10.5 H3 Z M3 13.5 H6.5 V15 H3 Z M17.5 9 H21 V10.5 H17.5 Z M17.5 13.5 H21 V15 H17.5 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Bolt — lightning bolt -->
|
||||||
|
<StreamGeometry x:Key="Icon.Bolt">M13 2 L4 14 H10 L9 22 L20 9 H13.6 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Rocket — hull with porthole hole + exhaust -->
|
||||||
|
<StreamGeometry x:Key="Icon.Rocket">F0 M12 2 C15.5 5 17.5 9.5 17.5 14 V17 L14.5 19 H9.5 L6.5 17 V14 C6.5 9.5 8.5 5 12 2 Z M12 7.5 a2.2 2.2 0 1 0 0 4.4 a2.2 2.2 0 1 0 0-4.4 Z M9.5 19.5 H14.5 L13 22.5 H11 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Bulb — bulb over two screw-base bands -->
|
||||||
|
<StreamGeometry x:Key="Icon.Bulb">F0 M12 2 C8.1 2 5 5.1 5 9 C5 11.6 6.4 13.9 8.5 15.1 V17 H15.5 V15.1 C17.6 13.9 19 11.6 19 9 C19 5.1 15.9 2 12 2 Z M8.5 18.2 H15.5 V19.8 H8.5 Z M9.8 20.9 H14.2 V22.3 H9.8 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Flask — Erlenmeyer flask, single closed outline -->
|
||||||
|
<StreamGeometry x:Key="Icon.Flask">M9 2 H15 V4 H14 V9.3 L19.4 19.4 A1.5 1.5 0 0 1 18.1 21.6 H5.9 A1.5 1.5 0 0 1 4.6 19.4 L10 9.3 V4 H9 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Shield — shield with a punched-out check -->
|
||||||
|
<StreamGeometry x:Key="Icon.Shield">F0 M12 2 L4 5 V11 C4 16 7.4 20.3 12 21.5 C16.6 20.3 20 16 20 11 V5 Z M11 16.2 L7.2 12.4 L8.9 10.7 L11 12.8 L15.4 8.4 L17.1 10.1 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Camera — body with viewfinder bump and punched-out lens -->
|
||||||
|
<StreamGeometry x:Key="Icon.Camera">F0 M9 3 H15 L16.5 5.5 H20 A2 2 0 0 1 22 7.5 V19 A2 2 0 0 1 20 21 H4 A2 2 0 0 1 2 19 V7.5 A2 2 0 0 1 4 5.5 H7.5 Z M12 9 a4.5 4.5 0 1 0 0 9 a4.5 4.5 0 1 0 0-9 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Image — frame with mountains and sun inside the cut-out -->
|
||||||
|
<StreamGeometry x:Key="Icon.Image">F0 M3 4 H21 V20 H3 Z M5 6 H19 V18 H5 Z M6.5 16.5 L10.5 11 L13.5 14.5 L15.5 12.5 L17.5 16.5 Z M8 8 a1.6 1.6 0 1 0 0 3.2 a1.6 1.6 0 1 0 0-3.2 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Barcode — six bars of varying width -->
|
||||||
|
<StreamGeometry x:Key="Icon.Barcode">M3 4 H5 V20 H3 Z M6.5 4 H7.5 V20 H6.5 Z M9 4 H11.5 V20 H9 Z M13 4 H14 V20 H13 Z M15.5 4 H17.5 V20 H15.5 Z M19 4 H21 V20 H19 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Mail — envelope: frame cut out, flap drawn back inside it -->
|
||||||
|
<StreamGeometry x:Key="Icon.Mail">F0 M2 5 H22 V19 H2 Z M4 7 H20 V17 H4 Z M4 7 L12 13.5 L20 7 L20 8.7 L12 15.2 L4 8.7 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Terminal — console frame with prompt chevron and caret -->
|
||||||
|
<StreamGeometry x:Key="Icon.Terminal">F0 M2 4 H22 V20 H2 Z M4 6 H20 V18 H4 Z M6 8.4 L7.4 7 L11.4 11 L7.4 15 L6 13.6 L8.6 11 Z M12.5 13.4 H17 V15.2 H12.5 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Code — angle brackets around a slash -->
|
||||||
|
<StreamGeometry x:Key="Icon.Code">M8.6 5.4 L10 6.8 L5.8 11 L10 15.2 L8.6 16.6 L3 11 Z M15.4 5.4 L21 11 L15.4 16.6 L14 15.2 L18.2 11 L14 6.8 Z M13.6 3.4 L15.4 3.9 L10.4 20.6 L8.6 20.1 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Database — cylinder with the top ellipse punched out. Béziers, not arcs: an arc
|
||||||
|
spanning exactly its own diameter degenerates to a straight line here (measured). -->
|
||||||
|
<StreamGeometry x:Key="Icon.Database">F0 M5.3 5.2 C5.3 3.43 8.3 2 12 2 C15.7 2 18.7 3.43 18.7 5.2 V18.8 C18.7 20.57 15.7 22 12 22 C8.3 22 5.3 20.57 5.3 18.8 Z M5.3 5.2 C5.3 3.43 8.3 2 12 2 C15.7 2 18.7 3.43 18.7 5.2 C18.7 6.97 15.7 8.4 12 8.4 C8.3 8.4 5.3 6.97 5.3 5.2 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Wand — wand shaft plus three sparkles (kept clear of the shaft) -->
|
||||||
|
<StreamGeometry x:Key="Icon.Wand">M3.3 19.3 L13.3 9.3 L14.7 10.7 L4.7 20.7 Z M18 2.5 L19.1 5.9 L22.5 7 L19.1 8.1 L18 11.5 L16.9 8.1 L13.5 7 L16.9 5.9 Z M6 3 L6.7 4.8 L8.5 5.5 L6.7 6.2 L6 8 L5.3 6.2 L3.5 5.5 L5.3 4.8 Z M19.5 15 L20.2 16.8 L22 17.5 L20.2 18.2 L19.5 20 L18.8 18.2 L17 17.5 L18.8 16.8 Z</StreamGeometry>
|
||||||
|
|
||||||
|
<!-- Icon.Clock — ring with hands; the hands only touch at x=12.8, never overlap (EvenOdd).
|
||||||
|
Béziers, not arcs: see Icon.Database. -->
|
||||||
|
<StreamGeometry x:Key="Icon.Clock">F0 M2 12 C2 6.48 6.48 2 12 2 C17.52 2 22 6.48 22 12 C22 17.52 17.52 22 12 22 C6.48 22 2 17.52 2 12 Z M4 12 C4 7.58 7.58 4 12 4 C16.42 4 20 7.58 20 12 C20 16.42 16.42 20 12 20 C7.58 20 4 16.42 4 12 Z M11.2 6.5 H12.8 V12.8 H11.2 Z M12.8 11.2 H16.5 V12.8 H12.8 Z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Badge brushes -->
|
<!-- Badge brushes -->
|
||||||
<SolidColorBrush x:Key="DraftBadgeBrush" Color="{StaticResource TextMuteColor}"/>
|
<SolidColorBrush x:Key="DraftBadgeBrush" Color="{StaticResource TextMuteColor}"/>
|
||||||
<SolidColorBrush x:Key="PlanningBadgeBrush" Color="{StaticResource PeatColor}"/>
|
<SolidColorBrush x:Key="PlanningBadgeBrush" Color="{StaticResource PeatColor}"/>
|
||||||
@@ -224,6 +300,18 @@
|
|||||||
<Setter Property="Foreground" Value="{StaticResource StatusQueuedBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource StatusQueuedBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- waiting → muted amber: a Queued task blocked on an unmet predecessor (a task_dependencies
|
||||||
|
edge, or the planning chain's BlockedByTaskId) — distinct from queued (unblocked, next up
|
||||||
|
to run) and from children (a parent waiting on its own subtasks). Mutually exclusive with
|
||||||
|
.queued on the same chip (IsQueued/IsWaiting), so no declaration-order fight between them. -->
|
||||||
|
<Style Selector="Border.chip.waiting">
|
||||||
|
<Setter Property="Background" Value="#332F1A" />
|
||||||
|
<Setter Property="BorderBrush" Value="#4D451A" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.chip.waiting > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#C9A227" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- done → green (#6FA86B) -->
|
<!-- done → green (#6FA86B) -->
|
||||||
<Style Selector="Border.chip.done">
|
<Style Selector="Border.chip.done">
|
||||||
<Setter Property="Background" Value="{StaticResource DoneTintBrush}" />
|
<Setter Property="Background" Value="{StaticResource DoneTintBrush}" />
|
||||||
@@ -769,7 +857,7 @@
|
|||||||
<!-- LIST NAV ITEM -->
|
<!-- LIST NAV ITEM -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<Style Selector="Border.list-item">
|
<Style Selector="Border.list-item">
|
||||||
<Setter Property="Padding" Value="10,7" />
|
<Setter Property="Padding" Value="10,4" />
|
||||||
<Setter Property="CornerRadius" Value="8" />
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
@@ -778,6 +866,10 @@
|
|||||||
<Setter Property="BorderThickness" Value="1.5" />
|
<Setter Property="BorderThickness" Value="1.5" />
|
||||||
<Setter Property="BorderBrush" Value="Transparent" />
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<!-- Folder tree rows sit tighter than the smart lists — a deep tree gets long fast. -->
|
||||||
|
<Style Selector="Border.list-item.tree-row">
|
||||||
|
<Setter Property="Padding" Value="4,4,10,4" />
|
||||||
|
</Style>
|
||||||
<Style Selector="Border.list-item:pointerover">
|
<Style Selector="Border.list-item:pointerover">
|
||||||
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
|
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -789,6 +881,11 @@
|
|||||||
<Setter Property="Background" Value="{StaticResource AccentSoftBrush}" />
|
<Setter Property="Background" Value="{StaticResource AccentSoftBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<!-- Resting icon colour. Must live here, not inline on the PathIcon: a local value outranks
|
||||||
|
every style, so an inline Foreground makes the .active accent below unreachable. -->
|
||||||
|
<Style Selector="Border.list-item PathIcon.list-icon">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||||
|
</Style>
|
||||||
<!-- Active item text / icon colors -->
|
<!-- Active item text / icon colors -->
|
||||||
<Style Selector="Border.list-item.active TextBlock.list-label">
|
<Style Selector="Border.list-item.active TextBlock.list-label">
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
|||||||
@@ -104,6 +104,16 @@
|
|||||||
<SolidColorBrush x:Key="RoadblockTintBrush" Color="#1FD4A574" />
|
<SolidColorBrush x:Key="RoadblockTintBrush" Color="#1FD4A574" />
|
||||||
<SolidColorBrush x:Key="RoadblockTintBorderBrush" Color="#4CD4A574" />
|
<SolidColorBrush x:Key="RoadblockTintBorderBrush" Color="#4CD4A574" />
|
||||||
|
|
||||||
|
<!-- Mission Control tile identity colours. Cool hues only, on purpose: the warm/green half of
|
||||||
|
the wheel is taken by the status semantics above, and an identity strip must never read as
|
||||||
|
"running"/"review"/"failed". -->
|
||||||
|
<SolidColorBrush x:Key="Identity1Brush" Color="#5FA8C8" />
|
||||||
|
<SolidColorBrush x:Key="Identity2Brush" Color="#4FA396" />
|
||||||
|
<SolidColorBrush x:Key="Identity3Brush" Color="#7E88CC" />
|
||||||
|
<SolidColorBrush x:Key="Identity4Brush" Color="#A57BB5" />
|
||||||
|
<SolidColorBrush x:Key="Identity5Brush" Color="#C77FA0" />
|
||||||
|
<SolidColorBrush x:Key="Identity6Brush" Color="#5C78B8" />
|
||||||
|
|
||||||
<!-- Merge editor (3-pane conflict resolver) block tints -->
|
<!-- Merge editor (3-pane conflict resolver) block tints -->
|
||||||
<SolidColorBrush x:Key="MergeOursTintBrush" Color="#1F7C9166" /> <!-- ours side (moss) -->
|
<SolidColorBrush x:Key="MergeOursTintBrush" Color="#1F7C9166" /> <!-- ours side (moss) -->
|
||||||
<SolidColorBrush x:Key="MergeTheirsTintBrush" Color="#1FD4A574" /> <!-- theirs side (amber) -->
|
<SolidColorBrush x:Key="MergeTheirsTintBrush" Color="#1FD4A574" /> <!-- theirs side (amber) -->
|
||||||
@@ -148,6 +158,7 @@
|
|||||||
<Thickness x:Key="IslandHeaderPadding">18,16,18,12</Thickness>
|
<Thickness x:Key="IslandHeaderPadding">18,16,18,12</Thickness>
|
||||||
<Thickness x:Key="IslandBodyPadding">14</Thickness>
|
<Thickness x:Key="IslandBodyPadding">14</Thickness>
|
||||||
<Thickness x:Key="WindowBodyPadding">14</Thickness>
|
<Thickness x:Key="WindowBodyPadding">14</Thickness>
|
||||||
|
<Thickness x:Key="NestedRowIndent">18,0,0,0</Thickness> <!-- SpaceXl: a child row under its parent -->
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<!-- CORNERS & BORDERS -->
|
<!-- CORNERS & BORDERS -->
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Temp PNGs written for clipboard-image paste into the interactive terminal (see
|
||||||
|
/// <see cref="ClaudeDo.Ui.Views.InteractiveTerminalView"/>) — dropped into the composer as a path,
|
||||||
|
/// the same way a file drop is, then read by Claude like any other file.
|
||||||
|
/// </summary>
|
||||||
|
public static class ClipboardImageTempStore
|
||||||
|
{
|
||||||
|
public static string Directory => Path.Combine(Paths.AppDataRoot(), "tmp");
|
||||||
|
|
||||||
|
/// <summary>A fresh, collision-free path for one pasted image.</summary>
|
||||||
|
public static string NewPngPath(DateTime utcNow) => NewPngPath(Directory, utcNow);
|
||||||
|
|
||||||
|
/// <summary>Deletes files older than <paramref name="maxAge"/> under the tmp dir — called once at
|
||||||
|
/// app startup, not a running cleanup service.</summary>
|
||||||
|
public static void DeleteOlderThan(TimeSpan maxAge, DateTime utcNow) => DeleteOlderThan(Directory, maxAge, utcNow);
|
||||||
|
|
||||||
|
/// <summary>Test seam for <see cref="NewPngPath(DateTime)"/> — takes the target dir explicitly
|
||||||
|
/// instead of the real app data root.</summary>
|
||||||
|
internal static string NewPngPath(string dir, DateTime utcNow) =>
|
||||||
|
Path.Combine(dir, $"paste-{utcNow:yyyyMMdd-HHmmssfff}-{Guid.NewGuid():N}.png");
|
||||||
|
|
||||||
|
/// <summary>Test seam for <see cref="DeleteOlderThan(TimeSpan,DateTime)"/> — takes the target dir
|
||||||
|
/// explicitly instead of the real app data root.</summary>
|
||||||
|
internal static void DeleteOlderThan(string dir, TimeSpan maxAge, DateTime utcNow)
|
||||||
|
{
|
||||||
|
if (!System.IO.Directory.Exists(dir)) return;
|
||||||
|
|
||||||
|
foreach (var file in System.IO.Directory.EnumerateFiles(dir))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (utcNow - File.GetLastWriteTimeUtc(file) > maxAge)
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
catch (IOException) { /* best effort */ }
|
||||||
|
catch (UnauthorizedAccessException) { /* best effort */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,12 +28,16 @@ public interface IDialogService
|
|||||||
Task ShowConflictResolverAsync(ConflictResolverViewModel vm);
|
Task ShowConflictResolverAsync(ConflictResolverViewModel vm);
|
||||||
Task ShowLogVisualizerAsync(LogVisualizerViewModel vm);
|
Task ShowLogVisualizerAsync(LogVisualizerViewModel vm);
|
||||||
|
|
||||||
/// <summary>Modal yes/no confirmation. Returns true only when confirmed.</summary>
|
/// <summary>Modal yes/no confirmation. Returns true only when confirmed. <paramref name="confirmLabel"/>
|
||||||
Task<bool> ConfirmAsync(string message);
|
/// overrides the default localized "Confirm" button text (e.g. a localized "Delete" for destructive actions).</summary>
|
||||||
|
Task<bool> ConfirmAsync(string message, string? confirmLabel = null);
|
||||||
|
|
||||||
/// <summary>Modal error notice with a single dismiss button.</summary>
|
/// <summary>Modal error notice with a single dismiss button.</summary>
|
||||||
Task ShowErrorAsync(string message);
|
Task ShowErrorAsync(string message);
|
||||||
|
|
||||||
/// <summary>Show (or re-show + focus) the modeless Mission Control window. Lazily created; hides on close.</summary>
|
/// <summary>Show (or re-show + focus) the modeless Mission Control window. Lazily created; hides on close.</summary>
|
||||||
void ShowMissionControl(MissionControlViewModel vm);
|
void ShowMissionControl(MissionControlViewModel vm);
|
||||||
|
|
||||||
|
/// <summary>Show (or re-show + focus) the modeless cheat-sheet window for the shell's shared VM.</summary>
|
||||||
|
void ShowCheatSheets(CheatSheetsViewModel vm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace ClaudeDo.Ui.Services;
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
// Two named types because DI resolves them by type, not by key.
|
// Two named types because DI resolves them by type, not by key. Not sealed: tests subclass
|
||||||
public sealed class InstallerLocator() : InstallArtifactLocator("uninstaller", "ClaudeDo.Installer.exe");
|
// these to fake Find() without touching the real filesystem/registry.
|
||||||
|
public class InstallerLocator() : InstallArtifactLocator("uninstaller", "ClaudeDo.Installer.exe");
|
||||||
|
|
||||||
public sealed class WorkerLocator() : InstallArtifactLocator("worker", "ClaudeDo.Worker.exe");
|
public class WorkerLocator() : InstallArtifactLocator("worker", "ClaudeDo.Worker.exe");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locates an executable inside a ClaudeDo install: walk up from the running
|
/// Locates an executable inside a ClaudeDo install: walk up from the running
|
||||||
@@ -14,7 +15,7 @@ public abstract class InstallArtifactLocator(string subdir, string exeName)
|
|||||||
{
|
{
|
||||||
private const string InstallJson = "install.json";
|
private const string InstallJson = "install.json";
|
||||||
|
|
||||||
public string? Find()
|
public virtual string? Find()
|
||||||
=> FindByWalkingUp(AppContext.BaseDirectory)
|
=> FindByWalkingUp(AppContext.BaseDirectory)
|
||||||
?? (OperatingSystem.IsWindows() ? FindByRegistry() : null);
|
?? (OperatingSystem.IsWindows() ? FindByRegistry() : null);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace ClaudeDo.Ui.Services.Interfaces;
|
|||||||
public interface INotesApi
|
public interface INotesApi
|
||||||
{
|
{
|
||||||
Task<List<DailyNoteDto>> ListAsync(DateOnly day);
|
Task<List<DailyNoteDto>> ListAsync(DateOnly day);
|
||||||
|
/// <summary>Both ends inclusive — today plus its history in one call.</summary>
|
||||||
|
Task<List<DailyNoteDto>> ListBetweenAsync(DateOnly start, DateOnly end);
|
||||||
Task<DailyNoteDto?> AddAsync(DateOnly day, string text);
|
Task<DailyNoteDto?> AddAsync(DateOnly day, string text);
|
||||||
Task UpdateAsync(string id, string text);
|
Task UpdateAsync(string id, string text);
|
||||||
Task DeleteAsync(string id);
|
Task DeleteAsync(string id);
|
||||||
|
|||||||
@@ -30,6 +30,24 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
/// task to carry out nextPhase ("wait" | "merge" | "wait_final" | "merge_final").</summary>
|
/// task to carry out nextPhase ("wait" | "merge" | "wait_final" | "merge_final").</summary>
|
||||||
event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
|
event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
|
||||||
|
|
||||||
|
/// <summary>A Mission Control session called handoff_session: (paneId, briefPath). The UI replaces
|
||||||
|
/// the tile whose IdentityKey is paneId with a fresh session starting from the brief.</summary>
|
||||||
|
event Action<string, string>? SessionHandoffRequestedEvent;
|
||||||
|
|
||||||
|
/// <summary>start_list_handler was called through the external MCP: (listId, taskIds). The UI
|
||||||
|
/// starts a list-handler run for them exactly like the "Let Claude handle it" button.</summary>
|
||||||
|
event Action<string, IReadOnlyList<string>>? ListHandlerStartRequestedEvent;
|
||||||
|
/// <summary>The 5h usage window is running out under a running list-handler session:
|
||||||
|
/// (handlerTaskId, message). Mission Control types the message into that task's ConPTY pane so
|
||||||
|
/// the session can hand off cleanly; no pane for the task means nothing happens.</summary>
|
||||||
|
event Action<string, string>? HandlerUsageWarningEvent;
|
||||||
|
/// <summary>A list-handler handoff is deferred because the 5h usage window is exhausted:
|
||||||
|
/// (handlerTaskId, dueAt). Mission Control updates the pane title to show the reset time.</summary>
|
||||||
|
event Action<string, DateTimeOffset>? HandlerHandoffDeferredEvent;
|
||||||
|
|
||||||
|
/// <summary>An agent added a note through the MCP tool — an open notes editor reloads.</summary>
|
||||||
|
event Action? NotesUpdatedEvent;
|
||||||
|
|
||||||
event Action? PrepStartedEvent;
|
event Action? PrepStartedEvent;
|
||||||
event Action<string>? PrepLineEvent;
|
event Action<string>? PrepLineEvent;
|
||||||
event Action<bool>? PrepFinishedEvent;
|
event Action<bool>? PrepFinishedEvent;
|
||||||
@@ -41,13 +59,6 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
/// total.</summary>
|
/// total.</summary>
|
||||||
event Action<string, string, int, int>? OperationProgressEvent;
|
event Action<string, string, int, int>? OperationProgressEvent;
|
||||||
|
|
||||||
/// <summary>(taskId, phase, elapsedSeconds) — phase of an in-flight single-task merge
|
|
||||||
/// ("merging" | "verifying"). Fires while the MergeTask call itself is still pending, so the
|
|
||||||
/// waiting UI can show what it's blocked on; the verify phase re-fires every 30 s. A thin
|
|
||||||
/// forwarder over <see cref="OperationProgressEvent"/> kept for existing consumers (elapsed
|
|
||||||
/// seconds riding in the generic "current" slot).</summary>
|
|
||||||
event Action<string, string, int>? MergeProgressEvent;
|
|
||||||
|
|
||||||
event Action<string, string>? PlanningMergeStartedEvent;
|
event Action<string, string>? PlanningMergeStartedEvent;
|
||||||
event Action<string, string>? PlanningSubtaskMergedEvent;
|
event Action<string, string>? PlanningSubtaskMergedEvent;
|
||||||
/// <summary>(planningTaskId, subtaskId, conflictedFiles, externallyDriven). externallyDriven
|
/// <summary>(planningTaskId, subtaskId, conflictedFiles, externallyDriven). externallyDriven
|
||||||
@@ -106,9 +117,11 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
/// and clears BlockedByTaskId in the same update) — call once per row (parent and each
|
/// and clears BlockedByTaskId in the same update) — call once per row (parent and each
|
||||||
/// queued child) rather than cascading server-side.</summary>
|
/// queued child) rather than cascading server-side.</summary>
|
||||||
Task DequeueTaskAsync(string taskId);
|
Task DequeueTaskAsync(string taskId);
|
||||||
Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch);
|
/// <param name="autostash">Retry flag for status "dirty_target": stash the target working
|
||||||
|
/// tree's uncommitted changes before merging and restore them afterwards (git --autostash).</param>
|
||||||
|
Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch, bool autostash = false);
|
||||||
Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch);
|
Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch);
|
||||||
Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage);
|
Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage, bool autostash = false);
|
||||||
Task RejectReviewToQueueAsync(string taskId, string feedback);
|
Task RejectReviewToQueueAsync(string taskId, string feedback);
|
||||||
Task RejectReviewToIdleAsync(string taskId);
|
Task RejectReviewToIdleAsync(string taskId);
|
||||||
Task CancelReviewAsync(string taskId);
|
Task CancelReviewAsync(string taskId);
|
||||||
@@ -127,13 +140,18 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
/// in a task's worktree (same worktree prep as an autonomous run).</summary>
|
/// in a task's worktree (same worktree prep as an autonomous run).</summary>
|
||||||
Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default);
|
Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default);
|
||||||
/// <summary>Launch spec for an ad-hoc interactive session in an arbitrary directory --
|
/// <summary>Launch spec for an ad-hoc interactive session in an arbitrary directory --
|
||||||
/// no task, no worktree.</summary>
|
/// no task, no worktree. paneId (the tile's IdentityKey) is rendered into the handoff hint.</summary>
|
||||||
Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default);
|
Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, string paneId, CancellationToken ct = default);
|
||||||
|
/// <summary>Launch spec for the fresh session a tile hands off to after handoff_session.</summary>
|
||||||
|
Task<LaunchSpec> GetSessionHandoffLaunchSpecAsync(string paneId, string cwd, string briefPath, string? taskId, CancellationToken ct = default);
|
||||||
|
/// <summary>Launch spec resuming a recorded Mission Control session in its cwd, as tile paneId.</summary>
|
||||||
|
Task<LaunchSpec> GetResumeSessionLaunchSpecAsync(string sessionId, string paneId, CancellationToken ct = default);
|
||||||
/// <summary>Launch spec for an embedded ConPTY "merge helper" session that drives the given
|
/// <summary>Launch spec for an embedded ConPTY "merge helper" session that drives the given
|
||||||
/// tasks to a merged/Done state. listId scopes the session (and cwd) to that list.
|
/// tasks to a merged/Done state. listId scopes the session (and cwd) to that list.
|
||||||
/// handlerTaskId (from CreateMergeHelperTaskAsync) is rendered into the brief so the session
|
/// handlerTaskId (from CreateMergeHelperTaskAsync) is rendered into the brief so the session
|
||||||
/// can call handoff_list_handler on its own handler task.</summary>
|
/// can call handoff_list_handler on its own handler task. model overrides the triage
|
||||||
Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct = default);
|
/// session's model (null = default).</summary>
|
||||||
|
Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default);
|
||||||
/// <summary>Creates the ClaudeDo task that owns a list-handler run (one per run, Idle/IsManual,
|
/// <summary>Creates the ClaudeDo task that owns a list-handler run (one per run, Idle/IsManual,
|
||||||
/// never queued) so the ConPTY tile can be task-based instead of ad-hoc. Returns the new task id.</summary>
|
/// never queued) so the ConPTY tile can be task-based instead of ad-hoc. Returns the new task id.</summary>
|
||||||
Task<string> CreateMergeHelperTaskAsync(
|
Task<string> CreateMergeHelperTaskAsync(
|
||||||
@@ -143,6 +161,14 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
/// session's role: "wait" | "merge" | "wait_final" | "merge_final".</summary>
|
/// session's role: "wait" | "merge" | "wait_final" | "merge_final".</summary>
|
||||||
Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
|
Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
|
||||||
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default);
|
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default);
|
||||||
|
/// <summary>Reports that a task-based ConPTY session's process exited non-zero after it had been
|
||||||
|
/// running. The exit code only exists in this process; the worker decides whether it was a
|
||||||
|
/// list-handler session killed by the usage limit and reopens the same phase after the 5h reset.</summary>
|
||||||
|
Task ReportInteractiveSessionExitedAsync(string taskId, int exitCode, CancellationToken ct = default);
|
||||||
|
/// <summary>The handler round's still-open (Queued/Running) tasks, for the Mission Control
|
||||||
|
/// "abandon run" action. Empty when the round has no entry or nothing open -- the caller then
|
||||||
|
/// closes the pane without a confirmation dialog.</summary>
|
||||||
|
Task<IReadOnlyList<HandlerOpenTaskDto>> GetHandlerOpenTasksAsync(string handlerTaskId, CancellationToken ct = default);
|
||||||
/// <summary>Starts a planning session and returns the launch spec for an embedded ConPTY
|
/// <summary>Starts a planning session and returns the launch spec for an embedded ConPTY
|
||||||
/// planning terminal (replaces StartPlanningSessionAsync's external wt window).</summary>
|
/// planning terminal (replaces StartPlanningSessionAsync's external wt window).</summary>
|
||||||
Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default);
|
Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default);
|
||||||
@@ -172,15 +198,14 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
Task<AppSettingsDto?> GetAppSettingsAsync();
|
Task<AppSettingsDto?> GetAppSettingsAsync();
|
||||||
Task UpdateAppSettingsAsync(AppSettingsDto dto);
|
Task UpdateAppSettingsAsync(AppSettingsDto dto);
|
||||||
Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day);
|
Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day);
|
||||||
|
/// <summary>Both ends inclusive.</summary>
|
||||||
|
Task<List<DailyNoteDto>> GetDailyNotesBetweenAsync(DateOnly start, DateOnly end);
|
||||||
Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text);
|
Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text);
|
||||||
Task UpdateDailyNoteAsync(string id, string text);
|
Task UpdateDailyNoteAsync(string id, string text);
|
||||||
Task DeleteDailyNoteAsync(string id);
|
Task DeleteDailyNoteAsync(string id);
|
||||||
Task<string> GetLastPrepLogAsync();
|
Task<string> GetLastPrepLogAsync();
|
||||||
Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync();
|
Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync();
|
||||||
|
|
||||||
/// <summary>Git SHA the running worker was built from (null when offline or the build isn't stamped).</summary>
|
|
||||||
Task<WorkerBuildInfoDto?> GetWorkerBuildInfoAsync();
|
|
||||||
|
|
||||||
Task<List<PrimeScheduleDto>> GetPrimeSchedulesAsync();
|
Task<List<PrimeScheduleDto>> GetPrimeSchedulesAsync();
|
||||||
Task<PrimeScheduleDto?> UpsertPrimeScheduleAsync(PrimeScheduleDto dto);
|
Task<PrimeScheduleDto?> UpsertPrimeScheduleAsync(PrimeScheduleDto dto);
|
||||||
Task DeletePrimeScheduleAsync(Guid id);
|
Task DeletePrimeScheduleAsync(Guid id);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Iciclecreek.Terminal;
|
using Iciclecreek.Terminal;
|
||||||
@@ -67,6 +68,14 @@ public sealed class PtyTerminalSession : IDisposable
|
|||||||
await WaitForLaunchGateAsync(s_launchGate, s_launchGateTimeout, ct);
|
await WaitForLaunchGateAsync(s_launchGate, s_launchGateTimeout, ct);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// If ClaudeDo.App itself was started from inside a claude session (e.g. a dev/test
|
||||||
|
// launch), it inherits these markers and every embedded PTY session would otherwise
|
||||||
|
// inherit them too — the child claude then thinks it's a spawned subagent and
|
||||||
|
// disables transcript saving / degrades its own startup banner. Every ConPTY tile
|
||||||
|
// must look like a fresh top-level session regardless of how ClaudeDo.App was launched.
|
||||||
|
Environment.SetEnvironmentVariable("CLAUDE_CODE_CHILD_SESSION", null);
|
||||||
|
Environment.SetEnvironmentVariable("CLAUDECODE", null);
|
||||||
|
|
||||||
foreach (var (key, value) in descriptor.Env)
|
foreach (var (key, value) in descriptor.Env)
|
||||||
Environment.SetEnvironmentVariable(key, value);
|
Environment.SetEnvironmentVariable(key, value);
|
||||||
|
|
||||||
@@ -93,6 +102,96 @@ public sealed class PtyTerminalSession : IDisposable
|
|||||||
ProcessExited?.Invoke(this, e.ExitCode);
|
ProcessExited?.Invoke(this, e.ExitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Types <paramref name="text"/> into the running session and submits it, exactly as if the user
|
||||||
|
/// had typed it — used to warn a list-handler session before the usage limit
|
||||||
|
/// (<c>HandlerUsageWarning</c>). Verified against a real `claude` TUI, including while it was
|
||||||
|
/// mid-turn (the message is queued and acted on when the turn ends).
|
||||||
|
///
|
||||||
|
/// Three non-obvious details, all established by that spike:
|
||||||
|
/// • <see cref="TerminalControl"/> exposes no public write path at all — its only public paste is
|
||||||
|
/// <c>TerminalView.PasteAsync()</c>, which takes no argument and reads the clipboard. The
|
||||||
|
/// private <c>SendToPtyAsync</c> on the inner view is the write seam, hence the reflection.
|
||||||
|
/// • The text and the Enter must be TWO writes. A single <c>text + "\r"</c> chunk lands in
|
||||||
|
/// claude's composer but is never submitted — a chunk carrying an embedded CR reads as pasted
|
||||||
|
/// text, not as a keypress.
|
||||||
|
/// • The Enter bytes come from the emulator (<c>GenerateKeyInput</c>) rather than a literal
|
||||||
|
/// "\r", because claude turns Win32 input mode on and the emulator owns that encoding.
|
||||||
|
///
|
||||||
|
/// Best-effort by design: a package upgrade that renames the private member costs a warning that
|
||||||
|
/// never arrives, never a crashed Mission Control.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> SendLineAsync(string text)
|
||||||
|
{
|
||||||
|
if (_control is not { } control || !IsRunning || string.IsNullOrEmpty(text)) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SendRawAsync(control, text);
|
||||||
|
await Task.Delay(SubmitDelay);
|
||||||
|
await SendRawAsync(control, control.Terminal.GenerateKeyInput(XTerm.Input.Key.Enter, XTerm.Input.KeyModifiers.None));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts <paramref name="text"/> at the cursor without submitting it — the drop-a-file-path
|
||||||
|
/// case. Same write seam as <see cref="SendLineAsync"/>, just without the Enter.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> SendTextAsync(string text)
|
||||||
|
{
|
||||||
|
if (_control is not { } control || !IsRunning || string.IsNullOrEmpty(text)) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SendRawAsync(control, text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pastes the clipboard into the session. <see cref="TerminalControl"/> wires paste to the
|
||||||
|
/// right-click menu only, so Ctrl+V otherwise reaches the child as a raw control byte and does
|
||||||
|
/// nothing. Goes through the emulator's own <see cref="TerminalView.PasteAsync"/> rather than
|
||||||
|
/// writing the clipboard text raw, because that is what honours bracketed-paste mode — without
|
||||||
|
/// it a multi-line paste is read line by line and submits on the first newline.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> PasteAsync()
|
||||||
|
{
|
||||||
|
if (_control is not { } control || !IsRunning) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s_terminalViewField?.GetValue(control) is not TerminalView view) return false;
|
||||||
|
await view.PasteAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long enough that the two writes can't be read as one chunk by the child's stdin reader.
|
||||||
|
private static readonly TimeSpan SubmitDelay = TimeSpan.FromMilliseconds(150);
|
||||||
|
|
||||||
|
private static readonly FieldInfo? s_terminalViewField =
|
||||||
|
typeof(TerminalControl).GetField("_terminalView", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
private static readonly MethodInfo? s_sendToPty =
|
||||||
|
typeof(TerminalView).GetMethod("SendToPtyAsync", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
private static Task SendRawAsync(TerminalControl control, string data)
|
||||||
|
{
|
||||||
|
if (s_terminalViewField?.GetValue(control) is not TerminalView view || s_sendToPty is null)
|
||||||
|
throw new InvalidOperationException("Iciclecreek.Avalonia.Terminal no longer exposes a PTY write seam.");
|
||||||
|
return (Task)s_sendToPty.Invoke(view, new object[] { data, CancellationToken.None })!;
|
||||||
|
}
|
||||||
|
|
||||||
public void Kill()
|
public void Kill()
|
||||||
{
|
{
|
||||||
try { _control?.Kill(); }
|
try { _control?.Kill(); }
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ public static class RepoScanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsRepo(string dir)
|
// A real clone has .git as a directory. Worktrees and submodules have it as a file
|
||||||
{
|
// ("gitdir: ...") — those are not standalone repos and would flood the list.
|
||||||
var gitPath = Path.Combine(dir, ".git");
|
public static bool IsRepo(string dir) => Directory.Exists(Path.Combine(dir, ".git"));
|
||||||
return Directory.Exists(gitPath) || File.Exists(gitPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace ClaudeDo.Ui.Services;
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
public sealed record DailyNoteDto(string Id, string Date, string Text, int SortOrder);
|
public sealed record DailyNoteDto(string Id, string Date, string Text, int SortOrder, bool FromAgent);
|
||||||
|
|||||||
@@ -49,11 +49,16 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public event Action<string, string, string>? TaskQuestionAskedEvent;
|
public event Action<string, string, string>? TaskQuestionAskedEvent;
|
||||||
public event Action<string, string>? TaskQuestionResolvedEvent;
|
public event Action<string, string>? TaskQuestionResolvedEvent;
|
||||||
public event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
|
public event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;
|
||||||
|
public event Action<string, string>? SessionHandoffRequestedEvent;
|
||||||
|
public event Action<string, IReadOnlyList<string>>? ListHandlerStartRequestedEvent;
|
||||||
|
public event Action<string, string>? HandlerUsageWarningEvent;
|
||||||
|
public event Action<string, DateTimeOffset>? HandlerHandoffDeferredEvent;
|
||||||
public event Action? ConnectionRestoredEvent;
|
public event Action? ConnectionRestoredEvent;
|
||||||
public event Action<string>? WorktreeUpdatedEvent;
|
public event Action<string>? WorktreeUpdatedEvent;
|
||||||
public event Action<string>? ListUpdatedEvent;
|
public event Action<string>? ListUpdatedEvent;
|
||||||
public event Action<WorkerLogEntry>? WorkerLogReceivedEvent;
|
public event Action<WorkerLogEntry>? WorkerLogReceivedEvent;
|
||||||
|
|
||||||
|
public event Action? NotesUpdatedEvent;
|
||||||
public event Action? PrepStartedEvent;
|
public event Action? PrepStartedEvent;
|
||||||
public event Action<string>? PrepLineEvent;
|
public event Action<string>? PrepLineEvent;
|
||||||
public event Action<bool>? PrepFinishedEvent;
|
public event Action<bool>? PrepFinishedEvent;
|
||||||
@@ -64,7 +69,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public event Action<UsageSnapshotDto>? UsageUpdatedEvent;
|
public event Action<UsageSnapshotDto>? UsageUpdatedEvent;
|
||||||
|
|
||||||
public event Action<string, string, int, int>? OperationProgressEvent;
|
public event Action<string, string, int, int>? OperationProgressEvent;
|
||||||
public event Action<string, string, int>? MergeProgressEvent;
|
|
||||||
|
|
||||||
public event Action<string, string>? PlanningMergeStartedEvent;
|
public event Action<string, string>? PlanningMergeStartedEvent;
|
||||||
public event Action<string, string>? PlanningSubtaskMergedEvent;
|
public event Action<string, string>? PlanningSubtaskMergedEvent;
|
||||||
@@ -158,6 +162,26 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds, nextPhase));
|
Dispatcher.UIThread.Post(() => HandoffRequestedEvent?.Invoke(taskId, survivingTaskIds, nextPhase));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_hub.On<string, string>("SessionHandoffRequested", (paneId, briefPath) =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => SessionHandoffRequestedEvent?.Invoke(paneId, briefPath));
|
||||||
|
});
|
||||||
|
|
||||||
|
_hub.On<string, IReadOnlyList<string>>("ListHandlerStartRequested", (listId, taskIds) =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => ListHandlerStartRequestedEvent?.Invoke(listId, taskIds));
|
||||||
|
});
|
||||||
|
|
||||||
|
_hub.On<string, string>("HandlerUsageWarning", (taskId, message) =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => HandlerUsageWarningEvent?.Invoke(taskId, message));
|
||||||
|
});
|
||||||
|
|
||||||
|
_hub.On<string, DateTimeOffset>("HandlerHandoffDeferred", (taskId, dueAt) =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => HandlerHandoffDeferredEvent?.Invoke(taskId, dueAt));
|
||||||
|
});
|
||||||
|
|
||||||
_hub.On<string>("WorktreeUpdated", taskId =>
|
_hub.On<string>("WorktreeUpdated", taskId =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId));
|
Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId));
|
||||||
@@ -176,11 +200,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
|
|
||||||
_hub.On<string, string, int, int>("OperationProgress", (opKey, phase, current, total) =>
|
_hub.On<string, string, int, int>("OperationProgress", (opKey, phase, current, total) =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() => OperationProgressEvent?.Invoke(opKey, phase, current, total));
|
||||||
{
|
|
||||||
OperationProgressEvent?.Invoke(opKey, phase, current, total);
|
|
||||||
MergeProgressEvent?.Invoke(opKey, phase, current);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_hub.On<string, string>("PlanningMergeStarted", (planningTaskId, targetBranch) =>
|
_hub.On<string, string>("PlanningMergeStarted", (planningTaskId, targetBranch) =>
|
||||||
@@ -213,6 +233,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
Dispatcher.UIThread.Post(() => PrimeFired?.Invoke(new PrimeFiredEvent(id, ok, msg, when)));
|
Dispatcher.UIThread.Post(() => PrimeFired?.Invoke(new PrimeFiredEvent(id, ok, msg, when)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_hub.On("NotesUpdated", () => Dispatcher.UIThread.Post(() => NotesUpdatedEvent?.Invoke()));
|
||||||
_hub.On("PrepStarted", () => Dispatcher.UIThread.Post(() => PrepStartedEvent?.Invoke()));
|
_hub.On("PrepStarted", () => Dispatcher.UIThread.Post(() => PrepStartedEvent?.Invoke()));
|
||||||
_hub.On<string>("PrepLine", line => Dispatcher.UIThread.Post(() => PrepLineEvent?.Invoke(line)));
|
_hub.On<string>("PrepLine", line => Dispatcher.UIThread.Post(() => PrepLineEvent?.Invoke(line)));
|
||||||
_hub.On<bool>("PrepFinished", ok => Dispatcher.UIThread.Post(() => PrepFinishedEvent?.Invoke(ok)));
|
_hub.On<bool>("PrepFinished", ok => Dispatcher.UIThread.Post(() => PrepFinishedEvent?.Invoke(ok)));
|
||||||
@@ -289,6 +310,11 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
ok = true;
|
ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
catch { return default; }
|
catch { return default; }
|
||||||
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
||||||
}
|
}
|
||||||
@@ -303,6 +329,11 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
await invoke();
|
await invoke();
|
||||||
ok = true;
|
ok = true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +348,11 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
ok = true;
|
ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Warning(ex, "Hub call {method_name} failed", method);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
finally { OperationTiming.Shared.Record("hub", method, sw.Elapsed, ok); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,10 +380,11 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
await InvokeTimedAsync("ResetTask", () => _hub.InvokeAsync("ResetTask", taskId));
|
await InvokeTimedAsync("ResetTask", () => _hub.InvokeAsync("ResetTask", taskId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage)
|
public async Task<MergeResultDto> MergeTaskAsync(
|
||||||
|
string taskId, string targetBranch, bool removeWorktree, string commitMessage, bool autostash = false)
|
||||||
{
|
{
|
||||||
return await InvokeTimedAsync<MergeResultDto>("MergeTask", () => _hub.InvokeAsync<MergeResultDto>(
|
return await InvokeTimedAsync<MergeResultDto>("MergeTask", () => _hub.InvokeAsync<MergeResultDto>(
|
||||||
"MergeTask", taskId, targetBranch, removeWorktree, commitMessage));
|
"MergeTask", taskId, targetBranch, removeWorktree, commitMessage, autostash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
|
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
|
||||||
@@ -475,6 +512,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public async Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day)
|
public async Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day)
|
||||||
=> await TryInvokeAsync<List<DailyNoteDto>>("GetDailyNotes", IsoDay(day)) ?? new List<DailyNoteDto>();
|
=> await TryInvokeAsync<List<DailyNoteDto>>("GetDailyNotes", IsoDay(day)) ?? new List<DailyNoteDto>();
|
||||||
|
|
||||||
|
public async Task<List<DailyNoteDto>> GetDailyNotesBetweenAsync(DateOnly start, DateOnly end)
|
||||||
|
=> await TryInvokeAsync<List<DailyNoteDto>>("GetDailyNotesBetween", IsoDay(start), IsoDay(end)) ?? new List<DailyNoteDto>();
|
||||||
|
|
||||||
public async Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text)
|
public async Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text)
|
||||||
=> await InvokeTimedAsync<DailyNoteDto>("AddDailyNote", () => _hub.InvokeAsync<DailyNoteDto>("AddDailyNote", IsoDay(day), text));
|
=> await InvokeTimedAsync<DailyNoteDto>("AddDailyNote", () => _hub.InvokeAsync<DailyNoteDto>("AddDailyNote", IsoDay(day), text));
|
||||||
|
|
||||||
@@ -487,9 +527,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public async Task<string> GetLastPrepLogAsync()
|
public async Task<string> GetLastPrepLogAsync()
|
||||||
=> await TryInvokeAsync<string>("GetLastPrepLog") ?? string.Empty;
|
=> await TryInvokeAsync<string>("GetLastPrepLog") ?? string.Empty;
|
||||||
|
|
||||||
public Task<WorkerBuildInfoDto?> GetWorkerBuildInfoAsync()
|
|
||||||
=> TryInvokeAsync<WorkerBuildInfoDto>("GetWorkerBuildInfo");
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync()
|
public async Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync()
|
||||||
=> await TryInvokeAsync<List<WorkerLogEntry>>("GetRecentLogs") ?? new List<WorkerLogEntry>();
|
=> await TryInvokeAsync<List<WorkerLogEntry>>("GetRecentLogs") ?? new List<WorkerLogEntry>();
|
||||||
|
|
||||||
@@ -544,10 +581,10 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public Task DequeueTaskAsync(string taskId)
|
public Task DequeueTaskAsync(string taskId)
|
||||||
=> InvokeTimedAsync("DequeueTask", () => _hub.InvokeAsync("DequeueTask", taskId));
|
=> InvokeTimedAsync("DequeueTask", () => _hub.InvokeAsync("DequeueTask", taskId));
|
||||||
|
|
||||||
public async Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch)
|
public async Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch, bool autostash = false)
|
||||||
{
|
{
|
||||||
LastApproveTarget = targetBranch;
|
LastApproveTarget = targetBranch;
|
||||||
return await InvokeTimedAsync<MergeResultDto>("ApproveReview", () => _hub.InvokeAsync<MergeResultDto>("ApproveReview", taskId, targetBranch));
|
return await InvokeTimedAsync<MergeResultDto>("ApproveReview", () => _hub.InvokeAsync<MergeResultDto>("ApproveReview", taskId, targetBranch, autostash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch)
|
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch)
|
||||||
@@ -610,11 +647,17 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public async Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
public async Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
||||||
=> await InvokeTimedAsync<LaunchSpec>("GetInteractiveLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetInteractiveLaunchSpec", taskId, ct));
|
=> await InvokeTimedAsync<LaunchSpec>("GetInteractiveLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetInteractiveLaunchSpec", taskId, ct));
|
||||||
|
|
||||||
public async Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
|
public async Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, string paneId, CancellationToken ct = default)
|
||||||
=> await InvokeTimedAsync<LaunchSpec>("GetAdHocLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetAdHocLaunchSpec", directory, ct));
|
=> await InvokeTimedAsync<LaunchSpec>("GetAdHocLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetAdHocLaunchSpec", directory, paneId, ct));
|
||||||
|
|
||||||
public async Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct = default)
|
public async Task<LaunchSpec> GetSessionHandoffLaunchSpecAsync(string paneId, string cwd, string briefPath, string? taskId, CancellationToken ct = default)
|
||||||
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperLaunchSpec", taskIds, listId, handlerTaskId, ct));
|
=> await InvokeTimedAsync<LaunchSpec>("GetSessionHandoffLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetSessionHandoffLaunchSpec", paneId, cwd, briefPath, taskId, ct));
|
||||||
|
|
||||||
|
public async Task<LaunchSpec> GetResumeSessionLaunchSpecAsync(string sessionId, string paneId, CancellationToken ct = default)
|
||||||
|
=> await InvokeTimedAsync<LaunchSpec>("GetResumeSessionLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetResumeSessionLaunchSpec", sessionId, paneId, ct));
|
||||||
|
|
||||||
|
public async Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, string? model = null, CancellationToken ct = default)
|
||||||
|
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperLaunchSpec", taskIds, listId, handlerTaskId, model, ct));
|
||||||
|
|
||||||
public async Task<string> CreateMergeHelperTaskAsync(
|
public async Task<string> CreateMergeHelperTaskAsync(
|
||||||
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
|
IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default)
|
||||||
@@ -624,6 +667,12 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default)
|
string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default)
|
||||||
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, nextPhase, ct));
|
=> await InvokeTimedAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetMergeHelperHandoffLaunchSpec", taskId, survivingTaskIds, nextPhase, ct));
|
||||||
|
|
||||||
|
public Task ReportInteractiveSessionExitedAsync(string taskId, int exitCode, CancellationToken ct = default)
|
||||||
|
=> InvokeTimedAsync("ReportInteractiveSessionExited", () => _hub.InvokeAsync("ReportInteractiveSessionExited", taskId, exitCode, ct));
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<HandlerOpenTaskDto>> GetHandlerOpenTasksAsync(string handlerTaskId, CancellationToken ct = default)
|
||||||
|
=> await InvokeTimedAsync<List<HandlerOpenTaskDto>>("GetHandlerOpenTasks", () => _hub.InvokeAsync<List<HandlerOpenTaskDto>>("GetHandlerOpenTasks", handlerTaskId, ct));
|
||||||
|
|
||||||
public async Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
public async Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default)
|
||||||
=> await InvokeTimedAsync<LaunchSpec>("GetPlanningStartLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetPlanningStartLaunchSpec", taskId, ct));
|
=> await InvokeTimedAsync<LaunchSpec>("GetPlanningStartLaunchSpec", () => _hub.InvokeAsync<LaunchSpec>("GetPlanningStartLaunchSpec", taskId, ct));
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public sealed class WorkerNotesApi : INotesApi
|
|||||||
private readonly IWorkerClient _client;
|
private readonly IWorkerClient _client;
|
||||||
public WorkerNotesApi(IWorkerClient client) => _client = client;
|
public WorkerNotesApi(IWorkerClient client) => _client = client;
|
||||||
public Task<List<DailyNoteDto>> ListAsync(DateOnly day) => _client.GetDailyNotesAsync(day);
|
public Task<List<DailyNoteDto>> ListAsync(DateOnly day) => _client.GetDailyNotesAsync(day);
|
||||||
|
public Task<List<DailyNoteDto>> ListBetweenAsync(DateOnly start, DateOnly end) => _client.GetDailyNotesBetweenAsync(start, end);
|
||||||
public Task<DailyNoteDto?> AddAsync(DateOnly day, string text) => _client.AddDailyNoteAsync(day, text);
|
public Task<DailyNoteDto?> AddAsync(DateOnly day, string text) => _client.AddDailyNoteAsync(day, text);
|
||||||
public Task UpdateAsync(string id, string text) => _client.UpdateDailyNoteAsync(id, text);
|
public Task UpdateAsync(string id, string text) => _client.UpdateDailyNoteAsync(id, text);
|
||||||
public Task DeleteAsync(string id) => _client.DeleteDailyNoteAsync(id);
|
public Task DeleteAsync(string id) => _client.DeleteDailyNoteAsync(id);
|
||||||
|
|||||||
@@ -208,11 +208,13 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyCommand and ticketProjectId are List-only fields owned by ListSettingsModalViewModel
|
// verifyCommand, ticketProjectId and powerUseAllowed are List-only fields owned by
|
||||||
// (not this editor, which is also reused for Task scope); the caller passes them through so the
|
// ListSettingsModalViewModel (not this editor, which is also reused for Task scope); the caller
|
||||||
// single UpdateListConfig call carries the full desired row instead of clobbering it.
|
// passes them through so the single UpdateListConfig call carries the full desired row instead
|
||||||
|
// of clobbering it. powerUseAllowed stays null-by-default for the same reason ticketProjectId
|
||||||
|
// does: a caller that doesn't own it must not revoke the list's opt-in by omission.
|
||||||
public async System.Threading.Tasks.Task SaveAsync(
|
public async System.Threading.Tasks.Task SaveAsync(
|
||||||
string? verifyCommand = null, int? ticketProjectId = null)
|
string? verifyCommand = null, int? ticketProjectId = null, bool? powerUseAllowed = null)
|
||||||
{
|
{
|
||||||
if (TargetId is null) return;
|
if (TargetId is null) return;
|
||||||
var model = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
var model = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
||||||
@@ -225,7 +227,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
if (_scope == AgentConfigScope.Task)
|
if (_scope == AgentConfigScope.Task)
|
||||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills, permission));
|
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills, permission));
|
||||||
else
|
else
|
||||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, permission, ticketProjectId));
|
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, permission, ticketProjectId, powerUseAllowed));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string>? SelectedSessionSkillNames()
|
private List<string>? SelectedSessionSkillNames()
|
||||||
@@ -234,6 +236,10 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
return names.Count == 0 ? null : names;
|
return names.Count == 0 ? null : names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The list_config row fetched by the most recent LoadForListAsync — exposed so a caller that
|
||||||
|
// needs the same row (ListSettingsModalViewModel) can reuse it instead of fetching it again.
|
||||||
|
public ListConfigDto? LastLoadedListConfig { get; private set; }
|
||||||
|
|
||||||
public async System.Threading.Tasks.Task LoadForListAsync(string listId, CancellationToken ct = default)
|
public async System.Threading.Tasks.Task LoadForListAsync(string listId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
_suppressSave = true;
|
_suppressSave = true;
|
||||||
@@ -244,6 +250,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
await LoadGlobalDefaultsAsync();
|
await LoadGlobalDefaultsAsync();
|
||||||
|
|
||||||
var cfg = await _worker.GetListConfigAsync(listId);
|
var cfg = await _worker.GetListConfigAsync(listId);
|
||||||
|
LastLoadedListConfig = cfg;
|
||||||
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath, cfg?.PermissionMode);
|
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath, cfg?.PermissionMode);
|
||||||
await ReloadSessionSkillsAsync(cfg?.SessionSkills);
|
await ReloadSessionSkillsAsync(cfg?.SessionSkills);
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ public sealed partial class ConflictResolverViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
// Any non-conflict outcome here means nothing was left mid-merge for this dialog
|
// Any non-conflict outcome here means nothing was left mid-merge for this dialog
|
||||||
// to resolve -- surface why instead of quietly closing with no explanation.
|
// to resolve -- surface why instead of quietly closing with no explanation.
|
||||||
if (start.Status is "blocked" or "untracked_collision")
|
if (start.Status is "blocked" or "untracked_collision" or "dirty_target")
|
||||||
Error = start.ErrorMessage;
|
Error = start.ErrorMessage;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using ClaudeDo.Ui.Localization;
|
||||||
using ClaudeDo.Ui.Services;
|
using ClaudeDo.Ui.Services;
|
||||||
using Iciclecreek.Terminal;
|
using Iciclecreek.Terminal;
|
||||||
|
|
||||||
@@ -36,14 +37,29 @@ public sealed partial class InteractiveTerminalViewModel : ViewModelBase, IDispo
|
|||||||
/// can show a spinner instead of an empty black pane.</summary>
|
/// can show a spinner instead of an empty black pane.</summary>
|
||||||
public bool IsStarting => !IsRunning && !HasExited && StartError is null;
|
public bool IsStarting => !IsRunning && !HasExited && StartError is null;
|
||||||
|
|
||||||
|
/// <summary>True once the process has died without a <see cref="StartError"/> already covering it —
|
||||||
|
/// i.e. every death that isn't a launch-time failure: a crash after running a while, a clean exit,
|
||||||
|
/// or a quiet exit inside the startup grace window. Drives the host's "session ended" banner so a
|
||||||
|
/// dead pane never looks like it's still running/thinking.</summary>
|
||||||
|
public bool HasExitedAfterStartup => HasExited && StartError is null;
|
||||||
|
|
||||||
partial void OnIsRunningChanged(bool value)
|
partial void OnIsRunningChanged(bool value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsStarting));
|
OnPropertyChanged(nameof(IsStarting));
|
||||||
if (value) _startedAtUtc = _utcNow();
|
if (value) _startedAtUtc = _utcNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnHasExitedChanged(bool value) => OnPropertyChanged(nameof(IsStarting));
|
partial void OnHasExitedChanged(bool value)
|
||||||
partial void OnStartErrorChanged(string? value) => OnPropertyChanged(nameof(IsStarting));
|
{
|
||||||
|
OnPropertyChanged(nameof(IsStarting));
|
||||||
|
OnPropertyChanged(nameof(HasExitedAfterStartup));
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnStartErrorChanged(string? value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(IsStarting));
|
||||||
|
OnPropertyChanged(nameof(HasExitedAfterStartup));
|
||||||
|
}
|
||||||
|
|
||||||
public InteractiveTerminalViewModel() : this(() => DateTime.UtcNow)
|
public InteractiveTerminalViewModel() : this(() => DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
@@ -96,6 +112,22 @@ public sealed partial class InteractiveTerminalViewModel : ViewModelBase, IDispo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Raised when a user-initiated paste or drop couldn't reach the session (write seam
|
||||||
|
/// gone, no running process) — mirrors the island ViewModels' ErrorReported, forwarded by
|
||||||
|
/// <see cref="ClaudeDo.Ui.ViewModels.MissionControl.ConPtyPaneViewModel"/> into the footer error strip.</summary>
|
||||||
|
public event Action<string>? ErrorReported;
|
||||||
|
|
||||||
|
/// <summary>Called by the view after a failed <see cref="PasteAsync"/>/<see cref="SendTextAsync"/>
|
||||||
|
/// so the failure surfaces through <see cref="ErrorReported"/> instead of vanishing silently.</summary>
|
||||||
|
public void ReportSendFailed() => ErrorReported?.Invoke(Loc.T("missionControl.terminalSendFailed"));
|
||||||
|
|
||||||
|
/// <summary>Raised when the process died non-zero *after* clearing <see cref="StartupGraceWindow"/> —
|
||||||
|
/// i.e. a session that ran, then broke. That used to be a silent dead tile with no signal at all;
|
||||||
|
/// the worker turns it into a usage-limit auto-restart for list-handler sessions
|
||||||
|
/// (<c>HandlerRestartCoordinator</c>). A startup death keeps using <see cref="StartError"/> instead:
|
||||||
|
/// it never reached a state worth resuming.</summary>
|
||||||
|
public event Action<int>? ExitedAfterStartup;
|
||||||
|
|
||||||
/// <summary>Internal (not private) so tests can drive it directly without a real ConPTY spawn.</summary>
|
/// <summary>Internal (not private) so tests can drive it directly without a real ConPTY spawn.</summary>
|
||||||
internal void OnSessionProcessExited(object? sender, int exitCode)
|
internal void OnSessionProcessExited(object? sender, int exitCode)
|
||||||
{
|
{
|
||||||
@@ -103,11 +135,16 @@ public sealed partial class InteractiveTerminalViewModel : ViewModelBase, IDispo
|
|||||||
HasExited = true;
|
HasExited = true;
|
||||||
ExitCode = exitCode;
|
ExitCode = exitCode;
|
||||||
|
|
||||||
|
if (exitCode == 0) return;
|
||||||
|
if (_startedAtUtc is not { } startedAt) return; // never reached "running" — nothing ran to resume
|
||||||
|
|
||||||
// Died at startup: route through the same banner as a launch-time failure instead of
|
// Died at startup: route through the same banner as a launch-time failure instead of
|
||||||
// leaving a dead terminal with no Retry affordance (ConPtyPaneViewModel.CanRetry
|
// leaving a dead terminal with no Retry affordance (ConPtyPaneViewModel.CanRetry
|
||||||
// requires StartError).
|
// requires StartError).
|
||||||
if (exitCode != 0 && _startedAtUtc is { } startedAt && _utcNow() - startedAt < StartupGraceWindow)
|
if (_utcNow() - startedAt < StartupGraceWindow)
|
||||||
StartError = $"Session exited immediately (code {exitCode}).";
|
StartError = $"Session exited immediately (code {exitCode}).";
|
||||||
|
else
|
||||||
|
ExitedAfterStartup?.Invoke(exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reports a failure that happened before <see cref="Start"/> could be called (e.g. the
|
/// <summary>Reports a failure that happened before <see cref="Start"/> could be called (e.g. the
|
||||||
@@ -119,6 +156,20 @@ public sealed partial class InteractiveTerminalViewModel : ViewModelBase, IDispo
|
|||||||
StartError = message;
|
StartError = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Types a line into the live session and submits it — see
|
||||||
|
/// <see cref="PtyTerminalSession.SendLineAsync"/>. False when there is no running process to
|
||||||
|
/// type into (or the write seam is gone).</summary>
|
||||||
|
public Task<bool> SendLineAsync(string text) => _session.SendLineAsync(text);
|
||||||
|
|
||||||
|
/// <summary>Inserts text at the cursor without submitting — see
|
||||||
|
/// <see cref="PtyTerminalSession.SendTextAsync"/> (dropped file paths).</summary>
|
||||||
|
public Task<bool> SendTextAsync(string text) => _session.SendTextAsync(text);
|
||||||
|
|
||||||
|
/// <summary>Pastes the clipboard into the session (Ctrl+V) — see
|
||||||
|
/// <see cref="PtyTerminalSession.PasteAsync"/>. False when there is no running process to
|
||||||
|
/// paste into (or the write seam is gone).</summary>
|
||||||
|
public Task<bool> PasteAsync() => _session.PasteAsync();
|
||||||
|
|
||||||
public void Kill() => _session.Kill();
|
public void Kill() => _session.Kill();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||||
private readonly IWorkerClient _worker;
|
private readonly IWorkerClient _worker;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly INotesApi _notesApi;
|
|
||||||
private readonly MergeCoordinator _merge;
|
private readonly MergeCoordinator _merge;
|
||||||
|
|
||||||
// ── Section view models ───────────────────────────────────────────────────
|
// ── Section view models ───────────────────────────────────────────────────
|
||||||
@@ -39,29 +38,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
private readonly Action<string, string?> _workerPlanningMergeAbortedHandler;
|
private readonly Action<string, string?> _workerPlanningMergeAbortedHandler;
|
||||||
private readonly Action<string> _workerPlanningCompletedHandler;
|
private readonly Action<string> _workerPlanningCompletedHandler;
|
||||||
|
|
||||||
[ObservableProperty] private bool _isNotesMode;
|
|
||||||
[ObservableProperty] private bool _isPrepMode;
|
[ObservableProperty] private bool _isPrepMode;
|
||||||
|
|
||||||
public bool IsTaskDetailVisible => !IsNotesMode && !IsPrepMode;
|
public bool IsTaskDetailVisible => !IsPrepMode;
|
||||||
|
|
||||||
/// <summary>Centered placeholder shown when no task is selected — hidden in Notes/Prep mode,
|
/// <summary>Centered placeholder shown when no task is selected — hidden in Prep mode,
|
||||||
/// where <see cref="IsTaskDetailVisible"/> is already false.</summary>
|
/// where <see cref="IsTaskDetailVisible"/> is already false.</summary>
|
||||||
public bool IsEmptyStateVisible => Task is null && IsTaskDetailVisible;
|
public bool IsEmptyStateVisible => Task is null && IsTaskDetailVisible;
|
||||||
|
|
||||||
partial void OnIsNotesModeChanged(bool value)
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(IsTaskDetailVisible));
|
|
||||||
OnPropertyChanged(nameof(IsEmptyStateVisible));
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnIsPrepModeChanged(bool value)
|
partial void OnIsPrepModeChanged(bool value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsTaskDetailVisible));
|
OnPropertyChanged(nameof(IsTaskDetailVisible));
|
||||||
OnPropertyChanged(nameof(IsEmptyStateVisible));
|
OnPropertyChanged(nameof(IsEmptyStateVisible));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NotesEditorViewModel Notes { get; private set; } = null!;
|
|
||||||
|
|
||||||
// Current task row (set by IslandsShellViewModel via Bind)
|
// Current task row (set by IslandsShellViewModel via Bind)
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyCanExecuteChangedFor(nameof(EnqueueCommand))]
|
[NotifyCanExecuteChangedFor(nameof(EnqueueCommand))]
|
||||||
@@ -295,17 +285,15 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
private bool _suppressTitleSave;
|
private bool _suppressTitleSave;
|
||||||
private CancellationTokenSource? _titleSaveCts;
|
private CancellationTokenSource? _titleSaveCts;
|
||||||
|
|
||||||
// Set by shell so CloseDetailCommand can clear SelectedTask
|
// Set by shell so deleting a task can clear SelectedTask
|
||||||
public Action? CloseDetail { get; set; }
|
public Action? CloseDetail { get; set; }
|
||||||
|
|
||||||
// Set by shell so DeleteTaskCommand can remove from list
|
// Set by shell so DeleteTaskCommand can remove from list
|
||||||
public Func<TaskRowViewModel, System.Threading.Tasks.Task>? DeleteFromList { get; set; }
|
public Func<TaskRowViewModel, System.Threading.Tasks.Task>? DeleteFromList { get; set; }
|
||||||
|
|
||||||
// Set by the view so DeleteTaskCommand can prompt yes/no before deleting
|
// Set by the shell (mirrors ListsIslandViewModel.Dialogs) so confirm/error prompts go
|
||||||
public Func<string, System.Threading.Tasks.Task<bool>>? ConfirmAsync { get; set; }
|
// through the one shared dialog implementation instead of a view-local copy.
|
||||||
|
public IDialogService? Dialogs { get; set; }
|
||||||
// Set by the view so DeleteTaskCommand can show an error message
|
|
||||||
public Func<string, System.Threading.Tasks.Task>? ShowErrorAsync { get; set; }
|
|
||||||
|
|
||||||
// ── Review ──────────────────────────────────────────────────────────────
|
// ── Review ──────────────────────────────────────────────────────────────
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -324,13 +312,11 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||||
IWorkerClient worker,
|
IWorkerClient worker,
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
INotesApi notesApi,
|
|
||||||
MergeCoordinator merge)
|
MergeCoordinator merge)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
_services = services;
|
_services = services;
|
||||||
_notesApi = notesApi;
|
|
||||||
_merge = merge;
|
_merge = merge;
|
||||||
|
|
||||||
Monitor = new TaskMonitorViewModel(dbFactory, worker);
|
Monitor = new TaskMonitorViewModel(dbFactory, worker);
|
||||||
@@ -374,8 +360,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
if (e.PropertyName == nameof(OperationStatus.IsRunning)) ParkReviewCommand.NotifyCanExecuteChanged();
|
if (e.PropertyName == nameof(OperationStatus.IsRunning)) ParkReviewCommand.NotifyCanExecuteChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
Notes = new NotesEditorViewModel(_notesApi);
|
|
||||||
Notes.ErrorReported += msg => { if (ShowErrorAsync is not null) _ = ShowErrorAsync(msg); };
|
|
||||||
Subtasks.CollectionChanged += (_, _) => NotifyStepsChanged();
|
Subtasks.CollectionChanged += (_, _) => NotifyStepsChanged();
|
||||||
Subtasks.CollectionChanged += (_, _) => Merge.SyncChildOutcomes(HasChildOutcomes, Subtasks.Count);
|
Subtasks.CollectionChanged += (_, _) => Merge.SyncChildOutcomes(HasChildOutcomes, Subtasks.Count);
|
||||||
Attachments.CollectionChanged += (_, _) => OnPropertyChanged(nameof(FilesBadge));
|
Attachments.CollectionChanged += (_, _) => OnPropertyChanged(nameof(FilesBadge));
|
||||||
@@ -592,18 +576,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowNotes()
|
|
||||||
{
|
|
||||||
Bind(null, "notes");
|
|
||||||
IsPrepMode = false;
|
|
||||||
IsNotesMode = true;
|
|
||||||
_ = Notes.LoadDayAsync(DateOnly.FromDateTime(DateTime.Today));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowPrep()
|
public void ShowPrep()
|
||||||
{
|
{
|
||||||
Bind(null, "prep");
|
Bind(null, "prep");
|
||||||
IsNotesMode = false;
|
|
||||||
IsPrepMode = true;
|
IsPrepMode = true;
|
||||||
_ = Prep.LoadLastPrepLogIfEmptyAsync();
|
_ = Prep.LoadLastPrepLogIfEmptyAsync();
|
||||||
}
|
}
|
||||||
@@ -612,7 +587,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
/// logged with the bind timing so rebind churn can be attributed to a trigger.</param>
|
/// logged with the bind timing so rebind churn can be attributed to a trigger.</param>
|
||||||
public void Bind(TaskRowViewModel? row, string source = "?")
|
public void Bind(TaskRowViewModel? row, string source = "?")
|
||||||
{
|
{
|
||||||
IsNotesMode = false;
|
|
||||||
IsPrepMode = false;
|
IsPrepMode = false;
|
||||||
_loadCts?.Cancel();
|
_loadCts?.Cancel();
|
||||||
_loadCts?.Dispose();
|
_loadCts?.Dispose();
|
||||||
@@ -752,6 +726,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
Merge.MergeTargetBranches.Clear();
|
Merge.MergeTargetBranches.Clear();
|
||||||
foreach (var b in targets.LocalBranches) Merge.MergeTargetBranches.Add(b);
|
foreach (var b in targets.LocalBranches) Merge.MergeTargetBranches.Add(b);
|
||||||
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
||||||
|
Merge.TargetDirty = targets.TargetDirty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Merge.RefreshMergePreviewAsync();
|
await Merge.RefreshMergePreviewAsync();
|
||||||
@@ -802,6 +777,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
foreach (var b in targets.LocalBranches)
|
foreach (var b in targets.LocalBranches)
|
||||||
Merge.MergeTargetBranches.Add(b);
|
Merge.MergeTargetBranches.Add(b);
|
||||||
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
||||||
|
Merge.TargetDirty = targets.TargetDirty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,6 +824,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
foreach (var b in targets.LocalBranches)
|
foreach (var b in targets.LocalBranches)
|
||||||
Merge.MergeTargetBranches.Add(b);
|
Merge.MergeTargetBranches.Add(b);
|
||||||
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
Merge.SelectedMergeTarget = targets.DefaultBranch;
|
||||||
|
Merge.TargetDirty = targets.TargetDirty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1008,9 +985,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void CloseDetails() => CloseDetail?.Invoke();
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async System.Threading.Tasks.Task ToggleStarAsync()
|
private async System.Threading.Tasks.Task ToggleStarAsync()
|
||||||
{
|
{
|
||||||
@@ -1069,9 +1043,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
{
|
{
|
||||||
if (Task == null) return;
|
if (Task == null) return;
|
||||||
var row = Task;
|
var row = Task;
|
||||||
if (ConfirmAsync != null)
|
if (Dialogs != null)
|
||||||
{
|
{
|
||||||
var ok = await ConfirmAsync($"Delete \"{row.Title}\"? This cannot be undone.");
|
var ok = await Dialogs.ConfirmAsync($"Delete \"{row.Title}\"? This cannot be undone.", Loc.T("dialog.delete"));
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
// Routed through the worker (mirrors the MCP delete_task tool) so a deleted child
|
// Routed through the worker (mirrors the MCP delete_task tool) so a deleted child
|
||||||
@@ -1087,14 +1061,14 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
{
|
{
|
||||||
// Belt and braces: the connection can drop between the CanExecute check and
|
// Belt and braces: the connection can drop between the CanExecute check and
|
||||||
// this call, so a stray throw here must surface, not vanish silently.
|
// this call, so a stray throw here must surface, not vanish silently.
|
||||||
if (ShowErrorAsync != null)
|
if (Dialogs != null)
|
||||||
await ShowErrorAsync(ex.Message);
|
await Dialogs.ShowErrorAsync(ex.Message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!deleted)
|
if (!deleted)
|
||||||
{
|
{
|
||||||
if (ShowErrorAsync != null)
|
if (Dialogs != null)
|
||||||
await ShowErrorAsync(error ?? "Delete failed.");
|
await Dialogs.ShowErrorAsync(error ?? "Delete failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DeleteFromList != null)
|
if (DeleteFromList != null)
|
||||||
@@ -1243,10 +1217,10 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
private async System.Threading.Tasks.Task ResetAndRetryAsync()
|
private async System.Threading.Tasks.Task ResetAndRetryAsync()
|
||||||
{
|
{
|
||||||
if (Task == null) return;
|
if (Task == null) return;
|
||||||
if (ConfirmAsync == null) return;
|
if (Dialogs == null) return;
|
||||||
|
|
||||||
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
||||||
var ok = await ConfirmAsync(
|
var ok = await Dialogs.ConfirmAsync(
|
||||||
$"Reset and retry?\nThis discards branch {branchName} (and uncommitted changes), then queues the task to run from the beginning.");
|
$"Reset and retry?\nThis discards branch {branchName} (and uncommitted changes), then queues the task to run from the beginning.");
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
@@ -1289,43 +1263,51 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
using var op = ApproveOp.Begin(Loc.T("ops.merge.merging"));
|
using var op = ApproveOp.Begin(Loc.T("ops.merge.merging"));
|
||||||
// Subscribed only for the duration of the call: the worker broadcasts to every
|
// Subscribed only for the duration of the call: the worker broadcasts to every
|
||||||
// client, and a VM left on this event would keep reacting to merges for other tasks.
|
// client, and a VM left on this event would keep reacting to merges for other tasks.
|
||||||
void OnMergeProgress(string progressTaskId, string phase, int elapsedSeconds)
|
|
||||||
{
|
|
||||||
if (progressTaskId != taskId || phase != MergePhaseVerifying) return;
|
|
||||||
ApproveOp.Report(Loc.T("ops.merge.verifying", FormatElapsed(elapsedSeconds)));
|
|
||||||
}
|
|
||||||
// Separate event: MergeProgressEvent forwards elapsed-seconds-only (verifying), but the
|
|
||||||
// rebase phase needs the branch count too, which only rides the untrimmed OperationProgress.
|
|
||||||
void OnOperationProgress(string opKey, string phase, int current, int total)
|
void OnOperationProgress(string opKey, string phase, int current, int total)
|
||||||
{
|
{
|
||||||
if (opKey != taskId || phase != MergePhaseRebasing) return;
|
if (opKey != taskId) return;
|
||||||
ApproveOp.Report(FormatRebasing(current, total));
|
if (phase == MergePhaseVerifying)
|
||||||
|
ApproveOp.Report(Loc.T("ops.merge.verifying", FormatElapsed(current)));
|
||||||
|
else if (phase == MergePhaseRebasing)
|
||||||
|
ApproveOp.Report(FormatRebasing(current, total));
|
||||||
}
|
}
|
||||||
_worker.MergeProgressEvent += OnMergeProgress;
|
|
||||||
_worker.OperationProgressEvent += OnOperationProgress;
|
_worker.OperationProgressEvent += OnOperationProgress;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hasChildren = Subtasks.Count > 0 || ChildOutcomes.Count > 0;
|
var hasChildren = Subtasks.Count > 0 || ChildOutcomes.Count > 0;
|
||||||
var result = await _worker.ApproveReviewAsync(taskId, Merge.SelectedMergeTarget ?? "");
|
var result = await _worker.ApproveReviewAsync(taskId, Merge.SelectedMergeTarget ?? "");
|
||||||
if (!hasChildren && result?.Status == "conflict")
|
|
||||||
|
// Nothing landed -- the target working dir has uncommitted changes (often another
|
||||||
|
// session's WIP in the shared main checkout). Offer the one-shot stash retry; git
|
||||||
|
// restores the changes itself once the merge is through.
|
||||||
|
if (result?.Status == "dirty_target" && Dialogs != null
|
||||||
|
&& await Dialogs.ConfirmAsync(Loc.T("vm.detailsIsland.stashAndMergeConfirm")))
|
||||||
|
{
|
||||||
|
ApproveOp.Report(Loc.T("ops.merge.merging"));
|
||||||
|
result = await _worker.ApproveReviewAsync(
|
||||||
|
taskId, Merge.SelectedMergeTarget ?? "", autostash: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result?.Status == "dirty_target" && Dialogs != null)
|
||||||
|
await Dialogs.ShowErrorAsync(result.ErrorMessage ?? Loc.T("vm.detailsIsland.dirtyTarget"));
|
||||||
|
else if (!hasChildren && result?.Status == "conflict")
|
||||||
await _merge.ResolveConflictAsync(taskId, Merge.SelectedMergeTarget ?? "");
|
await _merge.ResolveConflictAsync(taskId, Merge.SelectedMergeTarget ?? "");
|
||||||
// The merge itself already landed; the verify command failed, so the task stayed
|
// The merge itself already landed; the verify command failed, so the task stayed
|
||||||
// out of Done. Surface that instead of silently looking like nothing happened.
|
// out of Done. Surface that instead of silently looking like nothing happened.
|
||||||
else if (!hasChildren && result?.Status == "verify_failed" && ShowErrorAsync != null)
|
else if (!hasChildren && result?.Status == "verify_failed" && Dialogs != null)
|
||||||
await ShowErrorAsync(result.ErrorMessage ?? Loc.T("vm.detailsIsland.verifyFailed"));
|
await Dialogs.ShowErrorAsync(result.ErrorMessage ?? Loc.T("vm.detailsIsland.verifyFailed"));
|
||||||
// Nothing landed -- the merge was refused before touching the repo because the
|
// Nothing landed -- the merge was refused before touching the repo because the
|
||||||
// branch would have overwritten an untracked file in the target working directory.
|
// branch would have overwritten an untracked file in the target working directory.
|
||||||
else if (!hasChildren && result?.Status == "untracked_collision" && ShowErrorAsync != null)
|
else if (!hasChildren && result?.Status == "untracked_collision" && Dialogs != null)
|
||||||
await ShowErrorAsync(result.ErrorMessage ?? Loc.T("vm.detailsIsland.untrackedCollision"));
|
await Dialogs.ShowErrorAsync(result.ErrorMessage ?? Loc.T("vm.detailsIsland.untrackedCollision"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (ShowErrorAsync != null)
|
if (Dialogs != null)
|
||||||
await ShowErrorAsync(ex.Message);
|
await Dialogs.ShowErrorAsync(ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_worker.MergeProgressEvent -= OnMergeProgress;
|
|
||||||
_worker.OperationProgressEvent -= OnOperationProgress;
|
_worker.OperationProgressEvent -= OnOperationProgress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1367,7 +1349,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
try { await _worker.SubmitTaskForReviewAsync(Task.Id); }
|
try { await _worker.SubmitTaskForReviewAsync(Task.Id); }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (ShowErrorAsync != null) await ShowErrorAsync(ex.Message);
|
if (Dialogs != null) await Dialogs.ShowErrorAsync(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1398,9 +1380,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async System.Threading.Tasks.Task ResetReviewAsync()
|
private async System.Threading.Tasks.Task ResetReviewAsync()
|
||||||
{
|
{
|
||||||
if (Task is null || !_worker.IsConnected || ConfirmAsync is null) return;
|
if (Task is null || !_worker.IsConnected || Dialogs is null) return;
|
||||||
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
||||||
var ok = await ConfirmAsync(
|
var ok = await Dialogs.ConfirmAsync(
|
||||||
$"Reset working tree?\nThis discards branch {branchName} (and all changes) and returns the task to Idle.");
|
$"Reset working tree?\nThis discards branch {branchName} (and all changes) and returns the task to Idle.");
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
try { await _worker.ResetTaskAsync(Task.Id); }
|
try { await _worker.ResetTaskAsync(Task.Id); }
|
||||||
@@ -1536,12 +1518,17 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
catch { /* best-effort */ }
|
catch { /* best-effort */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared by TaskRowViewModel.UpdateFromEntity — this is the one parser for
|
||||||
|
// `git diff --stat`'s summary line ("N insertions(+), M deletions(-)"). Anchored on the
|
||||||
|
// literal "(+)"/"(-)" markers rather than the English words: git for Windows localizes
|
||||||
|
// "insertion"/"deletion" to the OS language (e.g. German "Einfügung(en)"/"Löschung(en)"),
|
||||||
|
// but always keeps the untranslated (+)/(-) suffix, so this reads correctly in any locale.
|
||||||
internal static (int Additions, int Deletions) ParseDiffStat(string? stat)
|
internal static (int Additions, int Deletions) ParseDiffStat(string? stat)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(stat)) return (0, 0);
|
if (string.IsNullOrEmpty(stat)) return (0, 0);
|
||||||
int add = 0, del = 0;
|
int add = 0, del = 0;
|
||||||
var m1 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)\s+insertion");
|
var m1 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)[^,\n]*\(\+\)");
|
||||||
var m2 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)\s+deletion");
|
var m2 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)[^,\n]*\(-\)");
|
||||||
if (m1.Success) int.TryParse(m1.Groups[1].Value, out add);
|
if (m1.Success) int.TryParse(m1.Groups[1].Value, out add);
|
||||||
if (m2.Success) int.TryParse(m2.Groups[1].Value, out del);
|
if (m2.Success) int.TryParse(m2.Groups[1].Value, out del);
|
||||||
return (add, del);
|
return (add, del);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user