From 0c1f03ba3a8ef0f3986bba18cd4aa3053ff70b3e Mon Sep 17 00:00:00 2001 From: typebasedio Date: Wed, 22 Apr 2026 17:54:58 -0400 Subject: [PATCH] fix(v0.4.1): route all workspace commands through SearchRoots Every resolver, lister, and most-recent caller now passes config.SearchRoots() so CTASK_PROJECT_ROOT is searched alongside CTASK_ROOT. Commands use ws.Root when rendering relative paths or session env vars so displays and CTASK_ROOT exports are correct for workspaces living under CTASK_PROJECT_ROOT. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/archive.go | 6 +++--- cmd/delete.go | 8 ++++---- cmd/doctor.go | 2 +- cmd/helpers.go | 10 ++++++---- cmd/info.go | 4 ++-- cmd/last.go | 4 ++-- cmd/list.go | 4 ++-- cmd/open.go | 6 +++--- cmd/resume.go | 6 +++--- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cmd/archive.go b/cmd/archive.go index cfcd5a4..a7a382f 100644 --- a/cmd/archive.go +++ b/cmd/archive.go @@ -23,8 +23,8 @@ func init() { } func runArchive(cmd *cobra.Command, args []string) error { - root := config.ResolveRoot() - ws := resolveOne(root, args[0], false) + roots := config.SearchRoots() + ws := resolveOne(roots, args[0], false) now := time.Now().UTC().Truncate(time.Second) ws.Meta.Status = "archived" @@ -36,7 +36,7 @@ func runArchive(cmd *cobra.Command, args []string) error { return fmt.Errorf("updating metadata: %w", err) } - relPath := workspace.RelativePath(root, ws.Path) + relPath := workspace.RelativePath(ws.Root, ws.Path) fmt.Printf("[ctask] archived: %s\n", relPath) return nil diff --git a/cmd/delete.go b/cmd/delete.go index 3e0d727..3be75ad 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -32,8 +32,8 @@ func init() { } func runDelete(cmd *cobra.Command, args []string) error { - root := config.ResolveRoot() - ws := resolveOne(root, args[0], deleteAll) + roots := config.SearchRoots() + ws := resolveOne(roots, args[0], deleteAll) // Active workspace protection — two checks, BOTH run before any mutation. // @@ -61,7 +61,7 @@ func runDelete(cmd *cobra.Command, args []string) error { // --- Past this point, we are confident no session is active. --- - relPath := workspace.RelativePath(root, ws.Path) + relPath := workspace.RelativePath(ws.Root, ws.Path) // Gather contents summary (best-effort, read-only) fileCount, totalSize := summarizeContents(ws.Path) @@ -72,7 +72,7 @@ func runDelete(cmd *cobra.Command, args []string) error { // Check if this is the most recently updated workspace across both // tasks and projects. - if best, _ := workspace.MostRecentActive(root); best != nil { + if best, _ := workspace.MostRecentActive(roots); best != nil { absBest, _ := filepath.Abs(best.Path) if strings.EqualFold(absTarget, absBest) { fmt.Println() diff --git a/cmd/doctor.go b/cmd/doctor.go index fde7e2b..d40730c 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -152,7 +152,7 @@ func runDoctor(cmd *cobra.Command, args []string) error { } // Check 5: Workspace root has workspaces - results, err := workspace.ListWorkspaces(root, workspace.ListOpts{ + results, err := workspace.ListWorkspaces(config.SearchRoots(), workspace.ListOpts{ IncludeArchived: true, Limit: 0, // no limit }) diff --git a/cmd/helpers.go b/cmd/helpers.go index c16e72b..48ee29a 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -7,9 +7,11 @@ import ( "github.com/warrenronsiek/ctask/internal/workspace" ) -// resolveOne resolves a query to exactly one workspace. Prints errors and exits on 0 or >1 matches. -func resolveOne(root, query string, includeArchived bool) *workspace.QueryResult { - results, err := workspace.ResolveQuery(root, query, includeArchived) +// resolveOne resolves a query to exactly one workspace across the given roots. +// Prints errors and exits on 0 or >1 matches. Callers typically pass +// config.SearchRoots() so both CTASK_ROOT and CTASK_PROJECT_ROOT are searched. +func resolveOne(roots []string, query string, includeArchived bool) *workspace.QueryResult { + results, err := workspace.ResolveQuery(roots, query, includeArchived) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) @@ -23,7 +25,7 @@ func resolveOne(root, query string, includeArchived bool) *workspace.QueryResult if len(results) > 1 { fmt.Fprintf(os.Stderr, "Multiple workspaces match %q:\n", query) for _, r := range results { - fmt.Fprintf(os.Stderr, " %s\n", workspace.RelativePath(root, r.Path)) + fmt.Fprintf(os.Stderr, " %s\n", workspace.RelativePath(r.Root, r.Path)) } fmt.Fprintln(os.Stderr, "Specify a more precise query.") os.Exit(1) diff --git a/cmd/info.go b/cmd/info.go index 8fb7838..56849b9 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -24,8 +24,8 @@ func init() { } func runInfo(cmd *cobra.Command, args []string) error { - root := config.ResolveRoot() - ws := resolveOne(root, args[0], infoAll) + roots := config.SearchRoots() + ws := resolveOne(roots, args[0], infoAll) m := ws.Meta fmt.Printf("Task: %s\n", m.Slug) diff --git a/cmd/last.go b/cmd/last.go index ce4c951..8e80edd 100644 --- a/cmd/last.go +++ b/cmd/last.go @@ -31,9 +31,9 @@ func init() { } func runLast(cmd *cobra.Command, args []string) error { - root := config.ResolveRoot() + roots := config.SearchRoots() - best, err := workspace.MostRecentActive(root) + best, err := workspace.MostRecentActive(roots) if err != nil { return err } diff --git a/cmd/list.go b/cmd/list.go index 3fbd108..9cff248 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -46,7 +46,7 @@ func runList(cmd *cobra.Command, args []string) error { return fmt.Errorf("--task and --projects are mutually exclusive; pass at most one") } - root := config.ResolveRoot() + roots := config.SearchRoots() wsType := workspace.TypeAny switch { @@ -56,7 +56,7 @@ func runList(cmd *cobra.Command, args []string) error { wsType = workspace.TypeProject } - results, err := workspace.ListWorkspaces(root, workspace.ListOpts{ + results, err := workspace.ListWorkspaces(roots, workspace.ListOpts{ IncludeArchived: listAll, Category: listCategory, Limit: listLimit, diff --git a/cmd/open.go b/cmd/open.go index 7b0ad9d..efc35ed 100644 --- a/cmd/open.go +++ b/cmd/open.go @@ -31,8 +31,8 @@ func init() { } func runOpen(cmd *cobra.Command, args []string) error { - root := config.ResolveRoot() - ws := resolveOne(root, args[0], openAll) + roots := config.SearchRoots() + ws := resolveOne(roots, args[0], openAll) // Update updated_at now := time.Now().UTC().Truncate(time.Second) @@ -42,7 +42,7 @@ func runOpen(cmd *cobra.Command, args []string) error { return fmt.Errorf("updating metadata: %w", err) } - envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta)) + envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta)) return session.Run(session.LaunchOpts{ WsDir: ws.Path, diff --git a/cmd/resume.go b/cmd/resume.go index 10143a0..11dcab0 100644 --- a/cmd/resume.go +++ b/cmd/resume.go @@ -46,8 +46,8 @@ func doResume(query string, container, useShell, force bool, agentOverride strin return nil } - root := config.ResolveRoot() - ws := resolveOne(root, query, false) + roots := config.SearchRoots() + ws := resolveOne(roots, query, false) // Update updated_at now := time.Now().UTC().Truncate(time.Second) @@ -62,7 +62,7 @@ func doResume(query string, container, useShell, force bool, agentOverride strin agent = ws.Meta.Agent } - envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta)) + envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta)) return session.Run(session.LaunchOpts{ WsDir: ws.Path,