ctask agents check [workspace] validates that a workspace's agent
configuration can be launched without launching it: agent type known,
command resolvable on PATH, launch_dir valid, AGENTS.md present,
CLAUDE.md shim present (WARN only, claude type only). agent.env keys
are displayed informationally, with a WARN line when any key shadows a
ctask-exported CTASK_* var. Returns non-zero when any check FAILs.
ctask doctor includes the same sweep when a workspace context is
resolvable — the most-recently-active workspace via
workspace.MostRecentActive. When no active workspace exists, doctor
shows "Agent check: skipped (no workspace context)" without bumping
the failure counter. runAgentsCheckOnWorkspace is shared between the
standalone command and the doctor integration.
TestCompletionSubcommandViaExecute is made order-independent: cobra's
default completion command captures the root output writer on the first
Execute() in the process, and the new agents-check tests now run an
Execute() earlier in the suite.
New workspaces get:
- AGENTS.md (always): canonical agent instructions — handoff workflow,
notes-archive convention (~300-500 line trigger), cross-workspace
discovery, do-not-touch warnings. Project variant adds workspace-
structure and git-conventions sections.
- CLAUDE.md shim (claude type only per v0.6 spec §6 — opencode shim
deferred until its instruction-file convention is verified).
- handoff.md: minimal current-state template the agent updates per
session.
- context/notes-archive/.gitkeep: directory pinned for git tracking,
ready for the agent to populate per the archive convention.
seed.ClaudeMD and seed.ClaudeMDProject removed — no callers remain.
Existing workspaces are NOT modified; this is strictly a ctask-new code
path. The seed-wins overlay rule still applies — a user seed dir's
AGENTS.md/CLAUDE.md overrides the built-in.
Resolver gains SetCLIFlagAgent; DefaultAgent now layers CLIFlag above
EnvVar so doctor/info attribution renders the correct precedence chain
(CLIFlag overrides EnvVar overrides ConfigFile overrides Builtin).
ctask new --agent <type> writes agent.type into the new workspace's
task.yaml. Resolution and validation run before workspace.Create, so
--agent custom without a companion command refuses ("type custom
requires command") with no half-created workspace left on disk. The
deferred Phase 1 test TestCLIFlagOverridesEnvVar lands here.
--agent on resume/last/attach is unchanged (one-shot agent.command
override on the AgentSpec — Open Q 1).
LaunchOpts.Agent (string) and WorkspaceEntryOptions.Agent (string) are
replaced by *agent.Resolved, carrying Command, Args, and Env. The five
entry commands (new, resume, last, open, attach) each construct an
AgentSpec from the workspace metadata, apply --agent as a one-shot
agent.command override (Open Q 1 — keeps muscle memory for users
passing executable paths), call agent.Resolve, and pass the result
through. resolveEntryAgent centralises the resume/last/open/attach path.
shell.ExecAgent and shell.ExecTmuxAgent gain an args parameter; agent.env
is merged into the env map at the session.Run launch switch, AFTER
ctask's exported CTASK_* vars (per spec §5: agent.env wins on collision).
mergeAgentEnv is the centralised merge.
Lease, manifest, write lock, heartbeat, summary, and provisional
cleanup are unchanged. The Agent string fields on Lease, SessionSummary,
and SessionInfo continue to record the launched command for diagnostics.
Pure resolution logic combining a workspace's AgentSpec with the
user-level default_agent into a Resolved value carrying Command, Args,
and Env. No I/O — PATH lookup stays in shell.ExecAgent and ctask agents
check, so Resolve is trivially testable and reusable.
BuiltinProfiles enumerates claude and opencode; "custom" is the escape
hatch and requires command. Keep BuiltinProfiles in sync with
workspace.knownAgentTypes and workspace.IsBuiltinAgentType (Task 1).
Replace TaskMeta.Agent (string) with TaskMeta.Agent (AgentSpec) carrying
type/command/args/env. Custom UnmarshalYAML preserves the legacy scalar
form: a built-in name (claude, opencode) maps to that type; any other
scalar maps to type=custom with the scalar as command. A missing agent
field leaves Type empty so the resolver fills in default_agent at launch.
ValidateAgentSpec enforces: known type (claude|opencode|custom),
type=custom requires command, command must be an executable name or
path with no whitespace or shell metacharacters.
Launch-path wiring (Task 3) and the --agent flag rework (Task 4) are
intentionally not part of this commit; cmd/* call sites are patched to
the minimum needed for the build to compile.
Records:
- Branch feat/v0.6-multi-agent-config is the active surface for v0.6;
Phase 1 (5 commits) is implemented, tested, and reviewed but the
branch is NOT merged into main. Phase 2 and Phase 3 will continue on
the same branch per the v0.6 spec's "one branch, three sequential
milestones" policy. The eventual merge happens at the end of Phase 3
alongside the version bump.
- The five Phase 1 commits with one-line summaries each:
6f80c8b config file parser + resolver + source attribution
0b21b8d schema_version and workspace.mode in task.yaml
c918e5c doctor Settings section with source attribution
937a1c8 info source attribution on Agent and Launch session mode
6182d89 platform-override stderr warning on launch paths
- Per-commit detail: what each commit landed, where the load-bearing
code lives, and what the test coverage is.
- Verification gate (all clean on tip 6182d89):
go test ./... -count=1, go vet ./..., go build, just build-linux
(statically linked ELF). Version stays v0.5.4 — bump is deferred
to the end-of-Phase-3 commit.
- Phase 1 constraints held: no config auto-creation, no opportunistic
schema writes, env vars remain overrides, task.yaml remains
workspace state, no Phase 2 or Phase 3 work started.
- Native-Windows platform-override behavior across the three surfaces:
* doctor + info — show source attribution
* launch paths (new/resume/last/open) — emit one stderr warning
per invocation and downgrade to direct
* attach — does NOT downgrade; continues to refuse via
preflightPersistentEntry because it has no direct-mode equivalent
- Architecture notes worth preserving for future Phase work:
"one resolver per command", exported test seams, validation in
ReadMeta rather than callers, omitempty as the load-bearing
primitive for no-opportunistic-writes.
- Files-committed list and "How to resume" section both updated to
point at Phase 2 as the next horizon and to enumerate the resume
sanity checks against the unmerged branch.
Closes the half-feature surfaced during Phase 1 review: doctor now
shows that persistent session_mode is overridden to direct on native
Windows, but the launch-time entry paths were silently downgrading
without telling the user. This commit adds the one-line warning
specified by the user, gated to the four entry commands where the
downgrade actually has an effect.
cmd/persistent.go:
- New package-level const platformOverrideWarning carries the exact
spec-mandated text so tests don't have to retype the copy.
- New helper emitPlatformOverrideWarningIfNeeded(alwaysPersistent).
Loads the resolver, reads SessionMode(), and emits the warning to
stderr when:
1. the call site is NOT AlwaysPersistent (attach has no
direct-mode fallback and continues to refuse on native
Windows via preflightPersistentEntry), AND
2. the resolver's value resolved through PlatformOverride.
Doctor and info do not call this helper — they render source
attribution themselves and an extra stderr line from a diagnostic
command would be noise. "Once per invocation" is provided
implicitly by the call site running at most once per ctask
command; no warn-once subsystem.
cmd/entry.go:
- defaultRunWorkspaceEntry calls the helper at the top, before any
launch work. This covers all four entry paths (new / resume / last
/ open) since they all funnel through runWorkspaceEntry. attach
also routes here but sets AlwaysPersistent=true, so the helper
short-circuits.
cmd/platform_warning_test.go (5 cases):
- TestPlatformOverrideWarningEmittedOnLaunch — simulated native
Windows + config session_mode: persistent + AlwaysPersistent=false
→ warning text appears on stderr.
- TestPlatformOverrideWarningNotEmittedWhenDirect — config
session_mode: direct → no warning even on simulated Windows.
- TestPlatformOverrideWarningNotEmittedWithoutConfig — builtin
default (direct) → no warning.
- TestPlatformOverrideWarningNotEmittedOnNonWindows — persistent
config but isNativeWindows() returns false → no warning.
- TestPlatformOverrideWarningSkippedForAlwaysPersistent — attach's
AlwaysPersistent=true gate prevents emission. attach's actual
refusal contract on native Windows is already covered by
TestPreflightRefusesNativeWindows in persistent_test.go.
Validation: `go test ./... -count=1`, `go vet ./...`, `go build`,
and `just build-linux` all clean.
Adds source-attribution rendering to `ctask info` for the two Phase 1
settings whose effective value depends on user-level defaults: the
agent recorded in task.yaml and the configured launch session mode.
cmd/info.go:
- runInfo loads the resolver once (config.LoadResolver) and reuses
it across the rendering — matches the user's correction that "new
doctor/info code should load the resolver once and reuse it".
- Agent line now reads `Agent: <value> (workspace)` when task.yaml
has a non-empty agent (the common case), or `Agent: <value>
(default)` / `Agent: <value> (default — <source label>)` when the
field is empty and the value comes from the resolver fallback
chain. The fallback path is informational only: every workspace
created by recent ctask versions writes the resolved default into
task.yaml at Create time, so the (workspace) branch is what users
normally see.
- New "Launch session mode:" line lives immediately after Agent and
before Created — outside the v0.5.4 Session block per the user's
placement decision ("Keep it outside the Session block because it
represents the configured launch default, not the current session
lease mode"). Format: `Launch session mode: <value> (<source>)`.
- Two small helpers added: agentLineWithSource composes the agent
payload + label; infoSourceLabel renders a single-row source
string (CTASK_X env var / config file / built-in default /
platform override). infoSourceLabel intentionally omits the
override-chain suffix used by doctor — info's row layout has no
room for the extra parenthetical.
cmd/info_attribution_test.go (5 cases):
- TestInfoAgentSourceWorkspace — task.yaml with agent set → "(workspace)"
- TestInfoAgentSourceDefaultForLegacy — empty agent → "(default)"
- TestInfoLaunchSessionModeFromConfig — config session_mode value +
"config file" source label
- TestInfoLaunchSessionModeBuiltinDefault — no config, no env →
"direct (built-in default)"
- TestInfoLaunchSessionModeAfterAgentBeforeCreated — placement check:
Agent < Launch session mode < Created in the rendered output
Smoke-verified against an existing v0.5.x workspace on the installed
binary; render order and source labels match the spec example.
Adds a new ── Settings ── block at the bottom of `ctask doctor` (after
the existing tmux INFO line, before the summary). The block reports
every user-level default tracked by the resolver — ctask_root,
project_root, seed_dir, default_agent, default_category, session_mode,
editor — together with the source it was drawn from.
Implementation:
- runDoctor calls config.LoadResolver() once and passes the resolver
into checkSettings; no setting is re-resolved mid-block (the user's
correction: "new doctor/info code should load the resolver once and
reuse it").
- checkSettings prints the config-file lifecycle line first
(`Config file: <path>` / `Config file: not found — using built-in
defaults` / `[FAIL] unknown key: "..."` per the resolver's
ConfigErr state), then iterates the resolver's ResolvedSetting
accessors and feeds each one through printSettingLine.
- printSettingLine renders the key + value pair plus a `source: …`
child line. When the resolver chains an Override, the source line
embeds the overridden source + value in parens. For the
PlatformOverride session_mode case, an additional
`configured: <value>` line surfaces what the user asked for.
- formatSettingSource centralises the "EnvVar → CTASK_X env var" /
"PlatformOverride → ... (persistent mode requires tmux; not
available on native Windows)" / override-chain wording so doctor
and info can share the same labels in commit 4.
- Only the invalid-config-file branch increments the failed counter.
Missing file is INFO. The valid-file branch is purely informational.
Tests (cmd/doctor_settings_test.go, 6 cases):
- TestDoctorShowsSettingsSection — header present + all keys appear
- TestDoctorShowsSourceAttribution — every line has source: + at
least one "built-in default"
- TestDoctorShowsOverrides — env vs config override chain rendered
- TestDoctorConfigNotFound — INFO, no failed bump
- TestDoctorConfigInvalid — FAIL bump + unknown key named
- TestDoctorSessionModePlatformOverrideRendered — direct value +
"platform override" label + "configured: persistent" row
Smoke-verified end-to-end against the installed binary:
- no config + no env → all "built-in default"
- env CTASK_AGENT=aider + config default_agent=opencode →
"CTASK_AGENT env var (overrides config file: opencode)"
- config session_mode=persistent on native Windows → "platform
override (...)" plus "configured: persistent"
- config with typo "default_agnet: foo" → [FAIL] line names the key
and the "settings not applied" advisory
Adds the two new metadata fields specified for Phase 1 of v0.6 plus
the validation and defaulting helpers around them.
internal/workspace/metadata.go:
- CurrentMetaSchemaVersion = 1 constant.
- WorkspaceSection struct {Mode string} with omitempty.
- SchemaVersion int and Workspace WorkspaceSection fields added to
TaskMeta at the top of the struct. Both are omitempty so legacy
task.yaml files (no schema_version, no workspace block) round-trip
without acquiring these keys when an unrelated field is updated.
- EffectiveSchemaVersion(meta) — returns 1 for stored-value-0 legacy
workspaces; non-zero stored values are returned verbatim.
- ValidateSchemaVersion(slug, meta) — rejects stored values higher
than CurrentMetaSchemaVersion with the spec-mandated upgrade
message. Accepts 0 (legacy missing).
- ValidateWorkspaceMode(slug, meta) — rejects modes other than ""
and "native". "adopted" is reserved for v0.7.
- ReadMeta now runs both validators after unmarshal. The validation
error includes the workspace slug (derived from task.yaml's slug
field, falling back to the directory basename when the file itself
is corrupt).
internal/workspace/create.go:
- workspace.Create stamps every new meta with
SchemaVersion: CurrentMetaSchemaVersion and Workspace.Mode: "native".
This is the ONLY write site for these fields in v0.6; resume,
archive, restore, and any other path that rewrites task.yaml MUST
NOT backfill them (the "no opportunistic schema writes" invariant).
internal/workspace/schema_test.go:
- 10 tests:
* new meta written by Create contains schema_version: 1 +
workspace.mode: native (both serialization and round-trip)
* legacy meta without these fields loads with stored value 0 / ""
and EffectiveSchemaVersion returns 1
* task.yaml with schema_version: 2 is rejected with upgrade message
* task.yaml with workspace.mode: adopted is rejected
* the no-opportunistic-writes invariant is pinned for both WriteMeta
and WriteMetaLocked: a legacy file rewritten with an updated
UpdatedAt does NOT acquire schema_version or workspace: keys
* ValidateSchemaVersion accepts {0, 1}; ValidateWorkspaceMode
accepts {"", "native"}
Phase 1 foundation. Adds:
- internal/config/configfile.go — strict-key YAML parser for the
global config file. Unknown top-level keys invalidate the entire
file (no partial apply); future schema versions are rejected with
an upgrade message. Platform-appropriate path (XDG_CONFIG_HOME or
~/.config on Unix, %APPDATA% on native Windows).
- internal/config/resolver.go — Resolver / ResolvedSetting /
SettingSource (Builtin, ConfigFileSrc, EnvVar, CLIFlag,
PlatformOverride). Each setting carries provenance plus an
Override chain so callers can render "env var (overrides config
file: X)" lines. session_mode applies the native-Windows
platform override at the resolver layer with the configured
value chained as Override. Exports SetConfigPathForTest and
SetIsNativeWindowsForTest so cross-package tests can isolate
themselves from host config or simulate the override.
- internal/config/config.go — legacy ResolveRoot / ResolveAgent /
ResolveSeedDir / ResolveProjectRoot / ResolveSessionMode now
delegate to LoadResolver so config-file values take effect for
entry commands. ResolveProjectRoot preserves the "" sentinel for
built-in source so SearchRoots and doctor checkProjectRoot keep
their existing fallback semantics. ResolveSessionMode preserves
the v0.5.3 unknown-value stderr warning, distinguishing env-var
and config-file sources in the message.
- Tests: 6 LoadConfigFile cases (missing, basic, partial, unknown
key, future schema, malformed YAML), 3 ConfigFilePath cases
(XDG / ~/.config / %APPDATA%), 11 resolver cases including
layering, override chains, path expansion, platform override,
and SettingSource string rendering.
- Test isolation: vulnerable tests in config_test.go and
config_roots_test.go that asserted Builtin-source defaults now
call SetConfigPathForTest to point at a nonexistent path,
insulating them from developer-host config files. Three cmd
tests that asserted persistent session_mode behavior now call
SetIsNativeWindowsForTest to disable the platform override
(Phase 1 introduces the override; the layering-only behavior
these tests cover is tested separately in resolver_test.go).
No new dependencies (gopkg.in/yaml.v3 was already in go.mod).
No version bump (lands at the end of Phase 3 per the v0.6 spec).
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.
ctask resume <archived-workspace> printed both the helpful [ctask]
diagnostic + restore hint AND a redundant trailing "Error: workspace
archived" line from Cobra's default error rendering. Cosmetic but
unprofessional.
Add an errArchivedWorkspace sentinel and have runResume flip
SilenceErrors only when the inner error is that sentinel. All other
resume errors (lookup failure, metadata write failure, etc.) continue
to flow through Cobra's default rendering unchanged — we only silence
the case where we have already printed an equivalent diagnostic
ourselves.
Verified end-to-end through Cobra Execute (not just runResume direct
invocation) so the SilenceErrors-flip-from-RunE timing is exercised.
Bring docs/commands.md up to current state. The document was last
substantively updated in v0.5; v0.5.2 (restore/notes/path/list --names/
completion + archived-inclusive lookup), v0.5.3 (attach + --direct +
persistent session mode + doctor tmux check), and v0.5.4 (info Session
block + list SESSION column) were all undocumented.
Format follows the spec section §3: each command has Purpose, Usage,
Scenarios, Examples, Flags, Notes, Related. Sections are kept short
(roughly one screen each) — if a command needed more, the command is
doing too much, not the docs.
New non-command sections: workspace layout (task and project), a
consolidated environment-variables table (previously scattered),
explicit query-resolution rules including archive-inclusive lookup
behavior, session modes (direct vs persistent), and a shell-completion
how-to.
Examples use the canonical "ctask" command name per spec §3 — docs
describe the product, not the user's local binary path.
Audit walked every cmd/ and internal/ file that produces user-facing
output. All command-form hints (text the user is meant to type back)
were already routed through invocationName() in v0.5.3 — the audit is
a verification pass, not a code rewrite.
Additions:
- Extract formatResumeRestoreHint and formatDirectModeTmuxHint as
testable string-only helpers. Production paths are unchanged
behaviorally; the helpers exist purely so the audit can pin down
the format strings without simulating tmux or stderr capture.
- Two new tests pinning the invocation name to a non-canonical value
("my-bin"). The pre-existing tests already protect these surfaces
but pin the name to "ctask", so they cannot detect a regression
that hard-codes "ctask" inside the format string. The new tests
flush that out for the resume restore hint and the Layer-1
active-session attach hint.
- Drop the duplicated withInvocationName helper accidentally added in
the info-session tests; reuse the canonical helper from
persistent_test.go.
Product-identity references ("ctask persistent mode requires tmux",
the SSH-remote `ssh -t <host> ctask <subcmd>` hint, doctor's "[ctask]"
diagnostic prefix, root-command Use:/Long:) deliberately remain
literal per spec §2.
Add a SESSION column to ctask list output, inserted to the right of
STATUS per spec. Values: "direct", "persistent", "stale", or em dash
for no session. Populated by SessionStatus, so each workspace adds at
most one short lease-file read — negligible for typical workspace
counts.
Archived workspaces always render as the em dash regardless of any
lease file present. The spec calls this a display simplification, not
a lifecycle invariant: ctask info still surfaces the raw session
state on archived workspaces because info is the diagnostic command.
ctask list --names is unchanged: one basename per line, no header,
no SESSION column. Verified by a regression test that asserts every
emitted line is bare-basename whitespace-free and contains none of
the SESSION tokens.
Add a Session block to ctask info output, surfacing the workspace
session lease state derived from SessionStatus. Inserted between Path
and any launch-dir fields so the new content is visually distinct
from both blocks.
Format: state on the header line, then indented Mode / Owner / Attach
/ Note rows aligned at column 14. The Owner line omits the hostname
when it matches the local machine. The Attach hint surfaces only for
active+persistent sessions and uses invocationName() so the suggested
command reflects the user's actual invocation. Malformed leases
render as stale with a single-line diagnostic and no Mode/Owner/Attach
rows so we never display fields parsed from a broken file.
Exposes session.CurrentHostname() so the cmd layer has a single
source of truth for the local-vs-remote hostname check.
Add internal/session.SessionStatus(wsDir) that derives a display-only
view of the workspace session lease. Pure read of .ctask/session.json
with no tmux invocation, no PID liveness, no lock acquisition, and no
mutation of lease state.
States: none | active | stale. Mode defaults to "direct" when a
pre-v0.5.3 lease lacks the field. Malformed leases surface as stale
with a diagnostic so the present-but-broken case stays visible
instead of being silently classified as none.
Reused for ctask info and ctask list in subsequent commits. Lifecycle
code continues to use the existing primitives (ReadLease, IsFresh,
InspectLease).
Three independent v0.5.3 polish items surfaced during manual WSL smoke testing,
bundled into one commit since they're all string/UX touches with no behavior change.
1. v0.4 lease-prompt tightening (`internal/session/{lease,run,run_preflight}.go`,
`cmd/entry.go`): when ctask is about to enter direct mode on a workspace that
already has a live tmux session, the "Continue anyway?" prompt now suggests
`ctask attach <slug>` 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). Closes the footgun where a user forgot to
`export CTASK_SESSION_MODE=persistent` in a second terminal and hit the v0.4
coexistence prompt without realizing tmux passive-reattach was the intended
path.
2. Invocation-name in user-facing command hints (`cmd/invocation.go` new,
`cmd/{persistent,entry,resume,doctor}.go`): the binary name printed in
bypass / restore / "create one with" suggestions now reflects
`filepath.Base(os.Args[0])` instead of a hard-coded "ctask". Local-build
PowerShell users running `.\ctask.exe` see `ctask.exe new <ws> --direct`,
matching what they need to type. Installed contexts continue to see `ctask`.
Test seam (`invocationNameOverride`) pins the name to "ctask" in unit tests
so substring assertions stay stable across Go test binary names. Descriptive
prose ("ctask persistent mode requires...") and the ssh-remote hint
(`ssh -t <host> ctask <subcmd>`) intentionally keep the literal "ctask" —
they refer to the program identity / remote invocation, not the local
command form. Affected tests: `cmd/{persistent,resume}_test.go` tightened to
check the full `"<binary> <subcmd> <workspace> --direct"` form.
3. Smoke-checklist fixes (`docs/.../2026-05-08-v0.5.3-smoke-test-checklist.md`):
six issues caught during the run -- S2 now exports CTASK_SESSION_MODE in
both terminals (previously only WSL-A had it, which routed WSL-B's
secondary resume through direct mode instead of passive reattach); O3/A2
tmux-ls expectations corrected (tmux doesn't emit a literal "(detached)"
token); P2 expected behavior rewritten (passive reattach detach returns to
prompt immediately -- AttachExisting calls only shell.AttachSession with
no PollSessionEnd, the owner is responsible for finalize); A1 no longer
asks for Ctrl-C in WSL-B; A6 path now uses a glob (was hardcoded to the
checklist's authoring date, broke on v0.5.1 local-time directory naming);
M1 PATH-hide rewritten to `$HOME/.local/bin` only (was `:/bin` which is a
symlink to /usr/bin on usrmerge systems, did not hide tmux) and uses
`command -v` instead of `which` (which is itself in /usr/bin, unreachable
under the minimal PATH); M3/M4 reordered so the no-workspace verification
runs after PATH is restored (was silently failing under minimal PATH);
T1 wording made "substring match" explicit; T2 wording made N/A
unambiguous; section 10 split into 10a-10f, each a separate input, to
work around PSReadLine multi-line paste parsing.
Add the v0.5.2 round summary (commits a5e508b..3b6be0d + 5910100):
restore/notes/path commands, direct-lookup archived-inclusive policy,
resume archived hint, list --names, shell completion, cross-workspace
context seed section. Windows + WSL validation passed; Linux binary
statically linked.
Add load-bearing v0.5.2 invariants to the "don't unlearn" section
(archived-inclusive lookup policy, list --names emits basenames not
slugs, completion calls ListWorkspaces directly, etc.). Replace the
"Next: v0.5.2" pointer with a "Next: v0.6 (planning)" stub covering
config/agent profile work, resume-error polish, and flag-aware
completion for open/delete --all.
Drop v0.5.2-spec.md from the untracked-files list (committed in a5e508b).
Adds a concise "## Cross-Workspace Context" section to both the
task and project CLAUDE.md seed templates. Teaches the agent to
inspect related workspaces with the new commands before making
changes:
ctask list --all discover all workspaces, including archived
ctask info <workspace> view metadata and status of any workspace
ctask notes <workspace> read another workspace's notes.md
ctask path <workspace> get the filesystem path to inspect files
Closes the v0.5.2 design loop: ctask exposes workspace context
through CLI commands and lets agents consume it themselves. The
seed text is read-only by convention — the section explicitly
asks the agent to treat other workspaces as read-only unless the
user grants modification rights.
Applies to newly created workspaces only. Existing workspaces
keep their current CLAUDE.md (per spec: no retroactive overwrite).
Users with custom seed directories see this only if they update
their seeds.
Adds a --names flag to ctask list that emits one workspace
directory basename per line, no header, no decoration. Empty
result is empty stdout with zero exit code (no "No workspaces
found." placeholder).
Used by shell completion scripts and external tooling. Candidates
are directory basenames rather than bare slugs because basenames
are unique under the resolver's exact-match step while slugs can
collide across categories or dates.
Respects existing list filters: --all, --task, --projects,
--category, --limit. So:
ctask list --names active workspaces only
ctask list --names --all active and archived
ctask list --names --projects active project workspaces
The new TestListNamesCandidatesResolveUniquely test enforces the
spec invariant: every line emitted by list --names must resolve
to exactly one workspace via the standard resolver.