githits 0.2.2 → 0.2.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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "GitHits plugins for Claude Code - code examples from global open source",
9
- "version": "0.2.2"
9
+ "version": "0.2.3"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"
package/dist/cli.js CHANGED
@@ -41,11 +41,11 @@ import {
41
41
  setMcpClientVersionProvider,
42
42
  startTelemetrySpan,
43
43
  withTelemetrySpan
44
- } from "./shared/chunk-ns1j9dan.js";
44
+ } from "./shared/chunk-a9js75mw.js";
45
45
  import {
46
46
  __require,
47
47
  version
48
- } from "./shared/chunk-g6ay6x9v.js";
48
+ } from "./shared/chunk-js72s35a.js";
49
49
 
50
50
  // src/cli.ts
51
51
  import { Command } from "commander";
@@ -1146,12 +1146,280 @@ function mergeRanges(existing, incoming) {
1146
1146
  function clampCharacterOffset(text, offset) {
1147
1147
  return Math.max(0, Math.min(text.length, offset));
1148
1148
  }
1149
+ // src/shared/grep-repo-text.ts
1150
+ var SEP = " | ";
1151
+ function renderGrepRepoText(envelope) {
1152
+ const lines = [];
1153
+ lines.push(buildHeader(envelope));
1154
+ lines.push("");
1155
+ if (envelope.matches.length === 0) {
1156
+ lines.push("No matches.");
1157
+ const trailer2 = buildTrailer(envelope);
1158
+ if (trailer2.length > 0) {
1159
+ lines.push("");
1160
+ for (const t of trailer2)
1161
+ lines.push(t);
1162
+ }
1163
+ return lines.join(`
1164
+ `);
1165
+ }
1166
+ const blocks = buildRenderBlocks2(envelope.matches);
1167
+ const blocksByFile = groupBlocksByFile2(blocks);
1168
+ const useContext = blocksHaveContext(blocks);
1169
+ const matchCountsByFile = countMatchesByFile(envelope.matches);
1170
+ let firstFile = true;
1171
+ for (const [filePath, fileBlocks] of blocksByFile) {
1172
+ if (!firstFile)
1173
+ lines.push("");
1174
+ firstFile = false;
1175
+ const matchCount = matchCountsByFile.get(filePath) ?? 0;
1176
+ lines.push(`${filePath} (${matchCount})`);
1177
+ fileBlocks.forEach((block, idx) => {
1178
+ if (useContext && idx > 0)
1179
+ lines.push(" --");
1180
+ const gutterWidth = widestLineNumberInBlock(block);
1181
+ for (const ln of block.lines) {
1182
+ lines.push(renderLine(ln, gutterWidth, useContext));
1183
+ }
1184
+ });
1185
+ }
1186
+ const trailer = buildTrailer(envelope);
1187
+ if (trailer.length > 0) {
1188
+ lines.push("");
1189
+ for (const t of trailer)
1190
+ lines.push(t);
1191
+ }
1192
+ return lines.join(`
1193
+ `);
1194
+ }
1195
+ function buildHeader(envelope) {
1196
+ const parts = [
1197
+ `code_grep${SEP}${envelope.totalMatches} match${envelope.totalMatches === 1 ? "" : "es"} in ${envelope.uniqueFilesMatched} file${envelope.uniqueFilesMatched === 1 ? "" : "s"}`
1198
+ ];
1199
+ parts.push(`pattern=${quote(envelope.pattern)}`);
1200
+ const flags = [];
1201
+ if (envelope.patternType === "regex")
1202
+ flags.push("regex");
1203
+ if (envelope.caseSensitive)
1204
+ flags.push("case-sensitive");
1205
+ if (flags.length > 0)
1206
+ parts.push(flags.join(","));
1207
+ const filterEcho = buildFilterEcho(envelope);
1208
+ if (filterEcho)
1209
+ parts.push(filterEcho);
1210
+ return parts.join(SEP);
1211
+ }
1212
+ function buildFilterEcho(envelope) {
1213
+ const filter = envelope.filter;
1214
+ if (!filter)
1215
+ return "";
1216
+ const parts = [];
1217
+ if (filter.path)
1218
+ parts.push(`path=${quote(filter.path)}`);
1219
+ if (filter.pathPrefix)
1220
+ parts.push(`path_prefix=${quote(filter.pathPrefix)}`);
1221
+ if (filter.globs?.length)
1222
+ parts.push(`globs=${filter.globs.join(",")}`);
1223
+ if (filter.extensions?.length) {
1224
+ parts.push(`exts=${filter.extensions.join(",")}`);
1225
+ }
1226
+ if (typeof filter.maxMatches === "number") {
1227
+ parts.push(`max_matches=${filter.maxMatches}`);
1228
+ }
1229
+ if (typeof filter.maxMatchesPerFile === "number") {
1230
+ parts.push(`max_matches_per_file=${filter.maxMatchesPerFile}`);
1231
+ }
1232
+ return parts.join(" ");
1233
+ }
1234
+ function buildTrailer(envelope) {
1235
+ const lines = [];
1236
+ if (envelope.truncatedReason) {
1237
+ lines.push(`Truncated: ${envelope.truncatedReason}. Pass narrower path/path_prefix/globs or increase max_matches.`);
1238
+ }
1239
+ if (envelope.hasMore && envelope.nextCursor) {
1240
+ lines.push(`More matches available. Pass cursor=${envelope.nextCursor} for the next page.`);
1241
+ } else if (envelope.hasMore) {
1242
+ lines.push("More matches available.");
1243
+ }
1244
+ const skipNotes = [];
1245
+ if (envelope.binaryFilesSkipped) {
1246
+ skipNotes.push(`${envelope.binaryFilesSkipped} binary file(s) skipped`);
1247
+ }
1248
+ if (envelope.filesTooLargeSkipped) {
1249
+ skipNotes.push(`${envelope.filesTooLargeSkipped} oversized file(s) skipped`);
1250
+ }
1251
+ if (skipNotes.length > 0) {
1252
+ lines.push(`Note: ${skipNotes.join(", ")}.`);
1253
+ }
1254
+ return lines;
1255
+ }
1256
+ function buildRenderBlocks2(matches) {
1257
+ if (matches.length === 0)
1258
+ return [];
1259
+ const linesByFile = new Map;
1260
+ for (const match of matches) {
1261
+ let lineMap = linesByFile.get(match.filePath);
1262
+ if (!lineMap) {
1263
+ lineMap = new Map;
1264
+ linesByFile.set(match.filePath, lineMap);
1265
+ }
1266
+ const before = match.contextBefore ?? [];
1267
+ const beforeStart = match.line - before.length;
1268
+ for (let i = 0;i < before.length; i += 1) {
1269
+ const lineNumber = beforeStart + i;
1270
+ if (!lineMap.has(lineNumber)) {
1271
+ lineMap.set(lineNumber, {
1272
+ lineNumber,
1273
+ content: before[i] ?? "",
1274
+ isMatch: false
1275
+ });
1276
+ }
1277
+ }
1278
+ lineMap.set(match.line, {
1279
+ lineNumber: match.line,
1280
+ content: match.lineContent,
1281
+ isMatch: true
1282
+ });
1283
+ const after = match.contextAfter ?? [];
1284
+ for (let i = 0;i < after.length; i += 1) {
1285
+ const lineNumber = match.line + i + 1;
1286
+ if (!lineMap.has(lineNumber)) {
1287
+ lineMap.set(lineNumber, {
1288
+ lineNumber,
1289
+ content: after[i] ?? "",
1290
+ isMatch: false
1291
+ });
1292
+ }
1293
+ }
1294
+ }
1295
+ const blocks = [];
1296
+ for (const [filePath, lineMap] of linesByFile) {
1297
+ const sorted = [...lineMap.values()].sort((a, b) => a.lineNumber - b.lineNumber);
1298
+ let current = [];
1299
+ for (const line of sorted) {
1300
+ const previous = current[current.length - 1];
1301
+ if (!previous || line.lineNumber === previous.lineNumber + 1) {
1302
+ current.push(line);
1303
+ continue;
1304
+ }
1305
+ blocks.push({ filePath, lines: current });
1306
+ current = [line];
1307
+ }
1308
+ if (current.length > 0) {
1309
+ blocks.push({ filePath, lines: current });
1310
+ }
1311
+ }
1312
+ return blocks;
1313
+ }
1314
+ function groupBlocksByFile2(blocks) {
1315
+ const map = new Map;
1316
+ for (const block of blocks) {
1317
+ const list = map.get(block.filePath) ?? [];
1318
+ list.push(block);
1319
+ map.set(block.filePath, list);
1320
+ }
1321
+ return map;
1322
+ }
1323
+ function blocksHaveContext(blocks) {
1324
+ for (const block of blocks) {
1325
+ for (const line of block.lines) {
1326
+ if (!line.isMatch)
1327
+ return true;
1328
+ }
1329
+ }
1330
+ return false;
1331
+ }
1332
+ function widestLineNumberInBlock(block) {
1333
+ let max = 0;
1334
+ for (const line of block.lines) {
1335
+ const len = String(line.lineNumber).length;
1336
+ if (len > max)
1337
+ max = len;
1338
+ }
1339
+ return max;
1340
+ }
1341
+ function countMatchesByFile(matches) {
1342
+ const counts = new Map;
1343
+ for (const match of matches) {
1344
+ counts.set(match.filePath, (counts.get(match.filePath) ?? 0) + 1);
1345
+ }
1346
+ return counts;
1347
+ }
1348
+ function renderLine(line, gutterWidth, useContext) {
1349
+ const gutter = String(line.lineNumber).padStart(gutterWidth, " ");
1350
+ const sep = !useContext || line.isMatch ? ":" : "-";
1351
+ return ` ${gutter}${sep} ${line.content}`;
1352
+ }
1353
+ function quote(value) {
1354
+ return value.includes('"') ? `'${value}'` : `"${value}"`;
1355
+ }
1149
1356
  // src/shared/language-filter.ts
1150
1357
  var DEFAULT_LIMIT = 5;
1151
1358
  function filterLanguages(languages, query, limit = DEFAULT_LIMIT) {
1152
1359
  const lowerQuery = query.toLowerCase();
1153
1360
  return languages.filter((lang) => lang.name.toLowerCase().includes(lowerQuery) || lang.display_name.toLowerCase().includes(lowerQuery) || lang.aliases.some((a) => a.toLowerCase().includes(lowerQuery))).slice(0, limit).map(({ name, display_name }) => ({ name, display_name }));
1154
1361
  }
1362
+ // src/shared/list-files-text.ts
1363
+ var SEP2 = " | ";
1364
+ function renderListFilesText(envelope) {
1365
+ const lines = [];
1366
+ lines.push(buildHeader2(envelope));
1367
+ lines.push("");
1368
+ if (envelope.files.length === 0) {
1369
+ lines.push(envelope.hint ?? "No files match the requested filter.");
1370
+ return lines.join(`
1371
+ `);
1372
+ }
1373
+ for (const entry of envelope.files) {
1374
+ lines.push(entry.path);
1375
+ }
1376
+ if (envelope.hasMore) {
1377
+ lines.push("");
1378
+ lines.push("More files available. Pass limit=N to widen or refine path_prefix.");
1379
+ }
1380
+ if (envelope.hint) {
1381
+ lines.push("");
1382
+ lines.push(envelope.hint);
1383
+ }
1384
+ return lines.join(`
1385
+ `);
1386
+ }
1387
+ function buildHeader2(envelope) {
1388
+ const identity = buildIdentity(envelope);
1389
+ const countValue = envelope.hasMore ? `${envelope.files.length}+` : String(envelope.total);
1390
+ const parts = [
1391
+ `code_files${SEP2}${countValue} path${countValue === "1" ? "" : "s"}`
1392
+ ];
1393
+ if (identity)
1394
+ parts.push(identity);
1395
+ const filter = buildFilterEcho2(envelope);
1396
+ if (filter)
1397
+ parts.push(filter);
1398
+ return parts.join(SEP2);
1399
+ }
1400
+ function buildIdentity(envelope) {
1401
+ if (envelope.registry && envelope.name) {
1402
+ const version2 = envelope.indexedVersion ?? envelope.resolution?.resolvedRef;
1403
+ return version2 ? `${envelope.registry}:${envelope.name}@${version2}` : `${envelope.registry}:${envelope.name}`;
1404
+ }
1405
+ if (envelope.repoUrl) {
1406
+ return envelope.gitRef ? `${envelope.repoUrl}@${envelope.gitRef}` : envelope.repoUrl;
1407
+ }
1408
+ return "";
1409
+ }
1410
+ function buildFilterEcho2(envelope) {
1411
+ const parts = [];
1412
+ if (envelope.filter?.pathPrefix) {
1413
+ parts.push(`path_prefix=${quote2(envelope.filter.pathPrefix)}`);
1414
+ }
1415
+ if (envelope.filter?.limit !== undefined) {
1416
+ parts.push(`limit=${envelope.filter.limit}`);
1417
+ }
1418
+ return parts.join(" ");
1419
+ }
1420
+ function quote2(value) {
1421
+ return value.includes('"') ? `'${value}'` : `"${value}"`;
1422
+ }
1155
1423
  // src/shared/list-package-docs-request.ts
1156
1424
  function buildListPackageDocsParams(input) {
1157
1425
  const packageName = input.packageName?.trim() ?? "";
@@ -3114,7 +3382,7 @@ function formatReadPackageDocTerminal(envelope, options) {
3114
3382
  return envelope.content ?? "";
3115
3383
  }
3116
3384
  const lines = [];
3117
- lines.push(buildHeader(envelope, options.useColors));
3385
+ lines.push(buildHeader3(envelope, options.useColors));
3118
3386
  lines.push(`pageId: ${envelope.pageId}`);
3119
3387
  if (envelope.sourceUrl)
3120
3388
  lines.push(`source: ${envelope.sourceUrl}`);
@@ -3134,7 +3402,7 @@ function formatReadPackageDocTerminal(envelope, options) {
3134
3402
  `)}
3135
3403
  `;
3136
3404
  }
3137
- function buildHeader(envelope, useColors) {
3405
+ function buildHeader3(envelope, useColors) {
3138
3406
  const badge = envelope.sourceKind === "repo" ? "[repo]" : "[crawled]";
3139
3407
  const title = envelope.title ?? envelope.pageId;
3140
3408
  const prefix = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "documentation";
@@ -3581,6 +3849,188 @@ function parseRepoTarget(spec) {
3581
3849
  }
3582
3850
  return { repoUrl, gitRef };
3583
3851
  }
3852
+ // src/shared/unified-search-text.ts
3853
+ var SUMMARY_WRAP_WIDTH = 76;
3854
+ var SEP3 = " | ";
3855
+ function renderUnifiedSearchSuccess(payload) {
3856
+ const lines = [];
3857
+ lines.push(buildHeader4(payload));
3858
+ lines.push("");
3859
+ if (payload.results.length === 0) {
3860
+ lines.push(payload.completed ? "No hits." : "No hits yet - indexing.");
3861
+ } else {
3862
+ payload.results.forEach((hit, idx) => {
3863
+ if (idx > 0)
3864
+ lines.push("");
3865
+ appendHit(lines, idx + 1, hit);
3866
+ });
3867
+ }
3868
+ const trailer = buildTrailer2(payload);
3869
+ if (trailer.length > 0) {
3870
+ lines.push("");
3871
+ for (const line of trailer)
3872
+ lines.push(line);
3873
+ }
3874
+ return lines.join(`
3875
+ `);
3876
+ }
3877
+ function renderUnifiedSearchError(payload) {
3878
+ const lines = [];
3879
+ const header = `search${SEP3}ERROR${SEP3}code=${payload.code}${payload.retryable ? `${SEP3}retryable` : ""}`;
3880
+ lines.push(header);
3881
+ lines.push(payload.error);
3882
+ if (payload.details && Object.keys(payload.details).length > 0) {
3883
+ lines.push("");
3884
+ lines.push("details:");
3885
+ for (const [key, value] of Object.entries(payload.details)) {
3886
+ lines.push(` ${key}: ${formatDetailValue(value)}`);
3887
+ }
3888
+ }
3889
+ return lines.join(`
3890
+ `);
3891
+ }
3892
+ function buildHeader4(payload) {
3893
+ const count = payload.results.length;
3894
+ const status = payload.completed ? `${count} hit${count === 1 ? "" : "s"}` : `${count} partial`;
3895
+ const parts = [`search${SEP3}${status}`];
3896
+ parts.push(`query=${quote3(payload.query.raw)}`);
3897
+ if (!payload.completed) {
3898
+ parts.push(`searchRef=${payload.searchRef}`);
3899
+ }
3900
+ return parts.join(SEP3);
3901
+ }
3902
+ function appendHit(lines, index, hit) {
3903
+ const headerParts = [hit.target, shortType(hit.type)];
3904
+ if (typeof hit.score === "number") {
3905
+ headerParts.push(formatScore(hit.score));
3906
+ }
3907
+ lines.push(`[${index}] ${headerParts.join(" ")}`);
3908
+ const locator = buildLocatorLine(hit);
3909
+ if (locator)
3910
+ lines.push(` ${locator}`);
3911
+ if (hit.title && hit.title !== hit.locator.filePath) {
3912
+ lines.push(` ${hit.title}`);
3913
+ }
3914
+ if (hit.summary) {
3915
+ for (const wrapped of wrapText2(hit.summary, SUMMARY_WRAP_WIDTH)) {
3916
+ lines.push(` ${wrapped}`);
3917
+ }
3918
+ }
3919
+ }
3920
+ function shortType(type) {
3921
+ switch (type) {
3922
+ case "repository_code":
3923
+ return "code";
3924
+ case "repository_symbol":
3925
+ return "symbol";
3926
+ case "documentation_page":
3927
+ return "docs";
3928
+ case "repository_doc":
3929
+ return "repo-docs";
3930
+ default:
3931
+ return type;
3932
+ }
3933
+ }
3934
+ function buildLocatorLine(hit) {
3935
+ const loc = hit.locator;
3936
+ if (loc.filePath) {
3937
+ let line = `${loc.filePath}${formatLineRange(loc.startLine, loc.endLine)}`;
3938
+ const tail = [];
3939
+ if (loc.qualifiedPath)
3940
+ tail.push(loc.qualifiedPath);
3941
+ if (loc.kind)
3942
+ tail.push(loc.kind);
3943
+ if (tail.length > 0)
3944
+ line += ` ${tail.join(SEP3)}`;
3945
+ return line;
3946
+ }
3947
+ if (loc.pageId)
3948
+ return `pageId: ${loc.pageId}`;
3949
+ if (loc.sourceUrl)
3950
+ return loc.sourceUrl;
3951
+ return "";
3952
+ }
3953
+ function formatLineRange(start, end) {
3954
+ if (typeof start !== "number")
3955
+ return "";
3956
+ if (typeof end !== "number" || end === start)
3957
+ return `:${start}`;
3958
+ return `:${start}-${end}`;
3959
+ }
3960
+ function formatScore(score) {
3961
+ return score.toFixed(2);
3962
+ }
3963
+ function buildTrailer2(payload) {
3964
+ const lines = [];
3965
+ if (payload.hasMore) {
3966
+ const nextOffsetHint = typeof payload.nextOffset === "number" ? ` Pass offset=${payload.nextOffset} for the next page or limit=N to widen.` : " Pass limit=N to widen.";
3967
+ lines.push(`More hits available.${nextOffsetHint}`);
3968
+ }
3969
+ if (!payload.completed && payload.searchRef) {
3970
+ lines.push(`Indexing in progress. Call search_status with searchRef=${payload.searchRef} to follow up.`);
3971
+ }
3972
+ if (payload.sourceStatus && payload.sourceStatus.length > 0) {
3973
+ lines.push("source notes:");
3974
+ for (const entry of payload.sourceStatus) {
3975
+ lines.push(` - ${formatSourceStatus(entry)}`);
3976
+ }
3977
+ }
3978
+ return lines;
3979
+ }
3980
+ function formatSourceStatus(entry) {
3981
+ const parts = [`${entry.source} (${entry.targetLabel})`];
3982
+ if (entry.indexingStatus)
3983
+ parts.push(`indexing=${entry.indexingStatus}`);
3984
+ if (entry.codeIndexState)
3985
+ parts.push(`codeIndex=${entry.codeIndexState}`);
3986
+ if (entry.ignoredFilters?.length) {
3987
+ parts.push(`ignored=${entry.ignoredFilters.join(",")}`);
3988
+ }
3989
+ if (entry.incompatibleFilters?.length) {
3990
+ parts.push(`incompatible=${entry.incompatibleFilters.join(",")}`);
3991
+ }
3992
+ if (entry.ignoredQueryFeatures?.length) {
3993
+ parts.push(`ignoredQuery=${entry.ignoredQueryFeatures.join(",")}`);
3994
+ }
3995
+ if (entry.incompatibleQueryFeatures?.length) {
3996
+ parts.push(`incompatibleQuery=${entry.incompatibleQueryFeatures.join(",")}`);
3997
+ }
3998
+ if (entry.note)
3999
+ parts.push(entry.note);
4000
+ return parts.join(SEP3);
4001
+ }
4002
+ function quote3(value) {
4003
+ return value.includes('"') ? `'${value}'` : `"${value}"`;
4004
+ }
4005
+ function formatDetailValue(value) {
4006
+ if (value === null || value === undefined)
4007
+ return "";
4008
+ if (typeof value === "string")
4009
+ return value;
4010
+ if (typeof value === "number" || typeof value === "boolean")
4011
+ return String(value);
4012
+ return JSON.stringify(value);
4013
+ }
4014
+ function wrapText2(text, width) {
4015
+ const lines = [];
4016
+ for (const paragraph of text.split(/\n/)) {
4017
+ if (paragraph.length === 0) {
4018
+ lines.push("");
4019
+ continue;
4020
+ }
4021
+ let remaining = paragraph.trim();
4022
+ while (remaining.length > width) {
4023
+ let breakAt = remaining.lastIndexOf(" ", width);
4024
+ if (breakAt <= 0)
4025
+ breakAt = width;
4026
+ lines.push(remaining.slice(0, breakAt).trimEnd());
4027
+ remaining = remaining.slice(breakAt).trimStart();
4028
+ }
4029
+ if (remaining.length > 0)
4030
+ lines.push(remaining);
4031
+ }
4032
+ return lines;
4033
+ }
3584
4034
  // src/shared/list-files-request.ts
3585
4035
  var LIMIT_MIN2 = 1;
3586
4036
  var LIMIT_MAX2 = 1000;
@@ -4140,7 +4590,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
4140
4590
  match in --verbose output; full payload in --json).`;
4141
4591
  function registerCodeGrepCommand(pkgCommand) {
4142
4592
  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)", collectRepeatable, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable, []).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}`, collectRepeatable, []).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) => {
4143
- const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
4593
+ const { createContainer: createContainer2 } = await import("./shared/chunk-9zze00cp.js");
4144
4594
  const deps = await createContainer2();
4145
4595
  await pkgGrepAction(arg1, arg2, arg3, options, {
4146
4596
  codeNavigationService: deps.codeNavigationService,
@@ -4172,9 +4622,7 @@ function buildReadFileParams(input) {
4172
4622
  startLine,
4173
4623
  endLine,
4174
4624
  waitTimeoutMs
4175
- },
4176
- startLineExplicit: input.startLine !== undefined,
4177
- endLineExplicit: input.endLine !== undefined
4625
+ }
4178
4626
  };
4179
4627
  }
4180
4628
  function normaliseLine(raw, name) {
@@ -4238,7 +4686,7 @@ function formatReadFileTerminal(envelope, options) {
4238
4686
  function formatBinary(envelope, options, verbose) {
4239
4687
  const sentinel = dim("Binary file — cannot display as text.", options.useColors);
4240
4688
  if (verbose) {
4241
- return `${buildHeader2(envelope, options)}
4689
+ return `${buildHeader5(envelope, options)}
4242
4690
 
4243
4691
  ${sentinel}
4244
4692
  `;
@@ -4249,7 +4697,7 @@ ${sentinel}
4249
4697
  function formatNoContent(envelope, options, verbose) {
4250
4698
  const sentinel = dim("(no content returned)", options.useColors);
4251
4699
  if (verbose) {
4252
- return `${buildHeader2(envelope, options)}
4700
+ return `${buildHeader5(envelope, options)}
4253
4701
 
4254
4702
  ${sentinel}
4255
4703
  `;
@@ -4259,7 +4707,7 @@ ${sentinel}
4259
4707
  }
4260
4708
  function formatVerboseBody(envelope, options) {
4261
4709
  const lines = [];
4262
- lines.push(buildHeader2(envelope, options));
4710
+ lines.push(buildHeader5(envelope, options));
4263
4711
  lines.push("");
4264
4712
  const content = envelope.content ?? "";
4265
4713
  const bodyLines = content.split(`
@@ -4275,11 +4723,15 @@ function formatVerboseBody(envelope, options) {
4275
4723
  const gutter = dim(String(lineNumber).padStart(gutterWidth, " "), options.useColors);
4276
4724
  lines.push(`${gutter} ${bodyLines[i]}`);
4277
4725
  }
4726
+ if (envelope.hint) {
4727
+ lines.push("");
4728
+ lines.push(dim(envelope.hint, options.useColors));
4729
+ }
4278
4730
  lines.push("");
4279
4731
  return lines.join(`
4280
4732
  `);
4281
4733
  }
4282
- function buildHeader2(envelope, options) {
4734
+ function buildHeader5(envelope, options) {
4283
4735
  const parts = [envelope.path];
4284
4736
  if (envelope.language)
4285
4737
  parts.push(envelope.language);
@@ -4657,7 +5109,7 @@ function registerExampleCommand(program) {
4657
5109
  });
4658
5110
  }
4659
5111
  async function loadContainer() {
4660
- const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
5112
+ const { createContainer: createContainer2 } = await import("./shared/chunk-9zze00cp.js");
4661
5113
  return createContainer2();
4662
5114
  }
4663
5115
  // src/commands/feedback.ts
@@ -6066,9 +6518,10 @@ var schema3 = {
6066
6518
  max_matches_per_file: z4.number().optional(),
6067
6519
  cursor: z4.string().optional(),
6068
6520
  symbol_fields: z4.array(z4.enum(GREP_REPO_SYMBOL_FIELDS)).optional().describe(GREP_REPO_SYMBOL_FIELDS_NOTE),
6069
- wait_timeout_ms: z4.number().optional()
6521
+ wait_timeout_ms: z4.number().optional(),
6522
+ 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.')
6070
6523
  };
6071
- 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`. " + "Matches chain directly into `code_read` via `matches[].filePath`.";
6524
+ 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`).";
6072
6525
  function createGrepRepoTool(service) {
6073
6526
  return {
6074
6527
  name: "code_grep",
@@ -6124,6 +6577,9 @@ function createGrepRepoTool(service) {
6124
6577
  excludeTestFiles: build.params.excludeTestFiles,
6125
6578
  explicit: build.explicit
6126
6579
  });
6580
+ if (isTextFormat(args.format)) {
6581
+ return textResult(renderGrepRepoText(payload));
6582
+ }
6127
6583
  return textResult(JSON.stringify(payload));
6128
6584
  } catch (error2) {
6129
6585
  const mapped = mapCodeNavigationError(error2);
@@ -6137,15 +6593,19 @@ function createGrepRepoTool(service) {
6137
6593
  }
6138
6594
  };
6139
6595
  }
6596
+ function isTextFormat(format) {
6597
+ return format === undefined || format === "text" || format === "text-v1";
6598
+ }
6140
6599
  // src/tools/list-files.ts
6141
6600
  import { z as z5 } from "zod";
6142
6601
  var schema4 = {
6143
6602
  target: codeTargetSchema,
6144
6603
  path_prefix: z5.string().optional().describe("Literal directory prefix to filter by (e.g. `src/` or `lib/parser`). NOT a glob — `*.ts` and similar patterns won't match. Omit to list from the repository root."),
6145
6604
  limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
6146
- 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`.")
6605
+ 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`."),
6606
+ 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.')
6147
6607
  };
6148
- var DESCRIPTION4 = "List files in an indexed dependency. Response: " + "`{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. `path_prefix` " + "is a literal directory prefix — it does NOT accept globs " + "(`*.ts`) or extension filters. The returned `path` values 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`.";
6608
+ 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. `path_prefix` is a literal directory prefix — " + "it does NOT accept globs (`*.ts`) or extension 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`.";
6149
6609
  function createListFilesTool(service) {
6150
6610
  return {
6151
6611
  name: "code_files",
@@ -6174,6 +6634,9 @@ function createListFilesTool(service) {
6174
6634
  pathPrefix: build.params.pathPrefix,
6175
6635
  limit: build.params.limit
6176
6636
  });
6637
+ if (isTextFormat2(args.format)) {
6638
+ return textResult(renderListFilesText(payload));
6639
+ }
6177
6640
  return textResult(JSON.stringify(payload));
6178
6641
  } catch (error2) {
6179
6642
  const mapped = mapCodeNavigationError(error2);
@@ -6187,6 +6650,9 @@ function createListFilesTool(service) {
6187
6650
  }
6188
6651
  };
6189
6652
  }
6653
+ function isTextFormat2(format) {
6654
+ return format === undefined || format === "text" || format === "text-v1";
6655
+ }
6190
6656
  // src/tools/list-package-docs.ts
6191
6657
  import { z as z6 } from "zod";
6192
6658
  var schema5 = {
@@ -6718,14 +7184,34 @@ function createPackageVulnerabilitiesTool(service) {
6718
7184
  }
6719
7185
  // src/tools/read-file.ts
6720
7186
  import { z as z11 } from "zod";
7187
+ var MCP_READ_MAX_SPAN = 150;
6721
7188
  var schema10 = {
6722
7189
  target: codeTargetSchema,
6723
7190
  path: z11.string().describe("Path to the file. Package addressing: package-relative. Repo addressing: repo-relative. This is the same `path` key that `code_files` emits for each entry, so chaining needs no renaming."),
6724
- start_line: z11.number().optional().describe("Starting line (1-indexed). Omit for the full file from line 1."),
6725
- end_line: z11.number().optional().describe("Ending line (inclusive). Omit for end of file. Must be ≥ `start_line` when both are set."),
7191
+ start_line: z11.number().optional().describe(`Starting line (1-indexed). Omit to start at line 1. The MCP surface caps any single read at ${MCP_READ_MAX_SPAN} lines — pick a focused window from your prior \`search\` / \`code_grep\` hit.`),
7192
+ end_line: z11.number().optional().describe(`Ending line (inclusive). Must be ≥ \`start_line\` when both are set. Omitting it implies \`start_line + ${MCP_READ_MAX_SPAN - 1}\` because the MCP surface caps each read at ${MCP_READ_MAX_SPAN} lines.`),
6726
7193
  wait_timeout_ms: z11.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`.")
6727
7194
  };
6728
- var DESCRIPTION10 = "Read a file from an indexed dependency. Default returns the full " + "file; use `start_line` / `end_line` for a bounded range. Response: " + "`{path, language, totalLines, startLine, endLine, content, " + "isBinary}`. Binary files set `isBinary: true` and omit `content` — " + "agents branch on the flag rather than checking null. Pass the same " + "`path` emitted by `code_files`. Address via " + "`target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + `target.git_ref` (repo scope), mutually " + "exclusive. On `INDEXING` retry with a longer `wait_timeout_ms`. " + "When the path doesn't resolve the response is a `NOT_FOUND` (or " + "`FILE_NOT_FOUND`) error call `code_files` to discover the " + "actual paths.";
7195
+ var DESCRIPTION10 = "Read a file from an indexed dependency. **MCP cap: " + `${MCP_READ_MAX_SPAN} lines per call** — broader requests (or no ` + `range) silently truncate to the first ${MCP_READ_MAX_SPAN} lines ` + "from your start, with a `hint` describing what was returned vs. " + "requested. Pick a focused window from a `search` / `code_grep` " + "match. Response: `{path, language, totalLines, startLine, endLine, " + "content, isBinary, hint?}`. Binary files set `isBinary: true` and " + "omit `content`. Pass the same `path` emitted by `code_files`. " + "Address via `target.registry` + `target.package_name` (package " + "scope) or `target.repo_url` + `target.git_ref` (repo scope), " + "mutually exclusive. On `INDEXING` retry with a longer " + "`wait_timeout_ms`. On `NOT_FOUND` / `FILE_NOT_FOUND` call " + "`code_files` to discover the actual path.";
7196
+ function deriveBoundedRange(startLine, endLine) {
7197
+ const start = startLine ?? 1;
7198
+ if (endLine === undefined) {
7199
+ return {
7200
+ startLine: start,
7201
+ endLine: start + MCP_READ_MAX_SPAN - 1,
7202
+ capped: true
7203
+ };
7204
+ }
7205
+ const span = endLine - start + 1;
7206
+ if (span > MCP_READ_MAX_SPAN) {
7207
+ return {
7208
+ startLine: start,
7209
+ endLine: start + MCP_READ_MAX_SPAN - 1,
7210
+ capped: true
7211
+ };
7212
+ }
7213
+ return { startLine: start, endLine, capped: false };
7214
+ }
6729
7215
  function createReadFileTool(service) {
6730
7216
  return {
6731
7217
  name: "code_read",
@@ -6737,11 +7223,12 @@ function createReadFileTool(service) {
6737
7223
  if ("content" in target)
6738
7224
  return target;
6739
7225
  try {
7226
+ const bounded = deriveBoundedRange(args.start_line, args.end_line);
6740
7227
  const build = buildReadFileParams({
6741
7228
  target,
6742
7229
  filePath: args.path,
6743
- startLine: args.start_line,
6744
- endLine: args.end_line,
7230
+ startLine: bounded.startLine,
7231
+ endLine: bounded.endLine,
6745
7232
  waitTimeoutMs: args.wait_timeout_ms
6746
7233
  });
6747
7234
  const result = await service.readFile(build.params);
@@ -6752,6 +7239,9 @@ function createReadFileTool(service) {
6752
7239
  gitRef: target.gitRef,
6753
7240
  requestedFilePath: build.params.filePath
6754
7241
  });
7242
+ if (shouldEmitCappedHint(bounded, payload)) {
7243
+ payload.hint = buildCappedHint(payload, args.start_line, args.end_line);
7244
+ }
6755
7245
  return textResult(JSON.stringify(payload));
6756
7246
  } catch (error2) {
6757
7247
  const mapped = mapCodeNavigationError(error2);
@@ -6765,6 +7255,33 @@ function createReadFileTool(service) {
6765
7255
  }
6766
7256
  };
6767
7257
  }
7258
+ function shouldEmitCappedHint(bounded, payload) {
7259
+ if (!bounded.capped)
7260
+ return false;
7261
+ if (payload.isBinary)
7262
+ return false;
7263
+ if (payload.endLine === undefined)
7264
+ return false;
7265
+ if (payload.totalLines === undefined)
7266
+ return false;
7267
+ return payload.endLine < payload.totalLines;
7268
+ }
7269
+ function buildCappedHint(payload, originalStart, originalEnd) {
7270
+ const requested = describeRequest(originalStart, originalEnd);
7271
+ 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.`;
7272
+ }
7273
+ function describeRequest(originalStart, originalEnd) {
7274
+ if (originalStart === undefined && originalEnd === undefined) {
7275
+ return "no range";
7276
+ }
7277
+ if (originalEnd === undefined) {
7278
+ return `start_line=${originalStart}, no end_line`;
7279
+ }
7280
+ if (originalStart === undefined) {
7281
+ return `end_line=${originalEnd}, no start_line`;
7282
+ }
7283
+ return `lines ${originalStart}-${originalEnd}`;
7284
+ }
6768
7285
  // src/tools/read-package-doc.ts
6769
7286
  import { z as z12 } from "zod";
6770
7287
  var schema11 = {
@@ -6853,7 +7370,8 @@ var schema12 = {
6853
7370
  allow_partial_results: z13.boolean().optional().describe("Default false waits for all sources; if the wait window expires, returns only searchRef/progress. When true, includes hits from sources that finished so far and still returns searchRef for continuation. Partial payloads support normal pagination via nextOffset."),
6854
7371
  limit: z13.coerce.number().int().min(1).max(100).optional(),
6855
7372
  offset: z13.coerce.number().int().min(0).optional(),
6856
- wait_timeout_ms: z13.coerce.number().int().min(0).max(60000).optional()
7373
+ wait_timeout_ms: z13.coerce.number().int().min(0).max(60000).optional(),
7374
+ 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.')
6857
7375
  };
6858
7376
  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).";
6859
7377
  function createSearchTool(service) {
@@ -6891,9 +7409,16 @@ function createSearchTool(service) {
6891
7409
  });
6892
7410
  const outcome = await service.search(built.params);
6893
7411
  const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
7412
+ if (isTextFormat3(args.format)) {
7413
+ return textResult(renderUnifiedSearchSuccess(payload));
7414
+ }
6894
7415
  return textResult(JSON.stringify(payload));
6895
7416
  } catch (error2) {
6896
- return errorResult(JSON.stringify(buildUnifiedSearchErrorPayload(error2)));
7417
+ const payload = buildUnifiedSearchErrorPayload(error2);
7418
+ if (isTextFormat3(args.format)) {
7419
+ return errorResult(renderUnifiedSearchError(payload));
7420
+ }
7421
+ return errorResult(JSON.stringify(payload));
6897
7422
  }
6898
7423
  }
6899
7424
  };
@@ -6901,6 +7426,9 @@ function createSearchTool(service) {
6901
7426
  function isResolvedCodeTarget(target) {
6902
7427
  return !("content" in target);
6903
7428
  }
7429
+ function isTextFormat3(format) {
7430
+ return format === undefined || format === "text" || format === "text-v1";
7431
+ }
6904
7432
  // src/tools/search-language.ts
6905
7433
  import { z as z14 } from "zod";
6906
7434
  var schema13 = {
@@ -6945,7 +7473,7 @@ function createSearchStatusTool(service) {
6945
7473
  };
6946
7474
  }
6947
7475
  // src/commands/mcp-instructions.ts
6948
- var CORE_BLOCK = `GitHits surfaces verified, canonical code examples from global open source. Use it when you're stuck, the user is frustrated by repeated failed attempts, you need up-to-date API usage, or the user mentions GitHits.
7476
+ var CORE_BLOCK = `GitHits surfaces verified, canonical code examples from global open source. Use it when you're stuck, the user is frustrated by repeated failed attempts, 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.
6949
7477
 
6950
7478
  Workflow: call \`get_example\` with one focused question, optionally passing \`language\` when the desired language is known; call \`search_language\` first only if you need to force a language and the exact name is uncertain. Send \`feedback\` on the returned solution_id so quality improves. Each search addresses a single issue; reuse context from prior results before re-searching.`;
6951
7479
  var PACKAGE_TOOLS_PREAMBLE = `Package tools work with third-party dependency source plus registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library actually works, or you're evaluating whether to add or upgrade a package.
@@ -6957,12 +7485,14 @@ var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. W
6957
7485
  var PKG_VULNS_BULLET = "- `pkg_vulns` — known CVE / OSV advisories for npm, PyPI, Hex, or Crates packages (optionally pinned to `@version`). Malicious-package advisories surface in a disjoint `malware` bucket; filter with `min_severity` or include retracted advisories with `include_withdrawn`.";
6958
7486
  var PKG_DEPS_BULLET = "- `pkg_deps` — direct runtime deps plus, when the backend has them, dev / peer / optional / feature groups. Pass `lifecycle` to filter groups server-side, `include_transitive` for the full graph, and `include_importers` when you also need per-package provenance. Supports npm, PyPI, Hex, Crates, vcpkg, and Zig.";
6959
7487
  var PKG_CHANGELOG_BULLET = "- `pkg_changelog` — release notes for a package or GitHub repo, newest-first. Default latest mode returns the 10 most recent entries with full markdown bodies; `from_version` switches to range mode between two versions. Addressable via `registry` + `package_name` or `repo_url`. Set `include_bodies: false` for a version / date / URL timeline when bodies aren't needed.";
6960
- var SEARCH_BULLET = "- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.";
7488
+ var SEARCH_BULLET = '- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Default response is a compact text listing (`text-v1`); pass `format: "json"` for the structured envelope with full locator fields, highlights, and source status. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.';
6961
7489
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior unified search by `searchRef`. Use it after `search` returns incomplete state to check progress, fetch partial hits when the original request used `allow_partial_results: true`, or fetch final results.";
6962
- var CODE_FILES_BULLET = "- `code_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory; the response includes each file's language, type, and byte size. Returned `path` values feed directly into `code_read` and help scope `code_grep`.";
6963
- var CODE_READ_BULLET = "- `code_read` — fetch a file's contents from a dependency. Pass the same `path` emitted by `code_files`. Default returns the full file; pass `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — branch on the flag. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `code_files` for the actual path.";
6964
- var CODE_GREP_BULLET = "- `code_grep` — deterministic text grep over indexed source files. Use it when you know the exact text or regex to match; use `search` for discovery. Whole-target grep is the default; narrow with `path`, `path_prefix`, `globs`, or `extensions`. Returned `matches[].filePath` feeds directly into `code_read`.";
6965
- var SEARCH_VS_SYMBOLS_TIP = 'Prefer `get_example` for canonical example retrieval; prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Use `code_grep` for deterministic text matching and `code_read` for full-file inspection.';
7490
+ var CODE_FILES_BULLET = '- `code_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory. Default response is a paths-only listing (`text-v1`); pass `format: "json"` for the full envelope with each file\'s language, type, and byte size. Returned paths feed directly into `code_read` and help scope `code_grep`.';
7491
+ var CODE_READ_BULLET = "- `code_read` — fetch a file's contents from a dependency. Pass the same `path` emitted by `code_files`. **MCP cap: 150 lines per call** — broader requests (or no range) silently truncate to the first 150 lines from your start, with a `hint` describing what was returned vs. requested. Pick a focused window from a `search` / `code_grep` match. Binary files set `isBinary: true` and omit `content`. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `code_files` for the actual path.";
7492
+ var CODE_GREP_BULLET = '- `code_grep` — deterministic text grep over indexed source files. Use it when you know the exact text or regex to match; use `search` for discovery. Whole-target grep is the default; narrow with `path`, `path_prefix`, `globs`, or `extensions`. Default response groups matches by file with line numbers (`text-v1`); pass `format: "json"` for the `matches[]` array with byte offsets and symbol metadata. Each match\'s `path:line` chains directly into `code_read`.';
7493
+ var SEARCH_VS_SYMBOLS_TIP = 'Prefer `get_example` for canonical example retrieval; prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Use `code_grep` for deterministic text matching and `code_read` for focused-window inspection of a known file.';
7494
+ var REFERENCE_FIRST_TIP = "Strategy — reference-first, content-second. Locate symbols and lines with `search` / `code_grep` first, then read only the lines you actually need with `code_read` using explicit `start_line` / `end_line` windows around the match (typically 80-150 lines). The MCP `code_read` surface caps each call at 150 lines; bigger requests are silently truncated. Each turn — including retries to widen or re-narrow — costs context, so pick a focused window the first time rather than starting wide and trimming.";
7495
+ var MULTI_TURN_TIP = '**Delegate multi-call work to a sub-agent.** Code-navigation tools (`search`, `code_grep`, `code_read`, `code_files`) are inherently multi-call — answering even simple questions usually takes 3-10 tool calls, and reading raw source into the main conversation compounds quickly. The default approach for any cross-project comparison, codebase mapping, pattern survey, or "how does X actually work" investigation is to spawn a sub-task / sub-agent that does the digging and returns only a compact synthesis. Do the work in the main conversation only when the result genuinely belongs there (e.g., the user asked for a specific snippet to paste into their own code). When in doubt, delegate.';
6966
7496
  function isPackageToolsCapabilityOpen(deps) {
6967
7497
  return deps.codeNavigationCliOverrideEnabled || deps.codeNavigationCapability === "enabled" || deps.codeNavigationCapability === "unknown" && deps.envApiToken !== undefined;
6968
7498
  }
@@ -6994,9 +7524,14 @@ function buildMcpInstructions(deps) {
6994
7524
 
6995
7525
  `);
6996
7526
  }
6997
- const parts = [PACKAGE_TOOLS_PREAMBLE, bullets.join(`
6998
- `)];
7527
+ const parts = [PACKAGE_TOOLS_PREAMBLE];
7528
+ if (deps.codeNavigationService) {
7529
+ parts.push(MULTI_TURN_TIP);
7530
+ }
7531
+ parts.push(bullets.join(`
7532
+ `));
6999
7533
  if (deps.codeNavigationService) {
7534
+ parts.push(REFERENCE_FIRST_TIP);
7000
7535
  parts.push(SEARCH_VS_SYMBOLS_TIP);
7001
7536
  }
7002
7537
  sections.push(parts.join(`
@@ -7651,11 +8186,11 @@ function requireSearchService(deps) {
7651
8186
  return deps.codeNavigationService;
7652
8187
  }
7653
8188
  async function loadContainer2() {
7654
- const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
8189
+ const { createContainer: createContainer2 } = await import("./shared/chunk-9zze00cp.js");
7655
8190
  return createContainer2();
7656
8191
  }
7657
8192
  async function loadStartupCodeNavigationRegistrationState() {
7658
- const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-4drb8s8v.js");
8193
+ const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-9zze00cp.js");
7659
8194
  return resolveStartupCodeNavigationRegistrationState2();
7660
8195
  }
7661
8196
  function parseTargetSpecs(specs) {
@@ -8068,7 +8603,7 @@ function isSearchHelpTarget(value) {
8068
8603
  return value === "search" || value === "search-status";
8069
8604
  }
8070
8605
  async function loadHelpRegistrationOptions(args) {
8071
- const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-4drb8s8v.js");
8606
+ const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-9zze00cp.js");
8072
8607
  const registrationState = await resolveStartupCodeNavigationRegistrationState2();
8073
8608
  return {
8074
8609
  capability: registrationState.capability,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "./shared/chunk-g6ay6x9v.js";
3
+ } from "./shared/chunk-js72s35a.js";
4
4
  export {
5
5
  version
6
6
  };
@@ -2,8 +2,8 @@ import {
2
2
  createContainer,
3
3
  resolveStartupCodeNavigationCapability,
4
4
  resolveStartupCodeNavigationRegistrationState
5
- } from "./chunk-ns1j9dan.js";
6
- import"./chunk-g6ay6x9v.js";
5
+ } from "./chunk-a9js75mw.js";
6
+ import"./chunk-js72s35a.js";
7
7
  export {
8
8
  resolveStartupCodeNavigationRegistrationState,
9
9
  resolveStartupCodeNavigationCapability,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "./chunk-g6ay6x9v.js";
3
+ } from "./chunk-js72s35a.js";
4
4
 
5
5
  // src/services/auth-service.ts
6
6
  import { createServer } from "node:http";
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
  // package.json
4
- var version = "0.2.2";
4
+ var version = "0.2.3";
5
5
 
6
6
  export { __require, version };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Code examples from global open source for developers and AI assistants.",
5
5
  "mcpServers": {
6
6
  "githits": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "githits",
3
3
  "description": "CLI companion for GitHits - code examples from global open source for developers and AI assistants",
4
- "version": "0.2.2",
4
+ "version": "0.2.3",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"