githits 0.4.5 → 0.4.6

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
@@ -51,11 +51,11 @@ import {
51
51
  shouldRunUpdateCheck,
52
52
  startTelemetrySpan,
53
53
  withTelemetrySpan
54
- } from "./shared/chunk-ygqss6gp.js";
54
+ } from "./shared/chunk-f16s86ze.js";
55
55
  import {
56
56
  __require,
57
57
  version
58
- } from "./shared/chunk-vfyz65v1.js";
58
+ } from "./shared/chunk-a1hzwt2m.js";
59
59
 
60
60
  // src/cli.ts
61
61
  import { Command } from "commander";
@@ -583,6 +583,12 @@ function classify(error2) {
583
583
  if (error2.availableVersions && error2.availableVersions.length > 0) {
584
584
  details.availableVersions = error2.availableVersions;
585
585
  }
586
+ if (error2.availableRefs && error2.availableRefs.length > 0) {
587
+ details.availableRefs = error2.availableRefs;
588
+ }
589
+ if (error2.targetResolution) {
590
+ details.targetResolution = error2.targetResolution;
591
+ }
586
592
  return {
587
593
  code: "INDEXING",
588
594
  message: error2.message,
@@ -788,7 +794,7 @@ function parseCodeNavigationTargetSpec(spec) {
788
794
  function parseRepoTarget(spec) {
789
795
  const hashIndex = spec.lastIndexOf("#");
790
796
  if (hashIndex === -1) {
791
- throw new InvalidArgumentError("Repository target must include #gitRef for code_files/code_read/code_grep.");
797
+ return { repoUrl: spec };
792
798
  }
793
799
  const repoUrl = spec.slice(0, hashIndex);
794
800
  const gitRef = spec.slice(hashIndex + 1);
@@ -827,6 +833,7 @@ function buildSearchHitFollowUpCommand(hit) {
827
833
  version: loc.version,
828
834
  repoUrl: loc.repoUrl,
829
835
  gitRef: loc.gitRef,
836
+ requestedRef: loc.requestedRef,
830
837
  filePath: loc.filePath,
831
838
  startLine: loc.startLine,
832
839
  endLine: loc.endLine,
@@ -863,9 +870,8 @@ function buildTargetSpec(input) {
863
870
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
864
871
  }
865
872
  if (input.repoUrl) {
866
- if (!input.gitRef)
867
- return;
868
- return `${input.repoUrl}#${input.gitRef}`;
873
+ const ref = input.gitRef ?? input.requestedRef;
874
+ return ref ? `${input.repoUrl}#${ref}` : input.repoUrl;
869
875
  }
870
876
  if (input.registry && input.packageName) {
871
877
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
@@ -1061,6 +1067,161 @@ function shellQuote(value) {
1061
1067
  return `'${value.replaceAll("'", `'"'"'`)}'`;
1062
1068
  }
1063
1069
 
1070
+ // src/shared/target-resolution.ts
1071
+ function projectTargetResolution(resolution) {
1072
+ if (!resolution)
1073
+ return;
1074
+ return {
1075
+ ...resolution.requested ? { requested: projectIdentity(resolution.requested) } : {},
1076
+ ...resolution.resolvedRequested ? { resolvedRequested: projectIdentity(resolution.resolvedRequested) } : {},
1077
+ ...resolution.served ? { served: projectIdentity(resolution.served) } : {},
1078
+ ...resolution.freshness ? { freshness: resolution.freshness } : {},
1079
+ ...resolution.freshnessReason ? { freshnessReason: resolution.freshnessReason } : {},
1080
+ ...resolution.indexingRef ? { indexingRef: resolution.indexingRef } : {},
1081
+ availableVersions: resolution.availableVersions.map(projectArtifact),
1082
+ availableRefs: resolution.availableRefs.map(projectArtifact)
1083
+ };
1084
+ }
1085
+ function buildTargetResolutionNotes(resolution) {
1086
+ if (!resolution)
1087
+ return [];
1088
+ const lines = [];
1089
+ const requested = formatIdentity(resolution.requested);
1090
+ const fresh = formatIdentity(resolution.resolvedRequested);
1091
+ const served = formatIdentity(resolution.served);
1092
+ const reason = resolution.freshnessReason ? ` (${resolution.freshnessReason})` : "";
1093
+ switch (resolution.freshness) {
1094
+ case "fallback_recent": {
1095
+ const parts = ["using recent index"];
1096
+ if (served)
1097
+ parts.push(`served=${served}`);
1098
+ if (fresh && identitiesMateriallyDiffer(fresh, served)) {
1099
+ parts.push(`fresh=${fresh}`);
1100
+ }
1101
+ lines.push(`${parts.join(" | ")}${reason}`);
1102
+ break;
1103
+ }
1104
+ case "indexing": {
1105
+ const parts = ["indexing fresh target"];
1106
+ if (requested)
1107
+ parts.push(`requested=${requested}`);
1108
+ if (fresh)
1109
+ parts.push(`fresh=${fresh}`);
1110
+ if (resolution.indexingRef)
1111
+ parts.push(`indexingRef=${resolution.indexingRef}`);
1112
+ lines.push(`${parts.join(" | ")}${reason}`);
1113
+ break;
1114
+ }
1115
+ case "unavailable": {
1116
+ const parts = ["target unavailable"];
1117
+ if (requested)
1118
+ parts.push(`requested=${requested}`);
1119
+ lines.push(`${parts.join(" | ")}${reason}`);
1120
+ break;
1121
+ }
1122
+ case "current": {
1123
+ break;
1124
+ }
1125
+ default: {
1126
+ if (resolution.freshness || identitiesDiffer(requested, fresh, served)) {
1127
+ const parts = [
1128
+ `target resolution: ${resolution.freshness ?? "unknown"}`
1129
+ ];
1130
+ if (served)
1131
+ parts.push(`served=${served}`);
1132
+ if (requested)
1133
+ parts.push(`requested=${requested}`);
1134
+ if (fresh && fresh !== served)
1135
+ parts.push(`fresh=${fresh}`);
1136
+ lines.push(`${parts.join(" | ")}${reason}`);
1137
+ }
1138
+ break;
1139
+ }
1140
+ }
1141
+ const candidates = buildRetryCandidateLine(resolution);
1142
+ if (candidates)
1143
+ lines.push(candidates);
1144
+ return lines;
1145
+ }
1146
+ function buildRetryCandidateLine(resolution) {
1147
+ if (!resolution)
1148
+ return;
1149
+ const parts = [];
1150
+ if (resolution.availableVersions.length > 0) {
1151
+ parts.push(`versions=${resolution.availableVersions.map(formatArtifact).join(",")}`);
1152
+ }
1153
+ if (resolution.availableRefs.length > 0) {
1154
+ parts.push(`refs=${resolution.availableRefs.map(formatArtifact).join(",")}`);
1155
+ }
1156
+ return parts.length > 0 ? `queryable now: ${parts.join(" | ")}` : undefined;
1157
+ }
1158
+ function buildResolutionFromRetryCandidates(target) {
1159
+ if (!target.availableVersions?.length && !target.availableRefs?.length) {
1160
+ return;
1161
+ }
1162
+ return {
1163
+ freshness: target.freshness,
1164
+ indexingRef: target.indexingRef,
1165
+ availableVersions: target.availableVersions ?? [],
1166
+ availableRefs: target.availableRefs ?? []
1167
+ };
1168
+ }
1169
+ function projectIdentity(identity) {
1170
+ const out = {};
1171
+ if (identity.kind)
1172
+ out.kind = identity.kind;
1173
+ if (identity.registry)
1174
+ out.registry = identity.registry;
1175
+ if (identity.packageName)
1176
+ out.packageName = identity.packageName;
1177
+ if (identity.version)
1178
+ out.version = identity.version;
1179
+ if (identity.repoUrl)
1180
+ out.repoUrl = identity.repoUrl;
1181
+ if (identity.gitRef)
1182
+ out.gitRef = identity.gitRef;
1183
+ if (identity.commitSha)
1184
+ out.commitSha = identity.commitSha;
1185
+ return out;
1186
+ }
1187
+ function projectArtifact(artifact) {
1188
+ return artifact.version ? { version: artifact.version, ref: artifact.ref } : { ref: artifact.ref };
1189
+ }
1190
+ function formatIdentity(identity) {
1191
+ if (!identity)
1192
+ return;
1193
+ if (identity.registry && identity.packageName) {
1194
+ const version2 = identity.version ? `@${identity.version}` : "";
1195
+ const commit = identity.commitSha ? `#${shortSha(identity.commitSha)}` : "";
1196
+ return `${identity.registry.toLowerCase()}:${identity.packageName}${version2}${commit}`;
1197
+ }
1198
+ if (identity.repoUrl) {
1199
+ const ref = identity.gitRef ? `#${identity.gitRef}` : "";
1200
+ const commit = identity.commitSha ? `@${shortSha(identity.commitSha)}` : "";
1201
+ return `${identity.repoUrl}${ref}${commit}`;
1202
+ }
1203
+ return identity.gitRef ?? identity.version ?? identity.commitSha ?? identity.kind;
1204
+ }
1205
+ function formatArtifact(artifact) {
1206
+ return artifact.version ? `${artifact.version}@${artifact.ref}` : artifact.ref;
1207
+ }
1208
+ function identitiesDiffer(requested, fresh, served) {
1209
+ if (!served)
1210
+ return Boolean(requested || fresh);
1211
+ return Boolean(requested && requested !== served || fresh && fresh !== served);
1212
+ }
1213
+ function identitiesMateriallyDiffer(left, right) {
1214
+ if (!left || !right)
1215
+ return Boolean(left || right);
1216
+ return stripShortCommit(left) !== stripShortCommit(right);
1217
+ }
1218
+ function stripShortCommit(value) {
1219
+ return value.replace(/[@#][0-9a-f]{7}$/i, "");
1220
+ }
1221
+ function shortSha(value) {
1222
+ return /^[0-9a-f]{12,}$/i.test(value) ? value.slice(0, 7) : value;
1223
+ }
1224
+
1064
1225
  // src/shared/grep-repo-response.ts
1065
1226
  var UTF8_ENCODER = new TextEncoder;
1066
1227
  function buildGrepRepoSuccessPayload(result, options) {
@@ -1102,6 +1263,9 @@ function buildGrepRepoSuccessPayload(result, options) {
1102
1263
  if (result.resolution) {
1103
1264
  envelope.resolution = projectResolution(result.resolution);
1104
1265
  }
1266
+ const targetResolution = projectTargetResolution(result.targetResolution);
1267
+ if (targetResolution)
1268
+ envelope.targetResolution = targetResolution;
1105
1269
  const filter = buildFilterBlock(options);
1106
1270
  if (filter)
1107
1271
  envelope.filter = filter;
@@ -1189,8 +1353,11 @@ function buildFilterBlock(options) {
1189
1353
  return Object.keys(filter).length > 0 ? filter : undefined;
1190
1354
  }
1191
1355
  function formatGrepRepoTerminal(envelope, options) {
1192
- if (envelope.matches.length === 0) {
1193
- return { stdout: "" };
1356
+ if (envelope.matches.length === 0 && !options.verbose) {
1357
+ return {
1358
+ stdout: "",
1359
+ stderr: formatTerminalNotes(envelope, options.useColors)
1360
+ };
1194
1361
  }
1195
1362
  const blocks = buildRenderBlocks(envelope.matches);
1196
1363
  return options.verbose ? formatVerbose(envelope, blocks, options) : formatPlain(envelope, blocks, options);
@@ -1247,6 +1414,10 @@ function formatVerbose(envelope, blocks, options) {
1247
1414
  }
1248
1415
  lines.push("");
1249
1416
  const blocksByFile = groupBlocksByFile(blocks);
1417
+ if (blocksByFile.size === 0) {
1418
+ lines.push("No matches.");
1419
+ lines.push("");
1420
+ }
1250
1421
  for (const [filePath, fileBlocks] of blocksByFile) {
1251
1422
  lines.push(colorize(filePath, "bold", options.useColors));
1252
1423
  const gutterWidth = widestLineNumberInBlocks(fileBlocks);
@@ -1418,6 +1589,9 @@ function formatTerminalNotes(envelope, useColors) {
1418
1589
  if (envelope.hasMore && envelope.nextCursor) {
1419
1590
  lines.push(dim(`More grep results available — rerun with --cursor ${shellQuote(envelope.nextCursor)}`, useColors));
1420
1591
  }
1592
+ for (const note of buildTargetResolutionNotes(envelope.targetResolution)) {
1593
+ lines.push(dim(note, useColors));
1594
+ }
1421
1595
  if (lines.length === 0)
1422
1596
  return;
1423
1597
  return `${lines.join(`
@@ -1548,6 +1722,9 @@ function buildTrailer(envelope) {
1548
1722
  if (skipNotes.length > 0) {
1549
1723
  lines.push(`Note: ${skipNotes.join(", ")}.`);
1550
1724
  }
1725
+ for (const note of buildTargetResolutionNotes(envelope.targetResolution)) {
1726
+ lines.push(note);
1727
+ }
1551
1728
  return lines;
1552
1729
  }
1553
1730
  function buildRenderBlocks2(matches) {
@@ -1668,6 +1845,7 @@ function renderListFilesText(envelope) {
1668
1845
  lines.push("");
1669
1846
  if (envelope.files.length === 0) {
1670
1847
  lines.push(envelope.hint ?? "No files match the requested filter.");
1848
+ appendTargetResolutionNotes(lines, envelope);
1671
1849
  return lines.join(`
1672
1850
  `);
1673
1851
  }
@@ -1682,9 +1860,18 @@ function renderListFilesText(envelope) {
1682
1860
  lines.push("");
1683
1861
  lines.push(envelope.hint);
1684
1862
  }
1863
+ appendTargetResolutionNotes(lines, envelope);
1685
1864
  return lines.join(`
1686
1865
  `);
1687
1866
  }
1867
+ function appendTargetResolutionNotes(lines, envelope) {
1868
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
1869
+ if (notes.length === 0)
1870
+ return;
1871
+ lines.push("");
1872
+ for (const note of notes)
1873
+ lines.push(note);
1874
+ }
1688
1875
  function buildHeader2(envelope) {
1689
1876
  const identity = buildIdentity(envelope);
1690
1877
  const countValue = envelope.hasMore ? `${envelope.files.length}+` : String(envelope.total);
@@ -2203,7 +2390,7 @@ function buildDirectVersionLookup(graph) {
2203
2390
  if (!fromRoot)
2204
2391
  continue;
2205
2392
  const node = graph.nodes[edge.toIndex];
2206
- if (!node || !node.version)
2393
+ if (!node?.version)
2207
2394
  continue;
2208
2395
  if (!out.has(node.name)) {
2209
2396
  out.set(node.name, node.version);
@@ -5176,6 +5363,9 @@ function buildReadFileSuccessPayload(result, options) {
5176
5363
  } else if (result.content != null) {
5177
5364
  envelope.content = result.content;
5178
5365
  }
5366
+ const targetResolution = projectTargetResolution(result.targetResolution);
5367
+ if (targetResolution)
5368
+ envelope.targetResolution = targetResolution;
5179
5369
  return envelope;
5180
5370
  }
5181
5371
  function formatReadFileTerminal(envelope, options) {
@@ -5230,10 +5420,19 @@ function formatVerboseBody(envelope, options) {
5230
5420
  lines.push("");
5231
5421
  lines.push(dim(envelope.hint, options.useColors));
5232
5422
  }
5423
+ appendTargetResolutionNotes2(lines, envelope, options);
5233
5424
  lines.push("");
5234
5425
  return lines.join(`
5235
5426
  `);
5236
5427
  }
5428
+ function appendTargetResolutionNotes2(lines, envelope, options) {
5429
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
5430
+ if (notes.length === 0)
5431
+ return;
5432
+ lines.push("");
5433
+ for (const note of notes)
5434
+ lines.push(dim(note, options.useColors));
5435
+ }
5237
5436
  function splitReadFileContentLines(envelope) {
5238
5437
  if (!envelope.content)
5239
5438
  return [];
@@ -5295,6 +5494,12 @@ function renderReadFileText(envelope) {
5295
5494
  lines.push("");
5296
5495
  lines.push(`hint: ${envelope.hint}`);
5297
5496
  }
5497
+ const resolutionNotes = buildTargetResolutionNotes(envelope.targetResolution);
5498
+ if (resolutionNotes.length > 0) {
5499
+ lines.push("");
5500
+ for (const note of resolutionNotes)
5501
+ lines.push(note);
5502
+ }
5298
5503
  return lines.join(`
5299
5504
  `);
5300
5505
  }
@@ -6316,6 +6521,15 @@ function compactProgressTarget(target) {
6316
6521
  payload.indexingRef = target.indexingRef;
6317
6522
  if (target.requestedRefKind)
6318
6523
  payload.requestedRefKind = target.requestedRefKind;
6524
+ const targetResolution = projectTargetResolution(target.targetResolution);
6525
+ if (targetResolution)
6526
+ payload.targetResolution = targetResolution;
6527
+ if (target.availableVersions?.length) {
6528
+ payload.availableVersions = target.availableVersions;
6529
+ }
6530
+ if (target.availableRefs?.length) {
6531
+ payload.availableRefs = target.availableRefs;
6532
+ }
6319
6533
  return Object.keys(payload).length > 0 ? payload : undefined;
6320
6534
  }
6321
6535
  function buildFilterEcho3(filters) {
@@ -6358,7 +6572,11 @@ function buildProgressFreshnessWarnings(progress) {
6358
6572
  requestedTarget: target.requested,
6359
6573
  freshTarget: target.resolvedRequested,
6360
6574
  servedTarget: target.served
6361
- })).filter((entry) => Boolean(entry));
6575
+ })).concat((progress?.targets ?? []).map((target) => progressTargetResolutionWarning(target)).filter((entry) => Boolean(entry))).filter((entry) => Boolean(entry));
6576
+ }
6577
+ function progressTargetResolutionWarning(target) {
6578
+ const notes = buildTargetResolutionNotes(target.targetResolution ?? buildResolutionFromRetryCandidates(target));
6579
+ return notes.length > 0 ? notes.join(" ") : undefined;
6362
6580
  }
6363
6581
  function freshnessWarning(input) {
6364
6582
  if (!isTrustRelevantFreshness(input.freshness))
@@ -6377,7 +6595,7 @@ function labelsDiverge(input) {
6377
6595
  const served = input.servedTarget;
6378
6596
  if (!served)
6379
6597
  return false;
6380
- return Boolean(input.freshTarget && input.freshTarget !== served || input.requestedTarget && input.requestedTarget !== served);
6598
+ return Boolean(input.freshTarget && input.freshTarget !== served);
6381
6599
  }
6382
6600
  function warningForEntry(entry) {
6383
6601
  const reasons = [];
@@ -6389,6 +6607,14 @@ function warningForEntry(entry) {
6389
6607
  });
6390
6608
  if (freshness)
6391
6609
  return freshness;
6610
+ const terminalLifecycleReason = terminalLifecycleWarningReason(entry);
6611
+ if (terminalLifecycleReason) {
6612
+ reasons.push(terminalLifecycleReason);
6613
+ } else {
6614
+ const targetResolutionWarning = targetResolutionWarningForEntry(entry);
6615
+ if (targetResolutionWarning)
6616
+ reasons.push(targetResolutionWarning);
6617
+ }
6392
6618
  if (entry.incompatibleQueryFeatures?.length) {
6393
6619
  reasons.push(`incompatible query features [${entry.incompatibleQueryFeatures.join(", ")}]`);
6394
6620
  }
@@ -6401,10 +6627,10 @@ function warningForEntry(entry) {
6401
6627
  if (entry.ignoredFilters?.length) {
6402
6628
  reasons.push(`ignored filters [${entry.ignoredFilters.join(", ")}]`);
6403
6629
  }
6404
- if (entry.indexingStatus) {
6630
+ if (!terminalLifecycleReason && reasons.length === 0 && entry.indexingStatus) {
6405
6631
  reasons.push(`indexing status ${entry.indexingStatus}`);
6406
6632
  }
6407
- if (entry.codeIndexState) {
6633
+ if (!terminalLifecycleReason && reasons.length === 0 && entry.codeIndexState) {
6408
6634
  if (entry.codeIndexState !== "STALE") {
6409
6635
  reasons.push(`code index state ${entry.codeIndexState}`);
6410
6636
  }
@@ -6418,6 +6644,18 @@ function warningForEntry(entry) {
6418
6644
  }
6419
6645
  return;
6420
6646
  }
6647
+ function terminalLifecycleWarningReason(entry) {
6648
+ const states = Array.from(new Set([entry.indexingStatus, entry.codeIndexState].filter(Boolean)));
6649
+ const terminalStates = states.filter((state) => state !== "INDEXING" && state !== "STALE");
6650
+ if (terminalStates.length === 0)
6651
+ return;
6652
+ const status = terminalStates.join("/");
6653
+ return entry.note ? `${entry.note} (${status})` : `status ${status}`;
6654
+ }
6655
+ function targetResolutionWarningForEntry(entry) {
6656
+ const notes = buildTargetResolutionNotes(entry.targetResolution);
6657
+ return notes.length > 0 ? notes.join(" ") : undefined;
6658
+ }
6421
6659
  function compactSourceStatus(sourceStatus) {
6422
6660
  if (!sourceStatus || sourceStatus.length === 0)
6423
6661
  return;
@@ -6447,6 +6685,13 @@ function compactSourceStatusEntry(entry) {
6447
6685
  payload.codeIndexState = entry.codeIndexState;
6448
6686
  interesting = true;
6449
6687
  }
6688
+ const targetResolution = projectTargetResolution(entry.targetResolution);
6689
+ if (targetResolution) {
6690
+ payload.targetResolution = targetResolution;
6691
+ if (buildTargetResolutionNotes(targetResolution).length > 0) {
6692
+ interesting = true;
6693
+ }
6694
+ }
6450
6695
  if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
6451
6696
  payload.indexingStatus = entry.indexingStatus;
6452
6697
  interesting = true;
@@ -6484,7 +6729,7 @@ function assertSearchFollowUpInvariant(hit) {
6484
6729
  if ((hit.resultType === "DOCUMENTATION_PAGE" || hit.resultType === "REPOSITORY_DOC") && !hit.locator.pageId) {
6485
6730
  throw new MalformedCodeNavigationResponseError(`${hit.resultType} search hit missing required pageId.`);
6486
6731
  }
6487
- if (hit.resultType === "REPOSITORY_DOC" && (!hit.locator.repoUrl || !hit.locator.gitRef || !hit.locator.filePath)) {
6732
+ if (hit.resultType === "REPOSITORY_DOC" && (!hit.locator.repoUrl || !hit.locator.filePath)) {
6488
6733
  throw new MalformedCodeNavigationResponseError("REPOSITORY_DOC search hit missing repo locator fields.");
6489
6734
  }
6490
6735
  }
@@ -6496,7 +6741,7 @@ function renderUnifiedSearchSuccess(payload) {
6496
6741
  lines.push(buildHeader8(payload));
6497
6742
  lines.push("");
6498
6743
  if (payload.results.length === 0) {
6499
- lines.push(payload.completed ? "No hits." : "No hits yet - indexing.");
6744
+ lines.push(payload.completed ? "No hits." : noHitsYetMessage(payload));
6500
6745
  } else {
6501
6746
  appendUnifiedSearchHits(lines, payload.results);
6502
6747
  }
@@ -6509,6 +6754,18 @@ function renderUnifiedSearchSuccess(payload) {
6509
6754
  return lines.join(`
6510
6755
  `);
6511
6756
  }
6757
+ function noHitsYetMessage(payload) {
6758
+ if (payload.completed)
6759
+ return "No hits.";
6760
+ const status = payload.progress?.status;
6761
+ if (status === "TIMEOUT")
6762
+ return "No hits yet - timed out waiting.";
6763
+ if (status === "FAILED")
6764
+ return "No hits - search failed.";
6765
+ if (status === "SEARCHING")
6766
+ return "No hits yet - searching.";
6767
+ return "No hits yet - indexing.";
6768
+ }
6512
6769
  function renderUnifiedSearchError(payload) {
6513
6770
  const lines = [];
6514
6771
  const header = `search${SEP6}ERROR${SEP6}code=${payload.code}${payload.retryable ? `${SEP6}retryable` : ""}`;
@@ -6638,7 +6895,9 @@ function buildTrailer2(payload) {
6638
6895
  lines.push(`More hits available.${nextOffsetHint}`);
6639
6896
  }
6640
6897
  if (!payload.completed && payload.searchRef) {
6641
- lines.push(`Indexing in progress. Call search_status with searchRef=${payload.searchRef} to follow up.`);
6898
+ const status = payload.progress?.status;
6899
+ const action = status === "TIMEOUT" ? "Search timed out waiting for fresh data." : status === "FAILED" ? "Search failed before completion." : status === "SEARCHING" ? "Search in progress." : "Indexing in progress.";
6900
+ lines.push(`${action} Call search_status with searchRef=${payload.searchRef} to follow up.`);
6642
6901
  }
6643
6902
  if (payload.sourceStatus && payload.sourceStatus.length > 0) {
6644
6903
  lines.push("source notes:");
@@ -6669,6 +6928,9 @@ function formatProgressTarget(target) {
6669
6928
  parts.push(`intent=${target.requestedRefKind}`);
6670
6929
  if (target.indexingRef)
6671
6930
  parts.push(`indexingRef=${target.indexingRef}`);
6931
+ for (const note of buildTargetResolutionNotes(target.targetResolution ?? buildResolutionFromRetryCandidates(target))) {
6932
+ parts.push(note);
6933
+ }
6672
6934
  return parts.length > 0 ? parts.join(SEP6) : "target progress unavailable";
6673
6935
  }
6674
6936
  function describeFreshness(value) {
@@ -6686,6 +6948,10 @@ function describeFreshness(value) {
6686
6948
  }
6687
6949
  }
6688
6950
  function formatSourceStatus(entry) {
6951
+ const terminalReason = terminalLifecycleReason(entry);
6952
+ if (terminalReason) {
6953
+ return `${entry.source} (${entry.targetLabel})${SEP6}${terminalReason}`;
6954
+ }
6689
6955
  const parts = [`${entry.source} (${entry.targetLabel})`];
6690
6956
  if (entry.indexingStatus)
6691
6957
  parts.push(`indexing=${entry.indexingStatus}`);
@@ -6705,8 +6971,19 @@ function formatSourceStatus(entry) {
6705
6971
  }
6706
6972
  if (entry.note)
6707
6973
  parts.push(entry.note);
6974
+ for (const note of buildTargetResolutionNotes(entry.targetResolution)) {
6975
+ parts.push(note);
6976
+ }
6708
6977
  return parts.join(SEP6);
6709
6978
  }
6979
+ function terminalLifecycleReason(entry) {
6980
+ const states = Array.from(new Set([entry.indexingStatus, entry.codeIndexState].filter(Boolean)));
6981
+ const terminalStates = states.filter((state) => state !== "INDEXING" && state !== "STALE");
6982
+ if (terminalStates.length === 0)
6983
+ return;
6984
+ const status = terminalStates.join("/");
6985
+ return entry.note ? `${entry.note} (${status})` : `status ${status}`;
6986
+ }
6710
6987
  function quote4(value) {
6711
6988
  return value.includes('"') ? `'${value}'` : `"${value}"`;
6712
6989
  }
@@ -6769,7 +7046,7 @@ function renderUnifiedSearchStatusText(payload) {
6769
7046
  `);
6770
7047
  }
6771
7048
  function buildHeader9(payload) {
6772
- const state = payload.completed ? "complete" : "indexing";
7049
+ const state = payload.completed ? "complete" : payload.progress?.status.toLowerCase() ?? "incomplete";
6773
7050
  const parts = [`search_status${SEP7}${state}`];
6774
7051
  if (payload.searchRef)
6775
7052
  parts.push(`searchRef=${payload.searchRef}`);
@@ -6797,32 +7074,10 @@ function appendResult(lines, result) {
6797
7074
  lines.push("");
6798
7075
  lines.push("source notes:");
6799
7076
  for (const entry of result.sourceStatus) {
6800
- lines.push(` - ${formatSourceStatus2(entry)}`);
7077
+ lines.push(` - ${formatSourceStatus(entry)}`);
6801
7078
  }
6802
7079
  }
6803
7080
  }
6804
- function formatSourceStatus2(entry) {
6805
- const parts = [`${entry.source} (${entry.targetLabel})`];
6806
- if (entry.indexingStatus)
6807
- parts.push(`indexing=${entry.indexingStatus}`);
6808
- if (entry.codeIndexState)
6809
- parts.push(`codeIndex=${entry.codeIndexState}`);
6810
- if (entry.ignoredFilters?.length) {
6811
- parts.push(`ignored=${entry.ignoredFilters.join(",")}`);
6812
- }
6813
- if (entry.incompatibleFilters?.length) {
6814
- parts.push(`incompatible=${entry.incompatibleFilters.join(",")}`);
6815
- }
6816
- if (entry.ignoredQueryFeatures?.length) {
6817
- parts.push(`ignoredQuery=${entry.ignoredQueryFeatures.join(",")}`);
6818
- }
6819
- if (entry.incompatibleQueryFeatures?.length) {
6820
- parts.push(`incompatibleQuery=${entry.incompatibleQueryFeatures.join(",")}`);
6821
- }
6822
- if (entry.note)
6823
- parts.push(entry.note);
6824
- return parts.join(SEP7);
6825
- }
6826
7081
  function formatProgress(progress) {
6827
7082
  const next = progress.next ? `; next: ${progress.next}` : "";
6828
7083
  return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed${next}`;
@@ -7042,6 +7297,9 @@ function buildListFilesSuccessPayload(result, options) {
7042
7297
  envelope.indexedVersion = result.indexedVersion;
7043
7298
  if (result.resolution)
7044
7299
  envelope.resolution = projectResolution2(result.resolution);
7300
+ const targetResolution = projectTargetResolution(result.targetResolution);
7301
+ if (targetResolution)
7302
+ envelope.targetResolution = targetResolution;
7045
7303
  if (result.hint)
7046
7304
  envelope.hint = result.hint;
7047
7305
  const filter = buildFilterBlock2(options);
@@ -7152,6 +7410,7 @@ function formatVerbose2(envelope, options) {
7152
7410
  if (envelope.resolution || envelope.indexedVersion) {
7153
7411
  lines.push(buildResolutionLine(envelope, options));
7154
7412
  }
7413
+ appendTargetResolutionNotes3(lines, envelope, options);
7155
7414
  lines.push("");
7156
7415
  const pathWidth = longestPathLength(envelope.files);
7157
7416
  for (const file of envelope.files) {
@@ -7180,6 +7439,7 @@ function formatEmpty(envelope, options, verbose) {
7180
7439
  if (envelope.resolution || envelope.indexedVersion) {
7181
7440
  lines.push(buildResolutionLine(envelope, options));
7182
7441
  }
7442
+ appendTargetResolutionNotes3(lines, envelope, options);
7183
7443
  lines.push("");
7184
7444
  lines.push(dim(hint, options.useColors));
7185
7445
  lines.push("");
@@ -7202,6 +7462,11 @@ function buildResolutionLine(envelope, options) {
7202
7462
  parts.push(`commit ${commit.slice(0, 7)}`);
7203
7463
  return dim(parts.join(" · "), options.useColors);
7204
7464
  }
7465
+ function appendTargetResolutionNotes3(lines, envelope, options) {
7466
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
7467
+ for (const note of notes)
7468
+ lines.push(dim(note, options.useColors));
7469
+ }
7205
7470
  function buildIdentityLabel(envelope) {
7206
7471
  if (envelope.registry && envelope.name) {
7207
7472
  return `${envelope.name} · ${envelope.registry}`;
@@ -7251,13 +7516,10 @@ function resolveCliCodeNavTarget(spec, options) {
7251
7516
  const hasRepoUrl = Boolean(options.repoUrl);
7252
7517
  const hasGitRef = Boolean(options.gitRef);
7253
7518
  if (hasSpec && (hasRepoUrl || hasGitRef)) {
7254
- throw new InvalidPackageSpecError("Provide either a package spec (e.g. `npm:express`) or `--repo-url` + `--git-ref`, not both.");
7519
+ throw new InvalidPackageSpecError("Provide either a package spec (e.g. `npm:express`) or `--repo-url` with optional `--git-ref`, not both.");
7255
7520
  }
7256
7521
  if (!hasSpec && !hasRepoUrl) {
7257
- throw new InvalidPackageSpecError("A package spec (e.g. `npm:express`) or `--repo-url` + `--git-ref` is required.");
7258
- }
7259
- if (hasRepoUrl && !hasGitRef) {
7260
- throw new InvalidPackageSpecError("`--repo-url` requires `--git-ref` for code files/read/grep (a tag, branch, commit, or `HEAD`).");
7522
+ throw new InvalidPackageSpecError("A package spec (e.g. `npm:express`) or `--repo-url` is required.");
7261
7523
  }
7262
7524
  if (hasSpec) {
7263
7525
  const parsed = parsePackageSpec(spec);
@@ -7301,6 +7563,13 @@ function formatIndexingError(mapped) {
7301
7563
  const suffix = more > 0 ? ` (+${more} more)` : "";
7302
7564
  lines.push(` already-indexed versions: ${shown}${suffix}`);
7303
7565
  }
7566
+ const refs = detail.availableRefs;
7567
+ if (refs && refs.length > 0) {
7568
+ const shown = refs.slice(0, 5).map((entry) => entry.ref).join(", ");
7569
+ const more = refs.length - 5;
7570
+ const suffix = more > 0 ? ` (+${more} more)` : "";
7571
+ lines.push(` already-indexed refs: ${shown}${suffix}`);
7572
+ }
7304
7573
  return lines.join(`
7305
7574
  `);
7306
7575
  }
@@ -7439,7 +7708,7 @@ function resolvePositionals(firstArg, secondArg, hasRepoUrl) {
7439
7708
  throw new InvalidPackageSpecError("In --repo-url mode, pass only [path-prefix] — the package spec is replaced by --repo-url.");
7440
7709
  }
7441
7710
  if (firstArg && REGISTRY_SPEC_HINT.test(firstArg)) {
7442
- throw new InvalidPackageSpecError(`'${firstArg}' looks like a package spec. Provide either a package spec or \`--repo-url\` + \`--git-ref\`, not both.`);
7711
+ throw new InvalidPackageSpecError(`'${firstArg}' looks like a package spec. Provide either a package spec or \`--repo-url\` with optional \`--git-ref\`, not both.`);
7443
7712
  }
7444
7713
  return { spec: undefined, pathPrefix: firstArg };
7445
7714
  }
@@ -7467,7 +7736,7 @@ On an INDEXING response, the dependency is being indexed on-demand
7467
7736
  — retry with a longer --wait (up to 60000 ms) or pick one of the
7468
7737
  already-indexed versions surfaced in the error detail.`;
7469
7738
  function registerCodeFilesCommand(pkgCommand) {
7470
- return pkgCommand.command("files").summary("List files in an indexed dependency").description(PKG_FILES_DESCRIPTION).argument("[spec-or-prefix]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the path-prefix.").argument("[path-prefix]", "Spec mode only: literal directory prefix (not a glob). 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 selector").option("--glob <glob>", "Glob selector (repeatable)", collectRepeatable).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable).option("--file-type <type>", "File type filter such as source or doc (repeatable)", collectRepeatable).option("--language <language>", "Language filter matching aigrep language names (repeatable)", collectRepeatable).option("--file-intent <intent>", "Inclusive file-intent filter. Repeat to include multiple intents: production, test, benchmark, example, generated, fixture, build, vendor", collectRepeatable).option("--exclude-intent <intent>", "Exclude these file intents after inclusive filtering (repeatable)", collectRepeatable).option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--hidden", "Include dotfiles and dot-prefixed paths").option("--limit <n>", "Max entries (1-1000, default 200)").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Annotate each path with language / file-type / byte size").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, options) => {
7739
+ return pkgCommand.command("files").summary("List files in an indexed dependency").description(PKG_FILES_DESCRIPTION).argument("[spec-or-prefix]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the path-prefix.").argument("[path-prefix]", "Spec mode only: literal directory prefix (not a glob). Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --repo-url.").option("--path <path>", "Exact file selector").option("--glob <glob>", "Glob selector (repeatable)", collectRepeatable).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable).option("--file-type <type>", "File type filter such as source or doc (repeatable)", collectRepeatable).option("--language <language>", "Language filter matching aigrep language names (repeatable)", collectRepeatable).option("--file-intent <intent>", "Inclusive file-intent filter. Repeat to include multiple intents: production, test, benchmark, example, generated, fixture, build, vendor", collectRepeatable).option("--exclude-intent <intent>", "Exclude these file intents after inclusive filtering (repeatable)", collectRepeatable).option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--hidden", "Include dotfiles and dot-prefixed paths").option("--limit <n>", "Max entries (1-1000, default 200)").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Annotate each path with language / file-type / byte size").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, options) => {
7471
7740
  const deps = await createContainer();
7472
7741
  await pkgFilesAction(arg1, arg2, options, {
7473
7742
  codeNavigationService: deps.codeNavigationService,
@@ -7544,7 +7813,7 @@ async function pkgGrepAction(first, second, third, options, deps) {
7544
7813
  if (options.json) {
7545
7814
  console.log(JSON.stringify(payload));
7546
7815
  if (payload.totalMatches === 0)
7547
- process.exit(1);
7816
+ process.exitCode = 1;
7548
7817
  return;
7549
7818
  }
7550
7819
  const rendered = formatGrepRepoTerminal(payload, {
@@ -7557,7 +7826,7 @@ async function pkgGrepAction(first, second, third, options, deps) {
7557
7826
  if (rendered.stderr)
7558
7827
  process.stderr.write(rendered.stderr);
7559
7828
  if (payload.totalMatches === 0)
7560
- process.exit(1);
7829
+ process.exitCode = 1;
7561
7830
  } catch (error2) {
7562
7831
  handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint, 2);
7563
7832
  }
@@ -7570,7 +7839,7 @@ function resolvePositionals2(first, second, third, hasRepoUrl) {
7570
7839
  return { spec: undefined, pattern: first, pathPrefix: second };
7571
7840
  }
7572
7841
  if (first !== undefined && second === undefined) {
7573
- throw new InvalidPackageSpecError("In spec mode, pass at least <spec> <pattern>. If you meant to target a repository instead, pass --repo-url <url> --git-ref <ref>.");
7842
+ throw new InvalidPackageSpecError("In spec mode, pass at least <spec> <pattern>. If you meant to target a repository instead, pass --repo-url <url> with optional --git-ref <ref>.");
7574
7843
  }
7575
7844
  return { spec: first, pattern: second, pathPrefix: third };
7576
7845
  }
@@ -7595,7 +7864,7 @@ var PKG_GREP_DESCRIPTION = `Deterministic text grep over indexed dependency and
7595
7864
  ${CLI_GREP_PATTERN_NOTE}
7596
7865
  Use \`githits search\` for discovery; use \`githits code grep\` when you know the text or regex to match.
7597
7866
 
7598
- Addressing: <spec> (registry:name[@version]) OR --repo-url <url> --git-ref <ref>.
7867
+ Addressing: <spec> (registry:name[@version]) OR --repo-url <url> [--git-ref <ref>].
7599
7868
  Omitted version means latest release.
7600
7869
  In spec mode pass <spec> <pattern> [path-prefix]; in repo-URL mode pass only <pattern> [path-prefix].
7601
7870
 
@@ -7610,8 +7879,8 @@ for context, --verbose for grouped output, and --cursor to continue a paginated
7610
7879
  grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
7611
7880
  match in --verbose output; full payload in --json).`;
7612
7881
  function registerCodeGrepCommand(pkgCommand) {
7613
- 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) => {
7614
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
7882
+ 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 (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --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) => {
7883
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2fdxv7pe.js");
7615
7884
  const deps = await createContainer2();
7616
7885
  await pkgGrepAction(arg1, arg2, arg3, options, {
7617
7886
  codeNavigationService: deps.codeNavigationService,
@@ -7845,7 +8114,7 @@ Binary files show a one-line sentinel instead of content. When a
7845
8114
  path is missing, the response is a FILE_NOT_FOUND error — use
7846
8115
  \`code files\` to discover available paths.`;
7847
8116
  function registerCodeReadCommand(pkgCommand) {
7848
- return pkgCommand.command("read").summary("Read a file from an indexed dependency").description(PKG_READ_DESCRIPTION).argument("[spec-or-path]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the file path. See examples in `--help`.").argument("[path]", "File path (spec mode only — in --repo-url mode use the first positional).").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--lines <start-end>", "Line range (e.g. `10-40`, `10-` for open end, `-40` for open start)").option("--start <n>", "Starting line (1-indexed). Alternative to --lines.").option("--end <n>", "Ending line (inclusive). Alternative to --lines.").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render a header and a line-number gutter alongside the content").option("--json", "Emit the JSON envelope").action(async (spec, path, options) => {
8117
+ return pkgCommand.command("read").summary("Read a file from an indexed dependency").description(PKG_READ_DESCRIPTION).argument("[spec-or-path]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the file path. See examples in `--help`.").argument("[path]", "File path (spec mode only — in --repo-url mode use the first positional).").option("--repo-url <url>", "Repository URL addressing (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --repo-url.").option("--lines <start-end>", "Line range (e.g. `10-40`, `10-` for open end, `-40` for open start)").option("--start <n>", "Starting line (1-indexed). Alternative to --lines.").option("--end <n>", "Ending line (inclusive). Alternative to --lines.").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render a header and a line-number gutter alongside the content").option("--json", "Emit the JSON envelope").action(async (spec, path, options) => {
7849
8118
  const deps = await createContainer();
7850
8119
  await pkgReadAction(spec, path, options, {
7851
8120
  codeNavigationService: deps.codeNavigationService,
@@ -7862,7 +8131,7 @@ async function registerCodeCommandGroup(program, options = {}) {
7862
8131
  if (!registration.shouldRegister) {
7863
8132
  return;
7864
8133
  }
7865
- const codeCommand = program.command("code").summary("Source-level operations on indexed dependencies").description("List files, read files, and grep substrings inside indexed dependency source. Every command accepts either `<spec>` (registry:name[@version]) or `--repo-url <url> --git-ref <ref>`. Omitted versions use the latest release. For repo default-branch discovery without refs use `githits search`; for package-level metadata use `githits pkg`.");
8134
+ const codeCommand = program.command("code").summary("Source-level operations on indexed dependencies").description("List files, read files, and grep substrings inside indexed dependency source. Every command accepts either `<spec>` (registry:name[@version]) or `--repo-url <url> [--git-ref <ref>]`. Omitted package versions use the latest release; omitted repo refs use the default-branch intent. For package-level metadata use `githits pkg`.");
7866
8135
  registerCodeFilesCommand(codeCommand);
7867
8136
  registerCodeReadCommand(codeCommand);
7868
8137
  registerCodeGrepCommand(codeCommand);
@@ -8071,7 +8340,7 @@ function registerExampleCommand(program) {
8071
8340
  });
8072
8341
  }
8073
8342
  async function loadContainer() {
8074
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
8343
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2fdxv7pe.js");
8075
8344
  return createContainer2();
8076
8345
  }
8077
8346
  // src/commands/feedback.ts
@@ -8363,6 +8632,10 @@ function removeServerConfig(existingContent, serversKey, serverName) {
8363
8632
  };
8364
8633
  }
8365
8634
  function formatSetupPreview(config) {
8635
+ if (config.method === "composite") {
8636
+ return config.steps.map((step) => formatSetupPreview(step)).join(`
8637
+ `);
8638
+ }
8366
8639
  if (config.method === "cli") {
8367
8640
  return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
8368
8641
  `);
@@ -8373,6 +8646,10 @@ function formatSetupPreview(config) {
8373
8646
  ${snippet}`;
8374
8647
  }
8375
8648
  function formatUninstallPreview(config) {
8649
+ if (config.method === "composite") {
8650
+ return config.steps.map(({ step }) => formatUninstallPreview(step)).join(`
8651
+ `);
8652
+ }
8376
8653
  if (config.method === "cli") {
8377
8654
  return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
8378
8655
  `);
@@ -8433,6 +8710,26 @@ async function getConfigUninstallCheckStatus(config, fs) {
8433
8710
  };
8434
8711
  }
8435
8712
  }
8713
+ async function isSetupAlreadyConfigured(config, fs, execService) {
8714
+ if (config.method === "config-file") {
8715
+ return isAlreadyConfigured(config, fs);
8716
+ }
8717
+ if (config.method === "cli") {
8718
+ if (!config.checkCommand) {
8719
+ return false;
8720
+ }
8721
+ return isCliAlreadyConfigured(config.checkCommand, execService);
8722
+ }
8723
+ for (const step of config.steps) {
8724
+ if (!await isSetupAlreadyConfigured(step, fs, execService)) {
8725
+ return false;
8726
+ }
8727
+ }
8728
+ return true;
8729
+ }
8730
+ async function isCliAlreadyConfigured(check, execService) {
8731
+ return await getCliCheckStatus(check, execService) === "configured";
8732
+ }
8436
8733
  async function getCliCheckStatus(check, execService) {
8437
8734
  try {
8438
8735
  const result = await execService.exec(check.command, check.args);
@@ -8463,6 +8760,7 @@ var ALREADY_EXISTS_PATTERNS = [
8463
8760
  var ALREADY_ABSENT_PATTERNS = [
8464
8761
  /(?:plugin|extension|server|mcp server)\s+["']?githits["']?\s+(?:was\s+)?not\s+found/i,
8465
8762
  /["']?githits["']?\s+(?:plugin|extension|server)?\s*(?:does\s+not\s+exist|is\s+not\s+installed|not\s+installed)/i,
8763
+ /(?:package\s+)?["']?pi-mcp-adapter["']?\s+(?:is\s+)?not\s+installed/i,
8466
8764
  /unknown\s+(?:plugin|extension|server)\s+["']?githits["']?/i,
8467
8765
  /marketplace\s+["']?githits-plugins["']?\s+(?:was\s+)?not\s+found/i
8468
8766
  ];
@@ -8563,8 +8861,7 @@ async function executeCliUninstall(uninstall, execService) {
8563
8861
  let anyRemoved = false;
8564
8862
  let anyNotConfigured = false;
8565
8863
  const warnings = [];
8566
- for (let index = 0;index < uninstall.commands.length; index += 1) {
8567
- const cmd = uninstall.commands[index];
8864
+ for (const cmd of uninstall.commands) {
8568
8865
  const result = await executeCliUninstallCommand(cmd, execService);
8569
8866
  if (result.status === "failed") {
8570
8867
  if (anyRemoved) {
@@ -8599,6 +8896,49 @@ async function executeCliUninstall(uninstall, execService) {
8599
8896
  }
8600
8897
  return { status: "removed", message: "Removed successfully" };
8601
8898
  }
8899
+ async function executeCompositeUninstall(uninstall, fs, execService) {
8900
+ let anyRemoved = false;
8901
+ let anyNotConfigured = false;
8902
+ const warnings = [];
8903
+ for (const { step, failureMode } of uninstall.steps) {
8904
+ const result = await executeUninstallStep(step, fs, execService);
8905
+ if (result.status === "removed") {
8906
+ anyRemoved = true;
8907
+ warnings.push(...result.warnings ?? []);
8908
+ continue;
8909
+ }
8910
+ if (result.status === "not_configured") {
8911
+ if (anyRemoved) {
8912
+ warnings.push(result.message);
8913
+ } else {
8914
+ anyNotConfigured = true;
8915
+ }
8916
+ continue;
8917
+ }
8918
+ if (failureMode === "best-effort" && anyRemoved) {
8919
+ warnings.push(result.message);
8920
+ continue;
8921
+ }
8922
+ return result;
8923
+ }
8924
+ if (anyRemoved) {
8925
+ return {
8926
+ status: "removed",
8927
+ message: "Removed successfully",
8928
+ warnings: warnings.length > 0 ? warnings : undefined
8929
+ };
8930
+ }
8931
+ if (anyNotConfigured) {
8932
+ return {
8933
+ status: "not_configured",
8934
+ message: "GitHits not configured"
8935
+ };
8936
+ }
8937
+ return { status: "not_configured", message: "GitHits not configured" };
8938
+ }
8939
+ async function executeUninstallStep(step, fs, execService) {
8940
+ return step.method === "cli" ? executeCliUninstall(step, execService) : executeConfigFileUninstall(step, fs);
8941
+ }
8602
8942
  async function executeConfigFileSetup(setup, fs) {
8603
8943
  try {
8604
8944
  const parentDir = fs.getDirname(setup.configPath);
@@ -8642,6 +8982,26 @@ async function executeConfigFileSetup(setup, fs) {
8642
8982
  };
8643
8983
  }
8644
8984
  }
8985
+ async function executeCompositeSetup(setup, fs, execService) {
8986
+ let executedAny = false;
8987
+ for (const step of setup.steps) {
8988
+ if (await isSetupAlreadyConfigured(step, fs, execService)) {
8989
+ continue;
8990
+ }
8991
+ executedAny = true;
8992
+ const result = step.method === "cli" ? await executeCliSetup(step, execService) : await executeConfigFileSetup(step, fs);
8993
+ if (result.status === "failed") {
8994
+ return result;
8995
+ }
8996
+ }
8997
+ if (!executedAny) {
8998
+ return {
8999
+ status: "already_configured",
9000
+ message: "GitHits already configured"
9001
+ };
9002
+ }
9003
+ return { status: "success", message: "Configured successfully" };
9004
+ }
8645
9005
  async function executeConfigFileUninstall(setup, fs) {
8646
9006
  try {
8647
9007
  let existingContent = "";
@@ -8728,6 +9088,25 @@ function getOpenCodeConfigDir(fs) {
8728
9088
  }
8729
9089
  return fs.joinPath(fs.getHomeDir(), ".config", "opencode");
8730
9090
  }
9091
+ function expandHomePath(fs, path) {
9092
+ if (path === "~") {
9093
+ return fs.getHomeDir();
9094
+ }
9095
+ if (path.startsWith("~/")) {
9096
+ return fs.joinPath(fs.getHomeDir(), path.slice(2));
9097
+ }
9098
+ return path;
9099
+ }
9100
+ function getPiAgentDir(fs) {
9101
+ const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
9102
+ if (configuredDir) {
9103
+ return expandHomePath(fs, configuredDir);
9104
+ }
9105
+ return fs.joinPath(fs.getHomeDir(), ".pi", "agent");
9106
+ }
9107
+ function getPiMcpConfigPath(fs) {
9108
+ return fs.joinPath(getPiAgentDir(fs), "mcp.json");
9109
+ }
8731
9110
  function getOpenCodeDesktopDetectPaths(fs) {
8732
9111
  const userDataRoot = getUserDataRoot(fs);
8733
9112
  return [
@@ -8746,6 +9125,57 @@ async function isExecutableAvailable(exec, executable) {
8746
9125
  return false;
8747
9126
  }
8748
9127
  }
9128
+ async function resolveExecutableFromPath(exec, executable) {
9129
+ return isExecutableAvailable(exec, executable);
9130
+ }
9131
+ var PI_GLOBAL_BIN_PROBES = [
9132
+ { command: "npm", args: ["prefix", "-g"], output: "prefix" },
9133
+ { command: "pnpm", args: ["bin", "-g"], output: "binDir" },
9134
+ { command: "bun", args: ["pm", "bin", "-g"], output: "binDir" }
9135
+ ];
9136
+ var PI_ADAPTER_CONFIGURED_PATTERN = /(?:^|\s|:)(?:npm:)?pi-mcp-adapter(?:[\s@:]|$)/i;
9137
+ function getPiExecutableNames() {
9138
+ return process.platform === "win32" ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
9139
+ }
9140
+ async function runGlobalBinProbe(exec, probe) {
9141
+ try {
9142
+ const result = await exec.exec(probe.command, [...probe.args]);
9143
+ if (result.exitCode !== 0) {
9144
+ return null;
9145
+ }
9146
+ const probePath = result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
9147
+ if (!probePath) {
9148
+ return null;
9149
+ }
9150
+ if (probe.output === "prefix" && process.platform !== "win32") {
9151
+ return fsJoinPathLike(probePath, "bin");
9152
+ }
9153
+ return probePath;
9154
+ } catch {
9155
+ return null;
9156
+ }
9157
+ }
9158
+ function fsJoinPathLike(base, child) {
9159
+ return base.endsWith("/") ? `${base}${child}` : `${base}/${child}`;
9160
+ }
9161
+ async function detectPiExecutable(exec, fs) {
9162
+ if (await resolveExecutableFromPath(exec, "pi")) {
9163
+ return { command: "pi" };
9164
+ }
9165
+ for (const probe of PI_GLOBAL_BIN_PROBES) {
9166
+ const binDir = await runGlobalBinProbe(exec, probe);
9167
+ if (!binDir) {
9168
+ continue;
9169
+ }
9170
+ for (const executableName of getPiExecutableNames()) {
9171
+ const candidate = fs.joinPath(binDir, executableName);
9172
+ if (await fs.exists(candidate)) {
9173
+ return { command: candidate };
9174
+ }
9175
+ }
9176
+ }
9177
+ return null;
9178
+ }
8749
9179
  var claudeCode = {
8750
9180
  name: "Claude Code",
8751
9181
  id: "claude-code",
@@ -8885,6 +9315,76 @@ var codexCli = {
8885
9315
  ]
8886
9316
  })
8887
9317
  };
9318
+ var pi = {
9319
+ name: "Pi",
9320
+ id: "pi",
9321
+ detectionMethod: "binary",
9322
+ setupMethod: "composite",
9323
+ detectCommand: detectPiExecutable,
9324
+ getSetupConfig: (fs, context) => {
9325
+ const piCommand = context?.command ?? "pi";
9326
+ return {
9327
+ method: "composite",
9328
+ steps: [
9329
+ {
9330
+ method: "cli",
9331
+ commands: [
9332
+ {
9333
+ command: piCommand,
9334
+ args: ["install", "npm:pi-mcp-adapter"]
9335
+ }
9336
+ ],
9337
+ checkCommand: {
9338
+ command: piCommand,
9339
+ args: ["list"],
9340
+ configuredPattern: PI_ADAPTER_CONFIGURED_PATTERN
9341
+ }
9342
+ },
9343
+ {
9344
+ method: "config-file",
9345
+ configPath: getPiMcpConfigPath(fs),
9346
+ serversKey: "mcpServers",
9347
+ serverName: GITHITS_SERVER_NAME,
9348
+ serverConfig: {
9349
+ command: GITHITS_MCP_COMMAND,
9350
+ args: [...GITHITS_MCP_ARGS],
9351
+ lifecycle: "eager"
9352
+ }
9353
+ }
9354
+ ]
9355
+ };
9356
+ },
9357
+ getUninstallConfig: (fs, context) => {
9358
+ const piCommand = context?.command ?? "pi";
9359
+ return {
9360
+ method: "composite",
9361
+ steps: [
9362
+ {
9363
+ failureMode: "required",
9364
+ step: {
9365
+ method: "config-file",
9366
+ configPath: getPiMcpConfigPath(fs),
9367
+ serversKey: "mcpServers",
9368
+ serverName: GITHITS_SERVER_NAME,
9369
+ serverConfig: {}
9370
+ }
9371
+ },
9372
+ {
9373
+ failureMode: "required",
9374
+ step: {
9375
+ method: "cli",
9376
+ commands: [
9377
+ {
9378
+ command: piCommand,
9379
+ args: ["remove", "npm:pi-mcp-adapter"]
9380
+ }
9381
+ ]
9382
+ }
9383
+ }
9384
+ ]
9385
+ };
9386
+ }
9387
+ };
8888
9388
  var vscode = {
8889
9389
  name: "VS Code / Copilot",
8890
9390
  id: "vscode",
@@ -9009,6 +9509,7 @@ var agentDefinitions = [
9009
9509
  cline,
9010
9510
  claudeDesktop,
9011
9511
  codexCli,
9512
+ pi,
9012
9513
  geminiCli,
9013
9514
  googleAntigravity,
9014
9515
  openCode
@@ -9021,7 +9522,18 @@ async function scanAgents(definitions, fs, execService) {
9021
9522
  };
9022
9523
  for (const agent of definitions) {
9023
9524
  let detected = false;
9024
- if (agent.detectionMethod === "binary" && agent.detectBinary) {
9525
+ let setupContext;
9526
+ if (agent.detectCommand) {
9527
+ try {
9528
+ const resolvedCommand = await agent.detectCommand(execService, fs);
9529
+ if (resolvedCommand) {
9530
+ detected = true;
9531
+ setupContext = { command: resolvedCommand.command };
9532
+ }
9533
+ } catch {
9534
+ detected = false;
9535
+ }
9536
+ } else if (agent.detectionMethod === "binary" && agent.detectBinary) {
9025
9537
  try {
9026
9538
  detected = await agent.detectBinary(execService);
9027
9539
  } catch {
@@ -9060,31 +9572,31 @@ async function scanAgents(definitions, fs, execService) {
9060
9572
  result.notDetected.push(agent);
9061
9573
  continue;
9062
9574
  }
9063
- if (agent.setupMethod === "config-file") {
9064
- const config = agent.getSetupConfig(fs);
9065
- if (config.method === "config-file" && await isAlreadyConfigured(config, fs)) {
9066
- result.alreadyConfigured.push(agent);
9067
- } else {
9068
- result.needsSetup.push(agent);
9069
- }
9070
- } else {
9071
- const config = agent.getSetupConfig(fs);
9072
- if (config.method === "cli" && config.checkCommand) {
9575
+ const config = agent.getSetupConfig(fs, setupContext);
9576
+ const scannedAgent = {
9577
+ ...agent,
9578
+ resolvedSetupConfig: config,
9579
+ resolvedSetupContext: setupContext
9580
+ };
9581
+ if (agent.id === "gemini-cli" && config.method === "cli") {
9582
+ if (config.checkCommand) {
9073
9583
  const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9074
9584
  let configured = checkStatus === "configured";
9075
- if (!configured && agent.id === "gemini-cli") {
9076
- if (checkStatus === "probe_failed") {
9077
- configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9078
- }
9585
+ if (!configured && checkStatus === "probe_failed") {
9586
+ configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9079
9587
  }
9080
9588
  if (configured) {
9081
- result.alreadyConfigured.push(agent);
9589
+ result.alreadyConfigured.push(scannedAgent);
9082
9590
  } else {
9083
- result.needsSetup.push(agent);
9591
+ result.needsSetup.push(scannedAgent);
9084
9592
  }
9085
9593
  } else {
9086
- result.needsSetup.push(agent);
9594
+ result.needsSetup.push(scannedAgent);
9087
9595
  }
9596
+ } else if (await isSetupAlreadyConfigured(config, fs, execService)) {
9597
+ result.alreadyConfigured.push(scannedAgent);
9598
+ } else {
9599
+ result.needsSetup.push(scannedAgent);
9088
9600
  }
9089
9601
  }
9090
9602
  return result;
@@ -9094,6 +9606,20 @@ class ExitPromptError extends Error {
9094
9606
  name = "ExitPromptError";
9095
9607
  }
9096
9608
  // src/commands/init/init.ts
9609
+ function getResolvedSetupConfig(agent, fileSystemService) {
9610
+ return agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService, agent.resolvedSetupContext);
9611
+ }
9612
+ function getPiConfigFileUninstall(agent, fileSystemService) {
9613
+ if (agent.id !== "pi") {
9614
+ return null;
9615
+ }
9616
+ const uninstallConfig = agent.getUninstallConfig?.(fileSystemService);
9617
+ if (uninstallConfig?.method !== "composite") {
9618
+ return null;
9619
+ }
9620
+ const configStep = uninstallConfig.steps.map(({ step }) => step).find((step) => step.method === "config-file");
9621
+ return configStep ?? null;
9622
+ }
9097
9623
  function printReadyNextSteps() {
9098
9624
  console.log(" Setup complete. You're ready to use GitHits.");
9099
9625
  console.log();
@@ -9135,28 +9661,54 @@ async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
9135
9661
  message: `${agent.name} verification failed: still configured after uninstall.`
9136
9662
  };
9137
9663
  }
9138
- async function scanCliAgentForUninstall(agent, fileSystemService, execService) {
9139
- const config = agent.getSetupConfig(fileSystemService);
9140
- if (config.method !== "cli" || !config.checkCommand) {
9664
+ async function inspectSetupForUninstall(agent, config, fileSystemService, execService) {
9665
+ if (config.method === "config-file") {
9666
+ const check = await getConfigUninstallCheckStatus(config, fileSystemService);
9667
+ if (check.status === "configured")
9668
+ return "configured";
9669
+ if (check.status === "not_configured")
9670
+ return "not_configured";
9671
+ return { status: "failed", message: check.message };
9672
+ }
9673
+ if (config.method === "cli") {
9674
+ if (!config.checkCommand) {
9675
+ return {
9676
+ status: "failed",
9677
+ message: `${agent.name} does not have a verified uninstall check command.`
9678
+ };
9679
+ }
9680
+ const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9681
+ if (checkStatus === "configured")
9682
+ return "configured";
9683
+ if (checkStatus === "not_configured")
9684
+ return "not_configured";
9685
+ if (agent.id === "gemini-cli" && await isGeminiExtensionInstalledFromFilesystem(fileSystemService)) {
9686
+ return "configured";
9687
+ }
9141
9688
  return {
9142
9689
  status: "failed",
9143
- message: `${agent.name} does not have a verified uninstall check command.`
9690
+ message: `Cannot inspect ${agent.name}: ${config.checkCommand.command} ${config.checkCommand.args.join(" ")} failed.`
9144
9691
  };
9145
9692
  }
9146
- const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9147
- if (checkStatus === "configured") {
9148
- return "configured";
9149
- }
9150
- if (checkStatus === "not_configured") {
9151
- return "not_configured";
9693
+ const accumulated = {
9694
+ configured: false,
9695
+ notConfigured: false
9696
+ };
9697
+ for (const step of config.steps) {
9698
+ const check = await inspectSetupForUninstall(agent, step, fileSystemService, execService);
9699
+ if (check === "configured") {
9700
+ accumulated.configured = true;
9701
+ } else if (check === "not_configured") {
9702
+ accumulated.notConfigured = true;
9703
+ } else {
9704
+ accumulated.failure = check;
9705
+ }
9152
9706
  }
9153
- if (agent.id === "gemini-cli" && await isGeminiExtensionInstalledFromFilesystem(fileSystemService)) {
9707
+ if (accumulated.failure)
9708
+ return accumulated.failure;
9709
+ if (accumulated.configured)
9154
9710
  return "configured";
9155
- }
9156
- return {
9157
- status: "failed",
9158
- message: `Cannot inspect ${agent.name}: ${config.checkCommand.command} ${config.checkCommand.args.join(" ")} failed.`
9159
- };
9711
+ return "not_configured";
9160
9712
  }
9161
9713
  async function scanAgentsForUninstall(fileSystemService, execService) {
9162
9714
  const setupScan = await scanAgents(agentDefinitions, fileSystemService, execService);
@@ -9170,35 +9722,41 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
9170
9722
  ...setupScan.alreadyConfigured,
9171
9723
  ...setupScan.needsSetup
9172
9724
  ]) {
9173
- const config = agent.getSetupConfig(fileSystemService);
9174
- if (config.method === "config-file") {
9175
- const check = await getConfigUninstallCheckStatus(config, fileSystemService);
9176
- if (check.status === "configured") {
9177
- result.configured.push(agent);
9178
- } else if (check.status === "failed") {
9179
- result.failed.push({
9180
- id: agent.id,
9181
- name: agent.name,
9182
- status: "failed",
9183
- message: check.message
9184
- });
9185
- } else {
9186
- result.notConfigured.push(agent);
9187
- }
9725
+ const config = getResolvedSetupConfig(agent, fileSystemService);
9726
+ const check = await inspectSetupForUninstall(agent, config, fileSystemService, execService);
9727
+ if (check === "configured") {
9728
+ result.configured.push(agent);
9729
+ } else if (check === "not_configured") {
9730
+ result.notConfigured.push(agent);
9188
9731
  } else {
9189
- const check = await scanCliAgentForUninstall(agent, fileSystemService, execService);
9190
- if (check === "configured") {
9191
- result.configured.push(agent);
9192
- } else if (check === "not_configured") {
9193
- result.notConfigured.push(agent);
9194
- } else {
9195
- result.failed.push({
9196
- id: agent.id,
9197
- name: agent.name,
9198
- status: "failed",
9199
- message: check.message
9200
- });
9201
- }
9732
+ result.failed.push({
9733
+ id: agent.id,
9734
+ name: agent.name,
9735
+ status: "failed",
9736
+ message: check.message
9737
+ });
9738
+ }
9739
+ }
9740
+ for (const agent of setupScan.notDetected) {
9741
+ const piConfigUninstall = getPiConfigFileUninstall(agent, fileSystemService);
9742
+ if (!piConfigUninstall) {
9743
+ continue;
9744
+ }
9745
+ const check = await getConfigUninstallCheckStatus(piConfigUninstall, fileSystemService);
9746
+ if (check.status === "configured") {
9747
+ result.configured.push({
9748
+ ...agent,
9749
+ resolvedUninstallConfig: piConfigUninstall,
9750
+ skipUninstallVerification: true
9751
+ });
9752
+ result.notDetected = result.notDetected.filter((a) => a.id !== agent.id);
9753
+ } else if (check.status === "failed") {
9754
+ result.failed.push({
9755
+ id: agent.id,
9756
+ name: agent.name,
9757
+ status: "failed",
9758
+ message: check.message
9759
+ });
9202
9760
  }
9203
9761
  }
9204
9762
  return result;
@@ -9216,7 +9774,7 @@ async function initAction(options, deps) {
9216
9774
  let loginResult;
9217
9775
  try {
9218
9776
  const loginDeps = await createLoginDeps();
9219
- loginResult = await loginFlow({}, loginDeps);
9777
+ loginResult = loginDeps.hasValidToken ? { status: "already_authenticated", message: "Already logged in." } : await loginFlow({}, loginDeps);
9220
9778
  } catch (error2) {
9221
9779
  const msg = error2 instanceof Error ? error2.message : String(error2);
9222
9780
  loginResult = { status: "failed", message: msg };
@@ -9288,7 +9846,7 @@ async function initAction(options, deps) {
9288
9846
  for (const agent of toSetup) {
9289
9847
  console.log(` Setting up ${colorize(agent.name, "bold", useColors)}...
9290
9848
  `);
9291
- const config = agent.getSetupConfig(fileSystemService);
9849
+ const config = agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService);
9292
9850
  const preview2 = formatSetupPreview(config);
9293
9851
  for (const line of preview2.split(`
9294
9852
  `)) {
@@ -9317,7 +9875,7 @@ async function initAction(options, deps) {
9317
9875
  alwaysMode = true;
9318
9876
  }
9319
9877
  }
9320
- let result = config.method === "cli" ? await executeCliSetup(config, execService) : await executeConfigFileSetup(config, fileSystemService);
9878
+ let result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
9321
9879
  if (result.status === "success" || result.status === "already_configured") {
9322
9880
  const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
9323
9881
  if (!verification.ok) {
@@ -9401,8 +9959,8 @@ async function initUninstallAction(options, deps) {
9401
9959
  for (const agent of scan.configured) {
9402
9960
  console.log(` Uninstalling from ${colorize(agent.name, "bold", useColors)}...
9403
9961
  `);
9404
- const setupConfig = agent.getSetupConfig(fileSystemService);
9405
- const uninstallConfig = setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService);
9962
+ const setupConfig = getResolvedSetupConfig(agent, fileSystemService);
9963
+ const uninstallConfig = agent.resolvedUninstallConfig ?? (setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService, agent.resolvedSetupContext));
9406
9964
  if (!uninstallConfig) {
9407
9965
  outcomes.push({
9408
9966
  id: agent.id,
@@ -9442,8 +10000,8 @@ async function initUninstallAction(options, deps) {
9442
10000
  alwaysMode = true;
9443
10001
  }
9444
10002
  }
9445
- let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : await executeConfigFileUninstall(uninstallConfig, fileSystemService);
9446
- if (result.status === "removed") {
10003
+ let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : uninstallConfig.method === "config-file" ? await executeConfigFileUninstall(uninstallConfig, fileSystemService) : await executeCompositeUninstall(uninstallConfig, fileSystemService, execService);
10004
+ if (result.status === "removed" && !agent.skipUninstallVerification) {
9447
10005
  const verification = await verifyAgentUnconfigured(agent, fileSystemService, execService);
9448
10006
  if (!verification.ok) {
9449
10007
  result = {
@@ -9523,10 +10081,11 @@ var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
9523
10081
 
9524
10082
  Authenticates with your GitHits account, then scans for available agents
9525
10083
  (Claude Code, Cursor, Windsurf, VS Code, Cline, Claude Desktop, Codex CLI,
9526
- Gemini CLI, Google Antigravity), checks which are already configured,
10084
+ Pi, Gemini CLI, Google Antigravity), checks which are already configured,
9527
10085
  and sets up unconfigured ones with your confirmation.
9528
10086
 
9529
- Supports CLI-based setup (Claude Code, Codex, Gemini CLI) and config
10087
+ Supports CLI-based setup (Claude Code, Codex, Gemini CLI), Pi package setup,
10088
+ and config
9530
10089
  file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
9531
10090
  Google Antigravity) with atomic writes.`;
9532
10091
  var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
@@ -9543,7 +10102,7 @@ function registerInitCommand(program) {
9543
10102
  fileSystemService,
9544
10103
  promptService,
9545
10104
  execService,
9546
- createLoginDeps: () => createAuthCommandDependencies()
10105
+ createLoginDeps: () => createContainer()
9547
10106
  });
9548
10107
  });
9549
10108
  initCommand.command("uninstall").summary("Remove MCP server from your coding agents").description(INIT_UNINSTALL_DESCRIPTION).option("-y, --yes", "Skip prompts, uninstall from all configured agents").action(async (options) => {
@@ -9779,11 +10338,11 @@ var structuredCodeTargetSchema = z3.object({
9779
10338
  package_name: z3.string().max(255).optional().describe("Package name. Required for package scope."),
9780
10339
  version: z3.string().max(100).optional().describe("Package version, e.g. '4.18.2' (defaults to latest). For package scope only."),
9781
10340
  repo_url: z3.string().optional().describe("Repository URL (GitHub). Required for repo scope. Example: https://github.com/expressjs/express"),
9782
- git_ref: z3.string().optional().describe("Git ref - tag, branch, or commit. Required with repo_url for code_files/code_read/code_grep. Use HEAD for latest.")
9783
- }).describe("Target: provide registry + package_name (package scope) or repo_url + git_ref (repo scope).");
10341
+ git_ref: z3.string().optional().describe("Git ref - tag, branch, commit, or HEAD. Omit with repo_url to request the backend-resolved default branch.")
10342
+ }).describe("Target: provide registry + package_name (package scope) or repo_url with optional git_ref (repo scope; omitted ref means default branch intent).");
9784
10343
  var codeTargetSchema = z3.union([
9785
10344
  structuredCodeTargetSchema,
9786
- z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0` or `npm:react` for latest release. Repository: `https://github.com/facebook/react#HEAD` (git ref required for code_files/code_read/code_grep).")
10345
+ z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0` or `npm:react` for latest release. Repository: `https://github.com/facebook/react#HEAD` or `https://github.com/facebook/react` for default branch intent.")
9787
10346
  ]);
9788
10347
  function resolveCodeTarget(target) {
9789
10348
  if (typeof target === "string") {
@@ -9796,10 +10355,10 @@ function resolveCodeTarget(target) {
9796
10355
  const hasPackageTarget = Boolean(target.registry || target.package_name);
9797
10356
  const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
9798
10357
  if (hasPackageTarget && hasRepoTarget) {
9799
- return invalidTargetResult("Invalid target: provide either registry + package_name or repo_url + git_ref, not both.");
10358
+ return invalidTargetResult("Invalid target: provide either registry + package_name or repo_url with optional git_ref, not both.");
9800
10359
  }
9801
10360
  if (!hasPackageTarget && !hasRepoTarget) {
9802
- return invalidTargetResult("Missing target: provide registry + package_name or repo_url + git_ref.");
10361
+ return invalidTargetResult("Missing target: provide registry + package_name or repo_url.");
9803
10362
  }
9804
10363
  if (hasPackageTarget) {
9805
10364
  if (!target.registry || !target.package_name) {
@@ -9811,11 +10370,8 @@ function resolveCodeTarget(target) {
9811
10370
  version: target.version
9812
10371
  };
9813
10372
  }
9814
- if (!target.repo_url || !target.git_ref) {
9815
- if (!target.repo_url) {
9816
- return invalidTargetResult("Incomplete repository target: repo_url is required.");
9817
- }
9818
- return invalidTargetResult("Incomplete repository target: git_ref is required for code_files/code_read/code_grep.");
10373
+ if (!target.repo_url) {
10374
+ return invalidTargetResult("Incomplete repository target: repo_url is required.");
9819
10375
  }
9820
10376
  return {
9821
10377
  repoUrl: target.repo_url,
@@ -9861,7 +10417,7 @@ var schema3 = {
9861
10417
  wait_timeout_ms: z4.number().optional(),
9862
10418
  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.')
9863
10419
  };
9864
- var DESCRIPTION3 = "Deterministic text or regex grep over indexed dependency and repository source files. " + 'Use this when you know the pattern (literal by default; pass `pattern_type: "regex"` for RE2). ' + "Use `search` for discovery instead. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + "Each match's `filePath` (or text file heading) chains into `code_read.path`; pick a window around `match.line` for `code_read.start_line` / `end_line`." + `
10420
+ var DESCRIPTION3 = "Deterministic text or regex grep over indexed dependency and repository source files. " + 'Use this when you know the pattern (literal by default; pass `pattern_type: "regex"` for RE2). ' + "Use `search` for discovery instead. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + "Each match's `filePath` (or text file heading) chains into `code_read.path`; pick a window around `match.line` for `code_read.start_line` / `end_line`. " + "When fresh data is not ready within the wait window, responses may include `targetResolution` provenance and immediately-queryable alternatives in error details." + `
9865
10421
 
9866
10422
  ${CODE_GREP_GUARDRAIL}`;
9867
10423
  function createGrepRepoTool(service) {
@@ -9955,10 +10511,10 @@ var schema4 = {
9955
10511
  exclude_test_files: z5.boolean().optional(),
9956
10512
  include_hidden: z5.boolean().optional(),
9957
10513
  limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
9958
- 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`."),
10514
+ 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/ref from `details.availableVersions` / `details.availableRefs`."),
9959
10515
  format: z5.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact paths-only listing. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
9960
10516
  };
9961
- var DESCRIPTION4 = "List files in an indexed dependency. First choice for file/path " + "enumeration tasks such as files under a directory; use " + "`path_prefix` for directory prefixes (e.g. `lib/`) and optional " + "`extensions` for language filtering. Use this to discover paths " + "before `code_read` (when `code_read` returns `FILE_NOT_FOUND` or " + "you don't yet know the path) and to scope `code_grep`. Address " + "via `target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + `target.git_ref` (repo scope), mutually " + "exclusive. Narrow with `path`, `path_prefix`, `globs`, " + "`extensions`, `file_types`, `languages`, or file-intent filters. " + "JSON envelope shape: `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "Returns an `INDEXING` error envelope when the dependency is being " + "indexed on-demand retry with a longer `wait_timeout_ms` or use " + "a version from `details.availableVersions`.";
10517
+ var DESCRIPTION4 = "List files in an indexed dependency. First choice for file/path " + "enumeration tasks such as files under a directory; use " + "`path_prefix` for directory prefixes (e.g. `lib/`) and optional " + "`extensions` for language filtering. Use this to discover paths " + "before `code_read` (when `code_read` returns `FILE_NOT_FOUND` or " + "you don't yet know the path) and to scope `code_grep`. Address " + "via `target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + optional `target.git_ref` (repo scope), mutually " + "exclusive. Narrow with `path`, `path_prefix`, `globs`, " + "`extensions`, `file_types`, `languages`, or file-intent filters. " + "JSON envelope shape: `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "When fresh data is not ready within the wait window, responses may " + "include `targetResolution` provenance and immediately-queryable " + "alternatives. On an `INDEXING` error envelope, retry with a longer " + "`wait_timeout_ms` or use a version/ref from `details.availableVersions` " + "/ `details.availableRefs`.";
9962
10518
  function createListFilesTool(service) {
9963
10519
  return {
9964
10520
  name: "code_files",
@@ -10721,10 +11277,10 @@ var schema11 = {
10721
11277
  path: z12.string().describe("Exact file path to read, not a directory. Package addressing: package-relative. Repo addressing: repo-relative. Use `code_files` with `path_prefix` to list directories, then pass an emitted `path` here."),
10722
11278
  start_line: z12.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.`),
10723
11279
  end_line: z12.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.`),
10724
- wait_timeout_ms: z12.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`."),
11280
+ wait_timeout_ms: z12.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/ref from `details.availableVersions` / `details.availableRefs`."),
10725
11281
  format: z12.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — line-numbered source content. Pass `format: "json"` for the structured envelope.')
10726
11282
  };
10727
- var DESCRIPTION11 = "Read one exact file from an indexed dependency; it does not list " + "directories. Use `code_files` with `path_prefix` for file/path " + "enumeration. **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." + `
11283
+ var DESCRIPTION11 = "Read one exact file from an indexed dependency; it does not list " + "directories. Use `code_files` with `path_prefix` for file/path " + "enumeration. **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` + optional `target.git_ref` (repo scope), " + "mutually exclusive. When fresh data is not ready within the wait " + "window, responses may include `targetResolution` provenance and " + "immediately-queryable alternatives. On `INDEXING` retry with a " + "longer `wait_timeout_ms` or use a version/ref from error details. " + "On `NOT_FOUND` / `FILE_NOT_FOUND` call " + "`code_files` to discover the actual path." + `
10728
11284
 
10729
11285
  ${CODE_READ_GUARDRAIL}`;
10730
11286
  function deriveBoundedRange(startLine, endLine) {
@@ -11124,7 +11680,7 @@ var CORE_BLOCK = `GitHits provides verified, canonical code examples from global
11124
11680
  \`get_example\` workflow: pass \`language\` only when you know the exact name; otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`. Reuse prior results before searching again. For dependency-specific grounding, prefer package-scoped \`search\` before global \`get_example\`.`;
11125
11681
  var PACKAGE_TOOLS_PREAMBLE = `Indexed package/source tools inspect third-party dependency source, docs, and registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library works, or you're evaluating whether to add or upgrade a package.
11126
11682
 
11127
- Targets: \`registry:name[@version]\` (\`registry:name\` = latest release). For \`search\`, repo URL without \`#\` uses the backend default-branch snapshot; exact \`code_*\` tools need \`#ref\` such as \`#HEAD\`. Default outputs are compact \`text-v1\`; pass \`format: "json"\` only for structured parsing.`;
11683
+ Targets: \`registry:name[@version]\` (\`registry:name\` = latest release). Repo targets can request the backend default-branch intent by omitting the ref (\`https://github.com/org/repo\` for \`search\`, or \`repo_url\` without \`git_ref\` for \`code_*\`); pass \`#HEAD\` / \`git_ref:"HEAD"\` when you specifically need latest HEAD. Default outputs are compact \`text-v1\`; pass \`format: "json"\` only for structured parsing.`;
11128
11684
  var MULTI_TURN_TIP = '**Delegate multi-call work to a sub-agent.** Code-navigation work (`search`, `code_grep`, `code_read`, `code_files`) often takes 3-10 calls. For cross-project comparisons, codebase mapping, pattern surveys, or "how does X actually work" investigations, spawn a sub-task/sub-agent and ask for a compact synthesis. Do it inline only when the raw snippet belongs in the main conversation.';
11129
11685
  var SEARCH_BULLET = '- `search` — unified search across indexed dependency code, docs, and symbols. Omit `sources` for AUTO. Use `sources:["code"]` for implementation/examples/tests text, `sources:["symbol"]` for precise API/entity lookup, and `sources:["docs"]` for guides/reference/changelogs. Default output is compact `text-v1` with ready-to-call follow-up arguments; pass `format: "json"` for structured locators, highlights, and source status. Complete by default; opt into partial hits with `allow_partial_results: true`. Incomplete responses carry a `searchRef`.';
11130
11686
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior `search` by `searchRef` to check progress, fetch partial hits, or fetch final results.";
@@ -11948,7 +12504,7 @@ function requireSearchService(deps) {
11948
12504
  return deps.codeNavigationService;
11949
12505
  }
11950
12506
  async function loadContainer2() {
11951
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
12507
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2fdxv7pe.js");
11952
12508
  return createContainer2();
11953
12509
  }
11954
12510
  function parseTargetSpecs(specs) {
@@ -12047,7 +12603,7 @@ function formatUnifiedSearchTerminal(payload) {
12047
12603
  lines.push("");
12048
12604
  lines.push("Partial results:");
12049
12605
  }
12050
- const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus);
12606
+ const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus, warnings);
12051
12607
  if (payload.results.length === 0) {
12052
12608
  lines.push("No results.");
12053
12609
  if (sourceStatusNotes.length > 0) {
@@ -12159,13 +12715,17 @@ function formatSearchStatusPartialTerminal(payload) {
12159
12715
  sourceStatus: payload.result.sourceStatus
12160
12716
  });
12161
12717
  }
12162
- function formatSourceStatusNotes(sourceStatus) {
12718
+ function formatSourceStatusNotes(sourceStatus, warnings) {
12163
12719
  const useColors = shouldUseColors();
12164
12720
  if (!sourceStatus) {
12165
12721
  return [];
12166
12722
  }
12167
12723
  const lines = [];
12168
12724
  for (const entry of sourceStatus) {
12725
+ const warningPrefix = `Source '${entry.source.toLowerCase()}' for ${entry.targetLabel}:`;
12726
+ if (warnings?.some((warning2) => warning2.startsWith(warningPrefix))) {
12727
+ continue;
12728
+ }
12169
12729
  const label = `${entry.source.toLowerCase()} on ${entry.targetLabel}`;
12170
12730
  if (entry.ignoredFilters && entry.ignoredFilters.length > 0) {
12171
12731
  lines.push(dim(`Note: ${label} ignored filters: ${entry.ignoredFilters.join(", ")}`, useColors));
@@ -12396,7 +12956,7 @@ function mergeRanges2(ranges) {
12396
12956
  }
12397
12957
  return merged;
12398
12958
  }
12399
- function formatUnifiedSearchMetadata(entry, useColors) {
12959
+ function formatUnifiedSearchMetadata(entry, _useColors) {
12400
12960
  if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
12401
12961
  return [];
12402
12962
  }