entkapp 5.3.0 → 5.4.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.
@@ -2,13 +2,18 @@ import path from 'path';
2
2
 
3
3
  /**
4
4
  * Production-Grade Native Rust AST Parser Bridge (OXC)
5
- * Fixed for Windows environments to prevent path-based "Unexpected token" errors.
5
+ * Optimized for Monorepos and Windows environments.
6
+ * Implements the same semantic logic as ASTAnalyzer but using the high-performance OXC parser.
6
7
  */
7
8
  export class OxcAnalyzer {
8
9
  constructor(context) {
9
10
  this.context = context;
10
11
  this.oxc = null;
11
12
  this.isAvailable = false;
13
+ this.scopeStack = [];
14
+ this.currentScope = null;
15
+ this.scopeCounter = 0;
16
+ this.pass = 1;
12
17
  }
13
18
 
14
19
  async init() {
@@ -35,7 +40,6 @@ export class OxcAnalyzer {
35
40
 
36
41
  /**
37
42
  * WINDOWS FIX: Robust path normalization for OXC.
38
- * Replaces backslashes with forward slashes to prevent escape sequence errors.
39
43
  */
40
44
  normalizePath(filePath) {
41
45
  if (!filePath) return filePath;
@@ -64,13 +68,10 @@ export class OxcAnalyzer {
64
68
  lang: "typescript"
65
69
  });
66
70
  } catch (e) {
67
- // Fallback with normalized path if the options object fails
68
71
  try {
69
72
  result = this.oxc.parseSync(normalizedPath, cleanContent);
70
73
  } catch (innerErr) {
71
- if (this.context.verbose) {
72
- console.error(`[OXC-ERROR] Native parse failed for: ${normalizedPath}`);
73
- }
74
+ if (this.context.verbose) console.error(`[OXC-ERROR] Native parse failed for: ${normalizedPath}`);
74
75
  return false;
75
76
  }
76
77
  }
@@ -79,11 +80,8 @@ export class OxcAnalyzer {
79
80
  try {
80
81
  parsedResult = typeof result === 'string' ? JSON.parse(result) : JSON.parse(JSON.stringify(result));
81
82
  } catch (err) {
82
- if (result && typeof result === 'object') {
83
- parsedResult = result;
84
- } else {
85
- return false;
86
- }
83
+ if (result && typeof result === 'object') parsedResult = result;
84
+ else return false;
87
85
  }
88
86
 
89
87
  let programRoot = null;
@@ -93,24 +91,35 @@ export class OxcAnalyzer {
93
91
  else if (parsedResult.type === 'Program' || parsedResult.body) programRoot = parsedResult;
94
92
  }
95
93
 
96
- // --- VALIDATION CHECK ---
97
- // If the file has content but OXC returns an empty body, it's a silent failure.
98
94
  if (cleanContent.trim().length > 0 && (!programRoot || !programRoot.body || programRoot.body.length === 0)) {
99
- if (this.context.verbose) {
100
- console.warn(`[OXC-WARNING] Silent failure detected for ${normalizedPath}. Body is empty despite content. Triggering Fallback...`);
101
- }
102
- return false; // This triggers the fallback to TypeScript in the engine
103
- }
104
-
105
- if (!programRoot || !programRoot.body) {
95
+ if (this.context.verbose) console.warn(`[OXC-WARNING] Silent failure detected for ${normalizedPath}. Triggering Fallback...`);
106
96
  return false;
107
97
  }
108
98
 
109
- fileNode.ast = programRoot;
110
- fileNode.symbolTable = new Map();
99
+ if (!programRoot || !programRoot.body) return false;
100
+
101
+ fileNode.ast = programRoot;
102
+ fileNode.symbolTable = new Map();
103
+
104
+ // --- TWO-PASS ANALYSIS ---
111
105
 
112
- this.walkOxcAst(programRoot, fileNode, cleanContent, 1);
113
- this.walkOxcAst(programRoot, fileNode, cleanContent, 2);
106
+ // Pass 1: Declarations
107
+ this.currentScope = { symbols: new Map(), parent: null, children: [] };
108
+ this.scopeStack = [this.currentScope];
109
+ this.scopeCounter = 0;
110
+ this.pass = 1;
111
+ this.walkOxcAst(programRoot, fileNode, cleanContent);
112
+
113
+ // Pass 2: References
114
+ if (this.scopeStack.length > 0) {
115
+ this.currentScope = this.scopeStack[0];
116
+ this.scopeCounter = 0;
117
+ this.pass = 2;
118
+ this.walkOxcAst(programRoot, fileNode, cleanContent);
119
+ }
120
+
121
+ this.scopeStack = [];
122
+ this.currentScope = null;
114
123
 
115
124
  return true;
116
125
  } catch (e) {
@@ -118,9 +127,163 @@ export class OxcAnalyzer {
118
127
  }
119
128
  }
120
129
 
121
- // ... (walkOxcAst and other methods remain the same as in original)
122
- walkOxcAst(node, fileNode, content, pass) {
130
+ pushScope() {
131
+ if (this.pass === 1) {
132
+ const newScope = { symbols: new Map(), parent: this.currentScope, children: [] };
133
+ if (this.currentScope) this.currentScope.children.push(newScope);
134
+ this.scopeStack.push(newScope);
135
+ this.currentScope = newScope;
136
+ } else {
137
+ if (this.currentScope && this.currentScope.children) {
138
+ const nextScope = this.currentScope.children[this.scopeCounter++];
139
+ if (nextScope) {
140
+ this.scopeStack.push(nextScope);
141
+ this.currentScope = nextScope;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ popScope() {
148
+ this.scopeStack.pop();
149
+ this.currentScope = this.scopeStack[this.scopeStack.length - 1];
150
+ }
151
+
152
+ walkOxcAst(node, fileNode, content) {
123
153
  if (!node) return;
124
- // (Implementation omitted for brevity, should be copied from original OxcAnalyzer.js)
154
+
155
+ const isScopeNode = node.type === 'BlockStatement' || node.type === 'FunctionDeclaration' ||
156
+ node.type === 'ClassDeclaration' || node.type === 'ArrowFunctionExpression';
157
+
158
+ let previousCounter = 0;
159
+ if (isScopeNode) {
160
+ if (this.pass === 2) previousCounter = this.scopeCounter;
161
+ this.scopeCounter = 0;
162
+ this.pushScope();
163
+ }
164
+
165
+ if (this.pass === 1) {
166
+ this.handleNodePass1(node, fileNode, content);
167
+ } else {
168
+ this.handleNodePass2(node, fileNode, content);
169
+ }
170
+
171
+ // Traverse children
172
+ for (const key in node) {
173
+ const child = node[key];
174
+ if (Array.isArray(child)) {
175
+ child.forEach(c => this.walkOxcAst(c, fileNode, content));
176
+ } else if (child && typeof child === 'object' && child.type) {
177
+ this.walkOxcAst(child, fileNode, content);
178
+ }
179
+ }
180
+
181
+ if (isScopeNode) {
182
+ this.popScope();
183
+ if (this.pass === 2) this.scopeCounter = previousCounter + 1;
184
+ }
185
+ }
186
+
187
+ handleNodePass1(node, fileNode, content) {
188
+ switch (node.type) {
189
+ case 'ImportDeclaration':
190
+ this.handleImport(node, fileNode);
191
+ break;
192
+ case 'ExportNamedDeclaration':
193
+ this.handleExportNamed(node, fileNode);
194
+ break;
195
+ case 'ExportDefaultDeclaration':
196
+ fileNode.internalExports.set('default', { type: 'default', start: node.start, end: node.end });
197
+ break;
198
+ case 'ExportAllDeclaration':
199
+ if (node.source) {
200
+ const specifier = node.source.value;
201
+ fileNode.explicitImports.add(specifier);
202
+ fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
203
+ }
204
+ break;
205
+ }
206
+ }
207
+
208
+ handleNodePass2(node, fileNode, content) {
209
+ switch (node.type) {
210
+ case 'Identifier':
211
+ if (!this.isLocalShadowing(node.name)) {
212
+ fileNode.instantiatedIdentifiers.add(node.name);
213
+ }
214
+ break;
215
+ case 'CallExpression':
216
+ this.handleCall(node, fileNode);
217
+ break;
218
+ case 'MemberExpression':
219
+ if (node.property && node.property.name) {
220
+ fileNode.instantiatedIdentifiers.add(node.property.name);
221
+ }
222
+ break;
223
+ }
224
+ }
225
+
226
+ handleImport(node, fileNode) {
227
+ if (!node.source) return;
228
+ const specifier = node.source.value;
229
+ fileNode.explicitImports.add(specifier);
230
+
231
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
232
+ const pkgName = this._extractPackageName(specifier);
233
+ let isInternal = false;
234
+ if (this.context.pathMapper?.isTsconfigAlias?.(specifier)) isInternal = true;
235
+ if (!isInternal && this.context.workspaceGraph?.isLocalWorkspaceSpecifier?.(specifier)) isInternal = true;
236
+ if (!isInternal) fileNode.externalPackageUsage.add(pkgName);
237
+ }
238
+ }
239
+
240
+ handleExportNamed(node, fileNode) {
241
+ if (node.declaration) {
242
+ const decl = node.declaration;
243
+ const name = decl.id?.name || (decl.declarations?.[0]?.id?.name);
244
+ if (name) {
245
+ fileNode.internalExports.set(name, { type: 'export', start: node.start, end: node.end });
246
+ }
247
+ } else if (node.specifiers) {
248
+ node.specifiers.forEach(spec => {
249
+ const name = spec.exported.name || spec.exported.value;
250
+ fileNode.internalExports.set(name, { type: 'export', start: spec.start, end: spec.end });
251
+ });
252
+ }
253
+ }
254
+
255
+ handleCall(node, fileNode) {
256
+ const callee = node.callee;
257
+ if (callee.type === 'Identifier' && callee.name === 'require') {
258
+ const arg = node.arguments[0];
259
+ if (arg && (arg.type === 'StringLiteral' || arg.type === 'Literal')) {
260
+ const specifier = arg.value;
261
+ fileNode.explicitImports.add(specifier);
262
+ }
263
+ } else if (callee.type === 'Import') {
264
+ const arg = node.arguments[0];
265
+ if (arg && (arg.type === 'StringLiteral' || arg.type === 'Literal')) {
266
+ const specifier = arg.value;
267
+ fileNode.explicitImports.add(specifier);
268
+ fileNode.dynamicImports.add(specifier);
269
+ }
270
+ }
271
+ }
272
+
273
+ isLocalShadowing(name) {
274
+ let scope = this.currentScope;
275
+ while (scope) {
276
+ if (scope.symbols.has(name)) return scope.parent !== null;
277
+ scope = scope.parent;
278
+ }
279
+ return false;
280
+ }
281
+
282
+ _extractPackageName(specifier) {
283
+ if (specifier.startsWith('@')) {
284
+ const parts = specifier.split('/');
285
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
286
+ }
287
+ return specifier.split('/')[0];
125
288
  }
126
289
  }
@@ -35,45 +35,89 @@ const SENSITIVE_NAME_PATTERNS = [
35
35
  /private[_-]?key/i,
36
36
  /client[_-]?secret/i,
37
37
  /app[_-]?secret/i,
38
+ /security[_-]?token/i,
39
+ /master[_-]?key/i,
40
+ /root[_-]?password/i,
41
+
38
42
  // Database credentials
39
43
  /db[_-]?pass(word)?/i,
40
44
  /database[_-]?pass(word)?/i,
41
45
  /db[_-]?url/i,
42
46
  /database[_-]?url/i,
43
47
  /connection[_-]?string/i,
44
- // Passwords
48
+ /postgres(ql)?[_-]?url/i,
49
+ /mongo(db)?[_-]?url/i,
50
+ /redis[_-]?url/i,
51
+
52
+ // Passwords & Pins
45
53
  /^password$/i,
46
54
  /^passwd$/i,
47
55
  /^pwd$/i,
48
56
  /[_-]password$/i,
49
- // Tokens
57
+ /[_-]pass$/i,
58
+ /pass(phrase|code)/i,
59
+ /admin[_-]?pass/i,
60
+ /pin[_-]?number/i,
61
+
62
+ // Tokens & Sessions
50
63
  /[_-]token$/i,
51
64
  /^token$/i,
52
- /jwt[_-]?secret/i,
53
- /session[_-]?secret/i,
65
+ /jwt[_-]?(secret|token)/i,
66
+ /session[_-]?(secret|token|id)/i,
54
67
  /cookie[_-]?secret/i,
55
- // Cloud provider keys
56
- /aws[_-]?(access[_-]?key|secret|session[_-]?token)/i,
57
- /gcp[_-]?key/i,
58
- /azure[_-]?(key|secret|connection)/i,
59
- // Service-specific
60
- /stripe[_-]?(key|secret)/i,
61
- /twilio[_-]?(auth|token|sid)/i,
62
- /sendgrid[_-]?key/i,
63
- /github[_-]?token/i,
64
- /slack[_-]?(token|webhook)/i,
65
- /discord[_-]?(token|secret)/i,
66
- /openai[_-]?(key|token)/i,
68
+ /oauth[_-]?(token|secret|client)/i,
69
+ /refresh[_-]?token/i,
70
+ /csrf[_-]?token/i,
71
+
72
+ // Cloud Provider Keys (AWS, GCP, Azure)
73
+ /aws[_-]?(access[_-]?key|secret|session[_-]?token|key[_-]?id)/i,
74
+ /gcp[_-]?(key|secret|token|sa[_-]?key)/i,
75
+ /google[_-]?(api[_-]?key|credentials|client[_-]?secret)/i,
76
+ /azure[_-]?(key|secret|connection|token|tenant[_-]?id)/i,
77
+
78
+ // AI & Machine Learning Platforms
79
+ /openai[_-]?(key|token|secret)/i,
67
80
  /anthropic[_-]?key/i,
68
- /webhook[_-]?(url|secret)/i,
69
- /encryption[_-]?key/i,
81
+ /cohere[_-]?key/i,
82
+ /huggingface[_-]?token/i,
83
+ /replicate[_-]?api/i,
84
+ /langchain[_-]?api/i,
85
+ /pinecone[_-]?api/i,
86
+ /gemini[_-]?key/i,
87
+
88
+ // Service-Specific (CI/CD, Dev Tools, Payment, Comms)
89
+ /stripe[_-]?(key|secret|webhook)/i,
90
+ /twilio[_-]?(auth|token|sid)/i,
91
+ /sendgrid[_-]?(key|api)/i,
92
+ /github[_-]?(token|pat|secret|app[_-]?id)/i,
93
+ /gitlab[_-]?(token|pat|secret)/i,
94
+ /slack[_-]?(token|webhook|secret)/i,
95
+ /discord[_-]?(token|secret|webhook)/i,
96
+ /pagerduty[_-]?(token|key)/i,
97
+ /datadog[_-]?(api[_-]?key|app[_-]?key)/i,
98
+ /sentry[_-]?(dsn|auth[_-]?token)/i,
99
+ /heroku[_-]?(api[_-]?key|oauth)/i,
100
+ /atlassian[_-]?(token|secret)/i,
101
+ /jira[_-]?(token|password)/i,
102
+ /npm[_-]?auth[_-]?token/i,
103
+ /jfrog[_-]?(token|password|api)/i,
104
+ /firebase[_-]?(key|secret|token)/i,
105
+ /supabase[_-]?(key|secret|service[_-]?role)/i,
106
+
107
+ // Webhooks, Crypto, and Infrastructure
108
+ /webhook[_-]?(url|secret|token)/i,
109
+ /encryption[_-]?(key|secret|iv)/i,
70
110
  /signing[_-]?key/i,
71
- /hmac[_-]?key/i,
111
+ /hmac[_-]?(key|secret)/i,
72
112
  /salt$/i,
73
- /ssh[_-]?key/i,
74
- /private[_-]?key/i,
75
- /cert(ificate)?/i,
76
- /credential/i,
113
+ /ssh[_-]?(key|private|public)/i,
114
+ /cert(ificate)?(s)?/i,
115
+ /credential(s)?/i,
116
+ /tls[_-]?(key|cert)/i,
117
+ /ssl[_-]?(key|cert)/i,
118
+ /kube(config)?[_-]?(token|secret)/i,
119
+ /vault[_-]?(token|secret|key)/i,
120
+ /bitcoind?[_-]?(rpc)?password/i,
77
121
  ];
78
122
 
79
123
  /**
@@ -84,37 +128,55 @@ const SENSITIVE_VALUE_PATTERNS = [
84
128
  // AWS Access Key IDs
85
129
  { pattern: /AKIA[0-9A-Z]{16}/, label: 'AWS_ACCESS_KEY_ID', severity: SecretSeverity.CRITICAL },
86
130
  // AWS Secret Access Keys (40-char base64-ish)
87
- { pattern: /[A-Za-z0-9/+=]{40}/, label: 'AWS_SECRET_KEY_CANDIDATE', severity: SecretSeverity.MEDIUM },
88
- // Generic high-entropy hex strings (32+ chars)
89
- { pattern: /^[0-9a-f]{32,}$/i, label: 'HEX_SECRET', severity: SecretSeverity.HIGH },
90
- // Generic high-entropy base64 strings (32+ chars)
91
- { pattern: /^[A-Za-z0-9+/]{32,}={0,2}$/, label: 'BASE64_SECRET', severity: SecretSeverity.MEDIUM },
131
+ { pattern: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/, label: 'AWS_SECRET_KEY_CANDIDATE', severity: SecretSeverity.MEDIUM },
132
+
133
+ // Generic high-entropy hex strings (32+ chars) - Added word boundaries to reduce false positives
134
+ { pattern: /\b[0-9a-f]{32,}\b/i, label: 'HEX_SECRET', severity: SecretSeverity.HIGH },
135
+ // Generic high-entropy base64 strings (32+ chars) - Replaced rigid anchors with lookarounds
136
+ { pattern: /(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{32,}={0,2}(?![A-Za-z0-9+/])/, label: 'BASE64_SECRET', severity: SecretSeverity.MEDIUM },
137
+
92
138
  // JWT tokens
93
- { pattern: /^ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, label: 'JWT_TOKEN', severity: SecretSeverity.CRITICAL },
94
- // GitHub personal access tokens
95
- { pattern: /ghp_[A-Za-z0-9]{36}/, label: 'GITHUB_PAT', severity: SecretSeverity.CRITICAL },
96
- { pattern: /github_pat_[A-Za-z0-9_]{82}/, label: 'GITHUB_PAT_FINE', severity: SecretSeverity.CRITICAL },
139
+ { pattern: /\bey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/, label: 'JWT_TOKEN', severity: SecretSeverity.CRITICAL },
140
+
141
+ // GitHub access tokens (Updated for all token formats: ghp, gho, ghu, ghs, ghr, fine-grained)
142
+ { pattern: /\bgh[pousr]_[A-Za-z0-9]{36}\b/, label: 'GITHUB_TOKEN_CLASSIC', severity: SecretSeverity.CRITICAL },
143
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/, label: 'GITHUB_PAT_FINE', severity: SecretSeverity.CRITICAL },
144
+ { pattern: /\bghs_[A-Za-z0-9\.\-_]{36,}\b/, label: 'GITHUB_APP_STATELESS_TOKEN', severity: SecretSeverity.CRITICAL }, // Supports the modern 2026 ghs_ JWT format
145
+
97
146
  // Stripe keys
98
- { pattern: /sk_(live|test)_[A-Za-z0-9]{24,}/, label: 'STRIPE_SECRET_KEY', severity: SecretSeverity.CRITICAL },
99
- { pattern: /pk_(live|test)_[A-Za-z0-9]{24,}/, label: 'STRIPE_PUBLIC_KEY', severity: SecretSeverity.HIGH },
100
- // Slack tokens
101
- { pattern: /xox[baprs]-[A-Za-z0-9-]{10,}/, label: 'SLACK_TOKEN', severity: SecretSeverity.CRITICAL },
102
- // Discord tokens
103
- { pattern: /[MN][A-Za-z0-9]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}/, label: 'DISCORD_TOKEN', severity: SecretSeverity.CRITICAL },
104
- // Twilio SID
105
- { pattern: /AC[a-f0-9]{32}/, label: 'TWILIO_SID', severity: SecretSeverity.HIGH },
147
+ { pattern: /\bsk_(live|test)_[A-Za-z0-9]{24,}\b/, label: 'STRIPE_SECRET_KEY', severity: SecretSeverity.CRITICAL },
148
+ { pattern: /\bpk_(live|test)_[A-Za-z0-9]{24,}\b/, label: 'STRIPE_PUBLIC_KEY', severity: SecretSeverity.HIGH },
149
+
150
+ // Slack tokens & Webhooks
151
+ { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, label: 'SLACK_TOKEN', severity: SecretSeverity.CRITICAL },
152
+ { pattern: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9_]{8}\/B[A-Z0-9_]{8}\/[A-Za-z0-9_]{24}/, label: 'SLACK_WEBHOOK', severity: SecretSeverity.CRITICAL },
153
+
154
+ // Discord tokens & Webhooks
155
+ { pattern: /\b[MN][A-Za-z0-9]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}\b/, label: 'DISCORD_TOKEN', severity: SecretSeverity.CRITICAL },
156
+ { pattern: /https:\/\/discord\.com\/api\/webhooks\/[0-9]{18,20}\/[A-Za-z0-9_-]{68,}/, label: 'DISCORD_WEBHOOK', severity: SecretSeverity.CRITICAL },
157
+
158
+ // Twilio SID & Auth Token
159
+ { pattern: /\bAC[a-f0-9]{32}\b/, label: 'TWILIO_SID', severity: SecretSeverity.HIGH },
160
+ { pattern: /\b[a-f0-9]{32}\b/, label: 'TWILIO_AUTH_TOKEN_CANDIDATE', severity: SecretSeverity.MEDIUM }, // Twilio tokens are raw 32-char hex strings often found near SIDs
161
+
106
162
  // SendGrid API key
107
- { pattern: /SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/, label: 'SENDGRID_KEY', severity: SecretSeverity.CRITICAL },
108
- // OpenAI API key
109
- { pattern: /sk-[A-Za-z0-9]{32,}/, label: 'OPENAI_KEY', severity: SecretSeverity.CRITICAL },
110
- // Generic UUID-like tokens that look like secrets (not just any UUID)
111
- { pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, label: 'UUID_SECRET_CANDIDATE', severity: SecretSeverity.MEDIUM },
112
- // Google API Key
113
- { pattern: /AIza[0-9A-Za-z\\-_]{35}/, label: 'GOOGLE_API_KEY', severity: SecretSeverity.CRITICAL },
114
- // Firebase Web API Key
115
- { pattern: /AIzaSy[0-9A-Za-z\\-_]{33}/, label: 'FIREBASE_API_KEY', severity: SecretSeverity.CRITICAL },
116
- ];
163
+ { pattern: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/, label: 'SENDGRID_KEY', severity: SecretSeverity.CRITICAL },
164
+
165
+ // OpenAI & Anthropic API keys (Updated for Legacy, Project, and Claude token formats)
166
+ { pattern: /\bsk-[A-Za-z0-9]{32,}\b/, label: 'OPENAI_KEY_LEGACY', severity: SecretSeverity.CRITICAL },
167
+ { pattern: /\bsk-proj-[A-Za-z0-9-_]{40,}\b/, label: 'OPENAI_PROJECT_KEY', severity: SecretSeverity.CRITICAL },
168
+ { pattern: /\bsk-ant-sid01-[A-Za-z0-9-_]{93}\b/, label: 'ANTHROPIC_CLAUDE_KEY', severity: SecretSeverity.CRITICAL },
117
169
 
170
+ // Generic UUID-like tokens
171
+ { pattern: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i, label: 'UUID_SECRET_CANDIDATE', severity: SecretSeverity.MEDIUM },
172
+
173
+ // Google & Firebase Key
174
+ { pattern: /\bAIza[0-9A-Za-z\\-_]{35}\b/, label: 'GOOGLE_API_KEY', severity: SecretSeverity.CRITICAL },
175
+ { pattern: /\bAIzaSy[0-9A-Za-z\\-_]{33}\b/, label: 'FIREBASE_API_KEY', severity: SecretSeverity.CRITICAL },
176
+
177
+ // Private Key Files (Detects multi-line PEM blocks often found inside JSON/Env variables)
178
+ { pattern: /-----BEGIN [A-Z ]+ PRIVATE KEY-----/, label: 'PRIVATE_KEY_BLOCK', severity: SecretSeverity.CRITICAL },
179
+ ];
118
180
  /**
119
181
  * Minimum length a string value must have to be considered a potential secret.
120
182
  * Short strings like "test", "dev", "localhost" are excluded.
package/src/index.js CHANGED
@@ -113,22 +113,81 @@ export class RefactoringEngine {
113
113
 
114
114
  // Pass 1: Boot environment contexts and load alias configuration maps
115
115
  await this.oxcAnalyzer.init();
116
+ // UPGRADE: Wire pathMapper onto context so ASTAnalyzer alias-filtering works in all code paths
117
+ this.context.pathMapper = this.pathMapper;
116
118
  await this.pathMapper.loadMappings(this.context.tsconfigFilename);
117
119
 
118
120
  // Always attempt workspace mesh initialization
119
121
  console.log(ansis.dim('🌐 Probing for monorepo workspace configuration...'));
120
122
  await this.workspaceGraph.initializeWorkspaceMesh();
123
+ // UPGRADE: Always expose workspaceGraph on context (not only when workspace is enabled)
124
+ this.context.workspaceGraph = this.workspaceGraph;
125
+
126
+ // UPGRADE: Scan all discovered framework configs across the workspace EARLY
127
+ const discoveredConfigs = await this.workspaceGraph.findFrameworkConfigs();
128
+ if (discoveredConfigs.length > 0) {
129
+ console.log(ansis.dim(`⚙️ Detected ${discoveredConfigs.length} framework configurations...`));
130
+ const { FrameworkConfigParser } = await import('./resolution/FrameworkConfigParser.js');
131
+ const parser = new FrameworkConfigParser(this.context);
132
+
133
+ for (const configPath of discoveredConfigs) {
134
+ try {
135
+ const content = await fs.readFile(configPath, 'utf8');
136
+
137
+ // 1. Core Parsing (Aliases)
138
+ const { aliases } = parser.parse(content, configPath);
139
+ for (const [key, target] of aliases.entries()) {
140
+ this.pathMapper.addAlias(key, target);
141
+ }
142
+
143
+ // 2. Plugin-based Entry Detection
144
+ const detectedEntries = await this.magicDetector.detectEntryPointsFromPlugins(content, configPath);
145
+ for (const entry of detectedEntries) {
146
+ const absEntry = path.resolve(path.dirname(configPath), entry).replace(/\\/g, '/');
147
+ if (!this.context.entryPoints.includes(absEntry)) {
148
+ this.context.entryPoints.push(absEntry);
149
+ }
150
+ }
151
+
152
+ // 3. Register config file itself as entry
153
+ if (!this.context.entryPoints.includes(configPath)) {
154
+ this.context.entryPoints.push(configPath);
155
+ }
156
+ } catch (e) {}
157
+ }
158
+ }
159
+
121
160
  if (this.context.isWorkspaceEnabled) {
122
161
  console.log(ansis.dim('🌐 Monorepo workspace detected – mapping package mesh layers...'));
123
- // Expose workspaceGraph on context for WorkspaceDiagnostic and other components
124
- this.context.workspaceGraph = this.workspaceGraph;
162
+
163
+ // NEW: Register all workspace package entry points
164
+ for (const [dir, manifest] of this.workspaceGraph.packageManifests.entries()) {
165
+ if (manifest.entryPoints && manifest.entryPoints.length > 0) {
166
+ for (const ep of manifest.entryPoints) {
167
+ if (!this.context.entryPoints.includes(ep)) {
168
+ this.context.entryPoints.push(ep);
169
+ }
170
+ }
171
+ }
172
+ }
173
+
125
174
  // Reload PathMapper aliases now that workspace roots are known
126
175
  await this.pathMapper.loadMappings(this.context.tsconfigFilename);
127
176
  if (this.context.verbose) {
128
- console.log(`[Workspace] Found ${this.workspaceGraph.packageManifests.size} workspace packages:`);
177
+ const wsSummary = this.workspaceGraph.getSummary();
178
+ console.log(`[Workspace] Found ${wsSummary.packages} workspace packages:`);
129
179
  for (const [dir, manifest] of this.workspaceGraph.packageManifests.entries()) {
130
180
  console.log(ansis.dim(` • ${manifest.name || dir}`));
131
181
  }
182
+ if (wsSummary.tsconfigsLoaded > 0) {
183
+ console.log(ansis.dim(`[Workspace] Loaded ${wsSummary.tsconfigsLoaded} tsconfig.json file(s) from workspace packages`));
184
+ }
185
+ if (wsSummary.configFilesLoaded > 0) {
186
+ console.log(ansis.dim(`[Workspace] Loaded ${wsSummary.configFilesLoaded} *.config.ts/js file(s) from workspace packages`));
187
+ }
188
+ if (wsSummary.monorepoConfigs && wsSummary.monorepoConfigs.length > 0) {
189
+ console.log(ansis.dim(`[Workspace] Detected global configs: ${wsSummary.monorepoConfigs.join(', ')}`));
190
+ }
132
191
  }
133
192
  }
134
193
 
@@ -200,6 +259,14 @@ export class RefactoringEngine {
200
259
  for (const filePath of sourceCodeFilesList) {
201
260
  const absFilePath = slashifyInternal(filePath);
202
261
  const node = this.context.getOrCreateNode(absFilePath);
262
+
263
+ // Config data is now handled early in the workspace mesh initialization phase.
264
+ // We still mark the rawCode for plugin analysis.
265
+ if (absFilePath.includes('.config.')) {
266
+ try {
267
+ node.rawCode = await fs.readFile(absFilePath, 'utf8');
268
+ } catch (e) {}
269
+ }
203
270
  const currentHash = await this.cacheManager.computeHash(absFilePath);
204
271
  node.contentHash = currentHash;
205
272
 
@@ -227,6 +294,7 @@ export class RefactoringEngine {
227
294
  } else if (!parallelParseCompleted) {
228
295
  this.context.metrics.cacheMisses++;
229
296
  const fileContent = await fs.readFile(filePath, 'utf8');
297
+ node.rawCode = fileContent; // UPGRADE: Store raw code for plugin analysis
230
298
 
231
299
  let success = false;
232
300
  if (this.oxcAnalyzer.isAvailable) {
@@ -248,6 +316,10 @@ export class RefactoringEngine {
248
316
  if (!success || (oxcFailedToFindDependencies && (hasImportExportKeywords || hasCommonJSKeywords))) {
249
317
  await this.analyzer.parseFile(filePath, fileContent, node);
250
318
  }
319
+
320
+ // UPGRADE 5.3.0: Run plugin-specific content analysis
321
+ await this.magicDetector.runPluginContentAnalysis(node, absFilePath);
322
+
251
323
  // Secret scan on freshly parsed content
252
324
  const secretFindings = this.secretScanner.scanFileContent(filePath, fileContent);
253
325
  if (secretFindings.length > 0) {
@@ -562,6 +634,17 @@ export class RefactoringEngine {
562
634
  const nodeBuiltins = ['assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads', 'zlib'];
563
635
  if (nodeBuiltins.includes(basePkg) || nodeBuiltins.includes(basePkg.replace('node:', ''))) return;
564
636
 
637
+ // UPGRADE: Skip tsconfig path aliases (e.g. @shared/*, @idk/*, ~/*)
638
+ // Check full specifier AND basePkg against alias patterns
639
+ if (this.pathMapper && typeof this.pathMapper.isTsconfigAlias === 'function') {
640
+ if (this.pathMapper.isTsconfigAlias(specifier) || this.pathMapper.isTsconfigAlias(basePkg)) return;
641
+ }
642
+
643
+ // UPGRADE: Skip workspace packages detected via pnpm-workspace.yaml / package.json workspaces
644
+ if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
645
+ if (this.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.workspaceGraph.isLocalWorkspaceSpecifier(basePkg)) return;
646
+ }
647
+
565
648
  if (!localDeps.has(basePkg)) {
566
649
  const alreadyFlagged = this.context.unlistedDependencies.some(u => u.package === basePkg && u.file === filePath);
567
650
  if (!alreadyFlagged) {
@@ -574,7 +657,7 @@ export class RefactoringEngine {
574
657
  }
575
658
  });
576
659
  } catch (error) {
577
- if (this.context.options.verbose) {
660
+ if (this.context.options.verbose && error.code !== 'ENOENT') {
578
661
  console.error(ansis.red(` ❌ Manifest Parsing Exception: ${error.message}`));
579
662
  }
580
663
  }
@@ -59,6 +59,33 @@ export class IncrementalCacheManager {
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * UPGRADE 5.4.3: Affected-Only Analysis
64
+ * Identifies which files need re-analysis based on Git changes or cache misses.
65
+ */
66
+ async getAffectedFiles(allFiles) {
67
+ const manifest = await this.loadCacheManifest();
68
+ const affected = [];
69
+ const unchanged = [];
70
+
71
+ for (const file of allFiles) {
72
+ const cached = manifest[file];
73
+ if (!cached) {
74
+ affected.push(file);
75
+ continue;
76
+ }
77
+
78
+ const currentHash = await this.computeHash(file);
79
+ if (currentHash !== cached.hash) {
80
+ affected.push(file);
81
+ } else {
82
+ unchanged.push({ path: file, data: cached });
83
+ }
84
+ }
85
+
86
+ return { affected, unchanged };
87
+ }
88
+
62
89
  /**
63
90
  * Serializes the current active dependency graph, translating Maps and Sets into JSON schemas.
64
91
  * @param {Map<string, Object>} currentGraphState - In-memory structural project state map