feat(v0.5): include \$CTASK_ROOT/projects/ in SearchRoots by default
When CTASK_PROJECT_ROOT is unset, SearchRoots now appends \$CTASK_ROOT/projects/ so that projects created under the default category are discoverable from any shell without per-session env var setup. Dedupe in scanAllRoots prevents double-counting workspaces that are reachable both via the depth-2 scan under CTASK_ROOT and via the new explicit search root. Adds a named regression test asserting no duplicates appear in either ResolveQuery or ListWorkspaces results. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,21 +60,48 @@ func ResolveProjectRoot() string {
|
||||
}
|
||||
|
||||
// SearchRoots returns the deduplicated list of workspace roots that all query
|
||||
// and listing operations must consult. Always includes CTASK_ROOT; also
|
||||
// includes CTASK_PROJECT_ROOT when set and different from CTASK_ROOT.
|
||||
// and listing operations must consult. Always includes CTASK_ROOT. When
|
||||
// CTASK_PROJECT_ROOT is set, adds that (unless it duplicates CTASK_ROOT).
|
||||
// When CTASK_PROJECT_ROOT is unset, adds $CTASK_ROOT/projects/ so that
|
||||
// projects created under the default category are discoverable from any
|
||||
// shell without per-session env var setup.
|
||||
func SearchRoots() []string {
|
||||
taskRoot := ResolveRoot()
|
||||
roots := []string{taskRoot}
|
||||
seen := map[string]struct{}{searchRootKey(taskRoot): {}}
|
||||
|
||||
add := func(p string) {
|
||||
if p == "" {
|
||||
return
|
||||
}
|
||||
key := searchRootKey(p)
|
||||
if _, dup := seen[key]; dup {
|
||||
return
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
roots = append(roots, p)
|
||||
}
|
||||
|
||||
projRoot := ResolveProjectRoot()
|
||||
if projRoot == "" {
|
||||
return roots
|
||||
if projRoot != "" {
|
||||
add(projRoot)
|
||||
} else {
|
||||
// Default fallback: projects live under $CTASK_ROOT/projects/ when
|
||||
// no override is set. Explicit search root makes discovery work
|
||||
// from any shell.
|
||||
add(filepath.Join(taskRoot, "projects"))
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
if samePath(taskRoot, projRoot) {
|
||||
return roots
|
||||
// searchRootKey returns the dedup key for a root path: cleaned, and
|
||||
// lower-cased on Windows for case-insensitive comparison.
|
||||
func searchRootKey(p string) string {
|
||||
c := filepath.Clean(p)
|
||||
if runtime.GOOS == "windows" {
|
||||
return strings.ToLower(c)
|
||||
}
|
||||
return append(roots, projRoot)
|
||||
return c
|
||||
}
|
||||
|
||||
// samePath reports whether two absolute paths refer to the same directory
|
||||
|
||||
Reference in New Issue
Block a user