@girardmedia/bootspring 1.2.0 → 2.0.3
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.
- package/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,1134 @@
|
|
|
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
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { StructureAnalyzer } = require('../analyzers/structure-analyzer');
|
|
14
|
+
const { ArchitectureAnalyzer } = require('../analyzers/architecture-analyzer');
|
|
15
|
+
const { DependencyAnalyzer } = require('../analyzers/dependency-analyzer');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Workflow phase status
|
|
19
|
+
*/
|
|
20
|
+
const PHASE_STATUS = {
|
|
21
|
+
PENDING: 'pending',
|
|
22
|
+
IN_PROGRESS: 'in_progress',
|
|
23
|
+
COMPLETED: 'completed',
|
|
24
|
+
SKIPPED: 'skipped',
|
|
25
|
+
FAILED: 'failed'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Analysis phases
|
|
30
|
+
*/
|
|
31
|
+
const ANALYZE_PHASES = {
|
|
32
|
+
structure: {
|
|
33
|
+
name: 'Structure Mapping',
|
|
34
|
+
description: 'Parse files, build tree, detect monorepo',
|
|
35
|
+
order: 1,
|
|
36
|
+
required: true
|
|
37
|
+
},
|
|
38
|
+
architecture: {
|
|
39
|
+
name: 'Architecture Analysis',
|
|
40
|
+
description: 'Identify layers, modules, patterns (MVC, etc.)',
|
|
41
|
+
order: 2,
|
|
42
|
+
required: true,
|
|
43
|
+
dependencies: ['structure']
|
|
44
|
+
},
|
|
45
|
+
dependencies: {
|
|
46
|
+
name: 'Dependency Analysis',
|
|
47
|
+
description: 'Import graph, circular deps, unused deps',
|
|
48
|
+
order: 3,
|
|
49
|
+
required: true,
|
|
50
|
+
dependencies: ['structure']
|
|
51
|
+
},
|
|
52
|
+
patterns: {
|
|
53
|
+
name: 'Pattern Detection',
|
|
54
|
+
description: 'Design patterns, anti-patterns, code smells',
|
|
55
|
+
order: 4,
|
|
56
|
+
required: true,
|
|
57
|
+
dependencies: ['structure', 'architecture']
|
|
58
|
+
},
|
|
59
|
+
flows: {
|
|
60
|
+
name: 'Flow Tracing',
|
|
61
|
+
description: 'Entry points, request flows, data flows',
|
|
62
|
+
order: 5,
|
|
63
|
+
required: false,
|
|
64
|
+
dependencies: ['structure', 'dependencies']
|
|
65
|
+
},
|
|
66
|
+
components: {
|
|
67
|
+
name: 'Component Analysis',
|
|
68
|
+
description: 'Component signatures, prop drilling, coupling',
|
|
69
|
+
order: 6,
|
|
70
|
+
required: false,
|
|
71
|
+
dependencies: ['structure', 'architecture']
|
|
72
|
+
},
|
|
73
|
+
report: {
|
|
74
|
+
name: 'Report Generation',
|
|
75
|
+
description: 'Executive summary, Mermaid diagrams',
|
|
76
|
+
order: 7,
|
|
77
|
+
required: true,
|
|
78
|
+
dependencies: ['structure', 'architecture', 'dependencies', 'patterns']
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Analysis depth levels
|
|
84
|
+
*/
|
|
85
|
+
const ANALYSIS_DEPTH = {
|
|
86
|
+
shallow: {
|
|
87
|
+
phases: ['structure', 'architecture', 'report'],
|
|
88
|
+
description: 'Quick scan for basic understanding'
|
|
89
|
+
},
|
|
90
|
+
standard: {
|
|
91
|
+
phases: ['structure', 'architecture', 'dependencies', 'patterns', 'report'],
|
|
92
|
+
description: 'Standard analysis with patterns'
|
|
93
|
+
},
|
|
94
|
+
deep: {
|
|
95
|
+
phases: ['structure', 'architecture', 'dependencies', 'patterns', 'flows', 'components', 'report'],
|
|
96
|
+
description: 'Comprehensive analysis with flows'
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default workflow state
|
|
102
|
+
*/
|
|
103
|
+
const DEFAULT_STATE = {
|
|
104
|
+
version: '1.0.0',
|
|
105
|
+
startedAt: null,
|
|
106
|
+
lastUpdated: null,
|
|
107
|
+
currentPhase: null,
|
|
108
|
+
depth: 'standard',
|
|
109
|
+
phases: {},
|
|
110
|
+
results: {},
|
|
111
|
+
summary: null
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* AnalyzeWorkflowEngine - Manages analysis workflow
|
|
116
|
+
*/
|
|
117
|
+
class AnalyzeWorkflowEngine {
|
|
118
|
+
constructor(projectRoot, options = {}) {
|
|
119
|
+
this.projectRoot = projectRoot;
|
|
120
|
+
this.workflowDir = path.join(projectRoot, '.bootspring', 'analyze');
|
|
121
|
+
this.stateFile = path.join(this.workflowDir, 'workflow-state.json');
|
|
122
|
+
this.reportsDir = path.join(this.workflowDir, 'reports');
|
|
123
|
+
this.artifactsDir = path.join(this.workflowDir, 'artifacts');
|
|
124
|
+
this.options = {
|
|
125
|
+
depth: options.depth || 'standard',
|
|
126
|
+
include: options.include || null,
|
|
127
|
+
exclude: options.exclude || null,
|
|
128
|
+
...options
|
|
129
|
+
};
|
|
130
|
+
this.state = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Setup directories
|
|
135
|
+
*/
|
|
136
|
+
setupDirectories() {
|
|
137
|
+
const dirs = [this.workflowDir, this.reportsDir, this.artifactsDir];
|
|
138
|
+
|
|
139
|
+
for (const dir of dirs) {
|
|
140
|
+
if (!fs.existsSync(dir)) {
|
|
141
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Load workflow state
|
|
148
|
+
*/
|
|
149
|
+
loadState() {
|
|
150
|
+
if (fs.existsSync(this.stateFile)) {
|
|
151
|
+
try {
|
|
152
|
+
this.state = JSON.parse(fs.readFileSync(this.stateFile, 'utf-8'));
|
|
153
|
+
return true;
|
|
154
|
+
} catch (_e) {
|
|
155
|
+
this.state = { ...DEFAULT_STATE };
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this.state = { ...DEFAULT_STATE };
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Save workflow state
|
|
165
|
+
*/
|
|
166
|
+
saveState() {
|
|
167
|
+
this.setupDirectories();
|
|
168
|
+
this.state.lastUpdated = new Date().toISOString();
|
|
169
|
+
fs.writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Initialize workflow
|
|
174
|
+
*/
|
|
175
|
+
initializeWorkflow(depth = 'standard') {
|
|
176
|
+
this.setupDirectories();
|
|
177
|
+
this.state = {
|
|
178
|
+
...DEFAULT_STATE,
|
|
179
|
+
startedAt: new Date().toISOString(),
|
|
180
|
+
lastUpdated: new Date().toISOString(),
|
|
181
|
+
depth
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Get phases for this depth
|
|
185
|
+
const activePhases = ANALYSIS_DEPTH[depth]?.phases || ANALYSIS_DEPTH.standard.phases;
|
|
186
|
+
|
|
187
|
+
// Initialize phase states
|
|
188
|
+
for (const phaseId of Object.keys(ANALYZE_PHASES)) {
|
|
189
|
+
this.state.phases[phaseId] = {
|
|
190
|
+
status: activePhases.includes(phaseId) ? PHASE_STATUS.PENDING : PHASE_STATUS.SKIPPED,
|
|
191
|
+
startedAt: null,
|
|
192
|
+
completedAt: null,
|
|
193
|
+
result: null,
|
|
194
|
+
error: null
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.state.currentPhase = 'structure';
|
|
199
|
+
this.saveState();
|
|
200
|
+
return this.state;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if workflow exists
|
|
205
|
+
*/
|
|
206
|
+
hasWorkflow() {
|
|
207
|
+
return fs.existsSync(this.stateFile);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Reset workflow
|
|
212
|
+
*/
|
|
213
|
+
resetWorkflow() {
|
|
214
|
+
if (fs.existsSync(this.stateFile)) {
|
|
215
|
+
fs.unlinkSync(this.stateFile);
|
|
216
|
+
}
|
|
217
|
+
this.state = null;
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if phase dependencies are met
|
|
223
|
+
*/
|
|
224
|
+
arePhaseDependenciesMet(phaseId) {
|
|
225
|
+
const phase = ANALYZE_PHASES[phaseId];
|
|
226
|
+
if (!phase || !phase.dependencies || phase.dependencies.length === 0) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const depPhaseId of phase.dependencies) {
|
|
231
|
+
const depPhase = this.state.phases[depPhaseId];
|
|
232
|
+
if (!depPhase || (depPhase.status !== PHASE_STATUS.COMPLETED && depPhase.status !== PHASE_STATUS.SKIPPED)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get next phase
|
|
242
|
+
*/
|
|
243
|
+
getNextPhase() {
|
|
244
|
+
const phaseOrder = Object.keys(ANALYZE_PHASES).sort(
|
|
245
|
+
(a, b) => ANALYZE_PHASES[a].order - ANALYZE_PHASES[b].order
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
for (const phaseId of phaseOrder) {
|
|
249
|
+
const phase = this.state.phases[phaseId];
|
|
250
|
+
if (phase.status === PHASE_STATUS.PENDING && this.arePhaseDependenciesMet(phaseId)) {
|
|
251
|
+
return phaseId;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Start a phase
|
|
260
|
+
*/
|
|
261
|
+
startPhase(phaseId) {
|
|
262
|
+
if (!this.state.phases[phaseId]) {
|
|
263
|
+
throw new Error(`Unknown phase: ${phaseId}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.state.phases[phaseId].status = PHASE_STATUS.IN_PROGRESS;
|
|
267
|
+
this.state.phases[phaseId].startedAt = new Date().toISOString();
|
|
268
|
+
this.state.currentPhase = phaseId;
|
|
269
|
+
this.saveState();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Complete a phase
|
|
274
|
+
*/
|
|
275
|
+
completePhase(phaseId, result = null) {
|
|
276
|
+
if (!this.state.phases[phaseId]) {
|
|
277
|
+
throw new Error(`Unknown phase: ${phaseId}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.state.phases[phaseId].status = PHASE_STATUS.COMPLETED;
|
|
281
|
+
this.state.phases[phaseId].completedAt = new Date().toISOString();
|
|
282
|
+
this.state.phases[phaseId].result = result;
|
|
283
|
+
this.state.results[phaseId] = result;
|
|
284
|
+
this.saveState();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Fail a phase
|
|
289
|
+
*/
|
|
290
|
+
failPhase(phaseId, error) {
|
|
291
|
+
if (!this.state.phases[phaseId]) {
|
|
292
|
+
throw new Error(`Unknown phase: ${phaseId}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.state.phases[phaseId].status = PHASE_STATUS.FAILED;
|
|
296
|
+
this.state.phases[phaseId].completedAt = new Date().toISOString();
|
|
297
|
+
this.state.phases[phaseId].error = error;
|
|
298
|
+
this.saveState();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Run structure mapping phase
|
|
303
|
+
*/
|
|
304
|
+
async runStructureMapping() {
|
|
305
|
+
const analyzer = new StructureAnalyzer(this.projectRoot, {
|
|
306
|
+
includeHidden: false
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const result = analyzer.analyze();
|
|
310
|
+
|
|
311
|
+
// Save artifact
|
|
312
|
+
fs.writeFileSync(
|
|
313
|
+
path.join(this.artifactsDir, 'structure.json'),
|
|
314
|
+
JSON.stringify(result, null, 2)
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
totalFiles: result.stats.totalFiles,
|
|
319
|
+
totalDirs: result.stats.totalDirs,
|
|
320
|
+
structureType: result.structureType,
|
|
321
|
+
entryPoints: result.entryPoints.length,
|
|
322
|
+
keyDirectories: Object.keys(result.keyDirectories).filter(k => result.keyDirectories[k])
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Run architecture analysis phase
|
|
328
|
+
*/
|
|
329
|
+
async runArchitectureAnalysis() {
|
|
330
|
+
const analyzer = new ArchitectureAnalyzer(this.projectRoot);
|
|
331
|
+
const result = analyzer.analyze();
|
|
332
|
+
|
|
333
|
+
// Save artifact
|
|
334
|
+
fs.writeFileSync(
|
|
335
|
+
path.join(this.artifactsDir, 'architecture.json'),
|
|
336
|
+
JSON.stringify(result, null, 2)
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Save architecture report
|
|
340
|
+
const report = this.generateArchitectureReport(result);
|
|
341
|
+
fs.writeFileSync(
|
|
342
|
+
path.join(this.reportsDir, 'architecture.md'),
|
|
343
|
+
report
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
patterns: result.patterns.slice(0, 3),
|
|
348
|
+
layers: Object.keys(result.layers),
|
|
349
|
+
modules: result.modules.length,
|
|
350
|
+
hasComponentSystem: result.componentStructure.hasComponentDir
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Generate architecture report
|
|
356
|
+
*/
|
|
357
|
+
generateArchitectureReport(analysis) {
|
|
358
|
+
const lines = [
|
|
359
|
+
'# Architecture Analysis',
|
|
360
|
+
'',
|
|
361
|
+
`Generated: ${new Date().toISOString()}`,
|
|
362
|
+
'',
|
|
363
|
+
'## Detected Patterns',
|
|
364
|
+
''
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
for (const pattern of analysis.patterns) {
|
|
368
|
+
lines.push(`### ${pattern.name} (${pattern.confidence}% confidence)`);
|
|
369
|
+
lines.push(`${pattern.description}`);
|
|
370
|
+
lines.push(`Indicators: ${pattern.matchedIndicators.join(', ')}`);
|
|
371
|
+
lines.push('');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
lines.push('## Layer Structure');
|
|
375
|
+
lines.push('');
|
|
376
|
+
|
|
377
|
+
for (const [layer, dirs] of Object.entries(analysis.layers)) {
|
|
378
|
+
if (dirs.length > 0) {
|
|
379
|
+
lines.push(`### ${layer.charAt(0).toUpperCase() + layer.slice(1)}`);
|
|
380
|
+
for (const dir of dirs) {
|
|
381
|
+
lines.push(`- \`${dir.path}\`: ${dir.purpose}`);
|
|
382
|
+
}
|
|
383
|
+
lines.push('');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (analysis.modules.length > 0) {
|
|
388
|
+
lines.push('## Modules/Features');
|
|
389
|
+
lines.push('');
|
|
390
|
+
for (const mod of analysis.modules) {
|
|
391
|
+
lines.push(`- **${mod.name}** (\`${mod.path}\`): ${mod.type}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push('');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
lines.push('## Architecture Diagram');
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push(analysis.diagram);
|
|
399
|
+
|
|
400
|
+
return lines.join('\n');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Run dependency analysis phase
|
|
405
|
+
*/
|
|
406
|
+
async runDependencyAnalysis() {
|
|
407
|
+
// Auto-detect large codebase and skip heavy analysis
|
|
408
|
+
const LARGE_CODEBASE_THRESHOLD = 500;
|
|
409
|
+
const structurePath = path.join(this.artifactsDir, 'structure.json');
|
|
410
|
+
let fileCount = 0;
|
|
411
|
+
|
|
412
|
+
if (fs.existsSync(structurePath)) {
|
|
413
|
+
try {
|
|
414
|
+
const structure = JSON.parse(fs.readFileSync(structurePath, 'utf-8'));
|
|
415
|
+
fileCount = structure.stats?.totalFiles || 0;
|
|
416
|
+
} catch (_e) {
|
|
417
|
+
// Ignore
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// For large codebases, return lightweight results
|
|
422
|
+
if (fileCount >= LARGE_CODEBASE_THRESHOLD) {
|
|
423
|
+
const lightResult = {
|
|
424
|
+
fileCount: fileCount,
|
|
425
|
+
circularDependencies: [],
|
|
426
|
+
unusedDependencies: [],
|
|
427
|
+
packageUsage: [],
|
|
428
|
+
mostImported: [],
|
|
429
|
+
skipped: true,
|
|
430
|
+
reason: `Large codebase (${fileCount} files) - dependency analysis skipped for performance`
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// Save artifact with note
|
|
434
|
+
fs.writeFileSync(
|
|
435
|
+
path.join(this.artifactsDir, 'dependencies.json'),
|
|
436
|
+
JSON.stringify(lightResult, null, 2)
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// Save lightweight report
|
|
440
|
+
const report = [
|
|
441
|
+
'# Dependency Analysis',
|
|
442
|
+
'',
|
|
443
|
+
`> Skipped: Large codebase (${fileCount} files)`,
|
|
444
|
+
'',
|
|
445
|
+
'Dependency analysis was skipped for performance.',
|
|
446
|
+
'Run `bootspring analyze --depth=deep` to force full analysis.',
|
|
447
|
+
''
|
|
448
|
+
].join('\n');
|
|
449
|
+
|
|
450
|
+
fs.writeFileSync(
|
|
451
|
+
path.join(this.reportsDir, 'dependencies.md'),
|
|
452
|
+
report
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
fileCount: fileCount,
|
|
457
|
+
circularCount: 0,
|
|
458
|
+
unusedCount: 0,
|
|
459
|
+
externalPackages: 0,
|
|
460
|
+
mostImported: [],
|
|
461
|
+
skipped: true
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const analyzer = new DependencyAnalyzer(this.projectRoot);
|
|
466
|
+
const result = analyzer.analyze();
|
|
467
|
+
|
|
468
|
+
// Save artifact
|
|
469
|
+
fs.writeFileSync(
|
|
470
|
+
path.join(this.artifactsDir, 'dependencies.json'),
|
|
471
|
+
JSON.stringify(result, null, 2)
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
// Save dependency report
|
|
475
|
+
const report = this.generateDependencyReport(result);
|
|
476
|
+
fs.writeFileSync(
|
|
477
|
+
path.join(this.reportsDir, 'dependencies.md'),
|
|
478
|
+
report
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
fileCount: result.fileCount,
|
|
483
|
+
circularCount: result.circularDependencies.length,
|
|
484
|
+
unusedCount: result.unusedDependencies.length,
|
|
485
|
+
externalPackages: result.packageUsage.length,
|
|
486
|
+
mostImported: result.mostImported.slice(0, 5)
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Generate dependency report
|
|
492
|
+
*/
|
|
493
|
+
generateDependencyReport(analysis) {
|
|
494
|
+
const lines = [
|
|
495
|
+
'# Dependency Analysis',
|
|
496
|
+
'',
|
|
497
|
+
`Generated: ${new Date().toISOString()}`,
|
|
498
|
+
'',
|
|
499
|
+
'## Summary',
|
|
500
|
+
'',
|
|
501
|
+
`- **Total Files Analyzed**: ${analysis.fileCount}`,
|
|
502
|
+
`- **Circular Dependencies**: ${analysis.circularDependencies.length}`,
|
|
503
|
+
`- **Unused Dependencies**: ${analysis.unusedDependencies.length}`,
|
|
504
|
+
`- **External Packages**: ${analysis.packageUsage.length}`,
|
|
505
|
+
''
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
if (analysis.circularDependencies.length > 0) {
|
|
509
|
+
lines.push('## Circular Dependencies');
|
|
510
|
+
lines.push('');
|
|
511
|
+
lines.push('> These should be resolved to improve maintainability.');
|
|
512
|
+
lines.push('');
|
|
513
|
+
for (let i = 0; i < Math.min(analysis.circularDependencies.length, 5); i++) {
|
|
514
|
+
const cycle = analysis.circularDependencies[i];
|
|
515
|
+
lines.push(`${i + 1}. ${cycle.join(' → ')}`);
|
|
516
|
+
}
|
|
517
|
+
lines.push('');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (analysis.unusedDependencies.length > 0) {
|
|
521
|
+
lines.push('## Potentially Unused Dependencies');
|
|
522
|
+
lines.push('');
|
|
523
|
+
lines.push('> Consider removing these to reduce bundle size.');
|
|
524
|
+
lines.push('');
|
|
525
|
+
for (const dep of analysis.unusedDependencies) {
|
|
526
|
+
lines.push(`- \`${dep.name}\` (${dep.type})`);
|
|
527
|
+
}
|
|
528
|
+
lines.push('');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
lines.push('## Most Imported Files');
|
|
532
|
+
lines.push('');
|
|
533
|
+
lines.push('| File | Import Count |');
|
|
534
|
+
lines.push('|------|--------------|');
|
|
535
|
+
for (const file of analysis.mostImported.slice(0, 10)) {
|
|
536
|
+
lines.push(`| \`${file.file}\` | ${file.importCount} |`);
|
|
537
|
+
}
|
|
538
|
+
lines.push('');
|
|
539
|
+
|
|
540
|
+
lines.push('## Package Usage');
|
|
541
|
+
lines.push('');
|
|
542
|
+
lines.push('| Package | Usage Count |');
|
|
543
|
+
lines.push('|---------|-------------|');
|
|
544
|
+
for (const pkg of analysis.packageUsage.slice(0, 15)) {
|
|
545
|
+
lines.push(`| \`${pkg.package}\` | ${pkg.count} |`);
|
|
546
|
+
}
|
|
547
|
+
lines.push('');
|
|
548
|
+
|
|
549
|
+
lines.push('## Dependency Graph');
|
|
550
|
+
lines.push('');
|
|
551
|
+
lines.push(analysis.diagram);
|
|
552
|
+
|
|
553
|
+
return lines.join('\n');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Run pattern detection phase
|
|
558
|
+
*/
|
|
559
|
+
async runPatternDetection() {
|
|
560
|
+
// Load previous results
|
|
561
|
+
const architectureArtifact = path.join(this.artifactsDir, 'architecture.json');
|
|
562
|
+
const dependencyArtifact = path.join(this.artifactsDir, 'dependencies.json');
|
|
563
|
+
|
|
564
|
+
const patterns = {
|
|
565
|
+
designPatterns: [],
|
|
566
|
+
antiPatterns: [],
|
|
567
|
+
codeSmells: []
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
if (fs.existsSync(architectureArtifact)) {
|
|
571
|
+
const architecture = JSON.parse(fs.readFileSync(architectureArtifact, 'utf-8'));
|
|
572
|
+
patterns.designPatterns = architecture.patterns || [];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (fs.existsSync(dependencyArtifact)) {
|
|
576
|
+
const dependencies = JSON.parse(fs.readFileSync(dependencyArtifact, 'utf-8'));
|
|
577
|
+
|
|
578
|
+
// Detect anti-patterns from dependencies
|
|
579
|
+
if (dependencies.circularDependencies?.length > 0) {
|
|
580
|
+
patterns.antiPatterns.push({
|
|
581
|
+
id: 'circular-deps',
|
|
582
|
+
name: 'Circular Dependencies',
|
|
583
|
+
severity: 'medium',
|
|
584
|
+
count: dependencies.circularDependencies.length,
|
|
585
|
+
description: 'Files that depend on each other in a cycle'
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// High coupling detection
|
|
590
|
+
const highCoupling = dependencies.mostImported?.filter(f => f.importCount > 20);
|
|
591
|
+
if (highCoupling?.length > 0) {
|
|
592
|
+
patterns.antiPatterns.push({
|
|
593
|
+
id: 'high-coupling',
|
|
594
|
+
name: 'High Coupling',
|
|
595
|
+
severity: 'low',
|
|
596
|
+
count: highCoupling.length,
|
|
597
|
+
description: 'Files with many dependents (potential god objects)'
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Save artifact
|
|
603
|
+
fs.writeFileSync(
|
|
604
|
+
path.join(this.artifactsDir, 'patterns.json'),
|
|
605
|
+
JSON.stringify(patterns, null, 2)
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
// Save patterns report
|
|
609
|
+
const report = this.generatePatternsReport(patterns);
|
|
610
|
+
fs.writeFileSync(
|
|
611
|
+
path.join(this.reportsDir, 'patterns.md'),
|
|
612
|
+
report
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
designPatterns: patterns.designPatterns.length,
|
|
617
|
+
antiPatterns: patterns.antiPatterns.length,
|
|
618
|
+
codeSmells: patterns.codeSmells.length
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Generate patterns report
|
|
624
|
+
*/
|
|
625
|
+
generatePatternsReport(patterns) {
|
|
626
|
+
const lines = [
|
|
627
|
+
'# Pattern Analysis',
|
|
628
|
+
'',
|
|
629
|
+
`Generated: ${new Date().toISOString()}`,
|
|
630
|
+
''
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
if (patterns.designPatterns.length > 0) {
|
|
634
|
+
lines.push('## Design Patterns');
|
|
635
|
+
lines.push('');
|
|
636
|
+
for (const pattern of patterns.designPatterns) {
|
|
637
|
+
lines.push(`### ${pattern.name}`);
|
|
638
|
+
lines.push(`**Confidence**: ${pattern.confidence}%`);
|
|
639
|
+
lines.push(`${pattern.description || ''}`);
|
|
640
|
+
lines.push('');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (patterns.antiPatterns.length > 0) {
|
|
645
|
+
lines.push('## Anti-Patterns Detected');
|
|
646
|
+
lines.push('');
|
|
647
|
+
for (const pattern of patterns.antiPatterns) {
|
|
648
|
+
lines.push(`### ${pattern.name}`);
|
|
649
|
+
lines.push(`**Severity**: ${pattern.severity}`);
|
|
650
|
+
lines.push(`**Count**: ${pattern.count}`);
|
|
651
|
+
lines.push(`${pattern.description || ''}`);
|
|
652
|
+
lines.push('');
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (patterns.designPatterns.length === 0 && patterns.antiPatterns.length === 0) {
|
|
657
|
+
lines.push('No significant patterns detected.');
|
|
658
|
+
lines.push('');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return lines.join('\n');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Run flow tracing phase
|
|
666
|
+
*/
|
|
667
|
+
async runFlowTracing() {
|
|
668
|
+
// Load structure artifact
|
|
669
|
+
const structureArtifact = path.join(this.artifactsDir, 'structure.json');
|
|
670
|
+
|
|
671
|
+
const flows = {
|
|
672
|
+
entryPoints: [],
|
|
673
|
+
apiEndpoints: [],
|
|
674
|
+
pageRoutes: []
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (fs.existsSync(structureArtifact)) {
|
|
678
|
+
const structure = JSON.parse(fs.readFileSync(structureArtifact, 'utf-8'));
|
|
679
|
+
flows.entryPoints = structure.entryPoints || [];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Detect API routes
|
|
683
|
+
const apiDirs = ['api', 'app/api', 'pages/api', 'src/api'];
|
|
684
|
+
for (const apiDir of apiDirs) {
|
|
685
|
+
const apiPath = path.join(this.projectRoot, apiDir);
|
|
686
|
+
if (fs.existsSync(apiPath)) {
|
|
687
|
+
flows.apiEndpoints = this.findApiEndpoints(apiPath);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Save artifact
|
|
693
|
+
fs.writeFileSync(
|
|
694
|
+
path.join(this.artifactsDir, 'flows.json'),
|
|
695
|
+
JSON.stringify(flows, null, 2)
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
entryPoints: flows.entryPoints.length,
|
|
700
|
+
apiEndpoints: flows.apiEndpoints.length,
|
|
701
|
+
pageRoutes: flows.pageRoutes.length
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Find API endpoints in a directory
|
|
707
|
+
*/
|
|
708
|
+
findApiEndpoints(apiDir, basePath = '/api') {
|
|
709
|
+
const endpoints = [];
|
|
710
|
+
|
|
711
|
+
const walk = (dir, currentPath) => {
|
|
712
|
+
try {
|
|
713
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
714
|
+
|
|
715
|
+
for (const item of items) {
|
|
716
|
+
const fullPath = path.join(dir, item.name);
|
|
717
|
+
|
|
718
|
+
if (item.isDirectory()) {
|
|
719
|
+
const routePath = item.name.startsWith('[')
|
|
720
|
+
? `${currentPath}/:${item.name.replace(/[[\]]/g, '')}`
|
|
721
|
+
: `${currentPath}/${item.name}`;
|
|
722
|
+
walk(fullPath, routePath);
|
|
723
|
+
} else if (item.isFile() && /\.(js|ts)x?$/.test(item.name)) {
|
|
724
|
+
// Skip non-route files
|
|
725
|
+
if (item.name.startsWith('_')) continue;
|
|
726
|
+
|
|
727
|
+
// Determine route path
|
|
728
|
+
let routePath = currentPath;
|
|
729
|
+
if (item.name !== 'route.js' && item.name !== 'route.ts') {
|
|
730
|
+
const baseName = item.name.replace(/\.(js|ts)x?$/, '');
|
|
731
|
+
if (baseName !== 'index') {
|
|
732
|
+
routePath = `${currentPath}/${baseName}`;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Read file to detect methods
|
|
737
|
+
try {
|
|
738
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
739
|
+
const methods = [];
|
|
740
|
+
if (/export\s+(async\s+)?function\s+GET/i.test(content)) methods.push('GET');
|
|
741
|
+
if (/export\s+(async\s+)?function\s+POST/i.test(content)) methods.push('POST');
|
|
742
|
+
if (/export\s+(async\s+)?function\s+PUT/i.test(content)) methods.push('PUT');
|
|
743
|
+
if (/export\s+(async\s+)?function\s+PATCH/i.test(content)) methods.push('PATCH');
|
|
744
|
+
if (/export\s+(async\s+)?function\s+DELETE/i.test(content)) methods.push('DELETE');
|
|
745
|
+
|
|
746
|
+
endpoints.push({
|
|
747
|
+
path: routePath,
|
|
748
|
+
file: path.relative(this.projectRoot, fullPath),
|
|
749
|
+
methods: methods.length > 0 ? methods : ['ALL']
|
|
750
|
+
});
|
|
751
|
+
} catch (_e) {
|
|
752
|
+
// Skip unreadable files
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} catch (_e) {
|
|
757
|
+
// Skip inaccessible directories
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
walk(apiDir, basePath);
|
|
762
|
+
return endpoints;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Run component analysis phase
|
|
767
|
+
*/
|
|
768
|
+
async runComponentAnalysis() {
|
|
769
|
+
// Load architecture artifact
|
|
770
|
+
const architectureArtifact = path.join(this.artifactsDir, 'architecture.json');
|
|
771
|
+
|
|
772
|
+
const components = {
|
|
773
|
+
total: 0,
|
|
774
|
+
categories: [],
|
|
775
|
+
propDrilling: [],
|
|
776
|
+
coupling: []
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
if (fs.existsSync(architectureArtifact)) {
|
|
780
|
+
const architecture = JSON.parse(fs.readFileSync(architectureArtifact, 'utf-8'));
|
|
781
|
+
components.total = architecture.componentStructure?.totalComponents || 0;
|
|
782
|
+
components.categories = architecture.componentStructure?.categories || [];
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Save artifact
|
|
786
|
+
fs.writeFileSync(
|
|
787
|
+
path.join(this.artifactsDir, 'components.json'),
|
|
788
|
+
JSON.stringify(components, null, 2)
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
return {
|
|
792
|
+
total: components.total,
|
|
793
|
+
categories: components.categories.length
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Run report generation phase
|
|
799
|
+
*/
|
|
800
|
+
async runReportGeneration() {
|
|
801
|
+
const report = this.generateFullReport();
|
|
802
|
+
|
|
803
|
+
// Save main report
|
|
804
|
+
const reportPath = path.join(this.projectRoot, 'planning', 'CODEBASE_ANALYSIS.md');
|
|
805
|
+
const planningDir = path.join(this.projectRoot, 'planning');
|
|
806
|
+
|
|
807
|
+
if (!fs.existsSync(planningDir)) {
|
|
808
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
fs.writeFileSync(reportPath, report);
|
|
812
|
+
|
|
813
|
+
// Also save in workflow directory
|
|
814
|
+
fs.writeFileSync(
|
|
815
|
+
path.join(this.reportsDir, 'CODEBASE_ANALYSIS.md'),
|
|
816
|
+
report
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
this.state.summary = {
|
|
820
|
+
reportPath,
|
|
821
|
+
generatedAt: new Date().toISOString()
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
reportPath,
|
|
826
|
+
reportLength: report.length
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Generate full analysis report
|
|
832
|
+
*/
|
|
833
|
+
generateFullReport() {
|
|
834
|
+
const lines = [
|
|
835
|
+
'# Codebase Analysis Report',
|
|
836
|
+
'',
|
|
837
|
+
'**Generated by**: Bootspring Analyze',
|
|
838
|
+
`**Date**: ${new Date().toISOString().split('T')[0]}`,
|
|
839
|
+
`**Analysis Depth**: ${this.state.depth}`,
|
|
840
|
+
''
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
// Executive Summary
|
|
844
|
+
lines.push('## Executive Summary');
|
|
845
|
+
lines.push('');
|
|
846
|
+
|
|
847
|
+
const structure = this.loadArtifact('structure.json');
|
|
848
|
+
const architecture = this.loadArtifact('architecture.json');
|
|
849
|
+
const dependencies = this.loadArtifact('dependencies.json');
|
|
850
|
+
|
|
851
|
+
if (structure) {
|
|
852
|
+
lines.push(`- **Total Files**: ${structure.stats?.totalFiles || 0}`);
|
|
853
|
+
lines.push(`- **Source Files**: ${structure.stats?.byCategory?.source || 0}`);
|
|
854
|
+
lines.push(`- **Test Files**: ${structure.stats?.byCategory?.test || 0}`);
|
|
855
|
+
lines.push(`- **Structure Type**: ${structure.structureType?.pattern || 'unknown'}`);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (architecture) {
|
|
859
|
+
const primaryPattern = architecture.patterns?.[0];
|
|
860
|
+
lines.push(`- **Primary Architecture**: ${primaryPattern?.name || 'Not determined'}`);
|
|
861
|
+
lines.push(`- **Modules**: ${architecture.modules?.length || 0}`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (dependencies) {
|
|
865
|
+
lines.push(`- **Circular Dependencies**: ${dependencies.circularDependencies?.length || 0}`);
|
|
866
|
+
lines.push(`- **External Packages**: ${dependencies.packageUsage?.length || 0}`);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
lines.push('');
|
|
870
|
+
|
|
871
|
+
// Health Score
|
|
872
|
+
lines.push('## Health Score');
|
|
873
|
+
lines.push('');
|
|
874
|
+
const score = this.calculateHealthScore(structure, architecture, dependencies);
|
|
875
|
+
lines.push(`**Overall**: ${score.overall}/100`);
|
|
876
|
+
lines.push('');
|
|
877
|
+
lines.push('| Category | Score |');
|
|
878
|
+
lines.push('|----------|-------|');
|
|
879
|
+
lines.push(`| Structure | ${score.structure}/100 |`);
|
|
880
|
+
lines.push(`| Architecture | ${score.architecture}/100 |`);
|
|
881
|
+
lines.push(`| Dependencies | ${score.dependencies}/100 |`);
|
|
882
|
+
lines.push('');
|
|
883
|
+
|
|
884
|
+
// Architecture Overview
|
|
885
|
+
if (architecture) {
|
|
886
|
+
lines.push('## Architecture Overview');
|
|
887
|
+
lines.push('');
|
|
888
|
+
lines.push(architecture.diagram || '');
|
|
889
|
+
lines.push('');
|
|
890
|
+
|
|
891
|
+
if (architecture.patterns?.length > 0) {
|
|
892
|
+
lines.push('### Detected Patterns');
|
|
893
|
+
lines.push('');
|
|
894
|
+
for (const pattern of architecture.patterns.slice(0, 3)) {
|
|
895
|
+
lines.push(`- **${pattern.name}** (${pattern.confidence}%): ${pattern.description}`);
|
|
896
|
+
}
|
|
897
|
+
lines.push('');
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Entry Points
|
|
902
|
+
if (structure?.entryPoints?.length > 0) {
|
|
903
|
+
lines.push('## Entry Points');
|
|
904
|
+
lines.push('');
|
|
905
|
+
lines.push('| Path | Type |');
|
|
906
|
+
lines.push('|------|------|');
|
|
907
|
+
for (const entry of structure.entryPoints.slice(0, 10)) {
|
|
908
|
+
lines.push(`| \`${entry.path}\` | ${entry.type} |`);
|
|
909
|
+
}
|
|
910
|
+
lines.push('');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Dependencies
|
|
914
|
+
if (dependencies) {
|
|
915
|
+
lines.push('## Dependency Overview');
|
|
916
|
+
lines.push('');
|
|
917
|
+
|
|
918
|
+
if (dependencies.circularDependencies?.length > 0) {
|
|
919
|
+
lines.push('### Circular Dependencies');
|
|
920
|
+
lines.push('');
|
|
921
|
+
lines.push('> These create tight coupling and should be resolved.');
|
|
922
|
+
lines.push('');
|
|
923
|
+
for (const cycle of dependencies.circularDependencies.slice(0, 5)) {
|
|
924
|
+
lines.push(`- ${cycle.join(' → ')}`);
|
|
925
|
+
}
|
|
926
|
+
lines.push('');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (dependencies.mostImported?.length > 0) {
|
|
930
|
+
lines.push('### Most Imported Files');
|
|
931
|
+
lines.push('');
|
|
932
|
+
lines.push('| File | Imported By |');
|
|
933
|
+
lines.push('|------|-------------|');
|
|
934
|
+
for (const file of dependencies.mostImported.slice(0, 10)) {
|
|
935
|
+
lines.push(`| \`${file.file}\` | ${file.importCount} files |`);
|
|
936
|
+
}
|
|
937
|
+
lines.push('');
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Recommendations
|
|
942
|
+
lines.push('## Recommendations');
|
|
943
|
+
lines.push('');
|
|
944
|
+
const recommendations = this.generateRecommendations(structure, architecture, dependencies);
|
|
945
|
+
for (let i = 0; i < recommendations.length; i++) {
|
|
946
|
+
lines.push(`${i + 1}. **${recommendations[i].title}**: ${recommendations[i].description}`);
|
|
947
|
+
}
|
|
948
|
+
lines.push('');
|
|
949
|
+
|
|
950
|
+
lines.push('---');
|
|
951
|
+
lines.push('');
|
|
952
|
+
lines.push('*Generated by [Bootspring](https://bootspring.com)*');
|
|
953
|
+
|
|
954
|
+
return lines.join('\n');
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Load artifact file
|
|
959
|
+
*/
|
|
960
|
+
loadArtifact(filename) {
|
|
961
|
+
const filePath = path.join(this.artifactsDir, filename);
|
|
962
|
+
if (fs.existsSync(filePath)) {
|
|
963
|
+
try {
|
|
964
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
965
|
+
} catch (_e) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Calculate health score
|
|
974
|
+
*/
|
|
975
|
+
calculateHealthScore(structure, architecture, dependencies) {
|
|
976
|
+
const scores = {
|
|
977
|
+
structure: 100,
|
|
978
|
+
architecture: 100,
|
|
979
|
+
dependencies: 100
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
// Structure scoring
|
|
983
|
+
if (structure) {
|
|
984
|
+
const sourceFiles = structure.stats?.byCategory?.source || 0;
|
|
985
|
+
const testFiles = structure.stats?.byCategory?.test || 0;
|
|
986
|
+
const testRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
|
|
987
|
+
|
|
988
|
+
if (testRatio < 0.1) scores.structure -= 20;
|
|
989
|
+
else if (testRatio < 0.3) scores.structure -= 10;
|
|
990
|
+
|
|
991
|
+
if (!structure.structureType?.hasTypescript) scores.structure -= 10;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Architecture scoring
|
|
995
|
+
if (architecture) {
|
|
996
|
+
if (architecture.patterns?.length === 0) scores.architecture -= 20;
|
|
997
|
+
if (architecture.modules?.length === 0) scores.architecture -= 10;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Dependencies scoring
|
|
1001
|
+
if (dependencies) {
|
|
1002
|
+
const circularCount = dependencies.circularDependencies?.length || 0;
|
|
1003
|
+
if (circularCount > 10) scores.dependencies -= 30;
|
|
1004
|
+
else if (circularCount > 5) scores.dependencies -= 20;
|
|
1005
|
+
else if (circularCount > 0) scores.dependencies -= 10;
|
|
1006
|
+
|
|
1007
|
+
const unusedCount = dependencies.unusedDependencies?.length || 0;
|
|
1008
|
+
if (unusedCount > 10) scores.dependencies -= 10;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Ensure scores are within bounds
|
|
1012
|
+
for (const key of Object.keys(scores)) {
|
|
1013
|
+
scores[key] = Math.max(0, Math.min(100, scores[key]));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
scores.overall = Math.round(
|
|
1017
|
+
scores.structure * 0.3 +
|
|
1018
|
+
scores.architecture * 0.3 +
|
|
1019
|
+
scores.dependencies * 0.4
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
return scores;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Generate recommendations
|
|
1027
|
+
*/
|
|
1028
|
+
generateRecommendations(structure, architecture, dependencies) {
|
|
1029
|
+
const recommendations = [];
|
|
1030
|
+
|
|
1031
|
+
if (dependencies?.circularDependencies?.length > 0) {
|
|
1032
|
+
recommendations.push({
|
|
1033
|
+
title: 'Resolve Circular Dependencies',
|
|
1034
|
+
description: `Found ${dependencies.circularDependencies.length} circular dependency chains that increase coupling.`
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (dependencies?.unusedDependencies?.length > 5) {
|
|
1039
|
+
recommendations.push({
|
|
1040
|
+
title: 'Clean Up Unused Dependencies',
|
|
1041
|
+
description: `Found ${dependencies.unusedDependencies.length} potentially unused packages. Consider removing them.`
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (structure && !structure.structureType?.hasTypescript) {
|
|
1046
|
+
recommendations.push({
|
|
1047
|
+
title: 'Consider TypeScript',
|
|
1048
|
+
description: 'TypeScript provides better type safety and developer experience.'
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const testRatio = structure ? (structure.stats?.byCategory?.test || 0) / (structure.stats?.byCategory?.source || 1) : 0;
|
|
1053
|
+
if (testRatio < 0.2) {
|
|
1054
|
+
recommendations.push({
|
|
1055
|
+
title: 'Increase Test Coverage',
|
|
1056
|
+
description: 'Current test file ratio is low. Add more tests to improve reliability.'
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (recommendations.length === 0) {
|
|
1061
|
+
recommendations.push({
|
|
1062
|
+
title: 'Codebase Looks Good',
|
|
1063
|
+
description: 'No major issues detected. Continue following best practices.'
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return recommendations;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Get workflow progress
|
|
1072
|
+
*/
|
|
1073
|
+
getProgress() {
|
|
1074
|
+
if (!this.state) return null;
|
|
1075
|
+
|
|
1076
|
+
const phases = Object.entries(ANALYZE_PHASES).map(([phaseId, phase]) => {
|
|
1077
|
+
const phaseState = this.state.phases[phaseId] || { status: PHASE_STATUS.PENDING };
|
|
1078
|
+
|
|
1079
|
+
return {
|
|
1080
|
+
id: phaseId,
|
|
1081
|
+
name: phase.name,
|
|
1082
|
+
description: phase.description,
|
|
1083
|
+
order: phase.order,
|
|
1084
|
+
required: phase.required,
|
|
1085
|
+
status: phaseState.status,
|
|
1086
|
+
dependenciesMet: this.arePhaseDependenciesMet(phaseId)
|
|
1087
|
+
};
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
const completedCount = phases.filter(p => p.status === PHASE_STATUS.COMPLETED).length;
|
|
1091
|
+
const activeCount = phases.filter(p => p.status !== PHASE_STATUS.SKIPPED).length;
|
|
1092
|
+
|
|
1093
|
+
return {
|
|
1094
|
+
currentPhase: this.state.currentPhase,
|
|
1095
|
+
depth: this.state.depth,
|
|
1096
|
+
startedAt: this.state.startedAt,
|
|
1097
|
+
lastUpdated: this.state.lastUpdated,
|
|
1098
|
+
phases,
|
|
1099
|
+
overall: {
|
|
1100
|
+
completed: completedCount,
|
|
1101
|
+
total: activeCount,
|
|
1102
|
+
percentage: Math.round((completedCount / activeCount) * 100)
|
|
1103
|
+
},
|
|
1104
|
+
isComplete: completedCount === activeCount,
|
|
1105
|
+
summary: this.state.summary
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Get resume point
|
|
1111
|
+
*/
|
|
1112
|
+
getResumePoint() {
|
|
1113
|
+
if (!this.state || !this.state.currentPhase) {
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const phase = ANALYZE_PHASES[this.state.currentPhase];
|
|
1118
|
+
const phaseState = this.state.phases[this.state.currentPhase];
|
|
1119
|
+
|
|
1120
|
+
return {
|
|
1121
|
+
phase: this.state.currentPhase,
|
|
1122
|
+
phaseName: phase?.name,
|
|
1123
|
+
phaseStatus: phaseState?.status,
|
|
1124
|
+
lastUpdated: this.state.lastUpdated
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
module.exports = {
|
|
1130
|
+
AnalyzeWorkflowEngine,
|
|
1131
|
+
ANALYZE_PHASES,
|
|
1132
|
+
PHASE_STATUS,
|
|
1133
|
+
ANALYSIS_DEPTH
|
|
1134
|
+
};
|