Repository navigation
fix: relative --dir, worktree re-registration, dashboard history, health badges, cleanup - #104
Conversation
…dirs
gitutil.MainRepoRoot joined a relative `--git-common-dir` answer onto
its input `dir` without ever making `dir` absolute first, so calling
it with a relative --dir (".", "sub", "../x") produced a relative main
repo root. Every comparison of that value (checkDir, quest lookup,
worktree dedup) then depended on the caller happening to canonicalize
it. filepath.Abs the input before joining so MainRepoRoot always
returns an absolute root.
Extends TestCheckDir with relative-path cases from both a main
checkout and a linked worktree.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
fellowship_quests.worktree carries a UNIQUE index (schema v2), but upsertQuest's ON CONFLICT clause only deduped on name, so registering a worktree already held by a different quest row hit a constraint violation instead of being treated as re-registration. upsertQuest now clears the worktree from whichever row previously held it and prints a note naming the previous holder before completing the upsert; registering the same quest name against the same worktree again is still just an update in place. Updates the now-stale FindQuest comment (state.go) to describe the current one-row-per-worktree invariant instead of the old multiple-claimants tiebreak. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
handleGateApprove and handleGateReject hand-rolled the gate/phase mutation and only recorded events, skipping tome (history) entries — diverging from `fellowship gate approve|reject` and group.BatchApprove, which use state.Approve/state.Reject + gate.RecordApproval/RecordRejection and get history for free. Both handlers now go through the same functions, so a dashboard-driven gate decision produces gate/phase history rows exactly as a CLI or group approval does. Adds httptest coverage asserting history.LoadGates shows the resulting approved/rejected row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
quest_state carries no worktree column, so Sweep left QuestHealth.Worktree
empty for every quest — it now joins fellowship_quests by name to fill
it in, the same way the dashboard already looks up a quest's worktree.
app.js's getQuestHealth only matched health entries by worktree, which
silently dropped the badge whenever a quest had no registered worktree
yet; it now also matches by quest name. renderGroupHeader's "Implement+"
count compared PHASES.indexOf(phase) against a hardcoded 3 (Review only,
in the default four-phase order) instead of PHASES.indexOf("Implement"),
so it now derives the threshold from the actual phase list.
Adds TestSweepPopulatesWorktree.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
…s/gate - group.LoadDetail (`group show --json`) now wires CalculateProgress into a `progress` field, reusing fellowship.DiscoverQuests for quest status instead of a second query path. Deletes the now-unused LoadAndMarshalProgress, ProgressSummary, FindGroupForQuest, and the questEntryStatuses helper that only LoadAndMarshalProgress called. - db/migrate.go's listWorktreePaths duplicated gitutil.ListWorktrees's git worktree parsing; discoverJSONFiles now calls gitutil.ListWorktrees directly and keeps the same empty-list-falls-back-to-mainRepo behavior. Rewrites the two migrate tests that relied on mocking git output to use a real git repo with a linked worktree instead, since the mockable execCommand var went away with the duplicate. - todo.Init took a task description it silently discarded — neither errands nor errand_deps has a column for it and adding one is a schema change out of scope here, so `todo init` drops --task entirely (removed the flag, its usage line, its doc line in `fellowship` --help, and the always-empty QuestTodoList.Task field) rather than keep accepting and dropping it. - todo.List returned nil for an empty quest; `todo show` marshals it straight to JSON, so it now returns [] instead of null. - failures.go's inferTriggerFromDB checked status == "failed", a status this codebase never sets (fellowship_quests.status is constrained to active/completed/cancelled) — removed the unreachable branch and fixed history.QuestHistory's stale doc comment to match. - gate.RecordApproval recorded duration_s as a hardcoded 0. It now measures the real time spent in the phase being left: from the previous phase's completed_at, or the quest's created_at for its first phase completion. - Deleted dashboard's unused worktreeDirs (staticcheck U1000). Left out: sharing one resolveDirQuest/resolveHoldQuest resolver would require editing cmd/fellowship/gate.go (explicitly off-limits — owned by another agent in this batch) and misc.go beyond checkDir (also restricted to checkDir-only here), so it's skipped; noting it rather than crossing that line. `init` on a completed quest is likewise left alone per the task. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
…tic clock for DetectProblems
Adds TestHookGateGuard_StoreOpenExitCodes (cmd/fellowship): builds the real
binary to a temp dir and runs `hook gate-guard` against it end to end for
the two extremes of storeOpenExit's posture — no store at all (exit 0,
and must not create one just by running) and a store that exists but
can't be opened (exit 2, fail closed) — asserting on
cmd.ProcessState.ExitCode() rather than parsing Run()'s error.
events.DetectProblems gains a variadic Options{Now} so callers (and
tests) can inject a fixed clock; existing call sites are unaffected. This
also fixes a real bug: the zombie message's activityAge lookup read the
Now field off the caller's own health.DefaultOptions() value, which is
never resolved from its zero value the way health.Sweep resolves its own
copy internally — so every zombie's "no activity for" duration was
computed against a zero time.Time. problems_test.go now passes a fixed
Options{Now: ...} instead of live time.Now() offsets, and
TestZombieDetection asserts the resulting message string exactly.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
…fixes Summarizes this branch's fixes for the README changelog: relative --dir resolution, worktree re-registration, dashboard gate history sharing, health worktree/name matching, group show --json progress, todo init/show cleanup, and real gate duration_s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
|
Warning Review limit reachedNext included review available in 38 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThe changes correct relative repository path handling, worktree reassignment, dashboard gate transitions, gate history durations, health reporting, dashboard progress matching, group progress output, todo initialization, and empty todo serialization. Tests cover these behaviors. ChangesCLI correctness updates
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🔵 Low · up to Zombie health messages can show an inactivity-age bucket inconsistent with their classification near a threshold. Resolve the clock once before merge to keep health output coherent. Sequence Diagram(s)sequenceDiagram
participant DashboardAPI
participant State
participant GateRecord
participant History
DashboardAPI->>State: Approve or reject gate
State-->>DashboardAPI: Transition result
DashboardAPI->>GateRecord: Record gate event and duration
GateRecord->>History: Persist gate and phase history
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 59.15% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 71 functions across 24 files. (1 skipped: 1 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Team
Run ID: 4f2907f1-ab51-4795-8c16-3c13163bfb54
📒 Files selected for processing (26)
README.mdcli/cmd/fellowship/hook_subprocess_test.gocli/cmd/fellowship/main.gocli/cmd/fellowship/main_test.gocli/cmd/fellowship/todo.gocli/internal/dashboard/server.gocli/internal/dashboard/server_test.gocli/internal/dashboard/static/app.jscli/internal/db/migrate.gocli/internal/db/migrate_test.gocli/internal/events/problems.gocli/internal/events/problems_test.gocli/internal/failures/failures.gocli/internal/fellowship/fellowship.gocli/internal/fellowship/fellowship_test.gocli/internal/gate/record.gocli/internal/gate/record_test.gocli/internal/gitutil/gitutil.gocli/internal/group/group.gocli/internal/group/group_test.gocli/internal/health/health.gocli/internal/health/health_test.gocli/internal/history/history.gocli/internal/state/state.gocli/internal/todo/todo.gocli/internal/todo/todo_test.go
💤 Files with no reviewable changes (1)
- cli/cmd/fellowship/main.go
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.
group.LoadDetail called fellowship.DiscoverQuests to build the []QuestStatus CalculateProgress needs, which runs state.Load and todo.Progress for every quest across the whole fellowship rather than just this group's members (and duplicates the state.Load the existing loop already does per member). Restores a lean questEntryStatuses helper (one cheap query) and builds QuestStatus from data already gathered in the loop. Two other findings from this pass were rejected as out of scope for this branch rather than applied: a stale --task example in plugin/skills/fellowship/SKILL.md (plugin/ is owned by another agent in this batch) and a missing site changelog entry (this task's docs scope is README Unreleased bullets only). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
Round 1's questEntryStatuses-based LoadDetail dropped a case fellowship.DiscoverQuests used to handle: a quest whose fellowship entry is completed/cancelled but has no quest_state row (e.g. a history-only or pre-phase-machinery entry) was silently excluded from CalculateProgress instead of counting toward Progress.Completed the way it always had. LoadDetail's per-quest loop now falls back to a synthetic terminal-phase QuestStatus for that case, same as DiscoverQuests did. Adds TestLoadDetail_Progress_CompletedNoQuestState (fails without the fix) and fixes a stale comment left over from round 1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
Rounds 1-2 left the "completed/cancelled quest with no quest_state row" synthetic-entry logic duplicated between fellowship.DiscoverQuests and group.LoadDetail — the exact kind of drift that caused round 1 to drop it in LoadDetail in the first place. Extracts it into a shared fellowship.TerminalQuestStatus(name, worktree, entryStatus) helper and switches both call sites to it. Pure de-duplication: behavior is unchanged, verified by the existing DiscoverQuests and LoadDetail progress tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
What
gitutil.MainRepoRootnowfilepath.Abss its input before joining a relative--git-common-diranswer, so a relative--dir(.,sub,../x) resolves the same repo root as an absolute one —checkDircompares them correctly from both a main checkout and a linked worktree.fellowship.upsertQuesthandles a worktree already held by a different quest row (the unique index added in schema v2 would otherwise reject the insert): it clears the previous holder's worktree and reports the reassignment, rather than failing. Updated the now-staleFindQuestcomment instate.goto match (comment-only change to that file).handleGateApprove/handleGateRejectnow sharestate.Approve/state.Reject+gate.RecordApproval/RecordRejectionwith the CLI andgroup.BatchApprove, instead of a hand-rolled mutation that recorded events but skipped tome (history) rows.health.Sweepnow populatesQuestHealth.Worktreeby joiningfellowship_quests;app.jsmatches a quest's health badge by name as a fallback to worktree, and its "Implement+" count derives the threshold fromPHASES.indexOf("Implement")instead of a hardcoded index.group show --jsongained aprogressfield wired togroup.CalculateProgress(deleted the now-unusedLoadAndMarshalProgress/ProgressSummary/FindGroupForQuest);db/migrate.go'slistWorktreePathswas replaced withgitutil.ListWorktrees(same empty-list fallback);todo.Initno longer silently discards a--taskit never persisted (dropped the flag — no schema change was in scope to actually store it);todo.Listreturns[]instead ofnil; removed an unreachablestatus == "failed"branch infailures.go(that status is never set);gate.RecordApprovalnow records a realduration_sinstead of a hardcoded0; deleted the dashboard's unusedworktreeDirs(staticcheck U1000).cmd/fellowship) that builds the real binary and runshook gate-guardend to end for a missing store (exit 0) and a corrupt store (exit 2), asserting oncmd.ProcessState.ExitCode(). Gaveevents.DetectProblemsan injectableOptions{Now}(variadic, existing callers unaffected) and switchedproblems_test.goto it — this also fixed a real bug where the zombie "no activity for" message was computed against a zerotime.Timebecause the caller's ownNowwas never resolved the wayhealth.Sweep's internal copy is.Two minimal, out-of-scope touches, called out per this batch's scope discipline:
cmd/fellowship/main.go's--helptext still documented the removedtodo init --taskflag, so I deleted that one doc line (plugin/skills/fellowship/SKILL.md documents--tasktoo, but plugin/ is owned by another agent in this batch and is left alone — flagging it here for a follow-up);cmd/fellowship/events.go's singleDetectProblemscall site needed no change since the new option is variadic.Left out: sharing one
resolveDirQuest/resolveHoldQuestresolver would require editingcmd/fellowship/gate.go(owned by another agent in this batch) andmisc.gobeyondcheckDir(also restricted tocheckDir-only for this PR), so it's skipped rather than crossing that boundary.initon a completed quest is likewise left to another agent's PR.Why
Six correctness/cleanup issues flagged in review of the recent state-machine and dashboard work (PRs #94-#103): a relative
--dirsilently resolving to the wrong repo root, a worktree-reassignment path that the new unique index would reject, a dashboard gate decision that recorded events but not history, health/dashboard data not carrying a quest's worktree, several small dead-code/duplication spots, and thin test coverage around thehookbinary's store-open exit codes and the zombie-detection clock.Verification
cd cli && gofmt -l .— cleancd cli && go vet ./...— cleancd cli && go test -race ./...— all packages passcd site && npm ci && npm run build— site builds (app.jslives incli/internal/dashboard/static/, notsite/, but this was requested as part of this PR's verification)fellowship todo list --dir .from a main checkout afterstate add-quest --dir . --worktree .(relative--dirresolves)q1thenq1bon the same shared worktree — printsNote: worktree "…" was registered to quest "q1"; reassigning it to "q1b", and the worktree then resolves toq1bPOST /api/gate/approveagainst a quest with a pending gate —history.LoadGatesshows theapprovedrow andhistory.LoadPhasesshows a nonzeroduration_s🤖 Generated with Claude Code
https://claude.ai/code/session_0193Ae6GSguC4hYBDA7NhJoN
Generated by Claude Code
Summary by CodeRabbit
--dirpaths now resolve correctly.items: []instead ofnull.--taskoption fromtodo initand its help text.