@girardmedia/bootspring 2.0.21 → 2.0.23

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 (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,1453 @@
1
+ /**
2
+ * Bootspring Analyze Workflow Engine
3
+ *
4
+ * Deep codebase analysis for understanding architecture,
5
+ * dependencies, patterns, and code flows.
6
+ *
7
+ * @package bootspring
8
+ * @module core/analyze-workflow
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ // Import analyzers from JS modules
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ const { StructureAnalyzer } = require('../../analyzers/structure-analyzer') as {
17
+ StructureAnalyzer: new (projectRoot: string, options?: Record<string, unknown>) => {
18
+ analyze: () => Record<string, unknown>;
19
+ };
20
+ };
21
+
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const { ArchitectureAnalyzer } = require('../../analyzers/architecture-analyzer') as {
24
+ ArchitectureAnalyzer: new (projectRoot: string) => {
25
+ analyze: () => Record<string, unknown>;
26
+ };
27
+ };
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
30
+ const { DependencyAnalyzer } = require('../../analyzers/dependency-analyzer') as {
31
+ DependencyAnalyzer: new (projectRoot: string) => {
32
+ analyze: () => Record<string, unknown>;
33
+ };
34
+ };
35
+
36
+ // ============================================================================
37
+ // Types
38
+ // ============================================================================
39
+
40
+ export type PhaseStatus = 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed';
41
+ export type AnalysisDepthLevel = 'shallow' | 'standard' | 'deep';
42
+
43
+ export interface PhaseDefinition {
44
+ name: string;
45
+ description: string;
46
+ order: number;
47
+ required: boolean;
48
+ dependencies?: string[] | undefined;
49
+ }
50
+
51
+ export interface PhaseState {
52
+ status: PhaseStatus;
53
+ startedAt: string | null;
54
+ completedAt: string | null;
55
+ result: unknown | null;
56
+ error: string | null;
57
+ }
58
+
59
+ export interface DepthConfig {
60
+ phases: string[];
61
+ description: string;
62
+ }
63
+
64
+ export interface AnalyzeWorkflowState {
65
+ version: string;
66
+ startedAt: string | null;
67
+ lastUpdated: string | null;
68
+ currentPhase: string | null;
69
+ depth: AnalysisDepthLevel;
70
+ phases: Record<string, PhaseState>;
71
+ results: Record<string, unknown>;
72
+ summary: WorkflowSummary | null;
73
+ }
74
+
75
+ export interface WorkflowSummary {
76
+ reportPath: string;
77
+ generatedAt: string;
78
+ }
79
+
80
+ export interface StructureResult {
81
+ totalFiles: number;
82
+ totalDirs: number;
83
+ structureType: unknown;
84
+ entryPoints: number;
85
+ keyDirectories: string[];
86
+ }
87
+
88
+ export interface ArchitectureResult {
89
+ patterns: unknown[];
90
+ layers: string[];
91
+ modules: number;
92
+ hasComponentSystem: boolean;
93
+ }
94
+
95
+ export interface DependencyResult {
96
+ fileCount: number;
97
+ circularCount: number;
98
+ unusedCount: number;
99
+ externalPackages: number;
100
+ mostImported: unknown[];
101
+ skipped?: boolean | undefined;
102
+ }
103
+
104
+ export interface PatternResult {
105
+ designPatterns: number;
106
+ antiPatterns: number;
107
+ codeSmells: number;
108
+ }
109
+
110
+ export interface FlowResult {
111
+ entryPoints: number;
112
+ apiEndpoints: number;
113
+ pageRoutes: number;
114
+ }
115
+
116
+ export interface ComponentResult {
117
+ total: number;
118
+ categories: number;
119
+ }
120
+
121
+ export interface ReportResult {
122
+ reportPath: string;
123
+ reportLength: number;
124
+ }
125
+
126
+ export interface ApiEndpoint {
127
+ path: string;
128
+ file: string;
129
+ methods: string[];
130
+ }
131
+
132
+ export interface HealthScore {
133
+ overall: number;
134
+ structure: number;
135
+ architecture: number;
136
+ dependencies: number;
137
+ }
138
+
139
+ export interface Recommendation {
140
+ title: string;
141
+ description: string;
142
+ }
143
+
144
+ export interface PhaseProgress {
145
+ id: string;
146
+ name: string;
147
+ description: string;
148
+ order: number;
149
+ required: boolean;
150
+ status: PhaseStatus;
151
+ dependenciesMet: boolean;
152
+ }
153
+
154
+ export interface WorkflowProgress {
155
+ currentPhase: string | null;
156
+ depth: AnalysisDepthLevel;
157
+ startedAt: string | null;
158
+ lastUpdated: string | null;
159
+ phases: PhaseProgress[];
160
+ overall: {
161
+ completed: number;
162
+ total: number;
163
+ percentage: number;
164
+ };
165
+ isComplete: boolean;
166
+ summary: WorkflowSummary | null;
167
+ }
168
+
169
+ export interface ResumePoint {
170
+ phase: string;
171
+ phaseName: string | undefined;
172
+ phaseStatus: PhaseStatus | undefined;
173
+ lastUpdated: string | null;
174
+ }
175
+
176
+ export interface AnalyzeWorkflowOptions {
177
+ depth?: AnalysisDepthLevel | undefined;
178
+ include?: string | null | undefined;
179
+ exclude?: string | null | undefined;
180
+ [key: string]: unknown;
181
+ }
182
+
183
+ // ============================================================================
184
+ // Constants
185
+ // ============================================================================
186
+
187
+ /**
188
+ * Workflow phase status
189
+ */
190
+ export const PHASE_STATUS: Record<string, PhaseStatus> = {
191
+ PENDING: 'pending',
192
+ IN_PROGRESS: 'in_progress',
193
+ COMPLETED: 'completed',
194
+ SKIPPED: 'skipped',
195
+ FAILED: 'failed'
196
+ };
197
+
198
+ /**
199
+ * Analysis phases
200
+ */
201
+ export const ANALYZE_PHASES: Record<string, PhaseDefinition> = {
202
+ structure: {
203
+ name: 'Structure Mapping',
204
+ description: 'Parse files, build tree, detect monorepo',
205
+ order: 1,
206
+ required: true
207
+ },
208
+ architecture: {
209
+ name: 'Architecture Analysis',
210
+ description: 'Identify layers, modules, patterns (MVC, etc.)',
211
+ order: 2,
212
+ required: true,
213
+ dependencies: ['structure']
214
+ },
215
+ dependencies: {
216
+ name: 'Dependency Analysis',
217
+ description: 'Import graph, circular deps, unused deps',
218
+ order: 3,
219
+ required: true,
220
+ dependencies: ['structure']
221
+ },
222
+ patterns: {
223
+ name: 'Pattern Detection',
224
+ description: 'Design patterns, anti-patterns, code smells',
225
+ order: 4,
226
+ required: true,
227
+ dependencies: ['structure', 'architecture']
228
+ },
229
+ flows: {
230
+ name: 'Flow Tracing',
231
+ description: 'Entry points, request flows, data flows',
232
+ order: 5,
233
+ required: false,
234
+ dependencies: ['structure', 'dependencies']
235
+ },
236
+ components: {
237
+ name: 'Component Analysis',
238
+ description: 'Component signatures, prop drilling, coupling',
239
+ order: 6,
240
+ required: false,
241
+ dependencies: ['structure', 'architecture']
242
+ },
243
+ report: {
244
+ name: 'Report Generation',
245
+ description: 'Executive summary, Mermaid diagrams',
246
+ order: 7,
247
+ required: true,
248
+ dependencies: ['structure', 'architecture', 'dependencies', 'patterns']
249
+ }
250
+ };
251
+
252
+ /**
253
+ * Analysis depth levels
254
+ */
255
+ export const ANALYSIS_DEPTH: Record<AnalysisDepthLevel, DepthConfig> = {
256
+ shallow: {
257
+ phases: ['structure', 'architecture', 'report'],
258
+ description: 'Quick scan for basic understanding'
259
+ },
260
+ standard: {
261
+ phases: ['structure', 'architecture', 'dependencies', 'patterns', 'report'],
262
+ description: 'Standard analysis with patterns'
263
+ },
264
+ deep: {
265
+ phases: ['structure', 'architecture', 'dependencies', 'patterns', 'flows', 'components', 'report'],
266
+ description: 'Comprehensive analysis with flows'
267
+ }
268
+ };
269
+
270
+ /**
271
+ * Default workflow state
272
+ */
273
+ export const DEFAULT_STATE: AnalyzeWorkflowState = {
274
+ version: '1.0.0',
275
+ startedAt: null,
276
+ lastUpdated: null,
277
+ currentPhase: null,
278
+ depth: 'standard',
279
+ phases: {},
280
+ results: {},
281
+ summary: null
282
+ };
283
+
284
+ // ============================================================================
285
+ // AnalyzeWorkflowEngine Class
286
+ // ============================================================================
287
+
288
+ /**
289
+ * AnalyzeWorkflowEngine - Manages analysis workflow
290
+ */
291
+ export class AnalyzeWorkflowEngine {
292
+ readonly projectRoot: string;
293
+ readonly workflowDir: string;
294
+ readonly stateFile: string;
295
+ readonly reportsDir: string;
296
+ readonly artifactsDir: string;
297
+ readonly options: AnalyzeWorkflowOptions;
298
+ state: AnalyzeWorkflowState | null;
299
+
300
+ constructor(projectRoot: string, options: AnalyzeWorkflowOptions = {}) {
301
+ this.projectRoot = projectRoot;
302
+ this.workflowDir = path.join(projectRoot, '.bootspring', 'analyze');
303
+ this.stateFile = path.join(this.workflowDir, 'workflow-state.json');
304
+ this.reportsDir = path.join(this.workflowDir, 'reports');
305
+ this.artifactsDir = path.join(this.workflowDir, 'artifacts');
306
+ this.options = {
307
+ depth: options.depth ?? 'standard',
308
+ include: options.include ?? null,
309
+ exclude: options.exclude ?? null,
310
+ ...options
311
+ };
312
+ this.state = null;
313
+ }
314
+
315
+ /**
316
+ * Setup directories
317
+ */
318
+ setupDirectories(): void {
319
+ const dirs = [this.workflowDir, this.reportsDir, this.artifactsDir];
320
+
321
+ for (const dir of dirs) {
322
+ if (!fs.existsSync(dir)) {
323
+ fs.mkdirSync(dir, { recursive: true });
324
+ }
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Load workflow state
330
+ */
331
+ loadState(): boolean {
332
+ if (fs.existsSync(this.stateFile)) {
333
+ try {
334
+ this.state = JSON.parse(fs.readFileSync(this.stateFile, 'utf-8')) as AnalyzeWorkflowState;
335
+ return true;
336
+ } catch {
337
+ this.state = { ...DEFAULT_STATE };
338
+ return false;
339
+ }
340
+ }
341
+ this.state = { ...DEFAULT_STATE };
342
+ return false;
343
+ }
344
+
345
+ /**
346
+ * Save workflow state
347
+ */
348
+ saveState(): void {
349
+ if (!this.state) return;
350
+ this.setupDirectories();
351
+ this.state.lastUpdated = new Date().toISOString();
352
+ fs.writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
353
+ }
354
+
355
+ /**
356
+ * Initialize workflow
357
+ */
358
+ initializeWorkflow(depth: AnalysisDepthLevel = 'standard'): AnalyzeWorkflowState {
359
+ this.setupDirectories();
360
+ this.state = {
361
+ ...DEFAULT_STATE,
362
+ startedAt: new Date().toISOString(),
363
+ lastUpdated: new Date().toISOString(),
364
+ depth,
365
+ phases: {},
366
+ results: {}
367
+ };
368
+
369
+ // Get phases for this depth
370
+ const depthConfig = ANALYSIS_DEPTH[depth];
371
+ const activePhases = depthConfig?.phases ?? ANALYSIS_DEPTH.standard.phases;
372
+
373
+ // Initialize phase states
374
+ for (const phaseId of Object.keys(ANALYZE_PHASES)) {
375
+ const phaseStatus: PhaseStatus = activePhases.includes(phaseId) ? 'pending' : 'skipped';
376
+ this.state.phases[phaseId] = {
377
+ status: phaseStatus,
378
+ startedAt: null,
379
+ completedAt: null,
380
+ result: null,
381
+ error: null
382
+ };
383
+ }
384
+
385
+ this.state.currentPhase = 'structure';
386
+ this.saveState();
387
+ return this.state;
388
+ }
389
+
390
+ /**
391
+ * Check if workflow exists
392
+ */
393
+ hasWorkflow(): boolean {
394
+ return fs.existsSync(this.stateFile);
395
+ }
396
+
397
+ /**
398
+ * Reset workflow
399
+ */
400
+ resetWorkflow(): boolean {
401
+ if (fs.existsSync(this.stateFile)) {
402
+ fs.unlinkSync(this.stateFile);
403
+ }
404
+ this.state = null;
405
+ return true;
406
+ }
407
+
408
+ /**
409
+ * Check if phase dependencies are met
410
+ */
411
+ arePhaseDependenciesMet(phaseId: string): boolean {
412
+ const phase = ANALYZE_PHASES[phaseId];
413
+ if (!phase || !phase.dependencies || phase.dependencies.length === 0) {
414
+ return true;
415
+ }
416
+
417
+ for (const depPhaseId of phase.dependencies) {
418
+ const depPhase = this.state?.phases[depPhaseId];
419
+ if (!depPhase || (depPhase.status !== 'completed' && depPhase.status !== 'skipped')) {
420
+ return false;
421
+ }
422
+ }
423
+
424
+ return true;
425
+ }
426
+
427
+ /**
428
+ * Get next phase
429
+ */
430
+ getNextPhase(): string | null {
431
+ if (!this.state) return null;
432
+
433
+ const phaseOrder = Object.keys(ANALYZE_PHASES).sort((a, b) => {
434
+ const phaseA = ANALYZE_PHASES[a];
435
+ const phaseB = ANALYZE_PHASES[b];
436
+ return (phaseA?.order ?? 0) - (phaseB?.order ?? 0);
437
+ });
438
+
439
+ for (const phaseId of phaseOrder) {
440
+ const phase = this.state.phases[phaseId];
441
+ if (phase?.status === 'pending' && this.arePhaseDependenciesMet(phaseId)) {
442
+ return phaseId;
443
+ }
444
+ }
445
+
446
+ return null;
447
+ }
448
+
449
+ /**
450
+ * Start a phase
451
+ */
452
+ startPhase(phaseId: string): void {
453
+ if (!this.state) {
454
+ throw new Error('Workflow not initialized');
455
+ }
456
+
457
+ const phase = this.state.phases[phaseId];
458
+ if (!phase) {
459
+ throw new Error(`Unknown phase: ${phaseId}`);
460
+ }
461
+
462
+ phase.status = 'in_progress';
463
+ phase.startedAt = new Date().toISOString();
464
+ this.state.currentPhase = phaseId;
465
+ this.saveState();
466
+ }
467
+
468
+ /**
469
+ * Complete a phase
470
+ */
471
+ completePhase(phaseId: string, result: unknown = null): void {
472
+ if (!this.state) {
473
+ throw new Error('Workflow not initialized');
474
+ }
475
+
476
+ const phase = this.state.phases[phaseId];
477
+ if (!phase) {
478
+ throw new Error(`Unknown phase: ${phaseId}`);
479
+ }
480
+
481
+ phase.status = 'completed';
482
+ phase.completedAt = new Date().toISOString();
483
+ phase.result = result;
484
+ this.state.results[phaseId] = result;
485
+ this.saveState();
486
+ }
487
+
488
+ /**
489
+ * Fail a phase
490
+ */
491
+ failPhase(phaseId: string, error: string): void {
492
+ if (!this.state) {
493
+ throw new Error('Workflow not initialized');
494
+ }
495
+
496
+ const phase = this.state.phases[phaseId];
497
+ if (!phase) {
498
+ throw new Error(`Unknown phase: ${phaseId}`);
499
+ }
500
+
501
+ phase.status = 'failed';
502
+ phase.completedAt = new Date().toISOString();
503
+ phase.error = error;
504
+ this.saveState();
505
+ }
506
+
507
+ /**
508
+ * Run structure mapping phase
509
+ */
510
+ async runStructureMapping(): Promise<StructureResult> {
511
+ const analyzer = new StructureAnalyzer(this.projectRoot, {
512
+ includeHidden: false
513
+ });
514
+
515
+ const result = analyzer.analyze();
516
+
517
+ // Save artifact
518
+ fs.writeFileSync(
519
+ path.join(this.artifactsDir, 'structure.json'),
520
+ JSON.stringify(result, null, 2)
521
+ );
522
+
523
+ const keyDirs = result.keyDirectories as Record<string, unknown> | undefined;
524
+ const keyDirectories = keyDirs
525
+ ? Object.keys(keyDirs).filter(k => keyDirs[k])
526
+ : [];
527
+
528
+ const stats = result.stats as Record<string, number> | undefined;
529
+ const entryPoints = result.entryPoints as unknown[] | undefined;
530
+
531
+ return {
532
+ totalFiles: stats?.totalFiles ?? 0,
533
+ totalDirs: stats?.totalDirs ?? 0,
534
+ structureType: result.structureType,
535
+ entryPoints: entryPoints?.length ?? 0,
536
+ keyDirectories
537
+ };
538
+ }
539
+
540
+ /**
541
+ * Run architecture analysis phase
542
+ */
543
+ async runArchitectureAnalysis(): Promise<ArchitectureResult> {
544
+ const analyzer = new ArchitectureAnalyzer(this.projectRoot);
545
+ const result = analyzer.analyze();
546
+
547
+ // Save artifact
548
+ fs.writeFileSync(
549
+ path.join(this.artifactsDir, 'architecture.json'),
550
+ JSON.stringify(result, null, 2)
551
+ );
552
+
553
+ // Save architecture report
554
+ const report = this.generateArchitectureReport(result);
555
+ fs.writeFileSync(
556
+ path.join(this.reportsDir, 'architecture.md'),
557
+ report
558
+ );
559
+
560
+ const patterns = result.patterns as unknown[] | undefined;
561
+ const layers = result.layers as Record<string, unknown> | undefined;
562
+ const modules = result.modules as unknown[] | undefined;
563
+ const componentStructure = result.componentStructure as Record<string, boolean> | undefined;
564
+
565
+ return {
566
+ patterns: (patterns ?? []).slice(0, 3),
567
+ layers: Object.keys(layers ?? {}),
568
+ modules: modules?.length ?? 0,
569
+ hasComponentSystem: componentStructure?.hasComponentDir ?? false
570
+ };
571
+ }
572
+
573
+ /**
574
+ * Generate architecture report
575
+ */
576
+ generateArchitectureReport(analysis: Record<string, unknown>): string {
577
+ const lines: string[] = [
578
+ '# Architecture Analysis',
579
+ '',
580
+ `Generated: ${new Date().toISOString()}`,
581
+ '',
582
+ '## Detected Patterns',
583
+ ''
584
+ ];
585
+
586
+ const patterns = analysis.patterns as Array<{
587
+ name: string;
588
+ confidence: number;
589
+ description?: string;
590
+ matchedIndicators: string[];
591
+ }> | undefined;
592
+
593
+ if (patterns) {
594
+ for (const pattern of patterns) {
595
+ lines.push(`### ${pattern.name} (${pattern.confidence}% confidence)`);
596
+ lines.push(`${pattern.description ?? ''}`);
597
+ lines.push(`Indicators: ${pattern.matchedIndicators.join(', ')}`);
598
+ lines.push('');
599
+ }
600
+ }
601
+
602
+ lines.push('## Layer Structure');
603
+ lines.push('');
604
+
605
+ const layers = analysis.layers as Record<string, Array<{ path: string; purpose: string }>> | undefined;
606
+ if (layers) {
607
+ for (const [layer, dirs] of Object.entries(layers)) {
608
+ if (dirs.length > 0) {
609
+ lines.push(`### ${layer.charAt(0).toUpperCase() + layer.slice(1)}`);
610
+ for (const dir of dirs) {
611
+ lines.push(`- \`${dir.path}\`: ${dir.purpose}`);
612
+ }
613
+ lines.push('');
614
+ }
615
+ }
616
+ }
617
+
618
+ const modules = analysis.modules as Array<{ name: string; path: string; type: string }> | undefined;
619
+ if (modules && modules.length > 0) {
620
+ lines.push('## Modules/Features');
621
+ lines.push('');
622
+ for (const mod of modules) {
623
+ lines.push(`- **${mod.name}** (\`${mod.path}\`): ${mod.type}`);
624
+ }
625
+ lines.push('');
626
+ }
627
+
628
+ lines.push('## Architecture Diagram');
629
+ lines.push('');
630
+ lines.push(String(analysis.diagram ?? ''));
631
+
632
+ return lines.join('\n');
633
+ }
634
+
635
+ /**
636
+ * Run dependency analysis phase
637
+ */
638
+ async runDependencyAnalysis(): Promise<DependencyResult> {
639
+ // Auto-detect large codebase and skip heavy analysis
640
+ const LARGE_CODEBASE_THRESHOLD = 500;
641
+ const structurePath = path.join(this.artifactsDir, 'structure.json');
642
+ let fileCount = 0;
643
+
644
+ if (fs.existsSync(structurePath)) {
645
+ try {
646
+ const structure = JSON.parse(fs.readFileSync(structurePath, 'utf-8')) as Record<string, unknown>;
647
+ const stats = structure.stats as Record<string, number> | undefined;
648
+ fileCount = stats?.totalFiles ?? 0;
649
+ } catch {
650
+ // Ignore
651
+ }
652
+ }
653
+
654
+ // For large codebases, return lightweight results
655
+ if (fileCount >= LARGE_CODEBASE_THRESHOLD) {
656
+ const lightResult = {
657
+ fileCount: fileCount,
658
+ circularDependencies: [],
659
+ unusedDependencies: [],
660
+ packageUsage: [],
661
+ mostImported: [],
662
+ skipped: true,
663
+ reason: `Large codebase (${fileCount} files) - dependency analysis skipped for performance`
664
+ };
665
+
666
+ // Save artifact with note
667
+ fs.writeFileSync(
668
+ path.join(this.artifactsDir, 'dependencies.json'),
669
+ JSON.stringify(lightResult, null, 2)
670
+ );
671
+
672
+ // Save lightweight report
673
+ const report = [
674
+ '# Dependency Analysis',
675
+ '',
676
+ `> Skipped: Large codebase (${fileCount} files)`,
677
+ '',
678
+ 'Dependency analysis was skipped for performance.',
679
+ 'Run `bootspring analyze --depth=deep` to force full analysis.',
680
+ ''
681
+ ].join('\n');
682
+
683
+ fs.writeFileSync(
684
+ path.join(this.reportsDir, 'dependencies.md'),
685
+ report
686
+ );
687
+
688
+ return {
689
+ fileCount: fileCount,
690
+ circularCount: 0,
691
+ unusedCount: 0,
692
+ externalPackages: 0,
693
+ mostImported: [],
694
+ skipped: true
695
+ };
696
+ }
697
+
698
+ const analyzer = new DependencyAnalyzer(this.projectRoot);
699
+ const result = analyzer.analyze();
700
+
701
+ // Save artifact
702
+ fs.writeFileSync(
703
+ path.join(this.artifactsDir, 'dependencies.json'),
704
+ JSON.stringify(result, null, 2)
705
+ );
706
+
707
+ // Save dependency report
708
+ const report = this.generateDependencyReport(result);
709
+ fs.writeFileSync(
710
+ path.join(this.reportsDir, 'dependencies.md'),
711
+ report
712
+ );
713
+
714
+ const circularDependencies = result.circularDependencies as unknown[] | undefined;
715
+ const unusedDependencies = result.unusedDependencies as unknown[] | undefined;
716
+ const packageUsage = result.packageUsage as unknown[] | undefined;
717
+ const mostImported = result.mostImported as unknown[] | undefined;
718
+
719
+ return {
720
+ fileCount: (result.fileCount as number | undefined) ?? 0,
721
+ circularCount: circularDependencies?.length ?? 0,
722
+ unusedCount: unusedDependencies?.length ?? 0,
723
+ externalPackages: packageUsage?.length ?? 0,
724
+ mostImported: (mostImported ?? []).slice(0, 5)
725
+ };
726
+ }
727
+
728
+ /**
729
+ * Generate dependency report
730
+ */
731
+ generateDependencyReport(analysis: Record<string, unknown>): string {
732
+ const lines: string[] = [
733
+ '# Dependency Analysis',
734
+ '',
735
+ `Generated: ${new Date().toISOString()}`,
736
+ '',
737
+ '## Summary',
738
+ '',
739
+ `- **Total Files Analyzed**: ${analysis.fileCount ?? 0}`,
740
+ `- **Circular Dependencies**: ${(analysis.circularDependencies as unknown[])?.length ?? 0}`,
741
+ `- **Unused Dependencies**: ${(analysis.unusedDependencies as unknown[])?.length ?? 0}`,
742
+ `- **External Packages**: ${(analysis.packageUsage as unknown[])?.length ?? 0}`,
743
+ ''
744
+ ];
745
+
746
+ const circularDependencies = analysis.circularDependencies as string[][] | undefined;
747
+ if (circularDependencies && circularDependencies.length > 0) {
748
+ lines.push('## Circular Dependencies');
749
+ lines.push('');
750
+ lines.push('> These should be resolved to improve maintainability.');
751
+ lines.push('');
752
+ for (let i = 0; i < Math.min(circularDependencies.length, 5); i++) {
753
+ const cycle = circularDependencies[i];
754
+ if (cycle) {
755
+ lines.push(`${i + 1}. ${cycle.join(' → ')}`);
756
+ }
757
+ }
758
+ lines.push('');
759
+ }
760
+
761
+ const unusedDependencies = analysis.unusedDependencies as Array<{ name: string; type: string }> | undefined;
762
+ if (unusedDependencies && unusedDependencies.length > 0) {
763
+ lines.push('## Potentially Unused Dependencies');
764
+ lines.push('');
765
+ lines.push('> Consider removing these to reduce bundle size.');
766
+ lines.push('');
767
+ for (const dep of unusedDependencies) {
768
+ lines.push(`- \`${dep.name}\` (${dep.type})`);
769
+ }
770
+ lines.push('');
771
+ }
772
+
773
+ lines.push('## Most Imported Files');
774
+ lines.push('');
775
+ lines.push('| File | Import Count |');
776
+ lines.push('|------|--------------|');
777
+ const mostImported = analysis.mostImported as Array<{ file: string; importCount: number }> | undefined;
778
+ if (mostImported) {
779
+ for (const file of mostImported.slice(0, 10)) {
780
+ lines.push(`| \`${file.file}\` | ${file.importCount} |`);
781
+ }
782
+ }
783
+ lines.push('');
784
+
785
+ lines.push('## Package Usage');
786
+ lines.push('');
787
+ lines.push('| Package | Usage Count |');
788
+ lines.push('|---------|-------------|');
789
+ const packageUsage = analysis.packageUsage as Array<{ package: string; count: number }> | undefined;
790
+ if (packageUsage) {
791
+ for (const pkg of packageUsage.slice(0, 15)) {
792
+ lines.push(`| \`${pkg.package}\` | ${pkg.count} |`);
793
+ }
794
+ }
795
+ lines.push('');
796
+
797
+ lines.push('## Dependency Graph');
798
+ lines.push('');
799
+ lines.push(String(analysis.diagram ?? ''));
800
+
801
+ return lines.join('\n');
802
+ }
803
+
804
+ /**
805
+ * Run pattern detection phase
806
+ */
807
+ async runPatternDetection(): Promise<PatternResult> {
808
+ // Load previous results
809
+ const architectureArtifact = path.join(this.artifactsDir, 'architecture.json');
810
+ const dependencyArtifact = path.join(this.artifactsDir, 'dependencies.json');
811
+
812
+ const patterns: {
813
+ designPatterns: unknown[];
814
+ antiPatterns: Array<{
815
+ id: string;
816
+ name: string;
817
+ severity: string;
818
+ count: number;
819
+ description: string;
820
+ }>;
821
+ codeSmells: unknown[];
822
+ } = {
823
+ designPatterns: [],
824
+ antiPatterns: [],
825
+ codeSmells: []
826
+ };
827
+
828
+ if (fs.existsSync(architectureArtifact)) {
829
+ const architecture = JSON.parse(fs.readFileSync(architectureArtifact, 'utf-8')) as Record<string, unknown>;
830
+ patterns.designPatterns = (architecture.patterns as unknown[]) ?? [];
831
+ }
832
+
833
+ if (fs.existsSync(dependencyArtifact)) {
834
+ const dependencies = JSON.parse(fs.readFileSync(dependencyArtifact, 'utf-8')) as Record<string, unknown>;
835
+
836
+ // Detect anti-patterns from dependencies
837
+ const circularDeps = dependencies.circularDependencies as unknown[] | undefined;
838
+ if (circularDeps && circularDeps.length > 0) {
839
+ patterns.antiPatterns.push({
840
+ id: 'circular-deps',
841
+ name: 'Circular Dependencies',
842
+ severity: 'medium',
843
+ count: circularDeps.length,
844
+ description: 'Files that depend on each other in a cycle'
845
+ });
846
+ }
847
+
848
+ // High coupling detection
849
+ const mostImported = dependencies.mostImported as Array<{ importCount: number }> | undefined;
850
+ const highCoupling = mostImported?.filter(f => f.importCount > 20);
851
+ if (highCoupling && highCoupling.length > 0) {
852
+ patterns.antiPatterns.push({
853
+ id: 'high-coupling',
854
+ name: 'High Coupling',
855
+ severity: 'low',
856
+ count: highCoupling.length,
857
+ description: 'Files with many dependents (potential god objects)'
858
+ });
859
+ }
860
+ }
861
+
862
+ // Save artifact
863
+ fs.writeFileSync(
864
+ path.join(this.artifactsDir, 'patterns.json'),
865
+ JSON.stringify(patterns, null, 2)
866
+ );
867
+
868
+ // Save patterns report
869
+ const report = this.generatePatternsReport(patterns);
870
+ fs.writeFileSync(
871
+ path.join(this.reportsDir, 'patterns.md'),
872
+ report
873
+ );
874
+
875
+ return {
876
+ designPatterns: patterns.designPatterns.length,
877
+ antiPatterns: patterns.antiPatterns.length,
878
+ codeSmells: patterns.codeSmells.length
879
+ };
880
+ }
881
+
882
+ /**
883
+ * Generate patterns report
884
+ */
885
+ generatePatternsReport(patterns: {
886
+ designPatterns: unknown[];
887
+ antiPatterns: Array<{ name: string; severity: string; count: number; description?: string }>;
888
+ codeSmells: unknown[];
889
+ }): string {
890
+ const lines: string[] = [
891
+ '# Pattern Analysis',
892
+ '',
893
+ `Generated: ${new Date().toISOString()}`,
894
+ ''
895
+ ];
896
+
897
+ if (patterns.designPatterns.length > 0) {
898
+ lines.push('## Design Patterns');
899
+ lines.push('');
900
+ for (const patternRaw of patterns.designPatterns) {
901
+ const pattern = patternRaw as { name: string; confidence: number; description?: string };
902
+ lines.push(`### ${pattern.name}`);
903
+ lines.push(`**Confidence**: ${pattern.confidence}%`);
904
+ lines.push(`${pattern.description ?? ''}`);
905
+ lines.push('');
906
+ }
907
+ }
908
+
909
+ if (patterns.antiPatterns.length > 0) {
910
+ lines.push('## Anti-Patterns Detected');
911
+ lines.push('');
912
+ for (const pattern of patterns.antiPatterns) {
913
+ lines.push(`### ${pattern.name}`);
914
+ lines.push(`**Severity**: ${pattern.severity}`);
915
+ lines.push(`**Count**: ${pattern.count}`);
916
+ lines.push(`${pattern.description ?? ''}`);
917
+ lines.push('');
918
+ }
919
+ }
920
+
921
+ if (patterns.designPatterns.length === 0 && patterns.antiPatterns.length === 0) {
922
+ lines.push('No significant patterns detected.');
923
+ lines.push('');
924
+ }
925
+
926
+ return lines.join('\n');
927
+ }
928
+
929
+ /**
930
+ * Run flow tracing phase
931
+ */
932
+ async runFlowTracing(): Promise<FlowResult> {
933
+ // Load structure artifact
934
+ const structureArtifact = path.join(this.artifactsDir, 'structure.json');
935
+
936
+ const flows: {
937
+ entryPoints: unknown[];
938
+ apiEndpoints: ApiEndpoint[];
939
+ pageRoutes: unknown[];
940
+ } = {
941
+ entryPoints: [],
942
+ apiEndpoints: [],
943
+ pageRoutes: []
944
+ };
945
+
946
+ if (fs.existsSync(structureArtifact)) {
947
+ const structure = JSON.parse(fs.readFileSync(structureArtifact, 'utf-8')) as Record<string, unknown>;
948
+ flows.entryPoints = (structure.entryPoints as unknown[]) ?? [];
949
+ }
950
+
951
+ // Detect API routes
952
+ const apiDirs = ['api', 'app/api', 'pages/api', 'src/api'];
953
+ for (const apiDir of apiDirs) {
954
+ const apiPath = path.join(this.projectRoot, apiDir);
955
+ if (fs.existsSync(apiPath)) {
956
+ flows.apiEndpoints = this.findApiEndpoints(apiPath);
957
+ break;
958
+ }
959
+ }
960
+
961
+ // Save artifact
962
+ fs.writeFileSync(
963
+ path.join(this.artifactsDir, 'flows.json'),
964
+ JSON.stringify(flows, null, 2)
965
+ );
966
+
967
+ return {
968
+ entryPoints: flows.entryPoints.length,
969
+ apiEndpoints: flows.apiEndpoints.length,
970
+ pageRoutes: flows.pageRoutes.length
971
+ };
972
+ }
973
+
974
+ /**
975
+ * Find API endpoints in a directory
976
+ */
977
+ findApiEndpoints(apiDir: string, basePath: string = '/api'): ApiEndpoint[] {
978
+ const endpoints: ApiEndpoint[] = [];
979
+
980
+ const walk = (dir: string, currentPath: string): void => {
981
+ try {
982
+ const items = fs.readdirSync(dir, { withFileTypes: true });
983
+
984
+ for (const item of items) {
985
+ const fullPath = path.join(dir, item.name);
986
+
987
+ if (item.isDirectory()) {
988
+ const routePath = item.name.startsWith('[')
989
+ ? `${currentPath}/:${item.name.replace(/[[\]]/g, '')}`
990
+ : `${currentPath}/${item.name}`;
991
+ walk(fullPath, routePath);
992
+ } else if (item.isFile() && /\.(js|ts)x?$/.test(item.name)) {
993
+ // Skip non-route files
994
+ if (item.name.startsWith('_')) continue;
995
+
996
+ // Determine route path
997
+ let routePath = currentPath;
998
+ if (item.name !== 'route.js' && item.name !== 'route.ts') {
999
+ const baseName = item.name.replace(/\.(js|ts)x?$/, '');
1000
+ if (baseName !== 'index') {
1001
+ routePath = `${currentPath}/${baseName}`;
1002
+ }
1003
+ }
1004
+
1005
+ // Read file to detect methods
1006
+ try {
1007
+ const content = fs.readFileSync(fullPath, 'utf-8');
1008
+ const methods: string[] = [];
1009
+ if (/export\s+(async\s+)?function\s+GET/i.test(content)) methods.push('GET');
1010
+ if (/export\s+(async\s+)?function\s+POST/i.test(content)) methods.push('POST');
1011
+ if (/export\s+(async\s+)?function\s+PUT/i.test(content)) methods.push('PUT');
1012
+ if (/export\s+(async\s+)?function\s+PATCH/i.test(content)) methods.push('PATCH');
1013
+ if (/export\s+(async\s+)?function\s+DELETE/i.test(content)) methods.push('DELETE');
1014
+
1015
+ endpoints.push({
1016
+ path: routePath,
1017
+ file: path.relative(this.projectRoot, fullPath),
1018
+ methods: methods.length > 0 ? methods : ['ALL']
1019
+ });
1020
+ } catch {
1021
+ // Skip unreadable files
1022
+ }
1023
+ }
1024
+ }
1025
+ } catch {
1026
+ // Skip inaccessible directories
1027
+ }
1028
+ };
1029
+
1030
+ walk(apiDir, basePath);
1031
+ return endpoints;
1032
+ }
1033
+
1034
+ /**
1035
+ * Run component analysis phase
1036
+ */
1037
+ async runComponentAnalysis(): Promise<ComponentResult> {
1038
+ // Load architecture artifact
1039
+ const architectureArtifact = path.join(this.artifactsDir, 'architecture.json');
1040
+
1041
+ const components: {
1042
+ total: number;
1043
+ categories: unknown[];
1044
+ propDrilling: unknown[];
1045
+ coupling: unknown[];
1046
+ } = {
1047
+ total: 0,
1048
+ categories: [],
1049
+ propDrilling: [],
1050
+ coupling: []
1051
+ };
1052
+
1053
+ if (fs.existsSync(architectureArtifact)) {
1054
+ const architecture = JSON.parse(fs.readFileSync(architectureArtifact, 'utf-8')) as Record<string, unknown>;
1055
+ const componentStructure = architecture.componentStructure as Record<string, unknown> | undefined;
1056
+ components.total = (componentStructure?.totalComponents as number) ?? 0;
1057
+ components.categories = (componentStructure?.categories as unknown[]) ?? [];
1058
+ }
1059
+
1060
+ // Save artifact
1061
+ fs.writeFileSync(
1062
+ path.join(this.artifactsDir, 'components.json'),
1063
+ JSON.stringify(components, null, 2)
1064
+ );
1065
+
1066
+ return {
1067
+ total: components.total,
1068
+ categories: components.categories.length
1069
+ };
1070
+ }
1071
+
1072
+ /**
1073
+ * Run report generation phase
1074
+ */
1075
+ async runReportGeneration(): Promise<ReportResult> {
1076
+ const report = this.generateFullReport();
1077
+
1078
+ // Save main report
1079
+ const reportPath = path.join(this.projectRoot, 'planning', 'CODEBASE_ANALYSIS.md');
1080
+ const planningDir = path.join(this.projectRoot, 'planning');
1081
+
1082
+ if (!fs.existsSync(planningDir)) {
1083
+ fs.mkdirSync(planningDir, { recursive: true });
1084
+ }
1085
+
1086
+ fs.writeFileSync(reportPath, report);
1087
+
1088
+ // Also save in workflow directory
1089
+ fs.writeFileSync(
1090
+ path.join(this.reportsDir, 'CODEBASE_ANALYSIS.md'),
1091
+ report
1092
+ );
1093
+
1094
+ if (this.state) {
1095
+ this.state.summary = {
1096
+ reportPath,
1097
+ generatedAt: new Date().toISOString()
1098
+ };
1099
+ }
1100
+
1101
+ return {
1102
+ reportPath,
1103
+ reportLength: report.length
1104
+ };
1105
+ }
1106
+
1107
+ /**
1108
+ * Generate full analysis report
1109
+ */
1110
+ generateFullReport(): string {
1111
+ const lines: string[] = [
1112
+ '# Codebase Analysis Report',
1113
+ '',
1114
+ '**Generated by**: Bootspring Analyze',
1115
+ `**Date**: ${new Date().toISOString().split('T')[0]}`,
1116
+ `**Analysis Depth**: ${this.state?.depth ?? 'standard'}`,
1117
+ ''
1118
+ ];
1119
+
1120
+ // Executive Summary
1121
+ lines.push('## Executive Summary');
1122
+ lines.push('');
1123
+
1124
+ const structure = this.loadArtifact('structure.json') as Record<string, unknown> | null;
1125
+ const architecture = this.loadArtifact('architecture.json') as Record<string, unknown> | null;
1126
+ const dependencies = this.loadArtifact('dependencies.json') as Record<string, unknown> | null;
1127
+
1128
+ if (structure) {
1129
+ const stats = structure.stats as Record<string, unknown> | undefined;
1130
+ const byCategory = stats?.byCategory as Record<string, number> | undefined;
1131
+ const structureType = structure.structureType as Record<string, unknown> | undefined;
1132
+ lines.push(`- **Total Files**: ${stats?.totalFiles ?? 0}`);
1133
+ lines.push(`- **Source Files**: ${byCategory?.source ?? 0}`);
1134
+ lines.push(`- **Test Files**: ${byCategory?.test ?? 0}`);
1135
+ lines.push(`- **Structure Type**: ${structureType?.pattern ?? 'unknown'}`);
1136
+ }
1137
+
1138
+ if (architecture) {
1139
+ const patterns = architecture.patterns as Array<{ name: string }> | undefined;
1140
+ const primaryPattern = patterns?.[0];
1141
+ const modules = architecture.modules as unknown[] | undefined;
1142
+ lines.push(`- **Primary Architecture**: ${primaryPattern?.name ?? 'Not determined'}`);
1143
+ lines.push(`- **Modules**: ${modules?.length ?? 0}`);
1144
+ }
1145
+
1146
+ if (dependencies) {
1147
+ const circularDeps = dependencies.circularDependencies as unknown[] | undefined;
1148
+ const packageUsage = dependencies.packageUsage as unknown[] | undefined;
1149
+ lines.push(`- **Circular Dependencies**: ${circularDeps?.length ?? 0}`);
1150
+ lines.push(`- **External Packages**: ${packageUsage?.length ?? 0}`);
1151
+ }
1152
+
1153
+ lines.push('');
1154
+
1155
+ // Health Score
1156
+ lines.push('## Health Score');
1157
+ lines.push('');
1158
+ const score = this.calculateHealthScore(structure, architecture, dependencies);
1159
+ lines.push(`**Overall**: ${score.overall}/100`);
1160
+ lines.push('');
1161
+ lines.push('| Category | Score |');
1162
+ lines.push('|----------|-------|');
1163
+ lines.push(`| Structure | ${score.structure}/100 |`);
1164
+ lines.push(`| Architecture | ${score.architecture}/100 |`);
1165
+ lines.push(`| Dependencies | ${score.dependencies}/100 |`);
1166
+ lines.push('');
1167
+
1168
+ // Architecture Overview
1169
+ if (architecture) {
1170
+ lines.push('## Architecture Overview');
1171
+ lines.push('');
1172
+ lines.push(String(architecture.diagram ?? ''));
1173
+ lines.push('');
1174
+
1175
+ const patterns = architecture.patterns as Array<{ name: string; confidence: number; description: string }> | undefined;
1176
+ if (patterns && patterns.length > 0) {
1177
+ lines.push('### Detected Patterns');
1178
+ lines.push('');
1179
+ for (const pattern of patterns.slice(0, 3)) {
1180
+ lines.push(`- **${pattern.name}** (${pattern.confidence}%): ${pattern.description}`);
1181
+ }
1182
+ lines.push('');
1183
+ }
1184
+ }
1185
+
1186
+ // Entry Points
1187
+ const entryPoints = structure?.entryPoints as Array<{ path: string; type: string }> | undefined;
1188
+ if (entryPoints && entryPoints.length > 0) {
1189
+ lines.push('## Entry Points');
1190
+ lines.push('');
1191
+ lines.push('| Path | Type |');
1192
+ lines.push('|------|------|');
1193
+ for (const entry of entryPoints.slice(0, 10)) {
1194
+ lines.push(`| \`${entry.path}\` | ${entry.type} |`);
1195
+ }
1196
+ lines.push('');
1197
+ }
1198
+
1199
+ // Dependencies
1200
+ if (dependencies) {
1201
+ lines.push('## Dependency Overview');
1202
+ lines.push('');
1203
+
1204
+ const circularDeps = dependencies.circularDependencies as string[][] | undefined;
1205
+ if (circularDeps && circularDeps.length > 0) {
1206
+ lines.push('### Circular Dependencies');
1207
+ lines.push('');
1208
+ lines.push('> These create tight coupling and should be resolved.');
1209
+ lines.push('');
1210
+ for (const cycle of circularDeps.slice(0, 5)) {
1211
+ lines.push(`- ${cycle.join(' → ')}`);
1212
+ }
1213
+ lines.push('');
1214
+ }
1215
+
1216
+ const mostImported = dependencies.mostImported as Array<{ file: string; importCount: number }> | undefined;
1217
+ if (mostImported && mostImported.length > 0) {
1218
+ lines.push('### Most Imported Files');
1219
+ lines.push('');
1220
+ lines.push('| File | Imported By |');
1221
+ lines.push('|------|-------------|');
1222
+ for (const file of mostImported.slice(0, 10)) {
1223
+ lines.push(`| \`${file.file}\` | ${file.importCount} files |`);
1224
+ }
1225
+ lines.push('');
1226
+ }
1227
+ }
1228
+
1229
+ // Recommendations
1230
+ lines.push('## Recommendations');
1231
+ lines.push('');
1232
+ const recommendations = this.generateRecommendations(structure, architecture, dependencies);
1233
+ for (let i = 0; i < recommendations.length; i++) {
1234
+ const rec = recommendations[i];
1235
+ if (rec) {
1236
+ lines.push(`${i + 1}. **${rec.title}**: ${rec.description}`);
1237
+ }
1238
+ }
1239
+ lines.push('');
1240
+
1241
+ lines.push('---');
1242
+ lines.push('');
1243
+ lines.push('*Generated by [Bootspring](https://bootspring.com)*');
1244
+
1245
+ return lines.join('\n');
1246
+ }
1247
+
1248
+ /**
1249
+ * Load artifact file
1250
+ */
1251
+ loadArtifact(filename: string): unknown | null {
1252
+ const filePath = path.join(this.artifactsDir, filename);
1253
+ if (fs.existsSync(filePath)) {
1254
+ try {
1255
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1256
+ } catch {
1257
+ return null;
1258
+ }
1259
+ }
1260
+ return null;
1261
+ }
1262
+
1263
+ /**
1264
+ * Calculate health score
1265
+ */
1266
+ calculateHealthScore(
1267
+ structure: Record<string, unknown> | null,
1268
+ architecture: Record<string, unknown> | null,
1269
+ dependencies: Record<string, unknown> | null
1270
+ ): HealthScore {
1271
+ let structureScore = 100;
1272
+ let architectureScore = 100;
1273
+ let dependenciesScore = 100;
1274
+
1275
+ // Structure scoring
1276
+ if (structure) {
1277
+ const stats = structure.stats as Record<string, unknown> | undefined;
1278
+ const byCategory = stats?.byCategory as Record<string, number> | undefined;
1279
+ const sourceFiles = byCategory?.source ?? 0;
1280
+ const testFiles = byCategory?.test ?? 0;
1281
+ const testRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
1282
+
1283
+ if (testRatio < 0.1) structureScore -= 20;
1284
+ else if (testRatio < 0.3) structureScore -= 10;
1285
+
1286
+ const structureType = structure.structureType as Record<string, boolean> | undefined;
1287
+ if (!structureType?.hasTypescript) structureScore -= 10;
1288
+ }
1289
+
1290
+ // Architecture scoring
1291
+ if (architecture) {
1292
+ const patterns = architecture.patterns as unknown[] | undefined;
1293
+ const modules = architecture.modules as unknown[] | undefined;
1294
+ if (!patterns || patterns.length === 0) architectureScore -= 20;
1295
+ if (!modules || modules.length === 0) architectureScore -= 10;
1296
+ }
1297
+
1298
+ // Dependencies scoring
1299
+ if (dependencies) {
1300
+ const circularDeps = dependencies.circularDependencies as unknown[] | undefined;
1301
+ const circularCount = circularDeps?.length ?? 0;
1302
+ if (circularCount > 10) dependenciesScore -= 30;
1303
+ else if (circularCount > 5) dependenciesScore -= 20;
1304
+ else if (circularCount > 0) dependenciesScore -= 10;
1305
+
1306
+ const unusedDeps = dependencies.unusedDependencies as unknown[] | undefined;
1307
+ const unusedCount = unusedDeps?.length ?? 0;
1308
+ if (unusedCount > 10) dependenciesScore -= 10;
1309
+ }
1310
+
1311
+ // Ensure scores are within bounds
1312
+ structureScore = Math.max(0, Math.min(100, structureScore));
1313
+ architectureScore = Math.max(0, Math.min(100, architectureScore));
1314
+ dependenciesScore = Math.max(0, Math.min(100, dependenciesScore));
1315
+
1316
+ return {
1317
+ overall: Math.round(
1318
+ structureScore * 0.3 +
1319
+ architectureScore * 0.3 +
1320
+ dependenciesScore * 0.4
1321
+ ),
1322
+ structure: structureScore,
1323
+ architecture: architectureScore,
1324
+ dependencies: dependenciesScore
1325
+ };
1326
+ }
1327
+
1328
+ /**
1329
+ * Generate recommendations
1330
+ */
1331
+ generateRecommendations(
1332
+ structure: Record<string, unknown> | null,
1333
+ _architecture: Record<string, unknown> | null,
1334
+ dependencies: Record<string, unknown> | null
1335
+ ): Recommendation[] {
1336
+ const recommendations: Recommendation[] = [];
1337
+
1338
+ const circularDeps = dependencies?.circularDependencies as unknown[] | undefined;
1339
+ if (circularDeps && circularDeps.length > 0) {
1340
+ recommendations.push({
1341
+ title: 'Resolve Circular Dependencies',
1342
+ description: `Found ${circularDeps.length} circular dependency chains that increase coupling.`
1343
+ });
1344
+ }
1345
+
1346
+ const unusedDeps = dependencies?.unusedDependencies as unknown[] | undefined;
1347
+ if (unusedDeps && unusedDeps.length > 5) {
1348
+ recommendations.push({
1349
+ title: 'Clean Up Unused Dependencies',
1350
+ description: `Found ${unusedDeps.length} potentially unused packages. Consider removing them.`
1351
+ });
1352
+ }
1353
+
1354
+ const structureType = structure?.structureType as Record<string, boolean> | undefined;
1355
+ if (structure && !structureType?.hasTypescript) {
1356
+ recommendations.push({
1357
+ title: 'Consider TypeScript',
1358
+ description: 'TypeScript provides better type safety and developer experience.'
1359
+ });
1360
+ }
1361
+
1362
+ const stats = structure?.stats as Record<string, unknown> | undefined;
1363
+ const byCategory = stats?.byCategory as Record<string, number> | undefined;
1364
+ const testFiles = byCategory?.test ?? 0;
1365
+ const sourceFiles = byCategory?.source ?? 1;
1366
+ const testRatio = structure ? testFiles / sourceFiles : 0;
1367
+ if (testRatio < 0.2) {
1368
+ recommendations.push({
1369
+ title: 'Increase Test Coverage',
1370
+ description: 'Current test file ratio is low. Add more tests to improve reliability.'
1371
+ });
1372
+ }
1373
+
1374
+ if (recommendations.length === 0) {
1375
+ recommendations.push({
1376
+ title: 'Codebase Looks Good',
1377
+ description: 'No major issues detected. Continue following best practices.'
1378
+ });
1379
+ }
1380
+
1381
+ return recommendations;
1382
+ }
1383
+
1384
+ /**
1385
+ * Get workflow progress
1386
+ */
1387
+ getProgress(): WorkflowProgress | null {
1388
+ if (!this.state) return null;
1389
+
1390
+ const phases: PhaseProgress[] = Object.entries(ANALYZE_PHASES).map(([phaseId, phase]) => {
1391
+ const phaseState = this.state?.phases[phaseId];
1392
+ const status: PhaseStatus = phaseState?.status ?? 'pending';
1393
+
1394
+ return {
1395
+ id: phaseId,
1396
+ name: phase.name,
1397
+ description: phase.description,
1398
+ order: phase.order,
1399
+ required: phase.required,
1400
+ status,
1401
+ dependenciesMet: this.arePhaseDependenciesMet(phaseId)
1402
+ };
1403
+ });
1404
+
1405
+ const completedCount = phases.filter(p => p.status === 'completed').length;
1406
+ const activeCount = phases.filter(p => p.status !== 'skipped').length;
1407
+
1408
+ return {
1409
+ currentPhase: this.state.currentPhase,
1410
+ depth: this.state.depth,
1411
+ startedAt: this.state.startedAt,
1412
+ lastUpdated: this.state.lastUpdated,
1413
+ phases,
1414
+ overall: {
1415
+ completed: completedCount,
1416
+ total: activeCount,
1417
+ percentage: activeCount > 0 ? Math.round((completedCount / activeCount) * 100) : 0
1418
+ },
1419
+ isComplete: completedCount === activeCount,
1420
+ summary: this.state.summary
1421
+ };
1422
+ }
1423
+
1424
+ /**
1425
+ * Get resume point
1426
+ */
1427
+ getResumePoint(): ResumePoint | null {
1428
+ if (!this.state || !this.state.currentPhase) {
1429
+ return null;
1430
+ }
1431
+
1432
+ const phase = ANALYZE_PHASES[this.state.currentPhase];
1433
+ const phaseState = this.state.phases[this.state.currentPhase];
1434
+
1435
+ return {
1436
+ phase: this.state.currentPhase,
1437
+ phaseName: phase?.name,
1438
+ phaseStatus: phaseState?.status,
1439
+ lastUpdated: this.state.lastUpdated
1440
+ };
1441
+ }
1442
+ }
1443
+
1444
+ // ============================================================================
1445
+ // Factory Function
1446
+ // ============================================================================
1447
+
1448
+ export function createAnalyzeWorkflowEngine(
1449
+ projectRoot: string,
1450
+ options: AnalyzeWorkflowOptions = {}
1451
+ ): AnalyzeWorkflowEngine {
1452
+ return new AnalyzeWorkflowEngine(projectRoot, options);
1453
+ }