Skip to content

feat(cli): add path query (jaq over the cache) and path kind#114

Merged
benbaarber merged 7 commits into
mainfrom
ben/query
Jul 9, 2026
Merged

feat(cli): add path query (jaq over the cache) and path kind#114
benbaarber merged 7 commits into
mainfrom
ben/query

Conversation

@benbaarber

@benbaarber benbaarber commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Adds path query — loads every step in the local cache into one JSON array and transforms it with an in-process jaq (pure-Rust jq) filter (selection, projection, ranking, grouping, top-N). Each element wraps a Toolpath step (step/change/meta verbatim) with cache_id, path, and dead_end. Scope flags pick which docs load (--source/--id/--input, --project/--kind); output mirrors jq (-c compact, -r raw).

Also adds path kind — the cold-start companion that lists bundled kind specs or prints a kind's schema.json (the field reference for writing filters).

Full design and rationale: docs/superpowers/specs/2026-06-22-path-query-command-design.md

Breaking (pre-1.0): path query ancestorspath p query ancestors; dead-ends/filter are now jaq forms (map(select(.dead_end)), map(select(.step.actor | startswith("agent:")))). path-cli 0.14.0 → 0.15.0; adds jaq-core/jaq-std/jaq-json.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

🔍 Preview deployed: https://7fb87a72.toolpath.pages.dev

@benbaarber benbaarber requested a review from akesling July 1, 2026 14:28
@akesling akesling assigned benbaarber and unassigned akesling Jul 1, 2026
ben-emp pushed a commit that referenced this pull request Jul 6, 2026
…oc fixes

Addresses the second review of PR #114 (8 findings).

- plan.rs: scalar `add` no longer decomposes — float addition is not
  associative, so summing per-file partials could change the answer
  (files [[1e100], [-1e100, 1]]: decomposed 0.0, true sum 1.0). Only
  `length` (exact integer counts) keeps a scalar combine; the `map`/top-N
  `add` combines are array concatenation and unaffected (finding 1).
- toolpath-cli: bump the out-of-workspace shim to 0.15.0 and its path-cli
  pin to 0.15.0 so it resolves again; crates.json entry updated (finding 2).
- mod.rs: `--id X --source Y` records X as seen before the source filter,
  so a non-Y document yields an empty intersection instead of a false
  "no cached document with id" error (finding 3).
- mod.rs: `--input` cache_id is the path as given, not `file_stem()`, so
  same-basename inputs keep distinct identity triples (finding 4).
- plan.rs/CHANGELOG/CLAUDE.md: docs no longer claim `min`/`max` (or scalar
  `add`) decompose; the code comment explains why each slurps (finding 5).
- README.md/site/pages/cli.md: CLI reference block shows the real `query`
  and `kind` surface and `p query ancestors`, not the removed subcommands
  (finding 6).
- mod.rs: stdin double-parse failure reports both the JSON and JSONL
  errors instead of masking the JSON one (finding 7).
- comment cleanup per repo style: drop the Cargo.toml dep rationale and
  past-state narration in cmd_p_query.rs and test files (finding 8).

Below-the-cut: `TOOLPATH_QUERY_EXPLAIN=0` disables explain; `path p` help
lists `query`; BUNDLED_KINDS uses the `toolpath::v1` kind-URI constants.
ben-emp pushed a commit that referenced this pull request Jul 7, 2026
…oc fixes

Addresses the second review of PR #114 (8 findings).

- plan.rs: scalar `add` no longer decomposes — float addition is not
  associative, so summing per-file partials could change the answer
  (files [[1e100], [-1e100, 1]]: decomposed 0.0, true sum 1.0). Only
  `length` (exact integer counts) keeps a scalar combine; the `map`/top-N
  `add` combines are array concatenation and unaffected (finding 1).
- toolpath-cli: bump the out-of-workspace shim to 0.15.0 and its path-cli
  pin to 0.15.0 so it resolves again; crates.json entry updated (finding 2).
- mod.rs: `--id X --source Y` records X as seen before the source filter,
  so a non-Y document yields an empty intersection instead of a false
  "no cached document with id" error (finding 3).
- mod.rs: `--input` cache_id is the path as given, not `file_stem()`, so
  same-basename inputs keep distinct identity triples (finding 4).
- plan.rs/CHANGELOG/CLAUDE.md: docs no longer claim `min`/`max` (or scalar
  `add`) decompose; the code comment explains why each slurps (finding 5).
- README.md/site/pages/cli.md: CLI reference block shows the real `query`
  and `kind` surface and `p query ancestors`, not the removed subcommands
  (finding 6).
- mod.rs: stdin double-parse failure reports both the JSON and JSONL
  errors instead of masking the JSON one (finding 7).
- comment cleanup per repo style: drop the Cargo.toml dep rationale and
  past-state narration in cmd_p_query.rs and test files (finding 8).

Below-the-cut: `TOOLPATH_QUERY_EXPLAIN=0` disables explain; `path p` help
lists `query`; BUNDLED_KINDS uses the `toolpath::v1` kind-URI constants.
@benbaarber benbaarber assigned akesling and unassigned benbaarber Jul 8, 2026

@akesling akesling left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I run cargo run --bin path query right now it just prints everything in the cache. Perhaps this shouldn't be the default behavior? What should the default behavior / first user experience / first agent experience be when they try to use path query?

Otherwise, LGTM

@akesling akesling assigned benbaarber and unassigned akesling Jul 9, 2026
`path query` loads every step in the local cache into one JSON array and
transforms it with an in-process jaq (pure-Rust jq) filter — selection,
projection, ranking, grouping, and top-N are all jaq. Each array element
wraps a Toolpath step (step/change/meta verbatim) with `cache_id`, `path`
(the parent path's id/base/meta), and `dead_end` (off the head's ancestry).

Scope flags choose which docs load: `--source`/`--id`/`--input` (file
selection) and `--project`/`--kind` (content scoping, semver-prefix kind
match). Output mirrors jq: pretty on a TTY, compact when piped (`-c`), raw
strings with `-r`. Malformed cache docs are skipped with a stderr warning;
empty results exit 0, filter errors exit 1.

`path kind` is the cold-start companion: lists the bundled kind specs, or
prints a kind's schema.json (the field reference for writing filters).
`--kind` and `path kind` share one semver-prefix selector, sourced from a
new shared `kinds` module that `schema.rs` now also reads.

BREAKING (pre-1.0): the former `path query` subcommands change. `ancestors`
moves to `path p query ancestors`; `dead-ends`/`filter` become jaq forms
(`map(select(.dead_end))`, `map(select(.step.actor | startswith("agent:")))`).

path-cli 0.14.0 -> 0.15.0; adds jaq-core/jaq-std/jaq-json.
…ache

`path query` no longer loads every cached step into one array up front. The
executor reads the filter — it parses the jaq into jaq's own AST and picks an
execution plan:

- **PerFileStream** — element-wise filters (`.[] | g`, distributive for any g)
  run per document and print as they go; nothing accumulates.
- **Decompose** — algebraic aggregations run the filter per file, concatenate
  the per-file outputs, then run a derived combine over them: `map` → `add`,
  top-N `sort_by(k) | .[:N]` → `add | sort_by(k) | .[:N]`, `length`/`add` →
  `add`, `min`/`max` → themselves. A global top-N is a subset of the per-file
  top-Ns, so the merge is exact and bounded (files × N).
- **Slurp** — the always-correct fallback for anything not provably
  decomposable (`group_by`, `unique`, `as`-bindings, `reduce`, …). Still lean:
  values are held once, with no whole-cache byte buffer.

Recognition is conservative (a non-distributive prefix such as `unique` before
a top-N slurps), so the planner never changes an answer. Unit tests assert the
streamed output equals the slurp output byte-for-byte across element-wise,
top-N, and scalar-reduction filters; integration tests confirm cross-file top-N
and sums end to end. `TOOLPATH_QUERY_EXPLAIN=1` prints the chosen plan to
stderr. No user-facing flag — it's automatic.
Addresses a code review of the streaming-planner commit: eight cases where the
"planner never changes an answer" contract was violated, or an explicitly named
input failed silently.

- plan.rs: `is_slice_upto` decomposes `.[:N]` only for a literal nonnegative
  integer N. `.[:-1]` / `.[:length-1]` truncate per file before the merge, so
  they slurp (finding 1).
- plan.rs: drop `min`/`max` from scalar reductions — `[] | min == null` from an
  empty (or fully scoped-out) document poisons the merge; they slurp (finding 2).
- plan.rs: parse-guard the derived reduce — a source-span-recovered combine that
  doesn't reparse (e.g. a parenthesized `sort_by`) falls back to slurp instead
  of emitting a broken filter (finding 4).
- filter.rs: a Decompose over zero contributing documents runs the main filter
  over `[]` (matches slurp: `length`→0, top-N→[]) instead of reducing over `[]`
  (which gave null / "cannot use null as array") (finding 3).
- kinds.rs: a present-but-unparseable version segment (`/vgarbage`, `/v1.x`) no
  longer fails open to "any version" — the selector matches nothing and
  `path kind` reports no bundled spec (finding 5).
- mod.rs: an explicit `--input`/`--id` that won't read/parse, or a `--id` that
  matches no cached document, is now an error, not a skip-with-warning that
  returns a wrong answer. Warn-and-skip stays for the whole-cache scan. Stdin
  (`--input -`) accepts JSONL too (findings 6, 7).

The slurp-equality tests now assert the chosen plan is non-slurp, so they can't
silently vacate into slurp-vs-slurp (finding 8), plus a regression per finding.
Also: BufWriter on the streaming output path, and a test tying the bundled kind
URIs to the `toolpath` constants.
…oc fixes

Addresses the second review of PR #114 (8 findings).

- plan.rs: scalar `add` no longer decomposes — float addition is not
  associative, so summing per-file partials could change the answer
  (files [[1e100], [-1e100, 1]]: decomposed 0.0, true sum 1.0). Only
  `length` (exact integer counts) keeps a scalar combine; the `map`/top-N
  `add` combines are array concatenation and unaffected (finding 1).
- toolpath-cli: bump the out-of-workspace shim to 0.15.0 and its path-cli
  pin to 0.15.0 so it resolves again; crates.json entry updated (finding 2).
- mod.rs: `--id X --source Y` records X as seen before the source filter,
  so a non-Y document yields an empty intersection instead of a false
  "no cached document with id" error (finding 3).
- mod.rs: `--input` cache_id is the path as given, not `file_stem()`, so
  same-basename inputs keep distinct identity triples (finding 4).
- plan.rs/CHANGELOG/CLAUDE.md: docs no longer claim `min`/`max` (or scalar
  `add`) decompose; the code comment explains why each slurps (finding 5).
- README.md/site/pages/cli.md: CLI reference block shows the real `query`
  and `kind` surface and `p query ancestors`, not the removed subcommands
  (finding 6).
- mod.rs: stdin double-parse failure reports both the JSON and JSONL
  errors instead of masking the JSON one (finding 7).
- comment cleanup per repo style: drop the Cargo.toml dep rationale and
  past-state narration in cmd_p_query.rs and test files (finding 8).

Below-the-cut: `TOOLPATH_QUERY_EXPLAIN=0` disables explain; `path p` help
lists `query`; BUNDLED_KINDS uses the `toolpath::v1` kind-URI constants.
Bare `path query` (or scope flags with no filter) used to default to `.`
and dump the entire cache to the terminal. The filter is now a required
positional; clap prints usage on omission, and `path query .` says it
explicitly.
@benbaarber

Copy link
Copy Markdown
Collaborator Author

If I run cargo run --bin path query right now it just prints everything in the cache. Perhaps this shouldn't be the default behavior? What should the default behavior / first user experience / first agent experience be when they try to use path query?

Otherwise, LGTM

The positional argument for path query is now required, so running that command should print a usage message.

@benbaarber benbaarber merged commit 41e00e5 into main Jul 9, 2026
2 checks passed
ben-emp pushed a commit that referenced this pull request Jul 15, 2026
Main's #114 replaced path-cli's KIND_SCHEMAS const with the BUNDLED_KINDS
registry, keying the v1.1.0 entry on PATH_KIND_AGENT_CODING_SESSION — which
this branch repoints at v1.2.0. Give v1.1.0 its own constant
(PATH_KIND_AGENT_CODING_SESSION_V1_1_0), register v1.2.0 in BUNDLED_KINDS,
and update the newest-kind expectations in the kind/query/schema tests.
Also restore the compaction changelog entry the rebase dropped and refresh
CLAUDE.md's kind-constant note.
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