@mnemonik/shared 6.50.0 → 6.51.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 (60) hide show
  1. package/dist/ast/astChunker.d.ts +124 -0
  2. package/dist/ast/astChunker.d.ts.map +1 -0
  3. package/dist/ast/astChunker.js +559 -0
  4. package/dist/ast/astChunker.js.map +1 -0
  5. package/dist/ast/grammars.d.ts +170 -0
  6. package/dist/ast/grammars.d.ts.map +1 -0
  7. package/dist/ast/grammars.js +411 -0
  8. package/dist/ast/grammars.js.map +1 -0
  9. package/dist/codeScanner.d.ts +59 -0
  10. package/dist/codeScanner.d.ts.map +1 -1
  11. package/dist/codeScanner.js +259 -2
  12. package/dist/codeScanner.js.map +1 -1
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/index.js.map +1 -1
  17. package/package.json +3 -2
  18. package/queries/bash.tags.scm +7 -0
  19. package/queries/groovy.tags.scm +15 -0
  20. package/queries/powershell.tags.scm +14 -0
  21. package/scripts/vendor-grammars.ts +317 -0
  22. package/src/ast/astChunker.ts +661 -0
  23. package/src/ast/grammars.ts +490 -0
  24. package/src/codeScanner.ts +273 -3
  25. package/src/index.ts +22 -0
  26. package/wasm/bash.tags.scm +7 -0
  27. package/wasm/bash.wasm +0 -0
  28. package/wasm/c-sharp.tags.scm +23 -0
  29. package/wasm/c-sharp.wasm +0 -0
  30. package/wasm/c.tags.scm +9 -0
  31. package/wasm/c.wasm +0 -0
  32. package/wasm/cpp.tags.scm +15 -0
  33. package/wasm/cpp.wasm +0 -0
  34. package/wasm/elixir.tags.scm +54 -0
  35. package/wasm/elixir.wasm +0 -0
  36. package/wasm/go.tags.scm +42 -0
  37. package/wasm/go.wasm +0 -0
  38. package/wasm/groovy.tags.scm +15 -0
  39. package/wasm/groovy.wasm +0 -0
  40. package/wasm/java.tags.scm +20 -0
  41. package/wasm/java.wasm +0 -0
  42. package/wasm/javascript.tags.scm +99 -0
  43. package/wasm/javascript.wasm +0 -0
  44. package/wasm/php.tags.scm +40 -0
  45. package/wasm/php.wasm +0 -0
  46. package/wasm/powershell.tags.scm +14 -0
  47. package/wasm/powershell.wasm +0 -0
  48. package/wasm/python.tags.scm +14 -0
  49. package/wasm/python.wasm +0 -0
  50. package/wasm/ruby.tags.scm +64 -0
  51. package/wasm/ruby.wasm +0 -0
  52. package/wasm/rust.tags.scm +60 -0
  53. package/wasm/rust.wasm +0 -0
  54. package/wasm/scala.tags.scm +66 -0
  55. package/wasm/scala.wasm +0 -0
  56. package/wasm/solidity.tags.scm +43 -0
  57. package/wasm/solidity.wasm +0 -0
  58. package/wasm/tsx.wasm +0 -0
  59. package/wasm/typescript.tags.scm +23 -0
  60. package/wasm/typescript.wasm +0 -0
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,GAGpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,YAAY,EACZ,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,GAChB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,GAGrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GAKnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,YAAY,GAGb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,YAAY,EACZ,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,GAChB,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemonik/shared",
3
- "version": "6.50.0",
3
+ "version": "6.51.0",
4
4
  "description": "Shared constants and utilities for Mnemonik packages",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,8 @@
28
28
  "author": "Anthony",
29
29
  "license": "UNLICENSED",
30
30
  "dependencies": {
31
- "ignore": "^7.0.6"
31
+ "ignore": "^7.0.6",
32
+ "web-tree-sitter": "0.26.11"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^20.19.43",
@@ -0,0 +1,7 @@
1
+ ; Hand-written: tree-sitter-bash ships no queries/tags.scm.
2
+ ;
3
+ ; Node names verified by parsing a sample with the vendored bash.wasm rather than
4
+ ; taken from documentation — `function_definition` carries its name in a `name`
5
+ ; field typed `word`, covering both `foo() { }` and `function foo { }`.
6
+ (function_definition
7
+ name: (word) @name) @definition.function
@@ -0,0 +1,15 @@
1
+ ; Hand-written: tree-sitter-groovy ships no queries/tags.scm.
2
+ ;
3
+ ; Node names verified against the vendored groovy.wasm. Groovy distinguishes a
4
+ ; top-level `function_definition` from a `method_declaration` inside a
5
+ ; `class_body`, and both name via an `identifier` field — so methods get
6
+ ; definition.method and keep their own name, which is the defect this plan
7
+ ; exists to fix for every language.
8
+ (class_declaration
9
+ name: (identifier) @name) @definition.class
10
+
11
+ (method_declaration
12
+ name: (identifier) @name) @definition.method
13
+
14
+ (function_definition
15
+ name: (identifier) @name) @definition.function
@@ -0,0 +1,14 @@
1
+ ; Hand-written: tree-sitter-powershell ships no queries/tags.scm.
2
+ ;
3
+ ; Node names verified against the vendored powershell.wasm. PowerShell names
4
+ ; differ from every other grammar here: a function's name node is
5
+ ; `function_name` (not `identifier`), a class's is `simple_name`, and methods are
6
+ ; `class_method_definition`. Guessing these from another language's convention
7
+ ; would have produced a query that compiles and matches nothing.
8
+ (function_statement
9
+ (function_name) @name) @definition.function
10
+
11
+ (class_statement
12
+ (simple_name) @name) @definition.class
13
+
14
+ (class_method_definition) @definition.method
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Vendor tree-sitter grammar artifacts into packages/shared/wasm/.
3
+ *
4
+ * WHY `npm pack` AND NOT devDependencies. The first attempt added the grammar
5
+ * packages to this workspace's devDependencies and npm refused the tree:
6
+ * tree-sitter-groovy declares a `peerOptional` on the NATIVE tree-sitter
7
+ * (^0.21.1), which this design deliberately never installs. Rather than force
8
+ * the resolution, note that these packages have no business in the dependency
9
+ * graph at all — we want two files out of each of them, at build time, on our
10
+ * machine. `npm pack` fetches a tarball and performs NO dependency resolution,
11
+ * so there is no peer graph to satisfy, `npm ci` stays fast for every developer
12
+ * and every CI run, and nobody downloads ~283 MB of grammars to build the repo.
13
+ *
14
+ * What ships to a user: the extracted `.wasm` and `.tags.scm` files, committed
15
+ * here and published inside @mnemonik/shared. A user's install therefore pulls
16
+ * no grammar package, runs no postinstall, needs no compiler, and fetches
17
+ * nothing at runtime — which is the whole reason this plan chose WebAssembly
18
+ * over the native bindings.
19
+ *
20
+ * Run: npx tsx packages/shared/scripts/vendor-grammars.ts [--check]
21
+ * --check verify every manifest entry is present and non-empty; write nothing.
22
+ * This is what CI should run to catch a grammar that silently stopped
23
+ * shipping an artifact.
24
+ */
25
+
26
+ import { execFileSync } from 'node:child_process';
27
+ import {
28
+ copyFileSync,
29
+ existsSync,
30
+ mkdirSync,
31
+ mkdtempSync,
32
+ readFileSync,
33
+ readdirSync,
34
+ rmSync,
35
+ statSync,
36
+ } from 'node:fs';
37
+ import { tmpdir } from 'node:os';
38
+ import { basename, join } from 'node:path';
39
+ import { fileURLToPath } from 'node:url';
40
+ import { Language, Parser, Query } from 'web-tree-sitter';
41
+ import { AST_LANGUAGE_IDS, QUERY_CHAIN, grammarArtifactBasename } from '../src/ast/grammars.js';
42
+
43
+ const WASM_DIR = fileURLToPath(new URL('../wasm/', import.meta.url));
44
+ /** Queries we author ourselves, for grammars whose upstream ships none. */
45
+ const OURS_DIR = fileURLToPath(new URL('../queries/', import.meta.url));
46
+
47
+ interface GrammarSpec {
48
+ /** Vendored artifact basename, and the id the registry keys on. */
49
+ id: string;
50
+ /** npm package to fetch. */
51
+ pkg: string;
52
+ /**
53
+ * `.wasm` basename inside the tarball when it differs from the package's own
54
+ * name — tree-sitter-typescript ships two, and tree-sitter-c-sharp names its
55
+ * file with an underscore.
56
+ */
57
+ wasmFile?: string;
58
+ /**
59
+ * Where the tag query comes from. 'upstream' copies queries/tags.scm out of
60
+ * the tarball; 'ours' copies packages/shared/queries/<id>.tags.scm, which we
61
+ * maintain because the grammar ships no tags.scm at all.
62
+ */
63
+ tags: 'upstream' | 'ours' | 'inherited';
64
+ }
65
+
66
+ /**
67
+ * The vendored set. Deliberately explicit rather than globbed: a glob would
68
+ * quietly produce a smaller language set when a package changes its layout,
69
+ * and losing a language is exactly the silent degradation this plan exists to
70
+ * remove.
71
+ *
72
+ * Absent on purpose:
73
+ * julia, objc, haskell — the three largest grammars that ALSO ship no tag
74
+ * query, so each costs both its size (6.1/5.2/3.7 MB) and a hand-written
75
+ * query. Dropped to keep the payload under 40 MB; still indexed via the
76
+ * allowlist, still chunked heuristically.
77
+ * dart — its bundled .wasm (package v1.0.0, 2023) fails Language.load under
78
+ * web-tree-sitter 0.26.11 with a dylink-metadata error: the artifact
79
+ * predates the current wasm ABI. Presence is not loadability, which is why
80
+ * --check verifies both. Flutter matters, so this is worth revisiting by
81
+ * compiling from source with tree-sitter-cli.
82
+ * kotlin, swift, lua, perl, zig — bundle no .wasm, so they need a build step
83
+ * with tree-sitter-cli. Deferred until the grammars above are proven; they
84
+ * keep the heuristic chunker meanwhile.
85
+ * markdown — keeps chunkMarkdown. Headers already are a document's semantic
86
+ * unit; an AST would not improve on them.
87
+ */
88
+ const GRAMMARS: GrammarSpec[] = [
89
+ {
90
+ id: 'typescript',
91
+ pkg: 'tree-sitter-typescript',
92
+ wasmFile: 'tree-sitter-typescript.wasm',
93
+ tags: 'upstream',
94
+ },
95
+ { id: 'tsx', pkg: 'tree-sitter-typescript', wasmFile: 'tree-sitter-tsx.wasm', tags: 'inherited' },
96
+ { id: 'javascript', pkg: 'tree-sitter-javascript', tags: 'upstream' },
97
+ { id: 'python', pkg: 'tree-sitter-python', tags: 'upstream' },
98
+ { id: 'go', pkg: 'tree-sitter-go', tags: 'upstream' },
99
+ { id: 'java', pkg: 'tree-sitter-java', tags: 'upstream' },
100
+ { id: 'c', pkg: 'tree-sitter-c', tags: 'upstream' },
101
+ { id: 'cpp', pkg: 'tree-sitter-cpp', tags: 'upstream' },
102
+ {
103
+ id: 'c-sharp',
104
+ pkg: 'tree-sitter-c-sharp',
105
+ wasmFile: 'tree-sitter-c_sharp.wasm',
106
+ tags: 'upstream',
107
+ },
108
+ { id: 'ruby', pkg: 'tree-sitter-ruby', tags: 'upstream' },
109
+ { id: 'php', pkg: 'tree-sitter-php', wasmFile: 'tree-sitter-php.wasm', tags: 'upstream' },
110
+ { id: 'rust', pkg: 'tree-sitter-rust', tags: 'upstream' },
111
+ { id: 'scala', pkg: 'tree-sitter-scala', tags: 'upstream' },
112
+ { id: 'elixir', pkg: 'tree-sitter-elixir', tags: 'upstream' },
113
+ { id: 'solidity', pkg: 'tree-sitter-solidity', tags: 'upstream' },
114
+ { id: 'bash', pkg: 'tree-sitter-bash', tags: 'ours' },
115
+ { id: 'groovy', pkg: 'tree-sitter-groovy', tags: 'ours' },
116
+ { id: 'powershell', pkg: 'tree-sitter-powershell', tags: 'ours' },
117
+ ];
118
+
119
+ /**
120
+ * Ordered tag-query files composing each grammar's EFFECTIVE query, keyed by
121
+ * VENDORED ARTIFACT BASENAME (`c-sharp`, not `csharp`) because that is what this
122
+ * script names files with.
123
+ *
124
+ * Derived, not declared: the chains themselves live in the runtime registry
125
+ * (`src/ast/grammars.ts`), which is the module that has to compose them for real
126
+ * work. A second hand-maintained copy here could drift from the one the scanner
127
+ * uses, and a drifted chain does not fail — it silently under-matches, which is
128
+ * the exact failure this vendoring step exists to catch. See that module for why
129
+ * tag queries are deltas and what composing them is worth (948 TypeScript
130
+ * definitions against 6,471).
131
+ */
132
+ const CHAIN_BY_ARTIFACT: Record<string, string[]> = Object.fromEntries(
133
+ AST_LANGUAGE_IDS.map((id) => [
134
+ grammarArtifactBasename(id),
135
+ QUERY_CHAIN[id].map((part) => grammarArtifactBasename(part)),
136
+ ])
137
+ );
138
+
139
+ interface Problem {
140
+ id: string;
141
+ detail: string;
142
+ }
143
+
144
+ /**
145
+ * Verify each manifest entry is present, LOADS, and compiles its composed query.
146
+ *
147
+ * Presence alone is not enough, proven here: tree-sitter-dart's bundled .wasm
148
+ * exists and is 741 KB, and `Language.load` rejects it under web-tree-sitter
149
+ * 0.26.11 because the artifact predates the current wasm ABI. A check that only
150
+ * stats the file would have called that grammar vendored and shipped a language
151
+ * that silently never parses.
152
+ */
153
+ async function checkOnly(): Promise<Problem[]> {
154
+ const problems: Problem[] = [];
155
+ await Parser.init();
156
+
157
+ for (const spec of GRAMMARS) {
158
+ const wasm = join(WASM_DIR, `${spec.id}.wasm`);
159
+ if (!existsSync(wasm) || statSync(wasm).size === 0) {
160
+ problems.push({ id: spec.id, detail: `missing or empty ${spec.id}.wasm` });
161
+ continue;
162
+ }
163
+
164
+ let language: Language;
165
+ try {
166
+ language = await Language.load(wasm);
167
+ } catch (err) {
168
+ problems.push({
169
+ id: spec.id,
170
+ detail: `${spec.id}.wasm exists but Language.load rejected it (ABI mismatch?): ${
171
+ err instanceof Error ? err.message || err.constructor.name : String(err)
172
+ }`,
173
+ });
174
+ continue;
175
+ }
176
+
177
+ const chain = CHAIN_BY_ARTIFACT[spec.id];
178
+ if (!chain) {
179
+ problems.push({
180
+ id: spec.id,
181
+ detail: `no QUERY_CHAIN entry in src/ast/grammars.ts — the grammar would load and match nothing`,
182
+ });
183
+ continue;
184
+ }
185
+
186
+ let source = '';
187
+ for (const part of chain) {
188
+ const file = join(WASM_DIR, `${part}.tags.scm`);
189
+ if (!existsSync(file) || statSync(file).size === 0) {
190
+ problems.push({ id: spec.id, detail: `chain part missing or empty: ${part}.tags.scm` });
191
+ source = '';
192
+ break;
193
+ }
194
+ source += readFileSync(file, 'utf8') + '\n';
195
+ }
196
+ if (!source) continue;
197
+
198
+ try {
199
+ const query = new Query(language, source);
200
+ const captures = query.captureNames;
201
+ if (!captures.some((c) => c.startsWith('definition.'))) {
202
+ problems.push({
203
+ id: spec.id,
204
+ detail: `composed query compiles but captures no definition.* — it would name nothing`,
205
+ });
206
+ }
207
+ } catch (err) {
208
+ problems.push({
209
+ id: spec.id,
210
+ detail: `composed query failed to compile: ${
211
+ err instanceof Error ? err.message.slice(0, 140) : String(err)
212
+ }`,
213
+ });
214
+ }
215
+ }
216
+ return problems;
217
+ }
218
+
219
+ function vendor(): Problem[] {
220
+ mkdirSync(WASM_DIR, { recursive: true });
221
+ const work = mkdtempSync(join(tmpdir(), 'ts-grammars-'));
222
+ const problems: Problem[] = [];
223
+ // One tarball may serve several ids (typescript + tsx), so fetch each package
224
+ // once and extract from the shared checkout.
225
+ const extracted = new Map<string, string>();
226
+
227
+ try {
228
+ for (const spec of GRAMMARS) {
229
+ let dir = extracted.get(spec.pkg);
230
+ if (!dir) {
231
+ try {
232
+ const out = execFileSync(
233
+ 'npm',
234
+ ['pack', spec.pkg, '--silent', '--pack-destination', work],
235
+ {
236
+ cwd: work,
237
+ encoding: 'utf8',
238
+ }
239
+ );
240
+ const tarball = out.trim().split('\n').pop() ?? '';
241
+ dir = join(work, spec.pkg.replace(/[^a-z0-9-]/gi, '_'));
242
+ mkdirSync(dir, { recursive: true });
243
+ execFileSync('tar', [
244
+ 'xzf',
245
+ join(work, basename(tarball)),
246
+ '-C',
247
+ dir,
248
+ '--strip-components=1',
249
+ ]);
250
+ extracted.set(spec.pkg, dir);
251
+ } catch (err) {
252
+ problems.push({
253
+ id: spec.id,
254
+ detail: `npm pack/extract failed for ${spec.pkg}: ${err instanceof Error ? err.message.slice(0, 160) : err}`,
255
+ });
256
+ continue;
257
+ }
258
+ }
259
+
260
+ const wasmName =
261
+ spec.wasmFile ?? readdirSync(dir).find((f) => f.endsWith('.wasm')) ?? `${spec.pkg}.wasm`;
262
+ const wasmSrc = join(dir, wasmName);
263
+ if (!existsSync(wasmSrc)) {
264
+ problems.push({ id: spec.id, detail: `${spec.pkg} ships no ${wasmName}` });
265
+ } else {
266
+ copyFileSync(wasmSrc, join(WASM_DIR, `${spec.id}.wasm`));
267
+ }
268
+
269
+ if (spec.tags === 'upstream') {
270
+ const tagsSrc = join(dir, 'queries', 'tags.scm');
271
+ if (!existsSync(tagsSrc)) {
272
+ problems.push({
273
+ id: spec.id,
274
+ detail: `${spec.pkg} ships no queries/tags.scm — reclassify this entry as 'ours' and author one`,
275
+ });
276
+ } else {
277
+ copyFileSync(tagsSrc, join(WASM_DIR, `${spec.id}.tags.scm`));
278
+ }
279
+ } else if (spec.tags === 'ours') {
280
+ const ourSrc = join(OURS_DIR, `${spec.id}.tags.scm`);
281
+ if (!existsSync(ourSrc)) {
282
+ problems.push({
283
+ id: spec.id,
284
+ detail: `no hand-written query at packages/shared/queries/${spec.id}.tags.scm`,
285
+ });
286
+ } else {
287
+ copyFileSync(ourSrc, join(WASM_DIR, `${spec.id}.tags.scm`));
288
+ }
289
+ }
290
+ }
291
+ } finally {
292
+ rmSync(work, { recursive: true, force: true });
293
+ }
294
+ return problems;
295
+ }
296
+
297
+ const problems = process.argv.includes('--check') ? await checkOnly() : vendor();
298
+
299
+ if (problems.length > 0) {
300
+ // Non-zero and loud: a grammar that stops shipping an artifact must break the
301
+ // build, never silently remove a language from the product.
302
+ console.error('VENDOR_GRAMMARS_FAILED');
303
+ for (const p of problems) console.error(` ${p.id}: ${p.detail}`);
304
+ process.exit(1);
305
+ }
306
+
307
+ const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm`)).size);
308
+ const totalMB = +(sizes.reduce((a, b) => a + b, 0) / 1048576).toFixed(1);
309
+ console.log(
310
+ `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, totalWasmMB: totalMB })}`
311
+ );
312
+ // The plan's ceiling. Install size is a cost every user pays, so it fails the
313
+ // build rather than drifting upward one grammar at a time.
314
+ if (totalMB > 40) {
315
+ console.error(`VENDOR_GRAMMARS_TOO_LARGE ${totalMB}MB exceeds the 40MB ceiling`);
316
+ process.exit(1);
317
+ }