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,635 @@
1
+ /**
2
+ * Dependency Analyzer
3
+ *
4
+ * Analyzes project dependencies to identify:
5
+ * - Internal module dependencies
6
+ * - Circular dependencies
7
+ * - Dependency graphs
8
+ * - Independent modules (can be tested/built in parallel)
9
+ * - Coupling metrics
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ /**
16
+ * Import pattern matchers
17
+ */
18
+ const IMPORT_PATTERNS = {
19
+ esm: /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g,
20
+ cjs: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
21
+ dynamic: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
22
+ };
23
+
24
+ /**
25
+ * Dependency types
26
+ */
27
+ const DEP_TYPES = {
28
+ INTERNAL: 'internal',
29
+ EXTERNAL: 'external',
30
+ BUILTIN: 'builtin',
31
+ };
32
+
33
+ /**
34
+ * Node.js built-in modules
35
+ */
36
+ const BUILTIN_MODULES = new Set([
37
+ 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants',
38
+ 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https',
39
+ 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode',
40
+ 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'sys',
41
+ 'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'zlib',
42
+ ]);
43
+
44
+ /**
45
+ * Dependency Analyzer class
46
+ */
47
+ class DependencyAnalyzer {
48
+ constructor(options = {}) {
49
+ this.rootDir = options.rootDir || process.cwd();
50
+ this.extensions = options.extensions || ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
51
+ this.excludeDirs = options.excludeDirs || ['node_modules', '.git', 'dist', 'build', 'coverage'];
52
+ this.dependencies = new Map();
53
+ this.reverseDeps = new Map();
54
+ this.fileContents = new Map();
55
+ }
56
+
57
+ /**
58
+ * Analyze a directory recursively
59
+ * @param {string} dir - Directory to analyze
60
+ * @returns {object} Analysis results
61
+ */
62
+ analyze(dir = this.rootDir) {
63
+ this.clear();
64
+ this.scanDirectory(dir);
65
+ this.buildReverseDependencies();
66
+
67
+ return {
68
+ files: this.dependencies.size,
69
+ dependencies: this.getDependencyStats(),
70
+ circular: this.findCircularDependencies(),
71
+ independent: this.findIndependentModules(),
72
+ coupling: this.calculateCoupling(),
73
+ layers: this.detectLayers(),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Scan directory for source files
79
+ * @param {string} dir - Directory to scan
80
+ */
81
+ scanDirectory(dir) {
82
+ let entries;
83
+ try {
84
+ entries = fs.readdirSync(dir, { withFileTypes: true });
85
+ } catch {
86
+ return;
87
+ }
88
+
89
+ for (const entry of entries) {
90
+ const fullPath = path.join(dir, entry.name);
91
+
92
+ if (entry.isDirectory()) {
93
+ if (!this.excludeDirs.includes(entry.name)) {
94
+ this.scanDirectory(fullPath);
95
+ }
96
+ } else if (entry.isFile()) {
97
+ const ext = path.extname(entry.name);
98
+ if (this.extensions.includes(ext)) {
99
+ this.analyzeFile(fullPath);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Analyze a single file for dependencies
107
+ * @param {string} filePath - File to analyze
108
+ */
109
+ analyzeFile(filePath) {
110
+ let content;
111
+ try {
112
+ content = fs.readFileSync(filePath, 'utf-8');
113
+ } catch {
114
+ return;
115
+ }
116
+
117
+ this.fileContents.set(filePath, content);
118
+ const deps = this.extractDependencies(content, path.dirname(filePath));
119
+ this.dependencies.set(filePath, deps);
120
+ }
121
+
122
+ /**
123
+ * Extract dependencies from file content
124
+ * @param {string} content - File content
125
+ * @param {string} baseDir - Base directory for resolving paths
126
+ * @returns {Array<{path: string, type: string, raw: string}>}
127
+ */
128
+ extractDependencies(content, baseDir) {
129
+ const deps = [];
130
+ const seen = new Set();
131
+
132
+ for (const [_name, regex] of Object.entries(IMPORT_PATTERNS)) {
133
+ regex.lastIndex = 0;
134
+ let match;
135
+
136
+ while ((match = regex.exec(content)) !== null) {
137
+ const raw = match[1];
138
+ if (seen.has(raw)) continue;
139
+ seen.add(raw);
140
+
141
+ const dep = this.classifyDependency(raw, baseDir);
142
+ deps.push(dep);
143
+ }
144
+ }
145
+
146
+ return deps;
147
+ }
148
+
149
+ /**
150
+ * Classify a dependency as internal, external, or builtin
151
+ * @param {string} importPath - Import path
152
+ * @param {string} baseDir - Base directory
153
+ * @returns {{path: string, type: string, raw: string}}
154
+ */
155
+ classifyDependency(importPath, baseDir) {
156
+ const raw = importPath;
157
+
158
+ // Check for built-in modules
159
+ if (BUILTIN_MODULES.has(importPath) || importPath.startsWith('node:')) {
160
+ return { path: importPath, type: DEP_TYPES.BUILTIN, raw };
161
+ }
162
+
163
+ // Check for relative imports (internal)
164
+ if (importPath.startsWith('.') || importPath.startsWith('/')) {
165
+ const resolved = this.resolveImport(importPath, baseDir);
166
+ return { path: resolved || importPath, type: DEP_TYPES.INTERNAL, raw };
167
+ }
168
+
169
+ // Everything else is external (npm packages)
170
+ return { path: importPath.split('/')[0], type: DEP_TYPES.EXTERNAL, raw };
171
+ }
172
+
173
+ /**
174
+ * Resolve a relative import to absolute path
175
+ * @param {string} importPath - Import path
176
+ * @param {string} baseDir - Base directory
177
+ * @returns {string|null}
178
+ */
179
+ resolveImport(importPath, baseDir) {
180
+ const resolved = path.resolve(baseDir, importPath);
181
+
182
+ // Try with extensions
183
+ for (const ext of this.extensions) {
184
+ const withExt = resolved + ext;
185
+ if (fs.existsSync(withExt)) {
186
+ return withExt;
187
+ }
188
+ }
189
+
190
+ // Try index files
191
+ for (const ext of this.extensions) {
192
+ const indexPath = path.join(resolved, `index${ext}`);
193
+ if (fs.existsSync(indexPath)) {
194
+ return indexPath;
195
+ }
196
+ }
197
+
198
+ // Try exact path
199
+ if (fs.existsSync(resolved)) {
200
+ return resolved;
201
+ }
202
+
203
+ return null;
204
+ }
205
+
206
+ /**
207
+ * Build reverse dependency map
208
+ */
209
+ buildReverseDependencies() {
210
+ this.reverseDeps.clear();
211
+
212
+ for (const [file, deps] of this.dependencies) {
213
+ for (const dep of deps) {
214
+ if (dep.type === DEP_TYPES.INTERNAL && dep.path) {
215
+ if (!this.reverseDeps.has(dep.path)) {
216
+ this.reverseDeps.set(dep.path, new Set());
217
+ }
218
+ this.reverseDeps.get(dep.path).add(file);
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Find circular dependencies
226
+ * @returns {Array<string[]>} Array of circular dependency chains
227
+ */
228
+ findCircularDependencies() {
229
+ const cycles = [];
230
+ const visited = new Set();
231
+ const recursionStack = new Set();
232
+
233
+ const dfs = (file, chain = []) => {
234
+ if (recursionStack.has(file)) {
235
+ const cycleStart = chain.indexOf(file);
236
+ if (cycleStart !== -1) {
237
+ const cycle = chain.slice(cycleStart);
238
+ cycle.push(file);
239
+ cycles.push(cycle.map(f => this.relativePath(f)));
240
+ }
241
+ return;
242
+ }
243
+
244
+ if (visited.has(file)) return;
245
+
246
+ visited.add(file);
247
+ recursionStack.add(file);
248
+ chain.push(file);
249
+
250
+ const deps = this.dependencies.get(file) || [];
251
+ for (const dep of deps) {
252
+ if (dep.type === DEP_TYPES.INTERNAL && dep.path) {
253
+ dfs(dep.path, [...chain]);
254
+ }
255
+ }
256
+
257
+ recursionStack.delete(file);
258
+ };
259
+
260
+ for (const file of this.dependencies.keys()) {
261
+ dfs(file);
262
+ }
263
+
264
+ // Deduplicate cycles
265
+ const uniqueCycles = [];
266
+ const seen = new Set();
267
+
268
+ for (const cycle of cycles) {
269
+ const normalized = this.normalizeCycle(cycle);
270
+ const key = normalized.join('->');
271
+ if (!seen.has(key)) {
272
+ seen.add(key);
273
+ uniqueCycles.push(cycle);
274
+ }
275
+ }
276
+
277
+ return uniqueCycles;
278
+ }
279
+
280
+ /**
281
+ * Normalize a cycle for deduplication
282
+ * @param {string[]} cycle - Cycle to normalize
283
+ * @returns {string[]}
284
+ */
285
+ normalizeCycle(cycle) {
286
+ if (cycle.length <= 1) return cycle;
287
+
288
+ const withoutLast = cycle.slice(0, -1);
289
+ const minIndex = withoutLast.indexOf(
290
+ withoutLast.reduce((min, curr) => curr < min ? curr : min),
291
+ );
292
+
293
+ return [...withoutLast.slice(minIndex), ...withoutLast.slice(0, minIndex)];
294
+ }
295
+
296
+ /**
297
+ * Find independent modules (no internal dependencies)
298
+ * @returns {string[]}
299
+ */
300
+ findIndependentModules() {
301
+ const independent = [];
302
+
303
+ for (const [file, deps] of this.dependencies) {
304
+ const internalDeps = deps.filter(d => d.type === DEP_TYPES.INTERNAL);
305
+ if (internalDeps.length === 0) {
306
+ independent.push(this.relativePath(file));
307
+ }
308
+ }
309
+
310
+ return independent;
311
+ }
312
+
313
+ /**
314
+ * Calculate coupling metrics
315
+ * @returns {object}
316
+ */
317
+ calculateCoupling() {
318
+ const metrics = {
319
+ afferentCoupling: new Map(),
320
+ efferentCoupling: new Map(),
321
+ instability: new Map(),
322
+ };
323
+
324
+ for (const [file, deps] of this.dependencies) {
325
+ const relPath = this.relativePath(file);
326
+ const internalDeps = deps.filter(d => d.type === DEP_TYPES.INTERNAL);
327
+
328
+ // Efferent coupling (outgoing dependencies)
329
+ metrics.efferentCoupling.set(relPath, internalDeps.length);
330
+
331
+ // Afferent coupling (incoming dependencies)
332
+ const incoming = this.reverseDeps.get(file)?.size || 0;
333
+ metrics.afferentCoupling.set(relPath, incoming);
334
+
335
+ // Instability = Ce / (Ca + Ce)
336
+ const ce = internalDeps.length;
337
+ const ca = incoming;
338
+ const instability = (ca + ce) > 0 ? ce / (ca + ce) : 0;
339
+ metrics.instability.set(relPath, Math.round(instability * 100) / 100);
340
+ }
341
+
342
+ return {
343
+ afferent: Object.fromEntries(metrics.afferentCoupling),
344
+ efferent: Object.fromEntries(metrics.efferentCoupling),
345
+ instability: Object.fromEntries(metrics.instability),
346
+ averageInstability: this.average([...metrics.instability.values()]),
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Detect architectural layers based on directory structure
352
+ * @returns {object}
353
+ */
354
+ detectLayers() {
355
+ const layers = new Map();
356
+
357
+ for (const file of this.dependencies.keys()) {
358
+ const relPath = this.relativePath(file);
359
+ const parts = relPath.split(path.sep);
360
+
361
+ // Use first directory as layer
362
+ const layer = parts.length > 1 ? parts[0] : 'root';
363
+
364
+ if (!layers.has(layer)) {
365
+ layers.set(layer, { files: [], dependencies: new Set() });
366
+ }
367
+
368
+ layers.get(layer).files.push(relPath);
369
+
370
+ // Track cross-layer dependencies
371
+ const deps = this.dependencies.get(file) || [];
372
+ for (const dep of deps) {
373
+ if (dep.type === DEP_TYPES.INTERNAL && dep.path) {
374
+ const depRel = this.relativePath(dep.path);
375
+ const depParts = depRel.split(path.sep);
376
+ const depLayer = depParts.length > 1 ? depParts[0] : 'root';
377
+
378
+ if (depLayer !== layer) {
379
+ layers.get(layer).dependencies.add(depLayer);
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ const result = {};
386
+ for (const [layer, data] of layers) {
387
+ result[layer] = {
388
+ fileCount: data.files.length,
389
+ files: data.files,
390
+ dependsOn: Array.from(data.dependencies),
391
+ };
392
+ }
393
+
394
+ return result;
395
+ }
396
+
397
+ /**
398
+ * Get dependency statistics
399
+ * @returns {object}
400
+ */
401
+ getDependencyStats() {
402
+ let internal = 0;
403
+ let external = 0;
404
+ let builtin = 0;
405
+ const externalPackages = new Set();
406
+
407
+ for (const deps of this.dependencies.values()) {
408
+ for (const dep of deps) {
409
+ switch (dep.type) {
410
+ case DEP_TYPES.INTERNAL:
411
+ internal++;
412
+ break;
413
+ case DEP_TYPES.EXTERNAL:
414
+ external++;
415
+ externalPackages.add(dep.path);
416
+ break;
417
+ case DEP_TYPES.BUILTIN:
418
+ builtin++;
419
+ break;
420
+ default:
421
+ // Unknown type, skip
422
+ break;
423
+ }
424
+ }
425
+ }
426
+
427
+ return {
428
+ internal,
429
+ external,
430
+ builtin,
431
+ total: internal + external + builtin,
432
+ externalPackages: Array.from(externalPackages).sort(),
433
+ };
434
+ }
435
+
436
+ /**
437
+ * Get files that depend on a given file
438
+ * @param {string} filePath - File to check
439
+ * @returns {string[]}
440
+ */
441
+ getDependents(filePath) {
442
+ const absPath = path.isAbsolute(filePath)
443
+ ? filePath
444
+ : path.resolve(this.rootDir, filePath);
445
+
446
+ const dependents = this.reverseDeps.get(absPath);
447
+ return dependents
448
+ ? Array.from(dependents).map(f => this.relativePath(f))
449
+ : [];
450
+ }
451
+
452
+ /**
453
+ * Get files that a given file depends on
454
+ * @param {string} filePath - File to check
455
+ * @returns {string[]}
456
+ */
457
+ getDependencies(filePath) {
458
+ const absPath = path.isAbsolute(filePath)
459
+ ? filePath
460
+ : path.resolve(this.rootDir, filePath);
461
+
462
+ const deps = this.dependencies.get(absPath) || [];
463
+ return deps
464
+ .filter(d => d.type === DEP_TYPES.INTERNAL)
465
+ .map(d => this.relativePath(d.path))
466
+ .filter(Boolean);
467
+ }
468
+
469
+ /**
470
+ * Get topological sort order for building/testing
471
+ * @returns {string[]}
472
+ */
473
+ getTopologicalOrder() {
474
+ const order = [];
475
+ const visited = new Set();
476
+ const temp = new Set();
477
+
478
+ const visit = (file) => {
479
+ if (temp.has(file)) return; // Circular dependency
480
+ if (visited.has(file)) return;
481
+
482
+ temp.add(file);
483
+
484
+ const deps = this.dependencies.get(file) || [];
485
+ for (const dep of deps) {
486
+ if (dep.type === DEP_TYPES.INTERNAL && dep.path && this.dependencies.has(dep.path)) {
487
+ visit(dep.path);
488
+ }
489
+ }
490
+
491
+ temp.delete(file);
492
+ visited.add(file);
493
+ order.push(this.relativePath(file));
494
+ };
495
+
496
+ for (const file of this.dependencies.keys()) {
497
+ visit(file);
498
+ }
499
+
500
+ return order;
501
+ }
502
+
503
+ /**
504
+ * Generate dependency graph in DOT format
505
+ * @returns {string}
506
+ */
507
+ toDot() {
508
+ const lines = ['digraph Dependencies {', ' rankdir=LR;', ' node [shape=box];'];
509
+
510
+ for (const [file, deps] of this.dependencies) {
511
+ const from = this.relativePath(file).replace(/[/\\]/g, '_').replace(/\./g, '_');
512
+
513
+ for (const dep of deps) {
514
+ if (dep.type === DEP_TYPES.INTERNAL && dep.path) {
515
+ const to = this.relativePath(dep.path).replace(/[/\\]/g, '_').replace(/\./g, '_');
516
+ lines.push(` ${from} -> ${to};`);
517
+ }
518
+ }
519
+ }
520
+
521
+ lines.push('}');
522
+ return lines.join('\n');
523
+ }
524
+
525
+ /**
526
+ * Generate dependency report as markdown
527
+ * @returns {string}
528
+ */
529
+ toMarkdown() {
530
+ const analysis = this.analyze();
531
+ const lines = [
532
+ '# Dependency Analysis Report',
533
+ '',
534
+ `Generated: ${new Date().toISOString()}`,
535
+ '',
536
+ '## Summary',
537
+ '',
538
+ `- **Total Files**: ${analysis.files}`,
539
+ `- **Internal Dependencies**: ${analysis.dependencies.internal}`,
540
+ `- **External Dependencies**: ${analysis.dependencies.external}`,
541
+ `- **Built-in Dependencies**: ${analysis.dependencies.builtin}`,
542
+ `- **Average Instability**: ${analysis.coupling.averageInstability.toFixed(2)}`,
543
+ '',
544
+ ];
545
+
546
+ if (analysis.circular.length > 0) {
547
+ lines.push('## Circular Dependencies ⚠️', '');
548
+ for (const cycle of analysis.circular) {
549
+ lines.push(`- ${cycle.join(' → ')}`);
550
+ }
551
+ lines.push('');
552
+ }
553
+
554
+ if (analysis.dependencies.externalPackages.length > 0) {
555
+ lines.push('## External Packages', '');
556
+ for (const pkg of analysis.dependencies.externalPackages) {
557
+ lines.push(`- ${pkg}`);
558
+ }
559
+ lines.push('');
560
+ }
561
+
562
+ lines.push('## Layers', '');
563
+ for (const [layer, data] of Object.entries(analysis.layers)) {
564
+ lines.push(`### ${layer}`, '');
565
+ lines.push(`- Files: ${data.fileCount}`);
566
+ if (data.dependsOn.length > 0) {
567
+ lines.push(`- Depends on: ${data.dependsOn.join(', ')}`);
568
+ }
569
+ lines.push('');
570
+ }
571
+
572
+ if (analysis.independent.length > 0) {
573
+ lines.push('## Independent Modules', '');
574
+ lines.push('These modules have no internal dependencies and can be tested in isolation:', '');
575
+ for (const mod of analysis.independent) {
576
+ lines.push(`- ${mod}`);
577
+ }
578
+ lines.push('');
579
+ }
580
+
581
+ return lines.join('\n');
582
+ }
583
+
584
+ /**
585
+ * Get relative path from root
586
+ * @param {string} absPath - Absolute path
587
+ * @returns {string}
588
+ */
589
+ relativePath(absPath) {
590
+ if (!absPath) return '';
591
+ return path.relative(this.rootDir, absPath);
592
+ }
593
+
594
+ /**
595
+ * Calculate average of array
596
+ * @param {number[]} arr - Array of numbers
597
+ * @returns {number}
598
+ */
599
+ average(arr) {
600
+ if (arr.length === 0) return 0;
601
+ return arr.reduce((a, b) => a + b, 0) / arr.length;
602
+ }
603
+
604
+ /**
605
+ * Clear all data
606
+ */
607
+ clear() {
608
+ this.dependencies.clear();
609
+ this.reverseDeps.clear();
610
+ this.fileContents.clear();
611
+ }
612
+
613
+ /**
614
+ * Export analysis data
615
+ * @returns {object}
616
+ */
617
+ export() {
618
+ const deps = {};
619
+ for (const [file, fileDeps] of this.dependencies) {
620
+ deps[this.relativePath(file)] = fileDeps;
621
+ }
622
+
623
+ return {
624
+ rootDir: this.rootDir,
625
+ dependencies: deps,
626
+ analysis: this.analyze(),
627
+ };
628
+ }
629
+ }
630
+
631
+ module.exports = {
632
+ DependencyAnalyzer,
633
+ DEP_TYPES,
634
+ BUILTIN_MODULES,
635
+ };