scip-query 0.1.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.
Files changed (330) hide show
  1. package/IMPROVEMENTS.md +143 -0
  2. package/PLAN.md +320 -0
  3. package/README.md +1213 -0
  4. package/dist/chunk-2QZ23IBN.js +55 -0
  5. package/dist/chunk-2QZ23IBN.js.map +1 -0
  6. package/dist/chunk-36OMT7ZJ.js +144 -0
  7. package/dist/chunk-36OMT7ZJ.js.map +1 -0
  8. package/dist/chunk-3E2X7RIE.js +101 -0
  9. package/dist/chunk-3E2X7RIE.js.map +1 -0
  10. package/dist/chunk-3UOUTZQT.js +45 -0
  11. package/dist/chunk-3UOUTZQT.js.map +1 -0
  12. package/dist/chunk-3ZZJVBIO.js +88 -0
  13. package/dist/chunk-3ZZJVBIO.js.map +1 -0
  14. package/dist/chunk-4TYLS5XX.js +10 -0
  15. package/dist/chunk-4TYLS5XX.js.map +1 -0
  16. package/dist/chunk-5FGUEU7N.js +101 -0
  17. package/dist/chunk-5FGUEU7N.js.map +1 -0
  18. package/dist/chunk-5WTJAXY2.js +61 -0
  19. package/dist/chunk-5WTJAXY2.js.map +1 -0
  20. package/dist/chunk-6NBLIDF4.js +24 -0
  21. package/dist/chunk-6NBLIDF4.js.map +1 -0
  22. package/dist/chunk-6SXADWLW.js +43 -0
  23. package/dist/chunk-6SXADWLW.js.map +1 -0
  24. package/dist/chunk-6VJ6Q7IE.js +65 -0
  25. package/dist/chunk-6VJ6Q7IE.js.map +1 -0
  26. package/dist/chunk-7OZPA5OO.js +258 -0
  27. package/dist/chunk-7OZPA5OO.js.map +1 -0
  28. package/dist/chunk-BEPIEVLR.js +76 -0
  29. package/dist/chunk-BEPIEVLR.js.map +1 -0
  30. package/dist/chunk-BFSCMC22.js +42 -0
  31. package/dist/chunk-BFSCMC22.js.map +1 -0
  32. package/dist/chunk-BP2ATLK2.js +110 -0
  33. package/dist/chunk-BP2ATLK2.js.map +1 -0
  34. package/dist/chunk-CM454WL3.js +114 -0
  35. package/dist/chunk-CM454WL3.js.map +1 -0
  36. package/dist/chunk-DCKMSTJ4.js +74 -0
  37. package/dist/chunk-DCKMSTJ4.js.map +1 -0
  38. package/dist/chunk-DEZKCZXD.js +40 -0
  39. package/dist/chunk-DEZKCZXD.js.map +1 -0
  40. package/dist/chunk-DVWGWHFW.js +99 -0
  41. package/dist/chunk-DVWGWHFW.js.map +1 -0
  42. package/dist/chunk-EMDQWNYR.js +102 -0
  43. package/dist/chunk-EMDQWNYR.js.map +1 -0
  44. package/dist/chunk-FFSWWE5O.js +33 -0
  45. package/dist/chunk-FFSWWE5O.js.map +1 -0
  46. package/dist/chunk-FGXRVW7G.js +73 -0
  47. package/dist/chunk-FGXRVW7G.js.map +1 -0
  48. package/dist/chunk-FUHJCHS4.js +158 -0
  49. package/dist/chunk-FUHJCHS4.js.map +1 -0
  50. package/dist/chunk-GJFURBEW.js +64 -0
  51. package/dist/chunk-GJFURBEW.js.map +1 -0
  52. package/dist/chunk-GTILYBH6.js +102 -0
  53. package/dist/chunk-GTILYBH6.js.map +1 -0
  54. package/dist/chunk-JJP7KQND.js +1 -0
  55. package/dist/chunk-JJP7KQND.js.map +1 -0
  56. package/dist/chunk-JKP5GH6T.js +213 -0
  57. package/dist/chunk-JKP5GH6T.js.map +1 -0
  58. package/dist/chunk-KCBMVQL5.js +38 -0
  59. package/dist/chunk-KCBMVQL5.js.map +1 -0
  60. package/dist/chunk-KVSW5KYP.js +78 -0
  61. package/dist/chunk-KVSW5KYP.js.map +1 -0
  62. package/dist/chunk-LAWMH22O.js +172 -0
  63. package/dist/chunk-LAWMH22O.js.map +1 -0
  64. package/dist/chunk-LB7OS35Q.js +72 -0
  65. package/dist/chunk-LB7OS35Q.js.map +1 -0
  66. package/dist/chunk-LUSIFBXO.js +57 -0
  67. package/dist/chunk-LUSIFBXO.js.map +1 -0
  68. package/dist/chunk-MBVNHJVN.js +44 -0
  69. package/dist/chunk-MBVNHJVN.js.map +1 -0
  70. package/dist/chunk-MGNMHKX3.js +15 -0
  71. package/dist/chunk-MGNMHKX3.js.map +1 -0
  72. package/dist/chunk-N5KEREIA.js +41 -0
  73. package/dist/chunk-N5KEREIA.js.map +1 -0
  74. package/dist/chunk-NDSQYIWT.js +71 -0
  75. package/dist/chunk-NDSQYIWT.js.map +1 -0
  76. package/dist/chunk-NUZ4OMU3.js +28 -0
  77. package/dist/chunk-NUZ4OMU3.js.map +1 -0
  78. package/dist/chunk-QOV2R2WT.js +170 -0
  79. package/dist/chunk-QOV2R2WT.js.map +1 -0
  80. package/dist/chunk-SEFSL2GF.js +78 -0
  81. package/dist/chunk-SEFSL2GF.js.map +1 -0
  82. package/dist/chunk-T6ARFSBZ.js +103 -0
  83. package/dist/chunk-T6ARFSBZ.js.map +1 -0
  84. package/dist/chunk-TBP6BICL.js +46 -0
  85. package/dist/chunk-TBP6BICL.js.map +1 -0
  86. package/dist/chunk-TDNNOR6D.js +97 -0
  87. package/dist/chunk-TDNNOR6D.js.map +1 -0
  88. package/dist/chunk-TSPZOMHC.js +195 -0
  89. package/dist/chunk-TSPZOMHC.js.map +1 -0
  90. package/dist/chunk-UNTPVD36.js +55 -0
  91. package/dist/chunk-UNTPVD36.js.map +1 -0
  92. package/dist/chunk-VRUJH4BO.js +88 -0
  93. package/dist/chunk-VRUJH4BO.js.map +1 -0
  94. package/dist/chunk-VZ7AMAFL.js +76 -0
  95. package/dist/chunk-VZ7AMAFL.js.map +1 -0
  96. package/dist/chunk-XFXDXEUN.js +24 -0
  97. package/dist/chunk-XFXDXEUN.js.map +1 -0
  98. package/dist/chunk-YZAA4LYG.js +169 -0
  99. package/dist/chunk-YZAA4LYG.js.map +1 -0
  100. package/dist/chunk-Z73NYSBZ.js +92 -0
  101. package/dist/chunk-Z73NYSBZ.js.map +1 -0
  102. package/dist/chunk-ZJRYBOEE.js +125 -0
  103. package/dist/chunk-ZJRYBOEE.js.map +1 -0
  104. package/dist/cli.js +5798 -0
  105. package/dist/cli.js.map +1 -0
  106. package/dist/db-BxaevAyc.d.ts +683 -0
  107. package/dist/index.d.ts +254 -0
  108. package/dist/index.js +1271 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/postinstall.js +167 -0
  111. package/dist/postinstall.js.map +1 -0
  112. package/dist/queries/affected.d.ts +14 -0
  113. package/dist/queries/affected.js +9 -0
  114. package/dist/queries/affected.js.map +1 -0
  115. package/dist/queries/bottlenecks.d.ts +18 -0
  116. package/dist/queries/bottlenecks.js +8 -0
  117. package/dist/queries/bottlenecks.js.map +1 -0
  118. package/dist/queries/by-kind.d.ts +20 -0
  119. package/dist/queries/by-kind.js +10 -0
  120. package/dist/queries/by-kind.js.map +1 -0
  121. package/dist/queries/call-graph.d.ts +13 -0
  122. package/dist/queries/call-graph.js +9 -0
  123. package/dist/queries/call-graph.js.map +1 -0
  124. package/dist/queries/change-surface.d.ts +10 -0
  125. package/dist/queries/change-surface.js +9 -0
  126. package/dist/queries/change-surface.js.map +1 -0
  127. package/dist/queries/clean-signature.d.ts +9 -0
  128. package/dist/queries/clean-signature.js +7 -0
  129. package/dist/queries/clean-signature.js.map +1 -0
  130. package/dist/queries/code.d.ts +17 -0
  131. package/dist/queries/code.js +9 -0
  132. package/dist/queries/code.js.map +1 -0
  133. package/dist/queries/complexity-hotspots.d.ts +19 -0
  134. package/dist/queries/complexity-hotspots.js +9 -0
  135. package/dist/queries/complexity-hotspots.js.map +1 -0
  136. package/dist/queries/complexity.d.ts +13 -0
  137. package/dist/queries/complexity.js +9 -0
  138. package/dist/queries/complexity.js.map +1 -0
  139. package/dist/queries/convergence.d.ts +11 -0
  140. package/dist/queries/convergence.js +9 -0
  141. package/dist/queries/convergence.js.map +1 -0
  142. package/dist/queries/coupling.d.ts +17 -0
  143. package/dist/queries/coupling.js +9 -0
  144. package/dist/queries/coupling.js.map +1 -0
  145. package/dist/queries/cycles.d.ts +16 -0
  146. package/dist/queries/cycles.js +8 -0
  147. package/dist/queries/cycles.js.map +1 -0
  148. package/dist/queries/dataflow.d.ts +19 -0
  149. package/dist/queries/dataflow.js +9 -0
  150. package/dist/queries/dataflow.js.map +1 -0
  151. package/dist/queries/dead.d.ts +10 -0
  152. package/dist/queries/dead.js +9 -0
  153. package/dist/queries/dead.js.map +1 -0
  154. package/dist/queries/deep-chains.d.ts +16 -0
  155. package/dist/queries/deep-chains.js +8 -0
  156. package/dist/queries/deep-chains.js.map +1 -0
  157. package/dist/queries/deps.d.ts +9 -0
  158. package/dist/queries/deps.js +9 -0
  159. package/dist/queries/deps.js.map +1 -0
  160. package/dist/queries/diff-impact.d.ts +13 -0
  161. package/dist/queries/diff-impact.js +9 -0
  162. package/dist/queries/diff-impact.js.map +1 -0
  163. package/dist/queries/doc-coverage.d.ts +14 -0
  164. package/dist/queries/doc-coverage.js +8 -0
  165. package/dist/queries/doc-coverage.js.map +1 -0
  166. package/dist/queries/drift.d.ts +25 -0
  167. package/dist/queries/drift.js +8 -0
  168. package/dist/queries/drift.js.map +1 -0
  169. package/dist/queries/extract-candidates.d.ts +25 -0
  170. package/dist/queries/extract-candidates.js +9 -0
  171. package/dist/queries/extract-candidates.js.map +1 -0
  172. package/dist/queries/fan.d.ts +29 -0
  173. package/dist/queries/fan.js +14 -0
  174. package/dist/queries/fan.js.map +1 -0
  175. package/dist/queries/files.d.ts +6 -0
  176. package/dist/queries/files.js +7 -0
  177. package/dist/queries/files.js.map +1 -0
  178. package/dist/queries/health.d.ts +18 -0
  179. package/dist/queries/health.js +21 -0
  180. package/dist/queries/health.js.map +1 -0
  181. package/dist/queries/hierarchy.d.ts +13 -0
  182. package/dist/queries/hierarchy.js +8 -0
  183. package/dist/queries/hierarchy.js.map +1 -0
  184. package/dist/queries/hotspots.d.ts +13 -0
  185. package/dist/queries/hotspots.js +8 -0
  186. package/dist/queries/hotspots.js.map +1 -0
  187. package/dist/queries/imports.d.ts +19 -0
  188. package/dist/queries/imports.js +12 -0
  189. package/dist/queries/imports.js.map +1 -0
  190. package/dist/queries/index.d.ts +47 -0
  191. package/dist/queries/index.js +207 -0
  192. package/dist/queries/index.js.map +1 -0
  193. package/dist/queries/isolated.d.ts +14 -0
  194. package/dist/queries/isolated.js +9 -0
  195. package/dist/queries/isolated.js.map +1 -0
  196. package/dist/queries/members.d.ts +10 -0
  197. package/dist/queries/members.js +8 -0
  198. package/dist/queries/members.js.map +1 -0
  199. package/dist/queries/methods.d.ts +6 -0
  200. package/dist/queries/methods.js +8 -0
  201. package/dist/queries/methods.js.map +1 -0
  202. package/dist/queries/outline.d.ts +10 -0
  203. package/dist/queries/outline.js +8 -0
  204. package/dist/queries/outline.js.map +1 -0
  205. package/dist/queries/passthrough-candidates.d.ts +18 -0
  206. package/dist/queries/passthrough-candidates.js +9 -0
  207. package/dist/queries/passthrough-candidates.js.map +1 -0
  208. package/dist/queries/redundant-reexports.d.ts +22 -0
  209. package/dist/queries/redundant-reexports.js +8 -0
  210. package/dist/queries/redundant-reexports.js.map +1 -0
  211. package/dist/queries/refs.d.ts +6 -0
  212. package/dist/queries/refs.js +7 -0
  213. package/dist/queries/refs.js.map +1 -0
  214. package/dist/queries/similar-chains.d.ts +29 -0
  215. package/dist/queries/similar-chains.js +8 -0
  216. package/dist/queries/similar-chains.js.map +1 -0
  217. package/dist/queries/similar-files.d.ts +19 -0
  218. package/dist/queries/similar-files.js +8 -0
  219. package/dist/queries/similar-files.js.map +1 -0
  220. package/dist/queries/similar-signatures.d.ts +21 -0
  221. package/dist/queries/similar-signatures.js +8 -0
  222. package/dist/queries/similar-signatures.js.map +1 -0
  223. package/dist/queries/similar.d.ts +34 -0
  224. package/dist/queries/similar.js +11 -0
  225. package/dist/queries/similar.js.map +1 -0
  226. package/dist/queries/slice.d.ts +21 -0
  227. package/dist/queries/slice.js +9 -0
  228. package/dist/queries/slice.js.map +1 -0
  229. package/dist/queries/stale-abstractions.d.ts +18 -0
  230. package/dist/queries/stale-abstractions.js +9 -0
  231. package/dist/queries/stale-abstractions.js.map +1 -0
  232. package/dist/queries/stats.d.ts +6 -0
  233. package/dist/queries/stats.js +7 -0
  234. package/dist/queries/stats.js.map +1 -0
  235. package/dist/queries/surface.d.ts +7 -0
  236. package/dist/queries/surface.js +8 -0
  237. package/dist/queries/surface.js.map +1 -0
  238. package/dist/queries/symbols.d.ts +6 -0
  239. package/dist/queries/symbols.js +9 -0
  240. package/dist/queries/symbols.js.map +1 -0
  241. package/dist/queries/system.d.ts +7 -0
  242. package/dist/queries/system.js +9 -0
  243. package/dist/queries/system.js.map +1 -0
  244. package/dist/queries/test-coverage.d.ts +22 -0
  245. package/dist/queries/test-coverage.js +11 -0
  246. package/dist/queries/test-coverage.js.map +1 -0
  247. package/dist/queries/trace.d.ts +6 -0
  248. package/dist/queries/trace.js +8 -0
  249. package/dist/queries/trace.js.map +1 -0
  250. package/dist/queries/wrapper-candidates.d.ts +17 -0
  251. package/dist/queries/wrapper-candidates.js +9 -0
  252. package/dist/queries/wrapper-candidates.js.map +1 -0
  253. package/dist/reindex-worker.js +368 -0
  254. package/dist/reindex-worker.js.map +1 -0
  255. package/docs/AGENT_GUIDE.md +359 -0
  256. package/package.json +70 -0
  257. package/reports/debloat/2026-04-10-scip-query-self-audit.md +161 -0
  258. package/skills/concrete-plan/SKILL.md +318 -0
  259. package/skills/scip-debloat/SKILL.md +413 -0
  260. package/skills/scip-explore/SKILL.md +235 -0
  261. package/skills/scip-verify/SKILL.md +323 -0
  262. package/src/cli.ts +1480 -0
  263. package/src/config.ts +117 -0
  264. package/src/db.ts +127 -0
  265. package/src/gitignore-filter.ts +143 -0
  266. package/src/index.ts +11 -0
  267. package/src/postinstall.ts +8 -0
  268. package/src/queries/affected.ts +86 -0
  269. package/src/queries/bottlenecks.ts +67 -0
  270. package/src/queries/by-kind.ts +204 -0
  271. package/src/queries/call-graph.ts +66 -0
  272. package/src/queries/change-surface.ts +110 -0
  273. package/src/queries/clean-signature.ts +22 -0
  274. package/src/queries/code.ts +101 -0
  275. package/src/queries/complexity-hotspots.ts +119 -0
  276. package/src/queries/complexity.ts +152 -0
  277. package/src/queries/convergence.ts +82 -0
  278. package/src/queries/coupling.ts +99 -0
  279. package/src/queries/cycles.ts +78 -0
  280. package/src/queries/dataflow.ts +128 -0
  281. package/src/queries/dead.ts +122 -0
  282. package/src/queries/deep-chains.ts +59 -0
  283. package/src/queries/deps.ts +46 -0
  284. package/src/queries/diff-impact.ts +204 -0
  285. package/src/queries/doc-coverage.ts +86 -0
  286. package/src/queries/drift.ts +224 -0
  287. package/src/queries/extract-candidates.ts +167 -0
  288. package/src/queries/fan.ts +148 -0
  289. package/src/queries/files.ts +16 -0
  290. package/src/queries/health.ts +324 -0
  291. package/src/queries/hierarchy.ts +49 -0
  292. package/src/queries/hotspots.ts +53 -0
  293. package/src/queries/imports.ts +95 -0
  294. package/src/queries/index.ts +45 -0
  295. package/src/queries/isolated.ts +67 -0
  296. package/src/queries/members.ts +54 -0
  297. package/src/queries/methods.ts +27 -0
  298. package/src/queries/outline.ts +52 -0
  299. package/src/queries/passthrough-candidates.ts +94 -0
  300. package/src/queries/redundant-reexports.ts +170 -0
  301. package/src/queries/refs.ts +27 -0
  302. package/src/queries/similar-chains.ts +314 -0
  303. package/src/queries/similar-files.ts +140 -0
  304. package/src/queries/similar-signatures.ts +151 -0
  305. package/src/queries/similar.ts +305 -0
  306. package/src/queries/slice.ts +154 -0
  307. package/src/queries/stale-abstractions.ts +82 -0
  308. package/src/queries/stats.ts +22 -0
  309. package/src/queries/surface.ts +34 -0
  310. package/src/queries/symbols.ts +39 -0
  311. package/src/queries/system.ts +86 -0
  312. package/src/queries/test-coverage.ts +106 -0
  313. package/src/queries/trace.ts +55 -0
  314. package/src/queries/wrapper-candidates.ts +112 -0
  315. package/src/query-support.ts +226 -0
  316. package/src/reindex/detect.ts +58 -0
  317. package/src/reindex/index.ts +153 -0
  318. package/src/reindex/indexers.ts +220 -0
  319. package/src/reindex/install.ts +125 -0
  320. package/src/reindex-worker.ts +35 -0
  321. package/src/setup.ts +202 -0
  322. package/src/symbol-parser.ts +278 -0
  323. package/src/types.ts +654 -0
  324. package/src/watch.ts +274 -0
  325. package/tests/gitignore-filter.test.ts +48 -0
  326. package/tests/queries.test.ts +300 -0
  327. package/tests/symbol-parser.test.ts +157 -0
  328. package/tsconfig.json +20 -0
  329. package/tsup.config.ts +40 -0
  330. package/vitest.config.ts +7 -0
package/src/setup.ts ADDED
@@ -0,0 +1,202 @@
1
+ import {
2
+ existsSync, mkdirSync, symlinkSync, readlinkSync,
3
+ unlinkSync, chmodSync, createWriteStream,
4
+ } from 'node:fs';
5
+ import { join, dirname, resolve } from 'node:path';
6
+ import { homedir, platform, arch } from 'node:os';
7
+ import { execFileSync } from 'node:child_process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { get as httpsGet } from 'node:https';
10
+ import { tryInstallScipCli } from './reindex/install.js';
11
+
12
+ const IS_WINDOWS = platform() === 'win32';
13
+ const SKILLS = ['concrete-plan', 'scip-explore', 'scip-debloat', 'scip-verify'];
14
+ const SCIP_VERSION = 'v0.7.0';
15
+
16
+ // ── Skills Installation ────────────────────────────────────
17
+
18
+ export interface InstallSkillsResult {
19
+ installed: string[];
20
+ skipped: string[];
21
+ alreadyLinked: string[];
22
+ }
23
+
24
+ /**
25
+ * Install scip-query skills into both Claude Code (~/.claude/skills/)
26
+ * and Codex (~/.codex/skills/). Uses symlinks (junctions on Windows)
27
+ * so skills auto-update when the package updates.
28
+ */
29
+ export function installSkills(
30
+ opts: { quiet?: boolean } = {},
31
+ ): InstallSkillsResult {
32
+ const log = opts.quiet ? () => {} : console.log;
33
+ const thisFile = fileURLToPath(import.meta.url);
34
+ const skillsSource = resolve(dirname(thisFile), '..', 'skills');
35
+
36
+ const targets = [
37
+ join(homedir(), '.claude', 'skills'),
38
+ join(homedir(), '.codex', 'skills'),
39
+ ];
40
+
41
+ const result: InstallSkillsResult = {
42
+ installed: [],
43
+ skipped: [],
44
+ alreadyLinked: [],
45
+ };
46
+
47
+ for (const targetDir of targets) {
48
+ // Only install if the parent directory exists (tool is installed)
49
+ const parentDir = dirname(targetDir);
50
+ if (!existsSync(parentDir)) {
51
+ continue;
52
+ }
53
+
54
+ mkdirSync(targetDir, { recursive: true });
55
+ const toolName = targetDir.includes('.codex') ? 'Codex' : 'Claude';
56
+
57
+ for (const skill of SKILLS) {
58
+ const source = join(skillsSource, skill);
59
+ const target = join(targetDir, skill);
60
+
61
+ if (!existsSync(source)) {
62
+ result.skipped.push(`${toolName}/${skill}`);
63
+ continue;
64
+ }
65
+
66
+ if (existsSync(target)) {
67
+ try {
68
+ const existing = readlinkSync(target);
69
+ if (resolve(existing) === resolve(source)) {
70
+ result.alreadyLinked.push(`${toolName}/${skill}`);
71
+ log(` ok: ${skill} → ${toolName} (already linked)`);
72
+ continue;
73
+ }
74
+ } catch {
75
+ // Not a symlink — don't overwrite user's custom skill
76
+ result.skipped.push(`${toolName}/${skill}`);
77
+ log(` skip: ${skill} → ${toolName} (exists, not a symlink)`);
78
+ continue;
79
+ }
80
+ unlinkSync(target);
81
+ }
82
+
83
+ // Use 'junction' on Windows (doesn't need admin), 'dir' elsewhere
84
+ symlinkSync(source, target, IS_WINDOWS ? 'junction' : 'dir');
85
+ result.installed.push(`${toolName}/${skill}`);
86
+ log(` done: ${skill} → ${toolName}`);
87
+ }
88
+ }
89
+
90
+ return result;
91
+ }
92
+
93
+ // ── SCIP CLI Binary Detection & Installation ───────────────
94
+
95
+ /**
96
+ * Check if the `scip` CLI binary is available on PATH.
97
+ */
98
+ export function isScipInstalled(): boolean {
99
+ try {
100
+ const cmd = IS_WINDOWS ? 'where' : 'which';
101
+ execFileSync(cmd, ['scip'], { stdio: 'pipe' });
102
+ return true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get the scip CLI version if installed.
110
+ */
111
+ export function getScipVersion(): string | null {
112
+ try {
113
+ const output = execFileSync('scip', ['--version'], { stdio: 'pipe' }).toString().trim();
114
+ return output;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Resolve the download URL for the scip CLI binary for this platform.
122
+ */
123
+ function getScipDownloadUrl(): { url: string; filename: string } | null {
124
+ const os = platform();
125
+ const cpu = arch();
126
+
127
+ let osName: string;
128
+ let archName: string;
129
+ let ext: string;
130
+
131
+ switch (os) {
132
+ case 'darwin': osName = 'darwin'; ext = 'tar.gz'; break;
133
+ case 'linux': osName = 'linux'; ext = 'tar.gz'; break;
134
+ case 'win32': osName = 'windows'; ext = 'zip'; break;
135
+ default: return null;
136
+ }
137
+
138
+ switch (cpu) {
139
+ case 'arm64': archName = 'arm64'; break;
140
+ case 'x64': archName = 'amd64'; break;
141
+ default: return null;
142
+ }
143
+
144
+ const filename = `scip-${osName}-${archName}.${ext}`;
145
+ const url = `https://github.com/sourcegraph/scip/releases/download/${SCIP_VERSION}/${filename}`;
146
+ return { url, filename };
147
+ }
148
+
149
+ /**
150
+ * Print instructions for installing the scip CLI binary.
151
+ */
152
+ export function printScipInstallInstructions(): void {
153
+ const download = getScipDownloadUrl();
154
+
155
+ console.log('\nThe `scip` CLI is required but not found on PATH.\n');
156
+
157
+ if (platform() === 'darwin') {
158
+ console.log('Install via Homebrew:');
159
+ console.log(' brew install sourcegraph/scip/scip\n');
160
+ console.log('Or download manually:');
161
+ } else {
162
+ console.log('Download from:');
163
+ }
164
+
165
+ if (download) {
166
+ console.log(` ${download.url}\n`);
167
+ } else {
168
+ console.log(` https://github.com/sourcegraph/scip/releases/tag/${SCIP_VERSION}\n`);
169
+ }
170
+
171
+ console.log('After installing, ensure `scip` is on your PATH and run `scip-query reindex`.');
172
+ }
173
+
174
+ // ── First-Run Setup ────────────────────────────────────────
175
+
176
+ /**
177
+ * Run first-time setup: install skills and check for scip binary.
178
+ * Called from the postinstall script.
179
+ */
180
+ export function postinstall(): void {
181
+ console.log('scip-query: installing skills...');
182
+ const result = installSkills({ quiet: false });
183
+
184
+ const total = result.installed.length + result.alreadyLinked.length;
185
+ if (total > 0) {
186
+ console.log(`\n${result.installed.length} skill(s) installed, ${result.alreadyLinked.length} already linked.`);
187
+ }
188
+
189
+ // Check for scip binary — auto-install if missing
190
+ if (!isScipInstalled()) {
191
+ console.log('\nscip CLI not found on PATH. Attempting auto-install...');
192
+ const installed = tryInstallScipCli(console.log);
193
+ if (!installed) {
194
+ printScipInstallInstructions();
195
+ }
196
+ } else {
197
+ const version = getScipVersion();
198
+ console.log(`\nscip CLI: ${version ?? 'installed'}`);
199
+ }
200
+
201
+ console.log('');
202
+ }
@@ -0,0 +1,278 @@
1
+ import type { ScipSymbol, ScipDescriptor, ScipLocalSymbol, DescriptorSuffix } from './types.js';
2
+
3
+ /**
4
+ * SCIP Symbol Grammar (from the SCIP spec):
5
+ *
6
+ * <symbol> ::= <scheme> ' ' <package> ' ' <descriptor>+ | 'local ' <local-id>
7
+ * <package> ::= <manager> ' ' <package-name> ' ' <version> ' '
8
+ * <descriptor> ::= <name> <suffix>
9
+ *
10
+ * Suffix characters:
11
+ * / namespace
12
+ * # type (class, interface, enum)
13
+ * . term (variable, field, property)
14
+ * (). method
15
+ * [ type parameter
16
+ * () parameter
17
+ * : meta
18
+ * ! macro
19
+ *
20
+ * Names may be backtick-escaped: `some.weird/name`
21
+ */
22
+
23
+ const SUFFIX_MAP: Record<string, DescriptorSuffix> = {
24
+ '/': 'namespace',
25
+ '#': 'type',
26
+ '.': 'term',
27
+ '[': 'type-param',
28
+ ':': 'meta',
29
+ '!': 'macro',
30
+ };
31
+
32
+ /**
33
+ * Parse a SCIP symbol string into its structured components.
34
+ * Works for any SCIP-indexed language (TypeScript, Java, Rust, Python, etc.)
35
+ */
36
+ export function parseSymbol(raw: string): ScipSymbol | ScipLocalSymbol {
37
+ if (raw.startsWith('local ')) {
38
+ return { kind: 'local', id: raw.slice(6), raw };
39
+ }
40
+
41
+ // Split: <scheme> <manager> <package-name> <version> <descriptors...>
42
+ // The tricky part: package-name can contain spaces if backtick-escaped
43
+ const parts = raw.split(' ');
44
+ if (parts.length < 4) {
45
+ // Malformed — return a best-effort parse
46
+ return {
47
+ scheme: parts[0] ?? '',
48
+ manager: parts[1] ?? '',
49
+ packageName: parts[2] ?? '',
50
+ version: '',
51
+ descriptors: [],
52
+ raw,
53
+ };
54
+ }
55
+
56
+ const scheme = parts[0]!;
57
+ const manager = parts[1]!;
58
+
59
+ // Package name and version: package name might be backtick-escaped
60
+ // After scheme + manager, we need to find package + version + descriptor string
61
+ let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);
62
+
63
+ // Parse package name (may be backtick-escaped)
64
+ let packageName: string;
65
+ if (restAfterManager.startsWith('`')) {
66
+ const closingTick = restAfterManager.indexOf('`', 1);
67
+ if (closingTick === -1) {
68
+ packageName = restAfterManager.slice(1);
69
+ restAfterManager = '';
70
+ } else {
71
+ packageName = restAfterManager.slice(1, closingTick);
72
+ restAfterManager = restAfterManager.slice(closingTick + 2); // skip ` and space
73
+ }
74
+ } else {
75
+ const spaceIdx = restAfterManager.indexOf(' ');
76
+ if (spaceIdx === -1) {
77
+ packageName = restAfterManager;
78
+ restAfterManager = '';
79
+ } else {
80
+ packageName = restAfterManager.slice(0, spaceIdx);
81
+ restAfterManager = restAfterManager.slice(spaceIdx + 1);
82
+ }
83
+ }
84
+
85
+ // Parse version
86
+ let version: string;
87
+ const versionSpaceIdx = restAfterManager.indexOf(' ');
88
+ if (versionSpaceIdx === -1) {
89
+ version = restAfterManager;
90
+ restAfterManager = '';
91
+ } else {
92
+ version = restAfterManager.slice(0, versionSpaceIdx);
93
+ restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);
94
+ }
95
+
96
+ // Parse descriptors from the remaining string
97
+ const descriptors = parseDescriptors(restAfterManager);
98
+
99
+ return { scheme, manager, packageName, version, descriptors, raw };
100
+ }
101
+
102
+ /**
103
+ * Parse the descriptor chain from a SCIP symbol.
104
+ *
105
+ * SCIP descriptor grammar:
106
+ * namespace: name/
107
+ * type: name#
108
+ * term: name.
109
+ * method: name(disambiguator).
110
+ * type-param: [name] (bracket-wrapped, prefix syntax)
111
+ * parameter: (name) (paren-wrapped, prefix syntax)
112
+ * meta: name:
113
+ * macro: name!
114
+ *
115
+ * Names can be backtick-escaped: `some/name.with.dots`
116
+ */
117
+ function parseDescriptors(input: string): ScipDescriptor[] {
118
+ const descriptors: ScipDescriptor[] = [];
119
+ let i = 0;
120
+
121
+ while (i < input.length) {
122
+ // Type parameter: [name]
123
+ if (input[i] === '[') {
124
+ const closeBracket = input.indexOf(']', i + 1);
125
+ if (closeBracket === -1) {
126
+ descriptors.push({ name: input.slice(i + 1), suffix: 'type-param' });
127
+ break;
128
+ }
129
+ descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: 'type-param' });
130
+ i = closeBracket + 1;
131
+ continue;
132
+ }
133
+
134
+ // Parameter: (name) — only when ( appears at descriptor start with no preceding name
135
+ if (input[i] === '(' && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]!))) {
136
+ const closeParen = input.indexOf(')', i + 1);
137
+ if (closeParen !== -1 && input[closeParen + 1] !== '.') {
138
+ // This is a parameter (name), not a method disambiguator
139
+ descriptors.push({ name: input.slice(i + 1, closeParen), suffix: 'parameter' });
140
+ i = closeParen + 1;
141
+ continue;
142
+ }
143
+ }
144
+
145
+ let name: string;
146
+
147
+ // Backtick-escaped name
148
+ if (input[i] === '`') {
149
+ const closingTick = input.indexOf('`', i + 1);
150
+ if (closingTick === -1) {
151
+ name = input.slice(i + 1);
152
+ i = input.length;
153
+ descriptors.push({ name, suffix: 'term' });
154
+ break;
155
+ }
156
+ name = input.slice(i + 1, closingTick);
157
+ i = closingTick + 1;
158
+ } else {
159
+ // Read name until we hit a suffix character
160
+ const start = i;
161
+ while (i < input.length && !isSuffixChar(input[i]!)) {
162
+ i++;
163
+ }
164
+ name = input.slice(start, i);
165
+ }
166
+
167
+ // Parse suffix after name
168
+ if (i >= input.length) {
169
+ if (name) descriptors.push({ name, suffix: 'term' });
170
+ break;
171
+ }
172
+
173
+ const char = input[i]!;
174
+
175
+ // Method: name(disambiguator).
176
+ if (char === '(') {
177
+ const closeParen = input.indexOf(')', i + 1);
178
+ if (closeParen !== -1 && input[closeParen + 1] === '.') {
179
+ descriptors.push({ name, suffix: 'method' });
180
+ i = closeParen + 2; // skip past ).
181
+ } else if (closeParen !== -1) {
182
+ // Bare (disambiguator) without . — treat as method anyway (common in practice)
183
+ descriptors.push({ name, suffix: 'method' });
184
+ i = closeParen + 1;
185
+ } else {
186
+ descriptors.push({ name, suffix: 'term' });
187
+ i++;
188
+ }
189
+ } else {
190
+ const suffix = SUFFIX_MAP[char];
191
+ if (suffix) {
192
+ descriptors.push({ name, suffix });
193
+ i += 1;
194
+ } else {
195
+ i += 1; // Unknown suffix — skip
196
+ }
197
+ }
198
+ }
199
+
200
+ return descriptors;
201
+ }
202
+
203
+ function isSuffixChar(c: string): boolean {
204
+ return c === '/' || c === '#' || c === '.' || c === '(' || c === '[' || c === ':' || c === '!';
205
+ }
206
+
207
+ /**
208
+ * Convert a parsed SCIP symbol to a short, human-readable name.
209
+ * Language-agnostic: works for any SCIP-indexed language.
210
+ *
211
+ * Examples:
212
+ * "scip-typescript npm @vega/api 0.1.3 src/modules/auth/auth.service.ts/AuthService#login()."
213
+ * → "auth.service:AuthService:login()"
214
+ *
215
+ * "scip-java maven com.example/mylib 1.0.0 com/example/MyClass#doStuff()."
216
+ * → "MyClass:doStuff()"
217
+ *
218
+ * "rust-analyzer cargo my-crate 0.1.0 src/lib.rs/MyStruct#new()."
219
+ * → "lib:MyStruct:new()"
220
+ */
221
+ export function shortenSymbol(raw: string): string {
222
+ const parsed = parseSymbol(raw);
223
+ if ('kind' in parsed && parsed.kind === 'local') {
224
+ return `local:${parsed.id}`;
225
+ }
226
+
227
+ const sym = parsed as ScipSymbol;
228
+ if (sym.descriptors.length === 0) return sym.raw;
229
+
230
+ const parts: string[] = [];
231
+ for (const desc of sym.descriptors) {
232
+ // Strip file extensions from namespace descriptors (the file path parts)
233
+ let name = desc.name;
234
+ if (desc.suffix === 'namespace') {
235
+ // Remove common file extensions
236
+ name = name
237
+ .replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '')
238
+ .replace(/\.(py|pyi)$/, '')
239
+ .replace(/\.(rs)$/, '')
240
+ .replace(/\.(java|scala|kt|kts)$/, '')
241
+ .replace(/\.(rb)$/, '')
242
+ .replace(/\.(go)$/, '')
243
+ .replace(/\.(cs|vb)$/, '')
244
+ .replace(/\.(dart)$/, '')
245
+ .replace(/\.(php)$/, '')
246
+ .replace(/\.(c|cc|cpp|cxx|h|hpp)$/, '');
247
+ }
248
+
249
+ // Skip empty names (can happen with trailing suffixes)
250
+ if (!name) continue;
251
+
252
+ // For methods, append () for clarity
253
+ if (desc.suffix === 'method') {
254
+ parts.push(`${name}()`);
255
+ } else {
256
+ parts.push(name);
257
+ }
258
+ }
259
+
260
+ return parts.join(':');
261
+ }
262
+
263
+ /**
264
+ * Extract just the leaf name from a SCIP symbol.
265
+ * Useful when you only need the function/class/variable name without path context.
266
+ */
267
+ export function leafName(raw: string): string {
268
+ const parsed = parseSymbol(raw);
269
+ if ('kind' in parsed && parsed.kind === 'local') {
270
+ return parsed.id;
271
+ }
272
+
273
+ const sym = parsed as ScipSymbol;
274
+ if (sym.descriptors.length === 0) return '';
275
+
276
+ const last = sym.descriptors[sym.descriptors.length - 1]!;
277
+ return last.name;
278
+ }