@girardmedia/bootspring 2.0.21 → 2.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,1293 @@
1
+ /**
2
+ * Bootspring Onboard Workflow Engine
3
+ *
4
+ * Layers Bootspring onto existing codebases through detection,
5
+ * pattern scanning, and configuration generation.
6
+ *
7
+ * @package bootspring
8
+ * @module core/onboard-workflow
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ // Import analyzers from JS modules
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ const { StructureAnalyzer } = require('../../analyzers/structure-analyzer') as {
17
+ StructureAnalyzer: new (projectRoot: string, options?: Record<string, unknown>) => {
18
+ analyze: () => Record<string, unknown>;
19
+ };
20
+ };
21
+
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const { ArchitectureAnalyzer } = require('../../analyzers/architecture-analyzer') as {
24
+ ArchitectureAnalyzer: new (projectRoot: string) => {
25
+ analyze: () => Record<string, unknown>;
26
+ };
27
+ };
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
30
+ const { DependencyAnalyzer } = require('../../analyzers/dependency-analyzer') as {
31
+ DependencyAnalyzer: new (projectRoot: string) => {
32
+ analyze: () => Record<string, unknown>;
33
+ };
34
+ };
35
+
36
+ // ============================================================================
37
+ // Types
38
+ // ============================================================================
39
+
40
+ export type OnboardPhaseStatus = 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed';
41
+
42
+ export interface OnboardPhaseDefinition {
43
+ name: string;
44
+ description: string;
45
+ order: number;
46
+ required: boolean;
47
+ dependencies?: string[] | undefined;
48
+ }
49
+
50
+ export interface OnboardPhaseState {
51
+ status: OnboardPhaseStatus;
52
+ startedAt: string | null;
53
+ completedAt: string | null;
54
+ result: unknown | null;
55
+ error: string | null;
56
+ }
57
+
58
+ export interface DetectionPattern {
59
+ id: string;
60
+ name: string;
61
+ files?: string[] | undefined;
62
+ packages?: string[] | undefined;
63
+ }
64
+
65
+ export interface DetectedStack {
66
+ id: string;
67
+ name: string;
68
+ }
69
+
70
+ export interface StackDetection {
71
+ framework: DetectedStack | null;
72
+ language: DetectedStack | null;
73
+ database: DetectedStack | null;
74
+ hosting: DetectedStack | null;
75
+ testing: DetectedStack | null;
76
+ auth: DetectedStack | null;
77
+ payments: DetectedStack | null;
78
+ detected: Array<{ category: string } & DetectionPattern>;
79
+ packageJson?: {
80
+ name?: string;
81
+ version?: string;
82
+ description?: string;
83
+ } | null;
84
+ }
85
+
86
+ export interface DetectionData {
87
+ stack: StackDetection | null;
88
+ patterns: {
89
+ architecturePatterns?: unknown[];
90
+ moduleCount?: number;
91
+ circularDeps?: number;
92
+ quickMode?: boolean;
93
+ } | null;
94
+ docs: DocsDiscovery | null;
95
+ }
96
+
97
+ export interface GeneratedFiles {
98
+ config: string | null;
99
+ claudeMd: string | null;
100
+ timestamp?: string;
101
+ }
102
+
103
+ export interface BaselineMetrics {
104
+ files: number;
105
+ lines: number;
106
+ dependencies: number;
107
+ }
108
+
109
+ export interface Baseline {
110
+ timestamp: string;
111
+ metrics: BaselineMetrics;
112
+ }
113
+
114
+ export interface OnboardWorkflowState {
115
+ version: string;
116
+ startedAt: string | null;
117
+ lastUpdated: string | null;
118
+ currentPhase: string | null;
119
+ phases: Record<string, OnboardPhaseState>;
120
+ detection: DetectionData;
121
+ generated: GeneratedFiles;
122
+ baseline: Baseline | null;
123
+ }
124
+
125
+ export interface DetectionResult {
126
+ stack: StackDetection;
127
+ structure: Record<string, unknown>;
128
+ packageJson: {
129
+ name?: string;
130
+ version?: string;
131
+ description?: string;
132
+ } | null;
133
+ timestamp: string;
134
+ }
135
+
136
+ export interface PatternScanResult {
137
+ architecture: Record<string, unknown>;
138
+ dependencies: Record<string, unknown>;
139
+ features: FeatureDetection;
140
+ timestamp: string;
141
+ quickMode?: boolean;
142
+ }
143
+
144
+ export interface FeatureDetection {
145
+ auth: boolean;
146
+ payments: boolean;
147
+ api: boolean;
148
+ database: boolean;
149
+ email: boolean;
150
+ uploads: boolean;
151
+ search: boolean;
152
+ realtime: boolean;
153
+ analytics: boolean;
154
+ i18n: boolean;
155
+ [key: string]: boolean;
156
+ }
157
+
158
+ export interface DocInfo {
159
+ file: string;
160
+ type: string;
161
+ required: boolean;
162
+ path?: string;
163
+ size?: number;
164
+ }
165
+
166
+ export interface DocsDiscovery {
167
+ found: DocInfo[];
168
+ missing: DocInfo[];
169
+ recommended: DocInfo[];
170
+ hasDocsDir?: boolean;
171
+ docsCount?: number;
172
+ }
173
+
174
+ export interface ConfigGenerationResult {
175
+ configContent: string;
176
+ claudeMdContent: string;
177
+ configPath: string;
178
+ claudeMdPath: string;
179
+ }
180
+
181
+ export interface OnboardPhaseProgress {
182
+ id: string;
183
+ name: string;
184
+ description: string;
185
+ order: number;
186
+ required: boolean;
187
+ status: OnboardPhaseStatus;
188
+ dependenciesMet: boolean;
189
+ }
190
+
191
+ export interface OnboardWorkflowProgress {
192
+ currentPhase: string | null;
193
+ startedAt: string | null;
194
+ lastUpdated: string | null;
195
+ phases: OnboardPhaseProgress[];
196
+ overall: {
197
+ completed: number;
198
+ total: number;
199
+ percentage: number;
200
+ };
201
+ required: {
202
+ completed: number;
203
+ total: number;
204
+ percentage: number;
205
+ };
206
+ isComplete: boolean;
207
+ detection: DetectionData;
208
+ }
209
+
210
+ export interface OnboardResumePoint {
211
+ phase: string;
212
+ phaseName: string | undefined;
213
+ phaseStatus: OnboardPhaseStatus | undefined;
214
+ lastUpdated: string | null;
215
+ }
216
+
217
+ export interface OnboardWorkflowOptions {
218
+ [key: string]: unknown;
219
+ }
220
+
221
+ // ============================================================================
222
+ // Constants
223
+ // ============================================================================
224
+
225
+ /**
226
+ * Workflow phase status
227
+ */
228
+ export const PHASE_STATUS: Record<string, OnboardPhaseStatus> = {
229
+ PENDING: 'pending',
230
+ IN_PROGRESS: 'in_progress',
231
+ COMPLETED: 'completed',
232
+ SKIPPED: 'skipped',
233
+ FAILED: 'failed'
234
+ };
235
+
236
+ /**
237
+ * Onboarding phases
238
+ */
239
+ export const ONBOARD_PHASES: Record<string, OnboardPhaseDefinition> = {
240
+ detection: {
241
+ name: 'Stack Detection',
242
+ description: 'Detect framework, language, database, and hosting',
243
+ order: 1,
244
+ required: true
245
+ },
246
+ patterns: {
247
+ name: 'Pattern Scan',
248
+ description: 'Find auth, payments, components, and code patterns',
249
+ order: 2,
250
+ required: true,
251
+ dependencies: ['detection']
252
+ },
253
+ docs: {
254
+ name: 'Documentation Discovery',
255
+ description: 'Inventory existing docs and find gaps',
256
+ order: 3,
257
+ required: false,
258
+ dependencies: ['detection']
259
+ },
260
+ config: {
261
+ name: 'Config Generation',
262
+ description: 'Generate bootspring.config.js and CLAUDE.md',
263
+ order: 4,
264
+ required: true,
265
+ dependencies: ['detection', 'patterns']
266
+ },
267
+ baseline: {
268
+ name: 'Baseline Capture',
269
+ description: 'Record metrics for future comparison',
270
+ order: 5,
271
+ required: false,
272
+ dependencies: ['detection', 'patterns']
273
+ },
274
+ reverse: {
275
+ name: 'Reverse Engineer',
276
+ description: 'Generate PRD from code structure (optional)',
277
+ order: 6,
278
+ required: false,
279
+ dependencies: ['detection', 'patterns', 'docs']
280
+ }
281
+ };
282
+
283
+ /**
284
+ * Stack detection patterns
285
+ */
286
+ export const STACK_DETECTION: Record<string, DetectionPattern[]> = {
287
+ frameworks: [
288
+ { id: 'nextjs', name: 'Next.js', files: ['next.config.js', 'next.config.mjs', 'next.config.ts'], packages: ['next'] },
289
+ { id: 'remix', name: 'Remix', files: ['remix.config.js'], packages: ['@remix-run/react'] },
290
+ { id: 'nuxt', name: 'Nuxt', files: ['nuxt.config.js', 'nuxt.config.ts'], packages: ['nuxt'] },
291
+ { id: 'svelte', name: 'SvelteKit', files: ['svelte.config.js'], packages: ['@sveltejs/kit'] },
292
+ { id: 'astro', name: 'Astro', files: ['astro.config.mjs'], packages: ['astro'] },
293
+ { id: 'vite', name: 'Vite', files: ['vite.config.js', 'vite.config.ts'], packages: ['vite'] },
294
+ { id: 'express', name: 'Express', packages: ['express'] },
295
+ { id: 'fastify', name: 'Fastify', packages: ['fastify'] },
296
+ { id: 'nestjs', name: 'NestJS', packages: ['@nestjs/core'] },
297
+ { id: 'react', name: 'React', packages: ['react'] },
298
+ { id: 'vue', name: 'Vue', packages: ['vue'] },
299
+ { id: 'angular', name: 'Angular', files: ['angular.json'], packages: ['@angular/core'] }
300
+ ],
301
+ languages: [
302
+ { id: 'typescript', name: 'TypeScript', files: ['tsconfig.json'], packages: ['typescript'] },
303
+ { id: 'javascript', name: 'JavaScript', files: ['package.json'] }
304
+ ],
305
+ databases: [
306
+ { id: 'prisma', name: 'Prisma', files: ['prisma/schema.prisma'], packages: ['prisma', '@prisma/client'] },
307
+ { id: 'drizzle', name: 'Drizzle', packages: ['drizzle-orm'] },
308
+ { id: 'mongoose', name: 'MongoDB (Mongoose)', packages: ['mongoose'] },
309
+ { id: 'typeorm', name: 'TypeORM', packages: ['typeorm'] },
310
+ { id: 'sequelize', name: 'Sequelize', packages: ['sequelize'] },
311
+ { id: 'knex', name: 'Knex', packages: ['knex'] },
312
+ { id: 'supabase', name: 'Supabase', packages: ['@supabase/supabase-js'] },
313
+ { id: 'firebase', name: 'Firebase', packages: ['firebase', 'firebase-admin'] }
314
+ ],
315
+ hosting: [
316
+ { id: 'vercel', name: 'Vercel', files: ['vercel.json', '.vercel'] },
317
+ { id: 'netlify', name: 'Netlify', files: ['netlify.toml', '.netlify'] },
318
+ { id: 'railway', name: 'Railway', files: ['railway.json'] },
319
+ { id: 'docker', name: 'Docker', files: ['Dockerfile', 'docker-compose.yml'] },
320
+ { id: 'fly', name: 'Fly.io', files: ['fly.toml'] },
321
+ { id: 'render', name: 'Render', files: ['render.yaml'] },
322
+ { id: 'aws', name: 'AWS', files: ['serverless.yml', 'amplify.yml', 'samconfig.toml'] },
323
+ { id: 'gcp', name: 'Google Cloud', files: ['app.yaml', 'cloudbuild.yaml'] }
324
+ ],
325
+ testing: [
326
+ { id: 'vitest', name: 'Vitest', files: ['vitest.config.ts', 'vitest.config.js'], packages: ['vitest'] },
327
+ { id: 'jest', name: 'Jest', files: ['jest.config.js', 'jest.config.ts'], packages: ['jest'] },
328
+ { id: 'playwright', name: 'Playwright', files: ['playwright.config.ts'], packages: ['@playwright/test'] },
329
+ { id: 'cypress', name: 'Cypress', files: ['cypress.config.js', 'cypress.config.ts'], packages: ['cypress'] },
330
+ { id: 'mocha', name: 'Mocha', packages: ['mocha'] }
331
+ ],
332
+ auth: [
333
+ { id: 'nextauth', name: 'NextAuth.js', packages: ['next-auth'] },
334
+ { id: 'authjs', name: 'Auth.js', packages: ['@auth/core'] },
335
+ { id: 'clerk', name: 'Clerk', packages: ['@clerk/nextjs', '@clerk/clerk-react'] },
336
+ { id: 'auth0', name: 'Auth0', packages: ['@auth0/nextjs-auth0', 'auth0'] },
337
+ { id: 'supabase-auth', name: 'Supabase Auth', packages: ['@supabase/auth-helpers-nextjs'] },
338
+ { id: 'firebase-auth', name: 'Firebase Auth', packages: ['firebase/auth'] },
339
+ { id: 'passport', name: 'Passport.js', packages: ['passport'] }
340
+ ],
341
+ payments: [
342
+ { id: 'stripe', name: 'Stripe', packages: ['stripe', '@stripe/stripe-js'] },
343
+ { id: 'lemon-squeezy', name: 'Lemon Squeezy', packages: ['@lemonsqueezy/lemonsqueezy.js'] },
344
+ { id: 'paddle', name: 'Paddle', packages: ['@paddle/paddle-js'] },
345
+ { id: 'paypal', name: 'PayPal', packages: ['@paypal/react-paypal-js', 'paypal-rest-sdk'] }
346
+ ]
347
+ };
348
+
349
+ /**
350
+ * Default workflow state
351
+ */
352
+ export const DEFAULT_STATE: OnboardWorkflowState = {
353
+ version: '1.0.0',
354
+ startedAt: null,
355
+ lastUpdated: null,
356
+ currentPhase: null,
357
+ phases: {},
358
+ detection: {
359
+ stack: null,
360
+ patterns: null,
361
+ docs: null
362
+ },
363
+ generated: {
364
+ config: null,
365
+ claudeMd: null
366
+ },
367
+ baseline: null
368
+ };
369
+
370
+ // ============================================================================
371
+ // OnboardWorkflowEngine Class
372
+ // ============================================================================
373
+
374
+ /**
375
+ * OnboardWorkflowEngine - Manages onboarding workflow
376
+ */
377
+ export class OnboardWorkflowEngine {
378
+ readonly projectRoot: string;
379
+ readonly workflowDir: string;
380
+ readonly stateFile: string;
381
+ readonly detectionDir: string;
382
+ readonly generatedDir: string;
383
+ readonly baselineDir: string;
384
+ readonly options: OnboardWorkflowOptions;
385
+ state: OnboardWorkflowState | null;
386
+
387
+ constructor(projectRoot: string, options: OnboardWorkflowOptions = {}) {
388
+ this.projectRoot = projectRoot;
389
+ this.workflowDir = path.join(projectRoot, '.bootspring', 'onboard');
390
+ this.stateFile = path.join(this.workflowDir, 'workflow-state.json');
391
+ this.detectionDir = path.join(this.workflowDir, 'detection');
392
+ this.generatedDir = path.join(this.workflowDir, 'generated');
393
+ this.baselineDir = path.join(this.workflowDir, 'baseline');
394
+ this.options = options;
395
+ this.state = null;
396
+ }
397
+
398
+ /**
399
+ * Setup directories
400
+ */
401
+ setupDirectories(): void {
402
+ const dirs = [
403
+ this.workflowDir,
404
+ this.detectionDir,
405
+ this.generatedDir,
406
+ this.baselineDir
407
+ ];
408
+
409
+ for (const dir of dirs) {
410
+ if (!fs.existsSync(dir)) {
411
+ fs.mkdirSync(dir, { recursive: true });
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Load workflow state
418
+ */
419
+ loadState(): boolean {
420
+ if (fs.existsSync(this.stateFile)) {
421
+ try {
422
+ this.state = JSON.parse(fs.readFileSync(this.stateFile, 'utf-8')) as OnboardWorkflowState;
423
+ return true;
424
+ } catch {
425
+ this.state = { ...DEFAULT_STATE, phases: {}, detection: { stack: null, patterns: null, docs: null }, generated: { config: null, claudeMd: null } };
426
+ return false;
427
+ }
428
+ }
429
+ this.state = { ...DEFAULT_STATE, phases: {}, detection: { stack: null, patterns: null, docs: null }, generated: { config: null, claudeMd: null } };
430
+ return false;
431
+ }
432
+
433
+ /**
434
+ * Save workflow state
435
+ */
436
+ saveState(): void {
437
+ if (!this.state) return;
438
+ this.setupDirectories();
439
+ this.state.lastUpdated = new Date().toISOString();
440
+ fs.writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
441
+ }
442
+
443
+ /**
444
+ * Initialize workflow
445
+ */
446
+ initializeWorkflow(): OnboardWorkflowState {
447
+ this.setupDirectories();
448
+ this.state = {
449
+ ...DEFAULT_STATE,
450
+ startedAt: new Date().toISOString(),
451
+ lastUpdated: new Date().toISOString(),
452
+ phases: {},
453
+ detection: { stack: null, patterns: null, docs: null },
454
+ generated: { config: null, claudeMd: null }
455
+ };
456
+
457
+ // Initialize phase states
458
+ for (const phaseId of Object.keys(ONBOARD_PHASES)) {
459
+ this.state.phases[phaseId] = {
460
+ status: 'pending',
461
+ startedAt: null,
462
+ completedAt: null,
463
+ result: null,
464
+ error: null
465
+ };
466
+ }
467
+
468
+ this.state.currentPhase = 'detection';
469
+ this.saveState();
470
+ return this.state;
471
+ }
472
+
473
+ /**
474
+ * Check if workflow exists
475
+ */
476
+ hasWorkflow(): boolean {
477
+ return fs.existsSync(this.stateFile);
478
+ }
479
+
480
+ /**
481
+ * Reset workflow
482
+ */
483
+ resetWorkflow(): boolean {
484
+ if (fs.existsSync(this.stateFile)) {
485
+ fs.unlinkSync(this.stateFile);
486
+ }
487
+ this.state = null;
488
+ return true;
489
+ }
490
+
491
+ /**
492
+ * Check if phase dependencies are met
493
+ */
494
+ arePhaseDependenciesMet(phaseId: string): boolean {
495
+ const phase = ONBOARD_PHASES[phaseId];
496
+ if (!phase || !phase.dependencies || phase.dependencies.length === 0) {
497
+ return true;
498
+ }
499
+
500
+ for (const depPhaseId of phase.dependencies) {
501
+ const depPhase = this.state?.phases[depPhaseId];
502
+ if (!depPhase || depPhase.status !== 'completed') {
503
+ return false;
504
+ }
505
+ }
506
+
507
+ return true;
508
+ }
509
+
510
+ /**
511
+ * Get next phase
512
+ */
513
+ getNextPhase(): string | null {
514
+ if (!this.state) return null;
515
+
516
+ const phaseOrder = Object.keys(ONBOARD_PHASES).sort((a, b) => {
517
+ const phaseA = ONBOARD_PHASES[a];
518
+ const phaseB = ONBOARD_PHASES[b];
519
+ return (phaseA?.order ?? 0) - (phaseB?.order ?? 0);
520
+ });
521
+
522
+ for (const phaseId of phaseOrder) {
523
+ const phase = this.state.phases[phaseId];
524
+ if (phase?.status === 'pending' && this.arePhaseDependenciesMet(phaseId)) {
525
+ return phaseId;
526
+ }
527
+ }
528
+
529
+ return null;
530
+ }
531
+
532
+ /**
533
+ * Start a phase
534
+ */
535
+ startPhase(phaseId: string): void {
536
+ if (!this.state) {
537
+ throw new Error('Workflow not initialized');
538
+ }
539
+
540
+ const phase = this.state.phases[phaseId];
541
+ if (!phase) {
542
+ throw new Error(`Unknown phase: ${phaseId}`);
543
+ }
544
+
545
+ phase.status = 'in_progress';
546
+ phase.startedAt = new Date().toISOString();
547
+ this.state.currentPhase = phaseId;
548
+ this.saveState();
549
+ }
550
+
551
+ /**
552
+ * Complete a phase
553
+ */
554
+ completePhase(phaseId: string, result: unknown = null): void {
555
+ if (!this.state) {
556
+ throw new Error('Workflow not initialized');
557
+ }
558
+
559
+ const phase = this.state.phases[phaseId];
560
+ if (!phase) {
561
+ throw new Error(`Unknown phase: ${phaseId}`);
562
+ }
563
+
564
+ phase.status = 'completed';
565
+ phase.completedAt = new Date().toISOString();
566
+ phase.result = result;
567
+ this.saveState();
568
+ }
569
+
570
+ /**
571
+ * Fail a phase
572
+ */
573
+ failPhase(phaseId: string, error: string): void {
574
+ if (!this.state) {
575
+ throw new Error('Workflow not initialized');
576
+ }
577
+
578
+ const phase = this.state.phases[phaseId];
579
+ if (!phase) {
580
+ throw new Error(`Unknown phase: ${phaseId}`);
581
+ }
582
+
583
+ phase.status = 'failed';
584
+ phase.completedAt = new Date().toISOString();
585
+ phase.error = error;
586
+ this.saveState();
587
+ }
588
+
589
+ /**
590
+ * Skip a phase
591
+ */
592
+ skipPhase(phaseId: string): void {
593
+ if (!this.state) {
594
+ throw new Error('Workflow not initialized');
595
+ }
596
+
597
+ const phase = this.state.phases[phaseId];
598
+ if (!phase) {
599
+ throw new Error(`Unknown phase: ${phaseId}`);
600
+ }
601
+
602
+ phase.status = 'skipped';
603
+ phase.completedAt = new Date().toISOString();
604
+ this.saveState();
605
+ }
606
+
607
+ /**
608
+ * Run detection phase
609
+ */
610
+ async runDetection(): Promise<DetectionResult> {
611
+ const stack: StackDetection = {
612
+ framework: null,
613
+ language: null,
614
+ database: null,
615
+ hosting: null,
616
+ testing: null,
617
+ auth: null,
618
+ payments: null,
619
+ detected: []
620
+ };
621
+
622
+ // Read package.json if exists
623
+ const pkgPath = path.join(this.projectRoot, 'package.json');
624
+ let pkg: Record<string, unknown> | null = null;
625
+ if (fs.existsSync(pkgPath)) {
626
+ try {
627
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;
628
+ } catch {
629
+ // Skip
630
+ }
631
+ }
632
+
633
+ const deps = (pkg?.dependencies ?? {}) as Record<string, string>;
634
+ const devDeps = (pkg?.devDependencies ?? {}) as Record<string, string>;
635
+ const allDeps: Record<string, string> = pkg ? { ...deps, ...devDeps } : {};
636
+
637
+ // Check for files and packages
638
+ const checkDetection = (_category: string, detections: DetectionPattern[]): DetectionPattern | null => {
639
+ for (const detection of detections) {
640
+ // Check files
641
+ if (detection.files) {
642
+ for (const file of detection.files) {
643
+ const filePath = path.join(this.projectRoot, file);
644
+ if (fs.existsSync(filePath)) {
645
+ return detection;
646
+ }
647
+ }
648
+ }
649
+
650
+ // Check packages
651
+ if (detection.packages) {
652
+ for (const pkgName of detection.packages) {
653
+ if (allDeps[pkgName]) {
654
+ return detection;
655
+ }
656
+ }
657
+ }
658
+ }
659
+ return null;
660
+ };
661
+
662
+ // Map plural category names to singular for stack object
663
+ const categoryToStackKey: Record<string, keyof StackDetection> = {
664
+ frameworks: 'framework',
665
+ languages: 'language',
666
+ databases: 'database',
667
+ hosting: 'hosting',
668
+ testing: 'testing',
669
+ auth: 'auth',
670
+ payments: 'payments'
671
+ };
672
+
673
+ // Detect each category
674
+ for (const [category, detections] of Object.entries(STACK_DETECTION)) {
675
+ const detected = checkDetection(category, detections);
676
+ if (detected) {
677
+ const stackKey = categoryToStackKey[category] ?? category;
678
+ if (stackKey in stack && stackKey !== 'detected' && stackKey !== 'packageJson') {
679
+ (stack as unknown as Record<string, unknown>)[stackKey] = {
680
+ id: detected.id,
681
+ name: detected.name
682
+ };
683
+ }
684
+ stack.detected.push({
685
+ category,
686
+ ...detected
687
+ });
688
+ }
689
+ }
690
+
691
+ // Run structure analysis
692
+ const structureAnalyzer = new StructureAnalyzer(this.projectRoot);
693
+ const structure = structureAnalyzer.analyze();
694
+
695
+ // Save detection results
696
+ const pkgName = pkg?.name as string | undefined;
697
+ const pkgVersion = pkg?.version as string | undefined;
698
+ const pkgDescription = pkg?.description as string | undefined;
699
+
700
+ const detectionResult: DetectionResult = {
701
+ stack,
702
+ structure,
703
+ packageJson: pkg ? {
704
+ ...(pkgName !== undefined && { name: pkgName }),
705
+ ...(pkgVersion !== undefined && { version: pkgVersion }),
706
+ ...(pkgDescription !== undefined && { description: pkgDescription })
707
+ } : null,
708
+ timestamp: new Date().toISOString()
709
+ };
710
+
711
+ // Write to detection directory
712
+ fs.writeFileSync(
713
+ path.join(this.detectionDir, 'stack.json'),
714
+ JSON.stringify(stack, null, 2)
715
+ );
716
+ fs.writeFileSync(
717
+ path.join(this.detectionDir, 'structure.json'),
718
+ JSON.stringify(structure, null, 2)
719
+ );
720
+
721
+ if (this.state) {
722
+ this.state.detection.stack = stack;
723
+ }
724
+ return detectionResult;
725
+ }
726
+
727
+ /**
728
+ * Run pattern scan phase
729
+ */
730
+ async runPatternScan(): Promise<PatternScanResult> {
731
+ // Auto-detect large codebase and use quick mode
732
+ const structurePath = path.join(this.detectionDir, 'structure.json');
733
+ let fileCount = 0;
734
+ if (fs.existsSync(structurePath)) {
735
+ try {
736
+ const structure = JSON.parse(fs.readFileSync(structurePath, 'utf-8')) as Record<string, unknown>;
737
+ const stats = structure.stats as Record<string, number> | undefined;
738
+ fileCount = stats?.totalFiles ?? 0;
739
+ } catch {
740
+ // Ignore
741
+ }
742
+ }
743
+
744
+ // For large codebases (500+ files), use quick mode automatically
745
+ const LARGE_CODEBASE_THRESHOLD = 500;
746
+ if (fileCount >= LARGE_CODEBASE_THRESHOLD) {
747
+ return this.runQuickPatternScan();
748
+ }
749
+
750
+ // Run architecture analysis
751
+ const architectureAnalyzer = new ArchitectureAnalyzer(this.projectRoot);
752
+ const architecture = architectureAnalyzer.analyze();
753
+
754
+ // Run dependency analysis
755
+ const dependencyAnalyzer = new DependencyAnalyzer(this.projectRoot);
756
+ const dependencies = dependencyAnalyzer.analyze();
757
+
758
+ const patterns: PatternScanResult = {
759
+ architecture,
760
+ dependencies,
761
+ features: this.detectFeatures(),
762
+ timestamp: new Date().toISOString()
763
+ };
764
+
765
+ // Save pattern results
766
+ fs.writeFileSync(
767
+ path.join(this.detectionDir, 'patterns.json'),
768
+ JSON.stringify(patterns, null, 2)
769
+ );
770
+
771
+ const archPatterns = architecture.patterns as unknown[] | undefined;
772
+ const modules = architecture.modules as unknown[] | undefined;
773
+ const circularDeps = dependencies.circularDependencies as unknown[] | undefined;
774
+
775
+ if (this.state) {
776
+ this.state.detection.patterns = {
777
+ architecturePatterns: (archPatterns ?? []).slice(0, 3),
778
+ moduleCount: modules?.length ?? 0,
779
+ circularDeps: circularDeps?.length ?? 0
780
+ };
781
+ }
782
+
783
+ return patterns;
784
+ }
785
+
786
+ /**
787
+ * Run quick pattern scan (skips heavy dependency analysis)
788
+ */
789
+ async runQuickPatternScan(): Promise<PatternScanResult> {
790
+ // Run architecture analysis only (fast)
791
+ const architectureAnalyzer = new ArchitectureAnalyzer(this.projectRoot);
792
+ const architecture = architectureAnalyzer.analyze();
793
+
794
+ // Skip dependency analysis - it's too slow for large codebases
795
+ const patterns: PatternScanResult = {
796
+ architecture,
797
+ dependencies: {
798
+ fileCount: 0,
799
+ circularDependencies: [],
800
+ unusedDependencies: [],
801
+ summary: { totalFiles: 0, circularCount: 0, unusedCount: 0, skipped: true }
802
+ },
803
+ features: this.detectFeatures(),
804
+ timestamp: new Date().toISOString(),
805
+ quickMode: true
806
+ };
807
+
808
+ // Save pattern results
809
+ fs.writeFileSync(
810
+ path.join(this.detectionDir, 'patterns.json'),
811
+ JSON.stringify(patterns, null, 2)
812
+ );
813
+
814
+ const archPatterns = architecture.patterns as unknown[] | undefined;
815
+ const modules = architecture.modules as unknown[] | undefined;
816
+
817
+ if (this.state) {
818
+ this.state.detection.patterns = {
819
+ architecturePatterns: (archPatterns ?? []).slice(0, 3),
820
+ moduleCount: modules?.length ?? 0,
821
+ circularDeps: 0,
822
+ quickMode: true
823
+ };
824
+ }
825
+
826
+ return patterns;
827
+ }
828
+
829
+ /**
830
+ * Detect features in the codebase
831
+ */
832
+ detectFeatures(): FeatureDetection {
833
+ const features: FeatureDetection = {
834
+ auth: false,
835
+ payments: false,
836
+ api: false,
837
+ database: false,
838
+ email: false,
839
+ uploads: false,
840
+ search: false,
841
+ realtime: false,
842
+ analytics: false,
843
+ i18n: false
844
+ };
845
+
846
+ // Check for feature directories
847
+ const featureDirs: Record<string, string[]> = {
848
+ auth: ['auth', 'authentication', 'login'],
849
+ payments: ['payments', 'billing', 'subscriptions', 'checkout'],
850
+ api: ['api', 'routes', 'endpoints'],
851
+ database: ['db', 'database', 'prisma', 'models'],
852
+ email: ['email', 'mail', 'notifications'],
853
+ uploads: ['uploads', 'files', 'storage'],
854
+ search: ['search'],
855
+ realtime: ['websocket', 'realtime', 'socket'],
856
+ analytics: ['analytics', 'tracking'],
857
+ i18n: ['i18n', 'locales', 'translations']
858
+ };
859
+
860
+ const searchPaths = [
861
+ this.projectRoot,
862
+ path.join(this.projectRoot, 'src'),
863
+ path.join(this.projectRoot, 'app'),
864
+ path.join(this.projectRoot, 'lib')
865
+ ];
866
+
867
+ for (const searchPath of searchPaths) {
868
+ if (!fs.existsSync(searchPath)) continue;
869
+
870
+ try {
871
+ const items = fs.readdirSync(searchPath);
872
+ for (const item of items) {
873
+ const lowerItem = item.toLowerCase();
874
+ for (const [feature, dirs] of Object.entries(featureDirs)) {
875
+ if (dirs.includes(lowerItem)) {
876
+ features[feature] = true;
877
+ }
878
+ }
879
+ }
880
+ } catch {
881
+ // Skip
882
+ }
883
+ }
884
+
885
+ return features;
886
+ }
887
+
888
+ /**
889
+ * Run documentation discovery phase
890
+ */
891
+ async runDocDiscovery(): Promise<DocsDiscovery> {
892
+ const docs: DocsDiscovery = {
893
+ found: [],
894
+ missing: [],
895
+ recommended: []
896
+ };
897
+
898
+ // Check for common documentation files
899
+ const commonDocs: DocInfo[] = [
900
+ { file: 'README.md', type: 'readme', required: true },
901
+ { file: 'CONTRIBUTING.md', type: 'contributing', required: false },
902
+ { file: 'CHANGELOG.md', type: 'changelog', required: false },
903
+ { file: 'LICENSE', type: 'license', required: true },
904
+ { file: 'CODE_OF_CONDUCT.md', type: 'code-of-conduct', required: false },
905
+ { file: 'SECURITY.md', type: 'security', required: false },
906
+ { file: 'docs/README.md', type: 'docs-readme', required: false },
907
+ { file: 'API.md', type: 'api-docs', required: false },
908
+ { file: '.env.example', type: 'env-example', required: true }
909
+ ];
910
+
911
+ for (const doc of commonDocs) {
912
+ const docPath = path.join(this.projectRoot, doc.file);
913
+ if (fs.existsSync(docPath)) {
914
+ docs.found.push({
915
+ ...doc,
916
+ path: doc.file,
917
+ size: fs.statSync(docPath).size
918
+ });
919
+ } else if (doc.required) {
920
+ docs.missing.push(doc);
921
+ } else {
922
+ docs.recommended.push(doc);
923
+ }
924
+ }
925
+
926
+ // Check for docs directory
927
+ const docsDir = path.join(this.projectRoot, 'docs');
928
+ if (fs.existsSync(docsDir)) {
929
+ docs.hasDocsDir = true;
930
+ try {
931
+ const docFiles = fs.readdirSync(docsDir);
932
+ docs.docsCount = docFiles.length;
933
+ } catch {
934
+ docs.docsCount = 0;
935
+ }
936
+ }
937
+
938
+ if (this.state) {
939
+ this.state.detection.docs = docs;
940
+ }
941
+ return docs;
942
+ }
943
+
944
+ /**
945
+ * Run config generation phase
946
+ */
947
+ async runConfigGeneration(): Promise<ConfigGenerationResult> {
948
+ const stack = this.state?.detection.stack ?? ({} as StackDetection);
949
+ const patterns = this.state?.detection.patterns ?? {};
950
+
951
+ // Generate bootspring.config.js
952
+ const configContent = this.generateConfigFile(stack, patterns);
953
+ const configPath = path.join(this.projectRoot, 'bootspring.config.js');
954
+
955
+ // Generate CLAUDE.md
956
+ const claudeMdContent = this.generateClaudeMd(stack, patterns);
957
+ const claudeMdPath = path.join(this.projectRoot, 'CLAUDE.md');
958
+
959
+ // Save to generated directory first
960
+ fs.writeFileSync(
961
+ path.join(this.generatedDir, 'bootspring.config.js'),
962
+ configContent
963
+ );
964
+ fs.writeFileSync(
965
+ path.join(this.generatedDir, 'CLAUDE.md'),
966
+ claudeMdContent
967
+ );
968
+
969
+ if (this.state) {
970
+ this.state.generated = {
971
+ config: configPath,
972
+ claudeMd: claudeMdPath,
973
+ timestamp: new Date().toISOString()
974
+ };
975
+ }
976
+
977
+ return {
978
+ configContent,
979
+ claudeMdContent,
980
+ configPath,
981
+ claudeMdPath
982
+ };
983
+ }
984
+
985
+ /**
986
+ * Apply generated config files
987
+ */
988
+ applyGeneratedFiles(): boolean {
989
+ const generatedConfig = path.join(this.generatedDir, 'bootspring.config.js');
990
+ const generatedClaudeMd = path.join(this.generatedDir, 'CLAUDE.md');
991
+
992
+ if (fs.existsSync(generatedConfig)) {
993
+ fs.copyFileSync(generatedConfig, path.join(this.projectRoot, 'bootspring.config.js'));
994
+ }
995
+
996
+ if (fs.existsSync(generatedClaudeMd)) {
997
+ fs.copyFileSync(generatedClaudeMd, path.join(this.projectRoot, 'CLAUDE.md'));
998
+ }
999
+
1000
+ return true;
1001
+ }
1002
+
1003
+ /**
1004
+ * Generate bootspring.config.js content
1005
+ */
1006
+ generateConfigFile(stack: StackDetection, _patterns: Record<string, unknown>): string {
1007
+ const framework = stack.framework?.id ?? 'unknown';
1008
+ const language = stack.language?.id ?? 'javascript';
1009
+ const database = stack.database?.id ?? null;
1010
+ const hosting = stack.hosting?.id ?? null;
1011
+
1012
+ const plugins: string[] = [];
1013
+ if (database) {
1014
+ plugins.push(` database: {\n provider: '${database}',\n features: ['default']\n }`);
1015
+ }
1016
+ if (stack.testing) {
1017
+ plugins.push(` testing: {\n provider: '${stack.testing.id}',\n features: ['default']\n }`);
1018
+ }
1019
+ plugins.push(' security: {\n provider: \'default\',\n features: [\'default\']\n }');
1020
+
1021
+ const pkgName = stack.packageJson?.name ?? 'my-project';
1022
+ const pkgVersion = stack.packageJson?.version ?? '1.0.0';
1023
+ const pkgDescription = stack.packageJson?.description ?? 'A modern web application';
1024
+
1025
+ return `/**
1026
+ * Bootspring Configuration
1027
+ * Generated by: bootspring onboard
1028
+ * Generated at: ${new Date().toISOString()}
1029
+ */
1030
+
1031
+ module.exports = {
1032
+ // Project information
1033
+ project: {
1034
+ name: '${pkgName}',
1035
+ version: '${pkgVersion}',
1036
+ description: '${pkgDescription}'
1037
+ },
1038
+
1039
+ // Technology stack
1040
+ stack: {
1041
+ framework: '${framework}',
1042
+ language: '${language}',
1043
+ database: ${database ? `'${database}'` : 'null'},
1044
+ hosting: ${hosting ? `'${hosting}'` : 'null'}
1045
+ },
1046
+
1047
+ // Enabled plugins
1048
+ plugins: {
1049
+ ${plugins.join(',\n')}
1050
+ },
1051
+
1052
+ // AI context settings
1053
+ context: {
1054
+ // Files to include in AI context
1055
+ include: [
1056
+ 'src/**/*.{ts,tsx,js,jsx}',
1057
+ 'app/**/*.{ts,tsx,js,jsx}',
1058
+ 'lib/**/*.{ts,tsx,js,jsx}',
1059
+ 'components/**/*.{ts,tsx,js,jsx}'
1060
+ ],
1061
+ // Files to exclude from AI context
1062
+ exclude: [
1063
+ 'node_modules/**',
1064
+ 'dist/**',
1065
+ 'build/**',
1066
+ '.next/**',
1067
+ 'coverage/**'
1068
+ ]
1069
+ },
1070
+
1071
+ // Development settings
1072
+ dev: {
1073
+ // Quality gates
1074
+ quality: {
1075
+ preCommit: ['typecheck', 'lint'],
1076
+ prePush: ['typecheck', 'lint', 'test'],
1077
+ preDeploy: ['typecheck', 'lint', 'test', 'build']
1078
+ }
1079
+ }
1080
+ };
1081
+ `;
1082
+ }
1083
+
1084
+ /**
1085
+ * Generate CLAUDE.md content
1086
+ */
1087
+ generateClaudeMd(stack: StackDetection, patterns: Record<string, unknown>): string {
1088
+ const framework = stack.framework?.name ?? 'Unknown Framework';
1089
+ const language = stack.language?.name ?? 'JavaScript';
1090
+ const database = stack.database?.name ?? 'None';
1091
+ const hosting = stack.hosting?.name ?? 'Unknown';
1092
+
1093
+ const features = (patterns.features ?? {}) as Record<string, boolean>;
1094
+ const enabledFeatures = Object.entries(features)
1095
+ .filter(([, v]) => v)
1096
+ .map(([k]) => k);
1097
+
1098
+ const archPatterns = patterns.architecturePatterns as Array<{ name: string; description?: string }> | undefined;
1099
+
1100
+ return `# Project - AI Context
1101
+
1102
+ **Generated by**: Bootspring Onboard
1103
+ **Last Updated**: ${new Date().toISOString().split('T')[0]}
1104
+ **Status**: initialized
1105
+ **Health**: good
1106
+
1107
+ ---
1108
+
1109
+ ## Project Overview
1110
+
1111
+ A modern web application built with ${framework}.
1112
+
1113
+ ---
1114
+
1115
+ ## Tech Stack
1116
+
1117
+ | Component | Technology |
1118
+ |-----------|------------|
1119
+ | Framework | ${framework} |
1120
+ | Language | ${language} |
1121
+ | Database | ${database} |
1122
+ | Hosting | ${hosting} |
1123
+
1124
+ ---
1125
+
1126
+ ## Detected Features
1127
+
1128
+ ${enabledFeatures.length > 0 ? enabledFeatures.map(f => `- **${f}**: Detected`).join('\n') : '- No specific features detected'}
1129
+
1130
+ ---
1131
+
1132
+ ## Architecture
1133
+
1134
+ ${archPatterns && archPatterns.length > 0
1135
+ ? archPatterns.map(p => `- **${p.name}**: ${p.description ?? 'Detected pattern'}`).join('\n')
1136
+ : '- Standard project structure'}
1137
+
1138
+ ---
1139
+
1140
+ ## Development Guidelines
1141
+
1142
+ ### Code Style
1143
+ - Use ${language === 'TypeScript' ? 'TypeScript' : 'JavaScript'} for all new code
1144
+ - Follow existing naming conventions in the codebase
1145
+ - Keep files focused and under 300 lines when possible
1146
+
1147
+ ### Best Practices
1148
+ ${framework.includes('Next') ? '- Use Server Components by default\n- Prefer Server Actions over API routes for mutations' : '- Follow framework best practices'}
1149
+ - Use Zod for all input validation
1150
+ - Never expose API keys to client-side code
1151
+ - Write tests for new features
1152
+
1153
+ ### Git Commits
1154
+ - Use conventional commit format: \`feat:\`, \`fix:\`, \`docs:\`, \`refactor:\`
1155
+ - Keep commits focused and atomic
1156
+ - Never commit sensitive data or API keys
1157
+
1158
+ ---
1159
+
1160
+ ## Current State
1161
+
1162
+ - **Phase**: initialized
1163
+ - **Health**: good
1164
+ - **Open Todos**: 0
1165
+ - **Issues**: none
1166
+
1167
+ ---
1168
+
1169
+ *Generated by [Bootspring](https://bootspring.com) - Development scaffolding with intelligence*
1170
+ `;
1171
+ }
1172
+
1173
+ /**
1174
+ * Run baseline capture phase
1175
+ */
1176
+ async runBaselineCapture(): Promise<Baseline> {
1177
+ const baseline: Baseline = {
1178
+ timestamp: new Date().toISOString(),
1179
+ metrics: {
1180
+ files: 0,
1181
+ lines: 0,
1182
+ dependencies: 0
1183
+ }
1184
+ };
1185
+
1186
+ // Count files
1187
+ const structurePath = path.join(this.detectionDir, 'structure.json');
1188
+ if (fs.existsSync(structurePath)) {
1189
+ const structure = JSON.parse(fs.readFileSync(structurePath, 'utf-8')) as Record<string, unknown>;
1190
+ const stats = structure.stats as Record<string, number> | undefined;
1191
+ baseline.metrics.files = stats?.totalFiles ?? 0;
1192
+ }
1193
+
1194
+ // Count dependencies
1195
+ const pkgPath = path.join(this.projectRoot, 'package.json');
1196
+ if (fs.existsSync(pkgPath)) {
1197
+ try {
1198
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;
1199
+ const deps = (pkg.dependencies ?? {}) as Record<string, unknown>;
1200
+ const devDeps = (pkg.devDependencies ?? {}) as Record<string, unknown>;
1201
+ baseline.metrics.dependencies = Object.keys(deps).length + Object.keys(devDeps).length;
1202
+ } catch {
1203
+ // Skip
1204
+ }
1205
+ }
1206
+
1207
+ // Save baseline
1208
+ fs.writeFileSync(
1209
+ path.join(this.baselineDir, 'metrics.json'),
1210
+ JSON.stringify(baseline, null, 2)
1211
+ );
1212
+
1213
+ if (this.state) {
1214
+ this.state.baseline = baseline;
1215
+ }
1216
+ return baseline;
1217
+ }
1218
+
1219
+ /**
1220
+ * Get workflow progress
1221
+ */
1222
+ getProgress(): OnboardWorkflowProgress | null {
1223
+ if (!this.state) return null;
1224
+
1225
+ const phases: OnboardPhaseProgress[] = Object.entries(ONBOARD_PHASES).map(([phaseId, phase]) => {
1226
+ const phaseState = this.state?.phases[phaseId];
1227
+ const status: OnboardPhaseStatus = phaseState?.status ?? 'pending';
1228
+
1229
+ return {
1230
+ id: phaseId,
1231
+ name: phase.name,
1232
+ description: phase.description,
1233
+ order: phase.order,
1234
+ required: phase.required,
1235
+ status,
1236
+ dependenciesMet: this.arePhaseDependenciesMet(phaseId)
1237
+ };
1238
+ });
1239
+
1240
+ const completedCount = phases.filter(p => p.status === 'completed').length;
1241
+ const requiredCount = phases.filter(p => p.required).length;
1242
+ const requiredCompleted = phases.filter(p => p.required && p.status === 'completed').length;
1243
+
1244
+ return {
1245
+ currentPhase: this.state.currentPhase,
1246
+ startedAt: this.state.startedAt,
1247
+ lastUpdated: this.state.lastUpdated,
1248
+ phases,
1249
+ overall: {
1250
+ completed: completedCount,
1251
+ total: phases.length,
1252
+ percentage: phases.length > 0 ? Math.round((completedCount / phases.length) * 100) : 0
1253
+ },
1254
+ required: {
1255
+ completed: requiredCompleted,
1256
+ total: requiredCount,
1257
+ percentage: requiredCount > 0 ? Math.round((requiredCompleted / requiredCount) * 100) : 0
1258
+ },
1259
+ isComplete: requiredCompleted === requiredCount,
1260
+ detection: this.state.detection
1261
+ };
1262
+ }
1263
+
1264
+ /**
1265
+ * Get resume point
1266
+ */
1267
+ getResumePoint(): OnboardResumePoint | null {
1268
+ if (!this.state || !this.state.currentPhase) {
1269
+ return null;
1270
+ }
1271
+
1272
+ const phase = ONBOARD_PHASES[this.state.currentPhase];
1273
+ const phaseState = this.state.phases[this.state.currentPhase];
1274
+
1275
+ return {
1276
+ phase: this.state.currentPhase,
1277
+ phaseName: phase?.name,
1278
+ phaseStatus: phaseState?.status,
1279
+ lastUpdated: this.state.lastUpdated
1280
+ };
1281
+ }
1282
+ }
1283
+
1284
+ // ============================================================================
1285
+ // Factory Function
1286
+ // ============================================================================
1287
+
1288
+ export function createOnboardWorkflowEngine(
1289
+ projectRoot: string,
1290
+ options: OnboardWorkflowOptions = {}
1291
+ ): OnboardWorkflowEngine {
1292
+ return new OnboardWorkflowEngine(projectRoot, options);
1293
+ }