16 Commits

Author SHA1 Message Date
typebasedio b75b82e676 feat(v0.6): launch path carries ResolvedAgent (command + args + env)
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.
2026-05-15 11:08:03 -04:00
typebasedio 8120c399df feat(v0.6): AgentSpec field on TaskMeta with backward-compat unmarshal
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.
2026-05-15 10:58:06 -04:00
typebasedio ae9bfafb1f polish(v0.5.4): suppress Cobra duplicate Error on archived resume
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.
2026-05-14 20:01:19 -04:00
typebasedio 0fb8de697b polish(v0.5.4): invocation-name audit + targeted regression tests
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.
2026-05-14 19:56:05 -04:00
typebasedio c204d87b47 polish(v0.5.3): v0.4 lease-prompt hint, invocation-name in user-facing commands, smoke-checklist fixes
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.
2026-05-14 18:22:27 -04:00
typebasedio f5746df314 feat(v0.5.3): shared workspace-entry helper; resume + last delegate; fresh_remote prompt 2026-05-08 13:59:53 -04:00
typebasedio b923ae8892 feat(v0.5.2): direct lookup includes archived; resume hint for archived
Apply the v0.5.2 lookup policy to the existing commands and wire
ValidArgsFunction hooks across the workspace-accepting surface.

info: drop the --all/-a flag entirely. Direct lookup is now
archived-inclusive by default — the user typed a name, so we find
the workspace and surface its status in the output. The Status
line already distinguishes active vs archived clearly.

resume: when targeting an archived workspace, fail with a useful
hint instead of letting the resolver report "not found":

  [ctask] error: workspace "X" is archived

  To restore it:
    ctask restore X

This is implemented by resolving archived-inclusive and rejecting
status==archived before any session-launch logic runs. Genuine
not-found and ambiguous-match behavior are unchanged.

Adds ValidArgsFunction hooks to archive (active), delete (active),
open (active), info (any), resume (active). Restore/notes/path
already have hooks from the previous commit. Shell completion now
covers the full direct-lookup surface.
2026-05-07 19:47:24 -04:00
typebasedio 103f2cd33e feat(v0.5): launch agent inside project subdirectory via launch_dir
LaunchOpts gains LaunchDir. session.Run resolves it via
workspace.ResolveLaunch, prints any fallback warning, and passes the
absolute path as the child process's working directory. Security
violations (absolute paths, .. escape) abort the session. The banner
gains a 'project dir: <name>/' line when launch_dir is set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:49:29 -04:00
typebasedio 509a6d64ea feat(v0.5): export CTASK_LAUNCH_DIR into child sessions
config.EnvVars gains a 7th launchDir argument. cmd/new, cmd/resume, and
cmd/open pass ws.Meta.LaunchDir. Child sessions can read CTASK_LAUNCH_DIR
to know which subdirectory ctask launched them into.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:48:23 -04:00
typebasedio 0c1f03ba3a fix(v0.4.1): route all workspace commands through SearchRoots
Every resolver, lister, and most-recent caller now passes
config.SearchRoots() so CTASK_PROJECT_ROOT is searched alongside
CTASK_ROOT. Commands use ws.Root when rendering relative paths or
session env vars so displays and CTASK_ROOT exports are correct for
workspaces living under CTASK_PROJECT_ROOT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:54:58 -04:00
typebasedio 67138584d4 feat(v0.4): add --force flag on resume, last, and open 2026-04-21 17:07:04 -04:00
typebasedio ab89a167a5 refactor(v0.4): route session-lifecycle and archive task.yaml writes through write lock 2026-04-21 17:04:23 -04:00
typebasedio 8cda541f2c feat(v0.3): add --project flag, CTASK_TYPE env, project root semantics
ctask new gains --project, which:
  - records type=project on the workspace
  - defaults the category to "projects"
  - applies general + project seed overlays
  - uses CTASK_PROJECT_ROOT when set, with no doubled "projects/"
    path unless the user explicitly passes -c
  - exports CTASK_TYPE=project into the child session

EnvVars now takes a taskType arg and exports CTASK_TYPE. Empty
type defaults to "task" for safety. resume/open also pass
EffectiveType so the env var is correct on resume of a v0.2
workspace.

Git init for project mode is wired in the next commit.
2026-04-10 14:40:06 -04:00
typebasedio 10ab9efc80 feat: session lifecycle wrapper with manifest capture and session logging
Refactor new/resume/open to use session.Run() which wraps child process launch with pre/post manifest capture and append-only session logging to logs/sessions.log. Bump version to 0.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:56:21 -04:00
typebasedio 57f345ae2b fix: exit codes match spec (0, 1, 2, 127) and silence usage on runtime errors
Exit 2 for missing required arguments, exit 127 for agent not found. SilenceUsage on all commands to avoid dumping usage on runtime errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:40:50 -04:00
typebasedio 50e7333e84 feat: all six CLI commands (new, list, resume, open, info, archive)
Complete command implementations with all flags per spec. Shared query resolution helper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:34:40 -04:00