ghost-import-hunter 3.0.1 → 4.0.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.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/01Developer95/Ghost-Import-Hunter/main/media/header.png" alt="Ghost Hunter" width="100%">
2
+ <img src="https://raw.githubusercontent.com/01Developer95/Ghost-Import-Hunter/main/media/header_v4.png" alt="Ghost Import Hunter v4.0.0" width="100%">
3
3
  </div>
4
4
 
5
+ # Ghost Import Hunter v4.0.0
6
+
5
7
  <div align="center">
6
8
 
7
- ![Ghost Hunter](https://img.shields.io/badge/status-active-success.svg)
9
+ ![Ghost Import Hunter](https://img.shields.io/badge/status-active-success.svg)
8
10
  ![License](https://img.shields.io/badge/license-MIT-blue.svg)
9
11
  ![Node](https://img.shields.io/badge/node-%3E%3D16-brightgreen.svg)
10
12
 
@@ -17,16 +19,16 @@
17
19
  - **Deterministic Validation** - Verify every import against your actual installed modules. No guessing or regex.
18
20
  - **Deep Export Analysis** - Uses TypeScript Compiler API to correctly resolve `export *`, re-exports, and aliases.
19
21
  - **Local File Scanning** - Validates imports from your own local files, not just external packages.
20
- - **Zero Configuration** - Works out of the box. Just run `npx ghost-hunter` in your project root.
22
+ - **Zero Configuration** - Works out of the box. Just run `npx ghost-import-hunter` in your project root.
21
23
  - **CI/CD Ready** - Fails the build if hallucinations are detected. Preventing bad code from merging.
22
24
 
23
25
  ---
24
26
 
25
27
  ## 🤔 What is a Hallucination?
26
28
 
27
- AI coding assistants often suggest imports that **look real but don't exist**. Ghost Hunter catches these bugs before they break your app.
29
+ AI coding assistants often suggest imports that **look real but don't exist**. Ghost Import Hunter catches these bugs before they break your app.
28
30
 
29
- ### Examples of what Ghost Hunter catches:
31
+ ### Examples of what Ghost Import Hunter catches:
30
32
 
31
33
  **1. The "Fake Function" Hallucination**
32
34
  ```typescript
@@ -50,22 +52,22 @@ import { utils } from 'dependency-i-never-installed';
50
52
 
51
53
  ## 🧠 How it Works (Under the Hood)
52
54
 
53
- Ghost Hunter uses three core technologies to ensure your code is safe:
55
+ Ghost Import Hunter uses three core technologies to ensure your code is safe:
54
56
 
55
57
  ### 1. `glob` - The Scanner
56
58
  **Role:** Finding your files.
57
- Just like your terminal finds files when you type `ls *.ts`, Ghost Hunter uses `glob` to scan your entire project's TypeScript and JavaScript files, ignoring junk like `node_modules`.
59
+ Just like your terminal finds files when you type `ls *.ts`, Ghost Import Hunter uses `glob` to scan your entire project's TypeScript and JavaScript files, ignoring junk like `node_modules`.
58
60
 
59
61
  ### 2. TypeScript Compiler API - The Brain
60
62
  **Role:** Understanding your code.
61
- Unlike v1 which used Regex, v2.0 uses the real **TypeScript Compiler API** to parse your code, resolve symbols, and track exports across files. This allows it to:
63
+ Unlike early versions that relied on Regex, v4.0.0 uses the real **TypeScript Compiler API** to parse your code, resolve symbols, and track exports across files. This allows it to:
62
64
  - Follow `export * from ...` chains.
63
65
  - Understand aliased imports (`import { foo as bar }`).
64
66
  - Detect missing exports in local files.
65
67
 
66
68
  ### 3. `chalk` - The Reporter
67
69
  **Role:** Making sense of the output.
68
- When a hallucination is found, Ghost Hunter uses `chalk` to highlight the error in **red** and the file path in **bold**, making it impossible to miss critical bugs in your terminal.
70
+ When a hallucination is found, Ghost Import Hunter uses `chalk` to highlight the error in **red** and the file path in **bold**, making it impossible to miss critical bugs in your terminal.
69
71
 
70
72
  ---
71
73
 
@@ -137,7 +139,7 @@ Options:
137
139
 
138
140
  ## ⚙️ Configuration
139
141
 
140
- Ghost Hunter supports a `.ghostrc` file:
142
+ Ghost Import Hunter supports a `.ghostrc` file:
141
143
 
142
144
  ```json
143
145
  {
package/dist/analyzer.js CHANGED
@@ -37,14 +37,26 @@ exports.analyzeProject = analyzeProject;
37
37
  const ts = __importStar(require("typescript"));
38
38
  const path = __importStar(require("path"));
39
39
  const glob_1 = require("glob");
40
- async function analyzeProject(directory) {
40
+ async function analyzeProject(directory, options = {}) {
41
41
  const report = { hallucinations: [], unused: [], usedModules: [] };
42
42
  // 1. Find all files in the project
43
- const files = await (0, glob_1.glob)('**/*.{ts,tsx,js,jsx}', {
43
+ let files = await (0, glob_1.glob)('**/*.{ts,tsx,js,jsx}', {
44
44
  cwd: directory,
45
45
  ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
46
46
  absolute: true
47
47
  });
48
+ if (options.changedOnly) {
49
+ try {
50
+ const { execSync } = require('child_process');
51
+ // Get modified, deleted, and untracked files
52
+ const gitDiff = execSync('git diff --name-only HEAD && git ls-files --others --exclude-standard', { cwd: directory, encoding: 'utf8' });
53
+ const changedFiles = new Set(gitDiff.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(directory, f)));
54
+ files = files.filter(f => changedFiles.has(path.resolve(f)));
55
+ }
56
+ catch (err) {
57
+ console.warn('⚠️ Could not determine git changed files. Falling back to scanning all files.');
58
+ }
59
+ }
48
60
  if (files.length === 0) {
49
61
  return report;
50
62
  }
@@ -74,11 +86,19 @@ async function analyzeProject(directory) {
74
86
  // Map<Symbol, UnusedItem>
75
87
  const trackedImports = new Map();
76
88
  // Pass 1: Collect Imports & Check Hallucinations
77
- ts.forEachChild(sourceFile, (node) => {
89
+ const visitPass1 = (node) => {
78
90
  if (ts.isImportDeclaration(node)) {
79
91
  visitImportDeclaration(node, sourceFile, checker, report, trackedImports, allUsedModules);
80
92
  }
81
- });
93
+ else if (ts.isCallExpression(node)) {
94
+ visitCallExpression(node, sourceFile, checker, report, allUsedModules);
95
+ ts.forEachChild(node, visitPass1);
96
+ }
97
+ else {
98
+ ts.forEachChild(node, visitPass1);
99
+ }
100
+ };
101
+ visitPass1(sourceFile);
82
102
  // Pass 2: Check Usage
83
103
  // We visit all nodes EXCEPT ImportDeclarations (which we already processed)
84
104
  // If we find an identifier that resolves to a symbol in 'trackedImports', delete it from the map.
@@ -147,7 +167,24 @@ function visitImportDeclaration(node, sourceFile, checker, report, trackedImport
147
167
  if (node.importClause?.name) {
148
168
  const defaultImport = node.importClause.name;
149
169
  const defaultSymbol = checker.getSymbolAtLocation(defaultImport);
150
- if (defaultSymbol) {
170
+ // Check if module actually has a default export
171
+ const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
172
+ let hasDefaultExport = true;
173
+ if (moduleSymbol && moduleSymbol.exports) {
174
+ hasDefaultExport = moduleSymbol.exports.has(ts.escapeLeadingUnderscores('default'));
175
+ }
176
+ if (moduleSymbol && !hasDefaultExport) {
177
+ const { line } = sourceFile.getLineAndCharacterOfPosition(defaultImport.getStart());
178
+ report.hallucinations.push({
179
+ file: sourceFile.fileName,
180
+ line: line + 1,
181
+ start: defaultImport.getStart(),
182
+ end: defaultImport.getEnd(),
183
+ module: moduleName,
184
+ member: node.importClause.isTypeOnly ? 'default (type)' : 'default'
185
+ });
186
+ }
187
+ else if (defaultSymbol) {
151
188
  const { line } = sourceFile.getLineAndCharacterOfPosition(defaultImport.getStart());
152
189
  trackedImports.set(defaultSymbol, {
153
190
  file: sourceFile.fileName,
@@ -155,7 +192,7 @@ function visitImportDeclaration(node, sourceFile, checker, report, trackedImport
155
192
  start: defaultImport.getStart(),
156
193
  end: defaultImport.getEnd(),
157
194
  module: moduleName,
158
- member: 'default'
195
+ member: node.importClause.isTypeOnly ? 'default (type)' : 'default'
159
196
  });
160
197
  }
161
198
  }
@@ -219,9 +256,51 @@ function visitImportDeclaration(node, sourceFile, checker, report, trackedImport
219
256
  start: element.getStart(),
220
257
  end: element.getEnd(),
221
258
  module: moduleName,
222
- member: importName
259
+ member: (node.importClause?.isTypeOnly || element.isTypeOnly) ? `${importName} (type)` : importName
223
260
  });
224
261
  }
225
262
  });
226
263
  }
227
264
  }
265
+ function visitCallExpression(node, sourceFile, checker, report, allUsedModules) {
266
+ // Check for require('module')
267
+ if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
268
+ if (node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
269
+ const moduleName = node.arguments[0].text;
270
+ allUsedModules.add(moduleName);
271
+ // Hallucination Check
272
+ const symbol = checker.getSymbolAtLocation(node.arguments[0]);
273
+ if (!symbol) {
274
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
275
+ report.hallucinations.push({
276
+ file: sourceFile.fileName,
277
+ line: line + 1,
278
+ start: node.getStart(),
279
+ end: node.getEnd(),
280
+ module: moduleName,
281
+ member: 'require()'
282
+ });
283
+ }
284
+ }
285
+ }
286
+ // Check for import('module')
287
+ else if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
288
+ if (node.arguments.length >= 1 && ts.isStringLiteral(node.arguments[0])) {
289
+ const moduleName = node.arguments[0].text;
290
+ allUsedModules.add(moduleName);
291
+ // Hallucination Check
292
+ const symbol = checker.getSymbolAtLocation(node.arguments[0]);
293
+ if (!symbol) {
294
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
295
+ report.hallucinations.push({
296
+ file: sourceFile.fileName,
297
+ line: line + 1,
298
+ start: node.getStart(),
299
+ end: node.getEnd(),
300
+ module: moduleName,
301
+ member: 'import()'
302
+ });
303
+ }
304
+ }
305
+ }
306
+ }
package/dist/index.js CHANGED
@@ -54,6 +54,9 @@ program
54
54
  .option('--interactive', 'Interactively fix unused imports and hallucinations')
55
55
  .option('--prune', 'Uninstall completely unused dependencies from package.json')
56
56
  .option('--uninstall-self', 'Uninstall ghost-import-hunter globally from your system')
57
+ .option('--output <file>', 'Output report to JSON or HTML file')
58
+ .option('--changed', 'Only scan files changed in git (staged or unstaged)')
59
+ .option('--watch', 'Watch mode: auto re-scan on file save')
57
60
  .action(async (directory, options) => {
58
61
  if (options.uninstallSelf) {
59
62
  console.log(chalk_1.default.red('\n⚠️ WARNING: This will completely remove ghost-import-hunter from your system.'));
@@ -81,138 +84,172 @@ program
81
84
  }
82
85
  return;
83
86
  }
84
- console.log(chalk_1.default.blue(`👻 Ghost Import Hunter scanning: ${directory}...`));
85
- try {
86
- // New v2 Engine using TS Compiler API
87
- const report = await (0, analyzer_1.analyzeProject)(directory);
88
- let hasError = false;
89
- if (report.hallucinations.length > 0) {
90
- console.log(chalk_1.default.red('\n🚨 Hallucinations Detected (AI Lied!):'));
91
- report.hallucinations.forEach(h => {
92
- console.log(` - ${chalk_1.default.bold(h.module)}: Used member ${chalk_1.default.bold(h.member)} does not exist.`);
93
- console.log(` File: ${h.file}:${h.line}`);
94
- });
95
- hasError = true;
96
- }
97
- else {
98
- console.log(chalk_1.default.green('\n✅ No Hallucinations detected.'));
99
- }
100
- if (report.unused.length > 0) {
101
- console.log(chalk_1.default.yellow('\n⚠️ Unused Imports (Bloat):'));
102
- report.unused.forEach(u => {
103
- console.log(` - ${chalk_1.default.bold(u.module)}: Imported but never used.`);
104
- console.log(` File: ${u.file}:${u.line}`);
105
- });
106
- }
107
- if (options.interactive) {
108
- const fixes = [];
109
- const allIssues = [...report.hallucinations, ...report.unused];
110
- if (allIssues.length === 0) {
111
- console.log(chalk_1.default.green('\n✨ No issues to fix!'));
112
- return;
113
- }
114
- console.log(chalk_1.default.blue(`\n🕵️ Interactive Mode: Found ${allIssues.length} issues.`));
115
- const rl = readline.createInterface({
116
- input: process.stdin,
117
- output: process.stdout
118
- });
119
- for (const issue of allIssues) {
120
- const isHallucination = 'member' in issue && report.hallucinations.includes(issue);
121
- const type = isHallucination ? chalk_1.default.red('Hallucination') : chalk_1.default.yellow('Unused');
122
- console.log(chalk_1.default.gray('--------------------------------------------------'));
123
- console.log(`${type}: ${chalk_1.default.bold(issue.module)} (${issue.member})`);
124
- console.log(` File: ${issue.file}:${issue.line}`);
125
- const answer = await new Promise(resolve => {
126
- rl.question(chalk_1.default.cyan(' Action? [d]elete, [s]kip (default: skip): '), resolve);
87
+ const runAnalysis = async () => {
88
+ console.log(chalk_1.default.blue(`👻 Ghost Import Hunter scanning: ${directory}...`));
89
+ try {
90
+ // New v4 Engine using TS Compiler API
91
+ const report = await (0, analyzer_1.analyzeProject)(directory, { changedOnly: options.changed });
92
+ let hasError = false;
93
+ if (report.hallucinations.length > 0) {
94
+ console.log(chalk_1.default.red('\n🚨 Hallucinations Detected (AI Lied!):'));
95
+ report.hallucinations.forEach(h => {
96
+ console.log(` - ${chalk_1.default.bold(h.module)}: Used member ${chalk_1.default.bold(h.member)} does not exist.`);
97
+ console.log(` File: ${h.file}:${h.line}`);
127
98
  });
128
- if (answer.toLowerCase() === 'd') {
129
- fixes.push(issue);
130
- console.log(chalk_1.default.green(' -> Marked for deletion.'));
131
- }
132
- else {
133
- console.log(chalk_1.default.gray(' -> Skipped.'));
134
- }
135
- }
136
- rl.close();
137
- if (fixes.length > 0) {
138
- console.log(chalk_1.default.blue(`\n🔧 Applying ${fixes.length} fixes...`));
139
- await fixImports(fixes);
140
- console.log(chalk_1.default.green('✨ Fixes applied!'));
99
+ hasError = true;
141
100
  }
142
101
  else {
143
- console.log(chalk_1.default.yellow('\nℹ️ No changes made.'));
102
+ console.log(chalk_1.default.green('\n No Hallucinations detected.'));
144
103
  }
145
- // Skip the batch block below if we ran interactive
146
- return;
147
- }
148
- if (options.fix && report.unused.length > 0) {
149
- const rl = readline.createInterface({
150
- input: process.stdin,
151
- output: process.stdout
152
- });
153
- const answer = await new Promise(resolve => {
154
- rl.question(chalk_1.default.yellow(`\n❓ Found ${report.unused.length} unused imports. Do you want to fix them? (y/N) `), resolve);
155
- });
156
- rl.close();
157
- if (answer.toLowerCase() === 'y') {
158
- console.log(chalk_1.default.blue('\n🔧 Fixing unused imports...'));
159
- await fixImports(report.unused);
160
- console.log(chalk_1.default.green('✨ Auto-fix complete!'));
104
+ if (report.unused.length > 0) {
105
+ console.log(chalk_1.default.yellow('\n⚠️ Unused Imports (Bloat):'));
106
+ report.unused.forEach(u => {
107
+ console.log(` - ${chalk_1.default.bold(u.module)}: Imported but never used.`);
108
+ console.log(` File: ${u.file}:${u.line}`);
109
+ });
161
110
  }
162
- else {
163
- console.log(chalk_1.default.yellow('ℹ️ Auto-fix cancelled.'));
111
+ if (options.output) {
112
+ const outPath = path.resolve(directory, options.output);
113
+ const ext = path.extname(outPath).toLowerCase();
114
+ if (ext === '.html') {
115
+ const html = `<html>
116
+ <head><title>Ghost Import Hunter Report</title><style>body{font-family:sans-serif;padding:20px;}</style></head>
117
+ <body><h1>Ghost Import Hunter Report</h1><pre>${JSON.stringify(report, null, 2)}</pre></body>
118
+ </html>`;
119
+ fs.writeFileSync(outPath, html);
120
+ }
121
+ else {
122
+ fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
123
+ }
124
+ console.log(chalk_1.default.green(`\n📄 Report saved to ${options.output}`));
164
125
  }
165
- }
166
- if (options.prune) {
167
- const packageJsonPath = path.join(directory, 'package.json');
168
- if (fs.existsSync(packageJsonPath)) {
169
- console.log(chalk_1.default.blue('\n🔍 Checking for completely unused dependencies...'));
170
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
171
- const deps = Object.keys(pkg.dependencies || {});
172
- // Filter out types and our own tool just in case
173
- const projectDeps = deps.filter(d => !d.startsWith('@types/') && d !== 'ghost-import-hunter');
174
- const toRemove = projectDeps.filter(dep => !report.usedModules.includes(dep));
175
- if (toRemove.length > 0) {
176
- console.log(chalk_1.default.yellow(`\n🗑️ Found ${toRemove.length} unused dependencies:`));
177
- toRemove.forEach(d => console.log(` - ${chalk_1.default.bold(d)}`));
178
- const rl = readline.createInterface({
179
- input: process.stdin,
180
- output: process.stdout
181
- });
126
+ if (options.interactive) {
127
+ const fixes = [];
128
+ const allIssues = [...report.hallucinations, ...report.unused];
129
+ if (allIssues.length === 0) {
130
+ console.log(chalk_1.default.green('\n No issues to fix!'));
131
+ return;
132
+ }
133
+ console.log(chalk_1.default.blue(`\n🕵️ Interactive Mode: Found ${allIssues.length} issues.`));
134
+ const rl = readline.createInterface({
135
+ input: process.stdin,
136
+ output: process.stdout
137
+ });
138
+ for (const issue of allIssues) {
139
+ const isHallucination = 'member' in issue && report.hallucinations.includes(issue);
140
+ const type = isHallucination ? chalk_1.default.red('Hallucination') : chalk_1.default.yellow('Unused');
141
+ console.log(chalk_1.default.gray('--------------------------------------------------'));
142
+ console.log(`${type}: ${chalk_1.default.bold(issue.module)} (${issue.member})`);
143
+ console.log(` File: ${issue.file}:${issue.line}`);
182
144
  const answer = await new Promise(resolve => {
183
- rl.question(chalk_1.default.red(`\n❓ Are you sure you want to uninstall these packages? (y/N) `), resolve);
145
+ rl.question(chalk_1.default.cyan(' Action? [d]elete, [s]kip (default: skip): '), resolve);
184
146
  });
185
- rl.close();
186
- if (answer.toLowerCase() === 'y') {
187
- console.log(chalk_1.default.blue(`\n📦 Uninstalling ${toRemove.join(', ')}...`));
188
- try {
189
- const { execSync } = require('child_process');
190
- execSync(`npm uninstall ${toRemove.join(' ')}`, { stdio: 'inherit', cwd: directory });
191
- console.log(chalk_1.default.green('✨ Pruning complete!'));
147
+ if (answer.toLowerCase() === 'd') {
148
+ fixes.push(issue);
149
+ console.log(chalk_1.default.green(' -> Marked for deletion.'));
150
+ }
151
+ else {
152
+ console.log(chalk_1.default.gray(' -> Skipped.'));
153
+ }
154
+ }
155
+ rl.close();
156
+ if (fixes.length > 0) {
157
+ console.log(chalk_1.default.blue(`\n🔧 Applying ${fixes.length} fixes...`));
158
+ await fixImports(fixes);
159
+ console.log(chalk_1.default.green('✨ Fixes applied!'));
160
+ }
161
+ else {
162
+ console.log(chalk_1.default.yellow('\nℹ️ No changes made.'));
163
+ }
164
+ // Skip the batch block below if we ran interactive
165
+ return;
166
+ }
167
+ if (options.fix && report.unused.length > 0) {
168
+ const rl = readline.createInterface({
169
+ input: process.stdin,
170
+ output: process.stdout
171
+ });
172
+ const answer = await new Promise(resolve => {
173
+ rl.question(chalk_1.default.yellow(`\n❓ Found ${report.unused.length} unused imports. Do you want to fix them? (y/N) `), resolve);
174
+ });
175
+ rl.close();
176
+ if (answer.toLowerCase() === 'y') {
177
+ console.log(chalk_1.default.blue('\n🔧 Fixing unused imports...'));
178
+ await fixImports(report.unused);
179
+ console.log(chalk_1.default.green('✨ Auto-fix complete!'));
180
+ }
181
+ else {
182
+ console.log(chalk_1.default.yellow('ℹ️ Auto-fix cancelled.'));
183
+ }
184
+ }
185
+ if (options.prune) {
186
+ const packageJsonPath = path.join(directory, 'package.json');
187
+ if (fs.existsSync(packageJsonPath)) {
188
+ console.log(chalk_1.default.blue('\n🔍 Checking for completely unused dependencies...'));
189
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
190
+ const deps = Object.keys(pkg.dependencies || {});
191
+ // Filter out types and our own tool just in case
192
+ const projectDeps = deps.filter(d => !d.startsWith('@types/') && d !== 'ghost-import-hunter');
193
+ const toRemove = projectDeps.filter(dep => !report.usedModules.includes(dep));
194
+ if (toRemove.length > 0) {
195
+ console.log(chalk_1.default.yellow(`\n🗑️ Found ${toRemove.length} unused dependencies:`));
196
+ toRemove.forEach(d => console.log(` - ${chalk_1.default.bold(d)}`));
197
+ const rl = readline.createInterface({
198
+ input: process.stdin,
199
+ output: process.stdout
200
+ });
201
+ const answer = await new Promise(resolve => {
202
+ rl.question(chalk_1.default.red(`\n❓ Are you sure you want to uninstall these packages? (y/N) `), resolve);
203
+ });
204
+ rl.close();
205
+ if (answer.toLowerCase() === 'y') {
206
+ console.log(chalk_1.default.blue(`\n📦 Uninstalling ${toRemove.join(', ')}...`));
207
+ try {
208
+ const { execSync } = require('child_process');
209
+ execSync(`npm uninstall ${toRemove.join(' ')}`, { stdio: 'inherit', cwd: directory });
210
+ console.log(chalk_1.default.green('✨ Pruning complete!'));
211
+ }
212
+ catch (err) {
213
+ console.error(chalk_1.default.red('❌ Failed to uninstall packages:'), err);
214
+ }
192
215
  }
193
- catch (err) {
194
- console.error(chalk_1.default.red(' Failed to uninstall packages:'), err);
216
+ else {
217
+ console.log(chalk_1.default.yellow('ℹ️ Pruning cancelled.'));
195
218
  }
196
219
  }
197
220
  else {
198
- console.log(chalk_1.default.yellow('ℹ️ Pruning cancelled.'));
221
+ console.log(chalk_1.default.green('\n✨ No unused dependencies found in package.json!'));
199
222
  }
200
223
  }
201
224
  else {
202
- console.log(chalk_1.default.green('\n No unused dependencies found in package.json!'));
225
+ console.log(chalk_1.default.yellow('\n⚠️ No package.json found in the specified directory. Cannot prune dependencies.'));
203
226
  }
204
227
  }
205
- else {
206
- console.log(chalk_1.default.yellow('\n⚠️ No package.json found in the specified directory. Cannot prune dependencies.'));
228
+ if (!options.watch && hasError) {
229
+ process.exit(1);
207
230
  }
208
231
  }
209
- if (hasError) {
210
- process.exit(1);
232
+ catch (error) {
233
+ console.error(chalk_1.default.red('Error scanning project:'), error);
234
+ if (!options.watch) {
235
+ process.exit(1);
236
+ }
211
237
  }
238
+ };
239
+ if (options.watch) {
240
+ console.log(chalk_1.default.cyan(`\n👀 Watch mode enabled. Scanning on file changes...`));
241
+ await runAnalysis();
242
+ // Watch specific folder
243
+ fs.watch(directory, { recursive: true }, async (eventType, filename) => {
244
+ if (filename && (filename.endsWith('.ts') || filename.endsWith('.tsx') || filename.endsWith('.js') || filename.endsWith('.jsx'))) {
245
+ console.clear();
246
+ console.log(chalk_1.default.cyan(`\n🔄 File changed: ${filename}. Re-scanning...`));
247
+ await runAnalysis();
248
+ }
249
+ });
212
250
  }
213
- catch (error) {
214
- console.error(chalk_1.default.red('Error scanning project:'), error);
215
- process.exit(1);
251
+ else {
252
+ await runAnalysis();
216
253
  }
217
254
  });
218
255
  async function fixImports(unused) {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost-import-hunter",
3
- "version": "3.0.1",
3
+ "version": "4.0.1",
4
4
  "description": "Deterministic tool to detect AI hallucinations and code bloat by verifying import safety against installed node_modules",
5
5
  "repository": {
6
6
  "type": "git",
package/media/header.png DELETED
Binary file