githits 0.3.3 → 0.4.0

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-2d202fpq.js";
52
+ } from "./shared/chunk-1cyka46a.js";
53
53
  import {
54
54
  __require,
55
55
  version
56
- } from "./shared/chunk-tze0rsay.js";
56
+ } from "./shared/chunk-fqzth5mv.js";
57
57
 
58
58
  // src/cli.ts
59
59
  import { Command } from "commander";
@@ -254,9 +254,9 @@ async function authStatusAction(deps) {
254
254
  `);
255
255
  console.log(` Environment: ${mcpUrl}
256
256
  `);
257
- console.log("");
258
- console.log("To authenticate:");
259
- console.log(" githits login");
257
+ console.log(` Storage: ${authStorage.getStorageLocation()}
258
+ `);
259
+ printAuthTroubleshooting();
260
260
  return;
261
261
  }
262
262
  if (auth.expiresAt && new Date(auth.expiresAt) < new Date) {
@@ -276,8 +276,9 @@ async function authStatusAction(deps) {
276
276
  console.log(` Environment: ${mcpUrl}`);
277
277
  console.log(` Expired: ${new Date(auth.expiresAt).toLocaleDateString()}
278
278
  `);
279
- console.log("");
280
- console.log("Run `githits login` to re-authenticate.");
279
+ console.log(` Storage: ${authStorage.getStorageLocation()}
280
+ `);
281
+ printAuthTroubleshooting("expired");
281
282
  return;
282
283
  }
283
284
  console.log(`Authenticated.
@@ -287,6 +288,17 @@ async function authStatusAction(deps) {
287
288
  console.log(`
288
289
  Storage: ${authStorage.getStorageLocation()}`);
289
290
  }
291
+ function printAuthTroubleshooting(reason = "missing") {
292
+ const loginCommand = reason === "expired" ? "githits login --force" : "githits login";
293
+ console.log("Recovery steps:");
294
+ console.log(` ${loginCommand}`);
295
+ if (reason === "missing") {
296
+ console.log(" githits login --force # if a previous login is stale");
297
+ }
298
+ console.log("For CI/automation, set GITHITS_API_TOKEN.");
299
+ console.log("If your system keychain is locked or unavailable, unlock it and retry.");
300
+ console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
301
+ }
290
302
  var STATUS_DESCRIPTION = `Show current authentication status.
291
303
 
292
304
  Displays details about the stored token including environment
@@ -353,6 +365,11 @@ function highlight(text, useColors) {
353
365
  return text;
354
366
  return `${colors.bold}${colors.cyan}${text}${colors.reset}`;
355
367
  }
368
+ function highlightMatch(text, useColors) {
369
+ if (!useColors)
370
+ return text;
371
+ return `${colors.bold}${colors.yellow}${text}${colors.reset}`;
372
+ }
356
373
  function highlightRanges(text, ranges, useColors) {
357
374
  if (!useColors || !text || !ranges || ranges.length === 0)
358
375
  return text;
@@ -380,7 +397,7 @@ function highlightRanges(text, ranges, useColors) {
380
397
  for (const [start, end] of merged) {
381
398
  if (cursor < start)
382
399
  result += text.slice(cursor, start);
383
- result += highlight(text.slice(start, end), useColors);
400
+ result += highlightMatch(text.slice(start, end), useColors);
384
401
  cursor = end;
385
402
  }
386
403
  if (cursor < text.length)
@@ -763,7 +780,7 @@ function parseCodeNavigationTargetSpec(spec) {
763
780
  function parseRepoTarget(spec) {
764
781
  const hashIndex = spec.lastIndexOf("#");
765
782
  if (hashIndex === -1) {
766
- return { repoUrl: spec, gitRef: "HEAD" };
783
+ throw new InvalidArgumentError("Repository target must include #gitRef for exact code navigation.");
767
784
  }
768
785
  const repoUrl = spec.slice(0, hashIndex);
769
786
  const gitRef = spec.slice(hashIndex + 1);
@@ -804,7 +821,8 @@ function buildSearchHitFollowUpCommand(hit) {
804
821
  gitRef: loc.gitRef,
805
822
  filePath: loc.filePath,
806
823
  startLine: loc.startLine,
807
- endLine: loc.endLine
824
+ endLine: loc.endLine,
825
+ preferPackageTarget: isPackageTarget(hit)
808
826
  });
809
827
  }
810
828
  if (hit.type === "repository_code" || hit.type === "repository_symbol") {
@@ -833,14 +851,24 @@ function buildCodeReadCommand(input) {
833
851
  return parts.join(" ");
834
852
  }
835
853
  function buildTargetSpec(input) {
854
+ if (input.preferPackageTarget && input.registry && input.packageName) {
855
+ return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
856
+ }
836
857
  if (input.repoUrl) {
837
- return `${input.repoUrl}#${input.gitRef ?? "HEAD"}`;
858
+ if (!input.gitRef)
859
+ return;
860
+ return `${input.repoUrl}#${input.gitRef}`;
838
861
  }
839
862
  if (input.registry && input.packageName) {
840
863
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
841
864
  }
842
865
  return;
843
866
  }
867
+ function isPackageTarget(hit) {
868
+ const registry = hit.locator.registry;
869
+ const packageName = hit.locator.packageName;
870
+ return Boolean(registry && packageName && hit.target.startsWith(`${registry}:${packageName}`));
871
+ }
844
872
  function appendRange(parts, startLine, endLine) {
845
873
  if (typeof startLine === "number")
846
874
  parts.push(`start_line=${startLine}`);
@@ -1934,7 +1962,7 @@ function renderListPackageDocsText(envelope) {
1934
1962
  if (page.sourceKind === "repo" && page.repoUrl && page.filePath) {
1935
1963
  lines.push(` ${buildCodeReadCommand({
1936
1964
  repoUrl: page.repoUrl,
1937
- gitRef: page.requestedRef ?? page.gitRef,
1965
+ gitRef: page.gitRef,
1938
1966
  filePath: page.filePath,
1939
1967
  startLine: 1,
1940
1968
  endLine: 150
@@ -4110,7 +4138,7 @@ function renderReadPackageDocText(envelope) {
4110
4138
  if (envelope.sourceUrl)
4111
4139
  lines.push(`source: ${envelope.sourceUrl}`);
4112
4140
  if (envelope.filePath) {
4113
- const ref = envelope.requestedRef ?? envelope.gitRef;
4141
+ const ref = envelope.gitRef;
4114
4142
  lines.push(`file: ${envelope.filePath}${ref ? ` @ ${ref}` : ""}`);
4115
4143
  }
4116
4144
  lines.push("");
@@ -4142,7 +4170,6 @@ function buildRange2(envelope) {
4142
4170
  }
4143
4171
  // src/shared/auto-login.ts
4144
4172
  var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
4145
- "init",
4146
4173
  "example",
4147
4174
  "languages",
4148
4175
  "feedback",
@@ -4175,9 +4202,6 @@ function isAutoLoginEligibleCommand(command, runtime = {
4175
4202
  stdoutIsTTY: Boolean(process.stdout.isTTY)
4176
4203
  }) {
4177
4204
  const commandPath = getCommandPath(command).join(" ");
4178
- if (commandPath === "init" && command.opts().skipLogin === true) {
4179
- return false;
4180
- }
4181
4205
  if (!AUTO_LOGIN_ELIGIBLE_COMMANDS.has(commandPath)) {
4182
4206
  return false;
4183
4207
  }
@@ -4237,8 +4261,6 @@ function createRootCliPreAction(deps) {
4237
4261
  }
4238
4262
  function getPostLoginContinuationMessage(command) {
4239
4263
  switch (getCommandPath(command).join(" ")) {
4240
- case "init":
4241
- return "Authentication complete. Continuing setup...";
4242
4264
  case "example":
4243
4265
  return "Authentication complete. Running example search...";
4244
4266
  case "languages":
@@ -4313,11 +4335,6 @@ function resolveTargets(target, targets) {
4313
4335
  seen.add(key);
4314
4336
  deduped.push(entry);
4315
4337
  }
4316
- const hasPackageTarget = deduped.some((entry) => entry.packageName);
4317
- const hasRepoTarget = deduped.some((entry) => entry.repoUrl);
4318
- if (hasPackageTarget && hasRepoTarget) {
4319
- throw new InvalidArgumentError("Do not mix package-scoped and repo-scoped targets in one search.");
4320
- }
4321
4338
  return deduped;
4322
4339
  }
4323
4340
  function normaliseRequiredQuery(query) {
@@ -4376,6 +4393,7 @@ var DEFAULT_LIMIT2 = DEFAULT_UNIFIED_SEARCH_LIMIT;
4376
4393
  var DEFAULT_OFFSET = 0;
4377
4394
  function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outcome) {
4378
4395
  const warnings = outcome.state === "completed" ? outcome.result.queryWarnings : outcome.result?.queryWarnings ?? outcome.progress?.queryWarnings ?? [];
4396
+ const progress = compactProgress(outcome.progress);
4379
4397
  const query = buildQueryEcho(params, rawQuery, compiledQuery, warnings);
4380
4398
  if (outcome.state === "incomplete") {
4381
4399
  const result = outcome.result;
@@ -4389,13 +4407,12 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
4389
4407
  if (result?.page.hasMore === true) {
4390
4408
  payload.nextOffset = result.page.offset + result.page.returned;
4391
4409
  }
4392
- const progress = compactProgress(outcome.progress);
4393
4410
  if (progress)
4394
4411
  payload.progress = progress;
4395
4412
  const sourceStatus2 = compactSourceStatus(result?.sourceStatus);
4396
4413
  if (sourceStatus2)
4397
4414
  payload.sourceStatus = sourceStatus2;
4398
- const combinedWarnings2 = combineWarnings(warnings, sourceStatus2);
4415
+ const combinedWarnings2 = combineWarnings(warnings, sourceStatus2, payload.results, progress);
4399
4416
  if (combinedWarnings2.length > 0)
4400
4417
  payload.warnings = combinedWarnings2;
4401
4418
  return payload;
@@ -4414,15 +4431,17 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
4414
4431
  const sourceStatus = compactSourceStatus(outcome.result.sourceStatus);
4415
4432
  if (sourceStatus)
4416
4433
  completed.sourceStatus = sourceStatus;
4417
- const combinedWarnings = combineWarnings(warnings, sourceStatus);
4434
+ const combinedWarnings = combineWarnings(warnings, sourceStatus, completed.results, progress);
4418
4435
  if (combinedWarnings.length > 0)
4419
4436
  completed.warnings = combinedWarnings;
4420
4437
  return completed;
4421
4438
  }
4422
- function combineWarnings(parserWarnings, sourceStatus) {
4439
+ function combineWarnings(parserWarnings, sourceStatus, hits = [], progress) {
4423
4440
  const out = [];
4424
4441
  if (parserWarnings.length > 0)
4425
4442
  out.push(...parserWarnings);
4443
+ out.push(...buildHitFreshnessWarnings(hits));
4444
+ out.push(...buildProgressFreshnessWarnings(progress));
4426
4445
  out.push(...buildSourceStatusWarnings(sourceStatus));
4427
4446
  return out;
4428
4447
  }
@@ -4449,6 +4468,9 @@ function buildUnifiedSearchStatusPayload(outcome) {
4449
4468
  const progress = compactProgress(outcome.progress);
4450
4469
  if (progress)
4451
4470
  payload2.progress = progress;
4471
+ const progressWarnings = buildProgressFreshnessWarnings(progress);
4472
+ if (progressWarnings.length > 0)
4473
+ payload2.warnings = progressWarnings;
4452
4474
  if (outcome.result) {
4453
4475
  payload2.result = buildUnifiedSearchStatusResultPayload(outcome.result);
4454
4476
  }
@@ -4464,6 +4486,7 @@ function buildUnifiedSearchStatusPayload(outcome) {
4464
4486
  }
4465
4487
  function buildUnifiedSearchStatusResultPayload(result) {
4466
4488
  const payload = {
4489
+ query: buildStatusQueryEcho(result),
4467
4490
  hasMore: result.page.hasMore,
4468
4491
  results: result.results.map(buildHitPayload)
4469
4492
  };
@@ -4482,6 +4505,18 @@ function buildUnifiedSearchStatusResultPayload(result) {
4482
4505
  }
4483
4506
  return payload;
4484
4507
  }
4508
+ function buildStatusQueryEcho(result) {
4509
+ const query = {
4510
+ raw: result.query
4511
+ };
4512
+ if (result.queryWarnings.length > 0) {
4513
+ query.warnings = result.queryWarnings;
4514
+ }
4515
+ if (result.sources.length > 0) {
4516
+ query.sources = result.sources.map((entry) => entry.toLowerCase());
4517
+ }
4518
+ return query;
4519
+ }
4485
4520
  function buildQueryEcho(params, rawQuery, compiledQuery, warnings) {
4486
4521
  const echo = {
4487
4522
  raw: rawQuery
@@ -4531,6 +4566,12 @@ function buildHitPayload(hit) {
4531
4566
  target: hit.targetLabel,
4532
4567
  locator: buildLocatorPayload(hit)
4533
4568
  };
4569
+ appendFreshness(payload, {
4570
+ requestedTargetLabel: hit.requestedTargetLabel,
4571
+ freshTargetLabel: hit.freshTargetLabel,
4572
+ servedTargetLabel: hit.servedTargetLabel,
4573
+ freshness: hit.freshness
4574
+ });
4534
4575
  if (hit.title)
4535
4576
  payload.title = hit.title;
4536
4577
  if (hit.summary)
@@ -4538,6 +4579,9 @@ function buildHitPayload(hit) {
4538
4579
  const highlights = buildHighlights(hit.highlights);
4539
4580
  if (highlights)
4540
4581
  payload.highlights = highlights;
4582
+ const followUp = buildSearchHitFollowUpCommand(payload);
4583
+ if (followUp)
4584
+ payload.followUp = followUp;
4541
4585
  return payload;
4542
4586
  }
4543
4587
  function buildLocatorPayload(hit) {
@@ -4599,10 +4643,79 @@ function compactProgress(progress) {
4599
4643
  targetsTotal: progress.targetsTotal,
4600
4644
  elapsedMs: progress.elapsedMs
4601
4645
  };
4646
+ if (progress.query)
4647
+ payload.query = progress.query;
4648
+ if (progress.requestedSources?.length) {
4649
+ payload.requestedSources = progress.requestedSources.map((entry) => entry.toLowerCase());
4650
+ }
4651
+ if (progress.targetMode)
4652
+ payload.targetMode = progress.targetMode;
4653
+ if (progress.requestedTargets?.length) {
4654
+ payload.requestedTargets = progress.requestedTargets;
4655
+ }
4656
+ if (progress.filters)
4657
+ payload.filters = buildFilterEcho3(progress.filters);
4658
+ if (typeof progress.limit === "number")
4659
+ payload.limit = progress.limit;
4660
+ if (typeof progress.offset === "number")
4661
+ payload.offset = progress.offset;
4662
+ const targets = progress.targets?.map(compactProgressTarget).filter(Boolean);
4663
+ if (targets?.length) {
4664
+ payload.targets = targets;
4665
+ }
4602
4666
  if (progress.expiresAt)
4603
4667
  payload.expiresAt = progress.expiresAt;
4668
+ payload.next = `search_status search_ref=${JSON.stringify(progress.searchRef)}`;
4604
4669
  return payload;
4605
4670
  }
4671
+ function appendFreshness(payload, source) {
4672
+ if (!isTrustRelevantFreshness(source.freshness) || !labelsDiverge({
4673
+ requestedTarget: source.requestedTargetLabel,
4674
+ freshTarget: source.freshTargetLabel,
4675
+ servedTarget: source.servedTargetLabel
4676
+ })) {
4677
+ return;
4678
+ }
4679
+ if (source.requestedTargetLabel)
4680
+ payload.requestedTarget = source.requestedTargetLabel;
4681
+ if (source.freshTargetLabel)
4682
+ payload.freshTarget = source.freshTargetLabel;
4683
+ if (source.servedTargetLabel)
4684
+ payload.servedTarget = source.servedTargetLabel;
4685
+ if (source.freshness)
4686
+ payload.freshness = source.freshness;
4687
+ }
4688
+ function compactProgressTarget(target) {
4689
+ const payload = {};
4690
+ if (target.requested)
4691
+ payload.requested = target.requested;
4692
+ if (target.resolvedRequested)
4693
+ payload.resolvedRequested = target.resolvedRequested;
4694
+ if (target.served)
4695
+ payload.served = target.served;
4696
+ if (target.freshness)
4697
+ payload.freshness = target.freshness;
4698
+ if (target.indexingRef)
4699
+ payload.indexingRef = target.indexingRef;
4700
+ if (target.requestedRefKind)
4701
+ payload.requestedRefKind = target.requestedRefKind;
4702
+ return Object.keys(payload).length > 0 ? payload : undefined;
4703
+ }
4704
+ function buildFilterEcho3(filters) {
4705
+ const echo = {};
4706
+ if (filters.kind)
4707
+ echo.kind = filters.kind.toLowerCase();
4708
+ if (filters.category)
4709
+ echo.category = filters.category.toLowerCase();
4710
+ if (filters.pathPrefix)
4711
+ echo.pathPrefix = filters.pathPrefix;
4712
+ if (filters.fileIntent)
4713
+ echo.fileIntent = filters.fileIntent.toLowerCase();
4714
+ if (typeof filters.publicOnly === "boolean") {
4715
+ echo.publicOnly = filters.publicOnly;
4716
+ }
4717
+ return Object.keys(echo).length > 0 ? echo : undefined;
4718
+ }
4606
4719
  function buildSourceStatusWarnings(sourceStatus) {
4607
4720
  if (!sourceStatus || sourceStatus.length === 0)
4608
4721
  return [];
@@ -4614,8 +4727,51 @@ function buildSourceStatusWarnings(sourceStatus) {
4614
4727
  }
4615
4728
  return warnings;
4616
4729
  }
4730
+ function buildHitFreshnessWarnings(hits) {
4731
+ return hits.map((hit) => freshnessWarning({
4732
+ freshness: hit.freshness,
4733
+ requestedTarget: hit.requestedTarget,
4734
+ freshTarget: hit.freshTarget,
4735
+ servedTarget: hit.servedTarget
4736
+ })).filter((entry) => Boolean(entry));
4737
+ }
4738
+ function buildProgressFreshnessWarnings(progress) {
4739
+ return (progress?.targets ?? []).map((target) => freshnessWarning({
4740
+ freshness: target.freshness,
4741
+ requestedTarget: target.requested,
4742
+ freshTarget: target.resolvedRequested,
4743
+ servedTarget: target.served
4744
+ })).filter((entry) => Boolean(entry));
4745
+ }
4746
+ function freshnessWarning(input) {
4747
+ if (!isTrustRelevantFreshness(input.freshness))
4748
+ return;
4749
+ if (!labelsDiverge(input))
4750
+ return;
4751
+ const requested = input.requestedTarget ?? "requested target";
4752
+ const served = input.servedTarget ?? "served target";
4753
+ const fresh = input.freshTarget;
4754
+ return fresh ? `requested ${requested}; served stale ${served} while ${fresh} indexes.` : `requested ${requested}; served stale ${served}.`;
4755
+ }
4756
+ function isTrustRelevantFreshness(value) {
4757
+ return value === "STALE" || value === "INDEXING";
4758
+ }
4759
+ function labelsDiverge(input) {
4760
+ const served = input.servedTarget;
4761
+ if (!served)
4762
+ return false;
4763
+ return Boolean(input.freshTarget && input.freshTarget !== served || input.requestedTarget && input.requestedTarget !== served);
4764
+ }
4617
4765
  function warningForEntry(entry) {
4618
4766
  const reasons = [];
4767
+ const freshness = freshnessWarning({
4768
+ freshness: entry.codeIndexState,
4769
+ requestedTarget: entry.requestedTarget,
4770
+ freshTarget: entry.freshTarget,
4771
+ servedTarget: entry.servedTarget
4772
+ });
4773
+ if (freshness)
4774
+ return freshness;
4619
4775
  if (entry.incompatibleQueryFeatures?.length) {
4620
4776
  reasons.push(`incompatible query features [${entry.incompatibleQueryFeatures.join(", ")}]`);
4621
4777
  }
@@ -4632,7 +4788,9 @@ function warningForEntry(entry) {
4632
4788
  reasons.push(`indexing status ${entry.indexingStatus}`);
4633
4789
  }
4634
4790
  if (entry.codeIndexState) {
4635
- reasons.push(`code index state ${entry.codeIndexState}`);
4791
+ if (entry.codeIndexState !== "STALE") {
4792
+ reasons.push(`code index state ${entry.codeIndexState}`);
4793
+ }
4636
4794
  }
4637
4795
  const prefix = `Source '${entry.source}' for ${entry.targetLabel}`;
4638
4796
  if (reasons.length > 0) {
@@ -4660,11 +4818,23 @@ function compactSourceStatusEntry(entry) {
4660
4818
  targetLabel: entry.targetLabel
4661
4819
  };
4662
4820
  let interesting = false;
4821
+ const staleDiverges = entry.codeIndexState === "STALE" && labelsDiverge({
4822
+ requestedTarget: entry.requestedTargetLabel,
4823
+ freshTarget: entry.freshTargetLabel,
4824
+ servedTarget: entry.servedTargetLabel
4825
+ });
4826
+ if (staleDiverges) {
4827
+ payload.requestedTarget = entry.requestedTargetLabel;
4828
+ payload.freshTarget = entry.freshTargetLabel;
4829
+ payload.servedTarget = entry.servedTargetLabel;
4830
+ payload.codeIndexState = entry.codeIndexState;
4831
+ interesting = true;
4832
+ }
4663
4833
  if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
4664
4834
  payload.indexingStatus = entry.indexingStatus;
4665
4835
  interesting = true;
4666
4836
  }
4667
- if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && entry.codeIndexState !== "STALE") {
4837
+ if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && (entry.codeIndexState !== "STALE" || staleDiverges)) {
4668
4838
  payload.codeIndexState = entry.codeIndexState;
4669
4839
  interesting = true;
4670
4840
  }
@@ -4839,8 +5009,45 @@ function buildTrailer2(payload) {
4839
5009
  lines.push(` - ${formatSourceStatus(entry)}`);
4840
5010
  }
4841
5011
  }
5012
+ const progress = "progress" in payload ? payload.progress : undefined;
5013
+ if (progress?.targets?.length) {
5014
+ lines.push("progress targets:");
5015
+ for (const target of progress.targets) {
5016
+ lines.push(` - ${formatProgressTarget(target)}`);
5017
+ }
5018
+ }
4842
5019
  return lines;
4843
5020
  }
5021
+ function formatProgressTarget(target) {
5022
+ const parts = [];
5023
+ if (target.requested)
5024
+ parts.push(`requested=${target.requested}`);
5025
+ if (target.resolvedRequested)
5026
+ parts.push(`fresh=${target.resolvedRequested}`);
5027
+ if (target.served)
5028
+ parts.push(`served=${target.served}`);
5029
+ if (target.freshness)
5030
+ parts.push(`state=${describeFreshness(target.freshness)}`);
5031
+ if (target.requestedRefKind)
5032
+ parts.push(`intent=${target.requestedRefKind}`);
5033
+ if (target.indexingRef)
5034
+ parts.push(`indexingRef=${target.indexingRef}`);
5035
+ return parts.length > 0 ? parts.join(SEP6) : "target progress unavailable";
5036
+ }
5037
+ function describeFreshness(value) {
5038
+ switch (value) {
5039
+ case "PENDING":
5040
+ case "INDEXING":
5041
+ return "indexing fresh target";
5042
+ case "STALE":
5043
+ return "served stale evidence";
5044
+ case "CURRENT":
5045
+ case "INDEXED":
5046
+ return "current";
5047
+ default:
5048
+ return value.toLowerCase();
5049
+ }
5050
+ }
4844
5051
  function formatSourceStatus(entry) {
4845
5052
  const parts = [`${entry.source} (${entry.targetLabel})`];
4846
5053
  if (entry.indexingStatus)
@@ -4903,6 +5110,17 @@ function renderUnifiedSearchStatusText(payload) {
4903
5110
  lines.push(buildHeader8(payload));
4904
5111
  if (!payload.completed && payload.progress) {
4905
5112
  lines.push(formatProgress(payload.progress));
5113
+ if (payload.progress.targets?.length) {
5114
+ lines.push("progress targets:");
5115
+ for (const target of payload.progress.targets) {
5116
+ lines.push(` - ${formatProgressTarget(target)}`);
5117
+ }
5118
+ }
5119
+ }
5120
+ if (!payload.completed && payload.warnings && payload.warnings.length > 0) {
5121
+ lines.push("warnings:");
5122
+ for (const warning2 of payload.warnings)
5123
+ lines.push(` - ${warning2}`);
4906
5124
  }
4907
5125
  const result = payload.result;
4908
5126
  if (result)
@@ -4969,14 +5187,39 @@ function formatSourceStatus2(entry) {
4969
5187
  return parts.join(SEP7);
4970
5188
  }
4971
5189
  function formatProgress(progress) {
4972
- return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed`;
5190
+ const next = progress.next ? `; next: ${progress.next}` : "";
5191
+ return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed${next}`;
4973
5192
  }
4974
5193
  function quote5(value) {
4975
5194
  return JSON.stringify(value);
4976
5195
  }
4977
5196
  // src/shared/unified-search-target.ts
4978
5197
  function parseUnifiedSearchTargetSpec(spec) {
4979
- return parseCodeNavigationTargetSpec(spec);
5198
+ const trimmed = spec.trim();
5199
+ if (trimmed.length === 0) {
5200
+ throw new InvalidArgumentError("Target spec cannot be empty.");
5201
+ }
5202
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
5203
+ return parseDiscoveryRepoTarget(trimmed);
5204
+ }
5205
+ const parsed = parsePackageSpec(trimmed);
5206
+ return {
5207
+ registry: toCodeNavigationRegistry(parsed.registry),
5208
+ packageName: parsed.name,
5209
+ version: parsed.version
5210
+ };
5211
+ }
5212
+ function parseDiscoveryRepoTarget(spec) {
5213
+ const hashIndex = spec.lastIndexOf("#");
5214
+ if (hashIndex === -1) {
5215
+ return { repoUrl: spec };
5216
+ }
5217
+ const repoUrl = spec.slice(0, hashIndex);
5218
+ const gitRef = spec.slice(hashIndex + 1);
5219
+ if (!repoUrl || !gitRef) {
5220
+ throw new InvalidArgumentError("Repository target must be a full URL with optional #gitRef suffix.");
5221
+ }
5222
+ return { repoUrl, gitRef };
4980
5223
  }
4981
5224
  // src/shared/list-files-request.ts
4982
5225
  var LIMIT_MIN2 = 1;
@@ -5734,7 +5977,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
5734
5977
  match in --verbose output; full payload in --json).`;
5735
5978
  function registerCodeGrepCommand(pkgCommand) {
5736
5979
  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) => {
5737
- const { createContainer: createContainer2 } = await import("./shared/chunk-b6avnx6d.js");
5980
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
5738
5981
  const deps = await createContainer2();
5739
5982
  await pkgGrepAction(arg1, arg2, arg3, options, {
5740
5983
  codeNavigationService: deps.codeNavigationService,
@@ -6210,6 +6453,10 @@ async function registerDocsCommandGroup(program, options = {}) {
6210
6453
  // src/commands/example.ts
6211
6454
  import { Option } from "commander";
6212
6455
  async function exampleAction(query, options, deps) {
6456
+ if (!deps.hasValidToken && options.json) {
6457
+ printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", true);
6458
+ process.exit(1);
6459
+ }
6213
6460
  requireAuth(deps);
6214
6461
  try {
6215
6462
  const result = await deps.githitsService.search({
@@ -6226,10 +6473,21 @@ async function exampleAction(query, options, deps) {
6226
6473
  console.log(result);
6227
6474
  }
6228
6475
  } catch (error2) {
6229
- console.error(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`);
6476
+ if (error2 instanceof AuthenticationError) {
6477
+ printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", options.json ?? false);
6478
+ process.exit(1);
6479
+ }
6480
+ printExampleError(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`, "UNKNOWN", options.json ?? false);
6230
6481
  process.exit(1);
6231
6482
  }
6232
6483
  }
6484
+ function printExampleError(error2, code, json) {
6485
+ if (json) {
6486
+ console.error(JSON.stringify({ error: error2, code, retryable: false }));
6487
+ return;
6488
+ }
6489
+ console.error(error2);
6490
+ }
6233
6491
  var EXAMPLE_DESCRIPTION = `Get verified, canonical code examples from global open source.
6234
6492
 
6235
6493
  For dependency, package, or repository source search, use \`githits search\` instead.
@@ -6253,7 +6511,7 @@ function registerExampleCommand(program) {
6253
6511
  });
6254
6512
  }
6255
6513
  async function loadContainer() {
6256
- const { createContainer: createContainer2 } = await import("./shared/chunk-b6avnx6d.js");
6514
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
6257
6515
  return createContainer2();
6258
6516
  }
6259
6517
  // src/commands/feedback.ts
@@ -7140,7 +7398,13 @@ async function loginFlow(options, deps, output = stdoutLoginOutput) {
7140
7398
  state,
7141
7399
  codeChallenge: challenge
7142
7400
  });
7143
- const serverPromise = authService.startCallbackServer(port, state);
7401
+ let callbackServer;
7402
+ try {
7403
+ callbackServer = await authService.startCallbackServer(port, state);
7404
+ } catch (error2) {
7405
+ const msg = error2 instanceof Error ? error2.message : String(error2);
7406
+ return { status: "failed", message: msg };
7407
+ }
7144
7408
  if (options.browser === false) {
7145
7409
  output.write(`Open this URL in your browser:
7146
7410
  `);
@@ -7148,7 +7412,17 @@ async function loginFlow(options, deps, output = stdoutLoginOutput) {
7148
7412
  `);
7149
7413
  } else {
7150
7414
  output.write("Opening browser...");
7151
- await browserService.open(authUrl);
7415
+ try {
7416
+ await browserService.open(authUrl);
7417
+ } catch (error2) {
7418
+ const msg = error2 instanceof Error ? error2.message : String(error2);
7419
+ output.write(`Could not open browser automatically: ${msg}
7420
+ `);
7421
+ output.write(`Open this URL in your browser:
7422
+ `);
7423
+ output.write(` ${authUrl}
7424
+ `);
7425
+ }
7152
7426
  }
7153
7427
  output.write(`Waiting for authentication...
7154
7428
  `);
@@ -7158,12 +7432,13 @@ async function loginFlow(options, deps, output = stdoutLoginOutput) {
7158
7432
  });
7159
7433
  let callback;
7160
7434
  try {
7161
- callback = await Promise.race([serverPromise, timeoutPromise]);
7435
+ callback = await Promise.race([callbackServer.result, timeoutPromise]);
7162
7436
  if (timeoutId)
7163
7437
  clearTimeout(timeoutId);
7164
7438
  } catch (error2) {
7165
7439
  if (timeoutId)
7166
7440
  clearTimeout(timeoutId);
7441
+ await callbackServer.close().catch(() => {});
7167
7442
  const msg = error2 instanceof Error ? error2.message : "Authentication failed";
7168
7443
  return { status: "failed", message: `${msg}.` };
7169
7444
  }
@@ -7220,7 +7495,7 @@ async function loginAction(options, deps) {
7220
7495
  if (result.status === "failed") {
7221
7496
  console.error(`${result.message}
7222
7497
  `);
7223
- console.log("Run `githits login` to try again.");
7498
+ printLoginRecoveryHint(result.message);
7224
7499
  process.exit(1);
7225
7500
  }
7226
7501
  console.log(`Logged in successfully.
@@ -7230,6 +7505,16 @@ async function loginAction(options, deps) {
7230
7505
  console.log(`
7231
7506
  You're ready to use githits with your AI assistant.`);
7232
7507
  }
7508
+ function printLoginRecoveryHint(message) {
7509
+ console.log("Recovery steps:");
7510
+ console.log(" githits auth status");
7511
+ console.log(" githits login --force");
7512
+ if (message.includes("Cannot persist OAuth credentials")) {
7513
+ console.log("If your system keychain is locked or unavailable, unlock it and retry.");
7514
+ console.log("For CI/automation, set GITHITS_API_TOKEN.");
7515
+ console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
7516
+ }
7517
+ }
7233
7518
  var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
7234
7519
 
7235
7520
  Opens your browser to complete authentication securely using OAuth.
@@ -7266,6 +7551,7 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
7266
7551
  async function initAction(options, deps) {
7267
7552
  const useColors = shouldUseColors();
7268
7553
  const { fileSystemService, promptService, execService, createLoginDeps } = deps;
7554
+ let continuedWithoutAuth = false;
7269
7555
  console.log(`
7270
7556
  ${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
7271
7557
  `);
@@ -7289,6 +7575,7 @@ async function initAction(options, deps) {
7289
7575
  } else {
7290
7576
  console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
7291
7577
  `);
7578
+ printAuthRecoveryHint();
7292
7579
  if (!options.yes) {
7293
7580
  try {
7294
7581
  const choice = await promptService.confirm3("Continue without authentication?");
@@ -7306,6 +7593,7 @@ async function initAction(options, deps) {
7306
7593
  throw err;
7307
7594
  }
7308
7595
  }
7596
+ continuedWithoutAuth = true;
7309
7597
  console.log(` Continuing without authentication...
7310
7598
  `);
7311
7599
  }
@@ -7329,6 +7617,11 @@ async function initAction(options, deps) {
7329
7617
  return;
7330
7618
  }
7331
7619
  if (scan.needsSetup.length === 0) {
7620
+ if (continuedWithoutAuth) {
7621
+ console.log(" MCP is already configured, but authentication is still required.");
7622
+ console.log(" Run `githits login` before using GitHits tools.\n");
7623
+ return;
7624
+ }
7332
7625
  console.log(` All detected agents are already configured. Nothing to do.
7333
7626
  `);
7334
7627
  return;
@@ -7401,6 +7694,9 @@ async function initAction(options, deps) {
7401
7694
  const skipped = outcomes.filter((o) => o.status === "skipped").length;
7402
7695
  if (failed > 0) {
7403
7696
  console.log(" Setup completed with errors.");
7697
+ } else if (continuedWithoutAuth && (configured > 0 || alreadyDone > 0)) {
7698
+ console.log(" MCP is configured, but authentication is still required.");
7699
+ console.log(" Run `githits login` before using GitHits tools.");
7404
7700
  } else if (configured > 0 || alreadyDone > 0) {
7405
7701
  console.log(" Done! GitHits is ready.");
7406
7702
  } else if (skipped > 0) {
@@ -7417,6 +7713,15 @@ async function initAction(options, deps) {
7417
7713
  }
7418
7714
  console.log();
7419
7715
  }
7716
+ function printAuthRecoveryHint() {
7717
+ console.log(" You can still configure MCP, but GitHits tools will require auth.");
7718
+ console.log(" Recovery steps:");
7719
+ console.log(" githits auth status");
7720
+ console.log(" githits login --force");
7721
+ console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
7722
+ console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
7723
+ `);
7724
+ }
7420
7725
  var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
7421
7726
 
7422
7727
  Authenticates with your GitHits account, then scans for available agents
@@ -7647,7 +7952,7 @@ var structuredCodeTargetSchema = z3.object({
7647
7952
  }).describe("Target: provide registry + package_name (package scope) or repo_url + git_ref (repo scope).");
7648
7953
  var codeTargetSchema = z3.union([
7649
7954
  structuredCodeTargetSchema,
7650
- z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix optional, defaults to HEAD).")
7955
+ z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix required for exact code navigation).")
7651
7956
  ]);
7652
7957
  function resolveCodeTarget(target) {
7653
7958
  if (typeof target === "string") {
@@ -7679,10 +7984,7 @@ function resolveCodeTarget(target) {
7679
7984
  if (!target.repo_url) {
7680
7985
  return invalidTargetResult("Incomplete repository target: repo_url is required.");
7681
7986
  }
7682
- return {
7683
- repoUrl: target.repo_url,
7684
- gitRef: target.git_ref ?? "HEAD"
7685
- };
7987
+ return invalidTargetResult("Incomplete repository target: git_ref is required for exact code navigation.");
7686
7988
  }
7687
7989
  return {
7688
7990
  repoUrl: target.repo_url,
@@ -8638,10 +8940,14 @@ function buildRange3(args, textMode) {
8638
8940
  }
8639
8941
  // src/tools/search.ts
8640
8942
  import { z as z13 } from "zod";
8943
+ var searchTargetSchema = z13.union([
8944
+ structuredCodeTargetSchema,
8945
+ z13.string().min(1).describe("Compact discovery target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react` for backend default branch or `https://github.com/facebook/react#HEAD` for an explicit ref.")
8946
+ ]);
8641
8947
  var schema12 = {
8642
8948
  query: z13.string().min(1).describe("Discovery query string. Supports implicit AND, uppercase OR, parentheses, unary -, quoted phrases, semantic qualifiers (kind:, category:, path:, lang:, name:, intent:), and routing qualifiers (registry:, package:, version:, repo:). Parsed once and compiled per source; it is not forwarded as a raw backend query."),
8643
- target: codeTargetSchema.optional(),
8644
- targets: z13.array(codeTargetSchema).max(20).optional(),
8949
+ target: searchTargetSchema.optional(),
8950
+ targets: z13.array(searchTargetSchema).max(20).optional(),
8645
8951
  sources: z13.array(z13.enum(["docs", "code", "symbol"])).optional().describe("Optional source selection. Omit for backend AUTO."),
8646
8952
  category: z13.enum(["callable", "type", "module", "data", "documentation"]).optional(),
8647
8953
  kind: z13.enum([
@@ -8703,17 +9009,17 @@ function createSearchTool(service) {
8703
9009
  annotations: { readOnlyHint: true },
8704
9010
  handler: async (args) => {
8705
9011
  try {
8706
- const resolvedTarget = args.target ? resolveCodeTarget(args.target) : undefined;
9012
+ const resolvedTarget = args.target ? resolveSearchTarget(args.target) : undefined;
8707
9013
  if (resolvedTarget && "content" in resolvedTarget)
8708
9014
  return resolvedTarget;
8709
- const resolvedTargets = args.targets?.map((entry) => resolveCodeTarget(entry));
9015
+ const resolvedTargets = args.targets?.map((entry) => resolveSearchTarget(entry));
8710
9016
  const resolvedTargetsError = resolvedTargets?.find((entry) => ("content" in entry));
8711
9017
  if (resolvedTargetsError) {
8712
9018
  return resolvedTargetsError;
8713
9019
  }
8714
9020
  const built = buildUnifiedSearchParams({
8715
9021
  target: resolvedTarget && !("content" in resolvedTarget) ? resolvedTarget : undefined,
8716
- targets: resolvedTargets?.filter(isResolvedCodeTarget),
9022
+ targets: resolvedTargets?.filter(isResolvedSearchTarget),
8717
9023
  query: args.query,
8718
9024
  sources: args.sources?.map((entry) => entry.toUpperCase()),
8719
9025
  kind: toSymbolKind(args.kind),
@@ -8744,9 +9050,53 @@ function createSearchTool(service) {
8744
9050
  }
8745
9051
  };
8746
9052
  }
8747
- function isResolvedCodeTarget(target) {
9053
+ function isResolvedSearchTarget(target) {
8748
9054
  return !("content" in target);
8749
9055
  }
9056
+ function resolveSearchTarget(target) {
9057
+ if (typeof target === "string") {
9058
+ try {
9059
+ return parseUnifiedSearchTargetSpec(target);
9060
+ } catch (error2) {
9061
+ const mapped = mapCodeNavigationError(error2);
9062
+ return errorResult(JSON.stringify({
9063
+ error: mapped.message,
9064
+ code: mapped.code,
9065
+ retryable: mapped.retryable ?? false,
9066
+ ...mapped.details ? { details: mapped.details } : {}
9067
+ }));
9068
+ }
9069
+ }
9070
+ const hasPackageTarget = Boolean(target.registry || target.package_name);
9071
+ const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
9072
+ if (hasPackageTarget && hasRepoTarget) {
9073
+ return invalidSearchTargetResult("Invalid target: provide either registry + package_name or repo_url + git_ref, not both.");
9074
+ }
9075
+ if (!hasPackageTarget && !hasRepoTarget) {
9076
+ return invalidSearchTargetResult("Missing target: provide registry + package_name or repo_url.");
9077
+ }
9078
+ if (hasPackageTarget) {
9079
+ if (!target.registry || !target.package_name) {
9080
+ return invalidSearchTargetResult("Incomplete package target: both registry and package_name are required.");
9081
+ }
9082
+ return {
9083
+ registry: toCodeNavigationRegistry(target.registry),
9084
+ packageName: target.package_name,
9085
+ version: target.version
9086
+ };
9087
+ }
9088
+ if (!target.repo_url) {
9089
+ return invalidSearchTargetResult("Incomplete repository target: repo_url is required.");
9090
+ }
9091
+ return { repoUrl: target.repo_url, gitRef: target.git_ref };
9092
+ }
9093
+ function invalidSearchTargetResult(message) {
9094
+ return errorResult(JSON.stringify({
9095
+ error: message,
9096
+ code: "INVALID_ARGUMENT",
9097
+ retryable: false
9098
+ }));
9099
+ }
8750
9100
  function isTextFormat11(format) {
8751
9101
  return format === undefined || format === "text" || format === "text-v1";
8752
9102
  }
@@ -9506,7 +9856,7 @@ function requireSearchService(deps) {
9506
9856
  return deps.codeNavigationService;
9507
9857
  }
9508
9858
  async function loadContainer2() {
9509
- const { createContainer: createContainer2 } = await import("./shared/chunk-b6avnx6d.js");
9859
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
9510
9860
  return createContainer2();
9511
9861
  }
9512
9862
  function parseTargetSpecs(specs) {
@@ -9585,8 +9935,9 @@ function formatSearchErrorTerminal(payload, context) {
9585
9935
  function formatUnifiedSearchTerminal(payload) {
9586
9936
  const lines = [];
9587
9937
  const useColors = shouldUseColors();
9588
- if (payload.query.warnings && payload.query.warnings.length > 0) {
9589
- for (const warning2 of payload.query.warnings) {
9938
+ const warnings = payload.warnings ?? payload.query.warnings;
9939
+ if (warnings && warnings.length > 0) {
9940
+ for (const warning2 of warnings) {
9590
9941
  lines.push(`Warning: ${warning2}`);
9591
9942
  }
9592
9943
  lines.push("");
@@ -9625,7 +9976,7 @@ function formatUnifiedSearchTerminal(payload) {
9625
9976
  lines.push("");
9626
9977
  for (const entry of display) {
9627
9978
  const location = formatUnifiedSearchLocation(entry.locator);
9628
- const header = formatUnifiedSearchHeader(entry, useColors, location);
9979
+ const header = formatUnifiedSearchHeader(entry, useColors, location, payload.query.raw);
9629
9980
  lines.push(header);
9630
9981
  const metadata = formatUnifiedSearchMetadata(entry, useColors);
9631
9982
  if (metadata.length > 0) {
@@ -9692,7 +10043,11 @@ function formatSearchStatusCompletedTerminal(payload) {
9692
10043
  results: payload.result.results,
9693
10044
  searchRef: payload.searchRef,
9694
10045
  progress: undefined,
9695
- query: { warnings: payload.result.warnings },
10046
+ query: {
10047
+ raw: payload.result.query?.raw,
10048
+ warnings: payload.result.warnings
10049
+ },
10050
+ warnings: payload.result.warnings,
9696
10051
  sourceStatus: payload.result.sourceStatus
9697
10052
  });
9698
10053
  }
@@ -9704,7 +10059,11 @@ function formatSearchStatusPartialTerminal(payload) {
9704
10059
  results: payload.result.results,
9705
10060
  searchRef: payload.searchRef,
9706
10061
  progress: payload.progress,
9707
- query: { warnings: payload.result.warnings },
10062
+ query: {
10063
+ raw: payload.result.query?.raw,
10064
+ warnings: payload.result.warnings
10065
+ },
10066
+ warnings: payload.result.warnings,
9708
10067
  sourceStatus: payload.result.sourceStatus
9709
10068
  });
9710
10069
  }
@@ -9815,11 +10174,113 @@ function formatUnifiedSearchLocation(locator) {
9815
10174
  }
9816
10175
  return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
9817
10176
  }
9818
- function formatUnifiedSearchHeader(entry, useColors, location) {
9819
- const primary = entry.type === "documentation_page" ? entry.target : location ? `${entry.target} ${location}` : entry.target;
10177
+ function formatUnifiedSearchHeader(entry, useColors, location, rawQuery) {
10178
+ const primary = formatUnifiedSearchPrimary(entry.type, entry.target, location, rawQuery, useColors);
9820
10179
  const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
9821
10180
  const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
9822
- return `${highlight(primary, useColors)} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
10181
+ return `${primary} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
10182
+ }
10183
+ function formatUnifiedSearchPrimary(type, target, location, rawQuery, useColors) {
10184
+ const formattedTarget = highlight(target, useColors);
10185
+ if (type === "documentation_page" || !location) {
10186
+ return formattedTarget;
10187
+ }
10188
+ return `${formattedTarget} ${formatLocationWithQueryHighlights(location, rawQuery, useColors)}`;
10189
+ }
10190
+ function formatLocationWithQueryHighlights(location, rawQuery, useColors) {
10191
+ const ranges = buildQueryTermRanges(location, rawQuery);
10192
+ if (ranges.length === 0)
10193
+ return highlight(location, useColors);
10194
+ if (!useColors)
10195
+ return location;
10196
+ let result = "";
10197
+ let cursor2 = 0;
10198
+ for (const [start, end] of ranges) {
10199
+ if (cursor2 < start)
10200
+ result += highlight(location.slice(cursor2, start), true);
10201
+ result += highlightMatch(location.slice(start, end), true);
10202
+ cursor2 = end;
10203
+ }
10204
+ if (cursor2 < location.length)
10205
+ result += highlight(location.slice(cursor2), true);
10206
+ return result;
10207
+ }
10208
+ function buildQueryTermRanges(text, rawQuery) {
10209
+ const terms = extractQueryHighlightTerms(rawQuery);
10210
+ if (terms.length === 0)
10211
+ return [];
10212
+ const lowerText = text.toLowerCase();
10213
+ const ranges = [];
10214
+ const orderedTerms = [...terms].sort((left, right) => right.length - left.length);
10215
+ for (const term of orderedTerms) {
10216
+ const lowerTerm = term.toLowerCase();
10217
+ let cursor2 = 0;
10218
+ while (cursor2 < lowerText.length) {
10219
+ const start = lowerText.indexOf(lowerTerm, cursor2);
10220
+ if (start === -1)
10221
+ break;
10222
+ const end = start + lowerTerm.length;
10223
+ if (!ranges.some((range) => rangesOverlap(range, [start, end]))) {
10224
+ ranges.push([start, end]);
10225
+ }
10226
+ cursor2 = end;
10227
+ }
10228
+ }
10229
+ return mergeRanges2(ranges);
10230
+ }
10231
+ function extractQueryHighlightTerms(rawQuery) {
10232
+ if (!rawQuery)
10233
+ return [];
10234
+ const booleanOperators = new Set(["AND", "OR", "NOT"]);
10235
+ const terms = new Set;
10236
+ const quotedRanges = [];
10237
+ for (const match of rawQuery.matchAll(/"([^"]+)"/g)) {
10238
+ const phrase = match[1];
10239
+ if (phrase) {
10240
+ addQueryHighlightTerm(phrase, terms, booleanOperators, {
10241
+ stripQualifier: false
10242
+ });
10243
+ }
10244
+ if (typeof match.index === "number") {
10245
+ quotedRanges.push([match.index, match.index + match[0].length]);
10246
+ }
10247
+ }
10248
+ for (const match of rawQuery.matchAll(/[A-Za-z0-9_./@:-]+/g)) {
10249
+ const index = match.index ?? 0;
10250
+ if (quotedRanges.some(([start, end]) => index >= start && index < end)) {
10251
+ continue;
10252
+ }
10253
+ addQueryHighlightTerm(match[0], terms, booleanOperators);
10254
+ }
10255
+ return Array.from(terms);
10256
+ }
10257
+ function addQueryHighlightTerm(candidate, terms, booleanOperators, options = { stripQualifier: true }) {
10258
+ const normalised = options.stripQualifier && /^[A-Za-z]+:.+/.test(candidate) ? candidate.split(":").slice(1).join(":") : candidate;
10259
+ const term = normalised.replace(/^[-+]+/, "").replace(/[-+]+$/, "");
10260
+ if (term.length < 2)
10261
+ return;
10262
+ if (booleanOperators.has(term.toUpperCase()))
10263
+ return;
10264
+ terms.add(term);
10265
+ }
10266
+ function rangesOverlap(left, right) {
10267
+ return left[0] < right[1] && right[0] < left[1];
10268
+ }
10269
+ function mergeRanges2(ranges) {
10270
+ const sorted = ranges.filter(([start, end]) => end > start).sort((left, right) => left[0] - right[0] || left[1] - right[1]);
10271
+ const merged = [];
10272
+ for (const current of sorted) {
10273
+ const previous = merged[merged.length - 1];
10274
+ if (!previous || current[0] > previous[1]) {
10275
+ merged.push(current);
10276
+ continue;
10277
+ }
10278
+ merged[merged.length - 1] = [
10279
+ previous[0],
10280
+ Math.max(previous[1], current[1])
10281
+ ];
10282
+ }
10283
+ return merged;
9823
10284
  }
9824
10285
  function formatUnifiedSearchMetadata(entry, useColors) {
9825
10286
  if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
@@ -9827,16 +10288,14 @@ function formatUnifiedSearchMetadata(entry, useColors) {
9827
10288
  }
9828
10289
  const lines = [];
9829
10290
  if (entry.locator.pageId) {
9830
- lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
10291
+ if (entry.type === "documentation_page") {
10292
+ lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
10293
+ }
9831
10294
  }
9832
10295
  const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
9833
- if (entry.locator.sourceUrl) {
10296
+ if (entry.locator.sourceUrl && entry.type === "documentation_page") {
9834
10297
  lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
9835
10298
  }
9836
- if (entry.type === "repository_doc" && entry.locator.filePath) {
9837
- const ref = entry.locator.requestedRef ?? entry.locator.gitRef;
9838
- lines.push(` ${dim("file:", useColors)} ${entry.locator.filePath}${ref ? ` @ ${ref}` : ""}`);
9839
- }
9840
10299
  return lines;
9841
10300
  }
9842
10301
  // src/cli.ts