clast-ai 0.1.2 → 0.2.1

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 (294) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +11 -9
  3. package/README.md +468 -398
  4. package/bench/ab/onair-concept.json +22 -0
  5. package/bench/ab/onair-deadcode.json +7 -0
  6. package/bench/ab/onair-hard.json +32 -0
  7. package/bench/ab/onair-impact.json +17 -0
  8. package/bench/ab/onair-refactors.json +18 -0
  9. package/bench/ab/onair-survey.json +17 -0
  10. package/bench/ab/onair-tasks.json +20 -0
  11. package/bench/ab/onair.json +52 -0
  12. package/bench/ab/vscode-concept.json +22 -0
  13. package/bench/ab/vscode-hard.json +32 -0
  14. package/bench/ab/vscode-impact.json +17 -0
  15. package/bench/ab/vscode-refactors.json +18 -0
  16. package/bench/ab/vscode-tasks.json +20 -0
  17. package/bench/ab/vscode.json +52 -0
  18. package/bench/ab.mjs +213 -0
  19. package/bench/bench.mjs +98 -0
  20. package/bench/resolution.mjs +75 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +266 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/config/defaults.d.ts +12 -2
  25. package/dist/config/defaults.js +38 -31
  26. package/dist/config/defaults.js.map +1 -1
  27. package/dist/config/index.d.ts +1 -1
  28. package/dist/config/index.js +26 -14
  29. package/dist/config/index.js.map +1 -1
  30. package/dist/db/index.d.ts +73 -13
  31. package/dist/db/index.js +481 -39
  32. package/dist/db/index.js.map +1 -1
  33. package/dist/engine.d.ts +15 -0
  34. package/dist/engine.js +28 -0
  35. package/dist/engine.js.map +1 -0
  36. package/dist/indexer/imports.d.ts +6 -0
  37. package/dist/indexer/imports.js +254 -0
  38. package/dist/indexer/imports.js.map +1 -0
  39. package/dist/indexer/index.d.ts +63 -0
  40. package/dist/indexer/index.js +360 -0
  41. package/dist/indexer/index.js.map +1 -0
  42. package/dist/indexer/process.d.ts +22 -0
  43. package/dist/indexer/process.js +42 -0
  44. package/dist/indexer/process.js.map +1 -0
  45. package/dist/indexer/scan.d.ts +43 -0
  46. package/dist/indexer/scan.js +223 -0
  47. package/dist/indexer/scan.js.map +1 -0
  48. package/dist/indexer/worker.d.ts +1 -0
  49. package/dist/indexer/worker.js +23 -0
  50. package/dist/indexer/worker.js.map +1 -0
  51. package/dist/llm/comment-generator.d.ts +17 -19
  52. package/dist/llm/comment-generator.js +46 -80
  53. package/dist/llm/comment-generator.js.map +1 -1
  54. package/dist/llm/index.d.ts +0 -1
  55. package/dist/llm/index.js.map +1 -1
  56. package/dist/llm/prompts.d.ts +0 -1
  57. package/dist/llm/prompts.js +16 -16
  58. package/dist/migrate.d.ts +35 -0
  59. package/dist/migrate.js +194 -0
  60. package/dist/migrate.js.map +1 -0
  61. package/dist/parser/extractor.d.ts +21 -16
  62. package/dist/parser/extractor.js +700 -228
  63. package/dist/parser/extractor.js.map +1 -1
  64. package/dist/parser/index.d.ts +17 -21
  65. package/dist/parser/index.js +115 -171
  66. package/dist/parser/index.js.map +1 -1
  67. package/dist/parser/langs/c.d.ts +3 -0
  68. package/dist/parser/langs/c.js +879 -0
  69. package/dist/parser/langs/c.js.map +1 -0
  70. package/dist/parser/langs/csharp.d.ts +2 -0
  71. package/dist/parser/langs/csharp.js +548 -0
  72. package/dist/parser/langs/csharp.js.map +1 -0
  73. package/dist/parser/langs/dart.d.ts +2 -0
  74. package/dist/parser/langs/dart.js +893 -0
  75. package/dist/parser/langs/dart.js.map +1 -0
  76. package/dist/parser/langs/go.d.ts +2 -0
  77. package/dist/parser/langs/go.js +455 -0
  78. package/dist/parser/langs/go.js.map +1 -0
  79. package/dist/parser/langs/java.d.ts +2 -0
  80. package/dist/parser/langs/java.js +390 -0
  81. package/dist/parser/langs/java.js.map +1 -0
  82. package/dist/parser/langs/php.d.ts +2 -0
  83. package/dist/parser/langs/php.js +486 -0
  84. package/dist/parser/langs/php.js.map +1 -0
  85. package/dist/parser/langs/python.d.ts +2 -0
  86. package/dist/parser/langs/python.js +216 -0
  87. package/dist/parser/langs/python.js.map +1 -0
  88. package/dist/parser/langs/ruby.d.ts +2 -0
  89. package/dist/parser/langs/ruby.js +602 -0
  90. package/dist/parser/langs/ruby.js.map +1 -0
  91. package/dist/parser/langs/rust.d.ts +2 -0
  92. package/dist/parser/langs/rust.js +931 -0
  93. package/dist/parser/langs/rust.js.map +1 -0
  94. package/dist/parser/langs/swift.d.ts +2 -0
  95. package/dist/parser/langs/swift.js +616 -0
  96. package/dist/parser/langs/swift.js.map +1 -0
  97. package/dist/parser/langs/typescript.d.ts +4 -0
  98. package/dist/parser/langs/typescript.js +535 -0
  99. package/dist/parser/langs/typescript.js.map +1 -0
  100. package/dist/parser/registry.d.ts +16 -0
  101. package/dist/parser/registry.js +87 -0
  102. package/dist/parser/registry.js.map +1 -0
  103. package/dist/parser/returns.d.ts +9 -0
  104. package/dist/parser/returns.js +152 -0
  105. package/dist/parser/returns.js.map +1 -0
  106. package/dist/parser/spec.d.ts +165 -0
  107. package/dist/parser/spec.js +188 -0
  108. package/dist/parser/spec.js.map +1 -0
  109. package/dist/query/arity.d.ts +27 -0
  110. package/dist/query/arity.js +187 -0
  111. package/dist/query/arity.js.map +1 -0
  112. package/dist/query/callgraph.d.ts +113 -0
  113. package/dist/query/callgraph.js +332 -0
  114. package/dist/query/callgraph.js.map +1 -0
  115. package/dist/query/format.d.ts +36 -0
  116. package/dist/query/format.js +136 -0
  117. package/dist/query/format.js.map +1 -0
  118. package/dist/query/hierarchy.d.ts +44 -0
  119. package/dist/query/hierarchy.js +441 -0
  120. package/dist/query/hierarchy.js.map +1 -0
  121. package/dist/query/resolve.d.ts +265 -0
  122. package/dist/query/resolve.js +1957 -0
  123. package/dist/query/resolve.js.map +1 -0
  124. package/dist/query/search.d.ts +35 -0
  125. package/dist/query/search.js +379 -0
  126. package/dist/query/search.js.map +1 -0
  127. package/dist/query/words.d.ts +30 -0
  128. package/dist/query/words.js +201 -0
  129. package/dist/query/words.js.map +1 -0
  130. package/dist/server/index.d.ts +1 -3
  131. package/dist/server/index.js +313 -73
  132. package/dist/server/index.js.map +1 -1
  133. package/dist/tools/context.d.ts +35 -0
  134. package/dist/tools/context.js +79 -0
  135. package/dist/tools/context.js.map +1 -0
  136. package/dist/tools/explore.d.ts +51 -0
  137. package/dist/tools/explore.js +779 -0
  138. package/dist/tools/explore.js.map +1 -0
  139. package/dist/tools/impact.d.ts +21 -0
  140. package/dist/tools/impact.js +743 -0
  141. package/dist/tools/impact.js.map +1 -0
  142. package/dist/tools/search.d.ts +59 -0
  143. package/dist/tools/search.js +843 -0
  144. package/dist/tools/search.js.map +1 -0
  145. package/dist/tools/stats.d.ts +26 -0
  146. package/dist/tools/stats.js +604 -0
  147. package/dist/tools/stats.js.map +1 -0
  148. package/dist/types.d.ts +95 -40
  149. package/dist/types.js +9 -1
  150. package/dist/types.js.map +1 -1
  151. package/dist/version.d.ts +2 -0
  152. package/dist/version.js +3 -0
  153. package/dist/version.js.map +1 -0
  154. package/dist/watcher/index.d.ts +18 -16
  155. package/dist/watcher/index.js +94 -77
  156. package/dist/watcher/index.js.map +1 -1
  157. package/package.json +21 -24
  158. package/plugin/server.mjs +72 -0
  159. package/skills/audit/SKILL.md +37 -0
  160. package/skills/deadcode/SKILL.md +33 -0
  161. package/skills/explore/SKILL.md +54 -0
  162. package/skills/impact/SKILL.md +24 -0
  163. package/skills/status/SKILL.md +11 -0
  164. package/src/cli.ts +265 -0
  165. package/src/config/defaults.ts +50 -32
  166. package/src/config/index.ts +23 -14
  167. package/src/db/index.ts +540 -43
  168. package/src/engine.ts +30 -0
  169. package/src/indexer/imports.ts +241 -0
  170. package/src/indexer/index.ts +369 -0
  171. package/src/indexer/process.ts +63 -0
  172. package/src/indexer/scan.ts +226 -0
  173. package/src/indexer/worker.ts +24 -0
  174. package/src/llm/comment-generator.ts +52 -103
  175. package/src/llm/index.ts +80 -80
  176. package/src/llm/prompts.ts +95 -95
  177. package/src/migrate.ts +213 -0
  178. package/src/parser/extractor.ts +643 -256
  179. package/src/parser/index.ts +105 -205
  180. package/src/parser/langs/c.ts +860 -0
  181. package/src/parser/langs/csharp.ts +519 -0
  182. package/src/parser/langs/dart.ts +820 -0
  183. package/src/parser/langs/go.ts +437 -0
  184. package/src/parser/langs/java.ts +361 -0
  185. package/src/parser/langs/php.ts +450 -0
  186. package/src/parser/langs/python.ts +206 -0
  187. package/src/parser/langs/ruby.ts +554 -0
  188. package/src/parser/langs/rust.ts +904 -0
  189. package/src/parser/langs/swift.ts +590 -0
  190. package/src/parser/langs/typescript.ts +499 -0
  191. package/src/parser/registry.ts +88 -0
  192. package/src/parser/returns.ts +137 -0
  193. package/src/parser/spec.ts +309 -0
  194. package/src/query/arity.ts +173 -0
  195. package/src/query/callgraph.ts +473 -0
  196. package/src/query/format.ts +136 -0
  197. package/src/query/hierarchy.ts +447 -0
  198. package/src/query/resolve.ts +1972 -0
  199. package/src/query/search.ts +430 -0
  200. package/src/query/words.ts +183 -0
  201. package/src/server/index.ts +390 -79
  202. package/src/tools/context.ts +82 -0
  203. package/src/tools/explore.ts +860 -0
  204. package/src/tools/impact.ts +748 -0
  205. package/src/tools/search.ts +886 -0
  206. package/src/tools/stats.ts +633 -0
  207. package/src/types.ts +113 -45
  208. package/src/version.ts +2 -0
  209. package/src/watcher/index.ts +82 -88
  210. package/tsconfig.json +6 -3
  211. package/.mcp.json +0 -8
  212. package/commands/clast-status.md +0 -9
  213. package/dist/config/defaults.d.ts.map +0 -1
  214. package/dist/config/index.d.ts.map +0 -1
  215. package/dist/db/index.d.ts.map +0 -1
  216. package/dist/db/queries.d.ts +0 -44
  217. package/dist/db/queries.d.ts.map +0 -1
  218. package/dist/db/queries.js +0 -251
  219. package/dist/db/queries.js.map +0 -1
  220. package/dist/db/schema.d.ts +0 -3
  221. package/dist/db/schema.d.ts.map +0 -1
  222. package/dist/db/schema.js +0 -94
  223. package/dist/db/schema.js.map +0 -1
  224. package/dist/db/types.d.ts +0 -44
  225. package/dist/db/types.d.ts.map +0 -1
  226. package/dist/db/types.js +0 -2
  227. package/dist/db/types.js.map +0 -1
  228. package/dist/llm/comment-generator.d.ts.map +0 -1
  229. package/dist/llm/index.d.ts.map +0 -1
  230. package/dist/llm/prompts.d.ts.map +0 -1
  231. package/dist/parser/call-resolver.d.ts +0 -15
  232. package/dist/parser/call-resolver.d.ts.map +0 -1
  233. package/dist/parser/call-resolver.js +0 -16
  234. package/dist/parser/call-resolver.js.map +0 -1
  235. package/dist/parser/extractor.d.ts.map +0 -1
  236. package/dist/parser/index.d.ts.map +0 -1
  237. package/dist/parser/languages.d.ts +0 -11
  238. package/dist/parser/languages.d.ts.map +0 -1
  239. package/dist/parser/languages.js +0 -74
  240. package/dist/parser/languages.js.map +0 -1
  241. package/dist/server/index.d.ts.map +0 -1
  242. package/dist/server/register-tools.d.ts +0 -7
  243. package/dist/server/register-tools.d.ts.map +0 -1
  244. package/dist/server/register-tools.js +0 -17
  245. package/dist/server/register-tools.js.map +0 -1
  246. package/dist/server/tools/clast-call-graph.d.ts +0 -4
  247. package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
  248. package/dist/server/tools/clast-call-graph.js +0 -70
  249. package/dist/server/tools/clast-call-graph.js.map +0 -1
  250. package/dist/server/tools/clast-comment.d.ts +0 -5
  251. package/dist/server/tools/clast-comment.d.ts.map +0 -1
  252. package/dist/server/tools/clast-comment.js +0 -64
  253. package/dist/server/tools/clast-comment.js.map +0 -1
  254. package/dist/server/tools/clast-file-summary.d.ts +0 -4
  255. package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
  256. package/dist/server/tools/clast-file-summary.js +0 -42
  257. package/dist/server/tools/clast-file-summary.js.map +0 -1
  258. package/dist/server/tools/clast-get-context.d.ts +0 -4
  259. package/dist/server/tools/clast-get-context.d.ts.map +0 -1
  260. package/dist/server/tools/clast-get-context.js +0 -80
  261. package/dist/server/tools/clast-get-context.js.map +0 -1
  262. package/dist/server/tools/clast-reindex.d.ts +0 -5
  263. package/dist/server/tools/clast-reindex.d.ts.map +0 -1
  264. package/dist/server/tools/clast-reindex.js +0 -36
  265. package/dist/server/tools/clast-reindex.js.map +0 -1
  266. package/dist/server/tools/clast-search.d.ts +0 -4
  267. package/dist/server/tools/clast-search.d.ts.map +0 -1
  268. package/dist/server/tools/clast-search.js +0 -29
  269. package/dist/server/tools/clast-search.js.map +0 -1
  270. package/dist/server/tools/clast-status.d.ts +0 -5
  271. package/dist/server/tools/clast-status.d.ts.map +0 -1
  272. package/dist/server/tools/clast-status.js +0 -23
  273. package/dist/server/tools/clast-status.js.map +0 -1
  274. package/dist/types.d.ts.map +0 -1
  275. package/dist/watcher/gitignore.d.ts +0 -9
  276. package/dist/watcher/gitignore.d.ts.map +0 -1
  277. package/dist/watcher/gitignore.js +0 -38
  278. package/dist/watcher/gitignore.js.map +0 -1
  279. package/dist/watcher/index.d.ts.map +0 -1
  280. package/skills/clast-explore/SKILL.md +0 -41
  281. package/src/db/queries.ts +0 -332
  282. package/src/db/schema.ts +0 -106
  283. package/src/db/types.ts +0 -47
  284. package/src/parser/call-resolver.ts +0 -17
  285. package/src/parser/languages.ts +0 -86
  286. package/src/server/register-tools.ts +0 -30
  287. package/src/server/tools/clast-call-graph.ts +0 -82
  288. package/src/server/tools/clast-comment.ts +0 -76
  289. package/src/server/tools/clast-file-summary.ts +0 -59
  290. package/src/server/tools/clast-get-context.ts +0 -97
  291. package/src/server/tools/clast-reindex.ts +0 -52
  292. package/src/server/tools/clast-search.ts +0 -38
  293. package/src/server/tools/clast-status.ts +0 -37
  294. package/src/watcher/gitignore.ts +0 -42
@@ -1,95 +1,95 @@
1
- const LANGUAGE_COMMENT_STYLES: Record<string, { docPrefix: string; example: string }> = {
2
- typescript: {
3
- docPrefix: '/**',
4
- example: '/** Calculates the total price including tax. */',
5
- },
6
- tsx: {
7
- docPrefix: '/**',
8
- example: '/** Calculates the total price including tax. */',
9
- },
10
- javascript: {
11
- docPrefix: '/**',
12
- example: '/** Calculates the total price including tax. */',
13
- },
14
- python: {
15
- docPrefix: '"""',
16
- example: '"""Calculates the total price including tax."""',
17
- },
18
- java: {
19
- docPrefix: '/**',
20
- example: '/** Calculates the total price including tax. */',
21
- },
22
- csharp: {
23
- docPrefix: '///',
24
- example: '/// <summary>Calculates the total price including tax.</summary>',
25
- },
26
- go: {
27
- docPrefix: '//',
28
- example: '// CalculateTotal calculates the total price including tax.',
29
- },
30
- rust: {
31
- docPrefix: '///',
32
- example: '/// Calculates the total price including tax.',
33
- },
34
- c: {
35
- docPrefix: '/**',
36
- example: '/** Calculates the total price including tax. */',
37
- },
38
- cpp: {
39
- docPrefix: '/**',
40
- example: '/** Calculates the total price including tax. */',
41
- },
42
- ruby: {
43
- docPrefix: '#',
44
- example: '# Calculates the total price including tax.',
45
- },
46
- php: {
47
- docPrefix: '/**',
48
- example: '/** Calculates the total price including tax. */',
49
- },
50
- };
51
-
52
- export function buildFunctionCommentPrompt(
53
- signature: string,
54
- bodyText: string,
55
- language: string
56
- ): { system: string; user: string } {
57
- const style = LANGUAGE_COMMENT_STYLES[language] ?? LANGUAGE_COMMENT_STYLES['typescript']!;
58
-
59
- const system = `You are a code documentation expert. Generate a concise, clear documentation comment for the given function or method. Use the ${language} documentation style. Output ONLY the comment text (no code fences, no extra explanation). Keep it brief: 1-3 sentences describing what the function does, its parameters, and return value if applicable.`;
60
-
61
- const user = `Language: ${language}
62
- Comment style example: ${style.example}
63
-
64
- Signature:
65
- ${signature}
66
-
67
- Body:
68
- ${bodyText.slice(0, 1500)}
69
-
70
- Generate a documentation comment for this function.`;
71
-
72
- return { system, user };
73
- }
74
-
75
- export function buildClassCommentPrompt(
76
- name: string,
77
- signature: string,
78
- methodNames: string[],
79
- language: string
80
- ): { system: string; user: string } {
81
- const style = LANGUAGE_COMMENT_STYLES[language] ?? LANGUAGE_COMMENT_STYLES['typescript']!;
82
-
83
- const system = `You are a code documentation expert. Generate a concise, clear documentation comment for the given class or type. Use the ${language} documentation style. Output ONLY the comment text (no code fences, no extra explanation). Keep it brief: 1-3 sentences describing the class purpose and responsibilities.`;
84
-
85
- const user = `Language: ${language}
86
- Comment style example: ${style.example}
87
-
88
- Class: ${name}
89
- Signature: ${signature}
90
- Methods: ${methodNames.join(', ') || 'none'}
91
-
92
- Generate a documentation comment for this class.`;
93
-
94
- return { system, user };
95
- }
1
+ const LANGUAGE_COMMENT_STYLES: Record<string, { docPrefix: string; example: string }> = {
2
+ typescript: {
3
+ docPrefix: '/**',
4
+ example: '/** Calculates the total price including tax. */',
5
+ },
6
+ tsx: {
7
+ docPrefix: '/**',
8
+ example: '/** Calculates the total price including tax. */',
9
+ },
10
+ javascript: {
11
+ docPrefix: '/**',
12
+ example: '/** Calculates the total price including tax. */',
13
+ },
14
+ python: {
15
+ docPrefix: '"""',
16
+ example: '"""Calculates the total price including tax."""',
17
+ },
18
+ java: {
19
+ docPrefix: '/**',
20
+ example: '/** Calculates the total price including tax. */',
21
+ },
22
+ csharp: {
23
+ docPrefix: '///',
24
+ example: '/// <summary>Calculates the total price including tax.</summary>',
25
+ },
26
+ go: {
27
+ docPrefix: '//',
28
+ example: '// CalculateTotal calculates the total price including tax.',
29
+ },
30
+ rust: {
31
+ docPrefix: '///',
32
+ example: '/// Calculates the total price including tax.',
33
+ },
34
+ c: {
35
+ docPrefix: '/**',
36
+ example: '/** Calculates the total price including tax. */',
37
+ },
38
+ cpp: {
39
+ docPrefix: '/**',
40
+ example: '/** Calculates the total price including tax. */',
41
+ },
42
+ ruby: {
43
+ docPrefix: '#',
44
+ example: '# Calculates the total price including tax.',
45
+ },
46
+ php: {
47
+ docPrefix: '/**',
48
+ example: '/** Calculates the total price including tax. */',
49
+ },
50
+ };
51
+
52
+ export function buildFunctionCommentPrompt(
53
+ signature: string,
54
+ bodyText: string,
55
+ language: string
56
+ ): { system: string; user: string } {
57
+ const style = LANGUAGE_COMMENT_STYLES[language] ?? LANGUAGE_COMMENT_STYLES['typescript']!;
58
+
59
+ const system = `You are a code documentation expert. Generate a concise, clear documentation comment for the given function or method. Use the ${language} documentation style. Output ONLY the comment text (no code fences, no extra explanation). Keep it brief: 1-3 sentences describing what the function does, its parameters, and return value if applicable.`;
60
+
61
+ const user = `Language: ${language}
62
+ Comment style example: ${style.example}
63
+
64
+ Signature:
65
+ ${signature}
66
+
67
+ Body:
68
+ ${bodyText.slice(0, 1500)}
69
+
70
+ Generate a documentation comment for this function.`;
71
+
72
+ return { system, user };
73
+ }
74
+
75
+ export function buildClassCommentPrompt(
76
+ name: string,
77
+ signature: string,
78
+ methodNames: string[],
79
+ language: string
80
+ ): { system: string; user: string } {
81
+ const style = LANGUAGE_COMMENT_STYLES[language] ?? LANGUAGE_COMMENT_STYLES['typescript']!;
82
+
83
+ const system = `You are a code documentation expert. Generate a concise, clear documentation comment for the given class or type. Use the ${language} documentation style. Output ONLY the comment text (no code fences, no extra explanation). Keep it brief: 1-3 sentences describing the class purpose and responsibilities.`;
84
+
85
+ const user = `Language: ${language}
86
+ Comment style example: ${style.example}
87
+
88
+ Class: ${name}
89
+ Signature: ${signature}
90
+ Methods: ${methodNames.join(', ') || 'none'}
91
+
92
+ Generate a documentation comment for this class.`;
93
+
94
+ return { system, user };
95
+ }
package/src/migrate.ts ADDED
@@ -0,0 +1,213 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { VERSION } from './version.js';
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // clast-ai migrate: remove what older installs left behind
9
+ // ---------------------------------------------------------------------------
10
+ //
11
+ // Before the plugin, clast was registered by hand (`claude mcp add`) or by the old VS Code extension
12
+ // (`.claude/settings.local.json`). With the plugin installed those registrations start a second copy
13
+ // of the server and list every tool twice. This finds them, and the v0.1 index file, and removes them.
14
+
15
+ export interface MigrateOptions {
16
+ /** Make the changes. Default: only report. */
17
+ apply?: boolean;
18
+ /** Run from the plugin at session start: once per version, silent, only when the plugin is installed. */
19
+ auto?: boolean;
20
+ /** Remove hand-made registrations even when the plugin is not installed. */
21
+ force?: boolean;
22
+ home?: string;
23
+ cwd?: string;
24
+ /** Replaces `claude mcp remove` (tests). Returns true on success. */
25
+ remove?: (name: string, scope: 'user' | 'local', cwd: string) => boolean;
26
+ }
27
+
28
+ export type Action =
29
+ | { kind: 'mcp'; scope: 'user' | 'local'; name: string; dir: string; command: string }
30
+ | { kind: 'settings'; file: string; name: string }
31
+ | { kind: 'index'; files: string[] }
32
+ | { kind: 'shared'; file: string; name: string };
33
+
34
+ interface ServerEntry {
35
+ command?: string;
36
+ args?: unknown[];
37
+ }
38
+
39
+ /** A registration of this server: by name, by the `clast-ai` binary, or by a path to its cli.js. */
40
+ function isClast(_name: string, entry: ServerEntry | undefined): boolean {
41
+ if (!entry || typeof entry !== 'object') return false;
42
+ const command = String(entry.command ?? '');
43
+ const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
44
+ if (/(^|[\\/])clast-ai(\.cmd|\.exe)?$/i.test(command)) return true;
45
+ if (args.some(a => /clast[^\\/]*[\\/]dist[\\/]cli\.js$/i.test(a))) return true;
46
+ // `npx clast-ai`, `npx -y clast-ai@0.1`
47
+ if (/(^|[\\/])npx(\.cmd)?$/i.test(command) && args.some(a => /^clast-ai(@|$)/.test(a))) return true;
48
+ return false;
49
+ }
50
+
51
+ function readJson(path: string): Record<string, unknown> | null {
52
+ try {
53
+ return JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function describe(entry: ServerEntry): string {
60
+ return [entry.command ?? '', ...(Array.isArray(entry.args) ? entry.args.map(String) : [])].join(' ').trim();
61
+ }
62
+
63
+ /** Is the clast plugin installed for this user? */
64
+ export function pluginInstalled(home: string): boolean {
65
+ const installed = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'));
66
+ const plugins = (installed?.plugins ?? {}) as Record<string, unknown>;
67
+ return Object.keys(plugins).some(id => /^clast@/.test(id));
68
+ }
69
+
70
+ /** Everything an older install left behind, without changing anything. */
71
+ export function planMigration(home: string, cwd: string): Action[] {
72
+ const actions: Action[] = [];
73
+ const config = readJson(join(home, '.claude.json'));
74
+ const servers = (config?.mcpServers ?? {}) as Record<string, ServerEntry>;
75
+ for (const [name, entry] of Object.entries(servers)) {
76
+ if (isClast(name, entry)) actions.push({ kind: 'mcp', scope: 'user', name, dir: cwd, command: describe(entry) });
77
+ }
78
+ const projects = (config?.projects ?? {}) as Record<string, { mcpServers?: Record<string, ServerEntry> }>;
79
+ for (const [dir, project] of Object.entries(projects)) {
80
+ for (const [name, entry] of Object.entries(project?.mcpServers ?? {})) {
81
+ if (isClast(name, entry)) actions.push({ kind: 'mcp', scope: 'local', name, dir, command: describe(entry) });
82
+ }
83
+ }
84
+ // Written by the old VS Code extension.
85
+ const settingsFile = join(cwd, '.claude', 'settings.local.json');
86
+ const settings = readJson(settingsFile);
87
+ for (const [name, entry] of Object.entries((settings?.mcpServers ?? {}) as Record<string, ServerEntry>)) {
88
+ if (isClast(name, entry)) actions.push({ kind: 'settings', file: settingsFile, name });
89
+ }
90
+ // The v0.1 index. v0.2 keeps its own file (.clast/index.db) and never reads this one.
91
+ const old = ['clast.db', 'clast.db-wal', 'clast.db-shm'].map(f => join(cwd, '.clast', f)).filter(f => existsSync(f));
92
+ if (old.length) actions.push({ kind: 'index', files: old });
93
+ // A team's shared file is theirs to change.
94
+ const sharedFile = join(cwd, '.mcp.json');
95
+ const shared = readJson(sharedFile);
96
+ for (const [name, entry] of Object.entries((shared?.mcpServers ?? {}) as Record<string, ServerEntry>)) {
97
+ if (isClast(name, entry)) actions.push({ kind: 'shared', file: sharedFile, name });
98
+ }
99
+ return actions;
100
+ }
101
+
102
+ function claudeRemove(name: string, scope: 'user' | 'local', cwd: string): boolean {
103
+ const windows = process.platform === 'win32';
104
+ // Fixed words plus a server name restricted to safe characters: nothing to escape.
105
+ if (!/^[\w.-]+$/.test(name)) return false;
106
+ const result = windows
107
+ ? spawnSync(`claude mcp remove ${name} -s ${scope}`, { cwd, shell: true, stdio: 'ignore', windowsHide: true, timeout: 30_000 })
108
+ : spawnSync('claude', ['mcp', 'remove', name, '-s', scope], { cwd, stdio: 'ignore', timeout: 30_000 });
109
+ return result.status === 0;
110
+ }
111
+
112
+ function line(a: Action): string {
113
+ switch (a.kind) {
114
+ case 'mcp':
115
+ return a.scope === 'user'
116
+ ? `server "${a.name}" registered for all projects (${a.command})`
117
+ : `server "${a.name}" registered for ${a.dir} (${a.command})`;
118
+ case 'settings':
119
+ return `server "${a.name}" in ${a.file} (written by the old VS Code extension)`;
120
+ case 'index':
121
+ return `v0.1 index: ${a.files.join(', ')}`;
122
+ case 'shared':
123
+ return `server "${a.name}" in ${a.file} (shared with your team: not changed; remove it there if everyone uses the plugin)`;
124
+ }
125
+ }
126
+
127
+ export async function migrate(opts: MigrateOptions = {}): Promise<string> {
128
+ const home = opts.home ?? homedir();
129
+ const cwd = resolve(opts.cwd ?? process.env.CLAST_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
130
+ const stateDir = process.env.CLAUDE_PLUGIN_DATA || join(home, '.clast-ai');
131
+ const marker = join(stateDir, 'migrated.json');
132
+ const plugin = pluginInstalled(home);
133
+
134
+ if (opts.auto) {
135
+ // Only the plugin starts this, but be sure: without it the registrations are the only install there is.
136
+ if (!plugin) return '';
137
+ const done = readJson(marker);
138
+ const fresh = done?.version !== VERSION;
139
+ // The registrations are checked once per version; a project's old index whenever it is seen.
140
+ const plan = planMigration(home, cwd).filter(a => a.kind === 'index' || (fresh && a.kind !== 'shared'));
141
+ const report = plan.length ? apply(plan, opts) : [];
142
+ try {
143
+ mkdirSync(stateDir, { recursive: true });
144
+ if (fresh) writeFileSync(marker, JSON.stringify({ version: VERSION, at: new Date().toISOString() }) + '\n');
145
+ if (report.length) {
146
+ appendFileSync(join(stateDir, 'migrate.log'), `${new Date().toISOString()} clast ${VERSION}\n${report.map(r => ' ' + r).join('\n')}\n`);
147
+ }
148
+ } catch {
149
+ // A read-only home directory must not stop the server.
150
+ }
151
+ return report.join('\n');
152
+ }
153
+
154
+ const plan = planMigration(home, cwd);
155
+ const out: string[] = [];
156
+ const removable = plan.filter(a => a.kind !== 'shared');
157
+ const blocked = !plugin && !opts.force;
158
+ if (plan.length === 0) {
159
+ out.push('nothing to migrate: no older clast registration or index found.');
160
+ } else if (!opts.apply) {
161
+ out.push(`found ${plan.length} leftover${plan.length === 1 ? '' : 's'} of an older clast install:`);
162
+ for (const a of plan) out.push(` - ${line(a)}`);
163
+ if (removable.length) {
164
+ out.push(
165
+ blocked
166
+ ? 'The clast plugin is not installed, so the registrations above are your only install. Install the plugin ' +
167
+ '(/plugin marketplace add xls/clast, /plugin install clast@clast) and run this again, or pass --force.'
168
+ : 'Run `clast-ai migrate --apply` to remove them (the plugin provides the server).'
169
+ );
170
+ }
171
+ } else {
172
+ const todo = blocked ? removable.filter(a => a.kind === 'index') : removable;
173
+ out.push(...apply(todo, opts));
174
+ for (const a of plan.filter(p => p.kind === 'shared')) out.push(`left alone: ${line(a)}`);
175
+ if (blocked && removable.some(a => a.kind !== 'index')) {
176
+ out.push('registrations kept: the clast plugin is not installed (pass --force to remove them anyway).');
177
+ }
178
+ if (todo.some(a => a.kind === 'mcp' || a.kind === 'settings')) out.push('Restart Claude Code to apply.');
179
+ }
180
+ if (!plugin) out.push('plugin: not installed.');
181
+ return out.join('\n');
182
+ }
183
+
184
+ function apply(plan: Action[], opts: MigrateOptions): string[] {
185
+ const remove = opts.remove ?? claudeRemove;
186
+ const report: string[] = [];
187
+ for (const a of plan) {
188
+ try {
189
+ if (a.kind === 'mcp') {
190
+ if (a.scope === 'local' && !existsSync(a.dir)) {
191
+ report.push(`skipped (directory no longer exists): ${line(a)}`);
192
+ continue;
193
+ }
194
+ report.push(`${remove(a.name, a.scope, a.dir) ? 'removed' : 'could not remove (is the claude CLI on PATH?)'}: ${line(a)}`);
195
+ } else if (a.kind === 'settings') {
196
+ const settings = readJson(a.file);
197
+ const servers = settings?.mcpServers as Record<string, unknown> | undefined;
198
+ if (settings && servers && a.name in servers) {
199
+ delete servers[a.name];
200
+ if (Object.keys(servers).length === 0) delete settings.mcpServers;
201
+ writeFileSync(a.file, JSON.stringify(settings, null, 2) + '\n');
202
+ report.push(`removed: ${line(a)}`);
203
+ }
204
+ } else if (a.kind === 'index') {
205
+ for (const f of a.files) rmSync(f, { force: true });
206
+ report.push(`deleted: ${line(a)}`);
207
+ }
208
+ } catch (err) {
209
+ report.push(`failed: ${line(a)}: ${err instanceof Error ? err.message : err}`);
210
+ }
211
+ }
212
+ return report;
213
+ }