entkapp 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,383 +1,11 @@
1
- export class OxcAnalyzer {
2
- constructor(context) {
3
- this.context = context;
4
- this.oxc = null;
5
- this.isAvailable = false;
6
- }
1
+ import { OxcAnalyzer as CoreOxcAnalyzer } from '../ast/OxcAnalyzer.js';
7
2
 
8
- async init() {
9
- if (this.isAvailable) return true;
10
- try {
11
- const oxc = await import("oxc-parser");
12
- this.oxc = oxc;
13
- this.isAvailable = true;
14
- return true;
15
- } catch (e) {
16
- try {
17
- const { createRequire } = await import('module');
18
- const require = createRequire(import.meta.url);
19
- this.oxc = require("oxc-parser");
20
- this.isAvailable = true;
21
- return true;
22
- } catch (err) {
23
- this.isAvailable = false;
24
- if (this.context.verbose) {
25
- console.warn(`[OxcAnalyzer] oxc-parser failed to load: ${err.message}`);
26
- }
27
- return false;
28
- }
3
+ /**
4
+ * Proxy for the Core OxcAnalyzer to maintain backward compatibility
5
+ * while centralizing the implementation.
6
+ */
7
+ export class OxcAnalyzer extends CoreOxcAnalyzer {
8
+ constructor(context) {
9
+ super(context);
29
10
  }
30
- }
31
-
32
- async parseFile(filePath, content, fileNode) {
33
- if (!this.isAvailable) {
34
- const initialized = await this.init();
35
- if (!initialized) return false;
36
- }
37
-
38
- try {
39
- // Fix: Ensure filePath is correctly handled for OXC (Windows path issue)
40
- const normalizedPath = filePath.replace(/\\/g, '/');
41
-
42
- // Try passing the path directly as the second argument if the object-based options fail
43
- // Some versions of oxc-parser expect a string as the second argument for the filename
44
- let result;
45
- try {
46
- result = this.oxc.parseSync(content, {
47
- sourceType: "module",
48
- sourceFilename: normalizedPath,
49
- lang: "typescript"
50
- });
51
- } catch (e) {
52
- // Fallback for versions that expect the path as second argument
53
- result = this.oxc.parseSync(content, normalizedPath);
54
- }
55
-
56
- // Fix: Handle cases where OXC returns a JSON string instead of an object
57
- // Stabilize result through JSON round-trip to fix N-API conversion issues on Windows
58
- let parsedResult;
59
- try {
60
- parsedResult = typeof result === 'string' ? JSON.parse(result) : JSON.parse(JSON.stringify(result));
61
- } catch (err) {
62
- if (typeof result === 'object') {
63
- parsedResult = result; // Last resort
64
- } else {
65
- throw new Error("OXC returned an invalid format");
66
- }
67
- }
68
-
69
- let ast;
70
- if (parsedResult && typeof parsedResult === 'object') {
71
- if (parsedResult.program) {
72
- ast = parsedResult;
73
- } else if (parsedResult.ast) {
74
- ast = { program: parsedResult.ast };
75
- } else {
76
- ast = { program: parsedResult };
77
- }
78
- } else {
79
- throw new Error("OXC returned an invalid AST format");
80
- }
81
-
82
- fileNode.ast = ast.program; // Store the AST for advanced analysis
83
- fileNode.jsxComponents = new Set();
84
- fileNode.jsxProps = new Set();
85
- fileNode.decorators = new Set();
86
-
87
- this.walkOxcAst(ast.program, fileNode, content);
88
-
89
- const lines = content.split('\n');
90
- const getLineCol = (pos) => {
91
- let count = 0;
92
- for (let i = 0; i < lines.length; i++) {
93
- if (count + lines[i].length + 1 > pos) {
94
- return { line: i + 1, column: pos - count + 1 };
95
- }
96
- count += lines[i].length + 1;
97
- }
98
- return { line: 1, column: 1 };
99
- };
100
-
101
- for (const [name, meta] of fileNode.internalExports.entries()) {
102
- if (meta.start !== undefined) {
103
- fileNode.symbolSourceLocations.set(name, getLineCol(meta.start));
104
- }
105
- }
106
-
107
- return true;
108
- } catch (e) {
109
- if (this.context.verbose) {
110
- console.warn(`[OXC] Failed to parse ${filePath}. Error: ${e.message}`);
111
- if (e.stack) console.debug(e.stack);
112
- console.info(`[OXC] Switching back to TypeScript Compiler API for ${filePath}`);
113
- }
114
- return false;
115
- }
116
- }
117
-
118
- walkOxcAst(node, fileNode, content) {
119
- if (!node) return;
120
-
121
- switch (node.type) {
122
- case "ImportDeclaration":
123
- this.handleImportDeclaration(node, fileNode);
124
- break;
125
- case "ExportNamedDeclaration":
126
- case "ExportDefaultDeclaration":
127
- case "ExportAllDeclaration":
128
- this.handleExportDeclaration(node, fileNode, content);
129
- break;
130
- case "CallExpression":
131
- this.handleCallExpression(node, fileNode);
132
- break;
133
- case "JSXElement":
134
- case "JSXFragment":
135
- this.handleJsxElement(node, fileNode);
136
- break;
137
- case "Decorator":
138
- this.handleDecorator(node, fileNode);
139
- break;
140
- case "StringLiteral":
141
- fileNode.rawStringReferences.add(node.value);
142
- break;
143
- }
144
-
145
- for (const key in node) {
146
- if (node[key] && typeof node[key] === "object") {
147
- if (Array.isArray(node[key])) {
148
- node[key].forEach((child) => this.walkOxcAst(child, fileNode, content));
149
- } else {
150
- this.walkOxcAst(node[key], fileNode, content);
151
- }
152
- }
153
- }
154
- }
155
-
156
- handleImportDeclaration(node, fileNode) {
157
- const specifier = node.source.value;
158
- fileNode.explicitImports.add(specifier);
159
-
160
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
161
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
162
- }
163
-
164
- if (node.specifiers) {
165
- node.specifiers.forEach((spec) => {
166
- if (spec.type === "ImportSpecifier") {
167
- const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
168
- fileNode.importedSymbols.add(`${specifier}:${importedName}`);
169
- } else if (spec.type === "ImportDefaultSpecifier") {
170
- fileNode.importedSymbols.add(`${specifier}:default`);
171
- } else if (spec.type === "ImportNamespaceSpecifier") {
172
- fileNode.importedSymbols.add(`${specifier}:*`);
173
- }
174
- });
175
- }
176
- }
177
-
178
- handleExportDeclaration(node, fileNode, content) {
179
- // 1. Default Exports
180
- if (node.type === "ExportDefaultDeclaration") {
181
- fileNode.internalExports.set("default", {
182
- type: "default",
183
- start: node.start,
184
- end: node.end
185
- });
186
- return;
187
- }
188
-
189
- // 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
190
- if (node.type === "ExportAllDeclaration") {
191
- const sourceSpecifier = node.source.value;
192
- fileNode.explicitImports.add(sourceSpecifier);
193
- if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
194
- fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
195
- }
196
-
197
- if (node.exported) {
198
- // export * as ns from 'mod'
199
- const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
200
- if (name) {
201
- fileNode.internalExports.set(name, {
202
- type: "re-export-namespace",
203
- source: sourceSpecifier,
204
- originalName: "*",
205
- start: node.start,
206
- end: node.end
207
- });
208
- fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
209
- }
210
- } else {
211
- // export * from 'mod'
212
- fileNode.internalExports.set("*", {
213
- type: "re-export-all",
214
- source: sourceSpecifier
215
- });
216
- fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
217
- }
218
- return;
219
- }
220
-
221
- // 3. Named Exports & Re-exports with specifiers
222
- if (node.source) {
223
- // Re-export: export { x } from 'mod'
224
- const specifier = node.source.value;
225
- fileNode.explicitImports.add(specifier);
226
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
227
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
228
- }
229
-
230
- if (node.specifiers) {
231
- node.specifiers.forEach((spec) => {
232
- const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
233
- const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
234
- fileNode.internalExports.set(exportedName, {
235
- type: "re-export",
236
- source: specifier,
237
- originalName: localName,
238
- start: spec.start,
239
- end: spec.end,
240
- });
241
- fileNode.importedSymbols.add(`${specifier}:${localName}`);
242
- });
243
- }
244
- } else if (node.declaration) {
245
- // Direct declaration export: export const x = 1, export function f() {}
246
- const decl = node.declaration;
247
- if (decl.type === "VariableDeclaration") {
248
- decl.declarations.forEach((d) => {
249
- this._extractNamesFromPattern(d.id, (name) => {
250
- fileNode.internalExports.set(name, {
251
- type: "variable",
252
- start: d.start,
253
- end: d.end
254
- });
255
- });
256
- });
257
- } else if (decl.id && decl.id.name) {
258
- let type = "unknown";
259
- if (decl.type === "FunctionDeclaration") type = "function";
260
- else if (decl.type === "ClassDeclaration") type = "class";
261
- else if (decl.type === "TSEnumDeclaration") type = "enum";
262
- else if (decl.type === "TSInterfaceDeclaration") type = "interface";
263
- else if (decl.type === "TSTypeAliasDeclaration") type = "type";
264
- else if (decl.type === "TSModuleDeclaration") type = "namespace";
265
-
266
- fileNode.internalExports.set(decl.id.name, {
267
- type,
268
- start: decl.start,
269
- end: decl.end
270
- });
271
- }
272
- } else if (node.specifiers) {
273
- // Export existing locals: export { x, y as z }
274
- node.specifiers.forEach((spec) => {
275
- const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
276
- const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
277
- fileNode.internalExports.set(exportedName, {
278
- type: "export",
279
- originalName: localName,
280
- start: spec.start,
281
- end: spec.end,
282
- });
283
- });
284
- }
285
- }
286
-
287
- _extractNamesFromPattern(node, callback) {
288
- if (!node) return;
289
- if (node.type === "Identifier") {
290
- callback(node.name);
291
- } else if (node.type === "ObjectPattern") {
292
- node.properties.forEach(p => {
293
- if (p.type === "Property") {
294
- this._extractNamesFromPattern(p.value, callback);
295
- } else if (p.type === "RestElement") {
296
- this._extractNamesFromPattern(p.argument, callback);
297
- }
298
- });
299
- } else if (node.type === "ArrayPattern") {
300
- node.elements.forEach(e => {
301
- if (e) this._extractNamesFromPattern(e, callback);
302
- });
303
- } else if (node.type === "AssignmentPattern") {
304
- this._extractNamesFromPattern(node.left, callback);
305
- } else if (node.type === "RestElement") {
306
- this._extractNamesFromPattern(node.argument, callback);
307
- }
308
- }
309
-
310
- handleCallExpression(node, fileNode) {
311
- // Dynamic import(): import('./module')
312
- if (node.callee.type === "Import" && node.arguments.length > 0) {
313
- const arg = node.arguments[0];
314
- if (arg.type === "StringLiteral") {
315
- const specifier = arg.value;
316
- fileNode.explicitImports.add(specifier);
317
- fileNode.dynamicImports.add(specifier);
318
- fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
319
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
320
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
321
- }
322
- } else {
323
- if (fileNode.calculatedDynamicImports) {
324
- fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
325
- }
326
- }
327
- } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
328
- const specifier = node.arguments[0].value;
329
- fileNode.explicitImports.add(specifier);
330
- fileNode.importedSymbols.add(`${specifier}:*`);
331
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
332
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
333
- }
334
- }
335
- }
336
-
337
- handleJsxElement(node, fileNode) {
338
- const getElementName = (nameNode) => {
339
- if (nameNode.type === "JSXIdentifier") return nameNode.name;
340
- if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
341
- if (nameNode.type === "JSXNamespacedName") return `${nameNode.namespace.name}:${nameNode.name.name}`;
342
- return "unknown";
343
- };
344
-
345
- if (node.openingElement) {
346
- const tagName = getElementName(node.openingElement.name);
347
- fileNode.jsxComponents.add(tagName);
348
-
349
- if (node.openingElement.attributes) {
350
- node.openingElement.attributes.forEach(attr => {
351
- if (attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier") {
352
- fileNode.jsxProps.add(`${tagName}:${attr.name.name}`);
353
- }
354
- });
355
- }
356
- }
357
- }
358
-
359
- handleDecorator(node, fileNode) {
360
- const getDecoratorName = (expr) => {
361
- if (expr.type === "Identifier") return expr.name;
362
- if (expr.type === "CallExpression") return getDecoratorName(expr.callee);
363
- if (expr.type === "MemberExpression") {
364
- const prop = expr.property.name || expr.property.value;
365
- return prop || "unknown";
366
- }
367
- return "unknown";
368
- };
369
-
370
- const decoratorName = getDecoratorName(node.expression);
371
- if (decoratorName !== "unknown") {
372
- fileNode.decorators.add(decoratorName);
373
- }
374
- }
375
-
376
- _extractPackageName(specifier) {
377
- if (specifier.startsWith('@')) {
378
- const parts = specifier.split('/');
379
- return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
380
- }
381
- return specifier.split('/')[0];
382
- }
383
11
  }
@@ -14,7 +14,7 @@
14
14
 
15
15
  import EventEmitter from 'events';
16
16
  import path from 'path';
17
- import { RefactoringEngine } from '../index.js';
17
+ // import { RefactoringEngine } from '../index.js';
18
18
 
19
19
  export class HeadlessAPI extends EventEmitter {
20
20
  constructor(options = {}) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ============================================================================
3
- * Plugin SDK for entkapp v4.0.0
3
+ * Plugin SDK for entkapp v4.7.0 (Enterprise Edition)
4
4
  * ============================================================================
5
5
  * Provides utilities and helpers for developing custom plugins that extend
6
6
  * entkapp's analysis and healing capabilities.
@@ -21,8 +21,6 @@ export class PluginSDKBase extends BasePlugin {
21
21
 
22
22
  /**
23
23
  * Register a hook for a specific lifecycle event
24
- * @param {string} eventName - Event name (e.g., 'analyze:start', 'refactor:complete')
25
- * @param {Function} handler - Handler function
26
24
  */
27
25
  registerHook(eventName, handler) {
28
26
  if (!this.hooks.has(eventName)) {
@@ -33,8 +31,6 @@ export class PluginSDKBase extends BasePlugin {
33
31
 
34
32
  /**
35
33
  * Emit a hook event
36
- * @param {string} eventName - Event name
37
- * @param {Object} data - Event data
38
34
  */
39
35
  async emitHook(eventName, data) {
40
36
  const handlers = this.hooks.get(eventName) || [];
@@ -45,7 +41,6 @@ export class PluginSDKBase extends BasePlugin {
45
41
 
46
42
  /**
47
43
  * Register a code transformer
48
- * @param {Function} transformer - Transformer function
49
44
  */
50
45
  registerTransformer(transformer) {
51
46
  this.transformers.push(transformer);
@@ -53,41 +48,17 @@ export class PluginSDKBase extends BasePlugin {
53
48
 
54
49
  /**
55
50
  * Register a validator
56
- * @param {Function} validator - Validator function
57
51
  */
58
52
  registerValidator(validator) {
59
53
  this.validators.push(validator);
60
54
  }
61
55
 
62
56
  /**
63
- * Apply all registered transformers to a code string
64
- * @param {string} code - Source code
65
- * @param {string} filePath - File path
66
- * @returns {Promise<string>} Transformed code
57
+ * Enterprise: Register a Call Graph visitor
67
58
  */
68
- async applyTransformers(code, filePath) {
69
- let result = code;
70
- for (const transformer of this.transformers) {
71
- result = await transformer(result, filePath);
72
- }
73
- return result;
74
- }
75
-
76
- /**
77
- * Run all validators on a code string
78
- * @param {string} code - Source code
79
- * @param {string} filePath - File path
80
- * @returns {Promise<Array>} Validation errors
81
- */
82
- async runValidators(code, filePath) {
83
- const errors = [];
84
- for (const validator of this.validators) {
85
- const result = await validator(code, filePath);
86
- if (result && result.length > 0) {
87
- errors.push(...result);
88
- }
89
- }
90
- return errors;
59
+ registerCallGraphVisitor(visitor) {
60
+ this.context.callGraphVisitors = this.context.callGraphVisitors || [];
61
+ this.context.callGraphVisitors.push(visitor);
91
62
  }
92
63
  }
93
64
 
@@ -97,8 +68,6 @@ export class PluginSDKBase extends BasePlugin {
97
68
  export class PluginSDK {
98
69
  /**
99
70
  * Create a custom plugin class
100
- * @param {Object} config - Plugin configuration
101
- * @returns {Class} Plugin class
102
71
  */
103
72
  static createPlugin(config) {
104
73
  return class CustomPlugin extends PluginSDKBase {
@@ -136,163 +105,30 @@ export class PluginSDK {
136
105
  return await config.analyze(node, filePath, this.context);
137
106
  }
138
107
  }
139
-
140
- async transform(code, filePath) {
141
- if (config.transform) {
142
- return await config.transform(code, filePath, this.context);
143
- }
144
- return code;
145
- }
146
-
147
- async validate(code, filePath) {
148
- if (config.validate) {
149
- return await config.validate(code, filePath, this.context);
150
- }
151
- return [];
152
- }
153
108
  };
154
109
  }
155
110
 
156
111
  /**
157
- * Create a CSS-in-JS analyzer plugin
158
- * @param {Object} config - Configuration for CSS-in-JS analysis
159
- * @returns {Class} Plugin class
112
+ * Create a specialized member usage analyzer
160
113
  */
161
- static createCSSInJSPlugin(config = {}) {
162
- const cssLibraries = config.libraries || [
163
- 'styled-components',
164
- 'emotion',
165
- '@emotion/react',
166
- '@emotion/styled',
167
- 'linaria',
168
- 'vanilla-extract'
169
- ];
170
-
171
- return this.createPlugin({
172
- name: config.name || 'css-in-js-analyzer',
173
- configFiles: [],
174
- routePatterns: [/\.(tsx?|jsx?)$/],
175
- requiredContracts: [],
176
-
177
- async analyze(node, filePath) {
178
- // Track CSS-in-JS imports
179
- for (const lib of cssLibraries) {
180
- if (node.explicitImports.has(lib)) {
181
- node.cssInJsLibraries = node.cssInJsLibraries || new Set();
182
- node.cssInJsLibraries.add(lib);
183
- }
184
- }
185
-
186
- // Detect styled component definitions
187
- const styledPattern = /(?:styled|css|keyframes)\s*\.\w+|styled\(\w+\)/g;
188
- for (const match of (node.rawCode || '').matchAll(styledPattern)) {
189
- node.styledComponentUsages = node.styledComponentUsages || [];
190
- node.styledComponentUsages.push(match[0]);
191
- }
192
- },
193
-
194
- async validate(code, filePath) {
195
- const errors = [];
196
- // Check for unused CSS-in-JS definitions
197
- const unusedStylesPattern = /(?:const|let|var)\s+(\w+)\s*=\s*(?:styled|css)\./g;
198
- const matches = [...code.matchAll(unusedStylesPattern)];
199
-
200
- for (const match of matches) {
201
- const styleName = match[1];
202
- const usagePattern = new RegExp(`\\b${styleName}\\b`);
203
- if (!usagePattern.test(code.substring(match.index + match[0].length))) {
204
- errors.push({
205
- type: 'unused-style',
206
- name: styleName,
207
- line: code.substring(0, match.index).split('\n').length,
208
- message: `Unused CSS-in-JS definition: ${styleName}`
209
- });
114
+ static createMemberAnalyzer(config = {}) {
115
+ return this.createPlugin({
116
+ name: config.name || 'custom-member-analyzer',
117
+ async analyze(node, filePath, context) {
118
+ // Custom logic to protect or flag specific members
119
+ if (config.protectMembers) {
120
+ for (const [symbol, meta] of node.internalExports.entries()) {
121
+ if (meta.members) {
122
+ meta.members.forEach(m => {
123
+ if (config.protectMembers.includes(m.name)) {
124
+ m.isPublic = true; // Mark as public to protect from deletion
125
+ }
126
+ });
127
+ }
128
+ }
129
+ }
210
130
  }
211
- }
212
-
213
- return errors;
214
- }
215
- });
216
- }
217
-
218
- /**
219
- * Create an asset tracking plugin
220
- * @param {Object} config - Configuration for asset tracking
221
- * @returns {Class} Plugin class
222
- */
223
- static createAssetTrackingPlugin(config = {}) {
224
- const assetExtensions = config.extensions || [
225
- '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp',
226
- '.mp4', '.webm', '.mp3', '.wav',
227
- '.woff', '.woff2', '.ttf', '.eot'
228
- ];
229
-
230
- return this.createPlugin({
231
- name: config.name || 'asset-tracker',
232
- configFiles: [],
233
- routePatterns: [/\.(tsx?|jsx?)$/],
234
-
235
- async analyze(node, filePath) {
236
- node.assetReferences = node.assetReferences || new Set();
237
-
238
- // Track asset imports
239
- const assetImportPattern = /import\s+(?:\*\s+as\s+\w+|[\w\s,{}]+)\s+from\s+['"]([^'"]+(?:${assetExtensions.join('|')}))['"]/g;
240
- for (const match of (node.rawCode || '').matchAll(assetImportPattern)) {
241
- node.assetReferences.add(match[1]);
242
- }
243
-
244
- // Track asset requires
245
- const assetRequirePattern = /require\s*\(\s*['"]([^'"]+(?:${assetExtensions.join('|')}))['"]\s*\)/g;
246
- for (const match of (node.rawCode || '').matchAll(assetRequirePattern)) {
247
- node.assetReferences.add(match[1]);
248
- }
249
-
250
- // Track asset URLs in strings
251
- const assetUrlPattern = /['"]([^'"]*(?:${assetExtensions.join('|')}))['"]/g;
252
- for (const match of (node.rawCode || '').matchAll(assetUrlPattern)) {
253
- node.assetReferences.add(match[1]);
254
- }
255
- }
256
- });
257
- }
258
-
259
- /**
260
- * Create a monorepo awareness plugin
261
- * @param {Object} config - Configuration for monorepo support
262
- * @returns {Class} Plugin class
263
- */
264
- static createMonorepoPlugin(config = {}) {
265
- return this.createPlugin({
266
- name: config.name || 'monorepo-aware',
267
- configFiles: config.configFiles || ['nx.json', 'pnpm-workspace.yaml', 'lerna.json'],
268
-
269
- async analyze(node, filePath) {
270
- // Track workspace package references
271
- node.workspaceReferences = node.workspaceReferences || new Set();
272
-
273
- // Detect workspace imports (e.g., @workspace/package-name)
274
- const workspacePattern = /@[\w-]+\/[\w-]+/g;
275
- for (const match of (node.rawCode || '').matchAll(workspacePattern)) {
276
- node.workspaceReferences.add(match[0]);
277
- }
278
- }
279
- });
280
- }
281
-
282
- /**
283
- * Create a circular dependency detector plugin
284
- * @param {Object} config - Configuration
285
- * @returns {Class} Plugin class
286
- */
287
- static createCircularDepPlugin(config = {}) {
288
- return this.createPlugin({
289
- name: config.name || 'circular-dep-detector',
290
-
291
- async analyze(node, filePath) {
292
- node.potentialCycles = node.potentialCycles || [];
293
- // Cycle detection will be handled by the main engine
294
- }
295
- });
131
+ });
296
132
  }
297
133
  }
298
134