fchek 1.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 (66) hide show
  1. package/README.md +64 -0
  2. package/bin/fchek.js +107 -0
  3. package/lib/api.js +110 -0
  4. package/lib/audit.js +211 -0
  5. package/lib/bench.js +248 -0
  6. package/lib/config.js +191 -0
  7. package/lib/context.js +356 -0
  8. package/lib/convention.js +526 -0
  9. package/lib/coverage.js +604 -0
  10. package/lib/db.js +135 -0
  11. package/lib/deps-check.js +264 -0
  12. package/lib/deps.js +374 -0
  13. package/lib/docker.js +84 -0
  14. package/lib/doctor.js +149 -0
  15. package/lib/dom.js +226 -0
  16. package/lib/fuzz.js +470 -0
  17. package/lib/git.js +290 -0
  18. package/lib/goto.js +544 -0
  19. package/lib/launch.js +182 -0
  20. package/lib/lint.js +624 -0
  21. package/lib/new_features.test.js +181 -0
  22. package/lib/output.js +46 -0
  23. package/lib/port.js +173 -0
  24. package/lib/process.js +228 -0
  25. package/lib/profile.js +453 -0
  26. package/lib/python.js +41 -0
  27. package/lib/race.js +186 -0
  28. package/lib/registry.js +179 -0
  29. package/lib/repl.js +135 -0
  30. package/lib/run.js +403 -0
  31. package/lib/screenshot.js +152 -0
  32. package/lib/secrets.js +257 -0
  33. package/lib/state.js +219 -0
  34. package/lib/test.js +471 -0
  35. package/lib/vuln.js +253 -0
  36. package/lib/watch.js +240 -0
  37. package/lib/winlog.js +123 -0
  38. package/package.json +27 -0
  39. package/skills/ACTIVATE.md +274 -0
  40. package/skills/README.md +163 -0
  41. package/skills/agent.md +444 -0
  42. package/skills/api.md +47 -0
  43. package/skills/bench.md +117 -0
  44. package/skills/context.md +116 -0
  45. package/skills/convention.md +143 -0
  46. package/skills/coverage.md +99 -0
  47. package/skills/csharp.md +97 -0
  48. package/skills/db.md +66 -0
  49. package/skills/deps-check.md +135 -0
  50. package/skills/deps.md +143 -0
  51. package/skills/docker.md +61 -0
  52. package/skills/dom.md +56 -0
  53. package/skills/fuzz.md +167 -0
  54. package/skills/goto.md +111 -0
  55. package/skills/lint.md +123 -0
  56. package/skills/port.md +57 -0
  57. package/skills/profile.md +91 -0
  58. package/skills/race.md +117 -0
  59. package/skills/repl.md +81 -0
  60. package/skills/rules.md +318 -0
  61. package/skills/run.md +135 -0
  62. package/skills/secrets.md +170 -0
  63. package/skills/security.md +360 -0
  64. package/skills/state.md +261 -0
  65. package/skills/vuln.md +57 -0
  66. package/skills/windows.md +320 -0
package/lib/goto.js ADDED
@@ -0,0 +1,544 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync, execSync } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const { output, ok, fail } = require('./output');
8
+
9
+ const HELP = `
10
+ fchek goto <file:line>
11
+
12
+ Show symbol definition and all references via LSP (batch mode, no editor needed).
13
+
14
+ Supported languages (auto-detected by extension):
15
+ .rs → rust-analyzer
16
+ .c / .cpp → clangd
17
+ .py → pyright (basedpyright)
18
+ .ts / .js → typescript-language-server
19
+ .cs → OmniSharp LSP (falls back to grep if not installed)
20
+ Install OmniSharp: dotnet tool install -g omnisharp
21
+
22
+ Output: JSON with symbol name, definition location, and references list.
23
+
24
+ Examples:
25
+ fchek goto main.rs:42
26
+ fchek goto src/main.cpp:100
27
+ fchek goto app.py:17
28
+ fchek goto src/Program.cs:15
29
+ fchek goto src/Program.cs:15:8
30
+ `.trim();
31
+
32
+ const DEFAULT_TIMEOUT_MS = 15_000;
33
+
34
+ function commandExists(cmd) {
35
+ try {
36
+ execSync(
37
+ process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`,
38
+ { stdio: 'ignore' }
39
+ );
40
+ return true;
41
+ } catch { return false; }
42
+ }
43
+
44
+ function detectLsp(file) {
45
+ const ext = path.extname(file).toLowerCase();
46
+ const map = {
47
+ '.rs': { server: 'rust-analyzer', lang: 'rust' },
48
+ '.cpp': { server: 'clangd', lang: 'cpp' },
49
+ '.cc': { server: 'clangd', lang: 'cpp' },
50
+ '.c': { server: 'clangd', lang: 'c' },
51
+ '.py': { server: 'pyright', lang: 'python' },
52
+ '.ts': { server: 'typescript-language-server', lang: 'typescript' },
53
+ '.js': { server: 'typescript-language-server', lang: 'javascript' },
54
+ };
55
+ return map[ext] || null;
56
+ }
57
+
58
+ /**
59
+ * Build a minimal LSP initialize + textDocument/definition request.
60
+ * We send it over stdin to the LSP server and read stdout.
61
+ */
62
+ function buildLspRequest(fileUri, line, character, rootUri) {
63
+ function msg(obj) {
64
+ const body = JSON.stringify(obj);
65
+ return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
66
+ }
67
+
68
+ const init = msg({
69
+ jsonrpc: '2.0', id: 1, method: 'initialize',
70
+ params: {
71
+ processId: process.pid,
72
+ rootUri,
73
+ capabilities: { textDocument: { definition: {}, references: {} } },
74
+ initializationOptions: {},
75
+ },
76
+ });
77
+
78
+ const initialized = msg({
79
+ jsonrpc: '2.0', method: 'initialized', params: {},
80
+ });
81
+
82
+ const didOpen = msg({
83
+ jsonrpc: '2.0', method: 'textDocument/didOpen',
84
+ params: {
85
+ textDocument: {
86
+ uri: fileUri,
87
+ languageId: 'rust', // overridden later — server infers from uri
88
+ version: 1,
89
+ text: fs.existsSync(fileUri.replace('file://', ''))
90
+ ? fs.readFileSync(fileUri.replace('file://', ''), 'utf8')
91
+ : '',
92
+ },
93
+ },
94
+ });
95
+
96
+ const definition = msg({
97
+ jsonrpc: '2.0', id: 2, method: 'textDocument/definition',
98
+ params: {
99
+ textDocument: { uri: fileUri },
100
+ position: { line: line - 1, character },
101
+ },
102
+ });
103
+
104
+ const references = msg({
105
+ jsonrpc: '2.0', id: 3, method: 'textDocument/references',
106
+ params: {
107
+ textDocument: { uri: fileUri },
108
+ position: { line: line - 1, character },
109
+ context: { includeDeclaration: true },
110
+ },
111
+ });
112
+
113
+ const shutdown = msg({ jsonrpc: '2.0', id: 99, method: 'shutdown', params: {} });
114
+
115
+ return init + initialized + didOpen + definition + references + shutdown;
116
+ }
117
+
118
+ function parseLspOutput(raw) {
119
+ const messages = [];
120
+ let pos = 0;
121
+ while (pos < raw.length) {
122
+ const clIdx = raw.indexOf('Content-Length:', pos);
123
+ if (clIdx === -1) break;
124
+ const eoh = raw.indexOf('\r\n\r\n', clIdx);
125
+ if (eoh === -1) break;
126
+ const len = parseInt(raw.slice(clIdx + 15, eoh).trim(), 10);
127
+ const body = raw.slice(eoh + 4, eoh + 4 + len);
128
+ try { messages.push(JSON.parse(body)); } catch {}
129
+ pos = eoh + 4 + len;
130
+ }
131
+ return messages;
132
+ }
133
+
134
+ function locationToObj(loc) {
135
+ if (!loc) return null;
136
+ const file = (loc.uri || '').replace(/^file:\/\//, '');
137
+ const line = (loc.range && loc.range.start) ? loc.range.start.line + 1 : null;
138
+ const col = (loc.range && loc.range.start) ? loc.range.start.character + 1 : null;
139
+ return { file, line, col };
140
+ }
141
+
142
+ function findProjectRoot(fromFile) {
143
+ const markers = ['Cargo.toml', 'package.json', 'CMakeLists.txt', '.git', 'pyproject.toml', '*.csproj', '*.sln'];
144
+ let dir = path.dirname(path.resolve(fromFile));
145
+ for (let i = 0; i < 8; i++) {
146
+ const entries = (() => { try { return fs.readdirSync(dir); } catch { return []; } })();
147
+ if (entries.some(e =>
148
+ e === 'Cargo.toml' || e === 'package.json' || e === 'CMakeLists.txt' ||
149
+ e === '.git' || e === 'pyproject.toml' || e.endsWith('.csproj') || e.endsWith('.sln')
150
+ )) return dir;
151
+ const parent = path.dirname(dir);
152
+ if (parent === dir) break;
153
+ dir = parent;
154
+ }
155
+ return path.dirname(path.resolve(fromFile));
156
+ }
157
+
158
+ // ─── OmniSharp LSP request builder ───────────────────────────────────────────
159
+
160
+ function buildOmniSharpRequest(fileUri, filePath, lineNum, colNum, rootPath) {
161
+ function msg(obj) {
162
+ const body = JSON.stringify(obj);
163
+ return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
164
+ }
165
+
166
+ const fileContent = (() => { try { return fs.readFileSync(filePath, 'utf8'); } catch { return ''; } })();
167
+
168
+ const init = msg({
169
+ jsonrpc: '2.0', id: 1, method: 'initialize',
170
+ params: {
171
+ processId: process.pid,
172
+ rootUri: `file://${rootPath.replace(/\\/g, '/')}`,
173
+ rootPath,
174
+ capabilities: {
175
+ textDocument: {
176
+ definition: { dynamicRegistration: false },
177
+ references: { dynamicRegistration: false },
178
+ hover: { dynamicRegistration: false },
179
+ synchronization: { dynamicRegistration: false },
180
+ },
181
+ workspace: { applyEdit: false },
182
+ },
183
+ initializationOptions: {
184
+ AutomaticWorkspaceInit: true,
185
+ },
186
+ },
187
+ });
188
+
189
+ const initialized = msg({ jsonrpc: '2.0', method: 'initialized', params: {} });
190
+
191
+ const didOpen = msg({
192
+ jsonrpc: '2.0', method: 'textDocument/didOpen',
193
+ params: {
194
+ textDocument: {
195
+ uri: fileUri,
196
+ languageId: 'csharp',
197
+ version: 1,
198
+ text: fileContent,
199
+ },
200
+ },
201
+ });
202
+
203
+ // Small delay to let OmniSharp analyze — send a dummy hover first
204
+ const hover = msg({
205
+ jsonrpc: '2.0', id: 10, method: 'textDocument/hover',
206
+ params: {
207
+ textDocument: { uri: fileUri },
208
+ position: { line: lineNum - 1, character: Math.max(0, colNum - 1) },
209
+ },
210
+ });
211
+
212
+ const definition = msg({
213
+ jsonrpc: '2.0', id: 2, method: 'textDocument/definition',
214
+ params: {
215
+ textDocument: { uri: fileUri },
216
+ position: { line: lineNum - 1, character: Math.max(0, colNum - 1) },
217
+ },
218
+ });
219
+
220
+ const references = msg({
221
+ jsonrpc: '2.0', id: 3, method: 'textDocument/references',
222
+ params: {
223
+ textDocument: { uri: fileUri },
224
+ position: { line: lineNum - 1, character: Math.max(0, colNum - 1) },
225
+ context: { includeDeclaration: true },
226
+ },
227
+ });
228
+
229
+ const shutdown = msg({ jsonrpc: '2.0', id: 99, method: 'shutdown', params: {} });
230
+
231
+ return init + initialized + didOpen + hover + definition + references + shutdown;
232
+ }
233
+
234
+ /**
235
+ * Find OmniSharp executable — checks:
236
+ * 1. omnisharp (if installed via dotnet tool install -g omnisharp)
237
+ * 2. OmniSharp (Windows capitalisation)
238
+ * 3. dotnet omnisharp (nuget global tool)
239
+ */
240
+ function findOmniSharp() {
241
+ for (const cmd of ['omnisharp', 'OmniSharp', 'OmniSharp.exe']) {
242
+ try {
243
+ execSync(process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`,
244
+ { stdio: 'ignore', timeout: 3000 });
245
+ return { cmd, args: ['--languageserver'] };
246
+ } catch {}
247
+ }
248
+ // Check dotnet tool: dotnet tool list -g
249
+ try {
250
+ const res = execSync('dotnet tool list -g', { encoding: 'utf8', timeout: 5000 });
251
+ if (/omnisharp/i.test(res)) {
252
+ return { cmd: 'omnisharp', args: ['--languageserver'] };
253
+ }
254
+ } catch {}
255
+ return null;
256
+ }
257
+
258
+ // ─── C# grep-based fallback ───────────────────────────────────────────────────
259
+
260
+ function grepCSharpSymbol(filePath, line, col) {
261
+ const content = fs.readFileSync(filePath, 'utf8');
262
+ const lines = content.split('\n');
263
+ const targetLine = lines[line - 1] || '';
264
+
265
+ const CSHARP_KEYWORDS = new Set([
266
+ 'public','private','protected','internal','static','readonly','const','abstract',
267
+ 'virtual','override','sealed','partial','async','await','new','return','using',
268
+ 'namespace','class','interface','struct','enum','record','void','var','let',
269
+ 'if','else','for','foreach','while','do','switch','case','break','continue',
270
+ 'try','catch','finally','throw','in','out','ref','params','this','base',
271
+ 'true','false','null','string','int','long','bool','double','float','object',
272
+ 'Task','List','Dictionary','IEnumerable','IList','IReadOnlyList',
273
+ ]);
274
+
275
+ let symbol = null;
276
+
277
+ if (col > 0) {
278
+ const slice = targetLine.slice(Math.max(0, col - 1));
279
+ const m = slice.match(/^([A-Za-z_]\w*)/);
280
+ if (m && !CSHARP_KEYWORDS.has(m[1])) symbol = m[1];
281
+ }
282
+
283
+ if (!symbol) {
284
+ const allTokens = [...targetLine.matchAll(/\b([A-Za-z_]\w*)\b/g)];
285
+ for (const tok of allTokens) {
286
+ if (!CSHARP_KEYWORDS.has(tok[1])) { symbol = tok[1]; break; }
287
+ }
288
+ }
289
+
290
+ if (!symbol) {
291
+ for (const offset of [1, -1, 2, -2]) {
292
+ const adjLine = lines[line - 1 + offset] || '';
293
+ const tokens = [...adjLine.matchAll(/\b([A-Za-z_]\w*)\b/g)];
294
+ for (const tok of tokens) {
295
+ if (!CSHARP_KEYWORDS.has(tok[1])) { symbol = tok[1]; break; }
296
+ }
297
+ if (symbol) break;
298
+ }
299
+ }
300
+
301
+ if (!symbol) {
302
+ return { error: `No symbol found at ${filePath}:${line}:${col}` };
303
+ }
304
+
305
+ const projectDir = findProjectRoot(filePath);
306
+ const csFiles = [];
307
+ function walkCs(dir) {
308
+ try {
309
+ for (const name of fs.readdirSync(dir)) {
310
+ if (['bin', 'obj', '.git', 'node_modules'].includes(name)) continue;
311
+ const full = path.join(dir, name);
312
+ if (fs.statSync(full).isDirectory()) walkCs(full);
313
+ else if (name.endsWith('.cs')) csFiles.push(full);
314
+ }
315
+ } catch {}
316
+ }
317
+ walkCs(projectDir);
318
+
319
+ const definitions = [];
320
+ const references = [];
321
+ const defPattern = new RegExp(
322
+ `(?:class|interface|record|struct|enum|void|async|public|private|protected)\\s+${symbol}\\b|\\b${symbol}\\s*\\(`
323
+ );
324
+ const refPattern = new RegExp(`\\b${symbol}\\b`);
325
+
326
+ for (const csFile of csFiles) {
327
+ const src = fs.readFileSync(csFile, 'utf8').split('\n');
328
+ for (let i = 0; i < src.length; i++) {
329
+ const srcLine = src[i];
330
+ if (defPattern.test(srcLine)) {
331
+ definitions.push({ file: csFile, line: i + 1, text: srcLine.trim().slice(0, 100) });
332
+ } else if (refPattern.test(srcLine)) {
333
+ references.push({ file: csFile, line: i + 1, text: srcLine.trim().slice(0, 80) });
334
+ }
335
+ }
336
+ }
337
+
338
+ return {
339
+ symbol,
340
+ projectDir,
341
+ definitions,
342
+ references,
343
+ };
344
+ }
345
+
346
+ // ─── Main C# goto handler ────────────────────────────────────────────────────
347
+
348
+ async function gotoCSharp(filePath, line, col) {
349
+ const absFile = path.resolve(filePath);
350
+ const rootDir = findProjectRoot(absFile);
351
+ const fileUri = `file://${absFile.replace(/\\/g, '/')}`;
352
+ const omnisharp = findOmniSharp();
353
+
354
+ // ── Try OmniSharp LSP first ──────────────────────────────────────────────
355
+ if (omnisharp) {
356
+ const input = buildOmniSharpRequest(fileUri, absFile, line, col, rootDir);
357
+
358
+ const res = spawnSync(omnisharp.cmd, omnisharp.args, {
359
+ input,
360
+ encoding: 'utf8',
361
+ timeout: DEFAULT_TIMEOUT_MS,
362
+ windowsHide: true,
363
+ });
364
+
365
+ if (!res.error && res.stdout) {
366
+ const messages = parseLspOutput(res.stdout);
367
+
368
+ let definitionResult = null;
369
+ let referencesResult = null;
370
+
371
+ for (const msg of messages) {
372
+ if (msg.id === 2 && msg.result !== undefined) {
373
+ const r = Array.isArray(msg.result) ? msg.result[0] : msg.result;
374
+ if (r) definitionResult = locationToObj(r);
375
+ }
376
+ if (msg.id === 3 && Array.isArray(msg.result)) {
377
+ referencesResult = msg.result.map(locationToObj).filter(Boolean);
378
+ }
379
+ }
380
+
381
+ // If LSP returned at least a definition, use it
382
+ if (definitionResult || referencesResult) {
383
+ return output(ok({
384
+ query: { file: absFile, line, col },
385
+ lsp: {
386
+ server: omnisharp.cmd,
387
+ language: 'csharp',
388
+ root: rootDir,
389
+ timeout_ms: DEFAULT_TIMEOUT_MS,
390
+ },
391
+ definition: definitionResult,
392
+ references: referencesResult || [],
393
+ ref_count: referencesResult ? referencesResult.length : 0,
394
+ }));
395
+ }
396
+ }
397
+
398
+ // OmniSharp found but returned nothing — fall through to grep with a note
399
+ const grep = grepCSharpSymbol(filePath, line, col);
400
+ if (grep.error) return output(fail(grep.error));
401
+
402
+ return output(ok({
403
+ query: { file: absFile, line, col },
404
+ lsp: {
405
+ server: omnisharp.cmd,
406
+ language: 'csharp',
407
+ root: rootDir,
408
+ note: 'OmniSharp did not return results (project may still be loading). Fell back to grep.',
409
+ },
410
+ symbol: grep.symbol,
411
+ definition: grep.definitions[0] || null,
412
+ all_definitions: grep.definitions.slice(0, 5),
413
+ references: grep.references.slice(0, 30),
414
+ ref_count: grep.references.length,
415
+ fallback: 'grep',
416
+ }));
417
+ }
418
+
419
+ // ── OmniSharp not installed — use grep fallback ──────────────────────────
420
+ const grep = grepCSharpSymbol(filePath, line, col);
421
+ if (grep.error) return output(fail(grep.error));
422
+
423
+ return output(ok({
424
+ query: { file: absFile, line, col },
425
+ lsp: {
426
+ server: 'grep-fallback',
427
+ language: 'csharp',
428
+ root: grep.projectDir,
429
+ note: 'OmniSharp not found. Install for full LSP: dotnet tool install -g omnisharp',
430
+ },
431
+ symbol: grep.symbol,
432
+ definition: grep.definitions[0] || null,
433
+ all_definitions: grep.definitions.slice(0, 5),
434
+ references: grep.references.slice(0, 30),
435
+ ref_count: grep.references.length,
436
+ fallback: 'grep',
437
+ }));
438
+ }
439
+
440
+ async function run(args) {
441
+ if (args.length === 0 || args[0] === '--help') {
442
+ console.log(HELP);
443
+ return;
444
+ }
445
+
446
+ const target = args[0];
447
+ const match = target.match(/^(.+):(\d+)(?::(\d+))?$/);
448
+ if (!match) {
449
+ return output(fail(`Bad format: "${target}". Expected file:line or file:line:col`));
450
+ }
451
+
452
+ const filePath = match[1];
453
+ const lineNum = parseInt(match[2], 10);
454
+ const colNum = parseInt(match[3] || '0', 10);
455
+
456
+ if (!fs.existsSync(filePath)) {
457
+ return output(fail(`File not found: ${filePath}`));
458
+ }
459
+
460
+ // C# — OmniSharp LSP with grep fallback
461
+ if (path.extname(filePath).toLowerCase() === '.cs') {
462
+ return gotoCSharp(filePath, lineNum, colNum);
463
+ }
464
+
465
+ const lspInfo = detectLsp(filePath);
466
+ if (!lspInfo) {
467
+ return output(fail(`No LSP server for extension: ${path.extname(filePath)}`));
468
+ }
469
+
470
+ if (!commandExists(lspInfo.server)) {
471
+ return output(fail(
472
+ `LSP server not found: ${lspInfo.server}\n` +
473
+ `Install it and ensure it is on PATH.`
474
+ ));
475
+ }
476
+
477
+ const absFile = path.resolve(filePath);
478
+ const rootDir = detectProjectRoot(absFile);
479
+ const fileUri = `file://${absFile.replace(/\\/g, '/')}`;
480
+ const rootUri = `file://${rootDir.replace(/\\/g, '/')}`;
481
+
482
+ const input = buildLspRequest(fileUri, lineNum, colNum, rootUri);
483
+
484
+ // rust-analyzer needs --stdio flag; clangd & pyright work without args by default
485
+ const serverArgs = lspInfo.server === 'rust-analyzer' ? ['--stdio'] : [];
486
+ const serverRes = spawnSync(lspInfo.server, serverArgs, {
487
+ input,
488
+ encoding: 'utf8',
489
+ timeout: DEFAULT_TIMEOUT_MS,
490
+ });
491
+
492
+ if (serverRes.error) {
493
+ if (serverRes.error.code === 'ETIMEDOUT') {
494
+ return output(fail(`LSP server timed out after ${DEFAULT_TIMEOUT_MS}ms`));
495
+ }
496
+ return output(fail(`LSP server error: ${serverRes.error.message}`));
497
+ }
498
+
499
+ const messages = parseLspOutput(serverRes.stdout || '');
500
+
501
+ let definitionResult = null;
502
+ let referencesResult = null;
503
+
504
+ for (const msg of messages) {
505
+ if (msg.id === 2 && msg.result !== undefined) {
506
+ const r = Array.isArray(msg.result) ? msg.result[0] : msg.result;
507
+ definitionResult = locationToObj(r);
508
+ }
509
+ if (msg.id === 3 && Array.isArray(msg.result)) {
510
+ referencesResult = msg.result.map(locationToObj).filter(Boolean);
511
+ }
512
+ }
513
+
514
+ output(ok({
515
+ query: {
516
+ file: absFile,
517
+ line: lineNum,
518
+ col: colNum,
519
+ },
520
+ lsp: {
521
+ server: lspInfo.server,
522
+ language: lspInfo.lang,
523
+ root: rootDir,
524
+ timeout_ms: DEFAULT_TIMEOUT_MS,
525
+ },
526
+ definition: definitionResult,
527
+ references: referencesResult || [],
528
+ ref_count: referencesResult ? referencesResult.length : 0,
529
+ }));
530
+ }
531
+
532
+ function detectProjectRoot(fromFile) {
533
+ const markers = ['Cargo.toml', 'package.json', 'CMakeLists.txt', '.git', 'pyproject.toml'];
534
+ let dir = path.dirname(fromFile);
535
+ for (let i = 0; i < 8; i++) {
536
+ if (markers.some(m => fs.existsSync(path.join(dir, m)))) return dir;
537
+ const parent = path.dirname(dir);
538
+ if (parent === dir) break;
539
+ dir = parent;
540
+ }
541
+ return path.dirname(fromFile);
542
+ }
543
+
544
+ module.exports = { run };