Extracts the cross-type "most recently updated active workspace"
selector that previously lived inline in cmd/last and cmd/delete.
The helper takes only a root path and returns (nil, nil) when
nothing matches, which keeps the call sites trivial.
Eight focused tests cover:
- tasks-only fixture
- projects-only fixture
- project most-recent vs older task
- task most-recent vs older project
- legacy v0.2 workspace (no Type field) winning, treated as task
- newest workspace archived, older active project wins
- empty root returns (nil, nil)
- all archived returns (nil, nil)
A new createTestWorkspaceFull helper takes an explicit UpdatedAt
timestamp so the "which is newest" assertions don't depend on
wall-clock ordering.
cmd/last and cmd/delete are migrated onto the helper in the next
commit.
ListOpts now exposes a Type string field (TypeAny / TypeTask /
TypeProject). TypeAny is the new way to express "both tasks and
projects" in a single ListWorkspaces call -- which the next two
commits will use to consolidate cmd/last and cmd/delete onto a
single helper, and to make 'ctask list' default to showing both
types.
Invalid Type values now return an explicit error from
ListWorkspaces (defensive against typos in callers).
cmd/list, cmd/last, and cmd/delete are migrated to the new field.
External behavior is unchanged in this commit; the cleanup of
ctask list semantics happens in a follow-up commit so the diff
stays reviewable.
ListOpts gains a Projects bool that filters by EffectiveType.
Default behavior (Projects: false) now returns tasks only --
this is a deliberate semantic change that supports the new
'ctask list' (tasks) vs 'ctask list --projects' (projects)
spec.
The change silently regresses two cmd-level callers that scan
for "the most recently updated workspace": cmd/last.go (used by
'ctask last') and cmd/delete.go (used to print the "this was
your most recent workspace" note). Both are fixed by unioning a
tasks-scan with a projects-scan, so 'last' and 'delete' continue
to consider both types.
Test helper createTestWorkspaceTyped allows setting an explicit
type (or "" to simulate a v0.2 workspace with no type field).
EnsureGitignore writes a minimal .gitignore (.ctask/ +
logs/sessions.log) iff one does not already exist. This is the
file-system half of the v0.3 seed-wins rule for .gitignore: if
either the general or project seed copied a .gitignore into the
workspace, EnsureGitignore must be a no-op.
GitAvailable + RunGitInit wrap exec.LookPath("git") and `git init`
respectively, so the caller in cmd/new.go can decide whether to
print the informational note when git is missing.
Tests cover:
- missing -> created with the minimal body
- present -> preserved verbatim
- integration: general seed .gitignore preserved end-to-end
- integration: project seed .gitignore preserved end-to-end
- integration: no seed -> minimal body created
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.
CreateOpts gains IsProject, SeedDir, and ProjectSeedDir. The new
seeding flow is:
1. write built-in defaults (task or project CLAUDE.md + notes.md)
2. apply general seed if SeedDir != "" (overlay)
3. apply project seed if IsProject && ProjectSeedDir != "" (overlay)
4. write task.yaml (last, so seeds can never inject metadata)
The obsolete TestCreateDoesNotOverwriteSeedFiles test is removed:
its v0.2 invariant ("don't overwrite existing files") no longer
holds because v0.3 always lays defaults onto a fresh dir and seeds
are expected to overwrite.
TaskMeta now records type: task or type: project. EffectiveType
returns 'task' for missing/empty/unknown values and for nil meta,
so v0.2 workspaces continue to read as tasks without any
migration. The field is placed between Category and Mode in the
YAML output. Tests cover the round-trip and the legacy
no-type-field case.
Create function produces full workspace layout (task.yaml, CLAUDE.md, notes.md, context/, output/, logs/). Seed files only written if missing. Collision suffixing tested.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Slugify, AutoTitle, DirName, ResolveDir with -2, -3 suffixing. Full test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TaskMeta struct matching task.yaml schema exactly. Tests for roundtrip, field presence, and archive state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>