Skip to content

Release v0.11.0: --json output for tsk list#16

Merged
paxcalpt merged 1 commit intomainfrom
release/0.11.0
Apr 17, 2026
Merged

Release v0.11.0: --json output for tsk list#16
paxcalpt merged 1 commit intomainfrom
release/0.11.0

Conversation

@paxcalpt
Copy link
Copy Markdown
Contributor

Summary

  • New --json flag on tsk list emits tasks as a JSON array on stdout for scripting and LLM consumption (no Rich-table truncation).
  • Each object includes the short display id, stable uuid, and all frontmatter fields (title, status, priority, repo, project, assignees, tags, links, due, created, modified, depends, parent, description). Dates are ISO 8601.
  • Composes with every existing filter (-s, --priority, -r, -p, -a, -t, --archived). Empty results return []. Merge-conflict warnings are redirected to stderr in JSON mode so stdout stays valid JSON.

Bumps version to 0.11.0 (minor — new feature, backwards compatible).

Test plan

  • All 152 existing unit tests pass (uv run pytest tests/ -q)
  • uv run ruff check clean
  • tsk list --json -s pending returns valid JSON parseable by json.load / jq
  • Short id values in JSON match the table view
  • Empty filter case returns []
  • After merge: tag v0.11.0 → PyPI publish, then just release taskrepo in ../homebrew-formulas

🤖 Generated with Claude Code

Adds a --json flag to `tsk list` that emits tasks as a JSON array on
stdout, making the command scriptable and LLM-friendly without the
truncation imposed by the Rich table view.

Each task object includes the short numeric display ID (id), the stable
UUID, and all frontmatter fields. Dates are ISO 8601. Composes with all
existing filters. Empty results return []. Merge-conflict warnings are
redirected to stderr in JSON mode so stdout stays valid JSON.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 17, 2026 16:55
@claude
Copy link
Copy Markdown

claude Bot commented Apr 17, 2026

Code Review: PR #16--json output for tsk list

Overview

This PR adds a --json flag to tsk list that emits tasks as a JSON array on stdout, redirecting conflict warnings to stderr in JSON mode to keep stdout valid JSON. Clean, focused feature addition with good backwards compatibility.


Code Quality & Style

Positive:

  • _task_to_dict is well-structured and explicit about every field — easy to audit.
  • Redirecting Console(stderr=True) in JSON mode is the right approach to keep stdout clean.
  • default=str fallback in json.dump is a sensible safety net.
  • The comment "Do this before serializing so JSON short IDs match the table view" correctly documents the ordering invariant.

Minor issues:

  1. Redundant conditional (list.py, _task_to_dict):

    # Current — both branches produce the same value
    "id": display_id if display_id is not None else None,
    # Simplified
    "id": display_id,
  2. Direct sys.stdout usagejson.dump(payload, sys.stdout, ...) bypasses Click's output layer. Prefer:

    click.echo(json.dumps(payload, indent=2, default=str))

    This ensures correct behavior when stdout is captured in tests or redirected, and avoids mixing sys.stdout.write with Click's output.


Potential Bugs / Issues

  1. Stale display IDs in filtered JSON output — The save_id_cache(sorted_tasks, rebalance=True) call (which assigns stable short IDs) only runs when not has_filters. For filtered views, get_display_id_from_uuid reads whatever is in the cache from the last unfiltered run. The comment says JSON short IDs will "match the table view," but the table also skips rebalancing on filtered views, so the behaviour is consistent. However, if the cache is stale or absent (fresh clone, first run), display_id will be None for all tasks in filtered JSON output. This should be documented or guarded:

    "id": display_id,  # None if cache not populated; run `tsk list` unfiltered first
  2. get_display_id_from_uuid with empty/missing cache — If the ID cache file doesn't exist yet, every task will have "id": null in JSON mode. This is a silent degradation. Consider emitting a stderr warning when display_id is None, or populating the cache on-demand.


Tests

  1. No new tests for --json — The project targets >80% coverage (per CLAUDE.md) and the test plan only mentions existing tests passing. Given this is new user-facing behaviour, at minimum the following cases should have unit tests:
    • Happy path: --json returns a valid JSON array with expected fields.
    • Empty result: --json returns [].
    • Filtered view: --json -s pending returns only pending tasks.
    • Conflict warning goes to stderr (not stdout) in JSON mode.

Security / Performance

No security concerns — data originates from internal task objects. Performance is fine; serializing even thousands of tasks synchronously is negligible.


Summary

Area Assessment
Feature correctness ✅ Works as described
Code style ✅ Clean, mostly follows conventions
Redundant code ⚠️ display_id if display_id is not None else None
Output mechanism ⚠️ Prefer click.echo(json.dumps(...)) over sys.stdout
Stale ID edge case ⚠️ null IDs silently on filtered views without a populated cache
Test coverage ❌ No new tests for the --json flag

The feature is well-motivated and the implementation is solid. Addressing the click.echo vs sys.stdout point and adding tests for the new flag would bring this to a clean merge. The redundant conditional and the null ID edge case are lower priority but worth fixing before the v0.11.0 tag.

🤖 Reviewed with Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds machine-readable JSON output for the tsk list CLI command to support scripting/LLM use cases, and cuts a new minor release.

Changes:

  • Add --json flag to tsk list to emit tasks as a JSON array to stdout (with conflict warnings redirected to stderr).
  • Introduce task-to-JSON serialization helper and ensure ID cache rebalance occurs before JSON serialization for unfiltered views.
  • Bump version to 0.11.0 and document the feature in CHANGELOG.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/taskrepo/cli/commands/list.py Implements --json output mode, JSON serialization, and stderr conflict warnings in JSON mode.
src/taskrepo/__version__.py Version bump to 0.11.0.
CHANGELOG.md Adds 0.11.0 entry documenting the new JSON output feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +24
def _task_to_dict(task) -> dict:
"""Serialize a Task to a JSON-compatible dict.

The ``id`` field is the short numeric display ID (same as the table view);
``uuid`` is the stable underlying identifier. All dates are ISO 8601.
"""
display_id = get_display_id_from_uuid(task.id)
return {
"id": display_id if display_id is not None else None,
Comment on lines +133 to +136
if json_output:
payload = [_task_to_dict(t) for t in sorted_tasks]
json.dump(payload, sys.stdout, indent=2, default=str)
sys.stdout.write("\n")
@paxcalpt paxcalpt merged commit 9ad2804 into main Apr 17, 2026
12 checks passed
@paxcalpt paxcalpt deleted the release/0.11.0 branch April 17, 2026 16:59
paxcalpt added a commit that referenced this pull request Apr 17, 2026
Addresses review feedback on PR #16:

- Load the ID cache once per invocation instead of per-task, eliminating
  O(N²) scans and N redundant file reads for large task lists.
- Switch to click.echo for JSON output so Click's test-capture layer and
  stdout redirection work correctly.
- Emit a stderr hint when the ID cache is empty on a filtered view, so
  null id values aren't silent.
- Drop a redundant conditional on the id field.
- Add 8 unit tests covering task-to-dict serialization, cache loading
  (missing/valid/malformed), and a regression guard against O(N²) reads.

Keeps the JSON schema stable (int | null id, uuid always present).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
paxcalpt added a commit that referenced this pull request Apr 17, 2026
* fix: --json performance and output correctness (v0.11.1)

Addresses review feedback on PR #16:

- Load the ID cache once per invocation instead of per-task, eliminating
  O(N²) scans and N redundant file reads for large task lists.
- Switch to click.echo for JSON output so Click's test-capture layer and
  stdout redirection work correctly.
- Emit a stderr hint when the ID cache is empty on a filtered view, so
  null id values aren't silent.
- Drop a redundant conditional on the id field.
- Add 8 unit tests covering task-to-dict serialization, cache loading
  (missing/valid/malformed), and a regression guard against O(N²) reads.

Keeps the JSON schema stable (int | null id, uuid always present).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* address review feedback on PR #17

- Harden _load_uuid_to_display_id: reject non-dict top-level, filter entries
  missing `uuid`, catch OSError/TypeError (Copilot comment).
- Add one-line comment clarifying the stderr-hint ordering invariant
  (claude[bot] suggestion).
- Fix inaccurate comment about Task.__post_init__ in the test helper
  (Copilot comment).
- Add CLI integration tests via CliRunner covering the exact regression
  path this release fixes: --json output end-to-end, empty result → [],
  and stderr hint on filtered empty-cache runs (claude[bot] suggestion).
- Add unit test for non-dict top-level cache files.

Deferred as follow-up: the save-then-load round-trip on unfiltered runs
(noted by claude[bot], non-blocking).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants