refactor(cmd): reorganize cmd/camp into package-owned command domains#170
refactor(cmd): reorganize cmd/camp into package-owned command domains#170obey-agent wants to merge 7 commits intomainfrom
Conversation
…e roots and shared helpers Add package-owned Cobra roots for cache, navigation, registry, project, dungeon, intent, leverage, refs, skills, and worktrees, and wire them explicitly from cmd/camp/root.go and cmd/camp/project/root.go. Extract shared navigation/git/command helpers into cmdutil and internal packages, add root-registration regression coverage, and record scaffold verification evidence. Establish the package-root scaffolding and explicit registration pattern before moving larger command families so later package splits can proceed incrementally without changing the CLI surface.
…ommand families into packages Move cache, registry, skills, and refs command implementations into package-owned files. Relocate refs integration coverage into cmd/camp/refs and keep root registration tests aligned. Preserve CLI help and representative runtime output while fixing new diff-scoped lint findings in skills command writers. Prove the repeated subtree-move workflow on safe command families before larger domain splits.
…avigation commands into packages Move dungeon, intent, go, and shortcuts command trees plus their tests into package-owned directories. Keep root registration explicit and relocate shared pin/config helpers into internal/pins and cmd/camp/cmdutil to avoid cross-package leaks. Preserve recorded help output and representative workflow behavior across dungeon, intent, and navigation commands. Reduce root-package ownership before the metrics and dev-domain extractions while keeping the CLI surface stable.
…nds into package-owned tree Move the leverage command family and its tests into cmd/camp/leverage/ and keep the helper seams owned by that package. Preserve leverage CLI behavior while recording that no quest command surface exists on this branch to migrate.
…mmand surfaces Move the canonical project worktree commands into cmd/camp/project/worktree/ and move the deprecated root compatibility commands into cmd/camp/worktrees/. Keep completion in cmdutil and shared mechanics in internal/worktree so both surfaces stay consistent without import cycles.
…ds to cmd/camp/project/ Move the core project command files into cmd/camp/project and keep project worktree registration owned by the project package. Record the absence of a project remote subtree on this branch and capture verification evidence for the project-domain migration. This completes the highest-risk package split in the cmd/camp refactor while preserving the existing CLI surface and keeping shared helpers in deliberate homes.
obey-agent
left a comment
There was a problem hiding this comment.
Staff Review — cmd/camp Package Reorganization
Well-executed mechanical refactor. The structural goals are achieved: command families are package-owned, the import graph has no cycles, root registration is explicit and tested, and shared helpers are extracted into a coherent cmdutil layer.
Architecture Assessment
Import graph is clean and acyclic. root.go imports all 11 sub-packages, each sub-package imports only cmdutil and internal/*, cmdutil imports only internal/*, and project/root.go imports project/worktree as the single intentional parent-to-child link.
cmdutil is not a dumping ground. The 5 files are all genuinely cross-cutting CLI-layer helpers. Good restraint.
root_registration_test.go pointer-identity tests are the right enforcement mechanism — catches any case where a command is constructed as a new instance rather than referencing the package's exported Cmd.
Important Issues
-
ctx == nilnil-guards in 3 files — these mask improperly constructed test commands rather than fixing the real issue.cobra.Command.Context()never returnsnilin production. See inline comments. -
cmdutil/config.go:LoadCampaignConfigSafeis a no-op wrapper — it callsconfig.LoadCampaignConfigFromCwdand returns all three values unchanged. The "Safe" suffix implies graceful error handling but it doesn't add any. Callers can invoke the underlying function directly. See inline.
Medium Issues
-
navigation/shortcuts.go:ShortcutsCmd.GroupIDset ininit()rather than var declaration — inconsistent with every other package in this PR. See inline. -
leverage/main_command.go: redundantCmd.Argsassignment —root.goalready setsArgs: cobra.MaximumNArgs(1), theninit()reassigns the same value. -
Two-phase
RunEinitialization (root.go sets placeholder, then commands.go/main_command.go overwrite ininit()) — technically correct but misleading to readers ofroot.goalone. Consider declaringRunE: nilin root and setting it only in the command file. -
worktrees/deprecation shim duplicates logic withproject/worktree/— accepted trade-off per PR description, but track the removal as a follow-up to prevent the duplication from becoming permanent.
What's Done Well
internal/navextraction eliminatesbuildCategoryMappingsduplication betweengo.goandcomplete.gointernal/git/query.gopromotesgitOutputtogit.Output, deleting both private copiespins.MigrateLegacyStoreextraction puts migration logic next to the type it operates on- Deprecation shim for
camp worktreesis mechanically sound - PR honestly documents that
<20 filestarget was not met (49 remain) with sound deferral rationale - 1887 unit + 123 integration tests all pass
No blocking issues. See inline comments for the items worth addressing.
cmd/camp/leverage/main_command.go
Outdated
| } | ||
|
|
||
| ctx := cmd.Context() | ||
| if ctx == nil { |
There was a problem hiding this comment.
Important: remove ctx == nil guard — fix test setup instead
This pattern appears in 3 files (leverage/main_command.go, navigation/go.go, refs/commands.go):
ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}cobra.Command.Context() never returns nil when run through cobra — it returns context.Background() by default. The only path to nil is tests that call RunE directly without calling SetContext first.
The fix is in the tests, not the production code:
cmd.SetContext(context.Background())root_registration_test.go already does this correctly for its handler tests. These guards will mask the fact that tests run without a proper context, which matters once any context-aware timeout or cancellation logic is added.
cmd/camp/cmdutil/config.go
Outdated
|
|
||
| // LoadCampaignConfigSafe loads campaign config without adding command-specific behavior. | ||
| func LoadCampaignConfigSafe(ctx context.Context) (*config.CampaignConfig, string, error) { | ||
| cfg, root, err := config.LoadCampaignConfigFromCwd(ctx) |
There was a problem hiding this comment.
Important: no-op wrapper
This function does nothing beyond calling config.LoadCampaignConfigFromCwd and returning all three values unchanged:
func LoadCampaignConfigSafe(ctx context.Context) (*config.CampaignConfig, string, error) {
cfg, root, err := config.LoadCampaignConfigFromCwd(ctx)
if err != nil {
return nil, "", err
}
return cfg, root, nil
}The "Safe" suffix implies graceful error handling, but it just propagates the error normally. Both callsites (navigation/shortcuts.go and concepts.go) handle the fallback in their own code after the call. Callers can invoke config.LoadCampaignConfigFromCwd directly — removing this wrapper eliminates a layer of indirection and a misleading name.
cmd/camp/navigation/shortcuts.go
Outdated
| rootCmd.AddCommand(shortcutsCmd) | ||
| shortcutsCmd.GroupID = "navigation" | ||
| ShortcutsCmd.GroupID = "navigation" | ||
|
|
There was a problem hiding this comment.
Medium: inconsistent GroupID assignment
Every other exported Cmd in this PR sets GroupID in the var Cmd = &cobra.Command{...} declaration (see cache/root.go, dungeon/root.go, intent/root.go, skills/root.go, worktrees/root.go). ShortcutsCmd sets it here in init() instead.
Move it to the var declaration for consistency:
var ShortcutsCmd = &cobra.Command{
Use: "shortcuts",
GroupID: "navigation",
...
}Follows the same package-ownership pattern established by PR #170 for dungeon, intent, leverage, etc. Quest command files move from flat cmd/camp/quest_*.go into cmd/camp/quest/ with package quest. The root command is exported as quest.Cmd and registered via a thin bridge file (quest_register.go) in package main.
obey-agent
left a comment
There was a problem hiding this comment.
Verification Note
Re-checked the three items from the initial review against the current branch state:
ctx == nilnil-guards — not present in the three files flagged (leverage/main_command.go,navigation/go.go,refs/commands.go). All usectx := cmd.Context()directly.LoadCampaignConfigSafe— does not exist in the codebase. Callers useconfig.LoadCampaignConfigFromCwddirectly.ShortcutsCmd.GroupID— already set inline in the var declaration, not ininit().
All three items are resolved. Build, vet, and tests pass. No outstanding issues remain on this PR.
obey-agent
left a comment
There was a problem hiding this comment.
Follow-up Review — commit af3156b4
All review items addressed.
1. ctx == nil nil-guards — Removed
Removed from all 3 files. leverage/main_command_test.go updated with root.SetContext(context.Background()) — correct fix in the test rather than guarding in production.
2. LoadCampaignConfigSafe — Deleted
cmdutil/config.go removed entirely. Both callers (concepts.go, shortcuts.go) now call config.LoadCampaignConfigFromCwd directly with updated imports.
3. ShortcutsCmd.GroupID — Moved to var declaration
Now inline at GroupID: "navigation" in the struct literal, matching every other package in this PR.
4. Bonus: redundant Cmd.Args — Removed
Cmd.Args = cobra.MaximumNArgs(1) removed from leverage/main_command.go init() — already declared in root.go.
No outstanding issues. This PR is ready to merge.
Summary
cache,registry,skills,refs,dungeon,intent,navigation,leverage,project, andproject/worktreecommand families into package-owned directories undercmd/camp/camp worktrees ...compatibility surface while makingcamp project worktree ...the canonical ownership boundaryquestandproject_remote, and record the final root cleanup assessment instead of forcing a late risky extractionValidation
fest validatepassed forcmd-camp-package-reorganization-CC0006just build stablejust build devjust test unit(1887/1887tests passed)just test integration(123/123tests passed)camp --helpcamp project --helpcamp project worktree --helpcamp worktrees --helpcamp intent --helpcamp refs-sync --helpgo vet ./cmd/camp/...go list ./cmd/camp/...Follow-Up
<20 filestarget was not met safely; the final root remains49.gofiles (35non-test,14test)project_remoteor a future dev-onlyquestfamily lands later, it should follow the same package-ownership pattern rather than reintroducing flat-package ownership