diff --git a/notes.md b/notes.md index 537c60e..b262cf9 100644 --- a/notes.md +++ b/notes.md @@ -1,14 +1,14 @@ # ctask — Session Handoff Notes -Last touched: 2026-05-09. **v0.5.2 is shipped on `main` and installed locally as `v0.5.2`. v0.5.3 implementation is complete on branch `feat/v0.5.3-persistent-session-mode` (20 commits) but is NOT yet merged or installed — it is awaiting the user's manual WSL smoke verification.** +Last touched: 2026-05-14. **v0.5.4 is shipped on `main` and installed locally as `v0.5.4`. Branch `feat/v0.5.4-session-visibility-polish` merged via `--no-ff` (merge commit `10b7d5a`) and deleted. No tag pushed (no remote). Theme: surface workspace session state in everyday commands + finish the v0.5.3 invocation-name work + bring `docs/commands.md` current — pure polish, no new subsystems.** ## Where we are -- **`main`:** v0.5.2 (workspace retrieval + cross-workspace context). Installed binary at `%LOCALAPPDATA%\ctask\bin\ctask.exe` is `v0.5.2`. -- **`feat/v0.5.3-persistent-session-mode`:** v0.5.3 (persistent session mode via tmux). 20 commits. All automated tests + `go vet` + cross-compile (`just build-linux` produces a static ELF) green on the Windows host. The Linux binary at `dist/ctask-linux-amd64` runs and reports `ctask v0.5.3` under WSL `debian-dev`. -- **Pending action:** the user runs the manual smoke checklist at `docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md` (v2 — explicit terminals, corrected expectations) and reports PASS/FAIL per section. On all-PASS we merge the branch to `main`, `just install` to refresh the installed binary, and optionally `git tag v0.5.3`. +- **`main`:** v0.5.4 (session-visibility polish). Tip at merge commit `10b7d5a`; version-bump commit `7704cd9`. Installed binary at `%LOCALAPPDATA%\ctask\bin\ctask.exe` is `v0.5.4`. v0.5.3 still load-bearing underneath — none of its surfaces changed. +- **Active branches:** none. v0.5.4 feature branch was deleted post-merge. +- **Pending action:** none. v0.6 Phase 1 is the next horizon (config file + schema_version + workspace.mode + source attribution in doctor/info). See "Next" section below. - Remote: none (local-only, intentional — see `CLAUDE.md`). -- `ctask doctor` reports 5 pass/fail + 2 seed-directory + 1 `CTASK_PROJECT_ROOT` check (all three-state). Once v0.5.3 lands, doctor adds an INFO line for `Session mode: direct|persistent` plus an INFO/FAIL line for tmux when persistent. +- `ctask doctor` reports 5 pass/fail + 2 seed-directory + 1 `CTASK_PROJECT_ROOT` check + 1 `Session mode` INFO line + 1 tmux INFO/FAIL line (when persistent mode is configured). Unchanged in v0.5.4. ### What v0.4 delivered (still true, unchanged) @@ -114,15 +114,98 @@ Theme: **workspace retrieval and cross-workspace context**. Five feature commits ### 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. +- ~~**`ctask resume ` prints `Error: workspace archived` below the actionable hint.**~~ Resolved in v0.5.4 (`ae9bfaf`) via the `errArchivedWorkspace` sentinel + conditional `SilenceErrors`. Other resume errors still flow through Cobra's default rendering. -### Next: v0.6 (planning) +### What v0.5.3 delivered -Not yet specced. Likely scope: +Theme: **persistent session mode via tmux** — workspaces can survive terminal disconnect, get reattached from a different terminal, and recover orphaned ownership when a previous foreground ctask died. Merge commit `1e93332`; final polish commit `c204d87`. -- **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. +User-facing surface: + +- New env var `CTASK_SESSION_MODE` (`direct` | `persistent`); `direct` is the default and requires no setup. Unknown values fall back to `direct` with a stderr warning. +- `ctask attach ` — always-tmux entry command (ignores `CTASK_SESSION_MODE`). Defaults to launching the agent; the `--shell` flag swaps in an interactive shell. +- `--direct` flag on `new` / `resume` / `last` / `open` to bypass persistent mode for one invocation. When a tmux session already exists for the workspace, the user gets a TTY confirmation prompt; non-TTY proceeds with a single stderr warning. +- `ctask doctor` reports `Session mode: direct|persistent` plus an INFO/FAIL line for tmux presence and version when persistent. +- v0.4 lease-prompt tightening (`c204d87`): when entering direct mode on a workspace that already has a live tmux session, the "Continue anyway?" prompt now suggests ` attach ` as the reattach path. Threaded via `PreflightOpts.ActiveLeaseHint` / `LaunchOpts.ActiveLeaseHint`; computed in `cmd/entry.go::directModeTmuxHint` (best-effort: silent when no tmux on PATH or no session for the workspace). +- Invocation name in user-facing command suggestions (`c204d87`): the binary name printed in bypass / restore / "create one with" suggestions reflects `filepath.Base(os.Args[0])` instead of a hard-coded "ctask". Local-build PowerShell users running `.\ctask.exe` see `ctask.exe new --direct`; installed contexts continue to see `ctask`. Test seam (`cmd/invocation.go::invocationNameOverride`) pins the name to `"ctask"` in unit tests so substring assertions stay stable. Descriptive prose ("ctask persistent mode requires...") and the ssh-remote hint (`ssh -t ctask `) intentionally keep the literal "ctask" — they refer to program identity / remote invocation, not the local command form. + +Architecture notes: + +- tmux is invoked via a three-call pattern (`has-session`, `new-session -d`, `attach-session`) with a 3-second polling loop to detect session end (`shell.PollInterval`). Polling cadence is below the 30-second heartbeat interval, so finalize lag is bounded. +- Session names are deterministic: `ctask---`, where the hash is the first 6 hex chars of `sha256(canonical absolute workspace path)`. On Windows the path is lowercased before hashing (matches `searchRootKey`). tmux's status bar truncates the name aggressively (e.g., `[ctask-pro0:bash*]`) — that's a tmux display thing, not a bug. +- Three entry paths (owner-create, passive reattach, adopted reattach) picked by the pure function `dispatchPersistent(hasTmuxSession, leaseState)`. Passive reattach (`AttachExisting`) runs no Preflight / lease / manifest / heartbeat / banner / finalize — it's a viewer-only attach via `shell.AttachSession`. The owner handles finalize. +- Adoption (`AdoptExistingPersistentSession`) transfers ownership under the metadata write lock with a re-check race guard. The previous lease is replaced, `task.yaml.UpdatedAt` is bumped only on successful adoption, a fresh start manifest is captured, and finalize stamps `session_ownership: "adopted"` plus `adopted_from_orphan_at`. +- The v0.4 four-layer concurrency model is preserved verbatim. Layer 3 is selectively skipped on reattach paths because no reliable end_manifest baseline exists from a previous orphaned owner. +- Provisional cleanup is bypassed in persistent mode (`shouldRunProvisional` returns false on `SessionMode == "persistent"`) — the gate's UX assumption ("Esc on prompt → empty diff → reclaim") does not translate to tmux. +- `last-session-summary.json` gains four optional fields (`end_reason`, `detected_via`, `session_ownership`, `adopted_from_orphan_at`); all `omitempty` so pre-v0.5.3 summaries continue to round-trip. +- Native Windows refuses persistent mode with a WSL recommendation. The check is `runtime.GOOS == "windows" && os.Getenv("WSL_DISTRO_NAME") == ""`. WSL sets `WSL_DISTRO_NAME` automatically, so WSL paths pass. macOS / Linux pass naturally. +- `ctask new` runs the persistent preflight BEFORE `workspace.Create` — a missing tmux must not leave a half-initialized workspace on disk. + +Validation: + +- Windows host: `go test ./... -count=1` green across all 7 packages; `go vet` clean; `go build` clean; `just build-linux` produces a statically linked ELF. +- Manual WSL smoke test (`docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md`): completed end-to-end on 2026-05-14 across three terminals (WSL-A, WSL-B, PS-C). All sections PASS: owner-create (O1-O3), passive reattach (P1-P2), adopted reattach (A1-A6), non-TTY refusal (T1), nested-tmux refusal, --direct confirmation (D1-D4), tmux-missing workspace-not-created refusal (M1-M4), native Windows refusal (10a-10f), doctor output (11), cleanup (12). T2 (ssh-without-t) skipped — sshd not running on the WSL distro (explicitly allowed by checklist). +- The checklist itself was hardened during the smoke run (10 corrections); see commit `c204d87` for the diff. + +Out of scope (deferred): + +- Native Windows persistent mode (PSmux is a candidate; not committed). +- Config file (`~/.config/ctask/config.yaml`) — env var remains the only config surface until v0.6. +- `switch-client` for nested-tmux entry, `tmux wait-for` / `set-hook`-based detection, banner injection inside tmux, `ctask sessions` listing command. + +### Known limitation (v0.5.3) + +- **Refusal/bypass hints reflect `basename(os.Args[0])` for the command-form line, but descriptive prose ("ctask persistent mode requires...") and the ssh-remote hint stay hardcoded as "ctask".** Intentional — descriptive prose refers to program identity, and the ssh-remote `ctask` runs on the remote, not the local binary. **v0.5.4 audit confirmed this split is the right line** (spec §2 codified it: command-form via `invocationName()`, product-identity literal). Closed. +- **Adoption requires waiting 60s for the previous owner's lease to go stale (`StaleLeaseAfter`).** A user who Ctrl-C's the foreground `ctask` and immediately re-runs `ctask resume` hits the v0.4 Layer-1 prompt instead of the adoption path. Acceptable but lazy-cleanup-unfriendly; deferred to v0.6 Phase 2 (see follow-ups). Not in v0.6 Phase 1 scope. + +### What v0.5.4 delivered + +Theme: **session visibility + polish**. Pure polish — no new subsystems, no new metadata fields, no behavioral changes to session lifecycle. Merge commit `10b7d5a`. + +Commit list: + +- `7f2c43d` `feat(v0.5.4): SessionStatus display-only helper` — new `internal/session.SessionStatus(wsDir)` returning `{State, Mode, PID, Hostname, Diagnostic}`. Pure read of `.ctask/session.json` with no tmux invocation, no PID liveness, no lock acquisition, no mutation. States: `none` | `active` | `stale`. Pre-v0.5.3 leases without a `mode` field default to `direct` for display only. Display-only contract — lifecycle code keeps using `ReadLease`/`IsFresh`/`InspectLease`. +- `e0e9cd7` `feat(v0.5.4): info Session block` — `ctask info` adds a Session block between `Path:` and the launch-dir fields. Mode/Owner/Attach/Note rows aligned at column 14. Owner line omits hostname when it matches the local machine. Attach hint surfaces only for active+persistent and uses `invocationName()`. Malformed leases render as stale with a diagnostic and no Mode/Owner/Attach rows. Exposes `session.CurrentHostname()` so the cmd layer has a single source of truth for the local-vs-remote check. +- `0c8076a` `feat(v0.5.4): list SESSION column` — `ctask list` gets a SESSION column inserted right of STATUS. Values: `direct`, `persistent`, `stale`, or em dash. Archived workspaces always show em dash (display simplification — `info` still surfaces the raw lease state for diagnostic purposes). One short lease-file read per workspace; `ctask list --names` is **unchanged** (basename-only, no header, no session column — protected by `TestListNamesUnchangedHasNoSessionColumn`). +- `0fb8de6` `polish(v0.5.4): invocation-name audit + regression tests` — audit walked every `cmd/` and `internal/` file producing user-facing text. **No production code changes were needed** — v0.5.3's `c204d87` had already routed every command-form hint through `invocationName()`. Two new regression tests pin a non-canonical name (`my-bin`) for the resume restore hint and the Layer-1 active-session attach hint, so a future change that hard-codes "ctask" in the format string fails loudly. Extracted `formatResumeRestoreHint` and `formatDirectModeTmuxHint` as testable string-only helpers; production paths are behaviorally identical. +- `4fd0bef` `docs(v0.5.4): rewrite commands.md as a structured reference` — `docs/commands.md` was last substantively updated in v0.5; v0.5.2 (restore/notes/path/list --names/completion + archive-inclusive lookup), v0.5.3 (attach + --direct + persistent session mode), and v0.5.4 (info Session block + list SESSION column) were all undocumented. Rewrite follows spec §3 (Purpose / Usage / Scenarios / Examples / Flags / Notes / Related per command) with new sections for workspace layout, env-vars table, query resolution, session modes, and shell completion. Examples use the canonical `ctask` name per spec — docs describe the product, not the user's local binary path. +- `ae9bfaf` `polish(v0.5.4): suppress Cobra duplicate Error on archived resume` — closes the v0.5.2 cosmetic-only known limitation. New `errArchivedWorkspace` sentinel; `runResume` flips `SilenceErrors=true` only when the inner error is that sentinel. All other resume errors (lookup failure, metadata write failure, etc.) flow through Cobra's default rendering unchanged. Verified end-to-end through Cobra `Execute()` (not just `runResume` direct invocation) so the SilenceErrors-flip-from-RunE timing is exercised by `TestResumeArchivedHintNoDuplicateError`. +- `7704cd9` `release(v0.5.4): bump version to 0.5.4`. +- `10b7d5a` `Merge branch 'feat/v0.5.4-session-visibility-polish' into main`. + +Validation: + +- Windows host: `go test ./... -count=1` green across all 7 packages; `go vet ./...` clean; `just build` produces `ctask.exe`; `just build-linux` produces a statically linked ELF (`file` reports `statically linked, not stripped`); `just install` succeeded; installed `ctask --version` reports `0.5.4`; `ctask doctor` 5/5 pass. +- Smoke (synthetic fixtures): `ctask list` shows SESSION column with `persistent` / `direct` / `stale` / em dash; `ctask list --all` shows em dash for archived even with stranded lease; `ctask list --names` remains basename-only with no whitespace and no session tokens; `ctask info ` shows the Session block with hostname omitted (local) and Attach hint via `invocationName()`; `ctask resume ` prints only the `[ctask]` block + restore hint (no trailing Cobra `Error:` line). +- No manual WSL smoke needed — v0.5.4 changes are display-only and exercised by unit tests against real lease fixture files. + +Spec deviations (intentional, not bugs): + +- **The v0.5.4 spec referred to `.ctask/lease.json` and a `session_mode` field; the actual existing implementation uses `.ctask/session.json` and `mode`.** The implementation correctly followed the existing metadata names per the spec's "no new metadata fields, no behavioral changes" constraint — renaming would have been a v0.5.3 schema change masquerading as polish. Future specs touching this surface should use `.ctask/session.json` and `mode` unless intentionally renaming the metadata. +- **The invocation-name audit found no production hardcoded command-form hints to change.** v0.5.3's `c204d87` had already done the work. v0.5.4 added two regression tests (`TestInvocationNameInActiveSessionPrompt`, `TestInvocationNameInRestoreHintNonCanonical`) that pin a non-canonical invocation name to prevent future drift. The split between command-form (uses `invocationName()`) and product-identity (literal `"ctask"`) is the codified rule per spec §2. + +### Next: v0.6 Phase 1 + +v0.6 is the **last specced direction** but the spec splits into two phases. **Only Phase 1 is in scope for the next branch.** Phase 2 (agent profiles, AGENTS.md / context-file templates, PID liveness, lazy-cleanup adoption changes) is explicitly out of scope until Phase 1 is implemented, tested, and reviewed. + +**Phase 1 scope (only thing to start next):** + +- **Config file parsing + resolver + tests.** Read `~/.config/ctask/config.yaml` (XDG) and Windows equivalent. Layered resolution: env var > config file > built-in default. Surface every existing `CTASK_*` setting through the new resolver without changing default behavior. Tests cover layering, malformed YAML, missing file, env-var override. +- **`schema_version` field in `task.yaml`.** Bump to `1`. Old workspaces (no `schema_version`) parse silently and are treated as version 0 (current behavior). New `ctask new` writes version 1. +- **`workspace.mode` field in `task.yaml`.** Replaces the implicit "always `local`" assumption. New workspaces default to `local`; reading is forward-compatible. No behavioral consequence yet — purely metadata. +- **Doctor source attribution.** When reporting a configuration value, doctor says where it came from: `[INFO] CTASK_ROOT: /home/warren/ai-workspaces (env)` vs `(config: ~/.config/ctask/config.yaml)` vs `(default)`. Three-state visibility for every resolved setting. +- **Info source attribution.** `ctask info ` includes the source for relevant resolved values (e.g., agent override path). + +**Phase 1 do-not-touch (deferred to Phase 2):** + +- Agent profiles (`profiles:` block in config). +- AGENTS.md / handoff.md / context-file templates / `context/notes-archive/` scaffolding (the design recorded in `v0.5.4-spec.md` § "v0.6 Context-File Architecture" is for Phase 2 — read it then, not now). +- PID liveness check on leases. v0.5.4 spec §1 explicitly defers this so it's only built once with behavioral consequences. +- Lazy-cleanup-friendly adoption (the 60s `StaleLeaseAfter` revisit). +- `orphaned` session state detection in display. +- Flag-aware completion for `open --all` / `delete --all`. + +The scope split exists because Phase 1 is foundation (resolver, schema versioning, source visibility) that Phase 2 features will build on. Implementing them together is what makes v0.6 stall. ## Post-v0.4 bugfixes (still live, carried forward) @@ -132,55 +215,34 @@ Covered in v0.4.1 notes. The exit-code gate (`childExitCode != 0 && startManifes ## Tree state at pause -- `main` clean with respect to v0.4, v0.4.1, v0.5, v0.5.1, v0.5.2. Latest tip is `e448eff docs(v0.5.2): record v0.5.2 completion in notes.md`. -- HEAD is on `feat/v0.5.3-persistent-session-mode` (20 commits ahead of main, 0 behind). Branch was created cleanly from `main` at the start of v0.5.3 work. -- Installed `ctask.exe` is **v0.5.2** — DO NOT reinstall yet. Wait until v0.5.3 has passed manual smoke + been merged. -- Untracked files (do NOT touch without asking): +- `main` clean with respect to v0.4, v0.4.1, v0.5, v0.5.1, v0.5.2, v0.5.3, v0.5.4. Latest tip is the merge commit `10b7d5a Merge branch 'feat/v0.5.4-session-visibility-polish' into main`; the v0.5.4 version-bump commit `7704cd9` sits on the merged-in branch tip. +- No tag pushed for v0.5.4 (no remote — the project is intentionally local-only per `CLAUDE.md`). v0.5.3 had `git tag v0.5.3` locally; v0.5.4 has none. Add later if a publication path opens up. +- No active branches. `feat/v0.5.4-session-visibility-polish` was deleted post-merge. +- Installed `ctask.exe` at `%LOCALAPPDATA%\ctask\bin\ctask.exe` is **v0.5.4** (refreshed via `just install` after merge). `dist/ctask-linux-amd64` is the v0.5.4 Linux cross-build (statically linked ELF, 7.5 MiB). +- Memory follow-ups (still live from v0.5.3, both relevant to v0.6 Phase 2 — see `memory/MEMORY.md`): + - `feedback_design_for_lazy_cleanup` — drives v0.6 Phase 2 work on the 60s freshness wait + PID liveness. + - `feedback_invocation_name_in_hints` — partially closed by the v0.5.4 audit (split between command-form and product-identity is now codified). Memory entry retained for the descriptive-prose question, which Phase 2 may revisit. +- Untracked files (do NOT touch without asking — pre-existing session-local working docs, unchanged from this session): - `.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`, `v0.5.3-spec.md` (specs the implementations followed) -- Files committed ON the v0.5.3 branch (already tracked, not in the untracked list above): - - `docs/superpowers/plans/2026-05-08-v0.5.3-implementation.md` — the executed plan - - `docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-log.md` — automated portions of Task 17 (cross-compile, version, native-Windows refusal, doctor on both platforms — all PASS) - - `docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md` — manual checklist v2 for the user to run + - `docs/superpowers/plans/2026-05-08-v0.5.3-implementation.md` (v0.5.3 plan — executed; was claimed-committed in earlier notes but actually stayed untracked) + - `v0.4-spec.md`, `v0.4.1-patch-spec.md`, `v0.5-spec.md`, `v0.5.3-spec.md`, `v0.5.4-spec.md` (specs the implementations followed; `v0.5.4-spec.md` is the spec the just-shipped work was driven from) +- Files committed on the v0.5.4 branch that are now on `main`: + - `internal/session/status.go` + `status_test.go` (SessionStatus helper) + - `cmd/info_session_test.go`, `cmd/list_session_test.go` (session-block / session-column behavioral tests) + - `cmd/invocation_audit_test.go` (regression tests pinning a non-canonical invocation name) + - `cmd/resume_archived_polish_test.go` (Cobra-end-to-end test that the duplicate `Error:` line is suppressed) + - `docs/commands.md` (rewrite — see commit `4fd0bef`) ## How to resume -### Completing the v0.5.3 ship (current pending action) +v0.5.4 is shipped; no pending follow-through. Next horizon is v0.6 Phase 1 only — see "Next: v0.6 Phase 1" above. **Do not begin Phase 2 work** (agent profiles, AGENTS.md, PID liveness, lazy-cleanup adoption) until Phase 1 is implemented, tested, and reviewed. -The v0.5.3 branch is ready — only manual WSL smoke verification stands between -it and `main`. - -```powershell -cd C:\Users\Warren\claude_tasks\ctask -git checkout feat/v0.5.3-persistent-session-mode -just test # all green on Windows -just build-linux # produces dist/ctask-linux-amd64 (static ELF) -``` - -Then the user runs through `docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md` -in WSL (three terminals: WSL-A, WSL-B, PS-C) and reports PASS/FAIL per section. - -After all-PASS: - -```powershell -git checkout main -git merge --no-ff feat/v0.5.3-persistent-session-mode -just install # refresh installed binary to v0.5.3 -ctask --version # expect: ctask v0.5.3 -git branch -d feat/v0.5.3-persistent-session-mode -git tag v0.5.3 # optional -``` - -If anything fails: capture the exact output (especially around the -adopted-reattach summary fields and the tmux session-name hash) and feed -it back to the next session. - -### General resume (when on main after a ship) +### General resume (on `main` after a ship) ```powershell cd C:\Users\Warren\claude_tasks\ctask @@ -189,29 +251,46 @@ just build # or: go build -o ctask.exe . just install # only reinstall if source changed ``` -Quick sanity checks that v0.5.1 is live: +Quick sanity checks that v0.5.4 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 +ctask --version # expect: ctask v0.5.4 +ctask doctor # expect: 5 pass/fail + 2 seed-dir INFO + 1 CTASK_PROJECT_ROOT INFO + 1 Session mode INFO -# Local-time directory prefix (v0.5.1): +# v0.5.4 surfaces: +ctask list # SESSION column inserted right of STATUS +ctask list --names # bare basenames, no header, no session column +ctask info # Session block between Path: and (if any) launch fields + +# Direct mode (the default — no env var needed): 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 +# Expected output: [ctask] created projects/YYYY-MM-DD_tz-check +ctask info tz-check # info should show Session: none for the just-created ws 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 +# Persistent mode sanity (v0.5.3, requires tmux — WSL only): +# (WSL terminal) +export CTASK_SESSION_MODE=persistent +ctask doctor 2>&1 | grep -E "Session mode|tmux" +# Expect: +# [INFO] Session mode: persistent +# [INFO] tmux found: tmux (/path/to/tmux) +unset CTASK_SESSION_MODE +``` -# Default discovery (v0.5): -# Projects created without $CTASK_PROJECT_ROOT should now be findable from any shell. -ctask list --projects +### Starting v0.6 Phase 1 + +Suggested opening session prompt (paste into a fresh ctask agent session on `main`): + +> Implement v0.6 Phase 1 only, per the scope listed in `notes.md` ("Next: v0.6 Phase 1"): config file parser + resolver, `schema_version` and `workspace.mode` fields in `task.yaml`, source attribution in `ctask doctor` and `ctask info`. Start with the brainstorming skill to lock the config file format (XDG path, key names, layering rules). Do not start Phase 2 work (agent profiles, AGENTS.md, PID liveness, lazy-cleanup adoption) — those are explicitly deferred. + +Branch creation (when ready to start coding): + +```powershell +cd C:\Users\Warren\claude_tasks\ctask +git checkout main +git pull # no-op (no remote), but cheap habit +git checkout -b feat/v0.6-phase1-config-and-schema ``` ## Load-bearing design points (don't forget) @@ -275,7 +354,7 @@ ctask list --projects - **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. -### From v0.5.3 (new — don't unlearn) [pending merge to main] +### From v0.5.3 (new — don't unlearn) - **`CTASK_SESSION_MODE` is the only persistent-mode trigger.** No flag promotes a single command to persistent. `ctask attach` is the inverse — it always uses tmux regardless of env. Don't add a `--persistent` flag; the existing `direct` ↔ `persistent` ↔ `attach` triangle covers every use case. - **tmux command construction lives in exactly one place per operation** — `internal/shell/tmux.go`. `AttachExisting` and `AdoptExistingPersistentSession` use shell primitives via test seams (`adoptAttacher`, `adoptPoll`, `attacher`); they do NOT hand-roll their own `exec.Command("tmux", ...)` calls. If you find a fresh `exec.Command("tmux", ...)` outside `internal/shell/tmux.go`, that's drift — fix it. @@ -291,7 +370,20 @@ ctask list --projects - **`ctask new` runs the persistent preflight BEFORE `workspace.Create`.** A missing tmux must not leave a half-initialized workspace on disk. The `cmd/new.go` ordering is load-bearing — don't move the preflight after creation. - **The `[ctask] adopting orphaned persistent session...` line is the discriminator** between passive reattach and adoption in user-visible output. Don't suppress it; the manual smoke test relies on it. -## Open follow-ups (NOT in v0.4/v0.4.1/v0.5, deferred) +### From v0.5.4 (new — don't unlearn) + +- **`internal/session.SessionStatus(wsDir)` is display-only.** Pure read of `.ctask/session.json`. No tmux invocation, no PID liveness, no lock acquisition, no mutation of lease state. It must NOT be called from lifecycle or adoption code: the "missing `mode` field defaults to `direct`" rule and the malformed-lease-as-stale classification are display choices, not behavioral truths. Lifecycle code keeps using `ReadLease` / `IsFresh` / `InspectLease`. The status.go file calls this out at the function declaration — keep that comment. +- **Lease file is `.ctask/session.json` and the mode field is `mode`.** The v0.5.4 spec called these `lease.json` and `session_mode` respectively; the implementation correctly preserved the existing v0.4/v0.5.3 names per the spec's "no new metadata fields" constraint. Future specs touching this surface should use the actual names unless intentionally renaming the metadata. +- **`session.CurrentHostname()` is the exported wrapper around `currentHostname()`.** Use it in cmd-layer hostname comparisons (e.g., info's "omit hostname when local" rule) so the unknown-fallback semantics stay consistent with the rest of the package. Don't call `os.Hostname()` directly in cmd code. +- **`ctask info` Session block alignment is at column 14 across all rows.** Top-level `Session:` (8 chars + 6 spaces) and indented sub-rows (` Mode:` / ` Owner:` / ` Last owner:` / ` Attach:` / ` Note:`) all align values at column 14. Malformed leases render only `Session: stale` + ` Note: ` — no Mode/Owner/Attach rows because we can't trust values from a broken file. +- **`ctask info` Session block uses `invocationName()` for the Attach hint, NOT `session.SessionStatus`.** SessionStatus stays neutral — the hint string is built in `cmd/info.go::printSessionBlock`. Keep that boundary: the cmd layer owns invocation-name rendering; the session package owns lease parsing. +- **`ctask list` SESSION column is between STATUS and TYPE.** Order: status, session, type, mode, category, date, slug. No header (consistent with the existing list format). Archived workspaces ALWAYS show em dash regardless of any lease file present — display simplification, not a lifecycle invariant. `info` still surfaces the raw state for archived workspaces because info is the diagnostic command. +- **`ctask list --names` is unchanged in v0.5.4.** Bare basenames, no header, no session column, no whitespace per line, empty stdout on no match. `TestListNamesUnchangedHasNoSessionColumn` enforces this — if you touch the list rendering path, this test must keep passing. +- **`errArchivedWorkspace` is a sentinel, not a generic error.** `runResume` flips `cmd.SilenceErrors=true` only when the inner error is this exact sentinel. All other resume errors continue to flow through Cobra's default rendering. Don't generalize the sentinel into "any error we already printed" — the reason this works is the precise scoping. Test: `TestResumeArchivedHintNoDuplicateError` exercises the full Cobra `Execute()` path. +- **`formatResumeRestoreHint` and `formatDirectModeTmuxHint` are testable string-only helpers extracted purely so the audit can pin format strings without simulating tmux or stderr capture.** They have no production callers other than the original sites. Don't inline them back into their callers — the regression tests rely on calling them directly. +- **Invocation-name rule is codified per spec §2: command-form hints use `invocationName()`, product-identity references stay literal `"ctask"`.** Examples of literal-`"ctask"` (intentional): `[ctask]` log/error prefix, `"ctask persistent mode requires tmux"` (descriptive prose), the SSH-remote `ssh -t ctask ` hint (the remote runs `ctask`, not the user's local binary), and Cobra `Use:` / `Long:` strings. The two regression tests in `cmd/invocation_audit_test.go` pin `my-bin` (not `ctask`) for the resume restore hint and Layer-1 attach hint specifically to detect format-string regressions that would silently work under `withInvocationName(t, "ctask")`. + +## Open follow-ups (deferred; not in any shipped v0.4–v0.5.4 work) ### Potentially worth doing @@ -308,42 +400,55 @@ ctask list --projects ### 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. +- Several untracked working docs (`v0.4-spec.md`, `v0.4.1-patch-spec.md`, `v0.5-spec.md`, `v0.5.3-spec.md`, `v0.5.4-spec.md`, `bugfix-provisional-workspace.md`, the implementation plans for v0.4 / v0.4.1 / v0.5 / v0.5.3, and the install-workflow plan) could be committed alongside `v0.2-spec.md`/`v0.3-spec.md` for durability. Currently session-local; per `notes.md`'s long-standing rule, leave alone unless explicitly asked. - `.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. +- ~~Duplicate Cobra `Error: workspace archived` line on `ctask resume `~~ → resolved in v0.5.4 (`ae9bfaf`) via `errArchivedWorkspace` sentinel + conditional `SilenceErrors`. +- ~~`info` / `list` show no session state — users have to check lease files manually~~ → resolved in v0.5.4 (`e0e9cd7`, `0c8076a`) via Session block + SESSION column derived from new `internal/session.SessionStatus` helper. +- ~~`docs/commands.md` missing v0.5.2/v0.5.3/v0.5.4 commands and surfaces~~ → resolved in v0.5.4 (`4fd0bef`) via structured rewrite. ## Files to read first when resuming +For the v0.5.4 surface (just-shipped): + +1. `v0.5.4-spec.md` — the spec v0.5.4 followed (note: spec mentions `lease.json` and `session_mode`; actual code uses `session.json` and `mode` — the implementation correctly preserved the existing names) +2. `internal/session/status.go` + `status_test.go` — `SessionStatus` display-only helper with the explicit no-tmux/no-PID/no-lock contract +3. `cmd/info.go::printSessionBlock` — Session block rendering with column-14 alignment + invocation-name Attach hint +4. `cmd/list.go::sessionColumn` — SESSION column with archived-as-em-dash rule +5. `cmd/resume.go` — `errArchivedWorkspace` sentinel + conditional `SilenceErrors` +6. `cmd/invocation_audit_test.go` — regression tests pinning a non-canonical name +7. `docs/commands.md` — rewritten v0.5.4 reference (Purpose / Usage / Scenarios / Examples / Flags / Notes / Related per command) + 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 +8. `v0.5-spec.md` — the spec v0.5 followed +9. `docs/superpowers/plans/2026-04-22-v0.5-implementation.md` — the executed plan (includes scanner-safety regression tests, dedupe tests, amendment history) +10. `internal/workspace/launchdir.go` — `ResolveLaunch` with the three-outcome contract +11. `internal/workspace/create.go` — subdir scaffold + local-time date + `launch_dir` default +12. `internal/session/run.go` — `LaunchOpts.LaunchDir`, `ResolveLaunch` call site, banner extension +13. `cmd/info.go` — local-time display + launch fields (Session block sits between Path and these) +14. `cmd/doctor.go` — `checkProjectRoot` helper +15. `scripts/ctask-statusline.{sh,ps1}` — effective-path display logic +16. `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` +17. `internal/config/config.go` — `SearchRoots()`, `samePath`, `searchRootKey` dedup; default `$CTASK_ROOT/projects/` fallback +18. `internal/workspace/query.go` — two-depth `scanWorkspaces`, `scanAllRoots`, `QueryResult.Root` +19. `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 +20. `internal/session/run_preflight.go` — Layer 3 + Layer 1 preflight +21. `internal/session/lease.go` — Lease + freshness + cleanup +22. `internal/session/summary.go` — SessionSummary + launch-context banner +23. `internal/lockfile/writelock.go` — write-lock primitive +24. `internal/session/run_provisional.go` — `handleProvisional` with `childExitCode` gate +25. `docs/commands.md` — user-facing surface (rewritten in v0.5.4 — see entry #7 above) ## Don't re-do @@ -371,27 +476,11 @@ For the v0.4 surface: - **v0.5.3:** Do not silently overwrite a fresh remote lease. `confirmFreshRemoteAdoption` prompts on TTY and refuses on non-TTY. The non-TTY refusal carries the remote hostname so the user can disambiguate. - **v0.5.3:** Do not run the persistent preflight after `workspace.Create` in `cmd/new.go`. Pre-create ordering prevents half-initialized workspaces when tmux is missing. - **v0.5.3:** Do not change the `SessionName` algorithm. Name stability across processes is what makes passive reattach work without state. Windows path lowercasing matches `searchRootKey`. +- **v0.5.4:** Do not call `session.SessionStatus` from lifecycle or adoption code. It is display-only — the `mode`-defaults-to-`direct` rule and the malformed-as-stale classification are wrong for behavioral logic. Use `ReadLease`/`IsFresh`/`InspectLease` for lifecycle decisions. +- **v0.5.4:** Do not invoke tmux, check PID liveness, or acquire locks inside `SessionStatus` or its callers (`info` Session block, `list` SESSION column). PID liveness in particular is deferred to v0.6 Phase 2 where it has behavioral consequences (changing adoption thresholds) — building it display-only now would mean building it twice. +- **v0.5.4:** Do not add a session column or header to `ctask list --names` output. Machine-readable bare-basename-per-line is the contract. `TestListNamesUnchangedHasNoSessionColumn` enforces the invariant. +- **v0.5.4:** Do not collapse archived workspaces' SESSION column to anything other than the em dash in `list`. The display simplification is intentional. `info` is the diagnostic command — it still shows the raw lease state for archived workspaces if a lease file exists. +- **v0.5.4:** Do not generalize the `errArchivedWorkspace` sentinel into "any pre-printed error". The Cobra `SilenceErrors` flip is intentionally scoped to a single error path so unrelated resume errors keep getting Cobra's default rendering. +- **v0.5.4:** Do not move the Attach-hint string construction into `session.SessionStatus`. The cmd layer owns invocation-name rendering; the session package owns lease parsing. The boundary is what keeps SessionStatus testable without a `withInvocationName` seam. +- **v0.5.4:** Do not re-introduce a hardcoded `"ctask"` in command-form hints. Use `invocationName()`. The regression tests pin a non-canonical name specifically to catch this. -## What v0.5.3 delivered - -Persistent session mode is in. Key user-facing surfaces: - -- New env var `CTASK_SESSION_MODE` (`direct` | `persistent`); `direct` is the default and requires no setup. -- `ctask attach ` — always-tmux entry command. Defaults to launching the agent. -- `--direct` flag on `new` / `resume` / `last` / `open` to bypass persistent mode for one invocation, with confirmation when a tmux session already exists. -- `ctask doctor` now reports tmux presence and version when persistent mode is configured. - -Architecture notes: - -- tmux is invoked via a three-call pattern (`has-session`, `new-session -d`, `attach-session`) with a 3-second polling loop to detect session end. The polling cadence is below the 30-second heartbeat interval, so finalize lag is bounded. -- Session names are deterministic: `ctask---`, where the hash is the first 6 hex chars of `sha256(canonical absolute workspace path)`. On Windows the path is lowercased before hashing. -- Three entry paths (owner-create, passive reattach, adopted reattach) are picked based on tmux session existence and `InspectLease` four-state classification. -- Adoption transfers ownership under the metadata write lock with a re-check race guard. The previous lease is replaced, `task.yaml.UpdatedAt` is bumped, a fresh start manifest is captured, and finalize stamps `session_ownership: "adopted"` plus `adopted_from_orphan_at`. -- The v0.4 four-layer concurrency model is preserved verbatim. Layer 3 is selectively skipped on reattach paths because no reliable end_manifest baseline exists from a previous orphaned owner. -- Provisional cleanup is bypassed in persistent mode — the gate's UX assumption ("Esc on prompt -> empty diff") does not translate to tmux. -- `last-session-summary.json` gains four optional fields (`end_reason`, `detected_via`, `session_ownership`, `adopted_from_orphan_at`); pre-v0.5.3 summaries continue to load. - -Out of scope (deferred to future releases): -- Native Windows persistent mode (PSmux is a candidate; not committed). -- Config file (`~/.config/ctask/config.yaml`) — env var remains the only config surface until v0.6. -- `switch-client` for nested-tmux entry, `tmux wait-for` / `set-hook`-based detection, banner injection inside tmux, `ctask sessions` listing command.