feat(pkg-intel): add package_dependencies (pkg deps + MCP) - #16
Merged
Conversation
jlitola
force-pushed
the
feat/pkg-intel-dependencies
branch
6 times, most recently
from
April 20, 2026 18:58
1d369e8 to
ee9626b
Compare
Adds the `package_dependencies` MCP tool and `githits pkg deps` CLI
command behind the existing `code_navigation` capability gate. Both
surfaces share a single request builder, envelope builder, and
error classifier; a parity test asserts `toEqual` JSON across the
two surfaces for every service-sourced fixture.
## Surfaces
- **`githits pkg deps <spec>`** — default shows a summary row
(counts + hidden-groups mention by name) plus the flat direct-
runtime dependency list. `--transitive` replaces the deps list
with the full unique transitive closure (alphabetical, one per
line). `--transitive --verbose` annotates each entry with
`(required by <importer>@<constraint>, …)` computed from the
DAG. `--groups` / `--lifecycle` adds a structured groups block
beneath the deps list (additive, not a replacement view).
Supports `@version`, uppercase-tolerated `--lifecycle`, `--depth`
1–10 (CLI default 3), `--verbose`, `--json`.
- **`package_dependencies`** — MCP tool with the same lean
envelope. Permissive Zod schema (`lifecycle` accepts string or
string array); no `max_depth` default on the MCP path so the
backend's full-graph default applies.
## Design choices
- **Data-first envelope.** `runtime`, `groups`, `transitive`, and
`filter` are independent blocks emitted based on what the
backend returned and what the caller asked for, not on caller
flags. `runtime` emits whenever the backend returned `direct[]`
(including zero-dep packages); `groups` emits whenever the
backend returned `dependencyGroups`, distinguishing `{items: []}`
(filter matched nothing) from absent (backend has no groups
concept). Dependency lists everywhere use `items` for a single
consistent key.
- **Ergonomic semantic model.** Summary row always leads with the
scope signal; `--transitive` replaces (not augments) the deps
list; groups view is a separate block composing beneath; hidden
groups surface by name in the summary (`Hidden groups: argon2,
bcrypt — use --groups.`) rather than as an aggregate footer.
- **Best-effort DAG decoder** lives in the formatter (not the
service) so the JSON envelope stays fully opaque for
`transitive.dag`. Handles `[registry, name, version]` tuples +
`{n, v, l}` objects on the node side; `[from, to, constraint?,
lifecycle?]` + object form on edges. Returns null on unknown
shapes — provenance silently degrades but the transitive list
still renders.
- **Terminal-only dedup.** Crates target-cfg branches emit
duplicate `{name, constraint}` tuples; the terminal formatter
collapses them for scannability, the JSON envelope preserves
every tuple. A parity fixture exercises the round-trip.
- **Ecosystem-aware vocabulary.** PyPI feature-typed groups
render as `name (optional, extra)` (PEP 508); Cargo keeps
`feature`.
- **No `include_groups` MCP input.** The data-first envelope
makes it a silently-ignored no-op; omitting it forces the
correct mental model.
- **Canonical versions only.** Tag-style `v`-prefixed inputs
rejected client-side with `INVALID_ARGUMENT`.
- **Registry coverage.** npm, PyPI, Hex, Crates, vcpkg, Zig.
Other registries rejected client-side with a tool-specific
message.
- **Shared `promoteGenericVersionNotFound` helper.** Extracted
from P2's inline helper; used by both `packageVulnerabilities`
and `packageDependencies` executors.
- **Untyped passthroughs** (`transitive.dag`,
`transitive.conflicts`, `transitive.circularDependencies`,
`groups.environmentConstraints`) flow through as opaque
`UntypedGenericJSON`. Backend declares them `GenericJSON` and
hasn't published concrete shapes; typing them client-side would
be speculative. Each field has a `TODO(pkgseer-backend)` anchor
flagging the upgrade path when concrete types land upstream.
## CLI UX details
- Summary row always shows counts + hidden-groups-by-name.
- `--transitive` replaces the deps list with the full unique
transitive closure; `--verbose` adds `(required by …)`
provenance per entry.
- Alphabetical sort on every list is explicit and tested.
- `--depth` validates exact integers; partial inputs like `3.5`
or `5abc` are rejected rather than silently truncated.
- `VERSION_NOT_FOUND` error enriched with `package` / `requested`
/ `available` detail lines (reuses P2's helper).
## Tests
- `bun test` — 951 pass, 0 fail.
- `bun run typecheck` / `bun run build` / `bun run lint` — clean.
- Parity fixtures (16) cover: happy flat-runtime, zero-dep,
full-view, optional-lifecycle (tokio features), multi-lifecycle,
filter-matched-nothing, Crates-target-cfg dedup round-trip,
transitive with DAG passthrough, versioned match / real-diff,
`NOT_FOUND`, `VERSION_NOT_FOUND` with structured details,
`BACKEND_ERROR`, and three `INVALID_ARGUMENT` cases.
- Live-verified against production pkgseer across npm / PyPI /
Crates / Hex / vcpkg, with DAG provenance working on real
express data showing importer→constraint relationships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jlitola
force-pushed
the
feat/pkg-intel-dependencies
branch
from
April 20, 2026 19:07
ee9626b to
746082c
Compare
6 tasks
jlitola
added a commit
that referenced
this pull request
Apr 20, 2026
Fourth Wave 1 package-intelligence tool, following `package_summary` (#13), `package_vulnerabilities` (#14), `package_dependencies` (#16 + follow-up #17). Behind the existing `code_navigation` capability gate. Both surfaces share a single request builder, envelope builder, and error classifier; a parity test asserts `toEqual` JSON across the two surfaces for every service-sourced fixture. ## Surfaces - **`githits pkg changelog [spec]`** — default latest-mode output is a summary row (identity · source · mode · entry count) plus a one-line `version date url` per entry, newest-first. `--from <v>` switches to range mode (all entries between `--from` and `--to`/latest; `--limit` rejected). `--to <v>` / `--limit <n>` shape latest mode. `--verbose` expands each entry with its full markdown body, indented and dimmed. `--no-body` drops body fields from both terminal and JSON. `--git-ref` targets a specific branch/tag for CHANGELOG.md source. `--repo-url <url>` is an alternative addressing mode (mutually exclusive with `<spec>`). - **`package_changelog`** — MCP tool with the same envelope. Dual addressing: `registry` + `package_name` XOR `repo_url`. Permissive Zod schema (in-handler validation). `include_bodies` (default true) mirrors CLI's `--no-body`. Description is self-contained — no CLI-flag references. ## Design choices - **Dual addressing is unique to this tool.** P1 / P2 / P3 all accept only `registry` + `package_name` because their underlying backend queries are registry-metadata APIs. `packageChangelog` is intrinsically repo-level (its sources are GitHub Releases, CHANGELOG.md, HexDocs), so `repoUrl` is a peer addressing mode in the GraphQL signature, not a bolt-on. Exposing it on MCP was non-negotiable: `packageSummary` cannot resolve a repo URL → spec, so agents starting from a repo URL had no path in. Documented in `tools.md` so future tool authors don't cargo-cult the asymmetry. - **`<spec>@<version>` rejected.** `pkg vulns` / `pkg deps` treat `@version` as "for this exact version"; changelog has no single- version query. Silently remapping to `toVersion` would be a client-invented semantic shift. Rejected client-side with `INVALID_ARGUMENT` and a hint pointing to `--to` / `--from`. - **Data-first envelope.** `{registry|repoUrl, source, mode, entries: {count, items}, filter?}`. `source` always present here (null-source case is promoted to `NOT_FOUND` at the service boundary and never reaches the envelope). `mode` derived from request — `"range"` iff `fromVersion` non-null after normalisation, `"latest"` otherwise. `entries.count` computed client-side from `items.length`; the backend's count isn't selected on the wire so the invariant holds by construction. - **`version` kept when null, other per-entry nullables stripped.** `version` is the primary key agents index by, so the slot is always present (possibly null); stripping other nullables keeps the envelope lean. `body` additionally stripped under `include_bodies: false`. - **`filter.*` echo tracks explicit fields only.** Request builder tracks `explicitFilterFields` set; envelope emits `filter.*` only for caller-supplied inputs. Backend defaults (`limit: 10`, `toVersion: <latest>`) never round-trip as caller intent. - **`include_bodies` lever.** Release bodies on large packages can run 10 KB+ per entry; `include_bodies: false` drops `body` from all items explicitly (not silent truncation). Other fields preserved so agents still get the version / date / URL timeline. - **`metadata` dropped from envelope in v1.** Source-specific opaque `GenericJSON`; revisit via agent feedback with a `TODO(pkgseer-backend)` anchor if demand surfaces. - **No client-side registry restriction.** Unlike P3's 6-registry client-side gate, `packageChangelog` is source-pull rather than registry-query; the backend returns `NOT_FOUND` for registries without changelog sources. Live-smoke matrix documented in the plan to verify behaviour per registry. - **Mode mutual exclusion enforced client-side.** `--from` / `from_version` + `--limit` / `limit` together → `INVALID_ARGUMENT` with actionable hint. - **`source: null` promoted to NOT_FOUND.** Typed `PackageIntelligenceChangelogSourceNotFoundError` at the service boundary; shared classifier routes to `NOT_FOUND` with a message naming the sources tried (GitHub Releases, CHANGELOG.md, HexDocs). Empty `entries.items: []` with a valid `source` is success, not error. - **`--verbose` vs `--no-body` vs `--json` interaction.** `--verbose` is terminal-only (expands markdown bodies per entry); does not change `--json`. `--no-body` affects both terminal and `--json`. `--json` output shape is independent of `--verbose`. - **Shared `promoteGenericVersionNotFound` extended.** The helper now recognises `fromVersion` / `toVersion` in addition to `version`; preference order `version → fromVersion → toVersion`. `registry` / `packageName` made optional so repo-URL-addressed requests flow through without a spec. P2 / P3 regressions guarded by the existing tests plus 4 new helper tests. ## Tests - `bun test` — 1053 pass, 0 fail (was 966; +87 new tests). - `bun run typecheck` / `bun run build` / `bun run lint` — clean. - Unit tests: request-builder (addressing XOR, `<spec>@<version>` rejection, `--from`/`--limit` mutex, limit bounds, tag-style rejection, pre-release versions on `--from`/`--to`, explicit-flag tracking), envelope builder (shape, null handling, `include_bodies`, mode derivation, filter echo, empty entries), terminal formatter (default one-liners, `--verbose` expansion, missing fields), CLI action (13 cases incl. addressing, errors, flag interactions), MCP tool (15 cases incl. validation + service errors). - Parity test: 12 fixtures (happy latest, range mode, repo-URL addressing, `--no-body` / `include_bodies: false`, default bodies, empty entries, `NOT_FOUND` no-source, `TargetNotFoundError`, `VERSION_NOT_FOUND` with structured details, `BACKEND_ERROR`, `INVALID_ARGUMENT` for `<spec>@<version>` and `--from`+`--limit`). - Live-smoke deferred to review cycle (local non-interactive bash keychain prompt blocks the CLI startup handshake; shape correctness is locked in via the verbatim schema Zod parse + parity test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
jlitola
added a commit
that referenced
this pull request
Apr 21, 2026
Fourth Wave 1 package-intelligence tool, following `package_summary` (#13), `package_vulnerabilities` (#14), `package_dependencies` (#16 + follow-up #17). Behind the existing `code_navigation` capability gate. Both surfaces share a single request builder, envelope builder, and error classifier; a parity test asserts `toEqual` JSON across the two surfaces for every service-sourced fixture. ## Surfaces - **`githits pkg changelog [spec]`** — default latest-mode output is a summary row (identity · source · mode · entry count) plus a one-line `version date url` per entry, newest-first. `--from <v>` switches to range mode (all entries between `--from` and `--to`/latest; `--limit` rejected). `--to <v>` / `--limit <n>` shape latest mode. `--verbose` expands each entry with its full markdown body, indented and dimmed. `--no-body` drops body fields from both terminal and JSON. `--git-ref` targets a specific branch/tag for CHANGELOG.md source. `--repo-url <url>` is an alternative addressing mode (mutually exclusive with `<spec>`). - **`package_changelog`** — MCP tool with the same envelope. Dual addressing: `registry` + `package_name` XOR `repo_url`. Permissive Zod schema (in-handler validation). `include_bodies` (default true) mirrors CLI's `--no-body`. Description is self-contained — no CLI-flag references. ## Design choices - **Dual addressing is unique to this tool.** P1 / P2 / P3 all accept only `registry` + `package_name` because their underlying backend queries are registry-metadata APIs. `packageChangelog` is intrinsically repo-level (its sources are GitHub Releases, CHANGELOG.md, HexDocs), so `repoUrl` is a peer addressing mode in the GraphQL signature, not a bolt-on. Exposing it on MCP was non-negotiable: `packageSummary` cannot resolve a repo URL → spec, so agents starting from a repo URL had no path in. Documented in `tools.md` so future tool authors don't cargo-cult the asymmetry. - **`<spec>@<version>` rejected.** `pkg vulns` / `pkg deps` treat `@version` as "for this exact version"; changelog has no single- version query. Silently remapping to `toVersion` would be a client-invented semantic shift. Rejected client-side with `INVALID_ARGUMENT` and a hint pointing to `--to` / `--from`. - **Data-first envelope.** `{registry|repoUrl, source, mode, entries: {count, items}, filter?}`. `source` always present here (null-source case is promoted to `NOT_FOUND` at the service boundary and never reaches the envelope). `mode` derived from request — `"range"` iff `fromVersion` non-null after normalisation, `"latest"` otherwise. `entries.count` computed client-side from `items.length`; the backend's count isn't selected on the wire so the invariant holds by construction. - **`version` kept when null, other per-entry nullables stripped.** `version` is the primary key agents index by, so the slot is always present (possibly null); stripping other nullables keeps the envelope lean. `body` additionally stripped under `include_bodies: false`. - **`filter.*` echo tracks explicit fields only.** Request builder tracks `explicitFilterFields` set; envelope emits `filter.*` only for caller-supplied inputs. Backend defaults (`limit: 10`, `toVersion: <latest>`) never round-trip as caller intent. - **`include_bodies` lever.** Release bodies on large packages can run 10 KB+ per entry; `include_bodies: false` drops `body` from all items explicitly (not silent truncation). Other fields preserved so agents still get the version / date / URL timeline. - **`metadata` dropped from envelope in v1.** Source-specific opaque `GenericJSON`; revisit via agent feedback with a `TODO(pkgseer-backend)` anchor if demand surfaces. - **No client-side registry restriction.** Unlike P3's 6-registry client-side gate, `packageChangelog` is source-pull rather than registry-query; the backend returns `NOT_FOUND` for registries without changelog sources. Live-smoke matrix documented in the plan to verify behaviour per registry. - **Mode mutual exclusion enforced client-side.** `--from` / `from_version` + `--limit` / `limit` together → `INVALID_ARGUMENT` with actionable hint. - **`source: null` promoted to NOT_FOUND.** Typed `PackageIntelligenceChangelogSourceNotFoundError` at the service boundary; shared classifier routes to `NOT_FOUND` with a message naming the sources tried (GitHub Releases, CHANGELOG.md, HexDocs). Empty `entries.items: []` with a valid `source` is success, not error. - **`--verbose` vs `--no-body` vs `--json` interaction.** `--verbose` is terminal-only (expands markdown bodies per entry); does not change `--json`. `--no-body` affects both terminal and `--json`. `--json` output shape is independent of `--verbose`. - **Shared `promoteGenericVersionNotFound` extended.** The helper now recognises `fromVersion` / `toVersion` in addition to `version`; preference order `version → fromVersion → toVersion`. `registry` / `packageName` made optional so repo-URL-addressed requests flow through without a spec. P2 / P3 regressions guarded by the existing tests plus 4 new helper tests. ## Tests - `bun test` — 1053 pass, 0 fail (was 966; +87 new tests). - `bun run typecheck` / `bun run build` / `bun run lint` — clean. - Unit tests: request-builder (addressing XOR, `<spec>@<version>` rejection, `--from`/`--limit` mutex, limit bounds, tag-style rejection, pre-release versions on `--from`/`--to`, explicit-flag tracking), envelope builder (shape, null handling, `include_bodies`, mode derivation, filter echo, empty entries), terminal formatter (default one-liners, `--verbose` expansion, missing fields), CLI action (13 cases incl. addressing, errors, flag interactions), MCP tool (15 cases incl. validation + service errors). - Parity test: 12 fixtures (happy latest, range mode, repo-URL addressing, `--no-body` / `include_bodies: false`, default bodies, empty entries, `NOT_FOUND` no-source, `TargetNotFoundError`, `VERSION_NOT_FOUND` with structured details, `BACKEND_ERROR`, `INVALID_ARGUMENT` for `<spec>@<version>` and `--from`+`--limit`). - Live-smoke deferred to review cycle (local non-interactive bash keychain prompt blocks the CLI startup handshake; shape correctness is locked in via the verbatim schema Zod parse + parity test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jlitola
added a commit
that referenced
this pull request
Apr 21, 2026
Fourth Wave 1 package-intelligence tool, following `package_summary` (#13), `package_vulnerabilities` (#14), `package_dependencies` (#16 + follow-up #17). Behind the existing `code_navigation` capability gate. Both surfaces share a single request builder, envelope builder, and error classifier; a parity test asserts `toEqual` JSON across the two surfaces for every service-sourced fixture. ## Surfaces - **`githits pkg changelog [spec]`** — default latest-mode output is a summary row (identity · source · mode · entry count) plus, per entry, a `version date url` header followed by the first 10 lines of the markdown body (with a `… (+N more lines — use --verbose for the full body)` footer when the body is longer). `--verbose` uncaps the body preview; `--no-body` drops bodies entirely from both terminal and `--json`. `--from <v>` switches to range mode (all entries between `--from` and `--to`/latest; `--limit` rejected). `--to <v>` / `--limit <n>` shape latest mode. `--git-ref` targets a branch/tag for CHANGELOG.md source. `--repo-url <url>` is an alternative addressing mode, mutually exclusive with `<spec>`. - **`package_changelog`** — MCP tool with the same envelope. Dual addressing: `registry` + `package_name` XOR `repo_url`. Permissive Zod schema + in-handler validation. `include_bodies` (default true) mirrors CLI's `--no-body` and controls JSON envelope bodies. ## Design choices - **Dual addressing is unique to this tool.** P1 / P2 / P3 all accept only `registry` + `package_name` because their underlying backend queries are registry-metadata APIs. `packageChangelog` is intrinsically repo-level (sources: GitHub Releases, CHANGELOG.md, HexDocs), so `repoUrl` is a peer addressing mode in the GraphQL signature. Exposing it on MCP was non-negotiable: `packageSummary` cannot resolve repo-URL → spec, so agents starting from a repo URL had no path in. Documented in `tools.md` so future tool authors don't cargo-cult the asymmetry. - **`<spec>@<version>` rejected.** `pkg vulns` / `pkg deps` treat `@version` as "for this exact version"; changelog has no single-version query. Silently remapping to `toVersion` would be a client-invented semantic shift. Rejected with `INVALID_ARGUMENT` and a hint pointing to `--to` / `--from`. - **Body preview capped at 10 lines by default.** Release bodies routinely run 50-100+ lines — showing them unbounded would swamp the terminal; showing none would make the whole command useless for answering "what changed". 10-line cap shows the first one or two sections plus preamble, which is usually enough to answer the question; `--verbose` lifts the cap when full context is needed; `--no-body` drops bodies entirely for a pure timeline view. JSON envelope is always full-body unless `--no-body` / `include_bodies: false` is set — `--verbose` is terminal-only. - **Data-first envelope.** `{registry|repoUrl, source, mode, entries: {count, items}, filter?}`. `source` always present here (null-source case promoted to `NOT_FOUND` at the service boundary; envelope builder has a defence-in-depth throw if the invariant is ever violated upstream). `mode` derived from request — `"range"` iff `fromVersion` non-null, `"latest"` otherwise. `entries.count` computed client-side from `items.length`; backend count not selected on the wire. - **`version` kept when null, other per-entry nullables stripped.** `version` is the primary key agents index by. Empty-string values (e.g. `body: ""`) are preserved — distinct from absent — so agents can tell "empty release notes" from "no notes field". Terminal formatter renders `(empty release notes)` sentinel for empty-string bodies. - **`filter.*` tracks explicit fields only.** Builder returns an `explicitFilterFields` set; envelope emits `filter.*` only for caller-supplied inputs. Backend defaults (`limit: 10`, `toVersion: latest`) never round-trip as caller intent. - **`include_bodies` lever.** Default true. `false` drops `body` from every item explicitly; other fields preserved so agents still get the version / date / URL timeline. Measured 5.13× envelope size reduction on a 20-entry `npm:typescript` request (17.3 KB → 3.4 KB). - **`--no-body` + `--verbose` is an error.** Contradictory intents; CLI rejects with an actionable hint rather than silently ignoring `--verbose`. - **MCP `limit` validation in the shared builder, not Zod.** If Zod hard-rejected `limit: 51` at the SDK level, the agent would see a raw SDK error instead of our shared `{error, code, retryable}` envelope. Schema is permissive; builder enforces bounds. - **Registry coverage — all 9 on the wire, no client-side restriction.** `packageChangelog` is source-pull, not registry-query. Live-smoke confirmed npm / pypi / hex / crates / vcpkg / maven / packagist return useful data; nuget returns a generic `BACKEND_ERROR`; zig packages weren't in the backend index. All paths produce the shared envelope correctly. - **Mode mutex enforced client-side.** `--from` / `from_version` + `--limit` / `limit` → `INVALID_ARGUMENT` with actionable hint. - **`metadata` dropped in v1.** Source-specific opaque `GenericJSON`; revisit via agent feedback. - **Shared `promoteGenericVersionNotFound` extended.** Now recognises `fromVersion` / `toVersion` in addition to `version`. `registry` / `packageName` made optional so repo-URL-addressed requests flow through. Preference order `version → fromVersion → toVersion`. P2 / P3 regressions guarded by existing tests + 5 new helper tests. ## Tests - `bun test` — 1062 pass, 0 fail. - `bun run typecheck` / `bun run build` / `bun run lint` — clean. - Request builder, envelope builder, terminal formatter, MCP tool, CLI action, parity fixtures (12) — see docs for full matrix. - Live-smoke against production pkgseer: latest / range / repo-URL / `--no-body` / error paths / registry matrix / body-preview cap / `--verbose` uncap / truncation footer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jlitola
added a commit
that referenced
this pull request
Apr 21, 2026
Fourth Wave 1 package-intelligence tool, following `package_summary` (#13), `package_vulnerabilities` (#14), `package_dependencies` (#16 + follow-up #17). Behind the existing `code_navigation` capability gate. Both surfaces share a single request builder, envelope builder, and error classifier; a parity test asserts `toEqual` JSON across the two surfaces for every service-sourced fixture. ## Surfaces - **`githits pkg changelog [spec]`** — default latest-mode output is a summary row (identity · source · mode · entry count) plus, per entry, a `version date url` header followed by the first 10 lines of the markdown body (with a `… (+N more lines — use --verbose for the full body)` footer when the body is longer). `--verbose` uncaps the body preview; `--no-body` drops bodies entirely from both terminal and `--json`. `--from <v>` switches to range mode (all entries between `--from` and `--to`/latest; `--limit` rejected). `--to <v>` / `--limit <n>` shape latest mode. `--git-ref` targets a branch/tag for CHANGELOG.md source. `--repo-url <url>` is an alternative addressing mode, mutually exclusive with `<spec>`. - **`package_changelog`** — MCP tool with the same envelope. Dual addressing: `registry` + `package_name` XOR `repo_url`. Permissive Zod schema + in-handler validation. `include_bodies` (default true) mirrors CLI's `--no-body` and controls JSON envelope bodies. ## Design choices - **Dual addressing is unique to this tool.** P1 / P2 / P3 all accept only `registry` + `package_name` because their underlying backend queries are registry-metadata APIs. `packageChangelog` is intrinsically repo-level (sources: GitHub Releases, CHANGELOG.md, HexDocs), so `repoUrl` is a peer addressing mode in the GraphQL signature. Exposing it on MCP was non-negotiable: `packageSummary` cannot resolve repo-URL → spec, so agents starting from a repo URL had no path in. Documented in `tools.md` so future tool authors don't cargo-cult the asymmetry. - **`<spec>@<version>` rejected.** `pkg vulns` / `pkg deps` treat `@version` as "for this exact version"; changelog has no single-version query. Silently remapping to `toVersion` would be a client-invented semantic shift. Rejected with `INVALID_ARGUMENT` and a hint pointing to `--to` / `--from`. - **Body preview capped at 10 lines by default.** Release bodies routinely run 50-100+ lines — showing them unbounded would swamp the terminal; showing none would make the whole command useless for answering "what changed". 10-line cap shows the first one or two sections plus preamble, which is usually enough to answer the question; `--verbose` lifts the cap when full context is needed; `--no-body` drops bodies entirely for a pure timeline view. JSON envelope is always full-body unless `--no-body` / `include_bodies: false` is set — `--verbose` is terminal-only. - **Data-first envelope.** `{registry|repoUrl, source, mode, entries: {count, items}, filter?}`. `source` always present here (null-source case promoted to `NOT_FOUND` at the service boundary; envelope builder has a defence-in-depth throw if the invariant is ever violated upstream). `mode` derived from request — `"range"` iff `fromVersion` non-null, `"latest"` otherwise. `entries.count` computed client-side from `items.length`; backend count not selected on the wire. - **`version` kept when null, other per-entry nullables stripped.** `version` is the primary key agents index by. Empty-string values (e.g. `body: ""`) are preserved — distinct from absent — so agents can tell "empty release notes" from "no notes field". Terminal formatter renders `(empty release notes)` sentinel for empty-string bodies. - **`filter.*` tracks explicit fields only.** Builder returns an `explicitFilterFields` set; envelope emits `filter.*` only for caller-supplied inputs. Backend defaults (`limit: 10`, `toVersion: latest`) never round-trip as caller intent. - **`include_bodies` lever.** Default true. `false` drops `body` from every item explicitly; other fields preserved so agents still get the version / date / URL timeline. Measured 5.13× envelope size reduction on a 20-entry `npm:typescript` request (17.3 KB → 3.4 KB). - **`--no-body` + `--verbose` is an error.** Contradictory intents; CLI rejects with an actionable hint rather than silently ignoring `--verbose`. - **MCP `limit` validation in the shared builder, not Zod.** If Zod hard-rejected `limit: 51` at the SDK level, the agent would see a raw SDK error instead of our shared `{error, code, retryable}` envelope. Schema is permissive; builder enforces bounds. - **Registry coverage — all 9 on the wire, no client-side restriction.** `packageChangelog` is source-pull, not registry-query. Live-smoke confirmed npm / pypi / hex / crates / vcpkg / maven / packagist return useful data; nuget returns a generic `BACKEND_ERROR`; zig packages weren't in the backend index. All paths produce the shared envelope correctly. - **Mode mutex enforced client-side.** `--from` / `from_version` + `--limit` / `limit` → `INVALID_ARGUMENT` with actionable hint. - **`metadata` dropped in v1.** Source-specific opaque `GenericJSON`; revisit via agent feedback. - **Shared `promoteGenericVersionNotFound` extended.** Now recognises `fromVersion` / `toVersion` in addition to `version`. `registry` / `packageName` made optional so repo-URL-addressed requests flow through. Preference order `version → fromVersion → toVersion`. P2 / P3 regressions guarded by existing tests + 5 new helper tests. ## Tests - `bun test` — 1062 pass, 0 fail. - `bun run typecheck` / `bun run build` / `bun run lint` — clean. - Request builder, envelope builder, terminal formatter, MCP tool, CLI action, parity fixtures (12) — see docs for full matrix. - Live-smoke against production pkgseer: latest / range / repo-URL / `--no-body` / error paths / registry matrix / body-preview cap / `--verbose` uncap / truncation footer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third Wave 1 package-intelligence tool, following
package_summary(#13) andpackage_vulnerabilities(#14). Behind the existingcode_navigationcapability gate.githits pkg deps <spec>— default shows a summary row (counts + hidden-groups mentioned by name) plus the direct-runtime dependency list asname@resolved-version.--transitivereplaces the deps list with the full unique transitive closure (alphabetical, one per line, with conflict / cycle counts on the summary). In both modes,--verboseexpands each entry to a multi-line block:name@versionfollowed by indented- <constraint> required by importer@version, …bullets, with importers sharing a constraint collapsed onto one bullet.--verbosealso expands summary-level conflicts and circular dependencies into structured blocks.--groups/--lifecycleadds a structured groups block beneath the deps list (additive, not a replacement view). Supports@version, uppercase-tolerated--lifecycle,--depth1–10 (no default — backend's full-graph traversal),--json.package_dependencies— MCP tool with the same envelope. Permissive Zod schema (lifecycleaccepts string or string array); opt-ininclude_transitive,include_importers(requiresinclude_transitive),max_depth(requiresinclude_transitive). Descriptions are self-contained (no CLI-flag references) so agents can use the tool without extra context.Design choices
runtime,groups,transitive, andfilterare independent blocks emitted based on what the backend returned and what the caller asked for, not on caller flags.runtimeemits whenever the backend returneddirect[](including zero-dep packages);groupsemits whenever the backend returneddependencyGroups, distinguishing{items: []}(filter matched nothing) from absent (backend has no groups concept). Dependency lists everywhere useitemsfor a single consistent key.transitive.packages[]ships as{name, version}records, andinclude_importerslayers on per-packageimporters[]with importer name / version / constraint. Raw backend DAG is deliberately dropped from this PR's envelope — a futurepkg deps-dagcommand will expose a typed graph surface under a maintained contract.transitive.conflicts[]andtransitive.circularDependencies[]are typed ({name, requiredVersions}/{cycle: string[]}) when the observed backend shape decodes; raw passthrough otherwise.include_importersis opt-in because adding provenance roughly quadruples payload size on heavy graphs (≈59 KB → 14 KB on jest without it). CLI's--verbosewires through to enable it in both terminal output and--json— agents reading CLI JSON get the same lean-vs-rich toggle.name@resolved-versionalongside its constraint — matching what the package will actually install. Costs one extra backend round-trip vs direct-only; intentional for UX.include_importerswithoutinclude_transitive,max_depthwithoutinclude_transitive, and CLI--depthwithout--transitiveare all rejected withINVALID_ARGUMENTrather than silently ignored. Each case has an explicit test.name (optional, extra)(PEP 508 terminology); Cargo keepsfeature.{name, constraint}tuples; the terminal formatter collapses them for scannability, the JSON envelope preserves every tuple. A parity fixture exercises the round-trip.include_groupsMCP input. The data-first envelope makes it a silently-ignored no-op; omitting it forces the correct mental model.v-prefixed inputs rejected client-side withINVALID_ARGUMENTbefore the backend call (carries over from P2).packageDependenciesquery. Other registries are rejected client-side with a tool-specific message.promoteGenericVersionNotFoundhelper. Extracted from P2's inline helper and now used by bothpackageVulnerabilitiesandpackageDependenciesexecutors.transitive.conflicts,transitive.circularDependencies(when decoding fails), andgroups.environmentConstraintsflow through as opaqueUntypedGenericJSON. Each field has aTODO(pkgseer-backend)anchor flagging the upgrade path when concrete types land upstream.CLI UX details
Hidden groups: development — use --groups.).--lifecycleimplies--groups— no flat projection for non-runtime lifecycles on the wire.--depthvalidates exact integers; partial inputs like3.5or5abcare rejected rather than silently truncated.VERSION_NOT_FOUNDerror enriched withpackage/requested/availabledetail lines (reuses P2's helper).Tests
bun test— 962 pass, 0 fail.bun run typecheck/bun run build/bun run lint— clean.groups), full-view, optional-lifecycle (tokio features), multi-lifecycle, filter-matched-nothing (groups: {items: []}), Crates-target-cfg dedup round-trip, transitive with importers, lean-default transitive (no importers), versioned match / real-diff,NOT_FOUND,VERSION_NOT_FOUNDwith structured details,BACKEND_ERROR, threeINVALID_ARGUMENTcases.Test plan
bun testpasses (962 / 0)bun run typecheckcleanbun run buildcleanbun run lintcleanpkg depsscenarios smoked against a token withcode_navigationcapabilitytools/listshowspackage_dependenciesunder an open-gate token; server-levelinstructionsinclude the bulletpkg deps --helprenders under an open-gate token