From 8304545840d69eabca54cfad6f09ced2e8ff8175 Mon Sep 17 00:00:00 2001 From: typebasedio Date: Thu, 14 May 2026 20:22:06 -0400 Subject: [PATCH] docs(v0.5.4): session handoff in notes.md (ship report + v0.6 Phase 1 scope) Bump notes.md to anchor on v0.5.4: - Top stanza + "Where we are": v0.5.4 shipped on main at merge commit 10b7d5a, installed binary reports v0.5.4. - Insert "What v0.5.4 delivered" with the 8-commit list and validation results. - Insert "From v0.5.4 (new -- don't unlearn)" load-bearing design points covering SessionStatus's display-only contract, the session.json/mode naming preserved from v0.5.3, the column-14 alignment in info, the SESSION column / list --names invariant, and the errArchivedWorkspace sentinel scoping. - Append v0.5.4 Don't re-do entries (display-only SessionStatus, no PID liveness in the helper, --names contract, archived em-dash, sentinel scoping, cmd-vs-session boundary, no hardcoded "ctask"). - Replace "Next: v0.6 (planning)" with "Next: v0.6 Phase 1" listing config file + schema_version + workspace.mode + source attribution in doctor/info -- explicitly noting Phase 2 (agent profiles, AGENTS.md, PID liveness, lazy-cleanup adoption) is out of scope until Phase 1 is implemented and reviewed. - Update Tree state, How to resume, Files to read first. - Resolve closed limitations: v0.5.2 duplicate Cobra Error line, the v0.5.3 invocation-name asymmetry (audit codified the split as the right line), info/list session-state visibility, docs/commands.md staleness. - Record spec deviations: v0.5.4 spec referred to .ctask/lease.json and session_mode but the actual code uses session.json and mode. Implementation correctly preserved the existing names per the spec's "no new metadata fields" constraint. Future specs touching this surface should use the actual names. --- notes.md | 309 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 199 insertions(+), 110 deletions(-) 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.