feat(list): work outside a repo; group task list by project + session fallback#5
Conversation
… fallback Make task's read/list/global ops work OUTSIDE any git repo, and turn `task list` into a cross-project, session-aware view (ROADMAP: "Commands should work OUTSIDE a repo; task list groups by repo/project" + "task list: fallback to all-tasks + pagination + session-vs-all messaging"). Behavior: - `task list` outside any repo -> ALL tasks across the registered projects, GROUPED by project (heading per project, tickets beneath). A project whose backend errors is shown as a degraded group; it never aborts the aggregation. - `task list` inside a repo -> session scope, FALLING BACK to all of that repo's tickets (with a "showing all project tasks" line) when there's no agent session or the session has no tickets. `--all` gives the grouped cross-project view from anywhere. - `task read` / `status` / `find` work outside a repo: an id is routed to a registered project (Linear `HYP-3` by team; `#123` when one GitHub project is registered); an ambiguous id fails with a clear 3-part error. - Only `task create` is repo-bound -> a 3-part WHAT/WHY/HOW error outside a repo. Implementation: - New pure `tasklib/projects.py`: the `projects:` registry model (parse + config overlays), deduped by backend COORDINATE (repo/team), hostile-input tolerant. The multi-backend fan-out is an effect and lives in cli.py, reusing `get_backend` unchanged via a config overlay (`LoadedConfig.with_overlay`). - `_current_project_overlay` returns None ONLY when genuinely outside a git work tree; a broken in-repo origin surfaces its error, not "no projects". - Linear `list`/`search` scope to the backend's team (and project, if pinned) so the cross-project view doesn't double-count or leak workspace-wide hits. Docs: README (outside-repo section + `projects:` registry + JSON shape), AGENTS.md (the new rule), install.py SKILL_MD. Smoke: outside-a-repo `task list` degrades cleanly (exit 2, no traceback). Tests: 160 pass (test_projects, test_cli_non_repo, extended test_cli + test_backends). Covers grouping, degraded groups, session fallback, --label session filter (no cross-session leak), id routing, current marker, broken remote, and the 3-part errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d629cb49a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ands under a linear default
Two codex P2 review findings on the outside-a-repo routing:
1. cli.py: `backend: linear` with no `linear.team` returned None, demoting a real
in-repo misconfiguration to the "outside a repo" path (lying error, masking the
actionable "requires a team key"). Now mirror the github `repo: auto` branch:
inside a work tree, surface the backend error; only genuinely outside a repo
return None for the grouped/registry view.
2. projects.py: a registry entry's backend was defaulted from the MERGED top-level
backend, so inside a linear-backed repo a global `{repo: acme/web}` GitHub
shorthand was reinterpreted as a teamless linear entry and dropped from
`task list --all`. Infer the backend from the entry's own coordinate shape
(repo/github vs team/linear) before falling back to the merged default; an
explicit per-entry `backend` still wins.
Review (multi-model) findings addressed: NoReturn terminal instead of a
pragma'd `return None`; anchored the regression assertion to the real error text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both P2 review findings fixed in 9143977:
|
…imit (#6) Adds the pagination half of the ROADMAP items "Commands should work OUTSIDE a repo; task list groups by repo/project" + "task list: fallback to all-tasks + pagination + session-vs-all messaging" (the outside-repo/grouping/session fallback shipped in #5; this is the missing pager + interactive-limit refinement "task list pager: interactive-only + higher limit there"). Behavior (git's pager model): - Human (non-json) list/find output pages through less ONLY when stdout is an interactive TTY; piped/scripted output is plain text (scriptable). Short output that fits one screen prints directly (less -F). - Result cap follows interactivity: 100 in a terminal (the pager scrolls), 30 when piped, unless an explicit count is given (which always wins). - Opt out with the no-pager flag, NO_PAGER (any non-empty value), or an empty PAGER/TASK_PAGER (git's "cat, don't page"). Pager resolves TASK_PAGER -> PAGER -> less -> more; LESS defaults to FRX. - JSON output is never paged (machine-readable stays un-paged, un-decorated). Implementation: - New tasklib/pager.py: pure should_page decision (TTY + not-opted-out + a pager resolves) and a defensive page() that pipes through the pager and falls back to a direct write on any failure. Pipe is forced utf-8 (ticket titles can be non-ASCII; a locale-encoding crash would otherwise escape). stdin is always closed in finally so the pager sees EOF. - cli._emit_list is the single call site; _format_tickets/_format_groups now return strings (were direct prints) so output can be routed; JSON paths print straight to stdout via _print_tickets_json/_print_groups_json. - _effective_limit picks the cap by interactivity; the count default is None so an explicit value is distinguishable. Tests: tasklib/pager.py decision + routing (real child-process pager via TASK_PAGER, utf-8, broken-pipe/non-zero-exit/missing-binary fallbacks, LESS=FRX seeding, empty/whitespace pager, closed-stream isatty); cmd_list/cmd_find wiring (interactive paging, piped-not-paged-even-with-pager-configured, no-pager, NO_PAGER, JSON never paged, explicit count flows into the backend, grouped paging on a TTY). Docs: README pagination section, AGENTS.md rule, install.py SKILL_MD. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
Make
task's read/list/global ops work outside any git repo, and turntask listinto a cross-project, session-aware view. Implements two ROADMAP items:Behavior
task listoutside any repo → ALL tasks across the registered projects, grouped by project (a heading per project, tickets beneath). A project whose backend errors is shown as a degraded group; it never aborts the aggregation.task listinside a repo → session scope, falling back to all of that repo's tickets (with ashowing all project tasksline) when there's no agent session or the session has no tickets.--allgives the grouped cross-project view from anywhere.task read/status/findwork outside a repo: an id is routed to a registered project (LinearHYP-3by team;#123when exactly one GitHub project is registered); an ambiguous id fails with a clear 3-part error.task createis repo-bound → a 3-part WHAT/WHY/HOW error outside a repo (honest HOW:--repo owner/namefor GitHub,linear.teamfor Linear).Design (brainstormed, then reviewed)
A new pure
tasklib/projects.pymodels theprojects:registry (in the config cascade, usually the global~/.config/task-cli/config.yaml). Each entry becomes a config overlay that the existingget_backend()resolves unchanged — zero backend special-casing. Projects dedup by backend coordinate (repo/team), not display name. The multi-backend fan-out is an effect and lives incli.py.LoadedConfig.with_overlay()is the new (immutable) seam._current_project_overlayreturnsNoneONLY when genuinely outside a git work tree; a broken in-repooriginsurfaces its real error instead of masquerading as "no projects". Linearlist/searchnow scope to the backend's team (and project, if pinned) so the cross-project view doesn't double-count or leak workspace-wide hits.Acceptance evidence
task listoutside any repo, registry of 3 projects (one Linear group degraded on auth):3-part errors outside a repo (no traceback, exit 2):
Tests
160 passed(pytest) +tests/smoke.shgreen (adds an outside-a-repo clean-degradation check). New/extended:tests/test_projects.py— registry parsing, coordinate dedup, hostile input.tests/test_cli_non_repo.py— grouped list, degraded groups, JSON shape, id routing (GitHub single / ambiguous / Linear-by-team / two-same-team), session fallback, current marker (incl. repo-in-registry), broken-remote-surfaces-error, 3-part errors (GitHub + Linear).tests/test_cli.py—--labelfilters the session view; a filter that excludes all session tickets does NOT fall back (no cross-session leak).tests/test_backends.py— Linearlistteam+project filter;searchteam scoping.Review
Reviewed across models via
review --staged(3 rounds). All P1/P2 correctness findings addressed and regression-tested (filter-before-fallback leak, Linear same-team routing, current-marker-when-registered, swallowed remote error). Remaining findings are subjective style/refactor — declined per smallest-change.🤖 Generated with Claude Code