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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ GitHits also exposes indexed package/source tools:
| `search_status` | Follow up a prior indexed `search` by `searchRef` |
| `docs_list` | Browse mixed package documentation pages |
| `docs_read` | Read a documentation page by page ID |
| `pkg_info` | Quick package overview: version, license, downloads, quickstart, advisories |
| `pkg_info` | Quick package triage: version, license, repository popularity, downloads, vulnerabilities |
| `pkg_vulns` | CVE / OSV advisories for a package or specific version |
| `pkg_deps` | Direct dependencies, dependency groups, and optional transitive graph |
| `pkg_changelog` | Release notes / changelog entries for a package or GitHub repo |
Expand Down
4 changes: 2 additions & 2 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ githits pkg info crates:serde --json
githits pkg info npm:@types/node
```

Shows a concise overview for a single package: latest version, license, description, repository + homepage, publication date, download count, GitHub metadata, install command, and known vulnerabilities. Default output is a compact terminal block. `--verbose` adds usage snippet, recent advisories, topics, and a recent-changes list. `--json` emits the lean hand-crafted envelope — every null field is omitted, every block that carries no actionable data is omitted entirely.
Shows a concise latest-version overview for dependency triage: license, description, repository popularity (stars/forks/issues and `[ARCHIVED]` when applicable), homepage, publication date, download count, and explicit vulnerability status. Default output is compact. `--verbose` adds GitHub language/topics/last-pushed, recent advisories, and a recent-changes list. `--json` emits the lean hand-crafted envelope — null scalars are omitted, and vulnerability data is emitted whenever the backend reports a numeric count, including zero.

**Package spec.** `<registry>:<name>`. Registries: `npm`, `pypi`, `hex`, `crates`, `nuget`, `maven`, `zig`, `vcpkg`, `packagist`, `rubygems`, `go`. Scoped npm names (`npm:@types/node`) are supported.

**Always latest.** `pkg info` returns the latest published version regardless of input. Passing `<spec>@<version>` is rejected with `INVALID_ARGUMENT` and a clear message — the tool never silently swaps to latest. Use `pkg vulns` or `pkg deps` for version-pinned queries.

**`--verbose` + `--json`.** `--verbose` has no effect under `--json` — the JSON envelope always carries every field the verbose terminal view exposes (and more). The flag only affects human-readable output.

**Output envelope.** Success payload is hand-crafted for agent token efficiency: `{registry, name, version, description?, license?, homepage?, repository?, publishedAt?, downloads?, github?, install?, usage?, vulnerabilities?, recentChanges?}`. Omitted fields reflect backend nulls, not dropped data. Error envelope: `{error, code, retryable, details?}` — shared classifier family. Under `--json` the error envelope is written to **stderr** so stdout stays clean for `jq`.
**Output envelope.** Success payload is hand-crafted for agent token efficiency: `{registry, name, version, description?, license?, homepage?, repository?, publishedAt?, downloads?, github?, vulnerabilities?, recentChanges?}`. Omitted fields reflect backend nulls, not dropped data. Error envelope: `{error, code, retryable, details?}` — shared classifier family. Under `--json` the error envelope is written to **stderr** so stdout stays clean for `jq`.

**Troubleshooting.** `GITHITS_DEBUG=pkg-intel` emits PII-safe classified-error diagnostics (area, event, code, error class, detail keys). Use `GITHITS_DEBUG=*` to enable all non-sensitive package/source diagnostics.

Expand Down
6 changes: 4 additions & 2 deletions docs/implementation/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The CLI mirrors the production MCP tool contract where equivalent tools exist. C
| `search_status` | `search_ref`, `format?` | Check progress, fetch partial hits when the original request used `allow_partial_results: true`, or fetch final results for a prior unified search. Defaults to compact `text-v1`; pass `format: "json"` for the structured envelope. |
| `docs_list` | `registry`, `package_name`, `version?`, `limit?`, `after?`, `format?` | List hosted/crawled and repository-backed documentation pages for a package. Defaults to compact `text-v1` with ready-to-call `docs_read` follow-ups; repo-backed entries include exact source metadata for `code_read` follow-up when available. |
| `docs_read` | `page_id`, `start_line?`, `end_line?`, `format?` | Read a documentation page by page ID. Defaults to `text-v1` with a 150-line MCP text cap; explicit line ranges are supported. `format: "json"` preserves full-document default while still honoring explicit ranges. Repo-backed pages include exact file follow-up metadata. |
| `pkg_info` | `registry`, `package_name`, `format?` | Package overview: latest version, license, description, repository, downloads, GitHub metadata, install command, and known vulnerabilities. Defaults to compact text; pass `format: "json"` for the lean structured envelope. Always returns the latest published version. |
| `pkg_info` | `registry`, `package_name`, `verbose?`, `format?` | Latest-version package triage: license, description, repository popularity (stars/forks/issues and `[ARCHIVED]` when applicable), downloads, publish age, and vulnerability status. Example: `{registry:"npm", package_name:"express"}`. Set `verbose: true` for GitHub language/topics/last-pushed, recent advisories, and recent changes. Pass `format: "json"` for structured fields. |
| `pkg_vulns` | `registry`, `package_name`, `version?`, `min_severity?`, `include_withdrawn?`, `format?` | Known vulnerabilities for a package on npm, PyPI, Hex, or Crates. Defaults to compact summary text; pass `format: "json"` for per-advisory OSV ID + severity + affected/fix ranges, malware bucket, and upgrade paths. |
| `pkg_deps` | `registry`, `package_name`, `version?`, `lifecycle?`, `include_transitive?`, `include_importers?`, `max_depth?`, `format?` | Direct runtime dependency list by default with resolved versions. Non-runtime groups are hidden with an MCP-native hint (`pass lifecycle="all"`). Use `lifecycle: "runtime"` for explicit runtime-only, a concrete non-runtime lifecycle for runtime plus matching groups, or `lifecycle: "all"` for all available groups. Optional transitive output includes aggregate edge counts, the preprocessed install footprint, typed conflicts and circular-dependency cycles; opt into importer provenance with `include_importers`. Pass `format: "json"` for the lean structured envelope. |
| `pkg_changelog` | `registry?`, `package_name?`, `repo_url?`, `from_version?`, `to_version?`, `limit?`, `git_ref?`, `include_bodies?`, `format?` | Release notes or changelog entries for a package or GitHub repo. Defaults to compact text with newest-first entries and capped body previews; pass `format: "json"` for the structured envelope with full markdown bodies, or set `include_bodies: false` for a lean version / date / URL timeline. `from_version` switches to range mode (no count cap). Dual addressing (spec vs repo URL) is mutually exclusive. |
Expand All @@ -40,7 +40,9 @@ The CLI mirrors the production MCP tool contract where equivalent tools exist. C

### `pkg_info` response shape

**Default MCP text + JSON opt-in.** `pkg_info` defaults to compact text for agent turns. `format: "json"` returns a lean payload designed for programmatic consumers. Fields that do not add caller value are deliberately omitted. Null scalars are omitted; blocks (`github`, `vulnerabilities`, `downloads`, `recentChanges`) are omitted entirely when they carry no actionable data. `vulnerabilities` is omitted when `total === 0` or missing; when present, severity values include a CVSS-banded `severityLabel` (`critical` ≥9, `high` ≥7, `medium` ≥4, else `low`) for agent convenience.
**Default MCP text + JSON opt-in.** `pkg_info` defaults to compact triage text for agent turns: identity/license, description, repository popularity (stars/forks/issues and `[ARCHIVED]` when available), publish age, downloads, and explicit vulnerability status. `verbose: true` adds GitHub language/topics/last-pushed, recent advisories, and recent changes. `format: "json"` returns a lean payload designed for programmatic consumers. Fields that do not add caller value are deliberately omitted. Null scalars are omitted; blocks (`github`, `downloads`, `recentChanges`) are omitted entirely when they carry no actionable data. `vulnerabilities` is emitted whenever the backend reports a numeric vulnerability count, including `total: 0`, so callers can distinguish "no active vulnerabilities in latest" from unavailable data; when present, recent advisory severity values include a CVSS-banded `severityLabel` (`critical` ≥9, `high` ≥7, `medium` ≥4, else `low`) for agent convenience.

**No quickstart.** `pkg_info` intentionally does not expose install commands or usage snippets. Those values are package-manager-specific and not verified enough for dependency evaluation. Use `docs_*`, `search`, or `get_example` when usage guidance is needed.

**Validation.** The MCP schema is permissive (`registry: z.string()`, `package_name: z.string()`) — validation happens in-handler via `buildPackageSummaryParams`, producing the same structured `{error, code, retryable}` envelope as CLI. Raw Zod errors are never surfaced to agents.

Expand Down
16 changes: 16 additions & 0 deletions scripts/cli-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,18 @@ async function runLiveSmoke(): Promise<void> {
pkgInfoText.includes("express"),
"pkg info terminal missing package name",
);
assert(
pkgInfoText.includes("Repository") && pkgInfoText.includes("stars"),
"pkg info terminal missing repository popularity",
);
assert(
pkgInfoText.includes("Vulnerabilities"),
"pkg info terminal missing vulnerability status",
);
assert(
!pkgInfoText.includes("Install") && !pkgInfoText.includes("Usage"),
"pkg info terminal should not include quickstart fields",
);

const pkgInfoJson = assertJsonOutput(
await runCli(["pkg", "info", "npm:express", "--json"]),
Expand All @@ -414,6 +426,10 @@ async function runLiveSmoke(): Promise<void> {
typeof pkgInfoJson.version === "string",
"pkg info json missing version",
);
assert(
!("install" in pkgInfoJson) && !("usage" in pkgInfoJson),
"pkg info json should not include quickstart fields",
);

const depsText = assertTerminalOutput(
await runCli(["pkg", "deps", "npm:express"]),
Expand Down
16 changes: 16 additions & 0 deletions scripts/mcp-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ async function runLiveSmoke(client: Client): Promise<void> {
pkgInfoText.includes("express"),
"pkg_info default missing package name",
);
assert(
pkgInfoText.includes("Repository") && pkgInfoText.includes("stars"),
"pkg_info default missing repository popularity",
);
assert(
pkgInfoText.includes("Vulnerabilities"),
"pkg_info default missing vulnerability status",
);
assert(
!pkgInfoText.includes("Install") && !pkgInfoText.includes("Usage"),
"pkg_info default should not include quickstart fields",
);

const pkgInfoJson = assertJsonResult(
await callTool(client, "pkg_info", {
Expand All @@ -297,6 +309,10 @@ async function runLiveSmoke(client: Client): Promise<void> {
typeof pkgInfoJson.version === "string",
"pkg_info json missing version",
);
assert(
!("install" in pkgInfoJson) && !("usage" in pkgInfoJson),
"pkg_info json should not include quickstart fields",
);

const depsText = assertDefaultText(
await callTool(client, "pkg_deps", {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/mcp-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const DOCS_READ_BULLET =
"- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";

const PKG_INFO_BULLET =
'- `pkg_info` — compact package overview: latest version, license, downloads, quickstart, and active advisory count. Pass `format: "json"` for the structured envelope.';
'- `pkg_info` — latest-version package triage by `registry` + `package_name` (e.g. `npm` + `express`): license, repository popularity, downloads, publish age, and vulnerability status. Set `verbose: true` for GitHub language/topics/last-pushed, recent advisories, and recent changes; pass `format: "json"` for structured fields.';

const PKG_VULNS_BULLET =
'- `pkg_vulns` — compact known CVE / OSV advisory summary for npm, PyPI, Hex, or Crates packages, optionally pinned to `version`. Filter with `min_severity`; include retracted advisories with `include_withdrawn`. Pass `format: "json"` for per-advisory structured fields.';
Expand Down
8 changes: 6 additions & 2 deletions src/commands/pkg/info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ describe("pkgInfoAction", () => {
await pkgInfoAction("npm:express", {}, createDeps());

const combined = writes.join("");
expect(combined).toContain("express @ 4.18.2 · MIT");
expect(combined).toContain("express @ 4.18.2 | MIT");
expect(combined).toContain("Repository");
expect(combined).toContain("Install");
expect(combined).toContain("63k stars, 14k forks, 123 issues");
expect(combined).toContain("Vulnerabilities");
expect(combined).not.toContain("Install");
writeSpy.mockRestore();
});

Expand All @@ -52,6 +54,8 @@ describe("pkgInfoAction", () => {
expect(payload.name).toBe("express");
expect(payload.registry).toBe("npm");
expect(payload.version).toBe("4.18.2");
expect("install" in payload).toBe(false);
expect("usage" in payload).toBe(false);
logSpy.mockRestore();
});

Expand Down
14 changes: 9 additions & 5 deletions src/commands/pkg/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,17 @@ function handlePkgInfoCommandError(error: unknown, json: boolean): never {
process.exit(1);
}

const PKG_INFO_DESCRIPTION = `Get a package overview — latest version, license, description,
repository, downloads, GitHub stars, install command, and known
vulnerabilities. Use before picking a dependency or to orient on what
a package is.
const PKG_INFO_DESCRIPTION = `Latest-version package overview for dependency triage.

Default output shows license, description, repository popularity
(stars/forks/issues and [ARCHIVED] when applicable), downloads,
publish age, and vulnerability status. --verbose adds GitHub
language/topics/last-pushed, recent advisories, and recent changes.

Package spec: <registry>:<name>. Supported registries: ${PKGSEER_REGISTRY_LIST}.

Example: githits pkg info npm:express

Always returns data for the latest published version.`;

export function registerPkgInfoCommand(pkgCommand: Command): Command {
Expand All @@ -116,7 +120,7 @@ export function registerPkgInfoCommand(pkgCommand: Command): Command {
.argument("<spec>", "Package spec, e.g. npm:express or pypi:requests")
.option(
"-v, --verbose",
"Show advisories, install usage, topics, and recent changes",
"Show GitHub language/topics/last-pushed, recent advisories, and recent changes",
)
.option("--json", "Emit the lean JSON envelope")
.action(async (spec: string, options: PkgInfoCommandOptions) => {
Expand Down
1 change: 0 additions & 1 deletion src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ export type {
PackageSummaryParams,
PackageVersionIdentity,
PackageVulnerabilitiesParams,
QuickstartInfo,
ReadPackageDocParams,
TransitiveDependencySummary,
UntypedGenericJSON,
Expand Down
14 changes: 4 additions & 10 deletions src/services/package-intelligence-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ const HAPPY_BODY = {
},
],
},
quickstart: {
installCommand: "npm install express",
usageExample: "const express = require('express')",
},
latestChangelogs: [
{
version: "4.18.2",
Expand Down Expand Up @@ -120,11 +116,10 @@ describe("PackageIntelligenceServiceImpl", () => {
expect(result.package.downloadsLastMonth).toBe(86_000_000);
expect(result.package.githubRepository?.stargazersCount).toBe(63_400);
expect(result.security?.vulnerabilityCount).toBe(5);
expect(result.quickstart?.installCommand).toBe("npm install express");
expect(result.latestChangelogs?.[0]?.version).toBe("4.18.2");
});

it("preserves null blocks (security / quickstart / github absent)", async () => {
it("preserves null blocks (security / github absent)", async () => {
const body = {
data: {
packageSummary: {
Expand All @@ -142,7 +137,6 @@ describe("PackageIntelligenceServiceImpl", () => {
githubRepository: null,
},
security: null,
quickstart: null,
latestChangelogs: null,
},
},
Expand All @@ -163,7 +157,6 @@ describe("PackageIntelligenceServiceImpl", () => {
expect(result.package.name).toBe("obscure");
expect(result.package.githubRepository).toBeUndefined();
expect(result.security).toBeUndefined();
expect(result.quickstart).toBeUndefined();
expect(result.latestChangelogs).toBeUndefined();
});

Expand All @@ -173,7 +166,6 @@ describe("PackageIntelligenceServiceImpl", () => {
packageSummary: {
package: { name: null, latestVersion: "1.0.0" },
security: null,
quickstart: null,
latestChangelogs: null,
},
},
Expand All @@ -196,7 +188,6 @@ describe("PackageIntelligenceServiceImpl", () => {
packageSummary: {
package: { name: "x", latestVersion: null },
security: null,
quickstart: null,
latestChangelogs: null,
},
},
Expand Down Expand Up @@ -231,6 +222,9 @@ describe("PackageIntelligenceServiceImpl", () => {
const parsed = JSON.parse(capturedBody ?? "{}");
expect(parsed.query).toContain("packageSummary(registry: $registry");
expect(parsed.query).toContain("latestChangelogs(limit: 3)");
expect(parsed.query).not.toContain("quickstart");
expect(parsed.query).not.toContain("installCommand");
expect(parsed.query).not.toContain("usageExample");
expect(parsed.variables).toEqual({ registry: "NPM", name: "express" });
});

Expand Down
27 changes: 0 additions & 27 deletions src/services/package-intelligence-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,6 @@ export interface PackageSecurityOverview {
recentVulnerabilities?: VulnerabilityOverview[];
}

export interface QuickstartInfo {
installCommand?: string;
usageExample?: string;
}

export interface ChangelogEntry {
version?: string;
publishedAt?: string;
Expand All @@ -91,7 +86,6 @@ export interface ChangelogEntry {
export interface PackageSummary {
package: PackageIdentity;
security?: PackageSecurityOverview;
quickstart?: QuickstartInfo;
latestChangelogs?: ChangelogEntry[];
}

Expand Down Expand Up @@ -615,14 +609,6 @@ const packageSecurityOverviewSchema = z
.nullable()
.optional();

const quickstartInfoSchema = z
.object({
installCommand: z.string().nullable().optional(),
usageExample: z.string().nullable().optional(),
})
.nullable()
.optional();

const changelogEntrySchema = z.object({
version: z.string().nullable().optional(),
publishedAt: z.string().nullable().optional(),
Expand All @@ -632,7 +618,6 @@ const changelogEntrySchema = z.object({
const packageSummaryResponseSchema = z.object({
package: packageIdentitySchema.nullable().optional(),
security: packageSecurityOverviewSchema,
quickstart: quickstartInfoSchema,
latestChangelogs: z.array(changelogEntrySchema).nullable().optional(),
});

Expand Down Expand Up @@ -685,10 +670,6 @@ query PackageSummary($registry: Registry!, $name: String!) {
publishedAt
}
}
quickstart {
installCommand
usageExample
}
latestChangelogs(limit: 3) {
version
publishedAt
Expand Down Expand Up @@ -1536,13 +1517,6 @@ export class PackageIntelligenceServiceImpl
}
: undefined;

const quickstart: QuickstartInfo | undefined = data.quickstart
? {
installCommand: data.quickstart.installCommand ?? undefined,
usageExample: data.quickstart.usageExample ?? undefined,
}
: undefined;

const latestChangelogs: ChangelogEntry[] | undefined =
data.latestChangelogs?.map((entry) => ({
version: entry.version ?? undefined,
Expand All @@ -1553,7 +1527,6 @@ export class PackageIntelligenceServiceImpl
return {
package: identity,
security,
quickstart,
latestChangelogs,
};
}
Expand Down
4 changes: 0 additions & 4 deletions src/services/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,6 @@ export const defaultPackageSummary: PackageSummary = {
},
],
},
quickstart: {
installCommand: "npm install express",
usageExample: "const express = require('express')\nconst app = express()",
},
latestChangelogs: [
{
version: "4.18.2",
Expand Down
Loading
Loading