@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.
- package/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- 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
|
+
}
|