graph-indexer 2.1.0 → 3.0.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 (129) hide show
  1. package/CHANGELOG.md +260 -1
  2. package/README.md +232 -312
  3. package/SECURITY.md +37 -59
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +8 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +99 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +124 -0
  11. package/package.json +31 -105
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +356 -0
  14. package/src/cli/init.mjs +255 -0
  15. package/src/cli/resident.mjs +175 -0
  16. package/src/index/discover.mjs +130 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +331 -0
  21. package/src/index/resolver.mjs +954 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +973 -0
  25. package/src/parse/arity.mjs +152 -0
  26. package/src/parse/extract.mjs +1031 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +343 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +680 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +119 -0
  44. package/src/query/textsearch.mjs +160 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/paths.mjs +24 -0
  49. package/vendor/grammars/bash.wasm.gz +0 -0
  50. package/vendor/grammars/c.wasm.gz +0 -0
  51. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  52. package/vendor/grammars/cpp.wasm.gz +0 -0
  53. package/vendor/grammars/css.wasm.gz +0 -0
  54. package/vendor/grammars/go.wasm.gz +0 -0
  55. package/vendor/grammars/java.wasm.gz +0 -0
  56. package/vendor/grammars/javascript.wasm.gz +0 -0
  57. package/vendor/grammars/kotlin.wasm.gz +0 -0
  58. package/vendor/grammars/manifest.json +148 -0
  59. package/vendor/grammars/php.wasm.gz +0 -0
  60. package/vendor/grammars/python.wasm.gz +0 -0
  61. package/vendor/grammars/ruby.wasm.gz +0 -0
  62. package/vendor/grammars/rust.wasm.gz +0 -0
  63. package/vendor/grammars/scala.wasm.gz +0 -0
  64. package/vendor/grammars/tsx.wasm.gz +0 -0
  65. package/vendor/grammars/typescript.wasm.gz +0 -0
  66. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  67. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  68. package/cli-ui.mjs +0 -78
  69. package/colbert.mjs +0 -168
  70. package/config.mjs +0 -494
  71. package/daemon-ctl.mjs +0 -175
  72. package/daemon-lock.mjs +0 -69
  73. package/embedders/python/mlx_embed_server.py +0 -61
  74. package/embedders/python/requirements-mlx.txt +0 -5
  75. package/embedders/setup-mlx.mjs +0 -110
  76. package/embeddings.mjs +0 -571
  77. package/engine/binary.mjs +0 -437
  78. package/engine/memory.mjs +0 -1003
  79. package/engine/sqlite.mjs +0 -1136
  80. package/enrichment.mjs +0 -521
  81. package/git-signals.mjs +0 -248
  82. package/indexer.mjs +0 -546
  83. package/init.mjs +0 -1809
  84. package/layout.mjs +0 -123
  85. package/mcp/centrality.mjs +0 -117
  86. package/mcp/format.mjs +0 -61
  87. package/mcp/resolver.mjs +0 -116
  88. package/mcp/symbolgraph.mjs +0 -137
  89. package/mcp/taint.mjs +0 -225
  90. package/mcp/tools.mjs +0 -1582
  91. package/mcp/topology.mjs +0 -676
  92. package/mcp-server.mjs +0 -197
  93. package/parse/extractor.mjs +0 -628
  94. package/parse/imports.mjs +0 -365
  95. package/parse/interprocedural.mjs +0 -134
  96. package/parse/languages.mjs +0 -253
  97. package/parse/metadata.mjs +0 -701
  98. package/parse/routes.mjs +0 -327
  99. package/parse/scip.mjs +0 -391
  100. package/parse/taint-patterns.mjs +0 -119
  101. package/prompts/CORE.md +0 -91
  102. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  103. package/prompts/INTEGRATION.md +0 -123
  104. package/prompts/frameworks/ANDROID.md +0 -17
  105. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  106. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  107. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  108. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  109. package/prompts/frameworks/RAILS.md +0 -20
  110. package/prompts/frameworks/REACT.md +0 -20
  111. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  112. package/prompts/languages/BASH.md +0 -22
  113. package/prompts/languages/C.md +0 -21
  114. package/prompts/languages/CSHARP.md +0 -24
  115. package/prompts/languages/CSS_SCSS.md +0 -22
  116. package/prompts/languages/GO.md +0 -24
  117. package/prompts/languages/JAVA.md +0 -23
  118. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  119. package/prompts/languages/KOTLIN.md +0 -24
  120. package/prompts/languages/PHP.md +0 -23
  121. package/prompts/languages/PYTHON.md +0 -23
  122. package/prompts/languages/RUBY.md +0 -23
  123. package/prompts/languages/RUST.md +0 -21
  124. package/prompts/languages/SWIFT.md +0 -21
  125. package/seal.mjs +0 -400
  126. package/search-core.mjs +0 -777
  127. package/search-sparse.mjs +0 -183
  128. package/storage.mjs +0 -73
  129. package/watch-daemon.mjs +0 -289
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Module resolution: turns an import's source string into repository files (or a package
3
+ * directory). Everything is computed from the file list plus a few manifest files
4
+ * (package.json workspaces, tsconfig paths, go.mod) — no build, no network.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ const ECMA_EXTS = ['.ts', '.tsx', '.d.ts', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
10
+
11
+ function readJson(p) {
12
+ try {
13
+ const text = fs.readFileSync(p, 'utf8').replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, '').replace(/,(\s*[}\]])/g, '$1');
14
+ return JSON.parse(text);
15
+ } catch { return null; }
16
+ }
17
+
18
+ export class ModuleResolver {
19
+ constructor(root, filePaths) {
20
+ this.root = root;
21
+ this.setFiles(filePaths);
22
+ }
23
+
24
+ setFiles(filePaths) {
25
+ this.files = new Set(filePaths);
26
+ this.bySuffix = null; // lazy
27
+ this.dirs = new Map(); // dir -> [paths]
28
+ for (const f of filePaths) {
29
+ const d = path.posix.dirname(f);
30
+ (this.dirs.get(d) ?? this.dirs.set(d, []).get(d)).push(f);
31
+ }
32
+ this.#loadManifests();
33
+ }
34
+
35
+ addFile(f) {
36
+ if (this.files.has(f)) return;
37
+ this.files.add(f);
38
+ const d = path.posix.dirname(f);
39
+ (this.dirs.get(d) ?? this.dirs.set(d, []).get(d)).push(f);
40
+ this.bySuffix = null;
41
+ }
42
+
43
+ removeFile(f) {
44
+ if (!this.files.delete(f)) return;
45
+ const d = path.posix.dirname(f);
46
+ const list = this.dirs.get(d);
47
+ if (list) { const i = list.indexOf(f); if (i >= 0) list.splice(i, 1); }
48
+ this.bySuffix = null;
49
+ }
50
+
51
+ #loadManifests() {
52
+ // JS/TS workspace packages: package name -> directory
53
+ this.packages = new Map();
54
+ const pkgJsons = [...this.files].length ? this.#findManifests('package.json') : [];
55
+ for (const rel of pkgJsons) {
56
+ const j = readJson(path.join(this.root, rel));
57
+ if (j?.name) this.packages.set(j.name, { dir: path.posix.dirname(rel), json: j });
58
+ }
59
+ // tsconfig paths and baseUrl, per project: a file resolves through the nearest tsconfig.json
60
+ // above it (in a monorepo each package maps its own aliases, often the same `@/*` to a
61
+ // different directory), then through the root's
62
+ this.tsScopes = new Map();
63
+ const rootCfg = fs.existsSync(path.join(this.root, 'tsconfig.json')) ? 'tsconfig.json' : 'tsconfig.base.json';
64
+ const rootScope = this.#tsScope(rootCfg);
65
+ if (rootScope) this.tsScopes.set('.', rootScope);
66
+ for (const rel of this.#findManifests('tsconfig.json')) {
67
+ const dir = path.posix.dirname(rel);
68
+ if (dir === '.') continue;
69
+ const scope = this.#tsScope(rel);
70
+ if (scope) this.tsScopes.set(dir, scope);
71
+ }
72
+ // Go module path
73
+ this.goModules = [];
74
+ for (const rel of this.#findManifests('go.mod')) {
75
+ try {
76
+ const m = /^module\s+(\S+)/m.exec(fs.readFileSync(path.join(this.root, rel), 'utf8'));
77
+ if (m) this.goModules.push({ module: m[1], dir: path.posix.dirname(rel) });
78
+ } catch { /* unreadable */ }
79
+ }
80
+ this.goModules.sort((a, b) => b.module.length - a.module.length);
81
+ }
82
+
83
+ /** compilerOptions of a tsconfig file, following relative `extends` (a child's paths replace its parent's). */
84
+ #tsOptions(rel, depth = 0) {
85
+ const j = readJson(path.join(this.root, rel));
86
+ if (!j || depth > 5) return null;
87
+ const dir = path.posix.dirname(rel);
88
+ let out = { paths: null, pathsDir: null, baseUrl: null };
89
+ for (const ext of [j.extends ?? []].flat()) {
90
+ if (typeof ext !== 'string' || !ext.startsWith('.')) continue;
91
+ const parent = this.#tsOptions(path.posix.normalize(path.posix.join(dir, ext.endsWith('.json') ? ext : ext + '.json')), depth + 1);
92
+ if (parent) out = { paths: parent.paths ?? out.paths, pathsDir: parent.paths ? parent.pathsDir : out.pathsDir, baseUrl: parent.baseUrl ?? out.baseUrl };
93
+ }
94
+ const co = j.compilerOptions ?? {};
95
+ if (co.baseUrl != null) out.baseUrl = path.posix.normalize(path.posix.join(dir, co.baseUrl));
96
+ if (co.paths) { out.paths = co.paths; out.pathsDir = dir; }
97
+ return out;
98
+ }
99
+
100
+ /** The aliases a tsconfig (or, when it has none, the app/lib configs next to it) declares. */
101
+ #tsScope(rel) {
102
+ let o = this.#tsOptions(rel);
103
+ const dir = path.posix.dirname(rel);
104
+ if (o && !o.paths && !o.baseUrl) {
105
+ for (const alt of ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.build.json']) {
106
+ const a = this.#tsOptions(dir === '.' ? alt : `${dir}/${alt}`);
107
+ if (a?.paths || a?.baseUrl) { o = a; break; }
108
+ }
109
+ }
110
+ if (!o || (!o.paths && !o.baseUrl)) return null;
111
+ // targets are relative to baseUrl, or to the config that declares them when there is none
112
+ const base = o.baseUrl ?? o.pathsDir;
113
+ const paths = Object.entries(o.paths ?? {}).map(([pat, targets]) => ({ pat, targets: [targets].flat().map(t => path.posix.normalize(path.posix.join(base, t))) }));
114
+ return { paths, baseUrl: o.baseUrl };
115
+ }
116
+
117
+ #tsScopesFor(fromDir) {
118
+ const out = [];
119
+ for (let d = fromDir; ; d = path.posix.dirname(d)) {
120
+ const s = this.tsScopes.get(d);
121
+ if (s && d !== '.') { out.push(s); break; }
122
+ if (d === '.' || d === '/' || !d) break;
123
+ }
124
+ const root = this.tsScopes.get('.');
125
+ if (root) out.push(root);
126
+ return out;
127
+ }
128
+
129
+ #findManifests(name) {
130
+ // manifests are not source files, so look for them next to source directories (bounded)
131
+ const out = [];
132
+ const seen = new Set();
133
+ const consider = (dir) => {
134
+ if (seen.has(dir)) return; seen.add(dir);
135
+ const rel = dir === '.' ? name : `${dir}/${name}`;
136
+ if (fs.existsSync(path.join(this.root, rel))) out.push(rel);
137
+ };
138
+ consider('.');
139
+ for (const d of this.dirs.keys()) {
140
+ let cur = d;
141
+ for (let i = 0; i < 6 && cur && cur !== '.'; i++) {
142
+ if (/(^|\/)node_modules(\/|$)/.test(cur)) break;
143
+ consider(cur);
144
+ cur = path.posix.dirname(cur);
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+
150
+ #suffixIndex() {
151
+ if (!this.bySuffix) {
152
+ this.bySuffix = new Map();
153
+ for (const f of this.files) {
154
+ const parts = f.split('/');
155
+ for (let i = Math.max(0, parts.length - 6); i < parts.length; i++) {
156
+ const suf = parts.slice(i).join('/');
157
+ (this.bySuffix.get(suf) ?? this.bySuffix.set(suf, []).get(suf)).push(f);
158
+ }
159
+ }
160
+ }
161
+ return this.bySuffix;
162
+ }
163
+
164
+ #ecmaFile(base) {
165
+ if (this.files.has(base)) return base;
166
+ const noJs = base.replace(/\.(m|c)?js$/, '');
167
+ for (const ext of ECMA_EXTS) if (this.files.has(noJs + ext)) return noJs + ext;
168
+ for (const ext of ECMA_EXTS) if (this.files.has(`${noJs}/index${ext}`)) return `${noJs}/index${ext}`;
169
+ return null;
170
+ }
171
+
172
+ /**
173
+ * Resolve an import from `fromFile`.
174
+ * @returns {{ file?: string, dir?: string } | null} null → external dependency
175
+ */
176
+ resolve(lang, fromFile, source) {
177
+ if (!source) return null;
178
+ const fromDir = path.posix.dirname(fromFile);
179
+ switch (lang) {
180
+ case 'javascript': case 'typescript': case 'tsx': return this.#resolveEcma(fromDir, source);
181
+ case 'python': return this.#resolvePython(fromDir, source);
182
+ case 'go': return this.#resolveGo(source);
183
+ case 'c': case 'cpp': return this.#resolveInclude(fromDir, source);
184
+ case 'ruby': case 'bash': case 'css': return this.#resolveRelativeOrSuffix(fromDir, source, lang);
185
+ case 'rust': return this.#resolveRust(fromFile, source);
186
+ default: return null; // package-based languages resolve through the package index
187
+ }
188
+ }
189
+
190
+ #resolveEcma(fromDir, source) {
191
+ if (source.startsWith('.')) {
192
+ const f = this.#ecmaFile(path.posix.normalize(path.posix.join(fromDir, source)));
193
+ return f ? { file: f } : null;
194
+ }
195
+ if (source.startsWith('/')) return null;
196
+ const scopes = this.#tsScopesFor(fromDir);
197
+ for (const { pat, targets } of scopes.flatMap(s => s.paths)) {
198
+ const star = pat.indexOf('*');
199
+ if (star < 0 ? pat === source : (source.startsWith(pat.slice(0, star)) && source.endsWith(pat.slice(star + 1)))) {
200
+ const mid = star < 0 ? '' : source.slice(star, source.length - (pat.length - star - 1));
201
+ for (const t of targets) {
202
+ const f = this.#ecmaFile(t.replace('*', mid));
203
+ if (f) return { file: f };
204
+ }
205
+ }
206
+ }
207
+ // workspace package (monorepo): '@scope/pkg' or '@scope/pkg/sub/path'
208
+ const parts = source.split('/');
209
+ const pkgName = source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
210
+ const sub = source.startsWith('@') ? parts.slice(2).join('/') : parts.slice(1).join('/');
211
+ const pkg = this.packages.get(pkgName);
212
+ if (pkg) {
213
+ const base = pkg.dir === '.' ? '' : pkg.dir + '/';
214
+ if (sub) {
215
+ const f = this.#ecmaFile(base + sub) ?? this.#ecmaFile(base + 'src/' + sub) ?? this.#ecmaFile(base + 'lib/' + sub);
216
+ if (f) return { file: f };
217
+ }
218
+ const j = pkg.json;
219
+ const entries = [j.source, j.types, j.typings, j.module, j.main, 'index', 'src/index', 'lib/index'].filter(Boolean);
220
+ for (const e of entries) {
221
+ const f = this.#ecmaFile(path.posix.normalize(base + String(e).replace(/^\.\//, '')).replace(/\.d\.ts$/, '')) ?? this.#ecmaFile(path.posix.normalize(base + String(e).replace(/^\.\//, '')));
222
+ if (f) return { file: f };
223
+ }
224
+ }
225
+ for (const s of scopes) if (s.baseUrl) {
226
+ const f = this.#ecmaFile(path.posix.normalize(path.posix.join(s.baseUrl, source)));
227
+ if (f) return { file: f };
228
+ }
229
+ return null;
230
+ }
231
+
232
+ #resolvePython(fromDir, source) {
233
+ let base;
234
+ let mod = source;
235
+ if (source.startsWith('.')) {
236
+ const dots = /^\.+/.exec(source)[0].length;
237
+ base = fromDir;
238
+ for (let i = 1; i < dots; i++) base = path.posix.dirname(base);
239
+ mod = source.slice(dots);
240
+ const p = mod ? path.posix.join(base, mod.replace(/\./g, '/')) : base;
241
+ if (this.files.has(p + '.py')) return { file: p + '.py' };
242
+ if (this.files.has(p + '/__init__.py')) return { file: p + '/__init__.py', dir: p };
243
+ if (this.dirs.has(p)) return { dir: p };
244
+ return null;
245
+ }
246
+ const rel = mod.replace(/\./g, '/');
247
+ const idx = this.#suffixIndex();
248
+ const cands = [...(idx.get(rel + '.py') ?? []), ...(idx.get(rel + '/__init__.py') ?? [])]
249
+ .filter(f => f === rel + '.py' || f === rel + '/__init__.py' || f.endsWith('/' + rel + '.py') || f.endsWith('/' + rel + '/__init__.py'));
250
+ if (cands.length) {
251
+ cands.sort((a, b) => a.length - b.length);
252
+ const f = cands[0];
253
+ return f.endsWith('/__init__.py') ? { file: f, dir: path.posix.dirname(f) } : { file: f };
254
+ }
255
+ return null;
256
+ }
257
+
258
+ #resolveGo(source) {
259
+ for (const { module, dir } of this.goModules) {
260
+ if (source === module || source.startsWith(module + '/')) {
261
+ const sub = source.slice(module.length).replace(/^\//, '');
262
+ const d = path.posix.normalize(dir === '.' ? (sub || '.') : (sub ? `${dir}/${sub}` : dir));
263
+ return this.dirs.has(d) ? { dir: d } : null;
264
+ }
265
+ }
266
+ return null;
267
+ }
268
+
269
+ #resolveInclude(fromDir, source) {
270
+ const direct = path.posix.normalize(path.posix.join(fromDir, source));
271
+ if (this.files.has(direct)) return { file: direct };
272
+ const idx = this.#suffixIndex();
273
+ const cands = (idx.get(source) ?? []).filter(f => f === source || f.endsWith('/' + source));
274
+ if (cands.length) {
275
+ cands.sort((a, b) => a.length - b.length);
276
+ return { file: cands[0] };
277
+ }
278
+ return null;
279
+ }
280
+
281
+ #resolveRelativeOrSuffix(fromDir, source, lang) {
282
+ const exts = lang === 'ruby' ? ['', '.rb'] : lang === 'css' ? ['', '.css', '.scss'] : ['', '.sh', '.bash'];
283
+ if (source.startsWith('.') || lang === 'bash') {
284
+ for (const e of exts) {
285
+ const p = path.posix.normalize(path.posix.join(fromDir, source + e));
286
+ if (this.files.has(p)) return { file: p };
287
+ }
288
+ }
289
+ const idx = this.#suffixIndex();
290
+ for (const e of exts) {
291
+ const s = source.replace(/^\.\//, '') + e;
292
+ const cands = (idx.get(s) ?? []).filter(f => f === s || f.endsWith('/' + s));
293
+ if (cands.length) { cands.sort((a, b) => a.length - b.length); return { file: cands[0] }; }
294
+ }
295
+ return null;
296
+ }
297
+
298
+ #resolveRust(fromFile, source) {
299
+ // crate::a::b / self::a / super::a → src/a/b.rs | src/a/b/mod.rs | src/a.rs (item b inside)
300
+ const segs = source.split('::').filter(Boolean);
301
+ if (!segs.length) return null;
302
+ let baseDir;
303
+ const crateRoot = this.#rustCrateRoot(fromFile);
304
+ if (segs[0] === 'crate') { baseDir = crateRoot; segs.shift(); }
305
+ else if (segs[0] === 'self') { baseDir = this.#rustModDir(fromFile); segs.shift(); }
306
+ else if (segs[0] === 'super') { baseDir = path.posix.dirname(this.#rustModDir(fromFile)); segs.shift(); while (segs[0] === 'super') { baseDir = path.posix.dirname(baseDir); segs.shift(); } }
307
+ else return null; // external crate
308
+ for (let n = segs.length; n > 0; n--) {
309
+ const p = path.posix.join(baseDir, ...segs.slice(0, n));
310
+ if (this.files.has(p + '.rs')) return { file: p + '.rs' };
311
+ if (this.files.has(p + '/mod.rs')) return { file: p + '/mod.rs' };
312
+ }
313
+ const lib = ['lib.rs', 'main.rs', 'mod.rs'].map(f => path.posix.join(baseDir, f)).find(f => this.files.has(f));
314
+ return lib ? { file: lib } : null;
315
+ }
316
+
317
+ #rustCrateRoot(fromFile) {
318
+ let d = path.posix.dirname(fromFile);
319
+ while (d && d !== '.') {
320
+ if (this.files.has(d + '/lib.rs') || this.files.has(d + '/main.rs')) return d;
321
+ d = path.posix.dirname(d);
322
+ }
323
+ return 'src';
324
+ }
325
+
326
+ #rustModDir(fromFile) {
327
+ const base = path.posix.basename(fromFile);
328
+ const dir = path.posix.dirname(fromFile);
329
+ return (base === 'mod.rs' || base === 'lib.rs' || base === 'main.rs') ? dir : path.posix.join(dir, base.replace(/\.rs$/, ''));
330
+ }
331
+ }