Skip to content

Commit 0ae8fe7

Browse files
aksOpsclaude
andcommitted
test: coverage uplift to >85% — UI routes, server, yolo refactor
Final piece of the SonarCloud coverage push. Four parallel test sweeps, plus the long-deferred cmd/yolo.go refactor. UI: - ui/src/routes/SessionDetail.test.tsx — 15 tests covering tabs, sessionStorage persistence, attention border, every Checkpoints state, Meta tab. Mocks the SSE-backed children (PaneView, FeedStream, SubagentTree, etc.) since they have their own dedicated suites. Coverage: 0% → 98.95% lines / 85.5% branches. - ui/src/components/SseProvider.test.tsx — extended from 2 → 19 tests. Drives the mocked fetchEventSource onmessage/onerror callbacks per test to cover session_new/_attached/_killed, tool_call feed (incl. 500-row FEED_CAP trim), quota updates, attention raised/cleared, subagent/team invalidations, disconnect-grace timer scheduling. Coverage: 21.8% → 98.4% lines. - ui/src/components/SessionListPanel.test.tsx — new, 8 tests covering loading skeletons, empty/populated/error/no-sessions, "Show all" toggle, footer link, accessibility. 0% → 100%. Server: - internal/serve/server_extra_test.go — 36 tests. Shutdown 0% → 100%, Addr/Hub 0% → 100%, rescanTailers 0% → 95%, registerRoutes 77% → 88%, plus full coverage on ContextPct/Tokens/Attention/etc. adapters. - internal/serve/server.go — extracted buildSessionMaps and resolveLogUUIDToName from the duplicated UUID-resolution blocks Sonar flagged at lines 384-401 ↔ 543-559. Both new helpers at 100% coverage; both call sites collapsed. - Coverage: 59% → 82.5%. Cmd: - cmd/yolo.go — re-attempted refactor TDD-style. Eight pure helpers (decideModeAction, bannerFor, eventsFor, fireLaunchEvents, resolveSimpleName, resolveModeTarget, tearDownForRecreate, printBanner) average 93% coverage. Both Sonar dup blocks (24-line preamble × 2, 24-line resume-or-recreate × 2) collapsed. Behaviour preserved across runYolo / runYoloBang / runSafe. - cmd/yolo_helpers_test.go — 12 funcs, 28 sub-cases. Verification: - 830 Go tests pass across 27 packages with -tags sqlite_fts5 (was 762). - 155 UI tests pass (was 110). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6ead98c commit 0ae8fe7

7 files changed

Lines changed: 2744 additions & 161 deletions

File tree

cmd/yolo.go

Lines changed: 152 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,131 @@ func shouldResumeExisting(sess *session.Session, requestedMode string) bool {
3737
return sess != nil && sess.Mode == requestedMode
3838
}
3939

40+
// modeDecision is the action a yolo/safe launch must take given the
41+
// state of the store at launch time.
42+
type modeDecision int
43+
44+
const (
45+
// decisionFresh: no stored session — create from scratch.
46+
decisionFresh modeDecision = iota
47+
// decisionResume: stored session matches requested mode — preflight + reattach.
48+
decisionResume
49+
// decisionRecreate: stored session is in a different mode — kill+delete then create.
50+
decisionRecreate
51+
)
52+
53+
// decideModeAction maps the (store-lookup result, requested mode) pair to
54+
// one of three actions. Pure function — easy to unit-test.
55+
func decideModeAction(sess *session.Session, getErr error, requestedMode string) modeDecision {
56+
if getErr != nil {
57+
return decisionFresh
58+
}
59+
if shouldResumeExisting(sess, requestedMode) {
60+
return decisionResume
61+
}
62+
return decisionRecreate
63+
}
64+
65+
// bannerFor returns the banner text and styling flag for a given launch mode.
66+
// magenta=true → out.Magenta; false → out.Success (green). Unknown modes fall
67+
// back to safe-style green so the screen never goes silent.
68+
func bannerFor(mode string) (text string, magenta bool) {
69+
if mode == "yolo" {
70+
return ">>> YOLO MODE", true
71+
}
72+
upper := ""
73+
for _, r := range mode {
74+
if r >= 'a' && r <= 'z' {
75+
upper += string(r - 32)
76+
} else {
77+
upper += string(r)
78+
}
79+
}
80+
return fmt.Sprintf(">>> %s MODE", upper), false
81+
}
82+
83+
// eventsFor returns the (user-hook event, serve-hub event) pair for a mode.
84+
// Yolo fires "on_yolo" to both. Safe fires "on_safe" to user hooks but maps
85+
// to "session_attached" on the serve hub — the hub does not model a separate
86+
// safe-mode lifecycle, only the attach transition.
87+
func eventsFor(mode string) (hookEvent, serveEvent string) {
88+
if mode == "yolo" {
89+
return "on_yolo", "on_yolo"
90+
}
91+
return "on_" + mode, "session_attached"
92+
}
93+
94+
// fireLaunchEvents fires both the user-defined shell hook and the serve-hub
95+
// event for a launch in the given mode. Failures inside fireHook /
96+
// fireServeEvent are already swallowed; this wrapper just composes them.
97+
func fireLaunchEvents(store *session.Store, name, workdir, mode string) {
98+
hookEvent, serveEvent := eventsFor(mode)
99+
intent := yoloIntent(store, name, workdir, mode)
100+
fireHook(hookEvent, intent)
101+
fireServeEvent(serveEvent, intent)
102+
}
103+
104+
// resolveSimpleName returns args[0] when present, else "claude". This is the
105+
// name-resolution rule shared by `ctm yolo!` and `ctm safe`. (`ctm yolo` has a
106+
// richer rule that also handles 2-arg form and prompts for a path, so it
107+
// stays inline.)
108+
func resolveSimpleName(args []string) string {
109+
if len(args) > 0 {
110+
return args[0]
111+
}
112+
return "claude"
113+
}
114+
115+
// resolveModeTarget produces the (name, workdir) pair used by `ctm yolo!` and
116+
// `ctm safe`. Validates the name and resolves the workdir from the store, the
117+
// running tmux pane, or the current working directory in that order.
118+
func resolveModeTarget(args []string, store *session.Store, tc *tmux.Client) (string, string, error) {
119+
name := resolveSimpleName(args)
120+
if err := session.ValidateName(name); err != nil {
121+
return "", "", err
122+
}
123+
workdir, err := resolveWorkdir(name, store, tc)
124+
if err != nil {
125+
return "", "", err
126+
}
127+
return name, workdir, nil
128+
}
129+
130+
// tearDownForRecreate drops the tmux session and store record so that a fresh
131+
// UUID can be minted. Used when the requested mode differs from the stored
132+
// mode, or when `ctm yolo!` forces fresh state.
133+
//
134+
// loudOnDeleteErr controls the original yolo/safe asymmetry: `ctm yolo`
135+
// warns on a store.Delete failure; `ctm yolo!` swallows the error (it's a
136+
// force-reset path). Preserved verbatim so this is a pure refactor.
137+
func tearDownForRecreate(name string, store *session.Store, tc *tmux.Client, out *output.Printer, loudOnDeleteErr bool) {
138+
if tc.HasSession(name) {
139+
if err := tc.KillSession(name); err != nil {
140+
out.Warn("could not kill existing session: %v", err)
141+
}
142+
}
143+
if err := store.Delete(name); err != nil {
144+
if loudOnDeleteErr {
145+
out.Warn("could not remove session from store: %v", err)
146+
}
147+
// Silent branch: `ctm yolo!` ignores not-found and IO errors here.
148+
_ = err
149+
}
150+
}
151+
152+
// printBanner prints the launch banner using the appropriate color for mode.
153+
// We pass the text via `%s` so the banner string is never treated as a format
154+
// string — defensive against future refactors where the banner becomes
155+
// data-driven (silences `go vet` non-constant format string warnings).
156+
func printBanner(out *output.Printer, mode string) {
157+
text, magenta := bannerFor(mode)
158+
if magenta {
159+
out.Magenta("%s", text)
160+
} else {
161+
out.Success("%s", text)
162+
}
163+
}
164+
40165
var yoloCmd = &cobra.Command{
41166
Use: "yolo [name] [path]",
42167
Short: "Launch or relaunch a session in YOLO (unrestricted) mode",
@@ -117,32 +242,22 @@ func runYolo(cmd *cobra.Command, args []string) error {
117242
gitCheckpoint(workdir, out)
118243
}
119244

120-
out.Magenta(">>> YOLO MODE")
121-
{
122-
intent := yoloIntent(store, name, workdir, "yolo")
123-
fireHook("on_yolo", intent)
124-
fireServeEvent("on_yolo", intent)
125-
}
245+
printBanner(out, "yolo")
246+
fireLaunchEvents(store, name, workdir, "yolo")
126247

127248
// If session exists and mode matches → preflight. preflight handles both
128249
// live tmux (plain reattach) and dead tmux (recreate with --resume UUID),
129250
// so the session's claude history survives `claude` exiting on its own.
130251
// Only kill/delete when the mode actually changes (safe → yolo) or when
131252
// the user forces fresh state via `ctm yolo!` / `ctm kill`.
132-
if sess, err := store.Get(name); err == nil {
133-
if shouldResumeExisting(sess, "yolo") {
134-
out.Debug(Verbose, "existing yolo session %q — running pre-flight", name)
135-
return preflight(sess, cfg, store, tc, out)
136-
}
253+
sess, getErr := store.Get(name)
254+
switch decideModeAction(sess, getErr, "yolo") {
255+
case decisionResume:
256+
out.Debug(Verbose, "existing yolo session %q — running pre-flight", name)
257+
return preflight(sess, cfg, store, tc, out)
258+
case decisionRecreate:
137259
// Mode change: drop tmux + store record so a fresh UUID is minted.
138-
if tc.HasSession(name) {
139-
if err := tc.KillSession(name); err != nil {
140-
out.Warn("could not kill existing session: %v", err)
141-
}
142-
}
143-
if err := store.Delete(name); err != nil {
144-
out.Warn("could not remove session from store: %v", err)
145-
}
260+
tearDownForRecreate(name, store, tc, out, true)
146261
}
147262

148263
out.Debug(Verbose, "creating yolo session: %s", name)
@@ -161,16 +276,7 @@ func runYoloBang(cmd *cobra.Command, args []string) error {
161276
store := session.NewStore(config.SessionsPath())
162277
tc := tmux.NewClient(config.TmuxConfPath())
163278

164-
name := "claude"
165-
if len(args) > 0 {
166-
name = args[0]
167-
}
168-
if err := session.ValidateName(name); err != nil {
169-
return err
170-
}
171-
172-
// Get workdir from existing session or pane path
173-
workdir, err := resolveWorkdir(name, store, tc)
279+
name, workdir, err := resolveModeTarget(args, store, tc)
174280
if err != nil {
175281
return err
176282
}
@@ -179,22 +285,13 @@ func runYoloBang(cmd *cobra.Command, args []string) error {
179285
gitCheckpoint(workdir, out)
180286
}
181287

182-
out.Magenta(">>> YOLO MODE")
183-
{
184-
intent := yoloIntent(store, name, workdir, "yolo")
185-
fireHook("on_yolo", intent)
186-
fireServeEvent("on_yolo", intent)
187-
}
288+
printBanner(out, "yolo")
289+
fireLaunchEvents(store, name, workdir, "yolo")
188290

189-
if tc.HasSession(name) {
190-
if err := tc.KillSession(name); err != nil {
191-
out.Warn("could not kill existing session: %v", err)
192-
}
193-
}
194-
if err := store.Delete(name); err != nil {
195-
// ignore "not found" errors
196-
_ = err
197-
}
291+
// `yolo!` forces fresh state unconditionally — store.Delete errors are
292+
// swallowed (loud=false) because the historic behavior treated this as
293+
// a best-effort reset.
294+
tearDownForRecreate(name, store, tc, out, false)
198295

199296
return createAndAttach(name, workdir, "yolo", store, tc, out)
200297
}
@@ -211,47 +308,26 @@ func runSafe(cmd *cobra.Command, args []string) error {
211308
store := session.NewStore(config.SessionsPath())
212309
tc := tmux.NewClient(config.TmuxConfPath())
213310

214-
name := "claude"
215-
if len(args) > 0 {
216-
name = args[0]
217-
}
218-
if err := session.ValidateName(name); err != nil {
219-
return err
220-
}
221-
222-
// Get workdir from existing session or pane path
223-
workdir, err := resolveWorkdir(name, store, tc)
311+
name, workdir, err := resolveModeTarget(args, store, tc)
224312
if err != nil {
225313
return err
226314
}
227315

228-
out.Success(">>> SAFE MODE")
229-
{
230-
intent := yoloIntent(store, name, workdir, "safe")
231-
fireHook("on_safe", intent)
232-
// Map "on_safe" to a serve session_attached — the hub doesn't
233-
// model safe-mode separately, only the lifecycle transition.
234-
fireServeEvent("session_attached", intent)
235-
}
316+
printBanner(out, "safe")
317+
fireLaunchEvents(store, name, workdir, "safe")
236318

237319
// If session exists and mode matches → preflight. preflight handles both
238320
// live tmux (plain reattach) and dead tmux (recreate with --resume UUID),
239321
// so the session's claude history survives `claude` exiting on its own.
240322
// Force-fresh escape hatches: `ctm kill <name>` / `ctm forget <name>`.
241-
if sess, err := store.Get(name); err == nil {
242-
if shouldResumeExisting(sess, "safe") {
243-
out.Debug(Verbose, "existing safe session %q — running pre-flight", name)
244-
return preflight(sess, cfg, store, tc, out)
245-
}
246-
// Mode change: drop tmux + store record so a fresh UUID is minted.
247-
if tc.HasSession(name) {
248-
if err := tc.KillSession(name); err != nil {
249-
out.Warn("could not kill existing session: %v", err)
250-
}
251-
}
252-
if err := store.Delete(name); err != nil {
253-
_ = err
254-
}
323+
sess, getErr := store.Get(name)
324+
switch decideModeAction(sess, getErr, "safe") {
325+
case decisionResume:
326+
out.Debug(Verbose, "existing safe session %q — running pre-flight", name)
327+
return preflight(sess, cfg, store, tc, out)
328+
case decisionRecreate:
329+
// safe matches yolo's silent-on-delete-failure historical behavior.
330+
tearDownForRecreate(name, store, tc, out, false)
255331
}
256332

257333
return createAndAttach(name, workdir, "safe", store, tc, out)

0 commit comments

Comments
 (0)