pmp-gywd 3.3.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.
Files changed (126) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +567 -0
  3. package/bin/install.js +348 -0
  4. package/commands/gywd/add-phase.md +207 -0
  5. package/commands/gywd/anticipate.md +271 -0
  6. package/commands/gywd/bootstrap.md +336 -0
  7. package/commands/gywd/challenge.md +344 -0
  8. package/commands/gywd/check-drift.md +144 -0
  9. package/commands/gywd/complete-milestone.md +106 -0
  10. package/commands/gywd/consider-issues.md +202 -0
  11. package/commands/gywd/context.md +93 -0
  12. package/commands/gywd/create-roadmap.md +115 -0
  13. package/commands/gywd/deps.md +169 -0
  14. package/commands/gywd/digest.md +138 -0
  15. package/commands/gywd/discuss-milestone.md +47 -0
  16. package/commands/gywd/discuss-phase.md +60 -0
  17. package/commands/gywd/execute-plan.md +161 -0
  18. package/commands/gywd/extract-decisions.md +325 -0
  19. package/commands/gywd/health.md +150 -0
  20. package/commands/gywd/help.md +556 -0
  21. package/commands/gywd/history.md +278 -0
  22. package/commands/gywd/impact.md +317 -0
  23. package/commands/gywd/init.md +95 -0
  24. package/commands/gywd/insert-phase.md +227 -0
  25. package/commands/gywd/list-phase-assumptions.md +50 -0
  26. package/commands/gywd/map-codebase.md +84 -0
  27. package/commands/gywd/memory.md +159 -0
  28. package/commands/gywd/new-milestone.md +59 -0
  29. package/commands/gywd/new-project.md +315 -0
  30. package/commands/gywd/pause-work.md +123 -0
  31. package/commands/gywd/plan-fix.md +205 -0
  32. package/commands/gywd/plan-phase.md +93 -0
  33. package/commands/gywd/preview-plan.md +139 -0
  34. package/commands/gywd/profile.md +363 -0
  35. package/commands/gywd/progress.md +317 -0
  36. package/commands/gywd/remove-phase.md +338 -0
  37. package/commands/gywd/research-phase.md +91 -0
  38. package/commands/gywd/resume-work.md +40 -0
  39. package/commands/gywd/rollback.md +179 -0
  40. package/commands/gywd/status.md +42 -0
  41. package/commands/gywd/sync-github.md +234 -0
  42. package/commands/gywd/verify-work.md +71 -0
  43. package/commands/gywd/why.md +251 -0
  44. package/docs/COMMANDS.md +722 -0
  45. package/docs/CONTRIBUTING.md +342 -0
  46. package/docs/EXAMPLES.md +535 -0
  47. package/docs/GETTING-STARTED.md +262 -0
  48. package/docs/README.md +55 -0
  49. package/docs/RELEASING.md +159 -0
  50. package/get-your-work-done/core/agent-patterns.md +331 -0
  51. package/get-your-work-done/core/architecture.md +334 -0
  52. package/get-your-work-done/core/context-model-schema.json +154 -0
  53. package/get-your-work-done/core/decisions-schema.json +193 -0
  54. package/get-your-work-done/core/learning-state-schema.json +133 -0
  55. package/get-your-work-done/core/profile-schema.json +257 -0
  56. package/get-your-work-done/references/adaptive-decomposition.md +175 -0
  57. package/get-your-work-done/references/checkpoints.md +287 -0
  58. package/get-your-work-done/references/confidence-scoring.md +169 -0
  59. package/get-your-work-done/references/continuation-format.md +255 -0
  60. package/get-your-work-done/references/git-integration.md +254 -0
  61. package/get-your-work-done/references/plan-format.md +428 -0
  62. package/get-your-work-done/references/principles.md +157 -0
  63. package/get-your-work-done/references/questioning.md +162 -0
  64. package/get-your-work-done/references/research-pitfalls.md +215 -0
  65. package/get-your-work-done/references/scope-estimation.md +172 -0
  66. package/get-your-work-done/references/tdd.md +263 -0
  67. package/get-your-work-done/templates/codebase/architecture.md +255 -0
  68. package/get-your-work-done/templates/codebase/concerns.md +310 -0
  69. package/get-your-work-done/templates/codebase/conventions.md +307 -0
  70. package/get-your-work-done/templates/codebase/integrations.md +280 -0
  71. package/get-your-work-done/templates/codebase/stack.md +186 -0
  72. package/get-your-work-done/templates/codebase/structure.md +285 -0
  73. package/get-your-work-done/templates/codebase/testing.md +480 -0
  74. package/get-your-work-done/templates/config.json +18 -0
  75. package/get-your-work-done/templates/context.md +161 -0
  76. package/get-your-work-done/templates/continue-here.md +78 -0
  77. package/get-your-work-done/templates/discovery.md +146 -0
  78. package/get-your-work-done/templates/issues.md +32 -0
  79. package/get-your-work-done/templates/milestone-archive.md +123 -0
  80. package/get-your-work-done/templates/milestone-context.md +93 -0
  81. package/get-your-work-done/templates/milestone.md +115 -0
  82. package/get-your-work-done/templates/phase-prompt.md +303 -0
  83. package/get-your-work-done/templates/project.md +184 -0
  84. package/get-your-work-done/templates/research.md +529 -0
  85. package/get-your-work-done/templates/roadmap.md +196 -0
  86. package/get-your-work-done/templates/state.md +210 -0
  87. package/get-your-work-done/templates/summary.md +273 -0
  88. package/get-your-work-done/templates/uat-issues.md +143 -0
  89. package/get-your-work-done/workflows/complete-milestone.md +643 -0
  90. package/get-your-work-done/workflows/create-milestone.md +416 -0
  91. package/get-your-work-done/workflows/create-roadmap.md +481 -0
  92. package/get-your-work-done/workflows/discovery-phase.md +293 -0
  93. package/get-your-work-done/workflows/discuss-milestone.md +236 -0
  94. package/get-your-work-done/workflows/discuss-phase.md +247 -0
  95. package/get-your-work-done/workflows/execute-phase.md +1625 -0
  96. package/get-your-work-done/workflows/list-phase-assumptions.md +178 -0
  97. package/get-your-work-done/workflows/map-codebase.md +434 -0
  98. package/get-your-work-done/workflows/plan-phase.md +488 -0
  99. package/get-your-work-done/workflows/research-phase.md +436 -0
  100. package/get-your-work-done/workflows/resume-project.md +287 -0
  101. package/get-your-work-done/workflows/transition.md +580 -0
  102. package/get-your-work-done/workflows/verify-work.md +202 -0
  103. package/lib/automation/dependency-analyzer.js +635 -0
  104. package/lib/automation/doc-generator.js +643 -0
  105. package/lib/automation/index.js +42 -0
  106. package/lib/automation/test-generator.js +628 -0
  107. package/lib/context/context-analyzer.js +554 -0
  108. package/lib/context/context-cache.js +426 -0
  109. package/lib/context/context-predictor.js +622 -0
  110. package/lib/context/index.js +44 -0
  111. package/lib/memory/confidence-calibrator.js +484 -0
  112. package/lib/memory/feedback-collector.js +551 -0
  113. package/lib/memory/global-memory.js +465 -0
  114. package/lib/memory/index.js +75 -0
  115. package/lib/memory/pattern-aggregator.js +487 -0
  116. package/lib/memory/team-sync.js +501 -0
  117. package/lib/profile/index.js +24 -0
  118. package/lib/profile/pattern-learner.js +303 -0
  119. package/lib/profile/profile-manager.js +445 -0
  120. package/lib/questioning/index.js +49 -0
  121. package/lib/questioning/question-engine.js +311 -0
  122. package/lib/questioning/question-templates.js +315 -0
  123. package/lib/validators/command-validator.js +188 -0
  124. package/lib/validators/index.js +29 -0
  125. package/lib/validators/schema-validator.js +183 -0
  126. package/package.json +61 -0
@@ -0,0 +1,554 @@
1
+ /**
2
+ * Context Analyzer
3
+ *
4
+ * Analyzes codebase structure to understand file relationships.
5
+ * Builds a relationship graph for intelligent context prediction.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Relationship types between files
13
+ */
14
+ const RELATIONSHIP_TYPES = {
15
+ IMPORTS: 'imports',
16
+ IMPORTED_BY: 'imported_by',
17
+ SAME_DIRECTORY: 'same_dir',
18
+ SIMILAR_NAME: 'similar_name',
19
+ TEST_FOR: 'test_for',
20
+ TESTED_BY: 'tested_by',
21
+ CO_MODIFIED: 'co_modified',
22
+ CO_ACCESSED: 'co_accessed',
23
+ };
24
+
25
+ /**
26
+ * Weight multipliers for relationship types
27
+ */
28
+ const RELATIONSHIP_WEIGHTS = {
29
+ [RELATIONSHIP_TYPES.IMPORTS]: 1.0,
30
+ [RELATIONSHIP_TYPES.IMPORTED_BY]: 0.8,
31
+ [RELATIONSHIP_TYPES.SAME_DIRECTORY]: 0.3,
32
+ [RELATIONSHIP_TYPES.SIMILAR_NAME]: 0.5,
33
+ [RELATIONSHIP_TYPES.TEST_FOR]: 0.9,
34
+ [RELATIONSHIP_TYPES.TESTED_BY]: 0.9,
35
+ [RELATIONSHIP_TYPES.CO_MODIFIED]: 0.7,
36
+ [RELATIONSHIP_TYPES.CO_ACCESSED]: 0.6,
37
+ };
38
+
39
+ /**
40
+ * Regex patterns for import detection
41
+ */
42
+ const IMPORT_PATTERNS = {
43
+ // ES6: import x from './file'
44
+ esm: /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g,
45
+ // CommonJS: require('./file')
46
+ cjs: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
47
+ // Dynamic import: import('./file')
48
+ dynamic: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
49
+ };
50
+
51
+ /**
52
+ * Context Analyzer class
53
+ */
54
+ class ContextAnalyzer {
55
+ constructor() {
56
+ this.fileGraph = new Map(); // file -> Map<relatedFile, relationships[]>
57
+ this.fileMetadata = new Map(); // file -> metadata
58
+ this.keywords = new Map(); // keyword -> Set<files>
59
+ this.directories = new Map(); // directory -> Set<files>
60
+ }
61
+
62
+ /**
63
+ * Analyze a file and extract its relationships
64
+ * @param {string} filePath - Path to the file
65
+ * @param {string} content - File content
66
+ * @returns {object} Analysis result
67
+ */
68
+ analyzeFile(filePath, content) {
69
+ const normalizedPath = this.normalizePath(filePath);
70
+ const ext = path.extname(filePath);
71
+ const baseName = path.basename(filePath, ext);
72
+ const dirName = path.dirname(filePath);
73
+
74
+ // Initialize file in graph if needed
75
+ if (!this.fileGraph.has(normalizedPath)) {
76
+ this.fileGraph.set(normalizedPath, new Map());
77
+ }
78
+
79
+ // Store metadata
80
+ const metadata = {
81
+ path: normalizedPath,
82
+ extension: ext,
83
+ baseName,
84
+ directory: dirName,
85
+ size: content.length,
86
+ lineCount: content.split('\n').length,
87
+ isTest: this.isTestFile(filePath),
88
+ lastAnalyzed: Date.now(),
89
+ };
90
+ this.fileMetadata.set(normalizedPath, metadata);
91
+
92
+ // Track directory membership
93
+ if (!this.directories.has(dirName)) {
94
+ this.directories.set(dirName, new Set());
95
+ }
96
+ this.directories.get(dirName).add(normalizedPath);
97
+
98
+ // Extract imports
99
+ const imports = this.extractImports(content, dirName);
100
+
101
+ // Extract keywords
102
+ const keywords = this.extractKeywords(content, baseName);
103
+ for (const keyword of keywords) {
104
+ if (!this.keywords.has(keyword)) {
105
+ this.keywords.set(keyword, new Set());
106
+ }
107
+ this.keywords.get(keyword).add(normalizedPath);
108
+ }
109
+
110
+ return {
111
+ path: normalizedPath,
112
+ metadata,
113
+ imports,
114
+ keywords,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Extract import statements from file content
120
+ * @param {string} content - File content
121
+ * @param {string} baseDir - Base directory for resolving relative paths
122
+ * @returns {string[]} Array of imported file paths
123
+ */
124
+ extractImports(content, baseDir) {
125
+ const imports = new Set();
126
+
127
+ for (const [_type, regex] of Object.entries(IMPORT_PATTERNS)) {
128
+ // Reset regex state
129
+ regex.lastIndex = 0;
130
+ let match;
131
+
132
+ while ((match = regex.exec(content)) !== null) {
133
+ const importPath = match[1];
134
+
135
+ // Skip external packages (no ./ or ../)
136
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
137
+ continue;
138
+ }
139
+
140
+ // Resolve relative path
141
+ const resolved = this.resolveImportPath(importPath, baseDir);
142
+ if (resolved) {
143
+ imports.add(resolved);
144
+ }
145
+ }
146
+ }
147
+
148
+ return Array.from(imports);
149
+ }
150
+
151
+ /**
152
+ * Resolve an import path to a normalized file path
153
+ * @param {string} importPath - Import path from source
154
+ * @param {string} baseDir - Base directory
155
+ * @returns {string|null} Resolved path or null
156
+ */
157
+ resolveImportPath(importPath, baseDir) {
158
+ // Handle relative paths
159
+ const resolved = path.resolve(baseDir, importPath);
160
+
161
+ // Try common extensions if no extension
162
+ if (!path.extname(resolved)) {
163
+ const extensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
164
+ for (const ext of extensions) {
165
+ if (fs.existsSync(resolved + ext)) {
166
+ return this.normalizePath(resolved + ext);
167
+ }
168
+ }
169
+ // Try index files
170
+ for (const ext of extensions) {
171
+ const indexPath = path.join(resolved, `index${ext}`);
172
+ if (fs.existsSync(indexPath)) {
173
+ return this.normalizePath(indexPath);
174
+ }
175
+ }
176
+ }
177
+
178
+ if (fs.existsSync(resolved)) {
179
+ return this.normalizePath(resolved);
180
+ }
181
+
182
+ return null;
183
+ }
184
+
185
+ /**
186
+ * Extract keywords from file content and name
187
+ * @param {string} content - File content
188
+ * @param {string} baseName - File base name
189
+ * @returns {string[]} Array of keywords
190
+ */
191
+ extractKeywords(content, baseName) {
192
+ const keywords = new Set();
193
+
194
+ // Add words from file name (split on camelCase, PascalCase, kebab-case, snake_case)
195
+ const nameWords = baseName
196
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
197
+ .replace(/[-_]/g, ' ')
198
+ .toLowerCase()
199
+ .split(/\s+/)
200
+ .filter(w => w.length > 2);
201
+
202
+ nameWords.forEach(w => keywords.add(w));
203
+
204
+ // Extract function/class names
205
+ const functionMatches = content.matchAll(/(?:function|class|const|let|var)\s+(\w+)/g);
206
+ for (const match of functionMatches) {
207
+ const name = match[1].toLowerCase();
208
+ if (name.length > 2) {
209
+ keywords.add(name);
210
+ }
211
+ }
212
+
213
+ // Extract common domain terms
214
+ const domainTerms = [
215
+ 'auth', 'user', 'login', 'api', 'database', 'db', 'cache',
216
+ 'config', 'util', 'helper', 'service', 'controller', 'model',
217
+ 'view', 'component', 'route', 'middleware', 'test', 'spec',
218
+ 'payment', 'order', 'product', 'customer', 'admin', 'dashboard',
219
+ ];
220
+
221
+ const lowerContent = content.toLowerCase();
222
+ for (const term of domainTerms) {
223
+ if (lowerContent.includes(term)) {
224
+ keywords.add(term);
225
+ }
226
+ }
227
+
228
+ return Array.from(keywords);
229
+ }
230
+
231
+ /**
232
+ * Check if a file is a test file
233
+ * @param {string} filePath - File path
234
+ * @returns {boolean}
235
+ */
236
+ isTestFile(filePath) {
237
+ const lower = filePath.toLowerCase();
238
+ return (
239
+ lower.includes('.test.') ||
240
+ lower.includes('.spec.') ||
241
+ lower.includes('__tests__') ||
242
+ lower.includes('/test/') ||
243
+ lower.includes('/tests/')
244
+ );
245
+ }
246
+
247
+ /**
248
+ * Build relationship graph from analyzed files
249
+ */
250
+ buildRelationshipGraph() {
251
+ // Build import relationships
252
+ for (const [filePath, _metadata] of this.fileMetadata) {
253
+ const fileData = this.fileGraph.get(filePath);
254
+ if (!fileData) continue;
255
+
256
+ // Find test relationships
257
+ this.findTestRelationships(filePath);
258
+
259
+ // Find similar name relationships
260
+ this.findSimilarNameRelationships(filePath);
261
+ }
262
+
263
+ // Build same-directory relationships
264
+ for (const [_dir, files] of this.directories) {
265
+ const fileArray = Array.from(files);
266
+ for (let i = 0; i < fileArray.length; i++) {
267
+ for (let j = i + 1; j < fileArray.length; j++) {
268
+ this.addRelationship(fileArray[i], fileArray[j], RELATIONSHIP_TYPES.SAME_DIRECTORY);
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Add import relationship between files
276
+ * @param {string} fromFile - Importing file
277
+ * @param {string} toFile - Imported file
278
+ */
279
+ addImportRelationship(fromFile, toFile) {
280
+ this.addRelationship(fromFile, toFile, RELATIONSHIP_TYPES.IMPORTS);
281
+ this.addRelationship(toFile, fromFile, RELATIONSHIP_TYPES.IMPORTED_BY);
282
+ }
283
+
284
+ /**
285
+ * Find test-to-source relationships
286
+ * @param {string} filePath - File to analyze
287
+ */
288
+ findTestRelationships(filePath) {
289
+ const metadata = this.fileMetadata.get(filePath);
290
+ if (!metadata) return;
291
+
292
+ if (metadata.isTest) {
293
+ // Find the source file this tests
294
+ const sourcePattern = metadata.baseName
295
+ .replace(/\.test$/, '')
296
+ .replace(/\.spec$/, '')
297
+ .replace(/Test$/, '')
298
+ .replace(/Spec$/, '');
299
+
300
+ for (const [otherPath, otherMeta] of this.fileMetadata) {
301
+ if (otherPath === filePath) continue;
302
+ if (otherMeta.isTest) continue;
303
+
304
+ if (otherMeta.baseName.toLowerCase() === sourcePattern.toLowerCase()) {
305
+ this.addRelationship(filePath, otherPath, RELATIONSHIP_TYPES.TEST_FOR);
306
+ this.addRelationship(otherPath, filePath, RELATIONSHIP_TYPES.TESTED_BY);
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Find similar name relationships
314
+ * @param {string} filePath - File to analyze
315
+ */
316
+ findSimilarNameRelationships(filePath) {
317
+ const metadata = this.fileMetadata.get(filePath);
318
+ if (!metadata) return;
319
+
320
+ const baseName = metadata.baseName.toLowerCase();
321
+
322
+ for (const [otherPath, otherMeta] of this.fileMetadata) {
323
+ if (otherPath === filePath) continue;
324
+
325
+ const otherBase = otherMeta.baseName.toLowerCase();
326
+
327
+ // Check if names share a common root (>50% overlap)
328
+ if (this.calculateNameSimilarity(baseName, otherBase) > 0.5) {
329
+ this.addRelationship(filePath, otherPath, RELATIONSHIP_TYPES.SIMILAR_NAME);
330
+ }
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Calculate name similarity between two strings
336
+ * @param {string} a - First string
337
+ * @param {string} b - Second string
338
+ * @returns {number} Similarity score (0-1)
339
+ */
340
+ calculateNameSimilarity(a, b) {
341
+ if (a === b) return 1;
342
+ if (a.length === 0 || b.length === 0) return 0;
343
+
344
+ // Check prefix match
345
+ const minLen = Math.min(a.length, b.length);
346
+ let prefixMatch = 0;
347
+ for (let i = 0; i < minLen; i++) {
348
+ if (a[i] === b[i]) prefixMatch++;
349
+ else break;
350
+ }
351
+
352
+ // Check if one contains the other
353
+ if (a.includes(b) || b.includes(a)) {
354
+ return 0.7;
355
+ }
356
+
357
+ return prefixMatch / Math.max(a.length, b.length);
358
+ }
359
+
360
+ /**
361
+ * Add a relationship between two files
362
+ * @param {string} from - Source file
363
+ * @param {string} to - Target file
364
+ * @param {string} type - Relationship type
365
+ */
366
+ addRelationship(from, to, type) {
367
+ if (!this.fileGraph.has(from)) {
368
+ this.fileGraph.set(from, new Map());
369
+ }
370
+
371
+ const relations = this.fileGraph.get(from);
372
+ if (!relations.has(to)) {
373
+ relations.set(to, []);
374
+ }
375
+
376
+ const existing = relations.get(to);
377
+ if (!existing.includes(type)) {
378
+ existing.push(type);
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Record co-access pattern
384
+ * @param {string[]} files - Files accessed together
385
+ */
386
+ recordCoAccess(files) {
387
+ for (let i = 0; i < files.length; i++) {
388
+ for (let j = i + 1; j < files.length; j++) {
389
+ this.addRelationship(files[i], files[j], RELATIONSHIP_TYPES.CO_ACCESSED);
390
+ this.addRelationship(files[j], files[i], RELATIONSHIP_TYPES.CO_ACCESSED);
391
+ }
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Get related files with scores
397
+ * @param {string} filePath - File to find relations for
398
+ * @param {number} limit - Maximum results
399
+ * @returns {Array<{file: string, score: number, relationships: string[]}>}
400
+ */
401
+ getRelatedFiles(filePath, limit = 10) {
402
+ const normalizedPath = this.normalizePath(filePath);
403
+ const relations = this.fileGraph.get(normalizedPath);
404
+
405
+ if (!relations) return [];
406
+
407
+ const scored = [];
408
+
409
+ for (const [relatedFile, relationTypes] of relations) {
410
+ let score = 0;
411
+ for (const type of relationTypes) {
412
+ score += RELATIONSHIP_WEIGHTS[type] || 0.1;
413
+ }
414
+
415
+ scored.push({
416
+ file: relatedFile,
417
+ score,
418
+ relationships: relationTypes,
419
+ });
420
+ }
421
+
422
+ // Sort by score descending
423
+ scored.sort((a, b) => b.score - a.score);
424
+
425
+ return scored.slice(0, limit);
426
+ }
427
+
428
+ /**
429
+ * Find files by keyword
430
+ * @param {string} keyword - Keyword to search
431
+ * @returns {string[]} Matching file paths
432
+ */
433
+ findByKeyword(keyword) {
434
+ const lowerKeyword = keyword.toLowerCase();
435
+ const files = this.keywords.get(lowerKeyword);
436
+ return files ? Array.from(files) : [];
437
+ }
438
+
439
+ /**
440
+ * Find files by multiple keywords (intersection)
441
+ * @param {string[]} keywords - Keywords to search
442
+ * @returns {string[]} Matching file paths
443
+ */
444
+ findByKeywords(keywords) {
445
+ if (keywords.length === 0) return [];
446
+
447
+ const sets = keywords
448
+ .map(k => this.keywords.get(k.toLowerCase()))
449
+ .filter(s => s !== undefined);
450
+
451
+ if (sets.length === 0) return [];
452
+
453
+ // Intersection of all sets
454
+ let result = new Set(sets[0]);
455
+ for (let i = 1; i < sets.length; i++) {
456
+ result = new Set([...result].filter(x => sets[i].has(x)));
457
+ }
458
+
459
+ return Array.from(result);
460
+ }
461
+
462
+ /**
463
+ * Normalize file path for consistent comparison
464
+ * @param {string} filePath - File path
465
+ * @returns {string} Normalized path
466
+ */
467
+ normalizePath(filePath) {
468
+ return path.normalize(filePath).replace(/\\/g, '/');
469
+ }
470
+
471
+ /**
472
+ * Get analysis summary
473
+ * @returns {object} Summary statistics
474
+ */
475
+ getSummary() {
476
+ let totalRelationships = 0;
477
+ for (const relations of this.fileGraph.values()) {
478
+ totalRelationships += relations.size;
479
+ }
480
+
481
+ return {
482
+ filesAnalyzed: this.fileMetadata.size,
483
+ totalRelationships,
484
+ keywordsIndexed: this.keywords.size,
485
+ directoriesTracked: this.directories.size,
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Export graph data for persistence
491
+ * @returns {object} Serializable graph data
492
+ */
493
+ export() {
494
+ const graph = {};
495
+ for (const [file, relations] of this.fileGraph) {
496
+ graph[file] = {};
497
+ for (const [related, types] of relations) {
498
+ graph[file][related] = types;
499
+ }
500
+ }
501
+
502
+ const metadata = {};
503
+ for (const [file, meta] of this.fileMetadata) {
504
+ metadata[file] = meta;
505
+ }
506
+
507
+ const keywords = {};
508
+ for (const [keyword, files] of this.keywords) {
509
+ keywords[keyword] = Array.from(files);
510
+ }
511
+
512
+ return { graph, metadata, keywords };
513
+ }
514
+
515
+ /**
516
+ * Import graph data from persistence
517
+ * @param {object} data - Previously exported data
518
+ */
519
+ import(data) {
520
+ if (data.graph) {
521
+ for (const [file, relations] of Object.entries(data.graph)) {
522
+ this.fileGraph.set(file, new Map(Object.entries(relations)));
523
+ }
524
+ }
525
+
526
+ if (data.metadata) {
527
+ for (const [file, meta] of Object.entries(data.metadata)) {
528
+ this.fileMetadata.set(file, meta);
529
+ }
530
+ }
531
+
532
+ if (data.keywords) {
533
+ for (const [keyword, files] of Object.entries(data.keywords)) {
534
+ this.keywords.set(keyword, new Set(files));
535
+ }
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Clear all data
541
+ */
542
+ clear() {
543
+ this.fileGraph.clear();
544
+ this.fileMetadata.clear();
545
+ this.keywords.clear();
546
+ this.directories.clear();
547
+ }
548
+ }
549
+
550
+ module.exports = {
551
+ ContextAnalyzer,
552
+ RELATIONSHIP_TYPES,
553
+ RELATIONSHIP_WEIGHTS,
554
+ };