feat(v0.3): add SkipCategoryDir for CTASK_PROJECT_ROOT semantics
When SkipCategoryDir is true, Create places the workspace directly under Root and does not append a Category subdirectory. This is the mechanism that prevents doubled paths like ~/projects/projects/<slug> when the user sets CTASK_PROJECT_ROOT without an explicit -c flag. The Category value is still recorded on TaskMeta so list/info/filter still work.
This commit is contained in:
@@ -29,6 +29,12 @@ type CreateOpts struct {
|
||||
// ProjectSeedDir is the absolute path to the project-specific seed directory.
|
||||
// Only consulted when IsProject is true.
|
||||
ProjectSeedDir string
|
||||
|
||||
// SkipCategoryDir, when true, places the workspace directly under Root
|
||||
// without inserting a Category subdirectory. Used for project mode with
|
||||
// CTASK_PROJECT_ROOT set and no explicit -c flag. Category is still
|
||||
// recorded in TaskMeta.
|
||||
SkipCategoryDir bool
|
||||
}
|
||||
|
||||
// CreateResult holds the result of workspace creation.
|
||||
@@ -60,7 +66,10 @@ func Create(opts CreateOpts) (*CreateResult, error) {
|
||||
date := now.Format("2006-01-02")
|
||||
id := now.Format("20060102-150405")
|
||||
|
||||
categoryDir := filepath.Join(opts.Root, opts.Category)
|
||||
categoryDir := opts.Root
|
||||
if !opts.SkipCategoryDir {
|
||||
categoryDir = filepath.Join(opts.Root, opts.Category)
|
||||
}
|
||||
if err := os.MkdirAll(categoryDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("creating category dir: %w", err)
|
||||
}
|
||||
|
||||
@@ -241,6 +241,51 @@ func TestCreateSeedDoesNotReplaceTaskYAML(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSkipCategoryDirPlacesUnderRoot(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
opts := CreateOpts{
|
||||
Root: root,
|
||||
Title: "billing service",
|
||||
Category: "projects", // recorded in metadata
|
||||
Mode: "local",
|
||||
Agent: "claude",
|
||||
IsProject: true,
|
||||
SkipCategoryDir: true,
|
||||
}
|
||||
ws, err := Create(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
parent := filepath.Dir(ws.Path)
|
||||
if parent != root {
|
||||
t.Errorf("workspace parent: got %q, want %q", parent, root)
|
||||
}
|
||||
if ws.Meta.Category != "projects" {
|
||||
t.Errorf("Category metadata: got %q, want \"projects\"", ws.Meta.Category)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateExplicitCategoryAppendsSubdir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
opts := CreateOpts{
|
||||
Root: root,
|
||||
Title: "billing service",
|
||||
Category: "backend",
|
||||
Mode: "local",
|
||||
Agent: "claude",
|
||||
IsProject: true,
|
||||
// SkipCategoryDir: false (default)
|
||||
}
|
||||
ws, err := Create(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
parent := filepath.Dir(ws.Path)
|
||||
if parent != filepath.Join(root, "backend") {
|
||||
t.Errorf("workspace parent: got %q, want %q", parent, filepath.Join(root, "backend"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCollisionSuffix(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user