# ctask — Session Handoff Notes Last touched: 2026-05-07 (after v0.5.2 ship). Pause before starting v0.6. ## 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: /` 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:` (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. ### What v0.5.2 delivered Theme: **workspace retrieval and cross-workspace context**. Five feature commits (`a5e508b..3b6be0d`) plus a version bump (`5910100`). - **Three new direct-lookup commands** (`176e788`): - `ctask restore ` — un-archive (metadata-only flip; mirrors archive's lease guard; refuses already-active workspaces) - `ctask notes ` — stream `notes.md` to stdout raw, no framing; `[ctask]`-prefixed stderr on missing file (`SilenceErrors: true` so the spec format is the only diagnostic shown) - `ctask path ` — print absolute filesystem path on one line, OS-native separators - **Direct-lookup commands are archived-inclusive by default** (`b923ae8`). The `--all/-a` flag was dropped from `info` entirely; the `Status:` line in info output surfaces archived state. `delete` and `open` keep their `--all` flags (they are potentially destructive). `resume` and `archive` resolve archived-inclusive then filter, so they can give actionable error messages. - **`ctask resume ` prints a restore hint** (`b923ae8`): ``` [ctask] error: workspace "X" is archived To restore it: ctask restore X ``` Genuine not-found and ambiguity behavior preserved. - **`ctask list --names`** (`56d2e07`) — machine-readable enumeration. One workspace directory basename per line, no header. Empty stdout on no match (no "No workspaces found." placeholder). Honors all existing list filters. Spec invariant `TestListNamesCandidatesResolveUniquely` enforces that every emitted line resolves to exactly one workspace. - **Shell completion via Cobra** (`176e788`, `b923ae8`). `ctask completion {bash,zsh,fish,powershell}` works through Cobra's auto-injected subcommand. `ValidArgsFunction` hooks per spec policy: - `resume`, `archive` → active only - `restore` → archived only - `info`, `notes`, `path` → both - `delete`, `open` → active only (flag-aware completion deferred) Candidates are workspace directory basenames (unique under the resolver's exact-match step), not bare slugs (which can collide across categories or dates). - **Cross-workspace context section in seed CLAUDE.md** (`3b6be0d`). Both task and project templates teach agents to inspect related workspaces with `list --all` / `info` / `notes` / `path` before starting work, and to treat other workspaces as read-only. Applies to newly-created workspaces only — no retroactive overwrite of existing CLAUDE.md files. - **Version bump** to `0.5.2` (`5910100`). **Validation:** - Windows: `go test ./... -count=1` green across all 7 packages; `go vet` clean; `go build` clean. End-to-end smoke against the binary covered the full archive ↔ restore cycle plus completion-script generation. - WSL/Linux: refreshed validation copy; reinstalled via `install.sh`; full smoke checklist passed (16 behaviors across new + active + archived + restore + completion). Cross-built Linux binary is statically linked (`file` reports `statically linked`; `ldd` reports `not a dynamic executable`). ### Known limitation (v0.5.2) - **`ctask resume ` prints `Error: workspace archived` below the actionable hint.** Cosmetic only — the user-visible hint is shown first and is correct. Suppressing the trailing line would require `SilenceErrors: true` on `resume`, which would also swallow unrelated session-launch errors. Acceptable trade; revisit only if the redundancy actively confuses users. ### Next: v0.6 (planning) Not yet specced. Likely scope: - **Config / agent profile work.** The long-flagged direction towards `~/.config/ctask/` configuration; may supersede the env-var resolution dance for `CTASK_ROOT`/`CTASK_PROJECT_ROOT`/`CTASK_SEED_DIR`/`CTASK_AGENT`. - **Optional polish for resume error output.** Silence the trailing Cobra "Error: workspace archived" line — likely via per-error-path `SilenceErrors` or a sentinel-error wrapper. - **Flag-aware completion for `open`/`delete --all`.** Cobra supports `RegisterFlagCompletionFunc` per flag; would let completion offer archived candidates only when `--all` is passed. ## 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) ## 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. ### From v0.5.2 (new — don't unlearn) - **`info` no longer has `--all`/`-a`.** Direct lookup is archived-inclusive by default. Don't add the flag back. The `Status:` line in info output makes archived state obvious. `TestInfoFindsArchivedWorkspaceWithoutFlag` enforces this. - **`resume` / `archive` resolve archived-inclusive then filter.** Don't switch back to `includeArchived=false` at the resolver layer. The two-step pattern (resolve broadly, reject explicitly) is what lets `resume` give the actionable restore hint instead of a generic "not found". - **Direct-lookup commands (`info`, `notes`, `path`, `restore`) use `includeArchived=true`.** Active-only commands (`resume`, `archive`) reject archived after resolve. Potentially-destructive commands (`delete`, `open`) keep their explicit `--all` opt-in. Don't drift back to a uniform default — the policy is intentional. See `v0.5.2-spec.md` "Workspace Lookup Policy". - **`list --names` emits directory basenames, not bare slugs.** Bare slugs can collide (e.g., `promptvolley` archived in v1, active in v2). Basenames are unique under the resolver's exact-match step. `TestListNamesCandidatesResolveUniquely` enforces the invariant. - **`ValidArgsFunction` hooks call `workspace.ListWorkspaces` directly.** No subprocess shell-out to `ctask list --names`. Don't add one — direct calls are faster and don't depend on PATH state. - **Cobra adds the `completion` subcommand lazily on first `Execute()`.** A test that calls `rootCmd.Find("completion")` before any `Execute()` returns "unknown command". For unit tests, prefer the `rootCmd.GenXxxCompletion(...)` generators directly. For end-to-end, one `SetArgs(...)` + `Execute()` per test — running multiple `Execute()` calls in succession with different shell args has state issues. - **`notes` uses `SilenceErrors: true`** so the `[ctask] no notes.md found in workspace "X"` stderr line is the only diagnostic the user/agent sees. Don't set `SilenceErrors: false` and add a `[ctask]` prefix to the returned error message — Cobra would then print both, doubling the message. ## 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:` 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.