Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The CLI exposes three primary commands (`search`, `languages`, `feedback`) that
| `pkg info <spec>` | package spec | `--verbose`, `--json` | Show a package overview (latest version, downloads, license, vulnerabilities) |
| `pkg vulns <spec>` | package spec (optional `@version`) | `--severity`, `--include-withdrawn`, `--verbose`, `--json` | List known vulnerabilities for a package (npm/pypi/hex/crates) |
| `pkg deps <spec>` | package spec (optional `@version`) | `--groups`, `--lifecycle`, `--transitive`, `--depth`, `--verbose`, `--json` | Analyse dependencies: direct runtime deps, structured groups, optional transitive graph (npm/pypi/hex/crates/vcpkg/zig) |
| `pkg changelog [spec]` | package spec OR `--repo-url` | `--from`, `--to`, `--limit`, `--git-ref`, `--no-body`, `--verbose`, `--json` | Release notes / changelog entries for a package or GitHub repo (GitHub Releases, CHANGELOG.md, or HexDocs). Default shows each entry with a 10-line body preview; `--verbose` uncaps, `--no-body` drops. |

### `githits init`

Expand Down Expand Up @@ -186,6 +187,43 @@ Analyses dependencies for a package on npm, PyPI, Hex, Crates, vcpkg, or Zig. De

**Troubleshooting.** Same debug areas as `pkg info` / `pkg vulns` (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures).

### `githits pkg changelog`

```
githits pkg changelog npm:express
githits pkg changelog npm:express --from 4.0.0
githits pkg changelog npm:express --to 4.18.0 --limit 5
githits pkg changelog --repo-url https://github.com/expressjs/express --git-ref main
githits pkg changelog npm:express --json
githits pkg changelog pypi:requests --no-body --json # lean timeline
```

Fetches release notes or changelog entries for a package or GitHub repository. Output is a newest-first list with a summary header identifying the source (GitHub Releases, CHANGELOG.md, or HexDocs).

**Addressing.** `<spec>` (`registry:name`, same parser as `pkg info` / `pkg vulns` / `pkg deps`) **or** `--repo-url <url>`, mutually exclusive. Unlike the other `pkg` commands, `pkg changelog` is intrinsically repo-level on the backend — its sources live on the repo, not the registry — so repo-URL addressing is a first-class peer mode.

**`<spec>@<version>` rejected.** `pkg vulns` and `pkg deps` both treat `@version` as "for this exact version", but `pkg changelog` has no single-version query: all entries live on a timeline. Remapping `@version` to `--to` would be a silent semantic shift. CLI rejects with `INVALID_ARGUMENT` and a hint pointing to `--to <version>` (or `--from <version>` for range mode).

**Two modes.** Latest mode is the default; `--limit <n>` (1–50, backend default 10) caps entry count. `--from <version>` switches to range mode — returns every entry between `--from` and `--to` (or latest) with no count cap. `--to <version>` works in either mode. `--from` + `--limit` together is rejected client-side with a hint.

**Pre-release versions.** Backend-normalised versions flow through unchanged (`5.0.0-rc.1`, `2.32.0.dev0`, `1.7.0-rc.5` round-trip cleanly on `--from` / `--to`). Tag-style `v`-prefixed inputs are rejected on any version flag, consistent with `pkg vulns` / `pkg deps`.

**Default terminal output.** Summary header (`name · registry · source · mode · entry count`) followed by each entry's `version date url` header plus the first 10 lines of its markdown body, indented and dimmed. Bodies longer than the cap show a footer `… (+N more lines — use --verbose for the full body)`. Missing dates render as `—`; missing versions render as `(unversioned)`. The version column is padded to the longest entry in the current response (no fixed width).

**`--verbose`.** Uncaps the body preview — every entry's full markdown body renders, indented and dimmed, with no truncation footer. Terminal-only — does not change `--json` output.

**`--no-body`.** Drops body fields from entries. Affects both terminal output (no body preview, no footer) and `--json` (entry objects lose the `body` field). Mirrors MCP's `include_bodies: false`. Default `--json` keeps full markdown bodies; use `--no-body` when you only need the version / date / URL timeline (drops 10 KB+ per entry on large release notes — measured 5.13× size reduction on `npm:typescript --limit 20`).

**JSON envelope.** `{registry?, name?, repoUrl?, source, mode, entries: {count, items}, filter?}`. `source` is always present (the null-source case is promoted to `NOT_FOUND` at the service boundary and never reaches this shape). `entries.count` is computed client-side from `items.length`. `filter` emits only when the caller explicitly supplied one of `--from`, `--to`, `--limit`, `--git-ref`; backend defaults don't round-trip as caller intent.

**Per-entry shape.** `{version, normalizedVersion?, publishedAt?, htmlUrl?, body?}`. `version` is kept even when null so agents can map `items.map(e => e.version)` without guarding; other nullable fields are stripped. The backend's opaque per-entry `metadata` GenericJSON is deliberately dropped from the envelope — revisit via agent feedback.

**Errors.** `NOT_FOUND` covers both the backend's "package not found" case and the distinct "package exists but no changelog source resolved" case (typed `PackageIntelligenceChangelogSourceNotFoundError`; message names the sources that were tried). `VERSION_NOT_FOUND` enriches with structured `package` / `requested` / `available` detail lines from the shared `promoteGenericVersionNotFound` helper — which was extended in this PR to recognise `--from` and `--to` as promotable version inputs.

**Capability gate.** Same as the rest of the `pkg` family (inherits from the `code_navigation` token capability).

**Troubleshooting.** Same debug areas (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures).

## Architecture

```
Expand Down
79 changes: 78 additions & 1 deletion docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,25 @@ When a new tool lands with both MCP and CLI surfaces:
| `src/shared/package-vulnerabilities-response.ts` | Lean JSON envelope builder for `package_vulnerabilities` (shared); terminal formatter (CLI-only). |
| `src/shared/package-dependencies-request.ts` | Shared request builder for `package_dependencies`; owns `supportsDependenciesRegistry` + lifecycle / depth validation. |
| `src/shared/package-dependencies-response.ts` | Lean JSON envelope builder for `package_dependencies` (shared); terminal formatter (CLI-only). |
| `src/shared/package-changelog-request.ts` | Shared request builder for `package_changelog`; owns spec-XOR-repo-URL validation, `<spec>@<version>` rejection, `--from` / `--limit` mutex, tag-style version rejection, and the `explicitFilterFields` tracker. |
| `src/shared/package-changelog-response.ts` | JSON envelope builder for `package_changelog` (shared); terminal formatter (CLI-only). |
| `src/shared/package-intelligence-error-map.ts` | `mapPackageIntelligenceError` classifier (reuses `MappedError` from the code-nav map). |
| `src/services/promote-version-not-found.ts` | Shared helper that promotes generic backend errors with "no matching version" messages into typed `VERSION_NOT_FOUND`. Used by `packageVulnerabilities` and `packageDependencies` executors. |
| `src/services/promote-version-not-found.ts` | Shared helper that promotes generic backend errors with "no matching version" messages into typed `VERSION_NOT_FOUND`. Used by `packageVulnerabilities`, `packageDependencies`, and `packageChangelog` executors. Extended in P4 to recognise `fromVersion` / `toVersion` (and to skip the `details.package` synthesis when registry/name aren't available, i.e. repo-URL mode). |
| `src/tools/search-symbols.ts` | MCP tool definition for `search_symbols`. |
| `src/tools/package-summary.ts` | MCP tool definition for `package_summary`. |
| `src/tools/package-vulnerabilities.ts` | MCP tool definition for `package_vulnerabilities`. |
| `src/tools/package-dependencies.ts` | MCP tool definition for `package_dependencies`. |
| `src/tools/package-changelog.ts` | MCP tool definition for `package_changelog`. |
| `src/commands/code/search-symbols.ts` | CLI command. |
| `src/commands/pkg/info.ts` | CLI command for `pkg info`. |
| `src/commands/pkg/vulns.ts` | CLI command for `pkg vulns`. |
| `src/commands/pkg/deps.ts` | CLI command for `pkg deps`. |
| `src/commands/pkg/changelog.ts` | CLI command for `pkg changelog`. |
| `src/tools/search-symbols-parity.test.ts` | Parity tests (cite rule IDs). |
| `src/tools/package-summary-parity.test.ts` | Parity tests for `package_summary` (cite rule IDs). |
| `src/tools/package-vulnerabilities-parity.test.ts` | Parity tests for `package_vulnerabilities` (cite rule IDs). |
| `src/tools/package-dependencies-parity.test.ts` | Parity tests for `package_dependencies` (cite rule IDs). |
| `src/tools/package-changelog-parity.test.ts` | Parity tests for `package_changelog` (cite rule IDs). |

## Per-tool notes

Expand Down Expand Up @@ -292,3 +297,75 @@ When a new tool lands with both MCP and CLI surfaces:
- `toMatchObject` for builder-sourced `INVALID_ARGUMENT` cases:
unsupported registry (`nuget`), tag-style version (`v4.18.0`),
unknown lifecycle token (`dev`).

### `package_changelog`

- **Dual addressing — the only pkg-intel tool with it.** `registry`
+ `package_name` XOR `repo_url` on both surfaces. Justified because
`packageChangelog` is intrinsically repo-level on the backend (its
sources are GitHub Releases, CHANGELOG.md, HexDocs); repo-URL
isn't a bolt-on, it's a peer addressing mode on the GraphQL
signature. P1 / P2 / P3 omit it because their backend queries are
registry-metadata APIs without repo-URL alternatives. Future
pkg-intel tool authors should not cargo-cult the asymmetry.
- **`<spec>@<version>` rejected.** Other `pkg` commands give
`@version` a meaning (`for this exact version`), but changelog
has no single-version query — remapping to `to_version` would be
a client-invented semantic shift. Both CLI and MCP reject with
`INVALID_ARGUMENT` and redirect callers to `--to` / `to_version`.
- **Mode mutex enforced client-side.** `--from` / `from_version` +
`--limit` / `limit` together → `INVALID_ARGUMENT`. The backend's
same-shape rejection is generic; we catch it with a specific hint
before the wire.
- **`filter.*` echo tracks explicit fields only.** Request builder
exposes an `explicitFilterFields` set (`fromVersion`, `toVersion`,
`limit`, `gitRef`). The envelope builder consults the set before
emitting `filter.*`, so backend-default values
(e.g. `limit: 10` from the wire echo) never round-trip as caller
intent.
- **`entries: { count, items }` shape.** Matches the `runtime:
{ count, items }` convention from `package_dependencies`.
`entries.count === entries.items.length` by construction; the
backend's count field is never selected on the wire.
- **`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`). Other nullable fields
(`normalizedVersion`, `publishedAt`, `htmlUrl`, `body`) are
stripped when absent to keep the envelope lean. `body` is
additionally stripped when `include_bodies: false`.
- **`metadata` dropped.** `ChangelogEntry.metadata` is backend
`GenericJSON`; v1 envelope drops it entirely rather than
guessing at its shape. Revisit via agent feedback
(`TODO(pkgseer-backend)` anchor on the service type).
- **`source: null` promoted to `NOT_FOUND`.** The service layer
promotes the null-source case to a typed
`PackageIntelligenceChangelogSourceNotFoundError` which the
shared classifier routes to `NOT_FOUND` with a message naming
the sources tried (GitHub Releases, CHANGELOG.md, HexDocs).
`source: "releases"` + `entries.items: []` is success — "no
entries in this range" is a legitimate neutral outcome.
- **`--verbose` vs `--no-body` vs `--json` interaction.**
Default terminal output shows each entry's body truncated at
10 lines with a `… (+N more lines — use --verbose …)` footer.
`--verbose` lifts the cap (terminal-only — does not change
`--json` output). `--no-body` mirrors MCP's `include_bodies:
false` and affects both terminal (no body preview, no footer)
and `--json` (entry objects lose the `body` field) — explicit
opt-out, not silent truncation. `--no-body` + `--verbose` is
rejected with a specific hint because the two flags contradict.
- **`promoteGenericVersionNotFound` extension.** The shared helper
now recognises `fromVersion` / `toVersion` in addition to
`version`. Preference order: `version → fromVersion → toVersion`.
In repo-URL mode (no `registry` / `packageName`),
`details.package` is omitted; the error-map handles
`details.package === undefined` gracefully.
- **Parity assertion policy** (coded in
`src/tools/package-changelog-parity.test.ts`):
- `toEqual` across service-sourced fixtures: happy latest mode,
range mode (`--from` / `from_version`), repo-URL addressing,
`--no-body` / `include_bodies: false`, default bodies, empty
entries, `NOT_FOUND` (no source), `PackageIntelligenceTargetNotFoundError`
(package missing), `VERSION_NOT_FOUND` with structured details,
`BACKEND_ERROR`.
- `toMatchObject` for builder-sourced `INVALID_ARGUMENT` cases:
`<spec>@<version>` rejection, `--from` + `--limit` mutex.
Loading
Loading