@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.
Files changed (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -0,0 +1,1350 @@
1
+ /**
2
+ * Bootspring Audit Workflow Engine
3
+ *
4
+ * Quality, security, and best practices audit for codebases.
5
+ * Generates prioritized recommendations and remediation tasks.
6
+ *
7
+ * @package bootspring
8
+ * @module core/audit-workflow
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { SecurityScanner, SEVERITY } = require('../analyzers/security-scanner');
14
+ const { QualityAnalyzer } = require('../analyzers/quality-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
+ * Audit phases
30
+ */
31
+ const AUDIT_PHASES = {
32
+ quality: {
33
+ name: 'Quality Metrics',
34
+ description: 'Complexity, duplication, comments, naming',
35
+ order: 1,
36
+ required: true
37
+ },
38
+ security: {
39
+ name: 'Security Scan',
40
+ description: 'Secrets, vulnerabilities, OWASP, injection',
41
+ order: 2,
42
+ required: true
43
+ },
44
+ performance: {
45
+ name: 'Performance Analysis',
46
+ description: 'Bundle size, lazy loading, N+1 queries',
47
+ order: 3,
48
+ required: false,
49
+ dependencies: ['quality']
50
+ },
51
+ practices: {
52
+ name: 'Best Practices',
53
+ description: 'TypeScript strict, error handling, env vars',
54
+ order: 4,
55
+ required: true
56
+ },
57
+ techDebt: {
58
+ name: 'Tech Debt Inventory',
59
+ description: 'TODOs, deprecated APIs, dead code, missing tests',
60
+ order: 5,
61
+ required: true,
62
+ dependencies: ['quality']
63
+ },
64
+ recommendations: {
65
+ name: 'Recommendations',
66
+ description: 'Prioritize (P0-P3), estimate effort, generate tasks',
67
+ order: 6,
68
+ required: true,
69
+ dependencies: ['quality', 'security', 'practices', 'techDebt']
70
+ }
71
+ };
72
+
73
+ /**
74
+ * Finding severity levels with actions
75
+ */
76
+ const SEVERITY_LEVELS = {
77
+ critical: {
78
+ label: 'CRITICAL',
79
+ color: 'red',
80
+ action: 'Immediate fix required',
81
+ priority: 0
82
+ },
83
+ high: {
84
+ label: 'HIGH',
85
+ color: 'orange',
86
+ action: 'Fix within 1-2 days',
87
+ priority: 1
88
+ },
89
+ medium: {
90
+ label: 'MEDIUM',
91
+ color: 'yellow',
92
+ action: 'Plan fix within 1-2 weeks',
93
+ priority: 2
94
+ },
95
+ low: {
96
+ label: 'LOW',
97
+ color: 'blue',
98
+ action: 'Add to backlog',
99
+ priority: 3
100
+ },
101
+ info: {
102
+ label: 'INFO',
103
+ color: 'gray',
104
+ action: 'Consider when convenient',
105
+ priority: 4
106
+ }
107
+ };
108
+
109
+ /**
110
+ * Default workflow state
111
+ */
112
+ const DEFAULT_STATE = {
113
+ version: '1.0.0',
114
+ startedAt: null,
115
+ lastUpdated: null,
116
+ currentPhase: null,
117
+ phases: {},
118
+ findings: [],
119
+ summary: null
120
+ };
121
+
122
+ /**
123
+ * AuditWorkflowEngine - Manages audit workflow
124
+ */
125
+ class AuditWorkflowEngine {
126
+ constructor(projectRoot, options = {}) {
127
+ this.projectRoot = projectRoot;
128
+ this.workflowDir = path.join(projectRoot, '.bootspring', 'audit');
129
+ this.stateFile = path.join(this.workflowDir, 'workflow-state.json');
130
+ this.reportsDir = path.join(this.workflowDir, 'reports');
131
+ this.findingsDir = path.join(this.workflowDir, 'findings');
132
+ this.options = {
133
+ severityFilter: options.severityFilter || null,
134
+ autoFix: options.autoFix || false,
135
+ ciMode: options.ciMode || false,
136
+ ...options
137
+ };
138
+ this.state = null;
139
+ }
140
+
141
+ /**
142
+ * Setup directories
143
+ */
144
+ setupDirectories() {
145
+ const dirs = [this.workflowDir, this.reportsDir, this.findingsDir];
146
+
147
+ for (const dir of dirs) {
148
+ if (!fs.existsSync(dir)) {
149
+ fs.mkdirSync(dir, { recursive: true });
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Load workflow state
156
+ */
157
+ loadState() {
158
+ if (fs.existsSync(this.stateFile)) {
159
+ try {
160
+ this.state = JSON.parse(fs.readFileSync(this.stateFile, 'utf-8'));
161
+ return true;
162
+ } catch (_e) {
163
+ this.state = { ...DEFAULT_STATE };
164
+ return false;
165
+ }
166
+ }
167
+ this.state = { ...DEFAULT_STATE };
168
+ return false;
169
+ }
170
+
171
+ /**
172
+ * Save workflow state
173
+ */
174
+ saveState() {
175
+ this.setupDirectories();
176
+ this.state.lastUpdated = new Date().toISOString();
177
+ fs.writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
178
+ }
179
+
180
+ /**
181
+ * Initialize workflow
182
+ */
183
+ initializeWorkflow() {
184
+ this.setupDirectories();
185
+ this.state = {
186
+ ...DEFAULT_STATE,
187
+ startedAt: new Date().toISOString(),
188
+ lastUpdated: new Date().toISOString(),
189
+ findings: []
190
+ };
191
+
192
+ // Initialize phase states
193
+ for (const phaseId of Object.keys(AUDIT_PHASES)) {
194
+ this.state.phases[phaseId] = {
195
+ status: PHASE_STATUS.PENDING,
196
+ startedAt: null,
197
+ completedAt: null,
198
+ result: null,
199
+ error: null
200
+ };
201
+ }
202
+
203
+ this.state.currentPhase = 'quality';
204
+ this.saveState();
205
+ return this.state;
206
+ }
207
+
208
+ /**
209
+ * Check if workflow exists
210
+ */
211
+ hasWorkflow() {
212
+ return fs.existsSync(this.stateFile);
213
+ }
214
+
215
+ /**
216
+ * Reset workflow
217
+ */
218
+ resetWorkflow() {
219
+ if (fs.existsSync(this.stateFile)) {
220
+ fs.unlinkSync(this.stateFile);
221
+ }
222
+ this.state = null;
223
+ return true;
224
+ }
225
+
226
+ /**
227
+ * Check if phase dependencies are met
228
+ */
229
+ arePhaseDependenciesMet(phaseId) {
230
+ const phase = AUDIT_PHASES[phaseId];
231
+ if (!phase || !phase.dependencies || phase.dependencies.length === 0) {
232
+ return true;
233
+ }
234
+
235
+ for (const depPhaseId of phase.dependencies) {
236
+ const depPhase = this.state.phases[depPhaseId];
237
+ if (!depPhase || (depPhase.status !== PHASE_STATUS.COMPLETED && depPhase.status !== PHASE_STATUS.SKIPPED)) {
238
+ return false;
239
+ }
240
+ }
241
+
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * Get next phase
247
+ */
248
+ getNextPhase() {
249
+ const phaseOrder = Object.keys(AUDIT_PHASES).sort(
250
+ (a, b) => AUDIT_PHASES[a].order - AUDIT_PHASES[b].order
251
+ );
252
+
253
+ for (const phaseId of phaseOrder) {
254
+ const phase = this.state.phases[phaseId];
255
+ if (phase.status === PHASE_STATUS.PENDING && this.arePhaseDependenciesMet(phaseId)) {
256
+ return phaseId;
257
+ }
258
+ }
259
+
260
+ return null;
261
+ }
262
+
263
+ /**
264
+ * Start a phase
265
+ */
266
+ startPhase(phaseId) {
267
+ if (!this.state.phases[phaseId]) {
268
+ throw new Error(`Unknown phase: ${phaseId}`);
269
+ }
270
+
271
+ this.state.phases[phaseId].status = PHASE_STATUS.IN_PROGRESS;
272
+ this.state.phases[phaseId].startedAt = new Date().toISOString();
273
+ this.state.currentPhase = phaseId;
274
+ this.saveState();
275
+ }
276
+
277
+ /**
278
+ * Complete a phase
279
+ */
280
+ completePhase(phaseId, result = null) {
281
+ if (!this.state.phases[phaseId]) {
282
+ throw new Error(`Unknown phase: ${phaseId}`);
283
+ }
284
+
285
+ this.state.phases[phaseId].status = PHASE_STATUS.COMPLETED;
286
+ this.state.phases[phaseId].completedAt = new Date().toISOString();
287
+ this.state.phases[phaseId].result = result;
288
+ this.saveState();
289
+ }
290
+
291
+ /**
292
+ * Fail a phase
293
+ */
294
+ failPhase(phaseId, error) {
295
+ if (!this.state.phases[phaseId]) {
296
+ throw new Error(`Unknown phase: ${phaseId}`);
297
+ }
298
+
299
+ this.state.phases[phaseId].status = PHASE_STATUS.FAILED;
300
+ this.state.phases[phaseId].completedAt = new Date().toISOString();
301
+ this.state.phases[phaseId].error = error;
302
+ this.saveState();
303
+ }
304
+
305
+ /**
306
+ * Skip a phase
307
+ */
308
+ skipPhase(phaseId) {
309
+ if (!this.state.phases[phaseId]) {
310
+ throw new Error(`Unknown phase: ${phaseId}`);
311
+ }
312
+
313
+ this.state.phases[phaseId].status = PHASE_STATUS.SKIPPED;
314
+ this.state.phases[phaseId].completedAt = new Date().toISOString();
315
+ this.saveState();
316
+ }
317
+
318
+ /**
319
+ * Add finding
320
+ */
321
+ addFinding(finding) {
322
+ this.state.findings.push({
323
+ ...finding,
324
+ id: `F${this.state.findings.length + 1}`,
325
+ timestamp: new Date().toISOString()
326
+ });
327
+ this.saveState();
328
+ }
329
+
330
+ /**
331
+ * Run quality metrics phase
332
+ */
333
+ async runQualityMetrics() {
334
+ const analyzer = new QualityAnalyzer(this.projectRoot);
335
+ const result = analyzer.analyze();
336
+
337
+ // Add findings for quality issues
338
+ for (const file of result.worstFiles) {
339
+ for (const issue of file.issues) {
340
+ this.addFinding({
341
+ phase: 'quality',
342
+ category: 'quality',
343
+ severity: issue.type === 'high-complexity' ? 'medium' : 'low',
344
+ title: issue.type,
345
+ description: issue.message,
346
+ file: file.path,
347
+ function: issue.function || null,
348
+ remediation: this.getQualityRemediation(issue.type)
349
+ });
350
+ }
351
+ }
352
+
353
+ // Add findings for code smells
354
+ for (const smell of result.smells) {
355
+ this.addFinding({
356
+ phase: 'quality',
357
+ category: 'code-smell',
358
+ severity: smell.severity,
359
+ title: smell.name,
360
+ description: `Found ${smell.count} instance(s)`,
361
+ file: smell.file,
362
+ remediation: this.getSmellRemediation(smell.id)
363
+ });
364
+ }
365
+
366
+ // Save report
367
+ const report = this.generateQualityReport(result);
368
+ fs.writeFileSync(
369
+ path.join(this.reportsDir, 'quality.md'),
370
+ report
371
+ );
372
+
373
+ return {
374
+ score: result.scores.overall,
375
+ breakdown: result.scores.breakdown,
376
+ totalFiles: result.summary.totalFiles,
377
+ totalIssues: result.summary.totalIssues,
378
+ totalSmells: result.summary.totalSmells
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Get remediation for quality issue
384
+ */
385
+ getQualityRemediation(issueType) {
386
+ const remediations = {
387
+ 'long-function': 'Break function into smaller, focused functions',
388
+ 'high-complexity': 'Simplify logic by extracting helper functions or using early returns',
389
+ 'too-many-params': 'Use an options object or break into multiple functions',
390
+ 'long-file': 'Split into multiple modules with clear responsibilities',
391
+ 'low-comments': 'Add JSDoc comments for public functions and complex logic'
392
+ };
393
+ return remediations[issueType] || 'Review and refactor as needed';
394
+ }
395
+
396
+ /**
397
+ * Get remediation for code smell
398
+ */
399
+ getSmellRemediation(smellId) {
400
+ const remediations = {
401
+ 'todo-comment': 'Convert to a tracked issue or complete the TODO',
402
+ 'fixme-comment': 'Address the issue and remove the comment',
403
+ 'console-log': 'Remove console.log or replace with proper logging',
404
+ 'debugger': 'Remove debugger statement before committing',
405
+ 'empty-catch': 'Handle the error appropriately or add a comment explaining why it is safe to ignore',
406
+ 'any-type': 'Add proper TypeScript types',
407
+ 'ts-ignore': 'Fix the underlying TypeScript error',
408
+ 'eslint-disable': 'Fix the underlying ESLint error or add specific disable reason'
409
+ };
410
+ return remediations[smellId] || 'Review and address as appropriate';
411
+ }
412
+
413
+ /**
414
+ * Generate quality report
415
+ */
416
+ generateQualityReport(analysis) {
417
+ const lines = [
418
+ '# Quality Analysis Report',
419
+ '',
420
+ `Generated: ${new Date().toISOString()}`,
421
+ '',
422
+ '## Quality Score',
423
+ '',
424
+ `**Overall**: ${analysis.scores.overall}/100`,
425
+ '',
426
+ '| Category | Score |',
427
+ '|----------|-------|',
428
+ `| Complexity | ${analysis.scores.breakdown.complexity}/100 |`,
429
+ `| Maintainability | ${analysis.scores.breakdown.maintainability}/100 |`,
430
+ `| Documentation | ${analysis.scores.breakdown.documentation}/100 |`,
431
+ `| Code Smells | ${analysis.scores.breakdown.codeSmells}/100 |`,
432
+ '',
433
+ '## Summary',
434
+ '',
435
+ `- **Total Files**: ${analysis.summary.totalFiles}`,
436
+ `- **Total Lines**: ${analysis.summary.totalLines}`,
437
+ `- **Average Complexity**: ${analysis.summary.averageComplexity}`,
438
+ `- **Total Issues**: ${analysis.summary.totalIssues}`,
439
+ `- **Code Smells**: ${analysis.summary.totalSmells}`,
440
+ ''
441
+ ];
442
+
443
+ if (analysis.worstFiles.length > 0) {
444
+ lines.push('## Files Needing Attention');
445
+ lines.push('');
446
+ lines.push('| File | Issues | Complexity |');
447
+ lines.push('|------|--------|------------|');
448
+ for (const file of analysis.worstFiles.slice(0, 10)) {
449
+ lines.push(`| \`${file.path}\` | ${file.issues.length} | ${file.complexity} |`);
450
+ }
451
+ lines.push('');
452
+ }
453
+
454
+ if (analysis.smellsByCategory && Object.keys(analysis.smellsByCategory).length > 0) {
455
+ lines.push('## Code Smells by Category');
456
+ lines.push('');
457
+ for (const [category, smells] of Object.entries(analysis.smellsByCategory)) {
458
+ lines.push(`### ${category}`);
459
+ for (const smell of smells.slice(0, 5)) {
460
+ lines.push(`- **${smell.name}** in \`${smell.file}\`: ${smell.count} instances`);
461
+ }
462
+ lines.push('');
463
+ }
464
+ }
465
+
466
+ return lines.join('\n');
467
+ }
468
+
469
+ /**
470
+ * Run security scan phase
471
+ */
472
+ async runSecurityScan() {
473
+ const scanner = new SecurityScanner(this.projectRoot);
474
+ const result = scanner.scan();
475
+
476
+ // Add findings
477
+ for (const finding of result.findings) {
478
+ this.addFinding({
479
+ phase: 'security',
480
+ category: finding.type,
481
+ severity: finding.severity,
482
+ title: finding.name,
483
+ description: finding.message,
484
+ file: finding.file,
485
+ line: finding.line,
486
+ remediation: finding.remediation || this.getSecurityRemediation(finding.id)
487
+ });
488
+ }
489
+
490
+ // Save findings by severity
491
+ fs.writeFileSync(
492
+ path.join(this.findingsDir, 'critical.json'),
493
+ JSON.stringify(result.bySeverity[SEVERITY.CRITICAL], null, 2)
494
+ );
495
+ fs.writeFileSync(
496
+ path.join(this.findingsDir, 'high.json'),
497
+ JSON.stringify(result.bySeverity[SEVERITY.HIGH], null, 2)
498
+ );
499
+
500
+ // Save report
501
+ const report = this.generateSecurityReport(result);
502
+ fs.writeFileSync(
503
+ path.join(this.reportsDir, 'security.md'),
504
+ report
505
+ );
506
+
507
+ return {
508
+ passed: result.summary.passed,
509
+ total: result.summary.total,
510
+ critical: result.summary.critical,
511
+ high: result.summary.high,
512
+ medium: result.summary.medium,
513
+ low: result.summary.low
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Get security remediation
519
+ */
520
+ getSecurityRemediation(findingId) {
521
+ const remediations = {
522
+ 'aws-key': 'Remove AWS key and rotate credentials immediately',
523
+ 'api-key': 'Move API key to environment variables',
524
+ 'private-key': 'Remove private key from repository and rotate',
525
+ 'sql-injection': 'Use parameterized queries',
526
+ 'command-injection': 'Sanitize input and use spawn with array arguments',
527
+ 'xss': 'Sanitize output and use content security policy'
528
+ };
529
+ return remediations[findingId] || 'Review and remediate security issue';
530
+ }
531
+
532
+ /**
533
+ * Generate security report
534
+ */
535
+ generateSecurityReport(analysis) {
536
+ const lines = [
537
+ '# Security Audit Report',
538
+ '',
539
+ `Generated: ${new Date().toISOString()}`,
540
+ '',
541
+ '## Summary',
542
+ '',
543
+ `**Status**: ${analysis.summary.passed ? 'PASSED' : 'FAILED'}`,
544
+ '',
545
+ '| Severity | Count |',
546
+ '|----------|-------|',
547
+ `| Critical | ${analysis.summary.critical} |`,
548
+ `| High | ${analysis.summary.high} |`,
549
+ `| Medium | ${analysis.summary.medium} |`,
550
+ `| Low | ${analysis.summary.low} |`,
551
+ `| Info | ${analysis.summary.info} |`,
552
+ ''
553
+ ];
554
+
555
+ if (analysis.summary.critical > 0) {
556
+ lines.push('## Critical Findings');
557
+ lines.push('');
558
+ lines.push('> These require immediate attention.');
559
+ lines.push('');
560
+ for (const finding of analysis.bySeverity[SEVERITY.CRITICAL]) {
561
+ lines.push(`### ${finding.name}`);
562
+ lines.push(`- **File**: \`${finding.file}:${finding.line || 0}\``);
563
+ lines.push(`- **Message**: ${finding.message}`);
564
+ lines.push(`- **Remediation**: ${finding.remediation || 'Review and fix'}`);
565
+ lines.push('');
566
+ }
567
+ }
568
+
569
+ if (analysis.summary.high > 0) {
570
+ lines.push('## High Severity Findings');
571
+ lines.push('');
572
+ for (const finding of analysis.bySeverity[SEVERITY.HIGH]) {
573
+ lines.push(`- **${finding.name}** in \`${finding.file}\`: ${finding.message}`);
574
+ }
575
+ lines.push('');
576
+ }
577
+
578
+ if (analysis.byType.secret.length > 0) {
579
+ lines.push('## Potential Secrets');
580
+ lines.push('');
581
+ lines.push('> Remove these from version control and rotate credentials.');
582
+ lines.push('');
583
+ for (const finding of analysis.byType.secret) {
584
+ lines.push(`- **${finding.name}** in \`${finding.file}\``);
585
+ }
586
+ lines.push('');
587
+ }
588
+
589
+ return lines.join('\n');
590
+ }
591
+
592
+ /**
593
+ * Run performance analysis phase
594
+ */
595
+ async runPerformanceAnalysis() {
596
+ const findings = {
597
+ bundleSize: null,
598
+ lazyLoading: [],
599
+ n1Queries: [],
600
+ recommendations: []
601
+ };
602
+
603
+ // Check for bundle analysis config
604
+ const nextConfigPath = path.join(this.projectRoot, 'next.config.js');
605
+ if (fs.existsSync(nextConfigPath)) {
606
+ const content = fs.readFileSync(nextConfigPath, 'utf-8');
607
+ if (!content.includes('bundle-analyzer')) {
608
+ findings.recommendations.push({
609
+ id: 'add-bundle-analyzer',
610
+ title: 'Add Bundle Analyzer',
611
+ description: 'Consider adding @next/bundle-analyzer to monitor bundle size'
612
+ });
613
+ }
614
+ }
615
+
616
+ // Check for dynamic imports
617
+ const hasDynamicImports = await this.checkForDynamicImports();
618
+ if (!hasDynamicImports) {
619
+ findings.recommendations.push({
620
+ id: 'use-dynamic-imports',
621
+ title: 'Use Dynamic Imports',
622
+ description: 'Consider using dynamic imports for large components'
623
+ });
624
+ }
625
+
626
+ // Add findings
627
+ for (const rec of findings.recommendations) {
628
+ this.addFinding({
629
+ phase: 'performance',
630
+ category: 'performance',
631
+ severity: 'low',
632
+ title: rec.title,
633
+ description: rec.description,
634
+ remediation: 'Implement the suggested optimization'
635
+ });
636
+ }
637
+
638
+ return {
639
+ recommendations: findings.recommendations.length
640
+ };
641
+ }
642
+
643
+ /**
644
+ * Check for dynamic imports
645
+ */
646
+ async checkForDynamicImports() {
647
+ const patterns = [
648
+ /import\s*\(\s*['"][^'"]+['"]\s*\)/g,
649
+ /dynamic\s*\(\s*\(\)\s*=>\s*import/g,
650
+ /lazy\s*\(\s*\(\)\s*=>\s*import/g
651
+ ];
652
+
653
+ const sourceDirs = ['src', 'app', 'pages', 'components'];
654
+
655
+ for (const dir of sourceDirs) {
656
+ const dirPath = path.join(this.projectRoot, dir);
657
+ if (fs.existsSync(dirPath)) {
658
+ const found = await this.searchForPatterns(dirPath, patterns);
659
+ if (found) return true;
660
+ }
661
+ }
662
+
663
+ return false;
664
+ }
665
+
666
+ /**
667
+ * Search for patterns in directory
668
+ */
669
+ async searchForPatterns(dir, patterns) {
670
+ try {
671
+ const items = fs.readdirSync(dir, { withFileTypes: true });
672
+
673
+ for (const item of items) {
674
+ const fullPath = path.join(dir, item.name);
675
+
676
+ if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
677
+ if (await this.searchForPatterns(fullPath, patterns)) {
678
+ return true;
679
+ }
680
+ } else if (item.isFile() && /\.(js|jsx|ts|tsx)$/.test(item.name)) {
681
+ const content = fs.readFileSync(fullPath, 'utf-8');
682
+ for (const pattern of patterns) {
683
+ if (pattern.test(content)) {
684
+ return true;
685
+ }
686
+ }
687
+ }
688
+ }
689
+ } catch (_e) {
690
+ // Skip
691
+ }
692
+
693
+ return false;
694
+ }
695
+
696
+ /**
697
+ * Run best practices phase
698
+ */
699
+ async runBestPractices() {
700
+ const checks = {
701
+ typescript: await this.checkTypeScriptConfig(),
702
+ errorHandling: await this.checkErrorHandling(),
703
+ envVars: await this.checkEnvVars(),
704
+ gitignore: await this.checkGitignore()
705
+ };
706
+
707
+ // Add findings for failed checks
708
+ for (const [check, result] of Object.entries(checks)) {
709
+ if (!result.passed) {
710
+ for (const issue of result.issues) {
711
+ this.addFinding({
712
+ phase: 'practices',
713
+ category: 'best-practice',
714
+ severity: issue.severity || 'medium',
715
+ title: issue.title,
716
+ description: issue.description,
717
+ file: issue.file,
718
+ remediation: issue.remediation
719
+ });
720
+ }
721
+ }
722
+ }
723
+
724
+ const passedCount = Object.values(checks).filter(c => c.passed).length;
725
+
726
+ return {
727
+ passed: passedCount,
728
+ total: Object.keys(checks).length,
729
+ checks
730
+ };
731
+ }
732
+
733
+ /**
734
+ * Check TypeScript configuration
735
+ */
736
+ async checkTypeScriptConfig() {
737
+ const result = { passed: true, issues: [] };
738
+ const tsconfigPath = path.join(this.projectRoot, 'tsconfig.json');
739
+
740
+ if (!fs.existsSync(tsconfigPath)) {
741
+ result.passed = false;
742
+ result.issues.push({
743
+ title: 'TypeScript Not Configured',
744
+ description: 'No tsconfig.json found',
745
+ severity: 'low',
746
+ remediation: 'Consider using TypeScript for better type safety'
747
+ });
748
+ return result;
749
+ }
750
+
751
+ try {
752
+ const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
753
+ const compilerOptions = tsconfig.compilerOptions || {};
754
+
755
+ if (!compilerOptions.strict) {
756
+ result.passed = false;
757
+ result.issues.push({
758
+ title: 'TypeScript Strict Mode Disabled',
759
+ description: 'strict mode is not enabled in tsconfig.json',
760
+ file: 'tsconfig.json',
761
+ severity: 'low',
762
+ remediation: 'Enable "strict": true for better type safety'
763
+ });
764
+ }
765
+
766
+ if (compilerOptions.noImplicitAny === false) {
767
+ result.passed = false;
768
+ result.issues.push({
769
+ title: 'Implicit Any Allowed',
770
+ description: 'noImplicitAny is disabled',
771
+ file: 'tsconfig.json',
772
+ severity: 'low',
773
+ remediation: 'Enable noImplicitAny for stricter typing'
774
+ });
775
+ }
776
+ } catch (_e) {
777
+ // Invalid tsconfig
778
+ }
779
+
780
+ return result;
781
+ }
782
+
783
+ /**
784
+ * Check error handling patterns
785
+ */
786
+ async checkErrorHandling() {
787
+ const result = { passed: true, issues: [] };
788
+
789
+ // Check for global error handler in Next.js
790
+ const errorPagePaths = [
791
+ 'app/error.tsx', 'app/error.js',
792
+ 'pages/_error.tsx', 'pages/_error.js',
793
+ 'src/app/error.tsx', 'src/pages/_error.tsx'
794
+ ];
795
+
796
+ let hasErrorPage = false;
797
+ for (const errorPath of errorPagePaths) {
798
+ if (fs.existsSync(path.join(this.projectRoot, errorPath))) {
799
+ hasErrorPage = true;
800
+ break;
801
+ }
802
+ }
803
+
804
+ if (!hasErrorPage) {
805
+ result.passed = false;
806
+ result.issues.push({
807
+ title: 'No Global Error Handler',
808
+ description: 'No error page found for handling runtime errors',
809
+ severity: 'medium',
810
+ remediation: 'Create an error.tsx (App Router) or _error.tsx (Pages Router)'
811
+ });
812
+ }
813
+
814
+ return result;
815
+ }
816
+
817
+ /**
818
+ * Check environment variable handling
819
+ */
820
+ async checkEnvVars() {
821
+ const result = { passed: true, issues: [] };
822
+
823
+ const envPath = path.join(this.projectRoot, '.env');
824
+ const envExamplePath = path.join(this.projectRoot, '.env.example');
825
+ const envLocalPath = path.join(this.projectRoot, '.env.local');
826
+
827
+ // Check for .env.example
828
+ if (!fs.existsSync(envExamplePath)) {
829
+ if (fs.existsSync(envPath) || fs.existsSync(envLocalPath)) {
830
+ result.passed = false;
831
+ result.issues.push({
832
+ title: 'Missing .env.example',
833
+ description: 'No .env.example file to document required environment variables',
834
+ severity: 'low',
835
+ remediation: 'Create .env.example with all required variables (without values)'
836
+ });
837
+ }
838
+ }
839
+
840
+ // Check .gitignore includes .env
841
+ const gitignorePath = path.join(this.projectRoot, '.gitignore');
842
+ if (fs.existsSync(gitignorePath)) {
843
+ const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
844
+ if (!gitignore.includes('.env')) {
845
+ result.passed = false;
846
+ result.issues.push({
847
+ title: '.env Not in .gitignore',
848
+ description: '.env files should be excluded from version control',
849
+ file: '.gitignore',
850
+ severity: 'high',
851
+ remediation: 'Add .env* to .gitignore'
852
+ });
853
+ }
854
+ }
855
+
856
+ return result;
857
+ }
858
+
859
+ /**
860
+ * Check .gitignore configuration
861
+ */
862
+ async checkGitignore() {
863
+ const result = { passed: true, issues: [] };
864
+ const gitignorePath = path.join(this.projectRoot, '.gitignore');
865
+
866
+ if (!fs.existsSync(gitignorePath)) {
867
+ result.passed = false;
868
+ result.issues.push({
869
+ title: 'Missing .gitignore',
870
+ description: 'No .gitignore file found',
871
+ severity: 'medium',
872
+ remediation: 'Create a .gitignore file with appropriate exclusions'
873
+ });
874
+ return result;
875
+ }
876
+
877
+ const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
878
+ const requiredEntries = ['node_modules', '.env'];
879
+
880
+ for (const entry of requiredEntries) {
881
+ if (!gitignore.includes(entry)) {
882
+ result.passed = false;
883
+ result.issues.push({
884
+ title: `Missing ${entry} in .gitignore`,
885
+ description: `${entry} should be in .gitignore`,
886
+ file: '.gitignore',
887
+ severity: entry === '.env' ? 'high' : 'low',
888
+ remediation: `Add ${entry} to .gitignore`
889
+ });
890
+ }
891
+ }
892
+
893
+ return result;
894
+ }
895
+
896
+ /**
897
+ * Run tech debt inventory phase
898
+ */
899
+ async runTechDebtInventory() {
900
+ const techDebt = {
901
+ todos: [],
902
+ deprecated: [],
903
+ deadCode: [],
904
+ missingTests: []
905
+ };
906
+
907
+ // Scan for TODOs (already captured in quality phase)
908
+ const qualityFindings = this.state.findings.filter(
909
+ f => f.phase === 'quality' && f.category === 'code-smell'
910
+ );
911
+
912
+ techDebt.todos = qualityFindings.filter(
913
+ f => f.title.toLowerCase().includes('todo') || f.title.toLowerCase().includes('fixme')
914
+ );
915
+
916
+ // Check for missing tests
917
+ const srcDirs = ['src', 'lib', 'app', 'components'];
918
+ let sourceFiles = 0;
919
+ let testFiles = 0;
920
+
921
+ for (const dir of srcDirs) {
922
+ const dirPath = path.join(this.projectRoot, dir);
923
+ if (fs.existsSync(dirPath)) {
924
+ const counts = await this.countSourceAndTestFiles(dirPath);
925
+ sourceFiles += counts.source;
926
+ testFiles += counts.test;
927
+ }
928
+ }
929
+
930
+ const testRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
931
+ if (testRatio < 0.2) {
932
+ this.addFinding({
933
+ phase: 'techDebt',
934
+ category: 'tech-debt',
935
+ severity: 'medium',
936
+ title: 'Low Test Coverage',
937
+ description: `Only ${Math.round(testRatio * 100)}% test file ratio`,
938
+ remediation: 'Add unit and integration tests for critical paths'
939
+ });
940
+ }
941
+
942
+ // Check for unused dependencies (skip for large codebases)
943
+ const LARGE_CODEBASE_THRESHOLD = 500;
944
+ let unusedDeps = [];
945
+ let depAnalysisSkipped = false;
946
+
947
+ // Estimate file count from source files scanned
948
+ const estimatedFileCount = sourceFiles + testFiles;
949
+
950
+ if (estimatedFileCount < LARGE_CODEBASE_THRESHOLD) {
951
+ const depAnalyzer = new DependencyAnalyzer(this.projectRoot);
952
+ depAnalyzer.buildGraph();
953
+ unusedDeps = depAnalyzer.findUnusedDependencies();
954
+
955
+ for (const dep of unusedDeps.slice(0, 10)) {
956
+ this.addFinding({
957
+ phase: 'techDebt',
958
+ category: 'tech-debt',
959
+ severity: 'low',
960
+ title: 'Potentially Unused Dependency',
961
+ description: `${dep.name} may be unused`,
962
+ remediation: `Remove ${dep.name} if not needed, or verify it is used`
963
+ });
964
+ }
965
+ } else {
966
+ depAnalysisSkipped = true;
967
+ }
968
+
969
+ // Save tech debt report
970
+ const report = this.generateTechDebtReport(techDebt, testRatio, unusedDeps, depAnalysisSkipped);
971
+ fs.writeFileSync(
972
+ path.join(this.reportsDir, 'tech-debt.md'),
973
+ report
974
+ );
975
+
976
+ return {
977
+ todos: techDebt.todos.length,
978
+ unusedDeps: unusedDeps.length,
979
+ testCoverage: Math.round(testRatio * 100),
980
+ depAnalysisSkipped
981
+ };
982
+ }
983
+
984
+ /**
985
+ * Count source and test files
986
+ */
987
+ async countSourceAndTestFiles(dir) {
988
+ let source = 0;
989
+ let test = 0;
990
+
991
+ try {
992
+ const items = fs.readdirSync(dir, { withFileTypes: true });
993
+
994
+ for (const item of items) {
995
+ const fullPath = path.join(dir, item.name);
996
+
997
+ if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
998
+ const counts = await this.countSourceAndTestFiles(fullPath);
999
+ source += counts.source;
1000
+ test += counts.test;
1001
+ } else if (item.isFile() && /\.(js|jsx|ts|tsx)$/.test(item.name)) {
1002
+ if (/\.(test|spec)\.(js|jsx|ts|tsx)$/.test(item.name)) {
1003
+ test++;
1004
+ } else {
1005
+ source++;
1006
+ }
1007
+ }
1008
+ }
1009
+ } catch (_e) {
1010
+ // Skip
1011
+ }
1012
+
1013
+ return { source, test };
1014
+ }
1015
+
1016
+ /**
1017
+ * Generate tech debt report
1018
+ */
1019
+ generateTechDebtReport(techDebt, testRatio, unusedDeps, depAnalysisSkipped = false) {
1020
+ const lines = [
1021
+ '# Technical Debt Report',
1022
+ '',
1023
+ `Generated: ${new Date().toISOString()}`,
1024
+ '',
1025
+ '## Summary',
1026
+ '',
1027
+ `- **TODO/FIXME Comments**: ${techDebt.todos.length}`,
1028
+ `- **Test Coverage**: ${Math.round(testRatio * 100)}%`,
1029
+ `- **Unused Dependencies**: ${depAnalysisSkipped ? 'Skipped (large codebase)' : unusedDeps.length}`,
1030
+ ''
1031
+ ];
1032
+
1033
+ if (techDebt.todos.length > 0) {
1034
+ lines.push('## TODO/FIXME Items');
1035
+ lines.push('');
1036
+ for (const todo of techDebt.todos.slice(0, 20)) {
1037
+ lines.push(`- \`${todo.file}\`: ${todo.title}`);
1038
+ }
1039
+ lines.push('');
1040
+ }
1041
+
1042
+ if (unusedDeps.length > 0) {
1043
+ lines.push('## Potentially Unused Dependencies');
1044
+ lines.push('');
1045
+ for (const dep of unusedDeps) {
1046
+ lines.push(`- \`${dep.name}\` (${dep.type})`);
1047
+ }
1048
+ lines.push('');
1049
+ }
1050
+
1051
+ return lines.join('\n');
1052
+ }
1053
+
1054
+ /**
1055
+ * Run recommendations phase
1056
+ */
1057
+ async runRecommendations() {
1058
+ // Group findings by severity
1059
+ const bySeverity = {
1060
+ critical: [],
1061
+ high: [],
1062
+ medium: [],
1063
+ low: [],
1064
+ info: []
1065
+ };
1066
+
1067
+ for (const finding of this.state.findings) {
1068
+ const severity = finding.severity || 'info';
1069
+ if (bySeverity[severity]) {
1070
+ bySeverity[severity].push(finding);
1071
+ }
1072
+ }
1073
+
1074
+ // Generate prioritized recommendations
1075
+ const recommendations = [];
1076
+ let priority = 0;
1077
+
1078
+ // P0: Critical findings
1079
+ for (const finding of bySeverity.critical) {
1080
+ recommendations.push({
1081
+ priority: 'P0',
1082
+ severity: 'critical',
1083
+ title: finding.title,
1084
+ description: finding.description,
1085
+ file: finding.file,
1086
+ remediation: finding.remediation
1087
+ });
1088
+ priority++;
1089
+ }
1090
+
1091
+ // P1: High findings
1092
+ for (const finding of bySeverity.high.slice(0, 10)) {
1093
+ recommendations.push({
1094
+ priority: 'P1',
1095
+ severity: 'high',
1096
+ title: finding.title,
1097
+ description: finding.description,
1098
+ file: finding.file,
1099
+ remediation: finding.remediation
1100
+ });
1101
+ }
1102
+
1103
+ // P2: Medium findings
1104
+ for (const finding of bySeverity.medium.slice(0, 10)) {
1105
+ recommendations.push({
1106
+ priority: 'P2',
1107
+ severity: 'medium',
1108
+ title: finding.title,
1109
+ description: finding.description,
1110
+ file: finding.file,
1111
+ remediation: finding.remediation
1112
+ });
1113
+ }
1114
+
1115
+ // P3: Low findings (just summary)
1116
+ recommendations.push({
1117
+ priority: 'P3',
1118
+ severity: 'low',
1119
+ title: `${bySeverity.low.length} Low Priority Items`,
1120
+ description: 'Address when convenient',
1121
+ remediation: 'Review low priority findings in the audit report'
1122
+ });
1123
+
1124
+ // Generate final report
1125
+ const report = this.generateFinalReport(bySeverity, recommendations);
1126
+ const reportPath = path.join(this.projectRoot, 'planning', 'AUDIT_REPORT.md');
1127
+ const planningDir = path.join(this.projectRoot, 'planning');
1128
+
1129
+ if (!fs.existsSync(planningDir)) {
1130
+ fs.mkdirSync(planningDir, { recursive: true });
1131
+ }
1132
+
1133
+ fs.writeFileSync(reportPath, report);
1134
+
1135
+ // Also save in workflow directory
1136
+ fs.writeFileSync(
1137
+ path.join(this.reportsDir, 'AUDIT_REPORT.md'),
1138
+ report
1139
+ );
1140
+
1141
+ this.state.summary = {
1142
+ reportPath,
1143
+ totalFindings: this.state.findings.length,
1144
+ critical: bySeverity.critical.length,
1145
+ high: bySeverity.high.length,
1146
+ medium: bySeverity.medium.length,
1147
+ low: bySeverity.low.length,
1148
+ recommendations: recommendations.length,
1149
+ generatedAt: new Date().toISOString()
1150
+ };
1151
+
1152
+ return {
1153
+ reportPath,
1154
+ recommendations: recommendations.length,
1155
+ passed: bySeverity.critical.length === 0
1156
+ };
1157
+ }
1158
+
1159
+ /**
1160
+ * Generate final audit report
1161
+ */
1162
+ generateFinalReport(bySeverity, recommendations) {
1163
+ const totalFindings = Object.values(bySeverity).reduce((sum, arr) => sum + arr.length, 0);
1164
+
1165
+ const lines = [
1166
+ '# Audit Report',
1167
+ '',
1168
+ '**Generated by**: Bootspring Audit',
1169
+ `**Date**: ${new Date().toISOString().split('T')[0]}`,
1170
+ `**Status**: ${bySeverity.critical.length === 0 ? 'PASSED' : 'NEEDS ATTENTION'}`,
1171
+ '',
1172
+ '## Executive Summary',
1173
+ '',
1174
+ `This audit found **${totalFindings}** findings across quality, security, and best practices checks.`,
1175
+ '',
1176
+ '### Findings by Severity',
1177
+ '',
1178
+ '| Severity | Count | Action Required |',
1179
+ '|----------|-------|-----------------|',
1180
+ `| Critical | ${bySeverity.critical.length} | Immediate |`,
1181
+ `| High | ${bySeverity.high.length} | 1-2 days |`,
1182
+ `| Medium | ${bySeverity.medium.length} | 1-2 weeks |`,
1183
+ `| Low | ${bySeverity.low.length} | Backlog |`,
1184
+ ''
1185
+ ];
1186
+
1187
+ // Critical findings
1188
+ if (bySeverity.critical.length > 0) {
1189
+ lines.push('## Critical Findings');
1190
+ lines.push('');
1191
+ lines.push('> These require immediate attention.');
1192
+ lines.push('');
1193
+ for (const finding of bySeverity.critical) {
1194
+ lines.push(`### ${finding.title}`);
1195
+ lines.push(`- **Category**: ${finding.category}`);
1196
+ lines.push(`- **File**: ${finding.file ? `\`${finding.file}\`` : 'N/A'}`);
1197
+ lines.push(`- **Description**: ${finding.description}`);
1198
+ lines.push(`- **Remediation**: ${finding.remediation}`);
1199
+ lines.push('');
1200
+ }
1201
+ }
1202
+
1203
+ // High findings
1204
+ if (bySeverity.high.length > 0) {
1205
+ lines.push('## High Priority Findings');
1206
+ lines.push('');
1207
+ for (const finding of bySeverity.high) {
1208
+ lines.push(`- **${finding.title}** ${finding.file ? `(\`${finding.file}\`)` : ''}: ${finding.description}`);
1209
+ }
1210
+ lines.push('');
1211
+ }
1212
+
1213
+ // Recommendations
1214
+ lines.push('## Prioritized Recommendations');
1215
+ lines.push('');
1216
+ lines.push('| Priority | Title | Severity |');
1217
+ lines.push('|----------|-------|----------|');
1218
+ for (const rec of recommendations.slice(0, 15)) {
1219
+ lines.push(`| ${rec.priority} | ${rec.title} | ${rec.severity} |`);
1220
+ }
1221
+ lines.push('');
1222
+
1223
+ // Quality metrics
1224
+ const qualityPhase = this.state.phases.quality;
1225
+ if (qualityPhase?.result) {
1226
+ lines.push('## Quality Metrics');
1227
+ lines.push('');
1228
+ lines.push(`- **Quality Score**: ${qualityPhase.result.score}/100`);
1229
+ lines.push(`- **Total Files**: ${qualityPhase.result.totalFiles}`);
1230
+ lines.push(`- **Total Issues**: ${qualityPhase.result.totalIssues}`);
1231
+ lines.push('');
1232
+ }
1233
+
1234
+ // Security summary
1235
+ const securityPhase = this.state.phases.security;
1236
+ if (securityPhase?.result) {
1237
+ lines.push('## Security Summary');
1238
+ lines.push('');
1239
+ lines.push(`- **Status**: ${securityPhase.result.passed ? 'PASSED' : 'NEEDS ATTENTION'}`);
1240
+ lines.push(`- **Critical Issues**: ${securityPhase.result.critical}`);
1241
+ lines.push(`- **High Issues**: ${securityPhase.result.high}`);
1242
+ lines.push('');
1243
+ }
1244
+
1245
+ lines.push('---');
1246
+ lines.push('');
1247
+ lines.push('*Generated by [Bootspring](https://bootspring.com)*');
1248
+
1249
+ return lines.join('\n');
1250
+ }
1251
+
1252
+ /**
1253
+ * Get workflow progress
1254
+ */
1255
+ getProgress() {
1256
+ if (!this.state) return null;
1257
+
1258
+ const phases = Object.entries(AUDIT_PHASES).map(([phaseId, phase]) => {
1259
+ const phaseState = this.state.phases[phaseId] || { status: PHASE_STATUS.PENDING };
1260
+
1261
+ return {
1262
+ id: phaseId,
1263
+ name: phase.name,
1264
+ description: phase.description,
1265
+ order: phase.order,
1266
+ required: phase.required,
1267
+ status: phaseState.status,
1268
+ dependenciesMet: this.arePhaseDependenciesMet(phaseId)
1269
+ };
1270
+ });
1271
+
1272
+ const completedCount = phases.filter(p => p.status === PHASE_STATUS.COMPLETED).length;
1273
+ const activeCount = phases.filter(p => p.status !== PHASE_STATUS.SKIPPED).length;
1274
+
1275
+ // Count findings by severity
1276
+ const findingsBySeverity = {
1277
+ critical: 0,
1278
+ high: 0,
1279
+ medium: 0,
1280
+ low: 0,
1281
+ info: 0
1282
+ };
1283
+
1284
+ for (const finding of this.state.findings) {
1285
+ const severity = finding.severity || 'info';
1286
+ if (findingsBySeverity[severity] !== undefined) {
1287
+ findingsBySeverity[severity]++;
1288
+ }
1289
+ }
1290
+
1291
+ return {
1292
+ currentPhase: this.state.currentPhase,
1293
+ startedAt: this.state.startedAt,
1294
+ lastUpdated: this.state.lastUpdated,
1295
+ phases,
1296
+ overall: {
1297
+ completed: completedCount,
1298
+ total: activeCount,
1299
+ percentage: Math.round((completedCount / activeCount) * 100)
1300
+ },
1301
+ findings: {
1302
+ total: this.state.findings.length,
1303
+ ...findingsBySeverity
1304
+ },
1305
+ isComplete: completedCount === activeCount,
1306
+ summary: this.state.summary
1307
+ };
1308
+ }
1309
+
1310
+ /**
1311
+ * Get resume point
1312
+ */
1313
+ getResumePoint() {
1314
+ if (!this.state || !this.state.currentPhase) {
1315
+ return null;
1316
+ }
1317
+
1318
+ const phase = AUDIT_PHASES[this.state.currentPhase];
1319
+ const phaseState = this.state.phases[this.state.currentPhase];
1320
+
1321
+ return {
1322
+ phase: this.state.currentPhase,
1323
+ phaseName: phase?.name,
1324
+ phaseStatus: phaseState?.status,
1325
+ lastUpdated: this.state.lastUpdated,
1326
+ findingsCount: this.state.findings.length
1327
+ };
1328
+ }
1329
+
1330
+ /**
1331
+ * Get exit code for CI mode
1332
+ */
1333
+ getExitCode() {
1334
+ if (!this.state) return 1;
1335
+
1336
+ const criticalCount = this.state.findings.filter(f => f.severity === 'critical').length;
1337
+ const highCount = this.state.findings.filter(f => f.severity === 'high').length;
1338
+
1339
+ if (criticalCount > 0) return 2;
1340
+ if (highCount > 0) return 1;
1341
+ return 0;
1342
+ }
1343
+ }
1344
+
1345
+ module.exports = {
1346
+ AuditWorkflowEngine,
1347
+ AUDIT_PHASES,
1348
+ PHASE_STATUS,
1349
+ SEVERITY_LEVELS
1350
+ };