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,226 +1,126 @@
1
- import TreeSitter from 'web-tree-sitter';
2
- import { createHash } from 'node:crypto';
3
- import { readFileSync, readdirSync, statSync } from 'node:fs';
4
- import { join, extname, relative } from 'node:path';
5
- import { getLanguageForExtension, getGrammarWasmPath, getSupportedExtensions } from './languages.js';
6
- import { extractFromTree } from './extractor.js';
7
- import { resolveCallEdges } from './call-resolver.js';
8
- import type { ClastDatabase } from '../db/index.js';
9
- import type { ClastConfig } from '../config/index.js';
10
- import type { AstNodeRow } from '../db/types.js';
11
-
12
- export class ParserManager {
13
- private parser: TreeSitter | null = null;
14
- private languages = new Map<string, TreeSitter.Language>();
15
- private config: ClastConfig;
16
- private initialized = false;
17
-
18
- constructor(config: ClastConfig) {
19
- this.config = config;
20
- }
21
-
22
- async init(): Promise<void> {
23
- if (this.initialized) return;
24
- await TreeSitter.init();
25
- this.parser = new TreeSitter();
26
- this.initialized = true;
27
- }
28
-
29
- private async getLanguage(langName: string): Promise<TreeSitter.Language | null> {
30
- if (this.languages.has(langName)) {
31
- return this.languages.get(langName)!;
32
- }
33
-
34
- const wasmPath = getGrammarWasmPath(langName);
35
- if (!wasmPath) {
36
- console.error(`[clast] No WASM grammar found for ${langName}`);
37
- return null;
38
- }
39
-
40
- try {
41
- const language = await TreeSitter.Language.load(wasmPath);
42
- this.languages.set(langName, language);
43
- return language;
44
- } catch (err) {
45
- console.error(`[clast] Failed to load grammar for ${langName}:`, err);
46
- return null;
47
- }
1
+ import { Parser, Language } from 'web-tree-sitter';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, extname, join } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import type { Extraction } from '../types.js';
7
+ import type { LangSpec } from './spec.js';
8
+ import { compileSpec, extract, type CompiledSpec } from './extractor.js';
9
+ import { specById, specForExtension } from './registry.js';
10
+
11
+ let initPromise: Promise<void> | null = null;
12
+
13
+ function grammarPath(grammar: string): string | null {
14
+ const file = `tree-sitter-${grammar}.wasm`;
15
+ const candidates: string[] = [];
16
+ if (process.env.CLAST_GRAMMARS_DIR) candidates.push(join(process.env.CLAST_GRAMMARS_DIR, file));
17
+ try {
18
+ const require = createRequire(import.meta.url);
19
+ const pkg = dirname(require.resolve('@repomix/tree-sitter-wasms/package.json'));
20
+ candidates.push(join(pkg, 'out', file));
21
+ } catch {
22
+ // package not resolvable; fall through to local grammar dirs
48
23
  }
49
-
50
- async parseSource(source: string, langName: string): Promise<TreeSitter.Tree | null> {
51
- await this.init();
52
- if (!this.parser) return null;
53
-
54
- const language = await this.getLanguage(langName);
55
- if (!language) return null;
56
-
57
- this.parser.setLanguage(language);
58
- return this.parser.parse(source);
24
+ const here = dirname(fileURLToPath(import.meta.url));
25
+ candidates.push(join(here, '..', '..', 'grammars', file));
26
+ for (const c of candidates) {
27
+ if (existsSync(c)) return c;
59
28
  }
29
+ return null;
30
+ }
60
31
 
61
- async parseAndStore(filePath: string, projectRoot: string, db: ClastDatabase): Promise<boolean> {
62
- const ext = extname(filePath);
63
- const langEntry = getLanguageForExtension(ext);
64
- if (!langEntry) return false;
65
-
66
- const relPath = relative(projectRoot, filePath).replace(/\\/g, '/');
67
-
68
- let source: string;
69
- try {
70
- source = readFileSync(filePath, 'utf-8');
71
- } catch {
72
- return false;
73
- }
74
-
75
- const hash = createHash('sha256').update(source).digest('hex');
76
- const existingHash = db.queries.getFileHash(relPath);
77
- if (existingHash === hash) return false;
78
-
79
- const tree = await this.parseSource(source, langEntry.name);
80
- if (!tree) return false;
81
-
82
- const result = extractFromTree(
83
- tree,
84
- relPath,
85
- langEntry.name,
86
- source,
87
- this.config.maxBodySize
88
- );
89
-
90
- // Delete old data for this file and upsert file record BEFORE inserting nodes
91
- // (nodes have a FK to files.file_path)
92
- db.queries.deleteNodesForFile(relPath);
93
- db.queries.upsertFile(relPath, hash, langEntry.name, result.nodes.length);
94
-
95
- // Insert nodes
96
- const nodeRows: Omit<AstNodeRow, 'id'>[] = result.nodes.map(n => ({
97
- file_path: n.filePath,
98
- name: n.name,
99
- type: n.type,
100
- signature: n.signature,
101
- start_line: n.startLine,
102
- end_line: n.endLine,
103
- start_col: n.startCol,
104
- end_col: n.endCol,
105
- parent_id: n.parentId,
106
- language: n.language,
107
- body_text: n.bodyText,
108
- }));
109
-
110
- const nodeIds = db.queries.insertNodes(nodeRows);
111
-
112
- // Insert edges
113
- const edgeRows = result.edges
114
- .filter(e => nodeIds[e.callerNodeIndex] !== undefined)
115
- .map(e => ({
116
- caller_id: nodeIds[e.callerNodeIndex]!,
117
- callee_id: null as number | null,
118
- callee_name: e.calleeName,
119
- file_path: relPath,
120
- line: e.line,
121
- }));
122
-
123
- if (edgeRows.length > 0) {
124
- db.queries.insertEdges(edgeRows);
125
- }
126
-
127
- // Insert comments
128
- for (const c of result.comments) {
129
- const dbId = nodeIds[c.nodeIndex];
130
- if (dbId !== undefined) {
131
- db.queries.insertComment(dbId, c.text, c.source);
132
- }
133
- }
134
-
135
- // Save DB periodically
136
- db.save();
32
+ /**
33
+ * Owns one tree-sitter parser and lazily loaded grammars. One instance per
34
+ * thread (the main thread and each indexing worker have their own).
35
+ */
36
+ export class SourceParser {
37
+ private parser: Parser | null = null;
38
+ private compiled = new Map<string, CompiledSpec | null>();
137
39
 
138
- return true;
40
+ async init(): Promise<void> {
41
+ initPromise ??= Parser.init();
42
+ await initPromise;
43
+ this.parser ??= new Parser();
139
44
  }
140
45
 
141
- async fullIndex(
142
- projectRoot: string,
143
- db: ClastDatabase,
144
- onProgress?: (file: string, current: number, total: number) => void
145
- ): Promise<{ filesProcessed: number; nodesFound: number; edgesResolved: number; durationMs: number }> {
46
+ /** Load (once) and compile the spec's grammar. Returns null if unavailable. */
47
+ async load(spec: LangSpec): Promise<CompiledSpec | null> {
48
+ const cached = this.compiled.get(spec.id);
49
+ if (cached !== undefined) return cached;
146
50
  await this.init();
147
-
148
- const start = Date.now();
149
- const supportedExts = new Set(getSupportedExtensions(this.config.languages));
150
- const files = this.collectFiles(projectRoot, supportedExts);
151
-
152
- let filesProcessed = 0;
153
- for (let i = 0; i < files.length; i++) {
154
- const file = files[i]!;
155
- onProgress?.(file, i + 1, files.length);
156
- if (await this.parseAndStore(file, projectRoot, db)) {
157
- filesProcessed++;
51
+ let result: CompiledSpec | null = null;
52
+ const path = grammarPath(spec.grammar);
53
+ if (!path) {
54
+ console.error(`[clast] No grammar found for ${spec.id} (tree-sitter-${spec.grammar}.wasm)`);
55
+ } else {
56
+ try {
57
+ const language = await Language.load(path);
58
+ result = compileSpec(spec, language);
59
+ if (result.unknownTypes.length && process.env.CLAST_DEBUG) {
60
+ console.error(`[clast] ${spec.id}: node types not in grammar: ${result.unknownTypes.join(', ')}`);
61
+ }
62
+ } catch (err) {
63
+ console.error(`[clast] Failed to load grammar for ${spec.id}:`, err);
158
64
  }
159
65
  }
160
-
161
- const edgesResolved = resolveCallEdges(db.queries);
162
-
163
- // Final save
164
- db.save();
165
-
166
- const status = db.queries.getStatus();
167
- return {
168
- filesProcessed,
169
- nodesFound: status.total_nodes,
170
- edgesResolved,
171
- durationMs: Date.now() - start,
172
- };
66
+ this.compiled.set(spec.id, result);
67
+ return result;
173
68
  }
174
69
 
175
- async incrementalUpdate(filePath: string, projectRoot: string, db: ClastDatabase): Promise<boolean> {
176
- const parsed = await this.parseAndStore(filePath, projectRoot, db);
177
- if (parsed) {
178
- resolveCallEdges(db.queries);
179
- db.save();
70
+ /** Resolve the spec for a path (+ content sniffing), or undefined. */
71
+ specFor(path: string, source?: string): LangSpec | undefined {
72
+ const spec = specForExtension(extname(path));
73
+ if (spec && spec.sniff && source !== undefined) {
74
+ const alt = spec.sniff(source, path);
75
+ if (alt) return specById(alt) ?? spec;
180
76
  }
181
- return parsed;
182
- }
183
-
184
- removeFile(filePath: string, projectRoot: string, db: ClastDatabase): void {
185
- const relPath = relative(projectRoot, filePath).replace(/\\/g, '/');
186
- db.queries.deleteFile(relPath);
187
- db.save();
77
+ return spec;
188
78
  }
189
79
 
190
- private collectFiles(dir: string, supportedExts: Set<string>, depth: number = 0): string[] {
191
- if (depth > 20) return [];
192
-
193
- const files: string[] = [];
194
- let entries: string[];
195
-
80
+ /** Parse and extract a single source text. Returns null for unsupported files. */
81
+ async extract(path: string, source: string): Promise<Extraction | null> {
82
+ const spec = this.specFor(path, source);
83
+ if (!spec) return null;
84
+ const compiled = await this.load(spec);
85
+ if (!compiled || !this.parser) return null;
86
+ this.parser.setLanguage(compiled.language);
87
+ const tree = this.parser.parse(source);
88
+ if (!tree) return null;
196
89
  try {
197
- entries = readdirSync(dir);
198
- } catch {
199
- return files;
90
+ return extract(tree, source, compiled);
91
+ } finally {
92
+ tree.delete();
200
93
  }
94
+ }
201
95
 
202
- for (const entry of entries) {
203
- if (this.config.ignoredPaths.includes(entry)) continue;
204
- if (entry.startsWith('.')) continue;
205
-
206
- const fullPath = join(dir, entry);
207
- let stat;
208
- try {
209
- stat = statSync(fullPath);
210
- } catch {
211
- continue;
96
+ /** Debug helper: S-expression-ish dump of the tree with field names. */
97
+ async dumpTree(path: string, source: string, maxDepth = 40): Promise<string> {
98
+ const spec = this.specFor(path, source);
99
+ if (!spec) return `unsupported file type: ${path}`;
100
+ const compiled = await this.load(spec);
101
+ if (!compiled || !this.parser) return `grammar unavailable for ${spec.id}`;
102
+ this.parser.setLanguage(compiled.language);
103
+ const tree = this.parser.parse(source);
104
+ if (!tree) return 'parse failed';
105
+ const out: string[] = [];
106
+ const cursor = tree.walk();
107
+ const walk = (depth: number): void => {
108
+ if (cursor.nodeIsNamed) {
109
+ const f = cursor.currentFieldName;
110
+ const pos = cursor.startPosition;
111
+ let line = `${' '.repeat(depth)}${f ? f + ': ' : ''}${cursor.nodeType} [${pos.row + 1}:${pos.column}]`;
112
+ const node = cursor.currentNode;
113
+ if (node.namedChildCount === 0) line += ` ${JSON.stringify(node.text.slice(0, 60))}`;
114
+ out.push(line);
212
115
  }
213
-
214
- if (stat.isDirectory()) {
215
- files.push(...this.collectFiles(fullPath, supportedExts, depth + 1));
216
- } else if (stat.isFile()) {
217
- const ext = extname(entry);
218
- if (supportedExts.has(ext)) {
219
- files.push(fullPath);
220
- }
116
+ if (depth < maxDepth && cursor.gotoFirstChild()) {
117
+ do walk(depth + (cursor.nodeIsNamed ? 1 : 1)); while (cursor.gotoNextSibling());
118
+ cursor.gotoParent();
221
119
  }
222
- }
223
-
224
- return files;
120
+ };
121
+ walk(0);
122
+ cursor.delete();
123
+ tree.delete();
124
+ return out.join('\n');
225
125
  }
226
126
  }