archsync 1.0.0 → 1.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.
Files changed (50) hide show
  1. package/README.md +67 -0
  2. package/dist/archsync.cjs +2 -0
  3. package/package.json +8 -4
  4. package/bin/cli.js +0 -91
  5. package/src/__tests__/e2e-workflow.test.js +0 -66
  6. package/src/__tests__/hashEngine.test.js +0 -109
  7. package/src/__tests__/impact.test.js +0 -137
  8. package/src/__tests__/parsers.test.js +0 -496
  9. package/src/__tests__/scan-pipeline.test.js +0 -332
  10. package/src/__tests__/schemaBuilder.test.js +0 -145
  11. package/src/__tests__/workspace.test.js +0 -178
  12. package/src/commands/backup.js +0 -54
  13. package/src/commands/connect.js +0 -129
  14. package/src/commands/diff.js +0 -228
  15. package/src/commands/export.js +0 -125
  16. package/src/commands/impactReport.js +0 -50
  17. package/src/commands/import.js +0 -126
  18. package/src/commands/init.js +0 -80
  19. package/src/commands/login.js +0 -116
  20. package/src/commands/plugin.js +0 -28
  21. package/src/commands/push.js +0 -194
  22. package/src/commands/register.js +0 -127
  23. package/src/commands/scan.js +0 -498
  24. package/src/commands/serve.js +0 -133
  25. package/src/commands/setup.js +0 -233
  26. package/src/commands/status.js +0 -56
  27. package/src/commands/validate.js +0 -245
  28. package/src/commands/watch.js +0 -70
  29. package/src/core/credentialStore.js +0 -76
  30. package/src/core/hashEngine.js +0 -34
  31. package/src/core/impactEngine.js +0 -192
  32. package/src/core/monorepoDetector.js +0 -41
  33. package/src/core/pluginManager.js +0 -40
  34. package/src/core/relationshipEngine.js +0 -917
  35. package/src/core/requestSigning.js +0 -16
  36. package/src/core/schemaBuilder.js +0 -230
  37. package/src/core/schemaDeduplicator.js +0 -54
  38. package/src/core/supabaseClient.js +0 -68
  39. package/src/core/workspaceDetector.js +0 -113
  40. package/src/parsers/astParser.js +0 -274
  41. package/src/parsers/configParser.js +0 -49
  42. package/src/parsers/dependencyGraph.js +0 -31
  43. package/src/parsers/flutterParser.js +0 -98
  44. package/src/parsers/goParser.js +0 -99
  45. package/src/parsers/index.js +0 -211
  46. package/src/parsers/javaParser.js +0 -89
  47. package/src/parsers/nodeParser.js +0 -429
  48. package/src/parsers/pythonParser.js +0 -109
  49. package/src/parsers/reactParser.js +0 -368
  50. package/src/parsers/smartComment.js +0 -144
@@ -1,368 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- /**
5
- * React/Next.js parser — extracts frontend component entities from JSX/TSX files.
6
- * Detects: components, hooks, API calls, RSC, Next.js App Router patterns,
7
- * React Query hooks, Zustand store slices.
8
- */
9
-
10
- const COMPONENT_REGEX = /(?:export\s+)?(?:default\s+)?(?:function|const)\s+(\w+)\s*(?:=\s*(?:React\.memo\s*\()?\s*(?:\(|(?:async\s+)?\()|\()/g;
11
- const USE_STATE_REGEX = /useState\s*(?:<[^>]+>)?\s*\(\s*([^)]*)\)/g;
12
- const USE_EFFECT_REGEX = /useEffect\s*\(/g;
13
- const USE_CONTEXT_REGEX = /useContext\s*\(\s*(\w+)/g;
14
- const CUSTOM_HOOK_REGEX = /(?:export\s+)?(?:function|const)\s+(use\w+)/g;
15
- const FETCH_REGEX = /fetch\s*\(\s*['"`]([^'"`]+)['"`]/g;
16
- const AXIOS_REGEX = /axios\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/g;
17
- const NEXT_API_REGEX = /\/api\/([^\s'"`]+)/g;
18
- const IMPORT_COMPONENT_REGEX = /import\s+(\w+)\s+from\s+['"`]([^'"`]+)['"`]/g;
19
- const ROUTER_REGEX = /(?:path|route)\s*[:=]\s*['"`]([^'"`]+)['"`]/gi;
20
-
21
- // ─── Next.js App Router ───────────────────────────────────────
22
- // Detects generateMetadata, generateStaticParams, revalidate exports
23
- const NEXT_METADATA_REGEX = /export\s+(?:async\s+)?function\s+generateMetadata/g;
24
- const NEXT_STATIC_PARAMS_REGEX = /export\s+(?:async\s+)?function\s+generateStaticParams/g;
25
- const NEXT_REVALIDATE_REGEX = /export\s+const\s+revalidate\s*=\s*(\d+|false)/g;
26
- const NEXT_DYNAMIC_REGEX = /export\s+const\s+dynamic\s*=\s*['"`](force-dynamic|force-static|auto|error)['"`]/g;
27
-
28
- // ─── React Server Components ──────────────────────────────────
29
- const USE_CLIENT_DIRECTIVE = /^['"]use client['"]/m;
30
- const USE_SERVER_DIRECTIVE = /^['"]use server['"]/m;
31
- const SERVER_ACTION_REGEX = /export\s+(?:async\s+)?function\s+(\w+Action|\w+Handler)\s*\(/g;
32
-
33
- // ─── React Query ─────────────────────────────────────────────
34
- const USE_QUERY_REGEX = /\buseQuery\s*(?:<[^>]+>)?\s*\(\s*(?:\[([^\]]+)\]|{)/g;
35
- const USE_MUTATION_REGEX = /\buseMutation\s*(?:<[^>]+>)?\s*\(/g;
36
- const USE_INFINITE_QUERY_REGEX = /\buseInfiniteQuery\s*(?:<[^>]+>)?\s*\(/g;
37
- const USE_SUSPENSE_QUERY_REGEX = /\buseSuspenseQuery\s*(?:<[^>]+>)?\s*\(/g;
38
-
39
- // ─── Zustand ─────────────────────────────────────────────────
40
- const ZUSTAND_USE_STORE_REGEX = /const\s+(\w+)\s*=\s*(?:create|createStore)\s*(?:<[^>]+>)?\s*\(/g;
41
- const ZUSTAND_SLICE_EXPORT = /export\s+(?:const|function)\s+(?:use\w+Store|use\w+Slice|\w+Store|\w+Slice)\s*[=(]/g;
42
-
43
- export function parseReactFile(filePath) {
44
- const source = fs.readFileSync(filePath, 'utf-8');
45
- const entities = [];
46
- const relations = [];
47
- const fileName = path.basename(filePath, path.extname(filePath));
48
- const dirName = path.basename(path.dirname(filePath));
49
-
50
- // Determine system from directory structure
51
- let system = 'web';
52
- if (/admin/i.test(filePath)) system = 'admin';
53
- else if (/app|mobile|native/i.test(filePath)) system = 'app';
54
-
55
- let match;
56
-
57
- // ─── Detect RSC / Client boundary ─────────────────────────
58
- const isClientComponent = USE_CLIENT_DIRECTIVE.test(source);
59
- const isServerComponent = USE_SERVER_DIRECTIVE.test(source) || !isClientComponent;
60
-
61
- // ─── Next.js App Router file patterns ─────────────────────
62
- const isAppRouterPage = /[/\\]page\.(tsx?|jsx?)$/.test(filePath);
63
- const isAppRouterLayout = /[/\\]layout\.(tsx?|jsx?)$/.test(filePath);
64
- const isAppRouterRoute = /[/\\]route\.(tsx?|jsx?)$/.test(filePath);
65
- const isAppRouterLoading = /[/\\]loading\.(tsx?|jsx?)$/.test(filePath);
66
- const isAppRouterError = /[/\\]error\.(tsx?|jsx?)$/.test(filePath);
67
- const isAppRouterNotFound = /[/\\]not-found\.(tsx?|jsx?)$/.test(filePath);
68
- const isAppRouterTemplate = /[/\\]template\.(tsx?|jsx?)$/.test(filePath);
69
-
70
- // ─── API Route handlers (Next.js App Router) ──────────────
71
- if (isAppRouterRoute) {
72
- const HTTP_HANDLER_REGEX = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(/g;
73
- HTTP_HANDLER_REGEX.lastIndex = 0;
74
- while ((match = HTTP_HANDLER_REGEX.exec(source)) !== null) {
75
- const method = match[1];
76
- // Derive URL path from file path — strip src/app prefix and route.tsx suffix
77
- const routePath = filePath
78
- .replace(/.*[/\\]app[/\\]/, '/')
79
- .replace(/[/\\]route\.(tsx?|jsx?)$/, '')
80
- .replace(/\\/g, '/') || '/';
81
- entities.push({
82
- entityType: 'route',
83
- name: `${method} ${routePath}`,
84
- system: 'backend',
85
- data: {
86
- method,
87
- path: routePath,
88
- kind: 'next-app-router-handler',
89
- serverComponent: true,
90
- },
91
- metadata: { sourceFile: filePath },
92
- });
93
- }
94
- }
95
-
96
- // ─── Layout / Loading / Error special components ──────────
97
- if (isAppRouterLayout || isAppRouterLoading || isAppRouterError || isAppRouterNotFound || isAppRouterTemplate) {
98
- const specialType = isAppRouterLayout ? 'layout'
99
- : isAppRouterLoading ? 'loading'
100
- : isAppRouterError ? 'error'
101
- : isAppRouterNotFound ? 'not-found'
102
- : 'template';
103
- const routePath = filePath
104
- .replace(/.*[/\\]app[/\\]/, '/')
105
- .replace(/[/\\](layout|loading|error|not-found|template)\.(tsx?|jsx?)$/, '')
106
- .replace(/\\/g, '/') || '/';
107
- entities.push({
108
- entityType: 'screen',
109
- name: `${specialType}:${routePath || '/'}`,
110
- system,
111
- data: {
112
- componentType: specialType,
113
- isServerComponent,
114
- appRouter: true,
115
- },
116
- metadata: { sourceFile: filePath },
117
- });
118
- }
119
-
120
- // ─── Components ───────────────────────────────────────────
121
- COMPONENT_REGEX.lastIndex = 0;
122
- while ((match = COMPONENT_REGEX.exec(source)) !== null) {
123
- const name = match[1];
124
- // Skip hooks and utilities
125
- if (name.startsWith('use') || /^[a-z]/.test(name)) continue;
126
-
127
- const isPage = isAppRouterPage || /page|screen|view|layout/i.test(filePath) || /page|screen|view|layout/i.test(name);
128
- const isWidget = /widget|card|item|button|input|modal/i.test(name);
129
-
130
- // Avoid duplication with already added special components
131
- if (entities.some(e => e.name === name)) continue;
132
-
133
- entities.push({
134
- entityType: isPage ? 'screen' : isWidget ? 'widget' : 'screen',
135
- name,
136
- system,
137
- data: {
138
- componentType: isPage ? 'page' : 'component',
139
- hasState: USE_STATE_REGEX.test(source),
140
- hasEffects: USE_EFFECT_REGEX.test(source),
141
- isServerComponent: isServerComponent && !isClientComponent,
142
- isClientComponent,
143
- appRouter: isAppRouterPage || isAppRouterLayout,
144
- },
145
- metadata: { sourceFile: filePath },
146
- });
147
- }
148
-
149
- // ─── Server Actions ───────────────────────────────────────
150
- if (isServerComponent) {
151
- SERVER_ACTION_REGEX.lastIndex = 0;
152
- while ((match = SERVER_ACTION_REGEX.exec(source)) !== null) {
153
- const actionName = match[1];
154
- if (entities.some(e => e.name === actionName)) continue;
155
- entities.push({
156
- entityType: 'service',
157
- name: actionName,
158
- system,
159
- data: { type: 'server-action', isServerComponent: true },
160
- metadata: { sourceFile: filePath },
161
- });
162
- }
163
- }
164
-
165
- // ─── generateMetadata / generateStaticParams ──────────────
166
- if (NEXT_METADATA_REGEX.test(source)) {
167
- entities.push({
168
- entityType: 'service',
169
- name: `generateMetadata:${fileName}`,
170
- system,
171
- data: { type: 'next-metadata', appRouter: true },
172
- metadata: { sourceFile: filePath },
173
- });
174
- }
175
-
176
- if (NEXT_STATIC_PARAMS_REGEX.test(source)) {
177
- entities.push({
178
- entityType: 'service',
179
- name: `generateStaticParams:${fileName}`,
180
- system,
181
- data: { type: 'next-static-params', appRouter: true },
182
- metadata: { sourceFile: filePath },
183
- });
184
- }
185
-
186
- // ─── Next.js route segment config (revalidate / dynamic) ──
187
- NEXT_REVALIDATE_REGEX.lastIndex = 0;
188
- const revalidateMatch = NEXT_REVALIDATE_REGEX.exec(source);
189
- NEXT_DYNAMIC_REGEX.lastIndex = 0;
190
- const dynamicMatch = NEXT_DYNAMIC_REGEX.exec(source);
191
- if (revalidateMatch || dynamicMatch) {
192
- entities.push({
193
- entityType: 'config',
194
- name: `routeConfig:${fileName}`,
195
- system,
196
- data: {
197
- type: 'next-route-config',
198
- revalidate: revalidateMatch ? revalidateMatch[1] : undefined,
199
- dynamic: dynamicMatch ? dynamicMatch[1] : undefined,
200
- },
201
- metadata: { sourceFile: filePath },
202
- });
203
- }
204
-
205
- // ─── Custom Hooks ─────────────────────────────────────────
206
- CUSTOM_HOOK_REGEX.lastIndex = 0;
207
- while ((match = CUSTOM_HOOK_REGEX.exec(source)) !== null) {
208
- const hookName = match[1];
209
- if (hookName === 'useState' || hookName === 'useEffect' || hookName === 'useContext') continue;
210
- // Skip React Query hooks — handled separately below
211
- if (/^use(Query|Mutation|InfiniteQuery|SuspenseQuery)$/.test(hookName)) continue;
212
- if (entities.some(e => e.name === hookName)) continue;
213
-
214
- entities.push({
215
- entityType: 'service',
216
- name: hookName,
217
- system,
218
- data: { type: 'hook' },
219
- metadata: { sourceFile: filePath },
220
- });
221
- }
222
-
223
- // ─── React Query hooks ────────────────────────────────────
224
- // useQuery
225
- USE_QUERY_REGEX.lastIndex = 0;
226
- let queryCount = 0;
227
- while ((match = USE_QUERY_REGEX.exec(source)) !== null) {
228
- queryCount++;
229
- }
230
- if (queryCount > 0) {
231
- // Try to find the containing hook/component for naming
232
- const enclosingHook = source.match(/(?:function|const)\s+(use\w+)\s*[=(]/)?.[1] || fileName;
233
- entities.push({
234
- entityType: 'service',
235
- name: `useQuery:${enclosingHook}`,
236
- system,
237
- data: { type: 'react-query', kind: 'useQuery', count: queryCount },
238
- metadata: { sourceFile: filePath },
239
- });
240
- }
241
-
242
- // useMutation
243
- USE_MUTATION_REGEX.lastIndex = 0;
244
- let mutationCount = 0;
245
- while ((match = USE_MUTATION_REGEX.exec(source)) !== null) {
246
- mutationCount++;
247
- }
248
- if (mutationCount > 0) {
249
- const enclosingHook = source.match(/(?:function|const)\s+(use\w+)\s*[=(]/)?.[1] || fileName;
250
- entities.push({
251
- entityType: 'service',
252
- name: `useMutation:${enclosingHook}`,
253
- system,
254
- data: { type: 'react-query', kind: 'useMutation', count: mutationCount },
255
- metadata: { sourceFile: filePath },
256
- });
257
- }
258
-
259
- // useInfiniteQuery
260
- USE_INFINITE_QUERY_REGEX.lastIndex = 0;
261
- if (USE_INFINITE_QUERY_REGEX.test(source)) {
262
- const enclosingHook = source.match(/(?:function|const)\s+(use\w+)\s*[=(]/)?.[1] || fileName;
263
- entities.push({
264
- entityType: 'service',
265
- name: `useInfiniteQuery:${enclosingHook}`,
266
- system,
267
- data: { type: 'react-query', kind: 'useInfiniteQuery' },
268
- metadata: { sourceFile: filePath },
269
- });
270
- }
271
-
272
- // useSuspenseQuery
273
- USE_SUSPENSE_QUERY_REGEX.lastIndex = 0;
274
- if (USE_SUSPENSE_QUERY_REGEX.test(source)) {
275
- const enclosingHook = source.match(/(?:function|const)\s+(use\w+)\s*[=(]/)?.[1] || fileName;
276
- entities.push({
277
- entityType: 'service',
278
- name: `useSuspenseQuery:${enclosingHook}`,
279
- system,
280
- data: { type: 'react-query', kind: 'useSuspenseQuery' },
281
- metadata: { sourceFile: filePath },
282
- });
283
- }
284
-
285
- // ─── Zustand store slices ─────────────────────────────────
286
- ZUSTAND_USE_STORE_REGEX.lastIndex = 0;
287
- while ((match = ZUSTAND_USE_STORE_REGEX.exec(source)) !== null) {
288
- const storeName = match[1];
289
- if (entities.some(e => e.name === storeName)) continue;
290
- entities.push({
291
- entityType: 'service',
292
- name: storeName,
293
- system,
294
- data: { type: 'zustand-store' },
295
- metadata: { sourceFile: filePath },
296
- });
297
- }
298
-
299
- ZUSTAND_SLICE_EXPORT.lastIndex = 0;
300
- while ((match = ZUSTAND_SLICE_EXPORT.exec(source)) !== null) {
301
- // Extract name from the matched export
302
- const sliceMatch = match[0].match(/export\s+(?:const|function)\s+(\w+)/);
303
- if (sliceMatch) {
304
- const sliceName = sliceMatch[1];
305
- if (entities.some(e => e.name === sliceName)) continue;
306
- entities.push({
307
- entityType: 'service',
308
- name: sliceName,
309
- system,
310
- data: { type: 'zustand-store-slice' },
311
- metadata: { sourceFile: filePath },
312
- });
313
- }
314
- }
315
-
316
- // ─── API Calls (generate API reference entities) ──────────
317
- // Only the pathname is comparable to backend routes — drop any origin.
318
- const toApiPath = (url) => url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, '') || '/';
319
-
320
- FETCH_REGEX.lastIndex = 0;
321
- while ((match = FETCH_REGEX.exec(source)) !== null) {
322
- const apiUrl = match[1];
323
- if (apiUrl.startsWith('http') || apiUrl.startsWith('/api')) {
324
- const apiPath = toApiPath(apiUrl);
325
- entities.push({
326
- entityType: 'api',
327
- name: `GET ${apiPath}`,
328
- system,
329
- data: { method: 'GET', path: apiPath, scope: 'frontend-ref' },
330
- metadata: { sourceFile: filePath },
331
- });
332
- }
333
- }
334
-
335
- AXIOS_REGEX.lastIndex = 0;
336
- while ((match = AXIOS_REGEX.exec(source)) !== null) {
337
- const apiPath = toApiPath(match[2]);
338
- entities.push({
339
- entityType: 'api',
340
- name: `${match[1].toUpperCase()} ${apiPath}`,
341
- system,
342
- data: { method: match[1].toUpperCase(), path: apiPath, scope: 'frontend-ref' },
343
- metadata: { sourceFile: filePath },
344
- });
345
- }
346
-
347
- // ─── Component Import Relations ───────────────────────────
348
- IMPORT_COMPONENT_REGEX.lastIndex = 0;
349
- while ((match = IMPORT_COMPONENT_REGEX.exec(source)) !== null) {
350
- const importedName = match[1];
351
- const importPath = match[2];
352
- if (importPath.startsWith('.') && /^[A-Z]/.test(importedName)) {
353
- if (entities.length > 0) {
354
- relations.push({
355
- source: entities[0].name,
356
- target: importedName,
357
- sourceType: entities[0].entityType,
358
- targetType: 'screen',
359
- sourceSystem: system,
360
- targetSystem: system,
361
- relation: 'navigates',
362
- });
363
- }
364
- }
365
- }
366
-
367
- return { filePath, entities, relations };
368
- }
@@ -1,144 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- // Smart comment pattern: // @archsync:<directive> <params>
5
- const COMMENT_REGEX = /\/\/\s*@archsync:(\w+)\s*(.*)/g;
6
- const BLOCK_COMMENT_REGEX = /\/\*\*?\s*@archsync:(\w+)\s*([\s\S]*?)\*\//g;
7
-
8
- /**
9
- * Parse ArchSync smart comments from source code.
10
- * Supports:
11
- * // @archsync:entity <type> <name>
12
- * // @archsync:relation <source> -> <target> <relation>
13
- * // @archsync:system <system>
14
- * // @archsync:api <method> <path>
15
- * // @archsync:model <name> { field: type, ... }
16
- * // @archsync:tag <tag1> <tag2>
17
- * // @archsync:description <text>
18
- * // @archsync:ignore
19
- */
20
- export function parseSmartComments(filePath) {
21
- const source = fs.readFileSync(filePath, 'utf-8');
22
- const entities = [];
23
- const relations = [];
24
- let currentSystem = null;
25
- let ignoreFile = false;
26
-
27
- const lines = source.split('\n');
28
- let currentEntity = null;
29
-
30
- for (let i = 0; i < lines.length; i++) {
31
- const line = lines[i];
32
- const match = line.match(/\/\/\s*@archsync:(\w+)\s*(.*)/);
33
- if (!match) continue;
34
-
35
- const [, directive, params] = match;
36
- const trimmedParams = params.trim();
37
-
38
- switch (directive) {
39
- case 'ignore':
40
- ignoreFile = true;
41
- break;
42
-
43
- case 'system':
44
- currentSystem = trimmedParams;
45
- break;
46
-
47
- case 'entity': {
48
- const parts = trimmedParams.split(/\s+/);
49
- const entityType = parts[0];
50
- const name = parts.slice(1).join(' ') || `Unnamed ${entityType}`;
51
- currentEntity = {
52
- entityType,
53
- name,
54
- system: currentSystem || 'backend',
55
- data: {},
56
- metadata: { tags: [], sourceLine: i + 1 },
57
- };
58
- entities.push(currentEntity);
59
- break;
60
- }
61
-
62
- case 'api': {
63
- const parts = trimmedParams.split(/\s+/);
64
- const method = parts[0] || 'GET';
65
- const apiPath = parts[1] || '/';
66
- const name = `${method} ${apiPath}`;
67
- currentEntity = {
68
- entityType: 'api',
69
- name,
70
- system: currentSystem || 'backend',
71
- data: { method, path: apiPath, scope: 'public' },
72
- metadata: { tags: [], sourceLine: i + 1 },
73
- };
74
- entities.push(currentEntity);
75
- break;
76
- }
77
-
78
- case 'model': {
79
- const nameMatch = trimmedParams.match(/^(\w+)\s*(\{.*\})?/);
80
- if (nameMatch) {
81
- const name = nameMatch[1];
82
- let fields = {};
83
- if (nameMatch[2]) {
84
- try {
85
- fields = JSON.parse(nameMatch[2].replace(/(\w+):/g, '"$1":'));
86
- } catch { /* skip malformed */ }
87
- }
88
- currentEntity = {
89
- entityType: 'model',
90
- name,
91
- system: currentSystem || 'backend',
92
- data: { attributes: Object.entries(fields).map(([k, v]) => ({ name: k, type: v })) },
93
- metadata: { tags: [], sourceLine: i + 1 },
94
- };
95
- entities.push(currentEntity);
96
- }
97
- break;
98
- }
99
-
100
- case 'relation': {
101
- const relMatch = trimmedParams.match(/(\S+)\s*->\s*(\S+)\s*(.*)/);
102
- if (relMatch) {
103
- relations.push({
104
- source: relMatch[1],
105
- target: relMatch[2],
106
- relation: relMatch[3].trim() || 'uses',
107
- sourceType: 'unknown',
108
- targetType: 'unknown',
109
- });
110
- }
111
- break;
112
- }
113
-
114
- case 'tag':
115
- if (currentEntity) {
116
- currentEntity.metadata.tags.push(...trimmedParams.split(/\s+/));
117
- }
118
- break;
119
-
120
- case 'description':
121
- if (currentEntity) {
122
- currentEntity.description = trimmedParams;
123
- }
124
- break;
125
-
126
- case 'scope':
127
- if (currentEntity && currentEntity.data) {
128
- currentEntity.data.scope = trimmedParams;
129
- }
130
- break;
131
-
132
- default:
133
- // Unknown directive, add as metadata
134
- if (currentEntity) {
135
- currentEntity.data[directive] = trimmedParams;
136
- }
137
- break;
138
- }
139
- }
140
-
141
- if (ignoreFile) return { filePath, entities: [], relations: [] };
142
-
143
- return { filePath, entities, relations };
144
- }