@girardmedia/bootspring 1.2.0 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
package/cli/project.js ADDED
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Bootspring Project Command
3
+ *
4
+ * Manage projects from the CLI.
5
+ *
6
+ * @package bootspring
7
+ * @module cli/project
8
+ */
9
+
10
+ const https = require('https');
11
+ const http = require('http');
12
+ const auth = require('../core/auth');
13
+ const session = require('../core/session');
14
+ const readline = require('readline');
15
+
16
+ const API_BASE = process.env.BOOTSPRING_API_URL || 'https://www.bootspring.com';
17
+
18
+ /**
19
+ * Make direct API request (without v1 prefix)
20
+ */
21
+ async function apiRequest(method, path, data = null) {
22
+ const token = auth.getToken();
23
+ const apiKey = auth.getApiKey();
24
+ const url = new URL(`/api${path}`, API_BASE);
25
+ const isHttps = url.protocol === 'https:';
26
+ const httpModule = isHttps ? https : http;
27
+
28
+ const headers = {
29
+ 'Content-Type': 'application/json',
30
+ 'User-Agent': `bootspring-cli/${require('../package.json').version}`,
31
+ };
32
+
33
+ // Use API key if available, otherwise use JWT token
34
+ if (apiKey) {
35
+ headers['X-API-Key'] = apiKey;
36
+ } else if (token) {
37
+ headers['Authorization'] = `Bearer ${token}`;
38
+ }
39
+
40
+ return new Promise((resolve, reject) => {
41
+ const req = httpModule.request(url, {
42
+ method,
43
+ headers,
44
+ timeout: 30000
45
+ }, (res) => {
46
+ let body = '';
47
+ res.on('data', chunk => body += chunk);
48
+ res.on('end', () => {
49
+ try {
50
+ const json = JSON.parse(body);
51
+ if (res.statusCode >= 400) {
52
+ const error = new Error(json.message || json.error || 'API Error');
53
+ error.status = res.statusCode;
54
+ error.code = json.error || json.code;
55
+ reject(error);
56
+ } else {
57
+ resolve(json);
58
+ }
59
+ } catch {
60
+ if (res.statusCode >= 400) {
61
+ const error = new Error(body || 'API Error');
62
+ error.status = res.statusCode;
63
+ reject(error);
64
+ } else {
65
+ resolve(body);
66
+ }
67
+ }
68
+ });
69
+ });
70
+
71
+ req.on('error', reject);
72
+ req.on('timeout', () => {
73
+ req.destroy();
74
+ reject(new Error('Request timeout'));
75
+ });
76
+
77
+ if (data) {
78
+ req.write(JSON.stringify(data));
79
+ }
80
+ req.end();
81
+ });
82
+ }
83
+
84
+ const BRAND = {
85
+ name: 'Bootspring',
86
+ color: '\x1b[36m',
87
+ reset: '\x1b[0m',
88
+ bold: '\x1b[1m',
89
+ dim: '\x1b[2m',
90
+ green: '\x1b[32m',
91
+ yellow: '\x1b[33m',
92
+ red: '\x1b[31m'
93
+ };
94
+
95
+ /**
96
+ * Prompt for input
97
+ */
98
+ function prompt(question) {
99
+ const rl = readline.createInterface({
100
+ input: process.stdin,
101
+ output: process.stdout
102
+ });
103
+
104
+ return new Promise((resolve) => {
105
+ rl.question(question, (answer) => {
106
+ rl.close();
107
+ resolve(answer.trim());
108
+ });
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Print a single project
114
+ */
115
+ function printProject(project, currentProject, showOwner = false) {
116
+ const isCurrent = currentProject && currentProject.id === project.id;
117
+ const marker = isCurrent ? `${BRAND.green}→${BRAND.reset} ` : ' ';
118
+ const activeTag = project.isActive ? '' : ` ${BRAND.dim}(inactive)${BRAND.reset}`;
119
+ const roleTag = project.role && project.role !== 'owner' ? ` ${BRAND.dim}(${project.role})${BRAND.reset}` : '';
120
+
121
+ console.log(`${marker}${BRAND.color}${project.name}${BRAND.reset}${roleTag}${activeTag}`);
122
+ console.log(` ${BRAND.dim}ID: ${project.id}${BRAND.reset}`);
123
+ console.log(` ${BRAND.dim}Slug: ${project.slug}${BRAND.reset}`);
124
+ if (showOwner && project.owner) {
125
+ console.log(` ${BRAND.dim}Owner: ${project.owner.email}${BRAND.reset}`);
126
+ }
127
+ if (project.description) {
128
+ console.log(` ${BRAND.dim}${project.description}${BRAND.reset}`);
129
+ }
130
+ console.log(` ${BRAND.dim}API Keys: ${project.apiKeyCount}${BRAND.reset}`);
131
+ if (project.memberCount > 1) {
132
+ console.log(` ${BRAND.dim}Members: ${project.memberCount}${BRAND.reset}`);
133
+ }
134
+ console.log();
135
+ }
136
+
137
+ /**
138
+ * List all projects (owned and shared)
139
+ */
140
+ async function listProjects() {
141
+ if (!auth.isAuthenticated()) {
142
+ console.log(`${BRAND.red}Not logged in.${BRAND.reset} Run: bootspring auth login`);
143
+ process.exit(1);
144
+ }
145
+
146
+ try {
147
+ const response = await apiRequest('GET', '/projects?grouped=true');
148
+ const owned = response.owned || [];
149
+ const shared = response.shared || [];
150
+
151
+ if (owned.length === 0 && shared.length === 0) {
152
+ console.log(`\n${BRAND.dim}No projects found.${BRAND.reset}`);
153
+ console.log(`Create one with: ${BRAND.color}bootspring project create${BRAND.reset}\n`);
154
+ return;
155
+ }
156
+
157
+ const currentProject = session.getEffectiveProject();
158
+
159
+ // Show owned projects
160
+ if (owned.length > 0) {
161
+ console.log(`\n${BRAND.bold}Your Projects${BRAND.reset}\n`);
162
+ for (const project of owned) {
163
+ printProject(project, currentProject, false);
164
+ }
165
+ }
166
+
167
+ // Show shared projects
168
+ if (shared.length > 0) {
169
+ console.log(`${BRAND.bold}Shared With You${BRAND.reset}\n`);
170
+ for (const project of shared) {
171
+ printProject(project, currentProject, true);
172
+ }
173
+ }
174
+
175
+ console.log(`${BRAND.dim}Limits: ${response.limits.current}/${response.limits.limit} projects${BRAND.reset}\n`);
176
+ } catch (error) {
177
+ console.log(`${BRAND.red}Error: ${error.message}${BRAND.reset}`);
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Parse flags from args
184
+ */
185
+ function parseFlags(args) {
186
+ const flags = {};
187
+ const positional = [];
188
+
189
+ for (let i = 0; i < args.length; i++) {
190
+ const arg = args[i];
191
+ if (arg.startsWith('--')) {
192
+ const [key, ...valueParts] = arg.slice(2).split('=');
193
+ flags[key] = valueParts.length > 0 ? valueParts.join('=') : (args[i + 1] && !args[i + 1].startsWith('-') ? args[++i] : true);
194
+ } else if (arg.startsWith('-')) {
195
+ flags[arg.slice(1)] = args[i + 1] && !args[i + 1].startsWith('-') ? args[++i] : true;
196
+ } else {
197
+ positional.push(arg);
198
+ }
199
+ }
200
+
201
+ return { flags, positional };
202
+ }
203
+
204
+ /**
205
+ * Check for similar projects (duplicate detection)
206
+ */
207
+ async function checkSimilarProjects(name, repoUrl) {
208
+ try {
209
+ const params = new URLSearchParams();
210
+ if (name) params.set('name', name);
211
+ if (repoUrl) params.set('repo', repoUrl);
212
+
213
+ const response = await apiRequest('GET', `/projects/similar?${params.toString()}`);
214
+ return response.matches || [];
215
+ } catch (error) {
216
+ // Don't block creation if similar check fails
217
+ console.log(`${BRAND.dim}(Could not check for similar projects)${BRAND.reset}`);
218
+ return [];
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Join an existing project as a member
224
+ */
225
+ async function joinProject(projectId) {
226
+ try {
227
+ // Get current user's email to add themselves
228
+ // For now, we'll inform the user to request access
229
+ console.log(`\n${BRAND.yellow}To join this project, ask the owner to add you as a member.${BRAND.reset}`);
230
+ console.log(`${BRAND.dim}They can do this from the project settings in the dashboard.${BRAND.reset}\n`);
231
+ return false;
232
+ } catch (error) {
233
+ console.log(`${BRAND.red}Error joining project: ${error.message}${BRAND.reset}`);
234
+ return false;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Create a new project
240
+ */
241
+ async function createProject(args) {
242
+ if (!auth.isAuthenticated()) {
243
+ console.log(`${BRAND.red}Not logged in.${BRAND.reset} Run: bootspring auth login`);
244
+ process.exit(1);
245
+ }
246
+
247
+ const { flags, positional } = parseFlags(args);
248
+ const isNonInteractive = flags.y || flags.yes || !process.stdin.isTTY;
249
+ const skipDuplicateCheck = flags['skip-duplicate-check'] || flags.force;
250
+
251
+ console.log(`\n${BRAND.bold}Create New Project${BRAND.reset}\n`);
252
+
253
+ // Get project name from args or prompt
254
+ let name = positional[0] || flags.name;
255
+ if (!name && !isNonInteractive) {
256
+ name = await prompt(`${BRAND.color}Project name:${BRAND.reset} `);
257
+ }
258
+
259
+ if (!name || name.trim().length === 0) {
260
+ console.log(`${BRAND.red}Project name is required.${BRAND.reset}`);
261
+ console.log(`\n${BRAND.dim}Usage: bootspring project create <name> [--description "..."] [--framework nodejs] [-y]${BRAND.reset}\n`);
262
+ process.exit(1);
263
+ }
264
+
265
+ // Get repository URL from flag
266
+ const repoUrl = flags.repo || flags.repository;
267
+
268
+ // Check for similar projects (duplicate detection)
269
+ if (!skipDuplicateCheck && !isNonInteractive) {
270
+ console.log(`${BRAND.dim}Checking for similar projects...${BRAND.reset}`);
271
+ const similar = await checkSimilarProjects(name.trim(), repoUrl);
272
+
273
+ if (similar.length > 0) {
274
+ console.log(`\n${BRAND.yellow}Similar project(s) found:${BRAND.reset}\n`);
275
+
276
+ for (const match of similar) {
277
+ const reasonMap = {
278
+ github_repo: 'GitHub repo match',
279
+ slug_match: 'Same slug',
280
+ name_similar: 'Similar name'
281
+ };
282
+ const reason = reasonMap[match.matchReason] || match.matchReason;
283
+ const ownerInfo = match.owner ? ` (owned by ${match.owner.email})` : '';
284
+ console.log(` ${BRAND.color}${match.name}${BRAND.reset}${ownerInfo}`);
285
+ console.log(` ${BRAND.dim}[${reason}]${BRAND.reset}\n`);
286
+ }
287
+
288
+ const proceed = await prompt(`${BRAND.color}Create a new project anyway? (y/N):${BRAND.reset} `);
289
+ if (proceed.toLowerCase() !== 'y') {
290
+ console.log(`\n${BRAND.dim}To join an existing project, ask the owner to add you.${BRAND.reset}\n`);
291
+ return;
292
+ }
293
+ console.log();
294
+ }
295
+ }
296
+
297
+ // Get description from flag or prompt
298
+ let description = flags.description || flags.d;
299
+ if (!description && !isNonInteractive) {
300
+ description = await prompt(`${BRAND.dim}Description (optional):${BRAND.reset} `);
301
+ }
302
+
303
+ // Get framework from flag or prompt
304
+ let framework = flags.framework || flags.f;
305
+ if (!framework && !isNonInteractive) {
306
+ framework = await prompt(`${BRAND.dim}Framework (e.g., nextjs, react, nodejs):${BRAND.reset} `);
307
+ }
308
+
309
+ console.log(`\n${BRAND.dim}Creating project...${BRAND.reset}`);
310
+
311
+ try {
312
+ const project = await apiRequest('POST', '/projects', {
313
+ name: name.trim(),
314
+ description: description || undefined,
315
+ framework: framework || undefined,
316
+ repositoryUrl: repoUrl || undefined
317
+ });
318
+
319
+ console.log(`\n${BRAND.green}✓${BRAND.reset} Project created successfully!\n`);
320
+ console.log(` ${BRAND.bold}Name:${BRAND.reset} ${project.name}`);
321
+ console.log(` ${BRAND.bold}ID:${BRAND.reset} ${project.id}`);
322
+ console.log(` ${BRAND.bold}Slug:${BRAND.reset} ${project.slug}`);
323
+
324
+ // Ask if user wants to switch to this project (auto-yes in non-interactive mode)
325
+ let switchTo = 'y';
326
+ if (!isNonInteractive) {
327
+ switchTo = await prompt(`\n${BRAND.color}Switch to this project now? (Y/n):${BRAND.reset} `);
328
+ }
329
+
330
+ if (switchTo.toLowerCase() !== 'n') {
331
+ session.setCurrentProject({
332
+ id: project.id,
333
+ name: project.name,
334
+ slug: project.slug
335
+ });
336
+ console.log(`\n${BRAND.green}✓${BRAND.reset} Switched to ${BRAND.color}${project.name}${BRAND.reset}\n`);
337
+ } else {
338
+ console.log(`\n${BRAND.dim}Run 'bootspring switch ${project.slug}' to switch to this project.${BRAND.reset}\n`);
339
+ }
340
+ } catch (error) {
341
+ if (error.status === 403 && error.message.includes('limit')) {
342
+ console.log(`\n${BRAND.yellow}Project limit reached.${BRAND.reset}`);
343
+ console.log(`${BRAND.dim}Upgrade your plan at: https://bootspring.com/dashboard/billing${BRAND.reset}\n`);
344
+ } else {
345
+ console.log(`\n${BRAND.red}Error: ${error.message}${BRAND.reset}`);
346
+ }
347
+ process.exit(1);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Show current project info
353
+ */
354
+ async function showProjectInfo() {
355
+ const project = session.getEffectiveProject();
356
+
357
+ if (!project) {
358
+ console.log(`\n${BRAND.yellow}No project context set.${BRAND.reset}`);
359
+ console.log(`${BRAND.dim}Run 'bootspring switch' to select a project.${BRAND.reset}\n`);
360
+ return;
361
+ }
362
+
363
+ console.log(`\n${BRAND.bold}Current Project${BRAND.reset}\n`);
364
+ console.log(` ${BRAND.bold}Name:${BRAND.reset} ${BRAND.color}${project.name}${BRAND.reset}`);
365
+ console.log(` ${BRAND.bold}ID:${BRAND.reset} ${project.id}`);
366
+ if (project.slug) {
367
+ console.log(` ${BRAND.bold}Slug:${BRAND.reset} ${project.slug}`);
368
+ }
369
+ console.log(` ${BRAND.bold}Source:${BRAND.reset} ${project.source || 'session'}`);
370
+ console.log();
371
+ }
372
+
373
+ /**
374
+ * Show usage for this command
375
+ */
376
+ function showUsage() {
377
+ console.log(`
378
+ ${BRAND.bold}Usage:${BRAND.reset} bootspring project <command> [args]
379
+
380
+ ${BRAND.bold}Commands:${BRAND.reset}
381
+ list List all projects (owned and shared)
382
+ create [name] Create a new project
383
+ info Show current project info
384
+
385
+ ${BRAND.bold}Options for create:${BRAND.reset}
386
+ --description, -d Project description
387
+ --framework, -f Project framework (e.g., nextjs, nodejs)
388
+ --repo Repository URL
389
+ --force Skip duplicate project check
390
+ -y, --yes Non-interactive mode
391
+
392
+ ${BRAND.bold}Examples:${BRAND.reset}
393
+ bootspring project list
394
+ bootspring project create "My App"
395
+ bootspring project create "My App" --framework nextjs --repo https://github.com/user/repo
396
+ bootspring project info
397
+ `);
398
+ }
399
+
400
+ /**
401
+ * Main entry point
402
+ */
403
+ async function run(args) {
404
+ const subcommand = args[0];
405
+
406
+ switch (subcommand) {
407
+ case 'list':
408
+ case 'ls':
409
+ await listProjects();
410
+ break;
411
+
412
+ case 'create':
413
+ case 'new':
414
+ await createProject(args.slice(1));
415
+ break;
416
+
417
+ case 'info':
418
+ case 'current':
419
+ await showProjectInfo();
420
+ break;
421
+
422
+ case undefined:
423
+ case 'help':
424
+ case '--help':
425
+ case '-h':
426
+ showUsage();
427
+ break;
428
+
429
+ default:
430
+ console.log(`${BRAND.red}Unknown subcommand: ${subcommand}${BRAND.reset}`);
431
+ showUsage();
432
+ process.exit(1);
433
+ }
434
+ }
435
+
436
+ module.exports = { run };
package/cli/quality.js CHANGED
@@ -12,6 +12,15 @@ const path = require('path');
12
12
  const config = require('../core/config');
13
13
  const utils = require('../core/utils');
14
14
 
15
+ // Lazy load audit workflow
16
+ let AuditWorkflowEngine = null;
17
+ function getAuditEngine() {
18
+ if (!AuditWorkflowEngine) {
19
+ AuditWorkflowEngine = require('../core/audit-workflow').AuditWorkflowEngine;
20
+ }
21
+ return AuditWorkflowEngine;
22
+ }
23
+
15
24
  /**
16
25
  * Detect project type and available tooling
17
26
  * @param {string} projectRoot - Project root path
@@ -422,6 +431,217 @@ ${utils.COLORS.bold}Available Gates${utils.COLORS.reset}
422
431
  console.log(` ${utils.COLORS.dim}npx husky add .husky/pre-commit "bootspring quality pre-commit"${utils.COLORS.reset}`);
423
432
  }
424
433
 
434
+ /**
435
+ * Run full quality pipeline (quality gates + audit)
436
+ */
437
+ async function runFullPipeline(options = {}) {
438
+ const cfg = config.load();
439
+ const projectRoot = cfg._projectRoot;
440
+
441
+ console.log(`
442
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Full Quality Pipeline${utils.COLORS.reset}
443
+ ${utils.COLORS.dim}Quality gates + codebase audit${utils.COLORS.reset}
444
+ `);
445
+
446
+ const results = {
447
+ qualityGate: null,
448
+ audit: null,
449
+ timestamp: new Date().toISOString(),
450
+ passed: false
451
+ };
452
+
453
+ // Phase 1: Pre-deploy quality gate
454
+ utils.print.header('Phase 1: Quality Gates');
455
+ results.qualityGate = await runGate('pre-deploy', {
456
+ strict: false,
457
+ skip: options.skip ? [options.skip] : []
458
+ });
459
+
460
+ if (results.qualityGate.failed > 0 && options.strict) {
461
+ utils.print.error('Quality gates failed. Stopping pipeline.');
462
+ return results;
463
+ }
464
+
465
+ console.log('');
466
+
467
+ // Phase 2: Bootspring audit
468
+ utils.print.header('Phase 2: Codebase Audit');
469
+
470
+ try {
471
+ const Engine = getAuditEngine();
472
+ const workflow = new Engine(projectRoot, { ciMode: true });
473
+
474
+ if (workflow.hasWorkflow()) {
475
+ workflow.loadState();
476
+ const progress = workflow.getProgress();
477
+ if (progress.isComplete) {
478
+ console.log(`${utils.COLORS.dim}Using existing audit results${utils.COLORS.reset}`);
479
+ results.audit = {
480
+ findings: progress.findings,
481
+ passed: progress.findings.critical === 0 && progress.findings.high === 0
482
+ };
483
+ } else {
484
+ // Reset and run fresh
485
+ workflow.resetWorkflow();
486
+ workflow.initializeWorkflow();
487
+ results.audit = await runAuditPhases(workflow);
488
+ }
489
+ } else {
490
+ workflow.initializeWorkflow();
491
+ results.audit = await runAuditPhases(workflow);
492
+ }
493
+ } catch (error) {
494
+ console.log(`${utils.COLORS.yellow}⚠${utils.COLORS.reset} Audit skipped: ${error.message}`);
495
+ results.audit = { skipped: true, error: error.message };
496
+ }
497
+
498
+ console.log('');
499
+
500
+ // Phase 3: Generate combined report
501
+ utils.print.header('Combined Quality Report');
502
+ results.passed = generateCombinedReport(results, projectRoot);
503
+
504
+ return results;
505
+ }
506
+
507
+ /**
508
+ * Run audit phases
509
+ */
510
+ async function runAuditPhases(workflow) {
511
+ const phases = ['quality', 'security', 'practices'];
512
+ const findings = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
513
+
514
+ for (const phaseId of phases) {
515
+ workflow.startPhase(phaseId);
516
+ const spinner = utils.createSpinner(`Auditing: ${phaseId}`).start();
517
+
518
+ try {
519
+ let result;
520
+ switch (phaseId) {
521
+ case 'quality':
522
+ result = await workflow.runQualityMetrics();
523
+ spinner.succeed(`Quality: Score ${result.score}/100`);
524
+ break;
525
+ case 'security':
526
+ result = await workflow.runSecurityScan();
527
+ if (result.critical > 0) {
528
+ spinner.fail(`Security: ${result.critical} critical issues`);
529
+ findings.critical += result.critical;
530
+ } else if (result.high > 0) {
531
+ spinner.warn(`Security: ${result.high} high issues`);
532
+ findings.high += result.high;
533
+ } else {
534
+ spinner.succeed('Security: Passed');
535
+ }
536
+ findings.medium += result.medium || 0;
537
+ findings.low += result.low || 0;
538
+ break;
539
+ case 'practices':
540
+ result = await workflow.runBestPractices();
541
+ spinner.succeed(`Best practices: ${result.passed}/${result.total} passed`);
542
+ break;
543
+ }
544
+ workflow.completePhase(phaseId, result);
545
+ } catch (error) {
546
+ spinner.fail(`${phaseId}: ${error.message}`);
547
+ workflow.failPhase(phaseId, error.message);
548
+ }
549
+ }
550
+
551
+ findings.total = findings.critical + findings.high + findings.medium + findings.low;
552
+
553
+ return {
554
+ findings,
555
+ passed: findings.critical === 0 && findings.high === 0
556
+ };
557
+ }
558
+
559
+ /**
560
+ * Generate combined quality report
561
+ */
562
+ function generateCombinedReport(results, projectRoot) {
563
+ const reportDir = path.join(projectRoot, '.bootspring', 'quality');
564
+ if (!fs.existsSync(reportDir)) {
565
+ fs.mkdirSync(reportDir, { recursive: true });
566
+ }
567
+
568
+ // Calculate overall status
569
+ const qualityPassed = results.qualityGate?.failed === 0;
570
+ const auditPassed = results.audit?.passed !== false;
571
+ const overallPassed = qualityPassed && auditPassed;
572
+
573
+ // Display summary
574
+ console.log('');
575
+ console.log(`${utils.COLORS.bold}Summary${utils.COLORS.reset}`);
576
+ console.log('');
577
+
578
+ // Quality gates
579
+ if (results.qualityGate) {
580
+ const qIcon = qualityPassed ?
581
+ `${utils.COLORS.green}✓${utils.COLORS.reset}` :
582
+ `${utils.COLORS.red}✗${utils.COLORS.reset}`;
583
+ console.log(` ${qIcon} Quality Gates: ${results.qualityGate.passed} passed, ${results.qualityGate.failed} failed`);
584
+ }
585
+
586
+ // Audit
587
+ if (results.audit && !results.audit.skipped) {
588
+ const aIcon = auditPassed ?
589
+ `${utils.COLORS.green}✓${utils.COLORS.reset}` :
590
+ `${utils.COLORS.red}✗${utils.COLORS.reset}`;
591
+ const f = results.audit.findings;
592
+ console.log(` ${aIcon} Codebase Audit: ${f.critical} critical, ${f.high} high, ${f.medium} medium, ${f.low} low`);
593
+ } else if (results.audit?.skipped) {
594
+ console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Codebase Audit: Skipped`);
595
+ }
596
+
597
+ console.log('');
598
+
599
+ // Overall result
600
+ if (overallPassed) {
601
+ utils.print.success('Quality pipeline PASSED');
602
+ } else {
603
+ utils.print.error('Quality pipeline FAILED');
604
+ if (!qualityPassed) {
605
+ console.log(` ${utils.COLORS.dim}Fix quality gate issues before proceeding${utils.COLORS.reset}`);
606
+ }
607
+ if (!auditPassed && results.audit?.findings) {
608
+ if (results.audit.findings.critical > 0) {
609
+ console.log(` ${utils.COLORS.red}● ${results.audit.findings.critical} critical issues require immediate attention${utils.COLORS.reset}`);
610
+ }
611
+ if (results.audit.findings.high > 0) {
612
+ console.log(` ${utils.COLORS.yellow}● ${results.audit.findings.high} high priority issues found${utils.COLORS.reset}`);
613
+ }
614
+ }
615
+ }
616
+
617
+ // Generate report file
618
+ const report = {
619
+ timestamp: results.timestamp,
620
+ passed: overallPassed,
621
+ qualityGates: {
622
+ passed: qualityPassed,
623
+ checks: results.qualityGate?.results || []
624
+ },
625
+ audit: results.audit?.skipped ? { skipped: true } : {
626
+ passed: auditPassed,
627
+ findings: results.audit?.findings || {}
628
+ }
629
+ };
630
+
631
+ const reportPath = path.join(reportDir, 'pipeline-report.json');
632
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
633
+
634
+ console.log('');
635
+ console.log(`${utils.COLORS.dim}Report saved: ${reportPath}${utils.COLORS.reset}`);
636
+
637
+ // Set exit code for CI
638
+ if (!overallPassed) {
639
+ process.exitCode = 1;
640
+ }
641
+
642
+ return overallPassed;
643
+ }
644
+
425
645
  /**
426
646
  * Show quality help
427
647
  */
@@ -437,10 +657,12 @@ ${utils.COLORS.bold}Gates:${utils.COLORS.reset}
437
657
  ${utils.COLORS.cyan}pre-commit${utils.COLORS.reset} Quick checks (types, lint, format)
438
658
  ${utils.COLORS.cyan}pre-push${utils.COLORS.reset} Thorough checks (tests, build)
439
659
  ${utils.COLORS.cyan}pre-deploy${utils.COLORS.reset} Full audit (security, everything)
660
+ ${utils.COLORS.cyan}full${utils.COLORS.reset} Combined: quality gates + codebase audit
440
661
 
441
662
  ${utils.COLORS.bold}Options:${utils.COLORS.reset}
442
663
  --strict Stop on first failure
443
664
  --skip <check> Skip specific check
665
+ --ci CI mode with exit codes
444
666
 
445
667
  ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
446
668
  ${utils.COLORS.cyan}quick${utils.COLORS.reset} Run quick type check only
@@ -450,6 +672,8 @@ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
450
672
  bootspring quality pre-commit
451
673
  bootspring quality pre-push --strict
452
674
  bootspring quality pre-deploy --skip security
675
+ bootspring quality full
676
+ bootspring quality full --ci
453
677
  bootspring quality quick
454
678
  bootspring quality status
455
679
  `);
@@ -477,6 +701,15 @@ async function run(args) {
477
701
  });
478
702
  break;
479
703
 
704
+ case 'full':
705
+ case 'pipeline':
706
+ await runFullPipeline({
707
+ strict: parsedArgs.strict,
708
+ skip: parsedArgs.skip,
709
+ ci: parsedArgs.ci
710
+ });
711
+ break;
712
+
480
713
  case 'quick':
481
714
  case 'check':
482
715
  runQuickCheck();