feat(v0.5): show launch_dir fields in ctask info output
For project workspaces with launch_dir set, info prints three extra lines after Path: Launch dir, Launch path, and Dir exists (yes/no from a direct os.Stat of the intended path). Tasks and pre-v0.5 projects still omit these lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warrenronsiek/ctask/internal/config"
|
||||
@@ -38,6 +39,22 @@ func runInfo(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Updated: %s\n", m.UpdatedAt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("Path: %s\n", ws.Path)
|
||||
|
||||
if m.LaunchDir != "" {
|
||||
// Per spec amendment: stat the expected path directly instead of
|
||||
// inferring existence from ResolveLaunch's fallback behavior. info
|
||||
// is a display command, not a launch command — a permission error
|
||||
// here is the same user-facing outcome as "not a directory", so
|
||||
// we just report whether the stat succeeded with a directory.
|
||||
launchAbs := filepath.Join(ws.Path, m.LaunchDir)
|
||||
dirExists := "no"
|
||||
if info, err := os.Stat(launchAbs); err == nil && info.IsDir() {
|
||||
dirExists = "yes"
|
||||
}
|
||||
fmt.Printf("Launch dir: %s/\n", m.LaunchDir)
|
||||
fmt.Printf("Launch path: %s\n", launchAbs)
|
||||
fmt.Printf("Dir exists: %s\n", dirExists)
|
||||
}
|
||||
|
||||
if m.ArchivedAt != nil {
|
||||
fmt.Printf("Archived: %s\n", m.ArchivedAt.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/warrenronsiek/ctask/internal/workspace"
|
||||
)
|
||||
|
||||
// runInfoCapture runs runInfo with CTASK_ROOT set and captures stdout.
|
||||
func runInfoCapture(t *testing.T, root, query string) (string, error) {
|
||||
t.Helper()
|
||||
prev := os.Getenv("CTASK_ROOT")
|
||||
os.Setenv("CTASK_ROOT", root)
|
||||
defer func() {
|
||||
if prev == "" {
|
||||
os.Unsetenv("CTASK_ROOT")
|
||||
} else {
|
||||
os.Setenv("CTASK_ROOT", prev)
|
||||
}
|
||||
}()
|
||||
|
||||
stdoutR, stdoutW, _ := os.Pipe()
|
||||
prevStdout := os.Stdout
|
||||
os.Stdout = stdoutW
|
||||
defer func() { os.Stdout = prevStdout }()
|
||||
|
||||
err := runInfo(infoCmd, []string{query})
|
||||
stdoutW.Close()
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(stdoutR)
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func TestInfoShowsLaunchFieldsWhenLaunchDirSet(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
wsDir := filepath.Join(root, "projects", "2026-04-22_demo")
|
||||
os.MkdirAll(wsDir, 0755)
|
||||
os.MkdirAll(filepath.Join(wsDir, "demo"), 0755)
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
meta := &workspace.TaskMeta{
|
||||
ID: "t", Slug: "demo", Title: "demo",
|
||||
CreatedAt: now, UpdatedAt: now,
|
||||
Status: "active", Category: "projects", Type: "project",
|
||||
Mode: "local", Agent: "claude", LaunchDir: "demo",
|
||||
}
|
||||
workspace.WriteMeta(filepath.Join(wsDir, "task.yaml"), meta)
|
||||
|
||||
out, err := runInfoCapture(t, root, "demo")
|
||||
if err != nil {
|
||||
t.Fatalf("runInfo: %v", err)
|
||||
}
|
||||
for _, want := range []string{"Launch dir:", "Launch path:", "Dir exists:", "demo"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("info output missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out, "Dir exists: yes") {
|
||||
t.Errorf("expected 'Dir exists: yes', got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoOmitsLaunchFieldsForTask(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
wsDir := filepath.Join(root, "general", "2026-04-22_regular")
|
||||
os.MkdirAll(wsDir, 0755)
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
meta := &workspace.TaskMeta{
|
||||
ID: "t", Slug: "regular", Title: "regular",
|
||||
CreatedAt: now, UpdatedAt: now,
|
||||
Status: "active", Category: "general", Type: "task",
|
||||
Mode: "local", Agent: "claude",
|
||||
}
|
||||
workspace.WriteMeta(filepath.Join(wsDir, "task.yaml"), meta)
|
||||
|
||||
out, err := runInfoCapture(t, root, "regular")
|
||||
if err != nil {
|
||||
t.Fatalf("runInfo: %v", err)
|
||||
}
|
||||
for _, shouldAbsent := range []string{"Launch dir:", "Launch path:", "Dir exists:"} {
|
||||
if strings.Contains(out, shouldAbsent) {
|
||||
t.Errorf("task info should not show %q:\n%s", shouldAbsent, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoShowsDirExistsNoWhenLaunchDirMissing(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
wsDir := filepath.Join(root, "projects", "2026-04-22_renamed")
|
||||
os.MkdirAll(wsDir, 0755)
|
||||
// Intentionally do NOT create the project subdir.
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
meta := &workspace.TaskMeta{
|
||||
ID: "t", Slug: "renamed", Title: "renamed",
|
||||
CreatedAt: now, UpdatedAt: now,
|
||||
Status: "active", Category: "projects", Type: "project",
|
||||
Mode: "local", Agent: "claude", LaunchDir: "renamed",
|
||||
}
|
||||
workspace.WriteMeta(filepath.Join(wsDir, "task.yaml"), meta)
|
||||
|
||||
out, err := runInfoCapture(t, root, "renamed")
|
||||
if err != nil {
|
||||
t.Fatalf("runInfo: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "Dir exists: no") {
|
||||
t.Errorf("expected 'Dir exists: no' for missing launch dir:\n%s", out)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user