@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/manager.js ADDED
@@ -0,0 +1,711 @@
1
+ /**
2
+ * Bootspring Manager Command
3
+ * Overview of all projects without entering each one
4
+ *
5
+ * Commands:
6
+ * list [--sort] [--type] List all projects with health scores (default)
7
+ * show <name> Detailed project view
8
+ * export Export report as markdown
9
+ * scan Scan for local projects in workspace
10
+ *
11
+ * @package bootspring
12
+ * @command manager
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const config = require('../core/config');
19
+ const utils = require('../core/utils');
20
+ const session = require('../core/session');
21
+ const projectState = require('../core/project-state');
22
+ const checkpointEngine = require('../core/checkpoint-engine');
23
+ const telemetry = require('../core/telemetry');
24
+
25
+ // ============================================================================
26
+ // Project Discovery
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Get all known projects
31
+ * Combines recent projects from session with workspace projects
32
+ * @returns {Array} Array of project objects
33
+ */
34
+ function getKnownProjects() {
35
+ const projects = [];
36
+ const seenPaths = new Set();
37
+
38
+ // Get recent projects from session
39
+ const recentProjects = session.getRecentProjects() || [];
40
+ for (const project of recentProjects) {
41
+ if (project.path && !seenPaths.has(project.path)) {
42
+ seenPaths.add(project.path);
43
+ projects.push({
44
+ ...project,
45
+ source: 'recent'
46
+ });
47
+ }
48
+ }
49
+
50
+ // Get current project
51
+ const currentProject = session.getCurrentProject();
52
+ if (currentProject?.path && !seenPaths.has(currentProject.path)) {
53
+ seenPaths.add(currentProject.path);
54
+ projects.push({
55
+ ...currentProject,
56
+ source: 'current'
57
+ });
58
+ }
59
+
60
+ // Scan workspace file if exists
61
+ const workspaceFile = path.join(os.homedir(), '.bootspring', 'workspace.json');
62
+ if (fs.existsSync(workspaceFile)) {
63
+ try {
64
+ const workspace = JSON.parse(fs.readFileSync(workspaceFile, 'utf-8'));
65
+ for (const projectPath of workspace.projects || []) {
66
+ if (!seenPaths.has(projectPath)) {
67
+ seenPaths.add(projectPath);
68
+ const projectName = path.basename(projectPath);
69
+ projects.push({
70
+ name: projectName,
71
+ path: projectPath,
72
+ source: 'workspace'
73
+ });
74
+ }
75
+ }
76
+ } catch {
77
+ // Ignore workspace read errors
78
+ }
79
+ }
80
+
81
+ return projects;
82
+ }
83
+
84
+ /**
85
+ * Get detailed info for a project
86
+ * @param {object} project - Project object with path
87
+ * @returns {object} Enhanced project with state and health
88
+ */
89
+ function getProjectDetails(project) {
90
+ const projectRoot = project.path;
91
+
92
+ if (!projectRoot || !fs.existsSync(projectRoot)) {
93
+ return {
94
+ ...project,
95
+ exists: false,
96
+ health: 0,
97
+ checkpointProgress: { percentage: 0, completed: 0, total: 0 },
98
+ projectType: null,
99
+ github: null,
100
+ lastActivity: null
101
+ };
102
+ }
103
+
104
+ // Load state
105
+ const state = projectState.loadState(projectRoot);
106
+
107
+ // Get checkpoint progress
108
+ let checkpointProgress = { percentage: 0, completed: 0, total: 0 };
109
+ if (state) {
110
+ checkpointProgress = projectState.getCheckpointProgress(projectRoot);
111
+ }
112
+
113
+ // Get last activity from git or file timestamps
114
+ let lastActivity = null;
115
+ try {
116
+ const gitHeadPath = path.join(projectRoot, '.git', 'HEAD');
117
+ if (fs.existsSync(gitHeadPath)) {
118
+ const stats = fs.statSync(gitHeadPath);
119
+ lastActivity = stats.mtime;
120
+ }
121
+ } catch {
122
+ // Ignore git errors
123
+ }
124
+
125
+ // Calculate health score
126
+ let health = 0;
127
+ if (state?.health?.score) {
128
+ health = state.health.score;
129
+ } else if (checkpointProgress.percentage > 0) {
130
+ // Calculate basic health from checkpoint progress
131
+ health = Math.round(checkpointProgress.percentage * 0.4); // 40% weight
132
+ if (state?.github?.connected) health += 20;
133
+ // Check for quality files
134
+ if (fs.existsSync(path.join(projectRoot, 'tsconfig.json'))) health += 10;
135
+ if (fs.existsSync(path.join(projectRoot, '.eslintrc')) ||
136
+ fs.existsSync(path.join(projectRoot, 'eslint.config.js'))) health += 10;
137
+ if (fs.existsSync(path.join(projectRoot, '__tests__')) ||
138
+ fs.existsSync(path.join(projectRoot, 'tests'))) health += 10;
139
+ }
140
+
141
+ return {
142
+ ...project,
143
+ exists: true,
144
+ health,
145
+ healthGrade: getHealthGrade(health),
146
+ checkpointProgress,
147
+ projectType: state?.projectType || 'development',
148
+ autoTagged: state?.autoTagged || false,
149
+ github: state?.github || null,
150
+ usage: state?.usage || null,
151
+ lastActivity,
152
+ stateExists: !!state
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Get health grade
158
+ * @param {number} score - Health score
159
+ * @returns {object} Grade object
160
+ */
161
+ function getHealthGrade(score) {
162
+ if (score >= 90) return { grade: 'A', color: utils.COLORS.green };
163
+ if (score >= 80) return { grade: 'B', color: utils.COLORS.green };
164
+ if (score >= 70) return { grade: 'C', color: utils.COLORS.yellow };
165
+ if (score >= 60) return { grade: 'D', color: utils.COLORS.yellow };
166
+ return { grade: 'F', color: utils.COLORS.red };
167
+ }
168
+
169
+ // ============================================================================
170
+ // Command Handlers
171
+ // ============================================================================
172
+
173
+ /**
174
+ * List all projects
175
+ * @param {object} options - Options
176
+ */
177
+ function handleList(options = {}) {
178
+ const projects = getKnownProjects();
179
+
180
+ if (projects.length === 0) {
181
+ console.log(`
182
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project Manager${utils.COLORS.reset}
183
+
184
+ ${utils.COLORS.dim}No projects found.${utils.COLORS.reset}
185
+
186
+ ${utils.COLORS.bold}To add projects:${utils.COLORS.reset}
187
+ 1. Use ${utils.COLORS.cyan}bootspring init${utils.COLORS.reset} in a project directory
188
+ 2. Or add to workspace: ${utils.COLORS.cyan}bootspring workspace add <path>${utils.COLORS.reset}
189
+ `);
190
+ return;
191
+ }
192
+
193
+ // Enhance with details
194
+ const enrichedProjects = projects.map(getProjectDetails);
195
+
196
+ // Filter by type if specified
197
+ let filteredProjects = enrichedProjects;
198
+ if (options.type) {
199
+ filteredProjects = enrichedProjects.filter(p => p.projectType === options.type);
200
+ }
201
+
202
+ // Sort projects
203
+ if (options.sort === 'health') {
204
+ filteredProjects.sort((a, b) => b.health - a.health);
205
+ } else if (options.sort === 'activity') {
206
+ filteredProjects.sort((a, b) => {
207
+ if (!a.lastActivity) return 1;
208
+ if (!b.lastActivity) return -1;
209
+ return new Date(b.lastActivity) - new Date(a.lastActivity);
210
+ });
211
+ } else if (options.sort === 'name') {
212
+ filteredProjects.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
213
+ }
214
+
215
+ // JSON output
216
+ if (options.json) {
217
+ console.log(JSON.stringify(filteredProjects, null, 2));
218
+ return;
219
+ }
220
+
221
+ // Display
222
+ console.log(`
223
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project Manager${utils.COLORS.reset}
224
+ ${utils.COLORS.dim}${filteredProjects.length} project(s)${utils.COLORS.reset}
225
+ `);
226
+
227
+ // Get current project for highlighting
228
+ const currentProject = session.getCurrentProject();
229
+ const currentPath = currentProject?.path;
230
+
231
+ for (const project of filteredProjects) {
232
+ const isCurrent = project.path === currentPath;
233
+ const marker = isCurrent ? `${utils.COLORS.green}›${utils.COLORS.reset} ` : ' ';
234
+
235
+ // Type badge
236
+ const typeBadge = getTypeBadge(project.projectType);
237
+
238
+ // Health indicator
239
+ const healthGrade = project.healthGrade || getHealthGrade(0);
240
+ const healthBadge = `${healthGrade.color}${healthGrade.grade}${utils.COLORS.reset}`;
241
+
242
+ // Checkpoint progress
243
+ const progress = project.checkpointProgress;
244
+ const progressBar = checkpointEngine.getColoredProgressBar(progress.percentage, 10);
245
+
246
+ // Name with current indicator
247
+ const nameStyle = isCurrent ? utils.COLORS.bold : '';
248
+ const name = project.name || path.basename(project.path || 'Unknown');
249
+
250
+ console.log(`${marker}${nameStyle}${name}${utils.COLORS.reset} ${typeBadge}`);
251
+ console.log(` Health: ${healthBadge} ${project.health}/100 Checkpoints: ${progressBar} ${progress.completed}/${progress.total}`);
252
+
253
+ // GitHub status
254
+ if (project.github?.connected) {
255
+ console.log(` ${utils.COLORS.dim}GitHub: ${project.github.owner}/${project.github.repo}${utils.COLORS.reset}`);
256
+ }
257
+
258
+ // Last activity
259
+ if (project.lastActivity) {
260
+ console.log(` ${utils.COLORS.dim}Last activity: ${utils.formatRelativeTime(project.lastActivity)}${utils.COLORS.reset}`);
261
+ }
262
+
263
+ console.log();
264
+ }
265
+
266
+ // Summary
267
+ const totalHealth = filteredProjects.reduce((sum, p) => sum + p.health, 0);
268
+ const avgHealth = filteredProjects.length > 0 ? Math.round(totalHealth / filteredProjects.length) : 0;
269
+ const connectedCount = filteredProjects.filter(p => p.github?.connected).length;
270
+
271
+ console.log(`${utils.COLORS.bold}Summary:${utils.COLORS.reset}`);
272
+ console.log(` Average health: ${avgHealth}/100`);
273
+ console.log(` GitHub connected: ${connectedCount}/${filteredProjects.length}`);
274
+ console.log();
275
+
276
+ // Emit telemetry
277
+ telemetry.emitEvent('manager:list', {
278
+ projectCount: filteredProjects.length,
279
+ avgHealth,
280
+ sortBy: options.sort || 'default'
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Get type badge
286
+ * @param {string} projectType - Project type
287
+ * @returns {string} Formatted badge
288
+ */
289
+ function getTypeBadge(projectType) {
290
+ const badges = {
291
+ development: `${utils.COLORS.cyan}[DEV]${utils.COLORS.reset}`,
292
+ content: `${utils.COLORS.magenta}[CONTENT]${utils.COLORS.reset}`,
293
+ business: `${utils.COLORS.yellow}[BUSINESS]${utils.COLORS.reset}`
294
+ };
295
+ return badges[projectType] || badges.development;
296
+ }
297
+
298
+ /**
299
+ * Show detailed project view
300
+ * @param {string} nameOrPath - Project name or path
301
+ * @param {object} options - Options
302
+ */
303
+ function handleShow(nameOrPath, options = {}) {
304
+ if (!nameOrPath) {
305
+ utils.print.error('Please specify a project name or path.');
306
+ console.log(`\n${utils.COLORS.bold}Usage:${utils.COLORS.reset} bootspring manager show <name>`);
307
+ return;
308
+ }
309
+
310
+ const projects = getKnownProjects();
311
+
312
+ // Find project by name or path
313
+ let project = projects.find(p =>
314
+ p.name === nameOrPath ||
315
+ p.path === nameOrPath ||
316
+ (p.path && path.basename(p.path) === nameOrPath)
317
+ );
318
+
319
+ // Try as absolute path
320
+ if (!project && fs.existsSync(nameOrPath)) {
321
+ project = { path: nameOrPath, name: path.basename(nameOrPath) };
322
+ }
323
+
324
+ if (!project) {
325
+ utils.print.error(`Project not found: ${nameOrPath}`);
326
+ console.log(`\n${utils.COLORS.bold}Available projects:${utils.COLORS.reset}`);
327
+ for (const p of projects) {
328
+ console.log(` - ${p.name || path.basename(p.path)}`);
329
+ }
330
+ return;
331
+ }
332
+
333
+ const details = getProjectDetails(project);
334
+
335
+ // JSON output
336
+ if (options.json) {
337
+ console.log(JSON.stringify(details, null, 2));
338
+ return;
339
+ }
340
+
341
+ const name = details.name || path.basename(details.path);
342
+ const typeBadge = getTypeBadge(details.projectType);
343
+ const healthGrade = details.healthGrade || getHealthGrade(0);
344
+
345
+ console.log(`
346
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project: ${name}${utils.COLORS.reset} ${typeBadge}
347
+ ${utils.COLORS.dim}${details.path}${utils.COLORS.reset}
348
+ `);
349
+
350
+ // Health score
351
+ console.log(`${utils.COLORS.bold}Health Score${utils.COLORS.reset}`);
352
+ console.log(` ${healthGrade.color}${healthGrade.grade}${utils.COLORS.reset} ${details.health}/100`);
353
+ console.log();
354
+
355
+ // Checkpoint progress
356
+ const progress = details.checkpointProgress;
357
+ console.log(`${utils.COLORS.bold}Checkpoint Progress${utils.COLORS.reset}`);
358
+ console.log(` ${checkpointEngine.getColoredProgressBar(progress.percentage, 30)} ${progress.percentage}%`);
359
+ console.log(` ${progress.completed}/${progress.total} complete`);
360
+ console.log();
361
+
362
+ // If we have full state, show checkpoints
363
+ if (details.stateExists && details.path) {
364
+ const checkpointStatus = checkpointEngine.getCheckpointStatus(details.path);
365
+ if (checkpointStatus.exists && checkpointStatus.checkpoints) {
366
+ console.log(`${utils.COLORS.bold}Checkpoints:${utils.COLORS.reset}`);
367
+ for (const cp of checkpointStatus.checkpoints) {
368
+ const icon = cp.completed
369
+ ? `${utils.COLORS.green}✓${utils.COLORS.reset}`
370
+ : `${utils.COLORS.dim}○${utils.COLORS.reset}`;
371
+ console.log(` ${icon} ${cp.label}`);
372
+ }
373
+ console.log();
374
+ }
375
+ }
376
+
377
+ // GitHub
378
+ console.log(`${utils.COLORS.bold}GitHub${utils.COLORS.reset}`);
379
+ if (details.github?.connected) {
380
+ console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Connected: ${details.github.owner}/${details.github.repo}`);
381
+ if (details.github.stats) {
382
+ console.log(` Commits: ${details.github.stats.totalCommits}`);
383
+ console.log(` Open PRs: ${details.github.stats.openPRs}`);
384
+ console.log(` Contributors: ${details.github.stats.contributors}`);
385
+ if (details.github.stats.lastCommitMessage) {
386
+ console.log(` Latest: ${details.github.stats.lastCommitMessage}`);
387
+ }
388
+ }
389
+ } else {
390
+ console.log(` ${utils.COLORS.dim}Not connected${utils.COLORS.reset}`);
391
+ }
392
+ console.log();
393
+
394
+ // Usage
395
+ if (details.usage) {
396
+ console.log(`${utils.COLORS.bold}Usage (${details.usage.currentPeriod})${utils.COLORS.reset}`);
397
+ console.log(` Context generations: ${details.usage.contextGenerations}`);
398
+ console.log(` Agent invocations: ${details.usage.agentInvocations}`);
399
+ console.log(` Skill searches: ${details.usage.skillSearches}`);
400
+ console.log(` MCP calls: ${details.usage.mcpCalls}`);
401
+ console.log();
402
+ }
403
+
404
+ // Commands
405
+ console.log(`${utils.COLORS.bold}Commands:${utils.COLORS.reset}`);
406
+ console.log(` bootspring switch "${name}" Switch to this project`);
407
+ console.log(' bootspring checkpoint View checkpoints');
408
+ console.log(' bootspring github status View GitHub status');
409
+ console.log();
410
+
411
+ // Emit telemetry
412
+ telemetry.emitEvent('manager:show', {
413
+ projectName: name,
414
+ health: details.health
415
+ });
416
+ }
417
+
418
+ /**
419
+ * Export project report as markdown
420
+ * @param {object} options - Options
421
+ */
422
+ function handleExport(options = {}) {
423
+ const projects = getKnownProjects().map(getProjectDetails);
424
+
425
+ if (projects.length === 0) {
426
+ utils.print.warning('No projects to export.');
427
+ return;
428
+ }
429
+
430
+ const date = new Date().toISOString().split('T')[0];
431
+ const lines = [];
432
+
433
+ lines.push('# Project Manager Report');
434
+ lines.push('');
435
+ lines.push(`Generated: ${date}`);
436
+ lines.push(`Projects: ${projects.length}`);
437
+ lines.push('');
438
+ lines.push('---');
439
+ lines.push('');
440
+
441
+ // Summary table
442
+ lines.push('## Summary');
443
+ lines.push('');
444
+ lines.push('| Project | Type | Health | Checkpoints | GitHub |');
445
+ lines.push('|---------|------|--------|-------------|--------|');
446
+
447
+ for (const project of projects) {
448
+ const name = project.name || path.basename(project.path || 'Unknown');
449
+ const type = project.projectType || 'development';
450
+ const health = `${project.healthGrade?.grade || '-'} (${project.health})`;
451
+ const checkpoints = `${project.checkpointProgress.completed}/${project.checkpointProgress.total}`;
452
+ const github = project.github?.connected ? `${project.github.owner}/${project.github.repo}` : '-';
453
+
454
+ lines.push(`| ${name} | ${type} | ${health} | ${checkpoints} | ${github} |`);
455
+ }
456
+
457
+ lines.push('');
458
+ lines.push('---');
459
+ lines.push('');
460
+
461
+ // Detailed sections
462
+ lines.push('## Project Details');
463
+ lines.push('');
464
+
465
+ for (const project of projects) {
466
+ const name = project.name || path.basename(project.path || 'Unknown');
467
+
468
+ lines.push(`### ${name}`);
469
+ lines.push('');
470
+ lines.push(`- **Type:** ${project.projectType}`);
471
+ lines.push(`- **Health:** ${project.health}/100 (${project.healthGrade?.grade || '-'})`);
472
+ lines.push(`- **Checkpoints:** ${project.checkpointProgress.completed}/${project.checkpointProgress.total} (${project.checkpointProgress.percentage}%)`);
473
+
474
+ if (project.github?.connected) {
475
+ lines.push(`- **GitHub:** [${project.github.owner}/${project.github.repo}](${project.github.repositoryUrl})`);
476
+ if (project.github.stats?.lastCommitMessage) {
477
+ lines.push(`- **Latest Commit:** ${project.github.stats.lastCommitMessage}`);
478
+ }
479
+ }
480
+
481
+ if (project.lastActivity) {
482
+ lines.push(`- **Last Activity:** ${utils.formatRelativeTime(project.lastActivity)}`);
483
+ }
484
+
485
+ lines.push('');
486
+ }
487
+
488
+ // Statistics
489
+ lines.push('## Statistics');
490
+ lines.push('');
491
+
492
+ const totalHealth = projects.reduce((sum, p) => sum + p.health, 0);
493
+ const avgHealth = projects.length > 0 ? Math.round(totalHealth / projects.length) : 0;
494
+ const connectedCount = projects.filter(p => p.github?.connected).length;
495
+ const byType = {};
496
+ for (const p of projects) {
497
+ const type = p.projectType || 'development';
498
+ byType[type] = (byType[type] || 0) + 1;
499
+ }
500
+
501
+ lines.push(`- **Total Projects:** ${projects.length}`);
502
+ lines.push(`- **Average Health:** ${avgHealth}/100`);
503
+ lines.push(`- **GitHub Connected:** ${connectedCount}/${projects.length}`);
504
+ lines.push('');
505
+ lines.push('**By Type:**');
506
+ for (const [type, count] of Object.entries(byType)) {
507
+ lines.push(`- ${type}: ${count}`);
508
+ }
509
+ lines.push('');
510
+
511
+ lines.push('---');
512
+ lines.push('');
513
+ lines.push('*Generated by Bootspring Project Manager*');
514
+
515
+ const content = lines.join('\n');
516
+
517
+ // Output to file or console
518
+ if (options.output) {
519
+ try {
520
+ fs.writeFileSync(options.output, content, 'utf-8');
521
+ utils.print.success(`Report exported to: ${options.output}`);
522
+ } catch (error) {
523
+ utils.print.error(`Failed to write file: ${error.message}`);
524
+ }
525
+ } else {
526
+ const defaultPath = path.join(process.cwd(), `project-report-${date}.md`);
527
+ fs.writeFileSync(defaultPath, content, 'utf-8');
528
+ utils.print.success(`Report exported to: ${defaultPath}`);
529
+ }
530
+
531
+ // Emit telemetry
532
+ telemetry.emitEvent('manager:export', {
533
+ projectCount: projects.length
534
+ });
535
+ }
536
+
537
+ /**
538
+ * Scan for local projects
539
+ * @param {object} options - Options
540
+ */
541
+ function handleScan(options = {}) {
542
+ const searchDir = options.dir || process.cwd();
543
+
544
+ const spinner = utils.createSpinner(`Scanning ${searchDir} for projects...`);
545
+ spinner.start();
546
+
547
+ const found = [];
548
+
549
+ try {
550
+ // Look for directories with bootspring indicators
551
+ const entries = fs.readdirSync(searchDir, { withFileTypes: true });
552
+
553
+ for (const entry of entries) {
554
+ if (!entry.isDirectory()) continue;
555
+ if (entry.name.startsWith('.')) continue;
556
+ if (entry.name === 'node_modules') continue;
557
+
558
+ const dirPath = path.join(searchDir, entry.name);
559
+
560
+ // Check for bootspring indicators
561
+ const hasConfig = fs.existsSync(path.join(dirPath, 'bootspring.config.js'));
562
+ const hasPlanning = fs.existsSync(path.join(dirPath, 'planning'));
563
+ const hasBootstrapDir = fs.existsSync(path.join(dirPath, '.bootspring'));
564
+ const hasPackageJson = fs.existsSync(path.join(dirPath, 'package.json'));
565
+
566
+ if (hasConfig || hasPlanning || hasBootstrapDir) {
567
+ found.push({
568
+ name: entry.name,
569
+ path: dirPath,
570
+ indicators: {
571
+ config: hasConfig,
572
+ planning: hasPlanning,
573
+ bootspringDir: hasBootstrapDir,
574
+ packageJson: hasPackageJson
575
+ }
576
+ });
577
+ }
578
+ }
579
+ } catch (error) {
580
+ spinner.fail(`Scan failed: ${error.message}`);
581
+ return;
582
+ }
583
+
584
+ spinner.succeed(`Found ${found.length} project(s)`);
585
+
586
+ if (found.length === 0) {
587
+ console.log(`\n${utils.COLORS.dim}No Bootspring projects found in ${searchDir}${utils.COLORS.reset}`);
588
+ return;
589
+ }
590
+
591
+ console.log();
592
+
593
+ for (const project of found) {
594
+ console.log(` ${utils.COLORS.cyan}●${utils.COLORS.reset} ${project.name}`);
595
+ console.log(` ${utils.COLORS.dim}${project.path}${utils.COLORS.reset}`);
596
+ }
597
+
598
+ console.log(`
599
+ ${utils.COLORS.bold}To add to workspace:${utils.COLORS.reset}
600
+ bootspring workspace add <path>
601
+ `);
602
+ }
603
+
604
+ /**
605
+ * Show help
606
+ */
607
+ function showHelp() {
608
+ console.log(`
609
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Manager${utils.COLORS.reset}
610
+ ${utils.COLORS.dim}Overview of all projects${utils.COLORS.reset}
611
+
612
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
613
+ bootspring manager [command] [options]
614
+
615
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
616
+ list List all projects with health scores (default)
617
+ show <name> Detailed project view
618
+ export Export report as markdown
619
+ scan Scan for local projects
620
+
621
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
622
+ --sort=<field> Sort by: health, activity, name
623
+ --type=<type> Filter by type: dev, content, business
624
+ --json Output as JSON
625
+ --output=<file> Export output file path
626
+ --dir=<path> Directory to scan
627
+
628
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
629
+ bootspring manager # List all projects
630
+ bootspring manager --sort=health # Sort by health score
631
+ bootspring manager show my-project # View project details
632
+ bootspring manager export # Generate markdown report
633
+ bootspring manager scan --dir=~/projects # Find projects
634
+ `);
635
+ }
636
+
637
+ // ============================================================================
638
+ // Main Runner
639
+ // ============================================================================
640
+
641
+ /**
642
+ * Run manager command
643
+ * @param {Array} args - Command arguments
644
+ */
645
+ async function run(args) {
646
+ // Parse arguments
647
+ const parsed = utils.parseArgs(args);
648
+ const subcommand = parsed._[0] || 'list';
649
+ const options = {
650
+ sort: parsed.sort || null,
651
+ type: parsed.type || null,
652
+ json: parsed.json || false,
653
+ output: parsed.output || null,
654
+ dir: parsed.dir || null
655
+ };
656
+
657
+ switch (subcommand) {
658
+ case 'list':
659
+ case 'ls':
660
+ case 'l':
661
+ handleList(options);
662
+ break;
663
+
664
+ case 'show':
665
+ case 'view':
666
+ case 's':
667
+ handleShow(parsed._[1], options);
668
+ break;
669
+
670
+ case 'export':
671
+ case 'report':
672
+ case 'e':
673
+ handleExport(options);
674
+ break;
675
+
676
+ case 'scan':
677
+ case 'find':
678
+ handleScan(options);
679
+ break;
680
+
681
+ case 'help':
682
+ case '-h':
683
+ case '--help':
684
+ showHelp();
685
+ break;
686
+
687
+ default:
688
+ // If it looks like a project name, treat as show
689
+ if (!subcommand.startsWith('-')) {
690
+ handleShow(subcommand, options);
691
+ } else {
692
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
693
+ showHelp();
694
+ }
695
+ }
696
+ }
697
+
698
+ // ============================================================================
699
+ // Exports
700
+ // ============================================================================
701
+
702
+ module.exports = {
703
+ run,
704
+ // Utility exports
705
+ getKnownProjects,
706
+ getProjectDetails,
707
+ handleList,
708
+ handleShow,
709
+ handleExport,
710
+ handleScan
711
+ };