docs(v0.5.1): record v0.5.1 completion; check in v0.5.2 spec
Update notes.md with the v0.5.1 Linux portability baseline (commits7a7b249,1033072): WSL-native validation passed, install.sh works, ctask doctor recognizes the Linux statusline helper, the cross-built Linux binary is statically linked, and WorkspacePath was removed from new task.yaml metadata. Add load-bearing notes for the new invariants (CGO_ENABLED=0, install script does not modify shell config) and a "Next: v0.5.2" pointer. Also check in v0.5.2-spec.md so the workspace-retrieval round has the same on-disk durability as the prior specs.
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
# ctask — Session Handoff Notes
|
||||
|
||||
Last touched: 2026-05-07 (after v0.5.1 Linux baseline merge). Pause before starting v0.5.2.
|
||||
|
||||
## Where we are
|
||||
|
||||
**v0.5.1 is shipped on `main` and installed locally.** v0.5 added nested project structure (project subdir scaffolding, `launch_dir`-driven cd into the subdir, default discovery including `$CTASK_ROOT/projects/`). v0.5.1 is a tiny follow-up that fixes a UTC-date confusion surfaced during v0.5 smoke testing.
|
||||
|
||||
- Version string: `v0.5.1` (see `cmd/root.go`)
|
||||
- Branch: `main`
|
||||
- Remote: none (local-only, intentional — see `CLAUDE.md`)
|
||||
- Tests: all pass across 7 packages (`go test ./... -count=1`)
|
||||
- `go vet ./...` clean, `go build ./...` clean
|
||||
- Installed at `%LOCALAPPDATA%\ctask\bin\ctask.exe` via `just install` — reflects both v0.5 and v0.5.1
|
||||
- `ctask doctor` now reports 5 pass/fail checks + 2 seed-directory checks + 1 `CTASK_PROJECT_ROOT` check (all three-state)
|
||||
|
||||
### What v0.4 delivered (still true, unchanged)
|
||||
|
||||
Workspace concurrency protection — session lease with heartbeat (Layer 1), metadata write lock (Layer 2), stale-workspace detection (Layer 3), session handoff summary (Layer 4). `--force` flag on `resume`/`last`/`open`. `WriteMetaLocked`, `LaunchOpts.Force`, `Preflight`. All unchanged in v0.5.
|
||||
|
||||
### What v0.4.1 delivered (still true, unchanged)
|
||||
|
||||
Correctness and polish: `config.SearchRoots()` multi-root discovery; two-depth `scanWorkspaces` (flat + category, first-match-wins); nested-git documentation; doctor seed-dir three-state checks; archive active-session warning with non-TTY refuse; provisional-cleanup exit-code gate. Still load-bearing — see "From v0.4.1" section below.
|
||||
|
||||
### What v0.5 delivered
|
||||
|
||||
Theme: **separate workspace management from project code**. `--project` now scaffolds a project subdirectory and session commands `cd` into it. Twelve commits on `main` (`175fbb0..8130a68`).
|
||||
|
||||
1. **`launch_dir` field in `TaskMeta`** (`175fbb0`)
|
||||
- New `LaunchDir string` with `yaml:"launch_dir,omitempty"` — empty for tasks and pre-v0.5 projects, defaults to the project slug for new projects.
|
||||
- Omitempty keeps on-disk task.yaml clean for backward compatibility.
|
||||
2. **`workspace.ResolveLaunch` helper** (`dcb1610`)
|
||||
- Converts a relative `launch_dir` into an absolute child-cwd with three outcomes:
|
||||
- Valid dir → return absolute path, no warning, no error.
|
||||
- Missing path (`os.IsNotExist`) or path-is-a-file → return `(wsDir, warning, nil)` so the caller warns and falls back.
|
||||
- Absolute path, `..`-escape, or any non-`IsNotExist` stat error (permission, ENOTDIR, invalid name) → return `("", "", error)` — caller must surface.
|
||||
- This asymmetry is deliberate — masking a permission error as a warning fallback would silently strip launch-dir semantics.
|
||||
3. **Project subdirectory scaffolding** (`7cfafdc`)
|
||||
- `ctask new --project` creates an empty subdir named after the **final suffixed slug** (so `dup-2` collisions produce matching `dup-2/` subdir).
|
||||
- ctask does not seed anything inside the subdir — the user places their own CLAUDE.md, source code, project configuration.
|
||||
- Task workspaces are completely unchanged — no subdir, no `launch_dir`.
|
||||
4. **`CTASK_LAUNCH_DIR` env var** (`509a6d6`)
|
||||
- Added as a 7th arg to `config.EnvVars`. All three cmd callers (`new`, `resume`, `open`) pass `ws.Meta.LaunchDir`.
|
||||
5. **Session launch routed through `LaunchDir`** (`103f2cd`)
|
||||
- `LaunchOpts.LaunchDir` added. `session.Run` calls `ResolveLaunch` before banner/exec, prints any warning to stderr, aborts on security error, and passes the absolute path to `shell.ExecAgent` / `shell.ExecShell` as the child's working directory.
|
||||
- Banner gains a `[ctask] project dir: <name>/` line when `launch_dir` is set.
|
||||
- Lease, manifest, heartbeat, and summary scope stays the **workspace root** — only the child's cwd changes.
|
||||
6. **`ctask info` shows launch fields** (`cdff7f3`)
|
||||
- For workspaces with `launch_dir` set, info prints `Launch dir:`, `Launch path:`, and `Dir exists: yes|no` (via direct `os.Stat`, not via `ResolveLaunch` — info is a display command, not a launch command).
|
||||
7. **`SearchRoots` default fallback** (`47430a1`)
|
||||
- When `CTASK_PROJECT_ROOT` is unset, `SearchRoots()` now appends `$CTASK_ROOT/projects/` so default-location projects are findable from any shell (resolves the v0.4.1 env-var scoping footgun from the previous follow-up list).
|
||||
- Dedupe in `scanAllRoots` prevents duplicate results when the same workspace is reachable via both the depth-2 scan under `CTASK_ROOT` and the explicit `$CTASK_ROOT/projects/` search root.
|
||||
8. **Doctor `CTASK_PROJECT_ROOT` check** (`70bd167`)
|
||||
- Three-state, matching the `checkSeedDir` pattern: `[INFO]` when unset (points at `$CTASK_ROOT/projects/`), `[INFO]` with user-scope advisory when set+exists, `[FAIL]` when set but missing. Only FAIL increments the failure counter.
|
||||
- Wording is advisory ("recommended: set at user scope…"), not prescriptive.
|
||||
9. **Project CLAUDE.md template rewrite** (`cdf1c55`)
|
||||
- Adds a "Workspace Structure" section explicitly describing the root-vs-subdir split.
|
||||
- Keeps the "Git" section from v0.4.1, now with an explicit mention that the project subdir is tracked by the root repo.
|
||||
10. **Status line helpers show effective launch path** (`0976dce`)
|
||||
- Both `.sh` and `.ps1` build `DISPLAY_PATH = $CTASK_WORKSPACE + (CTASK_LAUNCH_DIR if set)`. When `CTASK_LAUNCH_DIR != CTASK_TASK`, the tag becomes `|project:<launch_dir>` (user-overridden launch directory).
|
||||
11. **Docs updated** (`82c9445`)
|
||||
- `docs/commands.md`: workspace-layout diagram, `launch_dir` semantics (default, override, fallback vs error), `CTASK_LAUNCH_DIR` env var, doctor example with new INFO line, Query Resolution default-discovery paragraph.
|
||||
12. **Version bump `0.4.1` → `0.5.0`** (`8130a68`).
|
||||
|
||||
### What v0.5.1 delivered
|
||||
|
||||
Two rounds shipped under the v0.5.1 tag: **wall-clock date for user-facing surfaces** (`a162aec`, `a11d48b`) and **Linux portability baseline** (`7a7b249`, `1033072`).
|
||||
|
||||
#### Round 1 — date fix (a162aec, a11d48b)
|
||||
|
||||
- **Bug:** workspace directory names used UTC date, so at 20:22 EDT on April 22 a new workspace was named `2026-04-23_foo`. Confusing in file explorer, `ctask list`, and `ctask info`.
|
||||
- **Fix:** `internal/workspace/create.go` now uses `time.Now()` (local) for the directory-prefix date and the `YYYYMMDD-HHMMSS` ID. `cmd/info.go` formats `Created` / `Updated` / `Archived` with `.Local()`.
|
||||
- **Stored timestamps still UTC.** `task.yaml` `CreatedAt` / `UpdatedAt` / `ArchivedAt`, session logs, lease, manifest, and summary all continue to store UTC. Only user-facing surfaces (directory prefix + info display) switched.
|
||||
- **Regression guards:** `TestCreateDirectoryPrefixUsesLocalDate` and `TestInfoFormatsTimestampsInLocalZone` enforce both invariants.
|
||||
|
||||
#### Round 2 — Linux portability baseline (7a7b249, 1033072)
|
||||
|
||||
- **Build targets** (`justfile`): `build-linux`, `build-windows`, `build-all` output to `dist/`. Both cross-targets force `CGO_ENABLED=0` so the artifact is pure-Go statically linked regardless of build host (a native Linux build otherwise defaults to `CGO_ENABLED=1` and links against host glibc).
|
||||
- **POSIX install scripts** (`scripts/install.sh`, `scripts/uninstall.sh`): mirror the `.ps1` UX. Default install dir `~/.local/bin`, optional override arg, PATH check warns with the right shell snippet (zsh / bash / fallback) — the script does NOT modify shell config. Statusline helper `ctask-statusline.sh` ships alongside the binary; the `.ps1` helper is intentionally not installed on Linux. Workspace data is preserved on uninstall.
|
||||
- **`.gitignore`** now covers `ctask`, `ctask-*`, `dist/` (in addition to `*.exe`).
|
||||
- **`WorkspacePath` removed from `TaskMeta`.** The audit (`audit-report.md`) confirmed the field was write-only with no production readers — it persisted an absolute Windows path into `task.yaml` that would have been misleading on cross-OS shares. Existing `task.yaml` files with `workspace_path` continue to load (Go's YAML unmarshal silently ignores unknown fields). `TestMetaTypeMissingDefaultsToTask` keeps the legacy YAML literal in its fixture as a backward-compat regression guard.
|
||||
- **Validation:** Windows `go test ./...` green across all 7 packages; cross-compile produces ELF x86-64. **WSL-native validation passed:** `go test ./... -count=1` green; `just build-linux` produces a `statically linked` ELF (`ldd` reports `not a dynamic executable`); `./scripts/install.sh` installs to `~/.local/bin/ctask` and registers the statusline helper; `ctask doctor` recognizes the Linux statusline helper.
|
||||
|
||||
### Next: v0.5.2
|
||||
|
||||
Workspace retrieval and cross-workspace context (spec at `v0.5.2-spec.md`):
|
||||
|
||||
- `ctask restore <workspace>` — un-archive (metadata-only)
|
||||
- `ctask notes <workspace>` — print another workspace's `notes.md` to stdout (raw, agent-consumable)
|
||||
- `ctask path <workspace>` — print the absolute filesystem path
|
||||
- `ctask info <workspace>` — archived-inclusive by default; drop the `--all` flag
|
||||
- `ctask resume <archived-workspace>` — fail with a restore hint instead of generic "not found"
|
||||
- `ctask list --names` — machine-readable enumeration of workspace directory basenames
|
||||
- `ctask completion {bash,zsh,fish,powershell}` — Cobra-generated, with `ValidArgsFunction` hooks per command
|
||||
- Seed CLAUDE.md gets a concise cross-workspace context section
|
||||
|
||||
Lookup policy: **listing is filtered, direct lookup is comprehensive**. Active-only (`resume`, `archive`); archived-inclusive by default (`info`, `notes`, `path`, `restore`); current `--all/-a` semantics preserved on `delete` and `open`.
|
||||
|
||||
## Post-v0.4 bugfixes (still live, carried forward)
|
||||
|
||||
### Provisional-workspace cleanup (2026-04-22, commits `02dcdcc`, `ba8b3a1`)
|
||||
|
||||
Covered in v0.4.1 notes. The exit-code gate (`childExitCode != 0 && startManifest != nil && emptyDiff && NewlyCreated`) is unchanged in v0.5.
|
||||
|
||||
## Tree state at pause
|
||||
|
||||
- `main` clean with respect to v0.4, v0.4.1, v0.5, v0.5.1.
|
||||
- Installed `ctask.exe` is **v0.5.1** — no reinstall needed unless source changes.
|
||||
- Untracked files (do NOT touch without asking):
|
||||
- `.claude/settings.local.json` (modified — Claude Code local settings)
|
||||
- `bugfix-provisional-workspace.md` (spec for the 2026-04-22 initial provisional fix; may be deleted or archived)
|
||||
- `docs/superpowers/plans/2026-04-06-install-workflow.md` (install-workflow plan from an earlier session)
|
||||
- `docs/superpowers/plans/2026-04-21-v0.4-implementation.md` (v0.4 plan — executed)
|
||||
- `docs/superpowers/plans/2026-04-22-v0.4.1-patch.md` (v0.4.1 plan — executed)
|
||||
- `docs/superpowers/plans/2026-04-22-v0.5-implementation.md` (v0.5 plan — executed)
|
||||
- `v0.4-spec.md`, `v0.4.1-patch-spec.md`, `v0.5-spec.md` (specs the implementations followed)
|
||||
- `v0.5.2-spec.md` (next-round spec, in progress)
|
||||
|
||||
## How to resume
|
||||
|
||||
```powershell
|
||||
cd C:\Users\Warren\claude_tasks\ctask
|
||||
just test # go test ./... -count=1
|
||||
just build # or: go build -o ctask.exe .
|
||||
just install # only reinstall if source changed
|
||||
```
|
||||
|
||||
Quick sanity checks that v0.5.1 is live:
|
||||
|
||||
```powershell
|
||||
ctask --version # expect: ctask v0.5.1
|
||||
ctask doctor # expect: 5 pass/fail + 2 seed-dir INFO + 1 CTASK_PROJECT_ROOT INFO
|
||||
|
||||
# Local-time directory prefix (v0.5.1):
|
||||
ctask new --project "tz-check" --no-launch
|
||||
# Expected output: [ctask] created projects/YYYY-MM-DD_tz-check — YYYY-MM-DD matches
|
||||
# the user's local wall-clock date, not UTC.
|
||||
ctask info tz-check # Created/Updated in local zone
|
||||
ctask delete --force tz-check
|
||||
|
||||
# Project subdir + banner (v0.5):
|
||||
ctask new --project "smoke" --no-launch
|
||||
# A subdir named smoke/ exists inside the workspace.
|
||||
ctask resume smoke # banner shows "[ctask] project dir: smoke/" and Claude
|
||||
# launches with cwd inside smoke/. /exit to leave.
|
||||
ctask delete --force smoke
|
||||
|
||||
# Default discovery (v0.5):
|
||||
# Projects created without $CTASK_PROJECT_ROOT should now be findable from any shell.
|
||||
ctask list --projects
|
||||
```
|
||||
|
||||
## Load-bearing design points (don't forget)
|
||||
|
||||
### From v0.4 (unchanged)
|
||||
|
||||
- **Coexisting-session limitation:** "Continue anyway?" or `--force` → second session runs without its own lease. Documented in `docs/commands.md`. Don't redesign without a real user complaint.
|
||||
- **`end_manifest` in the summary** is ctask-internal (not in spec example JSON) and powers Layer-3 stale-workspace diff. Removing it breaks Layer 3.
|
||||
- **Write-lock skip classification** (best-effort vs important-but-non-fatal): unchanged.
|
||||
- **`cmd/delete.go` two-check invariant:** unchanged. `CTASK_WORKSPACE` match → refuse; `.ctask/manifest-start.json` exists → refuse. Both before any mutation.
|
||||
- **`session.Run()` lifecycle** in `internal/session/run.go`:
|
||||
1. `PreflightFull` (Layers 3 + 1)
|
||||
2. Write lock → write lease (unless coexisting)
|
||||
3. Write lock → capture + write start manifest
|
||||
4. Print launch-context banner (Layer 4)
|
||||
5. Start heartbeat (only if we own the lease)
|
||||
6. **v0.5: `workspace.ResolveLaunch(opts.WsDir, opts.LaunchDir)` — print warning or abort on security error**
|
||||
7. Banner lines (including `project dir:` when LaunchDir set) + exec child with cwd = resolved launch path
|
||||
8. Stop heartbeat
|
||||
9. `handleProvisional` (gated on `NewlyCreated && childExitCode != 0 && emptyDiff`) — if it removes the workspace, skip finalize entirely
|
||||
10. `finalize`: single write-lock acquire → append log + write summary + remove lease (if owned) + remove manifest-start
|
||||
- **`new` is intentionally not given `--force`.** Brand-new workspaces have nothing for Layer 1/3 to warn about.
|
||||
- **`internal/lockfile` is a neutral package** — no other ctask imports.
|
||||
|
||||
### From v0.4.1 (unchanged, still load-bearing)
|
||||
|
||||
- **`config.SearchRoots()` is the canonical way to enumerate workspace roots.** Always includes CTASK_ROOT. In v0.5 the semantics of the second entry expanded — see "From v0.5" below.
|
||||
- **`QueryResult.Root`** is how callers render relative paths and compute `CTASK_ROOT` env var. Populated by `scanAllRoots`.
|
||||
- **`scanWorkspaces` is a two-depth scan, first-match-wins.** Depth-1 (flat) check wins if present; never descends into a detected workspace.
|
||||
- **Archive's non-TTY stdin refuses.** Fresh lease + non-TTY = exit non-zero without mutating. Don't regress to "silent proceed".
|
||||
- **Doctor seed-dir checks are three-state.** Only "configured but missing" counts as a failure.
|
||||
- **Provisional-cleanup exit-code gate.** `childExitCode == 0` means preserve. Don't special-case specific codes.
|
||||
|
||||
### From v0.5 (new — don't unlearn)
|
||||
|
||||
- **`launch_dir` is stored as workspace-relative in `task.yaml`**, empty for tasks and pre-v0.5 projects. Never store absolute paths. Never interpret `launch_dir` outside `workspace.ResolveLaunch`.
|
||||
- **`workspace.ResolveLaunch` has three outcomes: valid, soft fallback (with warning), security/permission error.** Soft fallback is `(wsDir, warning, nil)` — caller warns and keeps launching. Error is `("", "", err)` — caller must abort. The asymmetry between `os.IsNotExist` (fallback) and other stat errors (propagate) is intentional; masking a permission error as a warning would silently strip launch-dir semantics.
|
||||
- **Project subdir name = final suffixed slug.** `dup-2` collision means subdir is `dup-2/`, not `dup/`. The `actualSlug` variable in `workspace.Create` is the single source of truth.
|
||||
- **Subdir is created after seeds, empty, never seeded.** Layer 1 (built-in defaults), Layer 2 (general seed), Layer 3 (project seed) all target the workspace root. The `if opts.IsProject { os.MkdirAll(projDir, 0755) }` block runs AFTER seed overlays and BEFORE `task.yaml` is written.
|
||||
- **Session scope is workspace-wide even when launched in the subdir.** Lease, manifest capture, write lock, summary, stale-workspace diff all operate on `opts.WsDir` (the workspace root), not on the resolved launch path. Only the child process's cwd changes.
|
||||
- **`CTASK_LAUNCH_DIR` is always exported** (empty string for tasks). Status line helpers and any future child-side integration can check `-n "$CTASK_LAUNCH_DIR"` safely.
|
||||
- **`SearchRoots()` now appends `$CTASK_ROOT/projects/` by default** when `CTASK_PROJECT_ROOT` is unset. This is redundant with the v0.4.1 two-depth scan under `$CTASK_ROOT` but explicit per spec. Dedupe in `scanAllRoots` (`searchRootKey` — cleaned + lower-cased on Windows) prevents double-counting. Don't remove the default fallback without also reverting the spec-driven expectation that default projects are findable from any shell.
|
||||
- **Doctor check for `CTASK_PROJECT_ROOT` is advisory, not prescriptive.** "Recommended: set at user scope so all terminals can discover these workspaces." Don't harden into a FAIL when the variable is set but the path exists.
|
||||
|
||||
### From v0.5.1 (new — don't unlearn)
|
||||
|
||||
- **Workspace directory prefix uses LOCAL time.** `time.Now()` in `workspace.Create` feeds the `YYYY-MM-DD` date and the `YYYYMMDD-HHMMSS` ID. Don't switch back to `.UTC()` for these. The `TestCreateDirectoryPrefixUsesLocalDate` regression test enforces this.
|
||||
- **`ctask info` displays timestamps in local zone via `.Local().Format(...)`.** Stored timestamps in `task.yaml` are still UTC (`meta.CreatedAt = nowLocal.UTC().Truncate(time.Second)`) — only the display converts.
|
||||
- **Stored timestamps everywhere else remain UTC.** Session logs, lease `StartedAt`/`LastHeartbeatAt`, manifest capture times, summary timestamps are all unambiguous UTC. Don't "localize" those — they're machine-readable.
|
||||
- **Cross-builds force `CGO_ENABLED=0`.** `just build-linux` and `just build-windows` set this explicitly so the artifact is pure-Go static. Don't drop the flag — a native Linux build defaults to cgo and produces a glibc-linked binary that won't run in Alpine / distroless / scratch containers.
|
||||
- **`WorkspacePath` is gone from `TaskMeta`.** Don't add it back. If a persistent workspace identifier is ever needed, add a properly relative-to-root `workspace_id` (forward-slash normalized). Old `task.yaml` files with `workspace_path` parse silently — the field is ignored on read.
|
||||
- **POSIX install script does not modify shell config.** It warns the user about PATH but never edits `~/.bashrc` / `~/.zshrc`. Don't change this without an explicit user request.
|
||||
|
||||
## Open follow-ups (NOT in v0.4/v0.4.1/v0.5, deferred)
|
||||
|
||||
### Potentially worth doing
|
||||
|
||||
- **Pre-v0.5 project workspaces have no `launch_dir`.** They launch from the workspace root, not from any project subdir. No migration path today. Acceptable — users can manually add a `launch_dir` to their `task.yaml` if they want the new behavior. Revisit only if someone complains.
|
||||
- **Drop unused `slug` / `category` / `workspacePath` parameters from `seed.ClaudeMD`** (v0.3 follow-up, still open).
|
||||
- **Status-line: color the `|project` / `|project:<dir>` marker** if Claude Code's `statusLine` ever supports ANSI.
|
||||
|
||||
### Concurrency-related (from v0.4)
|
||||
|
||||
- Coexisting-session detection (more than one live lease per workspace).
|
||||
- Cross-machine PID liveness check.
|
||||
- `--force` symmetry on other commands — no use case yet.
|
||||
- A `ctask sessions` subcommand to inspect lease/summary.
|
||||
|
||||
### Repo hygiene
|
||||
|
||||
- Several untracked working docs (`v0.4-spec.md`, `v0.4.1-patch-spec.md`, `v0.5-spec.md`, `bugfix-provisional-workspace.md`, the plan files) could be committed alongside `v0.2-spec.md`/`v0.3-spec.md` for durability. Currently session-local.
|
||||
- `.claude/settings.local.json` has uncommitted changes of unknown scope. Leave alone unless asked.
|
||||
|
||||
### Resolved (don't re-add to this list)
|
||||
|
||||
- ~~CTASK_PROJECT_ROOT env-var scoping UX footgun~~ → resolved in v0.5 via the default `$CTASK_ROOT/projects/` fallback in `SearchRoots()`.
|
||||
- ~~UTC date in directory names / info display~~ → resolved in v0.5.1.
|
||||
|
||||
## Files to read first when resuming
|
||||
|
||||
For the v0.5 surface:
|
||||
|
||||
1. `v0.5-spec.md` — the spec v0.5 followed
|
||||
2. `docs/superpowers/plans/2026-04-22-v0.5-implementation.md` — the executed plan (includes scanner-safety regression tests, dedupe tests, amendment history)
|
||||
3. `internal/workspace/launchdir.go` — `ResolveLaunch` with the three-outcome contract
|
||||
4. `internal/workspace/create.go` — subdir scaffold + local-time date + `launch_dir` default
|
||||
5. `internal/session/run.go` — `LaunchOpts.LaunchDir`, `ResolveLaunch` call site, banner extension
|
||||
6. `cmd/info.go` — local-time display + launch fields
|
||||
7. `cmd/doctor.go` — `checkProjectRoot` helper
|
||||
8. `scripts/ctask-statusline.{sh,ps1}` — effective-path display logic
|
||||
9. `internal/seed/templates.go` — `ClaudeMDProject` with Workspace Structure section
|
||||
|
||||
For the v0.4.1 surface (still load-bearing):
|
||||
|
||||
10. `internal/config/config.go` — `SearchRoots()`, `samePath`, `searchRootKey` dedup; default `$CTASK_ROOT/projects/` fallback
|
||||
11. `internal/workspace/query.go` — two-depth `scanWorkspaces`, `scanAllRoots`, `QueryResult.Root`
|
||||
12. `cmd/archive.go` — active-session check + non-TTY refusal + `isStdinTerminal`
|
||||
|
||||
For the v0.4 surface:
|
||||
|
||||
13. `internal/session/run_preflight.go` — Layer 3 + Layer 1 preflight
|
||||
14. `internal/session/lease.go` — Lease + freshness + cleanup
|
||||
15. `internal/session/summary.go` — SessionSummary + launch-context banner
|
||||
16. `internal/lockfile/writelock.go` — write-lock primitive
|
||||
17. `internal/session/run_provisional.go` — `handleProvisional` with `childExitCode` gate
|
||||
18. `docs/commands.md` — user-facing surface
|
||||
|
||||
## Don't re-do
|
||||
|
||||
- Do not invent remote install / `go install` commands (per `CLAUDE.md`, this project is local-only)
|
||||
- Do not touch `cmd/delete.go`'s two-check protection
|
||||
- Do not weaken the v0.3 `.gitignore` seed-wins rule
|
||||
- Do not redesign the lease model around multiple concurrent leases without a real user-reported problem
|
||||
- Do not re-run the v0.4 smoke tests in a fresh session unless source has changed
|
||||
- Do not expand the provisional-cleanup gate beyond "strictly empty manifest diff AND non-zero child exit". No size thresholds, no heuristics, no exit-code-specific mappings.
|
||||
- Do not extend provisional cleanup to `resume` / `open` / `last`. `NewlyCreated` is set only by `cmd/new.go`.
|
||||
- Do not drop `QueryResult.Root` or collapse `SearchRoots()` back to a single string.
|
||||
- Do not change archive's non-TTY behavior back to "proceed silently".
|
||||
- Do not add a workspace registry file. The default `$CTASK_ROOT/projects/` fallback solved the discovery footgun.
|
||||
- **v0.5:** Do not change `launch_dir` storage to absolute paths. Workspace-relative is load-bearing.
|
||||
- **v0.5:** Do not mask non-`os.IsNotExist` stat errors in `ResolveLaunch` as warning fallbacks. The asymmetry between soft-fallback (missing / not-a-dir) and hard-error (permission / ENOTDIR / invalid name) is deliberate.
|
||||
- **v0.5:** Do not seed anything inside the project subdirectory. Seeds target the workspace root only.
|
||||
- **v0.5:** Do not let the session launch change scope (lease, manifest, summary, write-lock) away from the workspace root. Only the child's cwd is the launch dir.
|
||||
- **v0.5:** Do not remove the default `$CTASK_ROOT/projects/` fallback in `SearchRoots()` — it resolves the v0.4.1 env-var scoping footgun.
|
||||
- **v0.5.1:** Do not switch the directory prefix / ID back to UTC. The `TestCreateDirectoryPrefixUsesLocalDate` test enforces local time.
|
||||
- **v0.5.1:** Do not remove `.Local()` from the `ctask info` Created/Updated/Archived formatting. `TestInfoFormatsTimestampsInLocalZone` enforces local display.
|
||||
- **v0.5.1:** Do not change *stored* timestamps (task.yaml, session logs, lease, manifest, summary) to local time. UTC storage is deliberate — only display converts.
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
# ctask v0.5.2 Spec — Workspace Retrieval and Cross-Workspace Context
|
||||
|
||||
## Theme
|
||||
|
||||
v0.1 = create and resume workspaces
|
||||
v0.2 = understand and continue workspaces later
|
||||
v0.3 = personalize workspaces and support longer-lived project work
|
||||
v0.4 = protect workspaces from concurrent session conflicts
|
||||
v0.5 = separate workspace management from project code
|
||||
v0.5.1 = ctask runs correctly on Linux (WSL and Docker)
|
||||
v0.5.2 = workspaces are discoverable, retrievable, and inspectable across sessions
|
||||
|
||||
## Problem
|
||||
|
||||
Four usability gaps discovered through real usage:
|
||||
|
||||
1. **Archived workspaces are a dead end.** `ctask archive` sets metadata and hides the workspace from default listing, but there is no `ctask restore` to bring it back. Users who archive prematurely must recreate the workspace and manually copy notes.md.
|
||||
|
||||
2. **Archived workspaces are invisible to direct lookup.** `ctask info <workspace>` does not find archived workspaces unless the user passes `-a`. Archived workspaces are still workspaces — users expect to look them up by name.
|
||||
|
||||
3. **No cross-workspace context access.** When an AI agent works in one workspace, it has no way to read the notes or location of another workspace. Prior work is invisible unless the user manually copies content. ctask should expose workspace context through CLI commands that agents can call.
|
||||
|
||||
4. **No shell autocompletion.** Workspace names can be long and detailed. Users must type them in full or rely on shell history. Since ctask knows what workspaces exist and uses Cobra, autocompletion should be available.
|
||||
|
||||
## Design Principles
|
||||
|
||||
Two rules govern this spec:
|
||||
|
||||
**Listing is filtered. Direct lookup is comprehensive.** `ctask list` shows active workspaces by default to keep output clean. Commands that accept a workspace name — `info`, `notes`, `path`, `restore` — search active and archived workspaces because the user asked for a specific workspace by name.
|
||||
|
||||
**ctask exposes workspace context through CLI commands. Agents consume it themselves.** ctask does not inject context into agent sessions, manage agent memory, or maintain a central knowledge base. It provides commands that print useful text to stdout. Agents (Claude Code or otherwise) can call these commands and read the output. This keeps ctask agent-agnostic and in its lane as a workspace manager.
|
||||
|
||||
## Scope
|
||||
|
||||
1. `ctask restore <workspace>` — un-archive a workspace
|
||||
2. `ctask notes <workspace>` — print another workspace's notes.md
|
||||
3. `ctask path <workspace>` — print the absolute path of a workspace
|
||||
4. Archived-inclusive direct lookup for `info` (and other direct-lookup commands)
|
||||
5. `ctask list --names` — machine-readable workspace enumeration
|
||||
6. `ctask completion <shell>` — shell autocompletion
|
||||
7. Updated CLAUDE.md seed text for cross-workspace awareness
|
||||
|
||||
---
|
||||
|
||||
## Workspace Lookup Policy
|
||||
|
||||
The resolver (`resolveOne` → `ResolveQuery`) already supports an `includeArchived` boolean. This spec standardizes how each command sets it.
|
||||
|
||||
### Active-only lookup
|
||||
|
||||
These commands operate on active workspaces. Looking up an archived workspace would be an error.
|
||||
|
||||
```
|
||||
ctask resume <workspace> active only (archived → error with restore hint)
|
||||
ctask archive <workspace> active only
|
||||
```
|
||||
|
||||
### Archived-inclusive lookup
|
||||
|
||||
These commands read or inspect workspaces. The user asked for a specific workspace by name — don't pretend it doesn't exist because it's archived.
|
||||
|
||||
```
|
||||
ctask info <workspace> active + archived
|
||||
ctask notes <workspace> active + archived
|
||||
ctask path <workspace> active + archived
|
||||
ctask restore <workspace> active + archived (then require status == archived)
|
||||
```
|
||||
|
||||
### Active-only by default, with `--all` opt-in
|
||||
|
||||
These commands are destructive or start sessions. Defaulting to active-only is safer; `--all` allows explicit archived access.
|
||||
|
||||
```
|
||||
ctask delete <workspace> active only by default; archived allowed with --all/-a
|
||||
ctask open <workspace> active only by default; archived allowed with --all/-a
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
For commands currently hardcoding `includeArchived=false` (`archive` at `cmd/archive.go:29`, `resume` at `cmd/resume.go:50`): no change needed, these are correct.
|
||||
|
||||
For `info` (`cmd/info.go:23`): remove the `--all/-a` flag and always resolve with `includeArchived=true`. The flag is not needed — `info` is a read-only lookup and should always find the workspace the user asked for.
|
||||
|
||||
For `delete` and `open`: keep their existing `--all/-a` flags with current defaults (active-only). No change needed.
|
||||
|
||||
### Resume behavior for archived workspaces
|
||||
|
||||
When a user runs `ctask resume <archived-workspace>`, the command should fail with a helpful message:
|
||||
|
||||
```
|
||||
[ctask] error: workspace "promptvolley-v2" is archived
|
||||
|
||||
To restore it:
|
||||
ctask restore promptvolley-v2
|
||||
```
|
||||
|
||||
This keeps archive and restore as intentional lifecycle actions, not something that happens silently through resume.
|
||||
|
||||
**Implementation note:** `resume` currently resolves with `includeArchived=false`, so it won't find archived workspaces at all — it just reports "not found." To provide the restore hint, change `resume` to resolve with `includeArchived=true`, then check the result's status. If the workspace is active, proceed normally. If archived, print the hint and exit. This way the user gets a specific, actionable message instead of a generic "not found" error.
|
||||
|
||||
---
|
||||
|
||||
## 1. `ctask restore <workspace>`
|
||||
|
||||
### Behavior
|
||||
|
||||
```bash
|
||||
ctask restore promptvolley-v2
|
||||
```
|
||||
|
||||
Resolves the workspace using archived-inclusive lookup, then:
|
||||
|
||||
- Verify the workspace status is `archived`. If it's already active, print an error: `workspace "promptvolley-v2" is already active`
|
||||
- Check for an active session lease (mirror the guard in `ctask archive`). If a lease is held, refuse with an appropriate message
|
||||
- Set `status: active` in task.yaml. Write the value explicitly — do not omit the field or set it to empty string, even if older code treats empty status as active
|
||||
- Clear `archived_at` (set to zero value / omit from YAML)
|
||||
- Update `updated_at` to current time
|
||||
- Write task.yaml
|
||||
- Print confirmation:
|
||||
|
||||
```
|
||||
[ctask] restored: projects/2026-04-24_promptvolley-v2
|
||||
[ctask] status: active
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Restore is metadata-only. No directory moves, no file changes beyond task.yaml
|
||||
- Restoring does not start a session. The user runs `ctask resume` afterward if they want to work in it
|
||||
- If a workspace with the same slug exists in active status (edge case: user archived one, created a new one with the same name), the resolver will find both. The resolver's existing ambiguity handling applies — it should report the conflict and ask the user to use the full name
|
||||
|
||||
### What restore does NOT do
|
||||
|
||||
- Does not launch an agent
|
||||
- Does not regenerate CLAUDE.md or any seed files
|
||||
- Does not modify notes.md, context/, or any workspace content
|
||||
- Does not create a session log entry (no session is started)
|
||||
|
||||
---
|
||||
|
||||
## 2. `ctask notes <workspace>`
|
||||
|
||||
### Behavior
|
||||
|
||||
```bash
|
||||
ctask notes promptvolley-v2
|
||||
```
|
||||
|
||||
Resolves the workspace using archived-inclusive lookup, then prints the contents of `notes.md` to stdout.
|
||||
|
||||
```bash
|
||||
# Equivalent to:
|
||||
cat "$(ctask path promptvolley-v2)/notes.md"
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Output is the raw contents of notes.md, no framing or decoration. This makes it directly consumable by agents reading stdout
|
||||
- If notes.md does not exist in the workspace, print a clear message to stderr and exit with a non-zero code: `[ctask] no notes.md found in workspace "promptvolley-v2"`
|
||||
- If the workspace is not found, the resolver's existing error handling applies
|
||||
- Works on both active and archived workspaces
|
||||
|
||||
### Why stdout matters
|
||||
|
||||
The primary consumer of this command is an AI coding agent running `ctask notes <workspace>` via shell. The agent reads stdout to get context from prior work. Keeping the output clean (no banners, no status lines) means the agent gets exactly the document content with no parsing needed. Informational messages go to stderr.
|
||||
|
||||
---
|
||||
|
||||
## 3. `ctask path <workspace>`
|
||||
|
||||
### Behavior
|
||||
|
||||
```bash
|
||||
ctask path promptvolley-v2
|
||||
```
|
||||
|
||||
Resolves the workspace using archived-inclusive lookup, then prints the absolute filesystem path to stdout.
|
||||
|
||||
```
|
||||
C:\Users\Warren\ai-workspaces\projects\2026-04-24_promptvolley-v2
|
||||
```
|
||||
|
||||
or on Linux:
|
||||
|
||||
```
|
||||
/home/warren/ai-workspaces/projects/2026-04-24_promptvolley-v2
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Output is a single line: the absolute, OS-native filesystem path. No trailing newline decoration, no framing
|
||||
- The path uses native separators (backslashes on Windows, forward slashes on Linux). This is the real filesystem path, not a display identifier
|
||||
- Works on both active and archived workspaces
|
||||
- This is `QueryResult.Path` — already available from the resolver, no new logic needed
|
||||
|
||||
### Use case
|
||||
|
||||
An agent that needs to inspect files in another workspace can run `ctask path <workspace>` and then read files at that location. Combined with `ctask notes`, this gives agents full read access to prior workspace context through standard shell commands.
|
||||
|
||||
---
|
||||
|
||||
## 4. `ctask info` — Archived-Inclusive by Default
|
||||
|
||||
### Current behavior
|
||||
|
||||
`ctask info <workspace>` requires `--all` or `-a` to find archived workspaces. Without the flag, an archived workspace is reported as not found.
|
||||
|
||||
### New behavior
|
||||
|
||||
`ctask info <workspace>` finds the workspace whether it is active or archived. If the workspace is archived, the info output should include the status clearly:
|
||||
|
||||
```
|
||||
Task: promptvolley-v2
|
||||
Title: promptvolley-v2
|
||||
Category: projects
|
||||
Status: archived
|
||||
...
|
||||
Archived: 2026-05-01 14:30:00
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
Change `cmd/info.go` so that the resolver call uses `includeArchived=true` unconditionally. Remove the `--all/-a` flag from `info` — it is no longer needed since `info` always searches comprehensively.
|
||||
|
||||
### Why this is correct
|
||||
|
||||
The user typed a specific workspace name. They know it exists. Telling them "not found" because it happens to be archived is unhelpful and forces them to guess at the flag. The principle: listing is filtered, direct lookup is comprehensive.
|
||||
|
||||
---
|
||||
|
||||
## 5. `ctask list --names`
|
||||
|
||||
### Behavior
|
||||
|
||||
```bash
|
||||
ctask list --names
|
||||
```
|
||||
|
||||
Outputs one workspace directory basename per line, no headers, no table formatting:
|
||||
|
||||
```
|
||||
2026-04-24_promptvolley-v2
|
||||
2026-04-22_litlink-v2
|
||||
2026-05-07_linux-test
|
||||
```
|
||||
|
||||
```bash
|
||||
ctask list --names --all
|
||||
```
|
||||
|
||||
Includes archived workspaces in the output.
|
||||
|
||||
### Rules
|
||||
|
||||
- One directory basename per line (e.g., `2026-04-24_promptvolley-v2`, not the bare slug `promptvolley-v2`). Basenames are unambiguous — bare slugs can collide across categories or dates
|
||||
- What `list --names` outputs, other commands accept as input. The resolver already accepts exact directory basenames
|
||||
- Respects existing filters: `--all`, `--archived`, `--projects`, or whatever category/status filters `list` currently supports
|
||||
- No additional decoration: no dates, no status, no categories. This is for machine consumption
|
||||
- If no workspaces match, output nothing (empty stdout, zero exit code)
|
||||
|
||||
### Why this exists
|
||||
|
||||
Shell completion scripts and external tooling need a stable, parseable enumeration of workspace names. While Cobra's `ValidArgsFunction` can enumerate internally, `list --names` serves agents, shell scripts, and any future integration that needs to discover workspaces without importing ctask's Go code.
|
||||
|
||||
---
|
||||
|
||||
## 6. `ctask completion <shell>`
|
||||
|
||||
### Behavior
|
||||
|
||||
```bash
|
||||
ctask completion powershell
|
||||
ctask completion bash
|
||||
ctask completion zsh
|
||||
ctask completion fish
|
||||
```
|
||||
|
||||
Outputs the completion script for the specified shell. The user sources it in their shell profile.
|
||||
|
||||
### Implementation
|
||||
|
||||
Cobra provides built-in completion generation. The implementation is:
|
||||
|
||||
- Enable Cobra's completion command (or add a thin wrapper if customization is needed)
|
||||
- Add `ValidArgsFunction` hooks to every command that accepts a workspace name argument
|
||||
|
||||
### `ValidArgsFunction` hooks
|
||||
|
||||
Each command gets a completion function that enumerates workspace candidates appropriate to that command's lookup policy:
|
||||
|
||||
```
|
||||
resume: active workspaces only
|
||||
archive: active workspaces only
|
||||
restore: archived workspaces only
|
||||
info: active + archived
|
||||
notes: active + archived
|
||||
path: active + archived
|
||||
delete: active workspaces only (flag-aware completion for --all can come later)
|
||||
open: active workspaces only (flag-aware completion for --all can come later)
|
||||
```
|
||||
|
||||
The completion function calls the workspace listing logic internally (no shell-out to `ctask list`). A shared helper should enumerate slugs with a configurable status filter to avoid duplicating logic across commands.
|
||||
|
||||
### PowerShell completion
|
||||
|
||||
PowerShell is the primary shell on Windows. The user adds the output of `ctask completion powershell` to their `$PROFILE`. Tab-cycling through candidates works natively with PowerShell's completion system.
|
||||
|
||||
### Bash/Zsh completion
|
||||
|
||||
For WSL and Linux environments. The user sources the script in `.bashrc` or `.zshrc`. This also benefits the workflow where the developer SSHs into a container and uses ctask there.
|
||||
|
||||
### Rules
|
||||
|
||||
- Completion scripts are generated, not maintained by hand. Cobra handles the shell-specific syntax
|
||||
- The `completion` command itself does not require workspace resolution — it is a utility command
|
||||
- Fish completion is included for free via Cobra. No extra work, but no need to test extensively unless a real user needs it
|
||||
|
||||
---
|
||||
|
||||
## 7. CLAUDE.md Seed Text Update
|
||||
|
||||
### Current state
|
||||
|
||||
ctask seeds a CLAUDE.md into each workspace with workspace management instructions. This text does not mention cross-workspace context access.
|
||||
|
||||
### Addition
|
||||
|
||||
Add the following section to the built-in CLAUDE.md template used during workspace creation:
|
||||
|
||||
```markdown
|
||||
## Cross-Workspace Context
|
||||
|
||||
Related work may exist in other ctask workspaces. For project continuation,
|
||||
migration, debugging, or building on prior work, inspect related workspaces
|
||||
before making changes.
|
||||
|
||||
Available commands:
|
||||
|
||||
ctask list --all discover all workspaces (including archived)
|
||||
ctask info <workspace> view metadata and status of any workspace
|
||||
ctask notes <workspace> read another workspace's notes.md
|
||||
ctask path <workspace> get the filesystem path to inspect files directly
|
||||
|
||||
Treat other workspaces as read-only unless the user explicitly asks you to
|
||||
modify them.
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- This text is added to the seed template, not retroactively injected into existing workspaces
|
||||
- Users who have customized their seed directory will not see this text unless they add it themselves. This is expected — seeds are user-controlled
|
||||
- The text is concise. CLAUDE.md is loaded into the agent's context window at session start; long instructions waste context budget
|
||||
- The text is agent-agnostic. It references CLI commands, not Claude-specific features. If AGENTS.md is in use (v0.6), this section belongs there instead, with the CLAUDE.md shim pointing to it
|
||||
|
||||
---
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Existing workspaces are unaffected. No metadata changes, no layout changes
|
||||
- `ctask info` becomes more permissive (finds archived workspaces), which is strictly a UX improvement — no existing workflow breaks
|
||||
- `ctask list` default behavior is unchanged (active only). `--names` is additive
|
||||
- Existing CLAUDE.md files in workspaces are not modified. The new seed text only applies to newly created workspaces
|
||||
|
||||
## Non-Goals for v0.5.2
|
||||
|
||||
- `ctask find <query>` — full-text search across workspaces. Defer until list + notes proves insufficient
|
||||
- Claude-specific slash commands or skills files. Defer until the manual workflow proves itself
|
||||
- `list --json` — defer unless a real consumer needs structured output. `--names` covers the completion and scripting use case
|
||||
- Fuzzy or partial-match workspace resolution. The current resolver accepts exact slug, exact basename, and case-insensitive substring. That's sufficient
|
||||
- Modifying other workspaces via ctask commands. Cross-workspace access is read-only by design
|
||||
|
||||
## Build Order
|
||||
|
||||
1. Settle the lookup policy: update `info` to use `includeArchived=true` by default. Verify no tests assume the old default
|
||||
2. Implement `ctask restore` with archived-inclusive lookup and active-lease guard
|
||||
3. Implement `ctask notes` with archived-inclusive lookup and clean stdout output
|
||||
4. Implement `ctask path` with archived-inclusive lookup
|
||||
5. Update `ctask resume` to print a helpful restore hint when targeting an archived workspace
|
||||
6. Add `ctask list --names` with filter support
|
||||
7. Add `ctask completion` command and `ValidArgsFunction` hooks for all workspace-accepting commands
|
||||
8. Update CLAUDE.md seed template with cross-workspace context section
|
||||
9. Tests:
|
||||
- restore: archived workspace restored to active with explicit `status: active`, already-active workspace refused, lease guard respected, metadata updated correctly
|
||||
- notes: prints notes.md content, handles missing notes.md, works on archived workspace
|
||||
- path: prints absolute path, correct separators per OS, works on archived workspace
|
||||
- info: finds archived workspace without `-a` flag, flag removed
|
||||
- resume: archived workspace refused with restore hint (not generic "not found")
|
||||
- list --names: outputs directory basenames one per line, respects --all filter, empty output on no matches, no slug collisions
|
||||
- completion: generates valid shell scripts (smoke test — no need to test shell integration)
|
||||
10. Full test suite pass on both Windows and Linux
|
||||
Reference in New Issue
Block a user