ghost-import-hunter 1.0.2 → 2.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
@@ -15,6 +15,8 @@
15
15
  ## šŸš€ Features
16
16
 
17
17
  - **Deterministic Validation** - Verify every import against your actual installed modules. No guessing or regex.
18
+ - **Deep Export Analysis** - Uses TypeScript Compiler API to correctly resolve `export *`, re-exports, and aliases.
19
+ - **Local File Scanning** - Validates imports from your own local files, not just external packages.
18
20
  - **Zero Configuration** - Works out of the box. Just run `npx ghost-hunter` in your project root.
19
21
  - **CI/CD Ready** - Fails the build if hallucinations are detected. Preventing bad code from merging.
20
22
 
@@ -54,9 +56,12 @@ Ghost Hunter uses three core technologies to ensure your code is safe:
54
56
  **Role:** Finding your files.
55
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`.
56
58
 
57
- ### 2. `fs` (File System) - The Reader
58
- **Role:** Reading your code.
59
- `fs` is the engine that allows Ghost Hunter to open every file found by the scanner and read its contents to find imports to verify.
59
+ ### 2. TypeScript Compiler API - The Brain
60
+ **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:
62
+ - Follow `export * from ...` chains.
63
+ - Understand aliased imports (`import { foo as bar }`).
64
+ - Detect missing exports in local files.
60
65
 
61
66
  ### 3. `chalk` - The Reporter
62
67
  **Role:** Making sense of the output.
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyzeProject = analyzeProject;
37
+ const ts = __importStar(require("typescript"));
38
+ const path = __importStar(require("path"));
39
+ const glob_1 = require("glob");
40
+ async function analyzeProject(directory) {
41
+ const report = { hallucinations: [], unused: [] };
42
+ // 1. Find all files in the project
43
+ const files = await (0, glob_1.glob)('**/*.{ts,tsx,js,jsx}', {
44
+ cwd: directory,
45
+ ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
46
+ absolute: true
47
+ });
48
+ if (files.length === 0) {
49
+ return report;
50
+ }
51
+ // 2. Create TypeScript Program
52
+ const program = ts.createProgram(files, {
53
+ target: ts.ScriptTarget.ESNext,
54
+ module: ts.ModuleKind.CommonJS,
55
+ moduleResolution: ts.ModuleResolutionKind.NodeJs,
56
+ allowJs: true,
57
+ jsx: ts.JsxEmit.React,
58
+ esModuleInterop: true,
59
+ skipLibCheck: true
60
+ });
61
+ const checker = program.getTypeChecker();
62
+ // 3. Analyze each file
63
+ for (const sourceFile of program.getSourceFiles()) {
64
+ // Skip external library files (node_modules)
65
+ if (sourceFile.fileName.includes('node_modules'))
66
+ continue;
67
+ // Also ensure we are only analyzing files we actually found (extra safety)
68
+ // Normalize paths for comparison
69
+ const normalizedFilePath = path.resolve(sourceFile.fileName);
70
+ const isProjectFile = files.some(f => path.resolve(f) === normalizedFilePath);
71
+ if (!isProjectFile)
72
+ continue;
73
+ ts.forEachChild(sourceFile, (node) => {
74
+ if (ts.isImportDeclaration(node)) {
75
+ visitImportDeclaration(node, sourceFile, checker, report);
76
+ }
77
+ });
78
+ }
79
+ return report;
80
+ }
81
+ function visitImportDeclaration(node, sourceFile, checker, report) {
82
+ const moduleSpecifier = node.moduleSpecifier;
83
+ if (!ts.isStringLiteral(moduleSpecifier))
84
+ return;
85
+ const moduleName = moduleSpecifier.text;
86
+ // Resolve Module Symbol
87
+ const symbol = checker.getSymbolAtLocation(moduleSpecifier);
88
+ if (!symbol) {
89
+ // Module not found at all
90
+ // Get line number
91
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
92
+ report.hallucinations.push({
93
+ file: sourceFile.fileName,
94
+ line: line + 1,
95
+ module: moduleName,
96
+ member: '*' // Whole module missing
97
+ });
98
+ return;
99
+ }
100
+ // Check Named Imports
101
+ if (node.importClause && node.importClause.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
102
+ node.importClause.namedBindings.elements.forEach(element => {
103
+ const importName = element.propertyName?.text || element.name.text;
104
+ const importSymbol = checker.getSymbolAtLocation(element.name);
105
+ const { line } = sourceFile.getLineAndCharacterOfPosition(element.getStart());
106
+ if (importSymbol) {
107
+ const aliasedSymbol = checker.getAliasedSymbol(importSymbol);
108
+ if (aliasedSymbol) {
109
+ // Check for "unknown" symbol or missing declarations which indicates a hallucination
110
+ if (aliasedSymbol.escapedName === 'unknown' || !aliasedSymbol.declarations || aliasedSymbol.declarations.length === 0) {
111
+ report.hallucinations.push({
112
+ file: sourceFile.fileName,
113
+ line: line + 1,
114
+ module: moduleName,
115
+ member: importName
116
+ });
117
+ }
118
+ }
119
+ else {
120
+ // If no aliased symbol, it might be a direct interface/type or something we couldn't resolve deeply
121
+ // For now, if we have a symbol, we assume it exists to avoid false positives unless we are sure.
122
+ // But in our prototype `ghost` gave a symbol that resolved to `unknown`.
123
+ // If `getAliasedSymbol` throws or returns undefined, it might be a local symbol.
124
+ // Double check with module exports if possible
125
+ // const moduleExports = checker.getExportsOfModule(symbol);
126
+ // if (!moduleExports.some(e => e.name === importName)) {
127
+ // // Potential hallucination or just a type?
128
+ // }
129
+ }
130
+ }
131
+ else {
132
+ // No symbol found at location - definitely a hallucination
133
+ report.hallucinations.push({
134
+ file: sourceFile.fileName,
135
+ line: line + 1,
136
+ module: moduleName,
137
+ member: importName
138
+ });
139
+ }
140
+ });
141
+ }
142
+ }
package/dist/index.js CHANGED
@@ -6,8 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
- const scanner_1 = require("./scanner");
10
- const validator_1 = require("./validator");
9
+ const analyzer_1 = require("./analyzer");
11
10
  const program = new commander_1.Command();
12
11
  program
13
12
  .name('ghost-hunter')
@@ -17,14 +16,15 @@ program
17
16
  .action(async (directory) => {
18
17
  console.log(chalk_1.default.blue(`šŸ‘» Ghost Hunter scanning: ${directory}...`));
19
18
  try {
20
- const imports = await (0, scanner_1.scanProject)(directory);
21
- const report = await (0, validator_1.validateImports)(directory, imports);
19
+ // New v2 Engine using TS Compiler API
20
+ const report = await (0, analyzer_1.analyzeProject)(directory);
22
21
  if (report.hallucinations.length > 0) {
23
22
  console.log(chalk_1.default.red('\n🚨 Hallucinations Detected (AI Lied!):'));
24
23
  report.hallucinations.forEach(h => {
25
- console.log(` - ${chalk_1.default.bold(h.module)}: Used member ${chalk_1.default.bold(h.member)} does not exist in installed version.`);
24
+ console.log(` - ${chalk_1.default.bold(h.module)}: Used member ${chalk_1.default.bold(h.member)} does not exist.`);
26
25
  console.log(` File: ${h.file}:${h.line}`);
27
26
  });
27
+ process.exit(1); // Fail the build
28
28
  }
29
29
  else {
30
30
  console.log(chalk_1.default.green('\nāœ… No Hallucinations detected.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost-import-hunter",
3
- "version": "1.0.2",
3
+ "version": "2.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",
@@ -45,4 +45,4 @@
45
45
  "devDependencies": {
46
46
  "ts-node": "^10.9.2"
47
47
  }
48
- }
48
+ }