Skip to content

Commit 81bd8ec

Browse files
authored
Merge pull request #16 from githits-com/feat/pkg-intel-dependencies
feat(pkg-intel): add package_dependencies (pkg deps + MCP)
2 parents 86fe969 + 746082c commit 81bd8ec

26 files changed

Lines changed: 5415 additions & 48 deletions

docs/implementation/cli-commands.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The CLI exposes three primary commands (`search`, `languages`, `feedback`) that
1515
| `code search <package> [query]` | package spec | `--keywords`, `--keyword`, `--match-mode`, `--category`, `--kind`, `--file`, `--intent`, `--limit`, `--wait`, `--json` | Search indexed dependency source code |
1616
| `pkg info <spec>` | package spec | `--verbose`, `--json` | Show a package overview (latest version, downloads, license, vulnerabilities) |
1717
| `pkg vulns <spec>` | package spec (optional `@version`) | `--severity`, `--include-withdrawn`, `--verbose`, `--json` | List known vulnerabilities for a package (npm/pypi/hex/crates) |
18+
| `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) |
1819

1920
### `githits init`
2021

@@ -149,6 +150,42 @@ Lists known CVE / OSV advisories for a package: severity, affected version range
149150

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

153+
### `githits pkg deps`
154+
155+
```
156+
githits pkg deps npm:express
157+
githits pkg deps npm:express --groups
158+
githits pkg deps crates:tokio --lifecycle optional
159+
githits pkg deps npm:express --lifecycle runtime,development
160+
githits pkg deps npm:express --transitive
161+
githits pkg deps npm:express --transitive --depth 2
162+
githits pkg deps npm:express --json
163+
```
164+
165+
Analyses dependencies for a package on npm, PyPI, Hex, Crates, vcpkg, or Zig. Default terminal output is a flat list of direct runtime dependencies with a hint summarising hidden groups.
166+
167+
**Package spec.** `<registry>:<name>[@<version>]`. `@<version>` is accepted (same as `pkg vulns`); defaults to latest. Tag-style inputs such as `@v4.18.0` are rejected client-side with `INVALID_ARGUMENT` — callers must use the canonical version. Only `npm`, `pypi`, `hex`, `crates`, `vcpkg`, and `zig` are supported; other registries are rejected client-side with `pkg deps only supports npm, pypi, hex, crates, vcpkg, and zig. Got: ${registry}.`
168+
169+
**Two views.** The default runtime view collapses to a single-column list from `dependencies.direct` — the flat answer to "what does this pull in?". The structured groups view (`--groups`, or implicitly via `--lifecycle`) iterates `dependencyGroups.groups` and preserves registry-specific condition metadata (PyPI extras, Crates features, NuGet TFMs). Dev / peer / build / optional deps live only in the groups view — the wire's `direct[]` is always runtime-only.
170+
171+
**Lifecycle filter.** `-l, --lifecycle <phases>` accepts a comma-separated list of canonical lowercase tokens (`runtime`, `development`, `build`, `peer`, `optional`). Uppercase and whitespace are tolerated. Filters server-side via the backend's `lifecycle: [String!]` input, which only affects `dependencyGroups`; `direct[]` and `transitive[]` are returned regardless. Unknown tokens are rejected with `INVALID_ARGUMENT` and the canonical list.
172+
173+
**Groups view (`--groups` or any `--lifecycle`).** Headings collapse to `name` when `conditionType === "always"` (e.g. `runtime`, `development`). Feature / TFM groups render `name (lifecycle, conditionType[: conditionValue])``conditionValue` is omitted when it equals `name` (the common case on Crates features and PyPI extras). Within each group, entries sort alphabetically. Duplicate `{name, constraint}` tuples inside a group collapse in the terminal for scannability; the JSON envelope preserves every duplicate the backend emitted.
174+
175+
**Transitive view (`--transitive`).** Replaces the direct-deps list with the full unique transitive closure (alphabetical, `name@version`, one per line). Summary row carries the aggregate counts + conflict / cycle counts, and `(max depth N)` only when `--depth` was applied — otherwise the backend's full-graph traversal is shown. `--depth <n>` (1–10) caps traversal; there is **no client-side default cap** (matches `npm ls` / `cargo tree` ergonomics).
176+
177+
**Verbose (`--verbose`).** In both plain and transitive modes, each dep expands to a multi-line block: the first line is `name@version`, followed by indented `- <constraint> required by <importer>@<importer-version>, …` bullets. Importers that share a constraint are collapsed onto one bullet with a comma-separated list. In plain mode each direct dep has exactly one importer (the root package itself); in transitive mode a popular leaf may list many importers grouped by constraint. Conflicts expand into a `Conflicts (N):` table (`name: range1, range2, …`, one row per package); circular dependencies expand into a `Circular dependencies (N):` list (`a → b → a` arrow chain).
178+
179+
**JSON envelope.** Preprocessed: `runtime.items[].version` surfaces the resolved version alongside the constraint. Under `--transitive`, `transitive.packages[]` carries `{name, version, importers[]}` records so agents get the same provenance signal as the verbose terminal output without decoding the raw DAG. `transitive.conflicts[]` and `transitive.circularDependencies[]` are typed (`{name, requiredVersions}` / `{cycle: string[]}`) when the observed backend shape decodes; raw passthrough otherwise. The raw DAG itself is deliberately **not** in the envelope — a future dedicated `pkg deps-dag` command will expose it under a typed contract for graph visualisation (mermaid / DOT / interactive viewer).
180+
181+
**Output envelope.** `{registry, name, version, requestedVersion?, runtime?, groups?, transitive?, filter?}`. Data-first: the `runtime` block emits whenever the backend returned `dependencies.direct` (including `{count: 0, items: []}` for zero-dep packages); the `groups` block emits whenever the backend returned `dependencyGroups` (including `{items: []}` when a lifecycle filter matched nothing, so agents distinguish "backend has no groups concept" from "filter excluded everything"). Each group carries its members under `items` (matches the top-level `runtime.items` naming so dependency lists share one key throughout the envelope). `filter.lifecycles` echoes the canonicalised, deduplicated, display-order-sorted list the backend received — not the raw CSV input.
182+
183+
**Exit codes.** 0 on success including zero-dep packages; 1 on any error. Under `--json`, the error envelope is written to **stderr**.
184+
185+
**Capability gate.** Same as `pkg info` / `pkg vulns` (inherits from the `code_navigation` token capability).
186+
187+
**Troubleshooting.** Same debug areas as `pkg info` / `pkg vulns` (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures).
188+
152189
## Architecture
153190

154191
```

docs/implementation/mcp-cli-parity.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,22 @@ When a new tool lands with both MCP and CLI surfaces:
150150
| `src/shared/package-summary-response.ts` | Lean JSON envelope builder and terminal formatter for `package_summary`. |
151151
| `src/shared/package-vulnerabilities-request.ts` | Shared request builder for `package_vulnerabilities`; owns the tool-local `supportsVulnerabilitiesRegistry` predicate and the severity-label → CVSS float map. |
152152
| `src/shared/package-vulnerabilities-response.ts` | Lean JSON envelope builder for `package_vulnerabilities` (shared); terminal formatter (CLI-only). |
153+
| `src/shared/package-dependencies-request.ts` | Shared request builder for `package_dependencies`; owns `supportsDependenciesRegistry` + lifecycle / depth validation. |
154+
| `src/shared/package-dependencies-response.ts` | Lean JSON envelope builder for `package_dependencies` (shared); terminal formatter (CLI-only). |
153155
| `src/shared/package-intelligence-error-map.ts` | `mapPackageIntelligenceError` classifier (reuses `MappedError` from the code-nav map). |
156+
| `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. |
154157
| `src/tools/search-symbols.ts` | MCP tool definition for `search_symbols`. |
155158
| `src/tools/package-summary.ts` | MCP tool definition for `package_summary`. |
156159
| `src/tools/package-vulnerabilities.ts` | MCP tool definition for `package_vulnerabilities`. |
160+
| `src/tools/package-dependencies.ts` | MCP tool definition for `package_dependencies`. |
157161
| `src/commands/code/search-symbols.ts` | CLI command. |
158162
| `src/commands/pkg/info.ts` | CLI command for `pkg info`. |
159163
| `src/commands/pkg/vulns.ts` | CLI command for `pkg vulns`. |
164+
| `src/commands/pkg/deps.ts` | CLI command for `pkg deps`. |
160165
| `src/tools/search-symbols-parity.test.ts` | Parity tests (cite rule IDs). |
161166
| `src/tools/package-summary-parity.test.ts` | Parity tests for `package_summary` (cite rule IDs). |
162167
| `src/tools/package-vulnerabilities-parity.test.ts` | Parity tests for `package_vulnerabilities` (cite rule IDs). |
168+
| `src/tools/package-dependencies-parity.test.ts` | Parity tests for `package_dependencies` (cite rule IDs). |
163169

164170
## Per-tool notes
165171

@@ -230,3 +236,59 @@ When a new tool lands with both MCP and CLI surfaces:
230236
inputs like `v4.18.0` are rejected as `INVALID_ARGUMENT` with an
231237
actionable message instead of relying on the current production
232238
backend, which returns a generic error for that input.
239+
240+
### `package_dependencies`
241+
242+
- **Data-first envelope.** `runtime`, `groups`, and `transitive` are
243+
three independent blocks emitted based on what the backend
244+
returned and what the caller asked for, not on additional caller
245+
flags. An MCP agent decides what to read based on what's in the
246+
envelope — no branching on invocation inputs.
247+
- **No `include_groups` input.** The data-first envelope emits the
248+
`groups` block unconditionally when the backend returned
249+
`dependencyGroups`, so an `include_groups: true` input would be a
250+
silently ignored no-op. Deliberately absent from the MCP schema.
251+
- **Dependency list naming.** Every list of dependencies in the
252+
envelope uses the `items` key: `runtime.items`, `groups.items`
253+
(array of groups), each group's nested `items` (array of member
254+
deps). Symmetric and easy to parse.
255+
- **Lifecycle filter echo.** `filter.lifecycles` is the
256+
canonicalised, deduplicated, display-order-sorted array the
257+
backend actually received (never the raw CSV). Emitted only when
258+
the caller supplied a non-empty input.
259+
- **Null vs empty matters.** `groups` is omitted entirely when the
260+
backend returned `dependencyGroups: null` (zero-dep packages);
261+
emitted with `items: []` when the backend returned a non-null
262+
`dependencyGroups` with zero groups (filter matched nothing).
263+
`runtime` is omitted when `dependencies: null` or `direct: null`;
264+
emitted with `count: 0, items: []` when `direct: []`.
265+
- **Terminal-only dedup.** Crates feature groups can contain
266+
duplicate `{name, constraint}` tuples (target-cfg branching). The
267+
terminal formatter collapses them; the JSON envelope preserves
268+
every duplicate the backend emitted. A parity fixture exercises
269+
the round-trip.
270+
- **Preprocessed transitive.** Backend declares `transitive.conflicts`,
271+
`transitive.circularDependencies`, and the DAG as `GenericJSON`,
272+
but the envelope builder decodes them using best-effort shape
273+
detectors so agents see typed data. `transitive.packages[]` carries
274+
`{name, version, importers[]}` records (importer name / version /
275+
constraint pulled from the DAG); `conflicts[]` is typed
276+
`{name, requiredVersions}` when decodable; `circularDependencies[]`
277+
is typed `{cycle: string[]}` when decodable. When a decoder can't
278+
match, that field falls back to raw `GenericJSON[]` so no data is
279+
lost. The raw DAG itself is deliberately dropped from this tool's
280+
envelope — a future `pkg deps-dag` command will expose it under a
281+
typed contract. `groups.environmentConstraints` remains raw
282+
`GenericJSON[]` (no live shape observed yet).
283+
- **Parity assertion policy** (coded in
284+
`src/tools/package-dependencies-parity.test.ts`):
285+
- `toEqual` across the service-sourced success fixtures: happy
286+
flat-runtime, zero-dep (omits `groups`), full-view, optional-
287+
lifecycle (tokio features), multi-lifecycle filter,
288+
filter-matched-nothing (`groups: {items: []}`),
289+
Crates-target-cfg dedup round-trip, versioned match / diff,
290+
`NOT_FOUND`, `VERSION_NOT_FOUND` with structured details,
291+
`BACKEND_ERROR`.
292+
- `toMatchObject` for builder-sourced `INVALID_ARGUMENT` cases:
293+
unsupported registry (`nuget`), tag-style version (`v4.18.0`),
294+
unknown lifecycle token (`dev`).

0 commit comments

Comments
 (0)