@tng-sh/js 0.2.1 → 0.2.5

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.
package/bin/tng.js CHANGED
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const { loadConfig } = require('../lib/config');
8
+ const { isIgnoredPath, filterIgnoredPaths, ignoredMessage } = require('../lib/ignore');
8
9
  const { saveTestFile } = require('../lib/saveFile');
9
10
  const { ping, getUserStats } = require('../index');
10
11
  const { applyFix } = require('../lib/fixApplier');
@@ -28,7 +29,7 @@ process.on('uncaughtException', (err) => {
28
29
  program
29
30
  .name('tng')
30
31
  .description('TNG - Advanced Code Audit, Test Generation, Visualization, Clone Detection, and Dead Code Analysis for JavaScript/TypeScript')
31
- .version('0.2.1');
32
+ .version('0.2.5');
32
33
 
33
34
  /**
34
35
  * Copy text to system clipboard
@@ -92,7 +93,11 @@ module.exports = {
92
93
  TEST_FRAMEWORK: "jest",
93
94
 
94
95
  // Test Directory
95
- TEST_DIRECTORY: "tests"
96
+ TEST_DIRECTORY: "tests",
97
+
98
+ // Ignore paths (relative to project root)
99
+ IGNORE_FILES: [],
100
+ IGNORE_FOLDERS: []
96
101
  };
97
102
  `;
98
103
  fs.writeFileSync(configPath, content);
@@ -189,11 +194,16 @@ program
189
194
  process.exit(1);
190
195
  }
191
196
 
197
+ const config = loadConfig();
198
+ const resolvedPath = path.resolve(options.file);
199
+ if (isIgnoredPath(resolvedPath, config)) {
200
+ console.log(chalk.red(ignoredMessage()));
201
+ process.exit(1);
202
+ }
203
+
192
204
  console.log(chalk.blue(`šŸ” Generating X-Ray for ${options.method} in ${options.file}...`));
193
205
 
194
206
  try {
195
- const config = loadConfig();
196
-
197
207
  // Re-use logic similar to _handleXrayFlow but tailored for CLI non-interactive output
198
208
  const { generateTest } = require('../index');
199
209
 
@@ -256,139 +266,183 @@ program
256
266
  .option('--all', 'Run dead code detection across the entire repo (respects .gitignore)')
257
267
  .option('--xray', 'Generate X-Ray visualization (Mermaid flowchart)')
258
268
  .option('--impact', 'Run impact analysis (blast radius check) on a method')
269
+ .option('--callsites', 'Find in-repo call sites for a method')
270
+ .option('--class <name>', 'Class/module name to disambiguate methods when multiple classes define the same method in a file')
259
271
  .option('--json', 'Output results as JSON events (machine-readable)')
260
272
 
261
273
  .action(async (options) => {
262
- if (options.method && options.file) {
263
- if (options.trace) {
264
- const { getSymbolicTrace } = require('../index');
265
- const GoUISession = require('../lib/goUiSession');
266
-
267
- try {
268
- const traceJson = getSymbolicTrace(
269
- path.resolve(options.file),
270
- options.method,
271
- null
272
- );
273
-
274
- if (options.json) {
275
- console.log(traceJson);
276
- return;
277
- }
278
-
279
- // Create temp file for the trace
280
- const tmpDir = require('os').tmpdir();
281
- const tmpFile = path.join(tmpDir, `trace-${Date.now()}.json`);
282
- fs.writeFileSync(tmpFile, traceJson);
274
+ if (options.callsites) {
275
+ if (!options.method) {
276
+ console.log(chalk.red('Error: --method is required for call site analysis.'));
277
+ process.exit(1);
278
+ }
283
279
 
284
- // Launch Go UI
285
- const session = new GoUISession();
286
- const binaryPath = session._binaryPath;
280
+ try {
281
+ const { findCallSites } = require('../index');
282
+ const projectRoot = process.cwd();
283
+ const resultJson = findCallSites(projectRoot, options.method);
287
284
 
288
- const { spawnSync } = require('child_process');
289
- spawnSync(binaryPath, ['js-trace-results', '--file', tmpFile], {
290
- stdio: 'inherit'
291
- });
285
+ if (options.json) {
286
+ console.log(resultJson);
287
+ return;
288
+ }
292
289
 
293
- // Cleanup
294
- try { fs.unlinkSync(tmpFile); } catch (e) { }
290
+ const parsed = JSON.parse(resultJson);
291
+ const sites = Array.isArray(parsed) ? parsed : (parsed.call_sites || parsed.sites || parsed);
292
+ if (!sites || sites.length === 0) {
293
+ console.log(chalk.green('\nNo call sites found in this project. You might be safe!'));
294
+ return;
295
+ }
295
296
 
296
- } catch (e) {
297
- console.log(chalk.red(`Trace failed: ${e.message}`));
298
- process.exit(1);
297
+ console.log(chalk.blue(`\nFound ${sites.length} call sites:\n`));
298
+ sites.slice(0, 200).forEach((site) => {
299
+ const file = site.file || 'unknown';
300
+ const line = site.line || 0;
301
+ const content = site.content || '';
302
+ console.log(`${chalk.yellow(file)}:${chalk.cyan(line)} ${content}`);
303
+ });
304
+ if (sites.length > 200) {
305
+ console.log(chalk.dim(`\n... ${sites.length - 200} more`));
299
306
  }
300
307
  return;
308
+ } catch (e) {
309
+ console.log(chalk.red(`Error: ${e.message}`));
310
+ process.exit(1);
311
+ }
312
+ }
313
+ // 1. Symbolic Trace
314
+ if (options.method && options.file && options.trace) {
315
+ const { getSymbolicTrace } = require('../index');
316
+ const GoUISession = require('../lib/goUiSession');
317
+ const config = loadConfig();
318
+ const resolvedPath = path.resolve(options.file);
319
+ if (isIgnoredPath(resolvedPath, config)) {
320
+ console.log(chalk.red(ignoredMessage()));
321
+ process.exit(1);
301
322
  }
302
323
 
303
- if (options.xray) {
304
- const config = loadConfig();
305
- const { generateTest: nativeGenerateTest } = require('../index');
324
+ try {
325
+ const traceJson = getSymbolicTrace(
326
+ resolvedPath,
327
+ options.method,
328
+ options.class || null
329
+ );
306
330
 
307
- try {
308
- const resultJson = nativeGenerateTest(
309
- path.resolve(options.file),
310
- options.method,
311
- null,
312
- 'visualize',
313
- JSON.stringify(config),
314
- (msg, percent) => {
315
- if (options.json) return;
316
- if (percent % 20 === 0) console.log(chalk.dim(`[${percent}%] ${msg}`));
317
- }
318
- );
319
-
320
- if (options.json) {
321
- console.log(resultJson);
322
- return;
323
- }
331
+ if (options.json) {
332
+ console.log(traceJson);
333
+ return;
334
+ }
324
335
 
325
- // For global --xray, we just launch the Premium UI directly if not JSON
326
- const GoUISession = require('../lib/goUiSession');
327
- const session = new GoUISession();
328
- let mermaidCode = "";
329
- let explanation = "";
330
- try {
331
- const parsed = JSON.parse(resultJson);
332
- mermaidCode = parsed.mermaid_code || resultJson;
333
- explanation = parsed.explanation || "";
334
- } catch (e) {
335
- mermaidCode = resultJson;
336
- }
336
+ // Create temp file for the trace
337
+ const tmpDir = require('os').tmpdir();
338
+ const tmpFile = path.join(tmpDir, `trace-${Date.now()}.json`);
339
+ fs.writeFileSync(tmpFile, traceJson);
337
340
 
338
- // Copy to clipboard
339
- copyToClipboard(mermaidCode);
341
+ // Launch Go UI
342
+ const session = new GoUISession();
343
+ const binaryPath = session._binaryPath;
340
344
 
341
- await session.showXrayResults(options.method, '', mermaidCode, explanation);
342
- } catch (e) {
343
- console.log(chalk.red(`X-Ray failed: ${e.message}`));
344
- process.exit(1);
345
- }
346
- return;
347
- }
345
+ const { spawnSync } = require('child_process');
346
+ spawnSync(binaryPath, ['js-trace-results', '--file', tmpFile], {
347
+ stdio: 'inherit'
348
+ });
348
349
 
350
+ // Cleanup
351
+ try { fs.unlinkSync(tmpFile); } catch (e) { }
349
352
 
350
- if (options.impact) {
351
- await runImpact(options.file, options.method, options.json);
352
- return;
353
+ } catch (e) {
354
+ console.log(chalk.red(`Trace failed: ${e.message}`));
355
+ process.exit(1);
353
356
  }
357
+ return;
358
+ }
354
359
 
355
- if (!options.type && !options.audit) {
356
- console.log(chalk.red('Error: --type <type> is required.'));
360
+ // 2. X-Ray
361
+ if (options.method && options.file && options.xray) {
362
+ const config = loadConfig();
363
+ const resolvedPath = path.resolve(options.file);
364
+ if (isIgnoredPath(resolvedPath, config)) {
365
+ console.log(chalk.red(ignoredMessage()));
357
366
  process.exit(1);
358
367
  }
359
- generateTest(options.file, options.method, options.type, options.audit, options.json);
360
- } else if (options.file && !options.method) {
361
- if (options.clones) {
362
- await runClones(options.file, options.level || 'all', options.json);
363
- } else if (options.deadcode) {
364
- if (options.all) {
365
- await runDeadCodeRepo(options.json);
366
- } else {
367
- await runDeadCode(options.file, options.json);
368
+ const { generateTest: nativeGenerateTest } = require('../index');
369
+
370
+ try {
371
+ const resultJson = nativeGenerateTest(
372
+ resolvedPath,
373
+ options.method,
374
+ options.class || null,
375
+ 'visualize',
376
+ JSON.stringify(config),
377
+ (msg, percent) => {
378
+ if (options.json) return;
379
+ if (percent % 20 === 0) console.log(chalk.dim(`[${percent}%] ${msg}`));
380
+ }
381
+ );
382
+
383
+ if (options.json) {
384
+ console.log(resultJson);
385
+ return;
368
386
  }
369
- } else {
370
- console.log(chalk.yellow('Specify a method with -m, use --outline to see methods, or run "tng i" for full selection.'));
371
- }
372
- if (options.xray) {
373
- // ... (existing xray logic) ...
374
- return;
387
+
388
+ const GoUISession = require('../lib/goUiSession');
389
+ const session = new GoUISession();
390
+ let mermaidCode = "";
391
+ let explanation = "";
392
+ try {
393
+ const parsed = JSON.parse(resultJson);
394
+ mermaidCode = parsed.mermaid_code || resultJson;
395
+ explanation = parsed.explanation || "";
396
+ } catch (e) {
397
+ mermaidCode = resultJson;
398
+ }
399
+
400
+ copyToClipboard(mermaidCode);
401
+ await session.showXrayResults(options.method, '', mermaidCode, explanation);
402
+ } catch (e) {
403
+ console.log(chalk.red(`X-Ray failed: ${e.message}`));
404
+ process.exit(1);
375
405
  }
406
+ return;
407
+ }
376
408
 
377
- if (options.impact) {
378
- await runImpact(options.file, options.method, options.json);
379
- return;
409
+ // 3. Impact Analysis
410
+ if (options.method && options.file && options.impact) {
411
+ await runImpact(options.file, options.method, options.json, options.class || null);
412
+ return;
413
+ }
414
+
415
+ // 4. Clone Detection
416
+ if (options.file && options.clones) {
417
+ await runClones(options.file, options.level || 'all', options.json);
418
+ return;
419
+ }
420
+
421
+ // 5. Dead Code Analysis
422
+ if (options.deadcode) {
423
+ if (options.all) {
424
+ await runDeadCodeRepo(options.json);
425
+ } else if (options.file) {
426
+ await runDeadCode(options.file, options.json);
427
+ } else {
428
+ console.log(chalk.red('Error: --file <path> or --all is required for deadcode analysis.'));
429
+ process.exit(1);
380
430
  }
431
+ return;
432
+ }
381
433
 
434
+ // 6. Test Generation / Audit (Method + File required)
435
+ if (options.method && options.file) {
382
436
  if (!options.type && !options.audit) {
383
437
  console.log(chalk.red('Error: --type <type> is required.'));
384
438
  process.exit(1);
385
439
  }
386
- generateTest(options.file, options.method, options.type, options.audit, options.json);
387
- } else if (options.deadcode && options.all && !options.file && !options.method) {
388
- await runDeadCodeRepo(options.json);
389
- } else if (options.file && !options.method) {
390
- launchInteractive();
440
+ generateTest(options.file, options.method, options.type, options.audit, options.json, options.class || null);
441
+ return;
391
442
  }
443
+
444
+ // 7. Interactive Fallback
445
+ launchInteractive();
392
446
  });
393
447
 
394
448
  /**
@@ -396,6 +450,7 @@ program
396
450
  */
397
451
  async function runClones(filePath, level, jsonMode = false) {
398
452
  const { analyzeClones } = require('../index');
453
+ const config = loadConfig();
399
454
  const absolutePath = path.resolve(filePath);
400
455
  const projectRoot = process.cwd();
401
456
 
@@ -403,6 +458,10 @@ async function runClones(filePath, level, jsonMode = false) {
403
458
  console.log(chalk.red(`File not found: ${filePath}`));
404
459
  process.exit(1);
405
460
  }
461
+ if (isIgnoredPath(absolutePath, config)) {
462
+ console.log(chalk.red(ignoredMessage()));
463
+ process.exit(1);
464
+ }
406
465
 
407
466
  const GoUISession = require('../lib/goUiSession');
408
467
  const session = new GoUISession();
@@ -452,12 +511,17 @@ async function runClones(filePath, level, jsonMode = false) {
452
511
  */
453
512
  async function runDeadCode(filePath, jsonMode = false) {
454
513
  const { detectDeadCode } = require('../index');
514
+ const config = loadConfig();
455
515
  const absolutePath = path.resolve(filePath);
456
516
 
457
517
  if (!fs.existsSync(absolutePath)) {
458
518
  console.log(chalk.red(`File not found: ${filePath}`));
459
519
  process.exit(1);
460
520
  }
521
+ if (isIgnoredPath(absolutePath, config)) {
522
+ console.log(chalk.red(ignoredMessage()));
523
+ process.exit(1);
524
+ }
461
525
 
462
526
  const GoUISession = require('../lib/goUiSession');
463
527
  const session = new GoUISession();
@@ -498,13 +562,14 @@ async function runDeadCode(filePath, jsonMode = false) {
498
562
  }
499
563
  }
500
564
 
501
- function listDeadCodeFiles(projectRoot) {
565
+ function listDeadCodeFiles(projectRoot, config) {
502
566
  const { listDeadcodeFiles } = require('../index');
503
567
  try {
504
568
  const filesJson = listDeadcodeFiles(projectRoot);
505
569
  const files = JSON.parse(filesJson);
506
570
  if (!Array.isArray(files)) return [];
507
- return files.filter((f) => !f.endsWith('.d.ts'));
571
+ const withoutTypes = files.filter((f) => !f.endsWith('.d.ts'));
572
+ return filterIgnoredPaths(withoutTypes, config, projectRoot);
508
573
  } catch (e) {
509
574
  return [];
510
575
  }
@@ -513,7 +578,8 @@ function listDeadCodeFiles(projectRoot) {
513
578
  async function runDeadCodeRepo(jsonMode = false) {
514
579
  const { detectDeadCode } = require('../index');
515
580
  const projectRoot = process.cwd();
516
- const files = listDeadCodeFiles(projectRoot);
581
+ const config = loadConfig();
582
+ const files = listDeadCodeFiles(projectRoot, config);
517
583
 
518
584
  if (files.length === 0) {
519
585
  console.log(chalk.yellow('No files found for dead code analysis.'));
@@ -674,7 +740,7 @@ program
674
740
  /**
675
741
  * Logic to generate test or run audit (delegates to native binary)
676
742
  */
677
- async function generateTest(filePath, methodName, testType, auditMode = false, jsonMode = false) {
743
+ async function generateTest(filePath, methodName, testType, auditMode = false, jsonMode = false, className = null) {
678
744
  const config = loadConfig();
679
745
 
680
746
  // Initialize JSON session if requested
@@ -706,6 +772,16 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
706
772
  }
707
773
  process.exit(1);
708
774
  }
775
+ if (isIgnoredPath(absolutePath, config)) {
776
+ const message = ignoredMessage();
777
+ if (jsonSession) {
778
+ jsonSession.displayError(message);
779
+ jsonSession.stop();
780
+ } else {
781
+ console.log(chalk.red(message));
782
+ }
783
+ process.exit(1);
784
+ }
709
785
 
710
786
  const { runAudit, generateTest: nativeGenerateTest } = require('../index');
711
787
 
@@ -744,7 +820,7 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
744
820
  resultJson = runAudit(
745
821
  absolutePath,
746
822
  methodName,
747
- null, // class_name
823
+ className,
748
824
  testType || null,
749
825
  JSON.stringify(config),
750
826
  callback
@@ -753,7 +829,7 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
753
829
  resultJson = nativeGenerateTest(
754
830
  absolutePath,
755
831
  methodName,
756
- null, // class_name
832
+ className,
757
833
  testType || null,
758
834
  JSON.stringify(config),
759
835
  callback
@@ -814,6 +890,10 @@ program.on('--help', () => {
814
890
  console.log(' 3: Fuzzy (Fuzzy structural, catches patterns with small variations)');
815
891
  console.log(' all: Runs all detection levels (default)');
816
892
  console.log('');
893
+ console.log('Ignore Paths (from tng.config.js):');
894
+ console.log(' IGNORE_FILES: [ "relative/path/file.js", ... ]');
895
+ console.log(' IGNORE_FOLDERS: [ "relative/path/folder", ... ]');
896
+ console.log('');
817
897
  console.log('X-Ray Visualization:');
818
898
  console.log(' tng src/components/MyComponent.js render --xray');
819
899
  console.log(' tng --file=api/handler.js --method=post --xray --json');
@@ -824,8 +904,9 @@ program.on('--help', () => {
824
904
  /**
825
905
  * Logic to run impact analysis
826
906
  */
827
- async function runImpact(filePath, methodName, jsonMode = false) {
907
+ async function runImpact(filePath, methodName, jsonMode = false, className = null) {
828
908
  const { analyzeImpact } = require('../index');
909
+ const config = loadConfig();
829
910
  const absolutePath = path.resolve(filePath);
830
911
  const projectRoot = process.cwd();
831
912
 
@@ -833,10 +914,14 @@ async function runImpact(filePath, methodName, jsonMode = false) {
833
914
  console.log(chalk.red(`File not found: ${filePath}`));
834
915
  process.exit(1);
835
916
  }
917
+ if (isIgnoredPath(absolutePath, config)) {
918
+ console.log(chalk.red(ignoredMessage()));
919
+ process.exit(1);
920
+ }
836
921
 
837
922
  if (jsonMode) {
838
923
  try {
839
- const resultJson = analyzeImpact(projectRoot, absolutePath, methodName);
924
+ const resultJson = analyzeImpact(projectRoot, absolutePath, methodName, className);
840
925
  console.log(resultJson);
841
926
  } catch (e) {
842
927
  console.error(JSON.stringify({ error: e.message }));
@@ -848,7 +933,7 @@ async function runImpact(filePath, methodName, jsonMode = false) {
848
933
  console.log(chalk.blue(`šŸ’„ Analyzing impact for ${methodName} in ${filePath}...`));
849
934
 
850
935
  try {
851
- const resultJson = analyzeImpact(projectRoot, absolutePath, methodName);
936
+ const resultJson = analyzeImpact(projectRoot, absolutePath, methodName, className);
852
937
  const result = JSON.parse(resultJson);
853
938
 
854
939
  if (result.status === 'error') {
Binary file
Binary file
Binary file
Binary file
package/index.d.ts CHANGED
@@ -69,4 +69,4 @@ export declare function listDeadcodeFiles(projectRoot: string): string
69
69
  * Analyzes the impact of changes in a specific method compared to git HEAD.
70
70
  * Returns a JSON string containing breaking changes and impacted files.
71
71
  */
72
- export declare function analyzeImpact(projectRoot: string, filePath: string, methodName: string): string
72
+ export declare function analyzeImpact(projectRoot: string, filePath: string, methodName: string, className?: string | undefined | null): string
@@ -5,8 +5,8 @@ const chalk = require('chalk');
5
5
  const GoUISession = require('./goUiSession');
6
6
  const { getFileOutline, generateTest, ping, getUserStats } = require('../index');
7
7
  const { loadConfig } = require('./config');
8
+ const { filterIgnoredPaths } = require('./ignore');
8
9
  const { saveTestFile } = require('./saveFile');
9
- const { applyFix } = require('./fixApplier');
10
10
 
11
11
  class GenerateTestsUI {
12
12
  constructor(cliMode = false) {
@@ -127,19 +127,23 @@ class GenerateTestsUI {
127
127
 
128
128
  let title = 'Select JavaScript File';
129
129
  if (isAudit) title = 'Select JavaScript File to Audit';
130
- else if (isImpact) title = 'Select JavaScript File to Impact Audit';
130
+ else if (isImpact) title = 'Select JavaScript File for Regression Check';
131
131
  else if (isXray) title = 'Select File for X-Ray';
132
132
  else if (isTrace) title = 'Select File for Symbolic Trace';
133
133
 
134
- const selectedName = this.goUiSession.showListView(title, items);
134
+ while (true) {
135
+ const selectedName = this.goUiSession.showListView(title, items);
135
136
 
136
- if (selectedName === 'back') return 'back';
137
- if (!selectedName || selectedName === 'exit') return 'exit';
137
+ if (selectedName === 'back') return 'back';
138
+ if (!selectedName || selectedName === 'exit') return 'exit';
138
139
 
139
- const selectedFile = path.resolve(cwd, selectedName);
140
- const result = await this._showMethodsForFile(selectedFile, isAudit, isXray, isTrace, isImpact);
141
- if (result === 'main_menu') return 'main_menu';
142
- return result;
140
+ const selectedFile = path.resolve(cwd, selectedName);
141
+ const result = await this._showMethodsForFile(selectedFile, isAudit, isXray, isTrace, isImpact);
142
+
143
+ if (result === 'main_menu') return 'main_menu';
144
+ if (result === 'exit') return 'exit';
145
+ // If result is 'back', we loop again
146
+ }
143
147
  }
144
148
 
145
149
  async _showMethodsForFile(filePath, isAudit = false, isXray = false, isTrace = false, isImpact = false) {
@@ -149,60 +153,60 @@ class GenerateTestsUI {
149
153
  outline = JSON.parse(result);
150
154
  } catch (e) {
151
155
  console.error(chalk.red(`\nError parsing file: ${e.message}\n`));
152
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
156
+ return 'back';
153
157
  }
154
158
 
155
159
  const methods = outline.methods || [];
156
160
  if (methods.length === 0) {
157
161
  this.goUiSession.showNoItems('methods');
158
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
162
+ return 'back';
159
163
  }
160
164
 
161
165
  const fileName = path.basename(filePath);
162
166
  const items = methods.map(m => ({
163
- name: m.class_name ? `${m.class_name}.${m.name}` : m.name,
167
+ name: m.class_name ? `${m.class_name}#${m.name}` : m.name,
164
168
  path: `Function in ${fileName}`,
165
169
  methodData: m
166
170
  }));
167
171
 
168
172
  let title = 'Select Method';
169
173
  if (isAudit) title = `Select Method to Audit for ${fileName}`;
170
- else if (isImpact) title = `Select Method to Impact Audit for ${fileName}`;
174
+ else if (isImpact) title = `Select Method for Regression Check for ${fileName}`;
171
175
  else if (isXray) title = `Select Method to X-Ray for ${fileName}`;
172
176
  else if (isTrace) title = `Select Method to Trace for ${fileName}`;
173
177
 
174
178
  const selectedDisplay = this.goUiSession.showListView(title, items);
175
179
 
176
- if (selectedDisplay === 'back' || !selectedDisplay) return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
180
+ if (selectedDisplay === 'back' || !selectedDisplay) return 'back';
177
181
 
178
182
  const selectedMethod = items.find(i => i.name === selectedDisplay)?.methodData;
179
183
 
180
184
  if (selectedMethod) {
181
185
  if (isTrace) {
182
- await this._launchTrace(filePath, selectedMethod.name);
183
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
186
+ await this._launchTrace(filePath, selectedMethod.name, selectedMethod.class_name || null);
187
+ return 'back';
184
188
  }
185
189
 
186
190
  if (isXray) {
187
191
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, 'visualize', false, true);
188
192
  if (choice === 'main_menu') return 'main_menu';
189
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
193
+ return 'back';
190
194
  }
191
195
 
192
196
  if (isImpact) {
193
197
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, false, false, true);
194
198
  if (choice === 'main_menu') return 'main_menu';
195
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
199
+ return 'back';
196
200
  }
197
201
 
198
202
  if (isAudit) {
199
203
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, true);
200
204
  if (choice === 'main_menu') return 'main_menu';
201
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
205
+ return 'back';
202
206
  }
203
207
 
204
208
  const testType = this.goUiSession.showJsTestMenu();
205
- if (testType === 'back') return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
209
+ if (testType === 'back') return 'back';
206
210
 
207
211
  const finalType = testType === 'auto' ? null : testType;
208
212
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, finalType, false);
@@ -211,7 +215,7 @@ class GenerateTestsUI {
211
215
  this._showPostGenerationMenu(choice);
212
216
  }
213
217
  }
214
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
218
+ return 'back';
215
219
  }
216
220
 
217
221
  async _generateTestsForMethod(filePath, method, testType, isAudit = false, isXray = false, isImpact = false) {
@@ -250,7 +254,7 @@ class GenerateTestsUI {
250
254
  const { analyzeImpact } = require('../index');
251
255
  const projectRoot = process.cwd();
252
256
 
253
- console.log(chalk.blue(`\nšŸ” Analyzing impact for ${method.name}...`));
257
+ console.log(chalk.blue(`\nAnalyzing impact for ${method.name}...`));
254
258
 
255
259
  let impactResult;
256
260
  try {
@@ -259,7 +263,7 @@ class GenerateTestsUI {
259
263
  // Artificial delay for UX
260
264
  await new Promise(r => setTimeout(r, 300));
261
265
 
262
- const json = analyzeImpact(projectRoot, filePath, method.name);
266
+ const json = analyzeImpact(projectRoot, filePath, method.name, method.class_name || null);
263
267
  progress.update('Comparing logic...', { percent: 80 });
264
268
 
265
269
  return json;
@@ -272,7 +276,7 @@ class GenerateTestsUI {
272
276
  }
273
277
 
274
278
  await this.goUiSession.showImpactResults(impactResult);
275
- return { message: 'Impact audit complete' };
279
+ return { message: 'Regression check complete' };
276
280
  }
277
281
 
278
282
  async _handleAuditFlow(filePath, method, testType) {
@@ -281,7 +285,7 @@ class GenerateTestsUI {
281
285
  const fileName = path.basename(filePath);
282
286
  const displayName = method.class_name ? `${method.class_name}#${method.name}` : `${fileName}#${method.name}`;
283
287
 
284
- console.log(chalk.blue(`\nšŸ” Auditing ${displayName}...`));
288
+ console.log(chalk.blue(`\nAuditing ${displayName}...`));
285
289
 
286
290
  let auditResult;
287
291
  try {
@@ -572,7 +576,7 @@ class GenerateTestsUI {
572
576
  }
573
577
  }
574
578
  } catch (e) {
575
- console.log(chalk.yellow(`\nāš ļø Failed to open editor: ${e.message}`));
579
+ console.log(chalk.yellow(`\n Failed to open editor: ${e.message}`));
576
580
  }
577
581
  }
578
582
 
@@ -589,18 +593,20 @@ class GenerateTestsUI {
589
593
  ];
590
594
 
591
595
  try {
592
- return await glob(patterns, {
596
+ const files = await glob(patterns, {
593
597
  ignore,
594
598
  absolute: true,
595
599
  onlyFiles: true
596
600
  });
601
+ const config = loadConfig();
602
+ return filterIgnoredPaths(files, config, process.cwd());
597
603
  } catch (e) {
598
604
  console.error(chalk.red(`\nError searching for files: ${e.message}\n`));
599
605
  return [];
600
606
  }
601
607
  }
602
608
 
603
- async _launchTrace(filePath, methodName) {
609
+ async _launchTrace(filePath, methodName, className) {
604
610
  const { getSymbolicTrace } = require('../index');
605
611
  const fs = require('fs');
606
612
  const path = require('path');
@@ -610,7 +616,7 @@ class GenerateTestsUI {
610
616
  // 1. Generate Trace (with Spinner)
611
617
  const result = this.goUiSession.showSpinner(`Tracing ${methodName}...`, () => {
612
618
  try {
613
- const traceJson = getSymbolicTrace(filePath, methodName, null);
619
+ const traceJson = getSymbolicTrace(filePath, methodName, className || null);
614
620
  const tmpDir = require('os').tmpdir();
615
621
  const f = path.join(tmpDir, `trace-${Date.now()}.json`);
616
622
  fs.writeFileSync(f, traceJson);
@@ -649,19 +655,21 @@ class GenerateTestsUI {
649
655
  path: path.dirname(file)
650
656
  }));
651
657
 
652
- const selectedName = this.goUiSession.showListView('Select File for Duplicate Detection', items);
658
+ while (true) {
659
+ const selectedName = this.goUiSession.showListView('Select File for Duplicate Detection', items);
653
660
 
654
- if (selectedName === 'back') return 'back';
655
- if (!selectedName || selectedName === 'exit') return 'exit';
661
+ if (selectedName === 'back') return 'back';
662
+ if (!selectedName || selectedName === 'exit') return 'exit';
656
663
 
657
- const selectedFile = path.resolve(cwd, selectedName);
664
+ const selectedFile = path.resolve(cwd, selectedName);
658
665
 
659
- // Show level selection
660
- const level = this.goUiSession.showCloneMenu();
661
- if (level === 'back') return this._showClonesSelection();
666
+ // Show level selection
667
+ const level = this.goUiSession.showCloneMenu();
668
+ if (level === 'back') continue;
662
669
 
663
- await this._runClones(selectedFile, level);
664
- return 'main_menu';
670
+ await this._runClones(selectedFile, level);
671
+ return 'main_menu';
672
+ }
665
673
  }
666
674
 
667
675
  async _runClones(filePath, level) {
package/lib/ignore.js ADDED
@@ -0,0 +1,56 @@
1
+ const path = require('path');
2
+
3
+ function normalizeEntry(entry) {
4
+ if (typeof entry !== 'string') return null;
5
+ const trimmed = entry.trim();
6
+ if (!trimmed) return null;
7
+ return trimmed.replace(/\\/g, '/');
8
+ }
9
+
10
+ function resolveConfigPath(entry, projectRoot) {
11
+ const normalized = normalizeEntry(entry);
12
+ if (!normalized) return null;
13
+ if (path.isAbsolute(normalized)) {
14
+ return path.resolve(normalized);
15
+ }
16
+ return path.resolve(projectRoot, normalized);
17
+ }
18
+
19
+ function isIgnoredPath(filePath, config = {}, projectRoot = process.cwd()) {
20
+ if (!filePath) return false;
21
+
22
+ const absolutePath = path.resolve(filePath);
23
+ const ignoreFiles = Array.isArray(config.IGNORE_FILES) ? config.IGNORE_FILES : [];
24
+ const ignoreFolders = Array.isArray(config.IGNORE_FOLDERS) ? config.IGNORE_FOLDERS : [];
25
+
26
+ for (const entry of ignoreFiles) {
27
+ const resolved = resolveConfigPath(entry, projectRoot);
28
+ if (resolved && resolved === absolutePath) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ for (const entry of ignoreFolders) {
34
+ const resolved = resolveConfigPath(entry, projectRoot);
35
+ if (!resolved) continue;
36
+ if (absolutePath === resolved) return true;
37
+ if (absolutePath.startsWith(resolved + path.sep)) return true;
38
+ }
39
+
40
+ return false;
41
+ }
42
+
43
+ function filterIgnoredPaths(paths, config = {}, projectRoot = process.cwd()) {
44
+ if (!Array.isArray(paths) || paths.length === 0) return [];
45
+ return paths.filter((p) => !isIgnoredPath(p, config, projectRoot));
46
+ }
47
+
48
+ function ignoredMessage() {
49
+ return 'Ignored by TNG config (ignore_files/ignore_folders). Remove it from config to proceed.';
50
+ }
51
+
52
+ module.exports = {
53
+ isIgnoredPath,
54
+ filterIgnoredPaths,
55
+ ignoredMessage
56
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tng-sh/js",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "TNG JavaScript CLI",
5
5
  "repository": {
6
6
  "type": "git",
Binary file
Binary file