githits 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -49,11 +49,11 @@ import {
49
49
  shouldRunUpdateCheck,
50
50
  startTelemetrySpan,
51
51
  withTelemetrySpan
52
- } from "./shared/chunk-v8sths32.js";
52
+ } from "./shared/chunk-15argn2z.js";
53
53
  import {
54
54
  __require,
55
55
  version
56
- } from "./shared/chunk-jdygt0ra.js";
56
+ } from "./shared/chunk-q6d3ttgn.js";
57
57
 
58
58
  // src/cli.ts
59
59
  import { Command } from "commander";
@@ -2022,7 +2022,7 @@ var SUPPORTED_DEPS_REGISTRIES = new Set([
2022
2022
  "RUBYGEMS",
2023
2023
  "GO"
2024
2024
  ]);
2025
- var SUPPORTED_DEPS_REGISTRIES_HUMAN = "npm, pypi, hex, crates, vcpkg, zig, rubygems, and go";
2025
+ var SUPPORTED_DEPS_REGISTRIES_LIST = PKGSEER_REGISTRY_ARGS.filter((arg) => SUPPORTED_DEPS_REGISTRIES.has(toPkgseerRegistry(arg))).join(", ");
2026
2026
  function supportsDependenciesRegistry(registry) {
2027
2027
  return SUPPORTED_DEPS_REGISTRIES.has(registry);
2028
2028
  }
@@ -2037,7 +2037,7 @@ function buildPackageDependenciesParams(input) {
2037
2037
  }
2038
2038
  const registry = toPkgseerRegistry(normalisedRegistryArg);
2039
2039
  if (!supportsDependenciesRegistry(registry)) {
2040
- throw new UnsupportedDependenciesRegistryError(`pkg deps only supports ${SUPPORTED_DEPS_REGISTRIES_HUMAN}. Got: ${normalisedRegistryArg}.`);
2040
+ throw new UnsupportedDependenciesRegistryError(`pkg deps only supports ${SUPPORTED_DEPS_REGISTRIES_LIST}. Got: ${normalisedRegistryArg}.`);
2041
2041
  }
2042
2042
  const version2 = normaliseVersion(input.version);
2043
2043
  const canonicalLifecycles = resolveLifecycles(input.lifecycle);
@@ -2900,12 +2900,6 @@ function buildPackageSummarySuccessPayload(summary) {
2900
2900
  const github = buildGithub(pkg.githubRepository);
2901
2901
  if (github)
2902
2902
  payload.github = github;
2903
- if (summary.quickstart?.installCommand) {
2904
- payload.install = summary.quickstart.installCommand;
2905
- }
2906
- const usage = normaliseUsage(summary.quickstart?.usageExample);
2907
- if (usage)
2908
- payload.usage = usage;
2909
2903
  const vulns = buildVulnerabilities(summary.security);
2910
2904
  if (vulns)
2911
2905
  payload.vulnerabilities = vulns;
@@ -2964,20 +2958,12 @@ function buildGithub(github) {
2964
2958
  result.lastPushedAt = lastPushedAt;
2965
2959
  return Object.keys(result).length > 0 ? result : undefined;
2966
2960
  }
2967
- function normaliseUsage(usage) {
2968
- if (!usage)
2969
- return;
2970
- const cleaned = usage.replace(/\r\n/g, `
2971
- `).replace(/\r/g, `
2972
- `);
2973
- return cleaned.length > 0 ? cleaned : undefined;
2974
- }
2975
2961
  function buildVulnerabilities(security) {
2976
2962
  if (!security)
2977
2963
  return;
2978
- const total = security.vulnerabilityCount ?? 0;
2979
- if (total === 0)
2964
+ if (typeof security.vulnerabilityCount !== "number")
2980
2965
  return;
2966
+ const total = security.vulnerabilityCount;
2981
2967
  const result = {
2982
2968
  total,
2983
2969
  affectsLatest: security.hasCurrentVulnerabilities ?? false
@@ -3033,7 +3019,7 @@ function pickChangelogSummary(entry) {
3033
3019
  const firstLine = entry.body.split(/\r?\n/).map((line) => stripMarkdownHeading(line.trim())).find((line) => line.length > 0);
3034
3020
  if (!firstLine)
3035
3021
  return;
3036
- return firstLine.length > 120 ? `${firstLine.slice(0, 120).trimEnd()}…` : firstLine;
3022
+ return firstLine.length > 120 ? `${firstLine.slice(0, 117).trimEnd()}...` : firstLine;
3037
3023
  }
3038
3024
  function stripMarkdownHeading(line) {
3039
3025
  return line.replace(/^#{1,6}\s+/, "").trim();
@@ -3044,7 +3030,7 @@ function formatPackageSummaryTerminal(summary, options = {}) {
3044
3030
  const width = resolveWidth(options.terminalWidth);
3045
3031
  const now = options.now ?? new Date;
3046
3032
  const sections = [];
3047
- const header = lean.license ? `${colorize(lean.name, "bold", useColors)} @ ${lean.version} · ${lean.license}` : `${colorize(lean.name, "bold", useColors)} @ ${lean.version}`;
3033
+ const header = lean.license ? `${colorize(lean.name, "bold", useColors)} @ ${lean.version} | ${lean.license}` : `${colorize(lean.name, "bold", useColors)} @ ${lean.version}`;
3048
3034
  sections.push(header);
3049
3035
  if (lean.description) {
3050
3036
  sections.push(wrapText(lean.description, width));
@@ -3054,9 +3040,6 @@ function formatPackageSummaryTerminal(summary, options = {}) {
3054
3040
  sections.push(fields.join(`
3055
3041
  `));
3056
3042
  }
3057
- if (lean.vulnerabilities) {
3058
- sections.push(formatVulnFooter(lean.vulnerabilities, useColors));
3059
- }
3060
3043
  if (options.verbose) {
3061
3044
  const verbose = buildVerboseSections(lean, useColors);
3062
3045
  if (verbose.length > 0)
@@ -3101,10 +3084,19 @@ function wrapText(text, width) {
3101
3084
  function buildFieldList(lean, summary, useColors, now) {
3102
3085
  const fields = [];
3103
3086
  if (lean.repository) {
3087
+ const repositoryParts = [dim(lean.repository, useColors)];
3088
+ const githubPopularity = formatGithubPopularity(lean.github);
3089
+ if (githubPopularity)
3090
+ repositoryParts.push(`(${githubPopularity})`);
3104
3091
  fields.push({
3105
3092
  label: "Repository",
3106
- value: dim(lean.repository, useColors)
3093
+ value: repositoryParts.join(" ")
3107
3094
  });
3095
+ } else {
3096
+ const githubPopularity = formatGithubPopularity(lean.github);
3097
+ if (githubPopularity) {
3098
+ fields.push({ label: "GitHub", value: githubPopularity });
3099
+ }
3108
3100
  }
3109
3101
  if (lean.homepage) {
3110
3102
  fields.push({ label: "Homepage", value: dim(lean.homepage, useColors) });
@@ -3124,54 +3116,53 @@ function buildFieldList(lean, summary, useColors, now) {
3124
3116
  value: `${formatCompactNumber(lean.downloads.total)} total`
3125
3117
  });
3126
3118
  }
3127
- if (lean.github) {
3128
- fields.push({ label: "GitHub", value: formatGithubLine(lean.github) });
3129
- }
3130
- if (lean.install) {
3131
- fields.push({ label: "Install", value: lean.install });
3119
+ if (lean.vulnerabilities) {
3120
+ fields.push({
3121
+ label: "Vulnerabilities",
3122
+ value: formatVulnerabilityStatus(lean.vulnerabilities)
3123
+ });
3132
3124
  }
3133
3125
  const labelWidth = Math.max(10, ...fields.map((field) => field.label.length));
3134
3126
  return fields.map((field) => `${field.label.padEnd(labelWidth)} ${field.value}`);
3135
3127
  }
3136
- function formatGithubLine(github) {
3128
+ function formatGithubPopularity(github) {
3129
+ if (!github)
3130
+ return;
3137
3131
  const parts = [];
3132
+ if (github.archived) {
3133
+ parts.push("[ARCHIVED]");
3134
+ }
3138
3135
  if (github.stars !== undefined) {
3139
- parts.push(`★ ${formatCompactNumber(github.stars)}`);
3136
+ parts.push(`${formatCompactNumber(github.stars)} stars`);
3140
3137
  }
3141
3138
  if (github.forks !== undefined) {
3142
3139
  parts.push(`${formatCompactNumber(github.forks)} forks`);
3143
3140
  }
3144
3141
  if (github.openIssues !== undefined) {
3145
- parts.push(`${formatCompactNumber(github.openIssues)} open issues`);
3142
+ parts.push(`${formatCompactNumber(github.openIssues)} issues`);
3146
3143
  }
3147
- if (github.archived) {
3148
- parts.push("archived");
3149
- }
3150
- return parts.join(" · ");
3144
+ return parts.length > 0 ? parts.join(", ") : undefined;
3151
3145
  }
3152
- function formatVulnFooter(vulns, useColors) {
3146
+ function formatVulnerabilityStatus(vulns) {
3147
+ if (vulns.total === 0) {
3148
+ return "No active vulnerabilities in latest published version";
3149
+ }
3153
3150
  const noun = vulns.total === 1 ? "vulnerability" : "vulnerabilities";
3154
- const base = `${vulns.total} known ${noun}`;
3155
- const full = vulns.affectsLatest ? `${base} · latest affected` : base;
3156
- return colorize(full, "yellow", useColors);
3151
+ if (vulns.affectsLatest) {
3152
+ return `${vulns.total} active ${noun}; latest affected`;
3153
+ }
3154
+ return `${vulns.total} known ${noun}; latest not affected`;
3157
3155
  }
3158
3156
  function buildVerboseSections(lean, useColors) {
3159
3157
  const blocks = [];
3160
- if (lean.usage) {
3161
- blocks.push([
3162
- highlight("Usage", useColors),
3163
- lean.usage.split(`
3164
- `).map((line) => ` ${line}`).join(`
3165
- `)
3166
- ].join(`
3167
- `));
3158
+ if (lean.github) {
3159
+ const github = formatVerboseGithub(lean.github, useColors);
3160
+ if (github)
3161
+ blocks.push(github);
3168
3162
  }
3169
3163
  if (lean.vulnerabilities?.recent && lean.vulnerabilities.recent.length > 0) {
3170
3164
  blocks.push(formatVerboseAdvisories(lean.vulnerabilities.recent, useColors));
3171
3165
  }
3172
- if (lean.github?.topics && lean.github.topics.length > 0) {
3173
- blocks.push(`${"Topics".padEnd(10)} ${lean.github.topics.join(", ")}`);
3174
- }
3175
3166
  if (lean.recentChanges && lean.recentChanges.length > 0) {
3176
3167
  blocks.push(formatVerboseChanges(lean.recentChanges, useColors));
3177
3168
  }
@@ -3179,6 +3170,23 @@ function buildVerboseSections(lean, useColors) {
3179
3170
 
3180
3171
  `);
3181
3172
  }
3173
+ function formatVerboseGithub(github, useColors) {
3174
+ const fields = [];
3175
+ if (github.language)
3176
+ fields.push({ label: "Language", value: github.language });
3177
+ if (github.lastPushedAt) {
3178
+ fields.push({ label: "Last pushed", value: github.lastPushedAt });
3179
+ }
3180
+ if (github.topics && github.topics.length > 0) {
3181
+ fields.push({ label: "Topics", value: github.topics.join(", ") });
3182
+ }
3183
+ const labelWidth = Math.max(10, ...fields.map((field) => field.label.length));
3184
+ return fields.length > 0 ? [
3185
+ highlight("GitHub", useColors),
3186
+ ...fields.map((field) => ` ${field.label.padEnd(labelWidth)} ${field.value}`)
3187
+ ].join(`
3188
+ `) : undefined;
3189
+ }
3182
3190
  function formatVerboseAdvisories(advisories, useColors) {
3183
3191
  const labelWidth = Math.max(...advisories.map((a) => (a.severityLabel ?? "").length));
3184
3192
  const lines = [highlight("Recent advisories", useColors)];
@@ -3228,12 +3236,22 @@ var SUPPORTED_VULN_REGISTRIES = new Set([
3228
3236
  "NPM",
3229
3237
  "PYPI",
3230
3238
  "HEX",
3231
- "CRATES"
3239
+ "CRATES",
3240
+ "NUGET",
3241
+ "MAVEN",
3242
+ "PACKAGIST",
3243
+ "RUBYGEMS",
3244
+ "GO"
3232
3245
  ]);
3233
- var SUPPORTED_VULN_REGISTRIES_HUMAN = "npm, pypi, hex, and crates";
3246
+ var SUPPORTED_VULN_REGISTRIES_HUMAN = "npm, pypi, hex, crates, nuget, maven, packagist, rubygems, and go";
3234
3247
  function supportsVulnerabilitiesRegistry(registry) {
3235
3248
  return SUPPORTED_VULN_REGISTRIES.has(registry);
3236
3249
  }
3250
+ var ADVISORY_SCOPE_TO_GRAPHQL = {
3251
+ affected: "AFFECTED",
3252
+ non_affecting: "NON_AFFECTING",
3253
+ all: "ALL"
3254
+ };
3237
3255
  function buildPackageVulnerabilitiesParams(input) {
3238
3256
  const trimmedName = input.packageName?.trim() ?? "";
3239
3257
  if (!trimmedName) {
@@ -3247,22 +3265,27 @@ function buildPackageVulnerabilitiesParams(input) {
3247
3265
  if (!supportsVulnerabilitiesRegistry(registry)) {
3248
3266
  throw new UnsupportedVulnerabilitiesRegistryError(`pkg vulns only supports ${SUPPORTED_VULN_REGISTRIES_HUMAN}. Got: ${normalisedRegistryArg}.`);
3249
3267
  }
3250
- const minSeverity = resolveMinSeverity(input.minSeverity);
3268
+ const severityLabel2 = resolveMinSeverityLabel(input.minSeverity);
3269
+ const minSeverity = severityLabel2 !== undefined ? SEVERITY_LABEL_TO_CVSS[severityLabel2] : undefined;
3251
3270
  const trimmedVersion = input.version?.trim();
3252
3271
  if (trimmedVersion && /^v\d/i.test(trimmedVersion)) {
3253
3272
  throw new InvalidPackageSpecError(`Invalid version '${trimmedVersion}'. Use the canonical package version without a leading 'v' (for example '4.18.0', not 'v4.18.0').`);
3254
3273
  }
3274
+ const advisoryScope = resolveAdvisoryScope(input.advisoryScope);
3275
+ const filterWithScope = buildFilterEcho3(severityLabel2, input.includeWithdrawn, advisoryScope);
3255
3276
  return {
3256
3277
  params: {
3257
3278
  registry,
3258
3279
  packageName: trimmedName,
3259
3280
  version: trimmedVersion && trimmedVersion.length > 0 ? trimmedVersion : undefined,
3260
3281
  minSeverity,
3261
- includeWithdrawn: input.includeWithdrawn
3262
- }
3282
+ includeWithdrawn: input.includeWithdrawn,
3283
+ advisoryScope: advisoryScope ? ADVISORY_SCOPE_TO_GRAPHQL[advisoryScope] : undefined
3284
+ },
3285
+ ...filterWithScope ? { filter: filterWithScope } : {}
3263
3286
  };
3264
3287
  }
3265
- function resolveMinSeverity(raw) {
3288
+ function resolveMinSeverityLabel(raw) {
3266
3289
  if (raw === undefined)
3267
3290
  return;
3268
3291
  const trimmed = raw.trim();
@@ -3272,12 +3295,36 @@ function resolveMinSeverity(raw) {
3272
3295
  if (!isSeverityLabel(lower)) {
3273
3296
  throw new InvalidPackageSpecError(`Unsupported severity '${raw}'. Expected one of: low, medium, high, critical.`);
3274
3297
  }
3275
- return SEVERITY_LABEL_TO_CVSS[lower];
3298
+ return lower;
3299
+ }
3300
+ function buildFilterEcho3(minSeverity, includeWithdrawn, advisoryScope) {
3301
+ const filter = {};
3302
+ if (minSeverity !== undefined)
3303
+ filter.minSeverity = minSeverity;
3304
+ if (includeWithdrawn === true)
3305
+ filter.includeWithdrawn = true;
3306
+ if (advisoryScope !== undefined && advisoryScope !== "affected") {
3307
+ filter.advisoryScope = advisoryScope;
3308
+ }
3309
+ return Object.keys(filter).length > 0 ? filter : undefined;
3310
+ }
3311
+ function resolveAdvisoryScope(raw) {
3312
+ if (raw === undefined)
3313
+ return;
3314
+ const trimmed = raw.trim();
3315
+ if (trimmed.length === 0)
3316
+ return;
3317
+ const lower = trimmed.toLowerCase().replace(/-/g, "_");
3318
+ if (lower === "affected" || lower === "non_affecting" || lower === "all") {
3319
+ return lower;
3320
+ }
3321
+ throw new InvalidPackageSpecError(`Unsupported advisory scope '${raw}'. Expected one of: affected, non_affecting, all.`);
3276
3322
  }
3277
3323
  function isSeverityLabel(value) {
3278
3324
  return value === "low" || value === "medium" || value === "high" || value === "critical";
3279
3325
  }
3280
3326
  // src/shared/package-vulnerabilities-response.ts
3327
+ var DEFAULT_ADVISORY_CAP = 5;
3281
3328
  function buildPackageVulnerabilitiesSuccessPayload(report, options = {}) {
3282
3329
  const pkg = report.package;
3283
3330
  const security = report.security;
@@ -3293,11 +3340,12 @@ function buildPackageVulnerabilitiesSuccessPayload(report, options = {}) {
3293
3340
  if (requestedEcho !== undefined) {
3294
3341
  payload.requestedVersion = requestedEcho;
3295
3342
  }
3296
- if (total > 0) {
3297
- const sortedAdvisories = sortAdvisories(dedupedAdvisories.map(buildAdvisory));
3298
- if (sortedAdvisories.length > 0) {
3299
- payload.advisories = sortedAdvisories;
3300
- }
3343
+ if (options.filter !== undefined) {
3344
+ payload.filter = options.filter;
3345
+ }
3346
+ const sortedAdvisories = sortAdvisories(dedupedAdvisories.map(buildAdvisory));
3347
+ if (sortedAdvisories.length > 0) {
3348
+ payload.advisories = sortedAdvisories;
3301
3349
  }
3302
3350
  const upgradePaths = security?.upgradePaths;
3303
3351
  if (upgradePaths && upgradePaths.length > 0) {
@@ -3343,7 +3391,7 @@ function buildSummary(total, security, dedupedAdvisories) {
3343
3391
  if (typeof security?.currentVersionAffected === "boolean") {
3344
3392
  summary.affected = security.currentVersionAffected;
3345
3393
  }
3346
- if (total === 0)
3394
+ if (dedupedAdvisories.length === 0)
3347
3395
  return summary;
3348
3396
  const bySeverity = computeBySeverity(dedupedAdvisories);
3349
3397
  const anyCounted = Object.values(bySeverity).some((n) => n > 0);
@@ -3731,17 +3779,25 @@ function lowerRegistry3(value) {
3731
3779
  }
3732
3780
  function formatPackageVulnerabilitiesTerminal(report, options = {}) {
3733
3781
  const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
3734
- requestedVersion: options.requestedVersion
3782
+ requestedVersion: options.requestedVersion,
3783
+ filter: options.filter
3735
3784
  });
3736
3785
  const useColors = options.useColors ?? false;
3737
3786
  const verbose = options.verbose ?? false;
3787
+ const surface = options.surface ?? "cli";
3738
3788
  const headerLine = formatHeader(payload, useColors);
3739
3789
  const requestedLine = payload.requestedVersion ? dim(`(requested ${payload.requestedVersion})`, useColors) : undefined;
3790
+ const filterLines = formatFilterLines(payload.filter);
3740
3791
  if (payload.summary.total === 0) {
3741
3792
  const lines = [headerLine];
3742
3793
  if (requestedLine)
3743
3794
  lines.push(requestedLine);
3795
+ lines.push(...filterLines);
3744
3796
  lines.push(formatNoAffectedVulnerabilitiesLine(payload));
3797
+ if (payload.advisories && payload.advisories.length > 0) {
3798
+ const rangeLimit = resolveAffectedRangesLimit(options.terminalWidth);
3799
+ lines.push("", formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit, surface));
3800
+ }
3745
3801
  return `${lines.join(`
3746
3802
  `)}
3747
3803
  `;
@@ -3750,15 +3806,21 @@ function formatPackageVulnerabilitiesTerminal(report, options = {}) {
3750
3806
  const headerBlock = [headerLine];
3751
3807
  if (requestedLine)
3752
3808
  headerBlock.push(requestedLine);
3809
+ headerBlock.push(...filterLines);
3753
3810
  headerBlock.push(formatSummaryLine(payload, useColors));
3754
- const breakdown = formatBreakdownLine(payload.summary, useColors);
3811
+ const selectedAdvisoryCount = payload.advisories?.length ?? 0;
3812
+ const scope = payload.filter?.advisoryScope;
3813
+ const selectedCountLine = formatSelectedAdvisoryCountLine(selectedAdvisoryCount, scope);
3814
+ if (selectedCountLine)
3815
+ headerBlock.push(selectedCountLine);
3816
+ const breakdown = scope === undefined ? formatBreakdownLine(payload.summary, useColors) : undefined;
3755
3817
  if (breakdown)
3756
3818
  headerBlock.push(breakdown);
3757
3819
  blocks.push(headerBlock.join(`
3758
3820
  `));
3759
3821
  if (payload.advisories && payload.advisories.length > 0) {
3760
3822
  const rangeLimit = resolveAffectedRangesLimit(options.terminalWidth);
3761
- blocks.push(formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit));
3823
+ blocks.push(formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit, surface));
3762
3824
  }
3763
3825
  const upgradeFooter = formatUpgradeFooter(payload.upgradePaths);
3764
3826
  if (upgradeFooter)
@@ -3770,7 +3832,29 @@ function formatPackageVulnerabilitiesTerminal(report, options = {}) {
3770
3832
  }
3771
3833
  function formatHeader(payload, useColors) {
3772
3834
  const name = colorize(payload.name, "bold", useColors);
3773
- return `${name} @ ${payload.version} · ${payload.registry}`;
3835
+ return `${name} @ ${payload.version} | ${payload.registry}`;
3836
+ }
3837
+ function formatFilterLines(filter) {
3838
+ if (!filter)
3839
+ return [];
3840
+ const lines = [];
3841
+ if (filter.advisoryScope) {
3842
+ lines.push(`Scope ${formatAdvisoryScope(filter.advisoryScope)}`);
3843
+ }
3844
+ if (filter.minSeverity) {
3845
+ lines.push(`Filter severity >= ${filter.minSeverity}`);
3846
+ }
3847
+ if (filter.includeWithdrawn === true) {
3848
+ lines.push("Filter include withdrawn");
3849
+ }
3850
+ return lines;
3851
+ }
3852
+ function formatAdvisoryScope(scope) {
3853
+ if (scope === "non_affecting")
3854
+ return "historical advisories only";
3855
+ if (scope === "all")
3856
+ return "all package advisories";
3857
+ return scope;
3774
3858
  }
3775
3859
  function formatSummaryLine(payload, useColors) {
3776
3860
  const n = payload.summary.total;
@@ -3786,13 +3870,41 @@ function formatSummaryLine(payload, useColors) {
3786
3870
  return base;
3787
3871
  }
3788
3872
  function formatNoAffectedVulnerabilitiesLine(payload) {
3873
+ const selectedAdvisoryCount = payload.advisories?.length ?? 0;
3874
+ if (payload.filter?.advisoryScope === "non_affecting") {
3875
+ if (selectedAdvisoryCount > 0) {
3876
+ return "No active vulnerabilities affect this version; historical advisories are listed below.";
3877
+ }
3878
+ return "No active vulnerabilities affect this version; no historical advisories match the current filter.";
3879
+ }
3880
+ if (payload.filter?.advisoryScope === "all") {
3881
+ if (selectedAdvisoryCount > 0) {
3882
+ return "No active vulnerabilities affect this version; package advisories are listed below.";
3883
+ }
3884
+ return "No active vulnerabilities affect this version; no package advisories match the current filter.";
3885
+ }
3886
+ if (payload.filter !== undefined) {
3887
+ return "No vulnerabilities matching the filter affect this version.";
3888
+ }
3789
3889
  const historical = payload.summary.nonAffectingVulnerabilityCount ?? 0;
3790
3890
  if (historical > 0) {
3791
3891
  const noun = historical === 1 ? "historical advisory" : "historical advisories";
3792
3892
  const verb = historical === 1 ? "does" : "do";
3793
- return `No vulnerabilities affect this version (${historical} ${noun} ${verb} not apply).`;
3893
+ return `No active vulnerabilities affect this version (${historical} ${noun} ${verb} not apply).`;
3794
3894
  }
3795
- return "No known vulnerabilities affect this version.";
3895
+ return "No active vulnerabilities affect this version.";
3896
+ }
3897
+ function formatSelectedAdvisoryCountLine(count, scope) {
3898
+ if (scope === undefined)
3899
+ return;
3900
+ const noun = count === 1 ? "advisory" : "advisories";
3901
+ if (scope === "non_affecting") {
3902
+ return ` showing ${count} historical ${noun} that do not affect this version`;
3903
+ }
3904
+ if (scope === "all") {
3905
+ return ` showing ${count} package ${noun} across affected and historical scopes`;
3906
+ }
3907
+ return;
3796
3908
  }
3797
3909
  function formatBreakdownLine(summary, useColors) {
3798
3910
  if (summary.total <= 1)
@@ -3818,22 +3930,31 @@ function formatBreakdownLine(summary, useColors) {
3818
3930
  }
3819
3931
  if (parts.length === 0)
3820
3932
  return;
3821
- return ` ${parts.join(" · ")}`;
3933
+ return ` ${parts.join(" | ")}`;
3822
3934
  }
3823
- function formatAdvisoryList(advisories, verbose, useColors, rangeLimit) {
3824
- const labelWidth = Math.max(...advisories.map((a) => severityColumnLabel(a).length));
3935
+ function formatAdvisoryList(advisories, verbose, useColors, rangeLimit, surface) {
3936
+ const renderedAdvisories = verbose ? advisories : advisories.slice(0, DEFAULT_ADVISORY_CAP);
3937
+ const labelWidth = Math.max(...renderedAdvisories.map((a) => severityColumnLabel(a).length));
3825
3938
  const lines = [];
3826
- for (const advisory of advisories) {
3827
- lines.push(...formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit));
3939
+ for (const advisory of renderedAdvisories) {
3940
+ lines.push(...formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit, surface));
3828
3941
  lines.push("");
3829
3942
  }
3943
+ const hidden = advisories.length - renderedAdvisories.length;
3944
+ if (hidden > 0) {
3945
+ lines.push(dim(formatAdvisoryCapHint(hidden, surface), useColors));
3946
+ }
3830
3947
  return lines.join(`
3831
3948
  `).trimEnd();
3832
3949
  }
3950
+ function formatAdvisoryCapHint(hidden, surface) {
3951
+ const hint = surface === "mcp" ? "use verbose=true or format=json" : "use -v";
3952
+ return `... (+${hidden} more; ${hint})`;
3953
+ }
3833
3954
  function severityColumnLabel(advisory) {
3834
3955
  if (advisory.isMalicious === true) {
3835
3956
  if (advisory.severityLabel)
3836
- return `MALWARE · ${advisory.severityLabel}`;
3957
+ return `MALWARE | ${advisory.severityLabel}`;
3837
3958
  return "MALWARE";
3838
3959
  }
3839
3960
  return advisory.severityLabel ?? "unrated";
@@ -3862,7 +3983,7 @@ function severityColumnColor(advisory, useColors, padded) {
3862
3983
  return dim(padded, useColors);
3863
3984
  }
3864
3985
  }
3865
- function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit) {
3986
+ function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit, surface) {
3866
3987
  const rawLabel = severityColumnLabel(advisory);
3867
3988
  const padded = rawLabel.padEnd(labelWidth);
3868
3989
  const colouredLabel = severityColumnColor(advisory, useColors, padded);
@@ -3879,7 +4000,7 @@ function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimi
3879
4000
  lines.push(` ${label.padEnd(detailWidth)} ${value}`);
3880
4001
  };
3881
4002
  if (advisory.affectedRanges && advisory.affectedRanges.length > 0) {
3882
- pushRow("affected", formatRangeList(advisory.affectedRanges, verbose, useColors, rangeLimit, advisory.affectedVersionRangesCount, advisory.affectedVersionRangesTruncated));
4003
+ pushRow("affected", formatRangeList(advisory.affectedRanges, verbose, useColors, rangeLimit, surface, advisory.affectedVersionRangesCount, advisory.affectedVersionRangesTruncated));
3883
4004
  }
3884
4005
  if (advisory.fixedIn && advisory.fixedIn.length > 0) {
3885
4006
  pushRow("fixed in", advisory.fixedIn.join(", "));
@@ -3906,12 +4027,12 @@ function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimi
3906
4027
  }
3907
4028
  return lines;
3908
4029
  }
3909
- function formatRangeList(ranges, verbose, useColors, limit, totalCount, backendTruncated) {
4030
+ function formatRangeList(ranges, verbose, useColors, limit, surface, totalCount, backendTruncated) {
3910
4031
  const actualTotal = Math.max(totalCount ?? ranges.length, ranges.length);
3911
4032
  const backendHidden = backendTruncated === true ? actualTotal - ranges.length : 0;
3912
4033
  const appendBackendHint = (shown2) => {
3913
4034
  if (backendHidden > 0) {
3914
- const hint2 = dim(`… (+${backendHidden} ranges omitted by service)`, useColors);
4035
+ const hint2 = dim(`... (+${backendHidden} ranges omitted by service)`, useColors);
3915
4036
  return shown2.length > 0 ? `${shown2}, ${hint2}` : hint2;
3916
4037
  }
3917
4038
  return shown2;
@@ -3921,7 +4042,8 @@ function formatRangeList(ranges, verbose, useColors, limit, totalCount, backendT
3921
4042
  }
3922
4043
  const shown = ranges.slice(0, limit).join(", ");
3923
4044
  const localHidden = ranges.length - limit;
3924
- const hintText = backendHidden > 0 ? `… (+${localHidden} more with -v; +${backendHidden} omitted by service)` : `… (+${localHidden} more; use -v)`;
4045
+ const localHint = surface === "mcp" ? "use verbose=true" : "use -v";
4046
+ const hintText = backendHidden > 0 ? `... (+${localHidden} more with ${localHint}; +${backendHidden} omitted by service)` : `... (+${localHidden} more; ${localHint})`;
3925
4047
  const hint = dim(hintText, useColors);
3926
4048
  return `${shown}, ${hint}`;
3927
4049
  }
@@ -4615,11 +4737,11 @@ function buildUnifiedSearchParams(input) {
4615
4737
  }
4616
4738
  function resolveTargets(target, targets) {
4617
4739
  if (target && targets) {
4618
- throw new InvalidArgumentError("Provide either target or targets, not both.");
4740
+ throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple, not both.");
4619
4741
  }
4620
4742
  const resolved = target ? [target] : targets ?? [];
4621
4743
  if (resolved.length === 0) {
4622
- throw new InvalidArgumentError("At least one target is required.");
4744
+ throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple; neither was set.");
4623
4745
  }
4624
4746
  const deduped = [];
4625
4747
  const seen = new Set;
@@ -4738,7 +4860,7 @@ function combineWarnings(parserWarnings, sourceStatus, hits = [], progress) {
4738
4860
  out.push(...buildHitFreshnessWarnings(hits));
4739
4861
  out.push(...buildProgressFreshnessWarnings(progress));
4740
4862
  out.push(...buildSourceStatusWarnings(sourceStatus));
4741
- return out;
4863
+ return Array.from(new Set(out));
4742
4864
  }
4743
4865
  function buildUnifiedSearchErrorPayload(error2) {
4744
4866
  const mapped = mapCodeNavigationError(error2);
@@ -4949,7 +5071,7 @@ function compactProgress(progress) {
4949
5071
  payload.requestedTargets = progress.requestedTargets;
4950
5072
  }
4951
5073
  if (progress.filters)
4952
- payload.filters = buildFilterEcho3(progress.filters);
5074
+ payload.filters = buildFilterEcho4(progress.filters);
4953
5075
  if (typeof progress.limit === "number")
4954
5076
  payload.limit = progress.limit;
4955
5077
  if (typeof progress.offset === "number")
@@ -4996,7 +5118,7 @@ function compactProgressTarget(target) {
4996
5118
  payload.requestedRefKind = target.requestedRefKind;
4997
5119
  return Object.keys(payload).length > 0 ? payload : undefined;
4998
5120
  }
4999
- function buildFilterEcho3(filters) {
5121
+ function buildFilterEcho4(filters) {
5000
5122
  const echo = {};
5001
5123
  if (filters.kind)
5002
5124
  echo.kind = filters.kind.toLowerCase();
@@ -6271,7 +6393,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
6271
6393
  match in --verbose output; full payload in --json).`;
6272
6394
  function registerCodeGrepCommand(pkgCommand) {
6273
6395
  return pkgCommand.command("grep").summary("Deterministic text grep over indexed dependency source").description(PKG_GREP_DESCRIPTION).argument("[spec-or-pattern]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the pattern.").argument("[pattern-or-prefix]", "Spec mode: the pattern. Repo mode: optional path-prefix.").argument("[path-prefix]", "Spec mode only: optional path-prefix. Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--path <path>", "Exact file path to grep").option("--glob <glob>", "Glob scope (repeatable)", collectRepeatable2, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable2, []).option("--regex", "Interpret the pattern as RE2 regex").option("--case-sensitive", "Enable ASCII case-sensitive matching").option("-C, --context <n>", "Context lines before and after each match (0-10)").option("-B, --before-context <n>", "Context lines before each match (0-10)").option("-A, --after-context <n>", "Context lines after each match (0-10)").option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--limit <n>", "Max matches to return on this page (1-1000, default 50)").option("--per-file-limit <n>", "Cap matches per file within this page (0-1000, 0 = unlimited)").option("--cursor <cursor>", "Opaque nextCursor from a previous grep result").option("--symbol-field <field>", `Repeatable; surfaces in --json and under each --verbose match. ${GREP_REPO_SYMBOL_FIELDS_NOTE}`, collectRepeatable2, []).option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render grouped output with file headers").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, arg3, options) => {
6274
- const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
6396
+ const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
6275
6397
  const deps = await createContainer2();
6276
6398
  await pkgGrepAction(arg1, arg2, arg3, options, {
6277
6399
  codeNavigationService: deps.codeNavigationService,
@@ -6805,7 +6927,7 @@ function registerExampleCommand(program) {
6805
6927
  });
6806
6928
  }
6807
6929
  async function loadContainer() {
6808
- const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
6930
+ const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
6809
6931
  return createContainer2();
6810
6932
  }
6811
6933
  // src/commands/feedback.ts
@@ -6833,17 +6955,25 @@ async function feedbackAction(solutionId, options, deps) {
6833
6955
  process.exit(1);
6834
6956
  }
6835
6957
  }
6836
- var FEEDBACK_DESCRIPTION = `Submit feedback on a search result.
6958
+ var FEEDBACK_DESCRIPTION = `Submit feedback on a tool result or the GitHits experience.
6959
+
6960
+ Two modes:
6961
+ - Solution-tied: pass the [solution_id] from a prior 'githits example'
6962
+ result (shown at the bottom of the markdown / under 'solution_id'
6963
+ in --json) to anchor feedback to that specific result.
6964
+ - Generic: omit [solution_id] to send feedback about any command
6965
+ (search, pkg, docs, code) or the overall experience. A --message
6966
+ is strongly recommended here.
6837
6967
 
6838
- Rate whether a code example was helpful. Use --accept for positive
6839
- feedback or --reject for negative. Optionally add a message.
6968
+ Use --accept for positive feedback or --reject for negative.
6840
6969
 
6841
6970
  Examples:
6842
6971
  githits feedback abc123 --accept
6843
6972
  githits feedback abc123 --reject -m "Example was outdated"
6844
- githits feedback abc123 --accept --message "Solved my problem" --json`;
6973
+ githits feedback --accept -m "code_grep regex is fast on npm:lodash"
6974
+ githits feedback --reject -m "search missing kotlin support"`;
6845
6975
  function registerFeedbackCommand(program) {
6846
- program.command("feedback").summary("Submit feedback on a search result").description(FEEDBACK_DESCRIPTION).argument("<solution_id>", "Solution ID from search result").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
6976
+ program.command("feedback").summary("Submit feedback on a tool result or the GitHits experience").description(FEEDBACK_DESCRIPTION).argument("[solution_id]", "Solution ID from a prior 'githits example' result (omit for generic feedback)").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
6847
6977
  try {
6848
6978
  const deps = await createContainer();
6849
6979
  await feedbackAction(solutionId, options, deps);
@@ -7908,13 +8038,17 @@ function classify3(operation, error2) {
7908
8038
 
7909
8039
  // src/tools/feedback.ts
7910
8040
  var schema = {
7911
- solution_id: z.string().min(1).describe("The solution ID from a previous search result (shown in the result)"),
7912
- accepted: z.boolean().describe("True if the example was helpful/good, False if unhelpful/bad"),
7913
- feedback_text: z.string().optional().describe('Optional text explaining why (e.g., "This solved problem X" or "Example was outdated")')
8041
+ solution_id: z.string().min(1).optional().describe("Optional. Pass the `solution_id` from a prior `get_example` response (shown on the trailing line of the markdown result, or under the `solution_id` key in JSON mode) to anchor feedback to that specific result. Omit for generic feedback about any tool (code/package navigation, search, docs) or the overall experience."),
8042
+ accepted: z.boolean().describe("True for positive feedback (helpful/good), False for negative (unhelpful/bad). Always required."),
8043
+ feedback_text: z.string().optional().describe('Optional context (e.g., "This solved problem X" or "code_grep regex over npm:lodash missed Foo function"). Strongly recommended when `solution_id` is omitted, since there is no specific result to anchor to.')
7914
8044
  };
7915
- var DESCRIPTION = `Submit feedback on a GitHits example result.
8045
+ var DESCRIPTION = `Submit feedback on a tool result or the GitHits experience.
8046
+
8047
+ Two modes:
8048
+ 1. **Solution-tied** — pass the \`solution_id\` from a prior \`get_example\` response to rate that specific result.
8049
+ 2. **Generic** — omit \`solution_id\` to send feedback about any tool (\`search\`, \`code_grep\`, \`code_read\`, \`code_files\`, \`docs_*\`, \`pkg_*\`) or the overall experience.
7916
8050
 
7917
- Call after \`get_example\` to record whether the returned example was used. \`accepted=true\` when it solved the problem or was useful; \`accepted=false\` when it was irrelevant or wrong. Use \`feedback_text\` to add a short reason. Feeds back into ranking quality.`;
8051
+ \`accepted\` is always required (true = positive, false = negative). Add \`feedback_text\` for context strongly recommended in generic mode. Feeds ranking and product quality.`;
7918
8052
  function createFeedbackTool(service) {
7919
8053
  return {
7920
8054
  name: "feedback",
@@ -7938,7 +8072,7 @@ var schema2 = {
7938
8072
  query: z2.string().min(1).describe("Natural-language example-search query for canonical code examples."),
7939
8073
  language: z2.string().min(1).optional().describe("Optional programming language. If omitted, GitHits tries to infer it automatically. Use search_language first only when you need to force a specific language and the exact name is uncertain."),
7940
8074
  license_mode: z2.enum(["strict", "yolo", "custom"]).optional().describe("License filtering mode: strict (default), yolo, or custom."),
7941
- format: z2.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` returns markdown directly with a trailing `solution_id` line when available. Pass `json` for `{result, solution_id?}`.")
8075
+ format: z2.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` returns markdown directly with a trailing `solution_id` line when available. Pass `format: "json"` for `{result, solution_id?}`.')
7942
8076
  };
7943
8077
  var DESCRIPTION2 = `Get verified, canonical code examples from global open source.
7944
8078
 
@@ -8063,7 +8197,7 @@ var schema3 = {
8063
8197
  wait_timeout_ms: z4.number().optional(),
8064
8198
  format: z4.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output (matches grouped by file with grep -A/-B notation for context). Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
8065
8199
  };
8066
- var DESCRIPTION3 = "Deterministic text grep over indexed dependency and repository source files. " + "Use this when you know the text pattern you want; use `search` for discovery. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + 'Default response is a compact line-oriented listing (`format: "text-v1"`); pass `format: "json"` for the structured envelope. ' + "Matches chain directly into `code_read` (the `path` and `line` from each match feed straight into `start_line` / `end_line`).";
8200
+ var DESCRIPTION3 = "Deterministic text or regex grep over indexed dependency and repository source files. " + 'Use this when you know the pattern (literal by default; pass `pattern_type: "regex"` for RE2). ' + "Use `search` for discovery instead. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + "Each match's `path` chains into `code_read.path`; pick a window around `match.line` for `code_read.start_line` / `end_line`.";
8067
8201
  function createGrepRepoTool(service) {
8068
8202
  return {
8069
8203
  name: "code_grep",
@@ -8156,9 +8290,9 @@ var schema4 = {
8156
8290
  include_hidden: z5.boolean().optional(),
8157
8291
  limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
8158
8292
  wait_timeout_ms: z5.number().optional().describe("Max milliseconds to wait for indexing (0–60000, default 20000). On an `INDEXING` error envelope, retry with a longer timeout or pass a version from `details.availableVersions`."),
8159
- format: z5.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact paths-only listing tuned for agent context efficiency. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
8293
+ format: z5.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact paths-only listing. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
8160
8294
  };
8161
- var DESCRIPTION4 = "List files in an indexed dependency. Default response is a compact " + 'paths-only listing (`format: "text-v1"`); pass `format: "json"` ' + "for the structured envelope `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "Address via `target.registry` + `target.package_name` (package " + "scope) or `target.repo_url` + `target.git_ref` (repo scope), " + "mutually exclusive. Narrow with `path`, `path_prefix`, `globs`, " + "`extensions`, `file_types`, `languages`, or file-intent filters. " + "The returned paths feed directly into `code_read` and help scope " + "`code_grep`. Returns an `INDEXING` error envelope when the " + "dependency is being indexed on-demand — retry with a longer " + "`wait_timeout_ms` or use a version from `details.availableVersions`.";
8295
+ var DESCRIPTION4 = "List files in an indexed dependency. Use this to discover paths " + "before `code_read` (when `code_read` returns `FILE_NOT_FOUND` or " + "you don't yet know the path) and to scope `code_grep`. Address " + "via `target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + `target.git_ref` (repo scope), mutually " + "exclusive. Narrow with `path`, `path_prefix`, `globs`, " + "`extensions`, `file_types`, `languages`, or file-intent filters. " + "JSON envelope shape: `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "Returns an `INDEXING` error envelope when the dependency is being " + "indexed on-demand — retry with a longer `wait_timeout_ms` or use " + "a version from `details.availableVersions`.";
8162
8296
  function createListFilesTool(service) {
8163
8297
  return {
8164
8298
  name: "code_files",
@@ -8451,11 +8585,11 @@ function formatPackageChangelogTerminal(envelope, options) {
8451
8585
  const dateWidth = 10;
8452
8586
  for (const entry of envelope.entries.items) {
8453
8587
  const version2 = entry.version ?? "(unversioned)";
8454
- const date = entry.publishedAt ? formatDate(entry.publishedAt) : dim("", options.useColors);
8588
+ const date = entry.publishedAt ? formatDate(entry.publishedAt) : dim("-", options.useColors);
8455
8589
  const url = entry.htmlUrl ? dim(entry.htmlUrl, options.useColors) : dim("(no link)", options.useColors);
8456
8590
  const padded = padColumn(version2, versionWidth);
8457
8591
  const datePadded = padColumn(date, dateWidth);
8458
- lines.push(`${colorize(padded, "bold", options.useColors)} ${datePadded} ${url}`);
8592
+ lines.push(`${highlight(`${padded} ${datePadded}`, options.useColors)} ${url}`);
8459
8593
  if (entry.body != null) {
8460
8594
  appendBodyLines(lines, entry.body, options);
8461
8595
  }
@@ -8472,28 +8606,28 @@ function appendBodyLines(lines, body, options) {
8472
8606
  }
8473
8607
  const bodyLines = body.split(`
8474
8608
  `);
8475
- const cap = options.verbose ? bodyLines.length : DEFAULT_BODY_PREVIEW_LINES;
8609
+ const cap = options.verbose ? bodyLines.length : options.bodyPreviewLines ?? DEFAULT_BODY_PREVIEW_LINES;
8476
8610
  const visible = bodyLines.slice(0, cap);
8477
8611
  for (const bodyLine of visible) {
8478
- lines.push(` ${dim(bodyLine, options.useColors)}`);
8612
+ lines.push(` ${bodyLine}`);
8479
8613
  }
8480
8614
  const hidden = bodyLines.length - visible.length;
8481
8615
  if (hidden > 0) {
8482
- lines.push(` ${dim(`… (+${hidden} more line${hidden === 1 ? "" : "s"} ${options.fullBodyHint ?? "use --verbose for the full body"})`, options.useColors)}`);
8616
+ lines.push(` ${dim(`... (+${hidden} more line${hidden === 1 ? "" : "s"} - ${options.fullBodyHint ?? "use --verbose for the full body"})`, options.useColors)}`);
8483
8617
  }
8484
8618
  }
8485
8619
  function buildSummaryLine(envelope, options) {
8486
- const identity = envelope.registry && envelope.name ? `${envelope.name} · ${envelope.registry}` : envelope.repoUrl ?? "(unknown)";
8620
+ const identity = envelope.registry && envelope.name ? `${envelope.name} | ${envelope.registry}` : envelope.repoUrl ?? "(unknown)";
8487
8621
  const sourceLabel = envelope.source ? humanizeSource(envelope.source) : "package versions";
8488
8622
  const modeLabel = envelope.mode === "range" ? rangeLabel(envelope) : latestLabel(envelope);
8489
8623
  const countLabel = `${envelope.entries.count} ${plural2("entry", "entries", envelope.entries.count)}`;
8490
8624
  const parts = [identity, `source: ${sourceLabel}`, modeLabel, countLabel];
8491
- return colorize(parts.join(" · "), "bold", options.useColors);
8625
+ return colorize(parts.join(" | "), "bold", options.useColors);
8492
8626
  }
8493
8627
  function rangeLabel(envelope) {
8494
8628
  const from = envelope.filter?.fromVersion ?? "earliest";
8495
8629
  const to = envelope.filter?.toVersion ?? "latest";
8496
- return `range ${from} ${to}`;
8630
+ return `range ${from} -> ${to}`;
8497
8631
  }
8498
8632
  function latestLabel(envelope) {
8499
8633
  if (envelope.filter?.toVersion) {
@@ -8552,9 +8686,11 @@ var schema6 = {
8552
8686
  limit: z7.number().optional().describe("Latest-mode cap on entry count (1–50, default 10). Rejected with `INVALID_ARGUMENT` when `from_version` is also set or when out of range."),
8553
8687
  git_ref: z7.string().optional().describe("Git branch or tag for CHANGELOG.md source (no effect on GitHub Releases or HexDocs). Defaults to the repository's default branch."),
8554
8688
  include_bodies: z7.boolean().optional().describe("When false, each entry in `entries.items[]` omits its `body` field. Default true. Set false when you only need the version / date / URL timeline — drops 10 KB+ per entry on large release notes."),
8555
- format: z7.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8689
+ verbose: z7.boolean().optional().describe("Text output only. Show full body previews. Mutually exclusive with include_bodies:false and body_lines."),
8690
+ body_lines: z7.number().optional().describe("Text output only. Number of body lines to preview per entry (1-50, default 10). Ignored for format=json and include_bodies:false. Mutually exclusive with verbose:true."),
8691
+ format: z7.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact entry timeline with body previews. Pass `format: "json"` for the structured envelope with full markdown bodies.')
8556
8692
  };
8557
- var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), ' + 'and entries with markdown body previews. Pass `format: "json"` ' + "for the structured envelope with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Package-version entries without changelog text succeed with `source` " + "omitted; no-source plus no entries returns `NOT_FOUND`. Supports npm, " + "PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, Packagist, RubyGems, Go.";
8693
+ var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), and entries with ' + "markdown body previews. Example: " + '`{"registry":"npm","package_name":"express","limit":2}`. ' + "Text output previews 10 body lines by default; use `body_lines` " + "to tune the preview or `verbose:true` for full text bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only; " + 'pass `format: "json"` for the complete structured envelope. ' + "Package-version entries without changelog " + "text succeed with `source` omitted; no-source plus no entries " + "returns `NOT_FOUND`. Supports npm, PyPI, Hex, Crates, NuGet, " + "Maven, Zig, vcpkg, Packagist, RubyGems, and Go.";
8558
8694
  function createPackageChangelogTool(service) {
8559
8695
  return {
8560
8696
  name: "pkg_changelog",
@@ -8563,6 +8699,8 @@ function createPackageChangelogTool(service) {
8563
8699
  annotations: { readOnlyHint: true },
8564
8700
  handler: async (args) => {
8565
8701
  try {
8702
+ const textFormat = isTextFormat5(args.format);
8703
+ const bodyPreviewLines = textFormat ? validateTextOptions(args) : undefined;
8566
8704
  const { params, explicitFilterFields } = buildPackageChangelogParams({
8567
8705
  registry: args.registry,
8568
8706
  packageName: args.package_name,
@@ -8585,11 +8723,12 @@ function createPackageChangelogTool(service) {
8585
8723
  limit: params.limit,
8586
8724
  gitRef: params.gitRef
8587
8725
  });
8588
- if (isTextFormat5(args.format)) {
8726
+ if (textFormat) {
8589
8727
  return textResult(formatPackageChangelogTerminal(payload, {
8590
8728
  useColors: false,
8591
- verbose: false,
8592
- fullBodyHint: 'pass format="json" for full bodies'
8729
+ verbose: args.verbose ?? false,
8730
+ bodyPreviewLines,
8731
+ fullBodyHint: 'pass verbose=true, body_lines=<n>, or format="json" for full bodies'
8593
8732
  }).trimEnd());
8594
8733
  }
8595
8734
  return textResult(JSON.stringify(payload));
@@ -8613,22 +8752,36 @@ function createPackageChangelogTool(service) {
8613
8752
  }
8614
8753
  };
8615
8754
  }
8755
+ function validateTextOptions(args) {
8756
+ if (args.include_bodies === false && args.verbose === true) {
8757
+ throw new InvalidPackageSpecError("verbose:true conflicts with include_bodies:false because bodies are omitted. Drop one of the two options.");
8758
+ }
8759
+ if (args.verbose === true && args.body_lines !== undefined) {
8760
+ throw new InvalidPackageSpecError("body_lines conflicts with verbose:true because verbose already shows full bodies. Drop one of the two options.");
8761
+ }
8762
+ if (args.body_lines === undefined)
8763
+ return;
8764
+ if (!Number.isInteger(args.body_lines) || args.body_lines < 1 || args.body_lines > 50) {
8765
+ throw new InvalidPackageSpecError(`body_lines must be an integer between 1 and 50. Got ${args.body_lines}.`);
8766
+ }
8767
+ return args.body_lines;
8768
+ }
8616
8769
  function isTextFormat5(format) {
8617
8770
  return format === undefined || format === "text" || format === "text-v1";
8618
8771
  }
8619
8772
  // src/tools/package-dependencies.ts
8620
8773
  import { z as z8 } from "zod";
8621
8774
  var schema7 = {
8622
- registry: z8.string().describe(`Package registry. Dependency data is available on ${SUPPORTED_DEPS_REGISTRIES_HUMAN}.`),
8775
+ registry: z8.string().describe(`Package registry. Dependency data is available on ${SUPPORTED_DEPS_REGISTRIES_LIST}.`),
8623
8776
  package_name: z8.string().describe("Package name (scoped names ok: @types/node)."),
8624
8777
  version: z8.string().optional().describe("Specific version to inspect. Defaults to latest when omitted. Tag-style inputs with a leading `v` (for example `v4.18.0`) are rejected — pass the canonical version (`4.18.0`)."),
8625
8778
  lifecycle: z8.union([z8.string(), z8.array(z8.string())]).optional().describe("Lifecycle breadth. Omit for runtime-only. Use `runtime` for explicit runtime-only, a concrete non-runtime lifecycle (`development`, `build`, `peer`, `optional`) for runtime plus matching groups, or `all` for runtime plus all available groups. Accepts a single value, a comma-separated string, or an array; `all` cannot be combined with other values. Uppercase is tolerated."),
8626
8779
  include_transitive: z8.boolean().optional().describe("When true the response gains a `transitive` block with aggregate counts (`edges`, `uniquePackages`), the preprocessed `packages[]` list (each `{name, version}` — the complete install footprint), plus typed `conflicts[]` (`{name, requiredVersions}`) and `circularDependencies[]` (`{cycle: string[]}`) when the backend reported any. Off by default."),
8627
8780
  include_importers: z8.boolean().optional().describe("Requires `include_transitive: true`. When true, each entry in `transitive.packages[]` also carries an `importers` array — every upstream package that pulls it in, with that importer's own resolved version and the constraint it declared. Off by default because adding provenance roughly quadruples the envelope size on heavy graphs. Turn on when you need to trace why a specific transitive dep is present."),
8628
8781
  max_depth: z8.number().int().min(1).max(10).optional().describe("Cap the transitive traversal at this depth (1–10). Omit to get the backend's full graph. Requires `include_transitive: true` — passing `max_depth` without the transitive flag is rejected with `INVALID_ARGUMENT`."),
8629
- format: z8.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8782
+ format: z8.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` compact dependency listing. Pass `format: "json"` for the structured envelope.')
8630
8783
  };
8631
- var DESCRIPTION7 = "Analyze a package's dependency graph. Default output is compact " + "text listing direct runtime dependencies with resolved versions; " + 'pass `format: "json"` for the structured envelope. Non-runtime ' + "groups are omitted by default for token efficiency. Use `lifecycle` " + "with a concrete value for runtime plus matching groups, or `all` " + "for runtime plus all available groups. Set " + "`include_transitive: true` to add a `transitive` block with the " + "full install footprint, conflict detection, and circular-" + "dependency flags; layer `include_importers: true` on top when you " + "also need per-package provenance. Supports npm, PyPI, Hex, Crates, " + "RubyGems, Go, vcpkg, and Zig.";
8784
+ var DESCRIPTION7 = "Analyze a package's dependency graph. Lists direct runtime " + "dependencies with resolved versions; non-runtime groups are " + "omitted by default. Use `lifecycle` with a concrete value for " + "runtime plus matching groups, or `all` for runtime plus every " + "available group. Set `include_transitive: true` to add a " + "`transitive` block with the full install footprint, conflict " + "detection, and circular-dependency flags; layer " + "`include_importers: true` on top when you also need per-package " + "provenance. Supports npm, PyPI, Hex, Crates, Zig, vcpkg, RubyGems, " + "and Go.";
8632
8785
  function createPackageDependenciesTool(service) {
8633
8786
  return {
8634
8787
  name: "pkg_deps",
@@ -8701,9 +8854,10 @@ import { z as z9 } from "zod";
8701
8854
  var schema8 = {
8702
8855
  registry: z9.string().describe(`Package registry. One of: ${PKGSEER_REGISTRY_LIST}.`),
8703
8856
  package_name: z9.string().describe("Package name (scoped names ok: @types/node)."),
8704
- format: z9.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8857
+ verbose: z9.boolean().optional().describe("Text only. Adds GitHub language/topics/last-pushed, recent advisories, and recent changes. Ignored for format=json."),
8858
+ format: z9.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact package overview. Pass `format: "json"` for the structured envelope.')
8705
8859
  };
8706
- var DESCRIPTION8 = "Get a package overview latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + 'Default output is compact text; pass `format: "json"` for the ' + "structured envelope. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "RubyGems, Go, vcpkg, and Zig. Always returns data for the latest published " + "version.";
8860
+ var DESCRIPTION8 = "Latest-version package overview for dependency triage. Provide " + "`registry` and `package_name` (for example `npm` + `express`). " + "Default text returns license, description, repository popularity " + "(stars/forks/issues and [ARCHIVED] when applicable), 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. Use ' + "`pkg_vulns` for version-specific vulnerability details.";
8707
8861
  function createPackageSummaryTool(service) {
8708
8862
  return {
8709
8863
  name: "pkg_info",
@@ -8720,6 +8874,7 @@ function createPackageSummaryTool(service) {
8720
8874
  const payload = buildPackageSummarySuccessPayload(summary);
8721
8875
  if (isTextFormat7(args.format)) {
8722
8876
  return textResult(formatPackageSummaryTerminal(summary, {
8877
+ verbose: args.verbose,
8723
8878
  useColors: false
8724
8879
  }).trimEnd());
8725
8880
  }
@@ -8750,14 +8905,16 @@ function isTextFormat7(format) {
8750
8905
  // src/tools/package-vulnerabilities.ts
8751
8906
  import { z as z10 } from "zod";
8752
8907
  var schema9 = {
8753
- registry: z10.string().describe("Package registry. Vulnerability data available on npm, pypi, hex, and crates only."),
8908
+ registry: z10.string().describe("Package registry. Vulnerability data is unavailable for vcpkg and zig."),
8754
8909
  package_name: z10.string().describe("Package name (scoped names ok: @types/node)."),
8755
8910
  version: z10.string().optional().describe("Specific version to check. Defaults to latest when omitted."),
8756
8911
  min_severity: z10.string().optional().describe("Only return advisories at or above this severity (`low`, `medium`, `high`, `critical`; uppercase tolerated). Omit to see all, including null-severity advisories."),
8757
8912
  include_withdrawn: z10.boolean().optional().describe("Include retracted advisories (default: false)."),
8758
- format: z10.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8913
+ advisory_scope: z10.string().optional().describe("Advisory rows to return: `affected` (default), `non_affecting` for historical advisories that do not affect the inspected version, or `all` for both affected and historical advisories. Counts always include affected/non-affecting/all totals."),
8914
+ verbose: z10.boolean().optional().describe("Text output only. Show every advisory and full detail rows; format=json always returns the complete structured envelope."),
8915
+ format: z10.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact advisory summary. Pass `format: "json"` for the structured envelope.')
8759
8916
  };
8760
- var DESCRIPTION9 = "Check known vulnerabilities for a package on npm, PyPI, Hex, or " + "Crates (other registries are not yet supported for vulnerability " + "data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges, and fix versions. Malicious-package advisories surface in a separate " + "bucket. Pass `version` to inspect a specific release; otherwise " + "the latest is checked. Use `min_severity` to filter to a threshold " + "(`low`, `medium`, `high`, `critical`) and `include_withdrawn` to " + "also see retracted advisories. Default output is compact text; " + 'pass `format: "json"` for the structured envelope.';
8917
+ var DESCRIPTION9 = "Check known vulnerabilities for a package on npm, PyPI, Hex, " + "Crates, NuGet, Maven, Packagist, RubyGems, or Go (vcpkg and Zig " + "are not supported for vulnerability data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges, and fix versions. Malicious-package " + "advisories surface in a separate bucket. Example: " + '`{"registry":"npm","package_name":"lodash","version":"4.17.20","min_severity":"high"}`. ' + "Pass `version` to inspect " + "a pinned release; omit it for latest. Default text is capped for " + "readability; use `verbose:true` for all selected advisory rows or " + '`format:"json"` for the complete envelope. Use ' + "`min_severity` to filter to a threshold (`low`, `medium`, `high`, " + "`critical`) and `include_withdrawn` to also see retracted " + 'advisories. Use `advisory_scope:"non_affecting"` to list ' + "historical advisories that do not affect the inspected version, or " + '`advisory_scope:"all"` to list affected and historical advisories together.';
8761
8918
  function createPackageVulnerabilitiesTool(service) {
8762
8919
  return {
8763
8920
  name: "pkg_vulns",
@@ -8766,21 +8923,26 @@ function createPackageVulnerabilitiesTool(service) {
8766
8923
  annotations: { readOnlyHint: true },
8767
8924
  handler: async (args) => {
8768
8925
  try {
8769
- const { params } = buildPackageVulnerabilitiesParams({
8926
+ const { params, filter } = buildPackageVulnerabilitiesParams({
8770
8927
  registry: args.registry,
8771
8928
  packageName: args.package_name,
8772
8929
  version: args.version,
8773
8930
  minSeverity: args.min_severity,
8774
- includeWithdrawn: args.include_withdrawn
8931
+ includeWithdrawn: args.include_withdrawn,
8932
+ advisoryScope: args.advisory_scope
8775
8933
  });
8776
8934
  const report = await service.packageVulnerabilities(params);
8777
8935
  const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
8778
- requestedVersion: args.version
8936
+ requestedVersion: args.version,
8937
+ filter
8779
8938
  });
8780
8939
  if (isTextFormat8(args.format)) {
8781
8940
  return textResult(formatPackageVulnerabilitiesTerminal(report, {
8782
8941
  useColors: false,
8783
- requestedVersion: args.version
8942
+ requestedVersion: args.version,
8943
+ filter,
8944
+ verbose: args.verbose,
8945
+ surface: "mcp"
8784
8946
  }).trimEnd());
8785
8947
  }
8786
8948
  return textResult(JSON.stringify(payload));
@@ -8900,7 +9062,8 @@ function shouldEmitCappedHint(bounded, payload) {
8900
9062
  }
8901
9063
  function buildCappedHint(payload, originalStart, originalEnd) {
8902
9064
  const requested = describeRequest(originalStart, originalEnd);
8903
- return `Returned lines ${payload.startLine}-${payload.endLine}/${payload.totalLines} ` + `(MCP cap: ${MCP_READ_MAX_SPAN} lines per call; you requested ${requested}). ` + `Pick a focused start_line/end_line window — typical 80-150 lines around a search/code_grep match. ` + `Each retry also costs context, so aim for one well-sized read.`;
9065
+ const continuation = payload.endLine !== undefined ? ` To continue, retry with start_line=${payload.endLine + 1}.` : "";
9066
+ return `Returned lines ${payload.startLine}-${payload.endLine}/${payload.totalLines} ` + `(MCP cap: ${MCP_READ_MAX_SPAN} lines per call; you requested ${requested}).` + `${continuation} ` + `Pick a focused start_line/end_line window — typical 80-150 lines around a search/code_grep match. ` + `Each retry also costs context, so aim for one well-sized read.`;
8904
9067
  }
8905
9068
  function describeRequest(originalStart, originalEnd) {
8906
9069
  if (originalStart === undefined && originalEnd === undefined) {
@@ -9031,9 +9194,9 @@ var schema12 = {
9031
9194
  limit: z13.coerce.number().int().min(1).max(100).optional().describe("Maximum results to return (default 10, max 100)."),
9032
9195
  offset: z13.coerce.number().int().min(0).optional(),
9033
9196
  wait_timeout_ms: z13.coerce.number().int().min(0).max(60000).optional(),
9034
- format: z13.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output tuned for agent context efficiency. Pass `format: "json"` for the structured envelope (programmatic consumers, parity testing). `text` is an alias for `text-v1`. The text format is a public, snapshot-tested contract.')
9197
+ format: z13.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. The text format is a public, snapshot-tested contract.')
9035
9198
  };
9036
- var DESCRIPTION12 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "Provide either `target` for one target or `targets` for many; omit `sources` to use backend AUTO. " + "The query field uses GitHits discovery syntax (AND/OR/parens/qualifiers; see the parameter description). " + "Structured parameters combine with that query using AND semantics. " + "Results are complete by default — if indexing is still running, the response carries a `searchRef` and no hits; pass it to `search_status` to follow up. " + "Set `allow_partial_results: true` to opt into hits from sources that finished while others continue indexing. " + "Each hit's `type` tells you the follow-up tool: `documentation_page` and `repository_doc` → `docs_read` with `locator.pageId`; `repository_code` and `repository_symbol` → `code_read` with `locator.filePath` (and `locator.startLine`/`endLine` when present).";
9199
+ var DESCRIPTION12 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "Provide either `target` for one target or `targets` for many; omit `sources` to use backend AUTO. " + "Structured parameters combine with the `query` using AND semantics. " + "Complete by default — if indexing is still running, the response carries a `searchRef` and no hits; pass it to `search_status` to follow up. " + "Set `allow_partial_results: true` to opt into hits from sources that finished while others continue indexing. " + "Each hit's `type` tells you the follow-up tool: `documentation_page` and `repository_doc` → `docs_read` with `locator.pageId`; `repository_code` and `repository_symbol` → `code_read` with `locator.filePath` (and `locator.startLine`/`endLine` when present).";
9037
9200
  function createSearchTool(service) {
9038
9201
  return {
9039
9202
  name: "search",
@@ -9137,7 +9300,7 @@ function isTextFormat11(format) {
9137
9300
  import { z as z14 } from "zod";
9138
9301
  var schema13 = {
9139
9302
  query: z14.string().min(1).describe('Language name or partial name to search for (e.g., "python", "type", "java")'),
9140
- format: z14.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` returns one language per line. Pass `json` for the structured array.")
9303
+ format: z14.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` returns one language per line. Pass `format: "json"` for the structured array.')
9141
9304
  };
9142
9305
  var DESCRIPTION13 = `Find the correct language name for \`get_example\` when it is uncertain. Returns up to 5 matching languages by name, display name, or alias. Default output is one language per line; pass \`format: "json"\` for the structured array.`;
9143
9306
  function createSearchLanguageTool(service) {
@@ -9173,10 +9336,10 @@ function renderLanguageMatches(matches) {
9173
9336
  // src/tools/search-status.ts
9174
9337
  import { z as z15 } from "zod";
9175
9338
  var schema14 = {
9176
- search_ref: z15.string().min(1).describe("Search reference returned by search."),
9339
+ search_ref: z15.string().min(1).describe("The `searchRef` field from a prior `search` response (camelCase in the response, snake_case as this parameter). Pass it through unchanged."),
9177
9340
  format: z15.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output matching `search`. Pass `format: "json"` for the structured envelope.')
9178
9341
  };
9179
- var DESCRIPTION14 = "Check progress, fetch partial hits when the original request used allow_partial_results: true, or fetch final results for a prior unified search. " + "Pass the search_ref returned by `search` when the original request did not complete within the wait window.";
9342
+ var DESCRIPTION14 = "Check progress, fetch partial hits (when the original request used `allow_partial_results: true`), or fetch final results for a prior `search` that returned a `searchRef`. " + "Pass the `searchRef` from that response as `search_ref` here (response field is camelCase; this parameter is snake_case).";
9180
9343
  function createSearchStatusTool(service) {
9181
9344
  return {
9182
9345
  name: "search_status",
@@ -9201,50 +9364,54 @@ function isTextFormat13(format) {
9201
9364
  return format === undefined || format === "text" || format === "text-v1";
9202
9365
  }
9203
9366
  // src/commands/mcp-instructions.ts
9204
- var CORE_BLOCK = `GitHits provides verified, canonical code examples from global open source. Use it for global solution synthesis and canonical examples when you're stuck, repeated attempts failed, you need up-to-date API usage, the question is comparative across OSS projects (e.g. "how does X vs Y handle Z"), the answer requires reading how a real codebase implements a feature, or the user mentions GitHits.
9367
+ var CORE_BLOCK = `GitHits provides verified, canonical code examples from global open source for AI agents. Pick a path:
9368
+
9369
+ - Need a canonical example or cross-project pattern with no specific package pinned (you're stuck, repeated attempts failed, the user wants up-to-date API usage, or the user mentioned GitHits) — call \`get_example\` for global solution synthesis.
9370
+ - Inspecting a specific known dependency or repository (stack trace points there, verifying how a particular library works, evaluating an upgrade) — call \`search\` plus the indexed code, docs, and package tools listed below.
9371
+ - Question is comparative across OSS projects (e.g. "how does X vs Y handle Z") or requires reading how a real codebase implements a feature — combine the two: \`search\` for known targets, \`get_example\` for cross-project synthesis.
9372
+ - Rate a result or share tool/UX feedback — call \`feedback\` with a \`solution_id\` from a prior \`get_example\` response (solution-tied) or omit it for generic feedback about any tool (\`search\`, \`code_*\`, \`docs_*\`, \`pkg_*\`) or the overall experience.
9205
9373
 
9206
- Example workflow: call \`get_example\` with one focused question; pass \`language\` only when you know the exact language name, otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`; send \`feedback\` on that solution_id. Reuse prior results before searching again. For dependency-specific grounding, use package-scoped \`search\` before global \`get_example\`.`;
9374
+ \`get_example\` workflow: pass \`language\` only when you know the exact name; otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`. Reuse prior results before searching again. For dependency-specific grounding, prefer package-scoped \`search\` before global \`get_example\`.`;
9207
9375
  var PACKAGE_TOOLS_PREAMBLE = `Indexed package/source tools inspect third-party dependency source, docs, and registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library works, or you're evaluating whether to add or upgrade a package.
9208
9376
 
9209
9377
  Package spec: \`registry:name[@version]\`. Default outputs are compact \`text-v1\` for agent context efficiency; pass \`format: "json"\` only when you need structured fields for programmatic parsing.`;
9210
- var PKG_INFO_BULLET = '- `pkg_info` compact package overview: latest version, license, downloads, quickstart, and active advisory count. Pass `format: "json"` for the structured envelope.';
9211
- var DOCS_LIST_BULLET = "- `docs_list` — browse hosted and repository-backed package docs. Entries include stable pageIds, source URLs, and repo-file follow-up metadata when available.";
9212
- var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";
9213
- var 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.';
9214
- var PKG_DEPS_BULLET = '- `pkg_deps` — compact direct runtime deps by default. Use `lifecycle: "runtime"` for explicit runtime-only, a concrete lifecycle for runtime plus matching non-runtime deps, or `lifecycle: "all"` for all groups. Use `include_transitive` for the full graph and `include_importers` for provenance. Pass `format: "json"` for the structured envelope.';
9215
- var PKG_CHANGELOG_BULLET = '- `pkg_changelog` — compact release notes for a package or GitHub repo, newest-first. Default latest mode returns recent entries with markdown body previews; `from_version` switches to range mode. Set `include_bodies: false` for a compact timeline or pass `format: "json"` for full bodies.';
9378
+ var MULTI_TURN_TIP = '**Delegate multi-call work to a sub-agent.** Code-navigation work (`search`, `code_grep`, `code_read`, `code_files`) often takes 3-10 calls. For cross-project comparisons, codebase mapping, pattern surveys, or "how does X actually work" investigations, spawn a sub-task/sub-agent and ask for a compact synthesis. Do it inline only when the raw snippet belongs in the main conversation.';
9216
9379
  var SEARCH_BULLET = '- `search` — unified search across indexed dependency code, docs, and symbols. Omit `sources` for AUTO. Use `sources:["code"]` for implementation/examples/tests text, `sources:["symbol"]` for precise API/entity lookup, and `sources:["docs"]` for guides/reference/changelogs. Default output is compact `text-v1` with ready-to-call follow-up arguments; pass `format: "json"` for structured locators, highlights, and source status. Complete by default; opt into partial hits with `allow_partial_results: true`. Incomplete responses carry a `searchRef`.';
9217
9380
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior `search` by `searchRef` to check progress, fetch partial hits, or fetch final results.";
9218
- var CODE_FILES_BULLET = '- `code_files` — list files in an indexed dependency. `path`, `path_prefix`, and `globs` are OR-ed selectors; extensions, language, file type, and file-intent filters intersect on top. Returned paths feed into `code_read` and help scope `code_grep`; pass `format: "json"` for language/type/size metadata.';
9219
- var CODE_READ_BULLET = "- `code_read` — read a dependency file by `path`. **MCP cap: 150 lines per call**; choose focused `start_line` / `end_line` windows from `search` or `code_grep`. Binary files set `isBinary: true` and omit `content`. On `FILE_NOT_FOUND` / `NOT_FOUND`, call `code_files` for the actual path.";
9220
9381
  var CODE_GREP_BULLET = "- `code_grep` — deterministic text or regex grep over indexed source. Use it when you know the pattern; use `search` for discovery. Narrow with `path`, `path_prefix`, `globs`, or `extensions`. Each match's `path:line` chains into `code_read`.";
9221
- var SEARCH_VS_SYMBOLS_TIP = 'Use code-first grounding for behavioral claims: source, symbols, tests, examples, and call sites beat docs prose. Prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Prefer `get_example` for global canonical example retrieval after package-scoped grounding. Use `code_grep` for deterministic text matching and `code_read` for focused-window inspection of a known file.';
9222
- var REFERENCE_FIRST_TIP = "Strategy — reference-first, content-second. Locate symbols and lines with `search` / `code_grep` first, then read only the needed window with `code_read` using explicit `start_line` / `end_line` values, typically 80-150 lines around the match. The MCP `code_read` surface caps each call at 150 lines.";
9223
- var MULTI_TURN_TIP = '**Delegate multi-call work to a sub-agent.** Code-navigation work (`search`, `code_grep`, `code_read`, `code_files`) often takes 3-10 calls. For cross-project comparisons, codebase mapping, pattern surveys, or "how does X actually work" investigations, spawn a sub-task/sub-agent and ask for a compact synthesis. Do it inline only when the raw snippet belongs in the main conversation.';
9382
+ var CODE_READ_BULLET = "- `code_read` read a dependency file by `path`. **MCP cap: 150 lines per call**. When you already have an exact path (e.g. from a stack trace), call this directly; otherwise locate it first with `search` or `code_grep` and pick a focused `start_line` / `end_line` window. Binary files set `isBinary: true` and omit `content`. On `FILE_NOT_FOUND` / `NOT_FOUND`, call `code_files` for the actual path.";
9383
+ var CODE_FILES_BULLET = '- `code_files` list files in an indexed dependency. `path`, `path_prefix`, and `globs` are OR-ed selectors; extensions, language, file type, and file-intent filters intersect on top. Returned paths feed into `code_read` and help scope `code_grep`; pass `format: "json"` for language/type/size metadata.';
9384
+ var DOCS_LIST_BULLET = "- `docs_list` browse hosted and repository-backed package docs. Entries include stable pageIds, source URLs, and repo-file follow-up metadata when available.";
9385
+ var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";
9386
+ var PKG_INFO_BULLET = '- `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.';
9387
+ var PKG_VULNS_BULLET = '- `pkg_vulns` — compact known CVE / OSV advisory summary for npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, or Go packages; vcpkg and Zig are not supported. Optionally pin `version` (e.g. `npm` + `lodash` + `4.17.20`). Filter with `min_severity`; include retracted advisories with `include_withdrawn`; set `advisory_scope: "non_affecting"` for historical advisories or `"all"` for affected + historical rows. Default text is capped; use `verbose: true` for all selected rows or `format: "json"` for the complete per-advisory envelope.';
9388
+ var PKG_DEPS_BULLET = '- `pkg_deps` — compact direct runtime deps by default. Use `lifecycle: "runtime"` for explicit runtime-only, a concrete lifecycle for runtime plus matching non-runtime deps, or `lifecycle: "all"` for all groups. Use `include_transitive` for the full graph and `include_importers` for provenance. Pass `format: "json"` for the structured envelope.';
9389
+ var PKG_CHANGELOG_BULLET = '- `pkg_changelog` — compact release notes for a package or GitHub repo, newest-first (e.g. `npm` + `express` + `limit: 2`). Default latest mode returns recent entries with 10-line markdown previews; `from_version` switches to range mode. Set `body_lines` to tune text previews, `verbose: true` for full text bodies, `include_bodies: false` for a compact timeline, or `format: "json"` for the complete envelope.';
9390
+ var STRATEGY_TIP = 'Strategy — reference-first. Locate symbols and lines with `search` or `code_grep` first, then read the needed window with `code_read` using explicit `start_line` / `end_line` (typical 80-150 lines around the match; the MCP `code_read` surface caps each call at 150 lines). Source, symbols, tests, and call sites beat docs prose for behavioral claims; use `sources:["symbol"]` when you want symbol-shaped results, `code_grep` for deterministic text matching, and `get_example` for global canonical examples after package-scoped grounding.';
9224
9391
  function buildMcpInstructions(_deps) {
9225
- const sections = [CORE_BLOCK];
9226
- const bullets = [];
9227
- bullets.push(DOCS_LIST_BULLET);
9228
- bullets.push(DOCS_READ_BULLET);
9229
- bullets.push(PKG_INFO_BULLET);
9230
- bullets.push(PKG_VULNS_BULLET);
9231
- bullets.push(PKG_DEPS_BULLET);
9232
- bullets.push(PKG_CHANGELOG_BULLET);
9233
- bullets.push(SEARCH_BULLET);
9234
- bullets.push(SEARCH_STATUS_BULLET);
9235
- bullets.push(CODE_FILES_BULLET);
9236
- bullets.push(CODE_READ_BULLET);
9237
- bullets.push(CODE_GREP_BULLET);
9238
- const parts = [PACKAGE_TOOLS_PREAMBLE];
9239
- parts.push(MULTI_TURN_TIP);
9240
- parts.push(bullets.join(`
9241
- `));
9242
- parts.push(REFERENCE_FIRST_TIP);
9243
- parts.push(SEARCH_VS_SYMBOLS_TIP);
9244
- sections.push(parts.join(`
9392
+ const bullets = [
9393
+ SEARCH_BULLET,
9394
+ SEARCH_STATUS_BULLET,
9395
+ CODE_GREP_BULLET,
9396
+ CODE_READ_BULLET,
9397
+ CODE_FILES_BULLET,
9398
+ DOCS_LIST_BULLET,
9399
+ DOCS_READ_BULLET,
9400
+ PKG_INFO_BULLET,
9401
+ PKG_VULNS_BULLET,
9402
+ PKG_DEPS_BULLET,
9403
+ PKG_CHANGELOG_BULLET
9404
+ ];
9405
+ const packageSection = [
9406
+ PACKAGE_TOOLS_PREAMBLE,
9407
+ MULTI_TURN_TIP,
9408
+ bullets.join(`
9409
+ `),
9410
+ STRATEGY_TIP
9411
+ ].join(`
9245
9412
 
9246
- `));
9247
- return sections.join(`
9413
+ `);
9414
+ return [CORE_BLOCK, packageSection].join(`
9248
9415
 
9249
9416
  `);
9250
9417
  }
@@ -9443,7 +9610,7 @@ function formatChangelogTerminalError(mapped) {
9443
9610
  if (available && available.length > 0) {
9444
9611
  const sample = available.slice(0, 5).join(", ");
9445
9612
  const more = available.length - 5;
9446
- const suffix = more > 0 ? `, (+${more} more)` : "";
9613
+ const suffix = more > 0 ? `, ... (+${more} more)` : "";
9447
9614
  lines.push(` available: ${sample}${suffix}`);
9448
9615
  }
9449
9616
  return lines.join(`
@@ -9582,8 +9749,7 @@ groups). Concrete --lifecycle values include runtime plus matching groups.
9582
9749
  conflict detection, and circular-dependency flags.
9583
9750
 
9584
9751
  Package spec: <registry>:<name>[@<version>]. Supported registries:
9585
- npm, pypi, hex, crates, vcpkg, zig, rubygems, go. Omit @<version> for the latest
9586
- release.`;
9752
+ ${SUPPORTED_DEPS_REGISTRIES_LIST}. Omit @<version> for the latest release.`;
9587
9753
  function registerPkgDepsCommand(pkgCommand) {
9588
9754
  return pkgCommand.command("deps").summary("Analyze dependencies for a package").description(PKG_DEPS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-l, --lifecycle <phases>", "Dependency lifecycle breadth (runtime, development, build, peer, optional, all; comma-separated for multi-select except all).").option("-t, --transitive", "Include aggregate transitive counts, conflicts, and circular dependencies").option("--depth <n>", "Cap transitive traversal depth (1-10). Omit for the full graph.").option("-v, --verbose", "Show conditionType / selectionMode / environmentConstraints metadata in the groups view").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
9589
9755
  const deps = await createContainer();
@@ -9640,16 +9806,20 @@ function handlePkgInfoCommandError(error2, json) {
9640
9806
  console.error(formatMappedErrorForTerminal(mapped));
9641
9807
  process.exit(1);
9642
9808
  }
9643
- var PKG_INFO_DESCRIPTION = `Get a package overview latest version, license, description,
9644
- repository, downloads, GitHub stars, install command, and known
9645
- vulnerabilities. Use before picking a dependency or to orient on what
9646
- a package is.
9809
+ var PKG_INFO_DESCRIPTION = `Latest-version package overview for dependency triage.
9810
+
9811
+ Default output shows license, description, repository popularity
9812
+ (stars/forks/issues and [ARCHIVED] when applicable), downloads,
9813
+ publish age, and vulnerability status. --verbose adds GitHub
9814
+ language/topics/last-pushed, recent advisories, and recent changes.
9647
9815
 
9648
9816
  Package spec: <registry>:<name>. Supported registries: ${PKGSEER_REGISTRY_LIST}.
9649
9817
 
9818
+ Example: githits pkg info npm:express
9819
+
9650
9820
  Always returns data for the latest published version.`;
9651
9821
  function registerPkgInfoCommand(pkgCommand) {
9652
- return pkgCommand.command("info").summary("Show a package overview").description(PKG_INFO_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or pypi:requests").option("-v, --verbose", "Show advisories, install usage, topics, and recent changes").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
9822
+ return pkgCommand.command("info").summary("Show a package overview").description(PKG_INFO_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or pypi:requests").option("-v, --verbose", "Show GitHub language/topics/last-pushed, recent advisories, and recent changes").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
9653
9823
  const deps = await createContainer();
9654
9824
  await pkgInfoAction(spec, options, {
9655
9825
  packageIntelligenceService: deps.packageIntelligenceService,
@@ -9668,17 +9838,19 @@ async function pkgVulnsAction(spec, options, deps) {
9668
9838
  throw new InvalidPackageSpecError("Package intelligence is not configured for this environment.");
9669
9839
  }
9670
9840
  const parsed = parsePackageSpec(spec);
9671
- const { params } = buildPackageVulnerabilitiesParams({
9841
+ const { params, filter } = buildPackageVulnerabilitiesParams({
9672
9842
  registry: parsed.registry,
9673
9843
  packageName: parsed.name,
9674
9844
  version: parsed.version,
9675
9845
  minSeverity: options.severity,
9676
- includeWithdrawn: options.includeWithdrawn
9846
+ includeWithdrawn: options.includeWithdrawn,
9847
+ advisoryScope: options.scope
9677
9848
  });
9678
9849
  const report = await deps.packageIntelligenceService.packageVulnerabilities(params);
9679
9850
  if (options.json) {
9680
9851
  const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
9681
- requestedVersion: parsed.version
9852
+ requestedVersion: parsed.version,
9853
+ filter
9682
9854
  });
9683
9855
  console.log(JSON.stringify(payload));
9684
9856
  return;
@@ -9687,6 +9859,8 @@ async function pkgVulnsAction(spec, options, deps) {
9687
9859
  verbose: options.verbose,
9688
9860
  useColors: shouldUseColors(),
9689
9861
  requestedVersion: parsed.version,
9862
+ filter,
9863
+ surface: "cli",
9690
9864
  terminalWidth: process.stdout.columns
9691
9865
  });
9692
9866
  process.stdout.write(output);
@@ -9729,24 +9903,30 @@ function formatVulnsTerminalError(mapped) {
9729
9903
  if (available && available.length > 0) {
9730
9904
  const sample = available.slice(0, 5).join(", ");
9731
9905
  const more = available.length - 5;
9732
- const suffix = more > 0 ? `, (+${more} more)` : "";
9906
+ const suffix = more > 0 ? `, ... (+${more} more)` : "";
9733
9907
  lines.push(` available: ${sample}${suffix}`);
9734
9908
  }
9735
9909
  return lines.join(`
9736
9910
  `);
9737
9911
  }
9738
9912
  var PKG_VULNS_DESCRIPTION = `Show known vulnerabilities for a package. Lists CVE / OSV advisories
9739
- with severity, affected version ranges, and fix versions.
9740
- Malicious-package advisories are flagged prominently.
9913
+ with severity, affected version ranges, and fix versions. Default text is
9914
+ capped for readability; use --verbose for all selected advisory rows or --json
9915
+ for the complete structured envelope.
9741
9916
 
9742
9917
  Package spec: <registry>:<name>[@<version>]. Supported registries:
9743
- npm, pypi, hex, crates. Omit @<version> to check the latest release.
9918
+ npm, pypi, hex, crates, nuget, maven, packagist, rubygems, go. vcpkg and zig are not supported.
9919
+ Omit @<version> to check the latest release.
9920
+ Example: githits pkg vulns npm:lodash@4.17.20 --severity high
9744
9921
 
9745
9922
  Severity filter (--severity) and withdrawn-advisory visibility
9746
9923
  (--include-withdrawn) are passed through to the backend; the
9747
- returned count reflects whatever survived the filter.`;
9924
+ returned count reflects whatever survived the filter and active filters
9925
+ are echoed in text and JSON output. Use --scope non_affecting to list
9926
+ historical advisories that do not affect the inspected version, or --scope all
9927
+ to list affected and historical package advisories together.`;
9748
9928
  function registerPkgVulnsCommand(pkgCommand) {
9749
- return pkgCommand.command("vulns").summary("List known vulnerabilities for a package").description(PKG_VULNS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-s, --severity <level>", "Only show advisories at or above this severity (low, medium, high, critical). Omit to see all.").option("--include-withdrawn", "Include retracted advisories (default: off)").option("-v, --verbose", "Show aliases, modified/withdrawn dates, and malicious-advisory markers").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
9929
+ return pkgCommand.command("vulns").summary("List known vulnerabilities for a package").description(PKG_VULNS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-s, --severity <level>", "Only show advisories at or above this severity (low, medium, high, critical). Omit to see all.").option("--scope <scope>", "Advisory rows to return: affected, non_affecting, all (default: affected)").option("--include-withdrawn", "Include retracted advisories (default: off)").option("-v, --verbose", "Show aliases, modified/withdrawn dates, and malicious-advisory markers").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
9750
9930
  const deps = await createContainer();
9751
9931
  await pkgVulnsAction(spec, options, {
9752
9932
  packageIntelligenceService: deps.packageIntelligenceService,
@@ -9763,7 +9943,7 @@ async function registerPkgCommandGroup(program, options = {}) {
9763
9943
  if (!registration.shouldRegister) {
9764
9944
  return;
9765
9945
  }
9766
- const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. For source-level operations inside a dependency, use `githits code`.");
9946
+ const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. Advisory data is unavailable for vcpkg and Zig. For source-level operations inside a dependency, use `githits code`.");
9767
9947
  registerPkgInfoCommand(pkgCommand);
9768
9948
  registerPkgVulnsCommand(pkgCommand);
9769
9949
  registerPkgDepsCommand(pkgCommand);
@@ -9887,7 +10067,7 @@ function requireSearchService(deps) {
9887
10067
  return deps.codeNavigationService;
9888
10068
  }
9889
10069
  async function loadContainer2() {
9890
- const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
10070
+ const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
9891
10071
  return createContainer2();
9892
10072
  }
9893
10073
  function parseTargetSpecs(specs) {