Files
ctask/notes.md
T
typebasedio a5e508bcb6 docs(v0.5.1): record v0.5.1 completion; check in v0.5.2 spec
Update notes.md with the v0.5.1 Linux portability baseline (commits
7a7b249, 1033072): WSL-native validation passed, install.sh works,
ctask doctor recognizes the Linux statusline helper, the cross-built
Linux binary is statically linked, and WorkspacePath was removed from
new task.yaml metadata. Add load-bearing notes for the new invariants
(CGO_ENABLED=0, install script does not modify shell config) and a
"Next: v0.5.2" pointer.

Also check in v0.5.2-spec.md so the workspace-retrieval round has the
same on-disk durability as the prior specs.
2026-05-07 19:47:01 -04:00

23 KiB

ctask — Session Handoff Notes

Last touched: 2026-05-07 (after v0.5.1 Linux baseline merge). Pause before starting v0.5.2.

Where we are

v0.5.1 is shipped on main and installed locally. v0.5 added nested project structure (project subdir scaffolding, launch_dir-driven cd into the subdir, default discovery including $CTASK_ROOT/projects/). v0.5.1 is a tiny follow-up that fixes a UTC-date confusion surfaced during v0.5 smoke testing.

  • Version string: v0.5.1 (see cmd/root.go)
  • Branch: main
  • Remote: none (local-only, intentional — see CLAUDE.md)
  • Tests: all pass across 7 packages (go test ./... -count=1)
  • go vet ./... clean, go build ./... clean
  • Installed at %LOCALAPPDATA%\ctask\bin\ctask.exe via just install — reflects both v0.5 and v0.5.1
  • ctask doctor now reports 5 pass/fail checks + 2 seed-directory checks + 1 CTASK_PROJECT_ROOT check (all three-state)

What v0.4 delivered (still true, unchanged)

Workspace concurrency protection — session lease with heartbeat (Layer 1), metadata write lock (Layer 2), stale-workspace detection (Layer 3), session handoff summary (Layer 4). --force flag on resume/last/open. WriteMetaLocked, LaunchOpts.Force, Preflight. All unchanged in v0.5.

What v0.4.1 delivered (still true, unchanged)

Correctness and polish: config.SearchRoots() multi-root discovery; two-depth scanWorkspaces (flat + category, first-match-wins); nested-git documentation; doctor seed-dir three-state checks; archive active-session warning with non-TTY refuse; provisional-cleanup exit-code gate. Still load-bearing — see "From v0.4.1" section below.

What v0.5 delivered

Theme: separate workspace management from project code. --project now scaffolds a project subdirectory and session commands cd into it. Twelve commits on main (175fbb0..8130a68).

  1. launch_dir field in TaskMeta (175fbb0)
    • New LaunchDir string with yaml:"launch_dir,omitempty" — empty for tasks and pre-v0.5 projects, defaults to the project slug for new projects.
    • Omitempty keeps on-disk task.yaml clean for backward compatibility.
  2. workspace.ResolveLaunch helper (dcb1610)
    • Converts a relative launch_dir into an absolute child-cwd with three outcomes:
      • Valid dir → return absolute path, no warning, no error.
      • Missing path (os.IsNotExist) or path-is-a-file → return (wsDir, warning, nil) so the caller warns and falls back.
      • Absolute path, ..-escape, or any non-IsNotExist stat error (permission, ENOTDIR, invalid name) → return ("", "", error) — caller must surface.
    • This asymmetry is deliberate — masking a permission error as a warning fallback would silently strip launch-dir semantics.
  3. Project subdirectory scaffolding (7cfafdc)
    • ctask new --project creates an empty subdir named after the final suffixed slug (so dup-2 collisions produce matching dup-2/ subdir).
    • ctask does not seed anything inside the subdir — the user places their own CLAUDE.md, source code, project configuration.
    • Task workspaces are completely unchanged — no subdir, no launch_dir.
  4. CTASK_LAUNCH_DIR env var (509a6d6)
    • Added as a 7th arg to config.EnvVars. All three cmd callers (new, resume, open) pass ws.Meta.LaunchDir.
  5. Session launch routed through LaunchDir (103f2cd)
    • LaunchOpts.LaunchDir added. session.Run calls ResolveLaunch before banner/exec, prints any warning to stderr, aborts on security error, and passes the absolute path to shell.ExecAgent / shell.ExecShell as the child's working directory.
    • Banner gains a [ctask] project dir: <name>/ line when launch_dir is set.
    • Lease, manifest, heartbeat, and summary scope stays the workspace root — only the child's cwd changes.
  6. ctask info shows launch fields (cdff7f3)
    • For workspaces with launch_dir set, info prints Launch dir:, Launch path:, and Dir exists: yes|no (via direct os.Stat, not via ResolveLaunch — info is a display command, not a launch command).
  7. SearchRoots default fallback (47430a1)
    • When CTASK_PROJECT_ROOT is unset, SearchRoots() now appends $CTASK_ROOT/projects/ so default-location projects are findable from any shell (resolves the v0.4.1 env-var scoping footgun from the previous follow-up list).
    • Dedupe in scanAllRoots prevents duplicate results when the same workspace is reachable via both the depth-2 scan under CTASK_ROOT and the explicit $CTASK_ROOT/projects/ search root.
  8. Doctor CTASK_PROJECT_ROOT check (70bd167)
    • Three-state, matching the checkSeedDir pattern: [INFO] when unset (points at $CTASK_ROOT/projects/), [INFO] with user-scope advisory when set+exists, [FAIL] when set but missing. Only FAIL increments the failure counter.
    • Wording is advisory ("recommended: set at user scope…"), not prescriptive.
  9. Project CLAUDE.md template rewrite (cdf1c55)
    • Adds a "Workspace Structure" section explicitly describing the root-vs-subdir split.
    • Keeps the "Git" section from v0.4.1, now with an explicit mention that the project subdir is tracked by the root repo.
  10. Status line helpers show effective launch path (0976dce)
    • Both .sh and .ps1 build DISPLAY_PATH = $CTASK_WORKSPACE + (CTASK_LAUNCH_DIR if set). When CTASK_LAUNCH_DIR != CTASK_TASK, the tag becomes |project:<launch_dir> (user-overridden launch directory).
  11. Docs updated (82c9445)
    • docs/commands.md: workspace-layout diagram, launch_dir semantics (default, override, fallback vs error), CTASK_LAUNCH_DIR env var, doctor example with new INFO line, Query Resolution default-discovery paragraph.
  12. Version bump 0.4.10.5.0 (8130a68).

What v0.5.1 delivered

Two rounds shipped under the v0.5.1 tag: wall-clock date for user-facing surfaces (a162aec, a11d48b) and Linux portability baseline (7a7b249, 1033072).

Round 1 — date fix (a162aec, a11d48b)

  • Bug: workspace directory names used UTC date, so at 20:22 EDT on April 22 a new workspace was named 2026-04-23_foo. Confusing in file explorer, ctask list, and ctask info.
  • Fix: internal/workspace/create.go now uses time.Now() (local) for the directory-prefix date and the YYYYMMDD-HHMMSS ID. cmd/info.go formats Created / Updated / Archived with .Local().
  • Stored timestamps still UTC. task.yaml CreatedAt / UpdatedAt / ArchivedAt, session logs, lease, manifest, and summary all continue to store UTC. Only user-facing surfaces (directory prefix + info display) switched.
  • Regression guards: TestCreateDirectoryPrefixUsesLocalDate and TestInfoFormatsTimestampsInLocalZone enforce both invariants.

Round 2 — Linux portability baseline (7a7b249, 1033072)

  • Build targets (justfile): build-linux, build-windows, build-all output to dist/. Both cross-targets force CGO_ENABLED=0 so the artifact is pure-Go statically linked regardless of build host (a native Linux build otherwise defaults to CGO_ENABLED=1 and links against host glibc).
  • POSIX install scripts (scripts/install.sh, scripts/uninstall.sh): mirror the .ps1 UX. Default install dir ~/.local/bin, optional override arg, PATH check warns with the right shell snippet (zsh / bash / fallback) — the script does NOT modify shell config. Statusline helper ctask-statusline.sh ships alongside the binary; the .ps1 helper is intentionally not installed on Linux. Workspace data is preserved on uninstall.
  • .gitignore now covers ctask, ctask-*, dist/ (in addition to *.exe).
  • WorkspacePath removed from TaskMeta. The audit (audit-report.md) confirmed the field was write-only with no production readers — it persisted an absolute Windows path into task.yaml that would have been misleading on cross-OS shares. Existing task.yaml files with workspace_path continue to load (Go's YAML unmarshal silently ignores unknown fields). TestMetaTypeMissingDefaultsToTask keeps the legacy YAML literal in its fixture as a backward-compat regression guard.
  • Validation: Windows go test ./... green across all 7 packages; cross-compile produces ELF x86-64. WSL-native validation passed: go test ./... -count=1 green; just build-linux produces a statically linked ELF (ldd reports not a dynamic executable); ./scripts/install.sh installs to ~/.local/bin/ctask and registers the statusline helper; ctask doctor recognizes the Linux statusline helper.

Next: v0.5.2

Workspace retrieval and cross-workspace context (spec at v0.5.2-spec.md):

  • ctask restore <workspace> — un-archive (metadata-only)
  • ctask notes <workspace> — print another workspace's notes.md to stdout (raw, agent-consumable)
  • ctask path <workspace> — print the absolute filesystem path
  • ctask info <workspace> — archived-inclusive by default; drop the --all flag
  • ctask resume <archived-workspace> — fail with a restore hint instead of generic "not found"
  • ctask list --names — machine-readable enumeration of workspace directory basenames
  • ctask completion {bash,zsh,fish,powershell} — Cobra-generated, with ValidArgsFunction hooks per command
  • Seed CLAUDE.md gets a concise cross-workspace context section

Lookup policy: listing is filtered, direct lookup is comprehensive. Active-only (resume, archive); archived-inclusive by default (info, notes, path, restore); current --all/-a semantics preserved on delete and open.

Post-v0.4 bugfixes (still live, carried forward)

Provisional-workspace cleanup (2026-04-22, commits 02dcdcc, ba8b3a1)

Covered in v0.4.1 notes. The exit-code gate (childExitCode != 0 && startManifest != nil && emptyDiff && NewlyCreated) is unchanged in v0.5.

Tree state at pause

  • main clean with respect to v0.4, v0.4.1, v0.5, v0.5.1.
  • Installed ctask.exe is v0.5.1 — no reinstall needed unless source changes.
  • Untracked files (do NOT touch without asking):
    • .claude/settings.local.json (modified — Claude Code local settings)
    • bugfix-provisional-workspace.md (spec for the 2026-04-22 initial provisional fix; may be deleted or archived)
    • docs/superpowers/plans/2026-04-06-install-workflow.md (install-workflow plan from an earlier session)
    • docs/superpowers/plans/2026-04-21-v0.4-implementation.md (v0.4 plan — executed)
    • docs/superpowers/plans/2026-04-22-v0.4.1-patch.md (v0.4.1 plan — executed)
    • docs/superpowers/plans/2026-04-22-v0.5-implementation.md (v0.5 plan — executed)
    • v0.4-spec.md, v0.4.1-patch-spec.md, v0.5-spec.md (specs the implementations followed)
    • v0.5.2-spec.md (next-round spec, in progress)

How to resume

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:

ctask --version                # expect: ctask v0.5.1
ctask doctor                   # expect: 5 pass/fail + 2 seed-dir INFO + 1 CTASK_PROJECT_ROOT INFO

# Local-time directory prefix (v0.5.1):
ctask new --project "tz-check" --no-launch
# Expected output: [ctask] created projects/YYYY-MM-DD_tz-check — YYYY-MM-DD matches
# the user's local wall-clock date, not UTC.
ctask info tz-check            # Created/Updated in local zone
ctask delete --force tz-check

# Project subdir + banner (v0.5):
ctask new --project "smoke" --no-launch
# A subdir named smoke/ exists inside the workspace.
ctask resume smoke             # banner shows "[ctask] project dir: smoke/" and Claude
                               # launches with cwd inside smoke/. /exit to leave.
ctask delete --force smoke

# Default discovery (v0.5):
# Projects created without $CTASK_PROJECT_ROOT should now be findable from any shell.
ctask list --projects

Load-bearing design points (don't forget)

From v0.4 (unchanged)

  • Coexisting-session limitation: "Continue anyway?" or --force → second session runs without its own lease. Documented in docs/commands.md. Don't redesign without a real user complaint.
  • end_manifest in the summary is ctask-internal (not in spec example JSON) and powers Layer-3 stale-workspace diff. Removing it breaks Layer 3.
  • Write-lock skip classification (best-effort vs important-but-non-fatal): unchanged.
  • cmd/delete.go two-check invariant: unchanged. CTASK_WORKSPACE match → refuse; .ctask/manifest-start.json exists → refuse. Both before any mutation.
  • session.Run() lifecycle in internal/session/run.go:
    1. PreflightFull (Layers 3 + 1)
    2. Write lock → write lease (unless coexisting)
    3. Write lock → capture + write start manifest
    4. Print launch-context banner (Layer 4)
    5. Start heartbeat (only if we own the lease)
    6. v0.5: workspace.ResolveLaunch(opts.WsDir, opts.LaunchDir) — print warning or abort on security error
    7. Banner lines (including project dir: when LaunchDir set) + exec child with cwd = resolved launch path
    8. Stop heartbeat
    9. handleProvisional (gated on NewlyCreated && childExitCode != 0 && emptyDiff) — if it removes the workspace, skip finalize entirely
    10. finalize: single write-lock acquire → append log + write summary + remove lease (if owned) + remove manifest-start
  • new is intentionally not given --force. Brand-new workspaces have nothing for Layer 1/3 to warn about.
  • internal/lockfile is a neutral package — no other ctask imports.

From v0.4.1 (unchanged, still load-bearing)

  • config.SearchRoots() is the canonical way to enumerate workspace roots. Always includes CTASK_ROOT. In v0.5 the semantics of the second entry expanded — see "From v0.5" below.
  • QueryResult.Root is how callers render relative paths and compute CTASK_ROOT env var. Populated by scanAllRoots.
  • scanWorkspaces is a two-depth scan, first-match-wins. Depth-1 (flat) check wins if present; never descends into a detected workspace.
  • Archive's non-TTY stdin refuses. Fresh lease + non-TTY = exit non-zero without mutating. Don't regress to "silent proceed".
  • Doctor seed-dir checks are three-state. Only "configured but missing" counts as a failure.
  • Provisional-cleanup exit-code gate. childExitCode == 0 means preserve. Don't special-case specific codes.

From v0.5 (new — don't unlearn)

  • launch_dir is stored as workspace-relative in task.yaml, empty for tasks and pre-v0.5 projects. Never store absolute paths. Never interpret launch_dir outside workspace.ResolveLaunch.
  • workspace.ResolveLaunch has three outcomes: valid, soft fallback (with warning), security/permission error. Soft fallback is (wsDir, warning, nil) — caller warns and keeps launching. Error is ("", "", err) — caller must abort. The asymmetry between os.IsNotExist (fallback) and other stat errors (propagate) is intentional; masking a permission error as a warning would silently strip launch-dir semantics.
  • Project subdir name = final suffixed slug. dup-2 collision means subdir is dup-2/, not dup/. The actualSlug variable in workspace.Create is the single source of truth.
  • Subdir is created after seeds, empty, never seeded. Layer 1 (built-in defaults), Layer 2 (general seed), Layer 3 (project seed) all target the workspace root. The if opts.IsProject { os.MkdirAll(projDir, 0755) } block runs AFTER seed overlays and BEFORE task.yaml is written.
  • Session scope is workspace-wide even when launched in the subdir. Lease, manifest capture, write lock, summary, stale-workspace diff all operate on opts.WsDir (the workspace root), not on the resolved launch path. Only the child process's cwd changes.
  • CTASK_LAUNCH_DIR is always exported (empty string for tasks). Status line helpers and any future child-side integration can check -n "$CTASK_LAUNCH_DIR" safely.
  • SearchRoots() now appends $CTASK_ROOT/projects/ by default when CTASK_PROJECT_ROOT is unset. This is redundant with the v0.4.1 two-depth scan under $CTASK_ROOT but explicit per spec. Dedupe in scanAllRoots (searchRootKey — cleaned + lower-cased on Windows) prevents double-counting. Don't remove the default fallback without also reverting the spec-driven expectation that default projects are findable from any shell.
  • Doctor check for CTASK_PROJECT_ROOT is advisory, not prescriptive. "Recommended: set at user scope so all terminals can discover these workspaces." Don't harden into a FAIL when the variable is set but the path exists.

From v0.5.1 (new — don't unlearn)

  • Workspace directory prefix uses LOCAL time. time.Now() in workspace.Create feeds the YYYY-MM-DD date and the YYYYMMDD-HHMMSS ID. Don't switch back to .UTC() for these. The TestCreateDirectoryPrefixUsesLocalDate regression test enforces this.
  • ctask info displays timestamps in local zone via .Local().Format(...). Stored timestamps in task.yaml are still UTC (meta.CreatedAt = nowLocal.UTC().Truncate(time.Second)) — only the display converts.
  • Stored timestamps everywhere else remain UTC. Session logs, lease StartedAt/LastHeartbeatAt, manifest capture times, summary timestamps are all unambiguous UTC. Don't "localize" those — they're machine-readable.
  • Cross-builds force CGO_ENABLED=0. just build-linux and just build-windows set this explicitly so the artifact is pure-Go static. Don't drop the flag — a native Linux build defaults to cgo and produces a glibc-linked binary that won't run in Alpine / distroless / scratch containers.
  • WorkspacePath is gone from TaskMeta. Don't add it back. If a persistent workspace identifier is ever needed, add a properly relative-to-root workspace_id (forward-slash normalized). Old task.yaml files with workspace_path parse silently — the field is ignored on read.
  • POSIX install script does not modify shell config. It warns the user about PATH but never edits ~/.bashrc / ~/.zshrc. Don't change this without an explicit user request.

Open follow-ups (NOT in v0.4/v0.4.1/v0.5, deferred)

Potentially worth doing

  • Pre-v0.5 project workspaces have no launch_dir. They launch from the workspace root, not from any project subdir. No migration path today. Acceptable — users can manually add a launch_dir to their task.yaml if they want the new behavior. Revisit only if someone complains.
  • Drop unused slug / category / workspacePath parameters from seed.ClaudeMD (v0.3 follow-up, still open).
  • Status-line: color the |project / |project:<dir> marker if Claude Code's statusLine ever supports ANSI.
  • 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.goResolveLaunch with the three-outcome contract
  4. internal/workspace/create.go — subdir scaffold + local-time date + launch_dir default
  5. internal/session/run.goLaunchOpts.LaunchDir, ResolveLaunch call site, banner extension
  6. cmd/info.go — local-time display + launch fields
  7. cmd/doctor.gocheckProjectRoot helper
  8. scripts/ctask-statusline.{sh,ps1} — effective-path display logic
  9. internal/seed/templates.goClaudeMDProject with Workspace Structure section

For the v0.4.1 surface (still load-bearing):

  1. internal/config/config.goSearchRoots(), samePath, searchRootKey dedup; default $CTASK_ROOT/projects/ fallback
  2. internal/workspace/query.go — two-depth scanWorkspaces, scanAllRoots, QueryResult.Root
  3. cmd/archive.go — active-session check + non-TTY refusal + isStdinTerminal

For the v0.4 surface:

  1. internal/session/run_preflight.go — Layer 3 + Layer 1 preflight
  2. internal/session/lease.go — Lease + freshness + cleanup
  3. internal/session/summary.go — SessionSummary + launch-context banner
  4. internal/lockfile/writelock.go — write-lock primitive
  5. internal/session/run_provisional.gohandleProvisional with childExitCode gate
  6. 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.