@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/metrics.js ADDED
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Bootspring Metrics Command
3
+ * Collect and display project metrics
4
+ *
5
+ * @package bootspring
6
+ * @command metrics
7
+ */
8
+
9
+ const config = require('../core/config');
10
+ const utils = require('../core/utils');
11
+ const metricsEngine = require('../core/metrics-engine');
12
+ const projectState = require('../core/project-state');
13
+
14
+ // ============================================================================
15
+ // Display Functions
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Create a visual bar
20
+ */
21
+ function createBar(value, max = 100, width = 20) {
22
+ const filled = Math.round((value / max) * width);
23
+ const empty = width - filled;
24
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
25
+ return bar;
26
+ }
27
+
28
+ /**
29
+ * Get color based on score
30
+ */
31
+ function getScoreColor(score) {
32
+ if (score >= 80) return utils.COLORS.green;
33
+ if (score >= 60) return utils.COLORS.yellow;
34
+ if (score >= 40) return utils.COLORS.magenta;
35
+ return utils.COLORS.red;
36
+ }
37
+
38
+ /**
39
+ * Display metrics overview
40
+ */
41
+ function displayMetrics(results, options = {}) {
42
+ console.log(`
43
+ ${utils.COLORS.cyan}${utils.COLORS.bold}📊 Project Metrics${utils.COLORS.reset}
44
+ ${utils.COLORS.dim}Collected: ${new Date(results.timestamp).toLocaleString()}${utils.COLORS.reset}
45
+ `);
46
+
47
+ // Overall score
48
+ const scoreColor = getScoreColor(results.overallScore);
49
+ console.log(`${utils.COLORS.bold}Overall Score${utils.COLORS.reset}`);
50
+ console.log(` ${scoreColor}${utils.COLORS.bold}${results.overallScore}%${utils.COLORS.reset} ${createBar(results.overallScore)}`);
51
+ console.log();
52
+
53
+ // Category breakdown
54
+ console.log(`${utils.COLORS.bold}Categories${utils.COLORS.reset}`);
55
+ for (const [category, data] of Object.entries(results.categories)) {
56
+ const catColor = getScoreColor(data.average);
57
+ const catLabel = category.charAt(0).toUpperCase() + category.slice(1);
58
+ console.log(` ${catLabel.padEnd(12)} ${catColor}${createBar(data.average, 100, 15)}${utils.COLORS.reset} ${data.average}%`);
59
+ }
60
+ console.log();
61
+
62
+ // Individual metrics
63
+ if (!options.compact) {
64
+ console.log(`${utils.COLORS.bold}Metrics${utils.COLORS.reset}`);
65
+
66
+ for (const [category, data] of Object.entries(results.categories)) {
67
+ console.log(`\n ${utils.COLORS.cyan}${category.toUpperCase()}${utils.COLORS.reset}`);
68
+
69
+ for (const metric of data.metrics) {
70
+ const color = getScoreColor(metric.score);
71
+ const bar = createBar(metric.score, 100, 10);
72
+ const icon = metric.icon || '•';
73
+ console.log(` ${icon} ${metric.label.padEnd(20)} ${color}${bar}${utils.COLORS.reset} ${metric.score}%`);
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Display metrics dashboard (compact view)
81
+ */
82
+ function displayDashboard(projectRoot) {
83
+ const state = projectState.loadState(projectRoot);
84
+
85
+ if (!state?.metrics) {
86
+ console.log(`${utils.COLORS.dim}No metrics collected yet. Run 'bootspring metrics collect' first.${utils.COLORS.reset}`);
87
+ return;
88
+ }
89
+
90
+ const m = state.metrics;
91
+ const color = getScoreColor(m.overallScore);
92
+
93
+ console.log(`
94
+ ${utils.COLORS.cyan}${utils.COLORS.bold}📊 Metrics Dashboard${utils.COLORS.reset}
95
+
96
+ ${utils.COLORS.bold}Score${utils.COLORS.reset} ${color}${m.overallScore}%${utils.COLORS.reset}
97
+ ${utils.COLORS.dim}Last updated: ${new Date(m.lastCollected).toLocaleString()}${utils.COLORS.reset}
98
+ `);
99
+
100
+ // Categories
101
+ for (const [cat, data] of Object.entries(m.categories)) {
102
+ const catColor = getScoreColor(data.score);
103
+ const catLabel = cat.charAt(0).toUpperCase() + cat.slice(1);
104
+ console.log(`${catLabel.padEnd(12)} ${catColor}${data.score}%${utils.COLORS.reset}`);
105
+ }
106
+
107
+ // Trend
108
+ if (m.history && m.history.length > 1) {
109
+ const trend = m.history[0].score - m.history[1].score;
110
+ const trendIcon = trend > 0 ? '↑' : trend < 0 ? '↓' : '→';
111
+ const trendColor = trend > 0 ? utils.COLORS.green : trend < 0 ? utils.COLORS.red : utils.COLORS.dim;
112
+ console.log(`\n${utils.COLORS.bold}Trend${utils.COLORS.reset} ${trendColor}${trendIcon} ${Math.abs(trend)}%${utils.COLORS.reset}`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Display metrics comparison with previous
118
+ */
119
+ function displayComparison(current, previous) {
120
+ console.log(`
121
+ ${utils.COLORS.cyan}${utils.COLORS.bold}📊 Metrics Comparison${utils.COLORS.reset}
122
+ `);
123
+
124
+ console.log(`${utils.COLORS.bold}Overall${utils.COLORS.reset}`);
125
+ const diff = current.overallScore - previous.overallScore;
126
+ const diffColor = diff > 0 ? utils.COLORS.green : diff < 0 ? utils.COLORS.red : utils.COLORS.dim;
127
+ const diffIcon = diff > 0 ? '↑' : diff < 0 ? '↓' : '→';
128
+ console.log(` ${previous.overallScore}% → ${current.overallScore}% ${diffColor}(${diffIcon}${Math.abs(diff)})${utils.COLORS.reset}`);
129
+ console.log();
130
+
131
+ console.log(`${utils.COLORS.bold}By Metric${utils.COLORS.reset}`);
132
+ for (const [metricId, currentMetric] of Object.entries(current.metrics)) {
133
+ const prevMetric = previous.metrics[metricId];
134
+ if (!prevMetric) continue;
135
+
136
+ const metricDef = metricsEngine.METRICS[metricId];
137
+ const label = metricDef?.label || metricId;
138
+ const metricDiff = currentMetric.score - prevMetric.score;
139
+
140
+ if (Math.abs(metricDiff) >= 5) {
141
+ const mDiffColor = metricDiff > 0 ? utils.COLORS.green : utils.COLORS.red;
142
+ const mDiffIcon = metricDiff > 0 ? '↑' : '↓';
143
+ console.log(` ${label.padEnd(20)} ${mDiffColor}${mDiffIcon}${Math.abs(metricDiff)}%${utils.COLORS.reset}`);
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Generate badges markdown
150
+ */
151
+ function generateBadges(metrics) {
152
+ const badges = [];
153
+
154
+ // Overall score badge
155
+ const score = metrics.overallScore;
156
+ const color = score >= 80 ? 'brightgreen' : score >= 60 ? 'yellow' : score >= 40 ? 'orange' : 'red';
157
+ badges.push(`![Metrics](https://img.shields.io/badge/metrics-${score}%25-${color})`);
158
+
159
+ // Category badges
160
+ for (const [cat, data] of Object.entries(metrics.categories)) {
161
+ const catColor = data.average >= 80 ? 'brightgreen' : data.average >= 60 ? 'yellow' : 'orange';
162
+ badges.push(`![${cat}](https://img.shields.io/badge/${cat}-${data.average}%25-${catColor})`);
163
+ }
164
+
165
+ return badges.join(' ');
166
+ }
167
+
168
+ /**
169
+ * Sync metrics to remote dashboard
170
+ * Sends raw metric values - server applies weights and calculates scores
171
+ */
172
+ async function syncToRemote(projectRoot, metrics) {
173
+ const cfg = config.load();
174
+ const auth = require('../core/auth');
175
+ const session = require('../core/session');
176
+
177
+ // Get API base URL
178
+ const apiBase = cfg.apiBase || cfg.api?.baseUrl || 'https://bootspring.com';
179
+
180
+ // Get project ID from session
181
+ const currentProject = session.getCurrentProject();
182
+ const projectId = currentProject?.id || cfg.projectId || cfg.project?.id;
183
+ if (!projectId) {
184
+ console.log(`${utils.COLORS.dim}No project selected. Run 'bootspring switch <project>' first.${utils.COLORS.reset}`);
185
+ return;
186
+ }
187
+
188
+ // Get auth
189
+ const apiKey = auth.getApiKey() || cfg.api?.token;
190
+ if (!apiKey) {
191
+ console.log(`${utils.COLORS.dim}Not authenticated. Run 'bootspring auth login' first.${utils.COLORS.reset}`);
192
+ return;
193
+ }
194
+
195
+ const spinner = utils.createSpinner('Syncing to dashboard...').start();
196
+
197
+ try {
198
+ // Extract raw metric values only - no weights or calculations
199
+ // Server will apply the scoring algorithms
200
+ const rawMetrics = {
201
+ quality: {
202
+ test_coverage: metrics.metrics?.test_coverage?.score,
203
+ lint_score: metrics.metrics?.lint_score?.score,
204
+ type_coverage: metrics.metrics?.type_coverage?.score
205
+ },
206
+ security: {
207
+ dependency_audit: metrics.metrics?.dependency_audit?.score,
208
+ secrets_scan: metrics.metrics?.secrets_scan?.score,
209
+ gitignore_coverage: metrics.metrics?.gitignore_coverage?.score,
210
+ env_security: metrics.metrics?.env_security?.score
211
+ },
212
+ documentation: {
213
+ readme_quality: metrics.metrics?.readme_quality?.score,
214
+ api_docs: metrics.metrics?.api_docs?.score,
215
+ inline_comments: metrics.metrics?.inline_comments?.score
216
+ },
217
+ performance: {
218
+ bundle_size: metrics.metrics?.bundle_size?.score,
219
+ load_time: metrics.metrics?.load_time?.score
220
+ },
221
+ progress: {
222
+ todo_completion: metrics.metrics?.todo_completion?.score,
223
+ checkpoint_progress: metrics.metrics?.checkpoint_progress?.score,
224
+ commit_frequency: metrics.metrics?.commit_frequency?.score
225
+ }
226
+ };
227
+
228
+ // Send raw data to server for evaluation
229
+ const response = await fetch(`${apiBase}/api/projects/${projectId}/metrics/evaluate`, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'Content-Type': 'application/json',
233
+ 'x-api-key': apiKey
234
+ },
235
+ body: JSON.stringify({ rawMetrics }),
236
+ redirect: 'follow'
237
+ });
238
+
239
+ if (response.ok) {
240
+ const result = await response.json();
241
+ spinner.succeed('Metrics synced to dashboard');
242
+
243
+ // Display server-calculated results
244
+ console.log(`
245
+ ${utils.COLORS.bold}Server Evaluation:${utils.COLORS.reset}
246
+ Overall Score: ${result.overallScore}% (${result.overallGrade?.grade || 'N/A'})
247
+ Last Sync: ${result.lastSyncAt ? new Date(result.lastSyncAt).toLocaleString() : 'Now'}
248
+ `);
249
+
250
+ // Show category breakdown
251
+ if (result.categories) {
252
+ console.log(`${utils.COLORS.bold}Category Scores:${utils.COLORS.reset}`);
253
+ for (const [catId, catData] of Object.entries(result.categories)) {
254
+ const cat = catData;
255
+ const label = catId.charAt(0).toUpperCase() + catId.slice(1);
256
+ console.log(` ${label}: ${cat.score}% (${cat.grade?.grade || 'N/A'})`);
257
+ }
258
+ console.log();
259
+ }
260
+ } else {
261
+ const error = await response.json().catch(() => ({}));
262
+ spinner.fail(`Sync failed: ${error.error || response.status}`);
263
+ }
264
+ } catch (error) {
265
+ spinner.fail(`Sync error: ${error.message}`);
266
+ }
267
+ }
268
+
269
+ // ============================================================================
270
+ // Commands
271
+ // ============================================================================
272
+
273
+ /**
274
+ * Collect all metrics
275
+ */
276
+ async function collectMetrics(options = {}) {
277
+ const cfg = config.load();
278
+ const projectRoot = cfg._projectRoot;
279
+
280
+ const spinner = utils.createSpinner('Collecting metrics...').start();
281
+
282
+ try {
283
+ const results = await metricsEngine.collectAllMetrics(projectRoot, options);
284
+ spinner.stop();
285
+
286
+ // Store metrics
287
+ metricsEngine.storeMetrics(projectRoot, results);
288
+
289
+ // Display
290
+ displayMetrics(results, options);
291
+
292
+ // Sync if requested
293
+ if (options.sync) {
294
+ await syncToRemote(projectRoot, results);
295
+ }
296
+
297
+ // Generate badges if requested
298
+ if (options.badges) {
299
+ console.log(`\n${utils.COLORS.bold}Badges${utils.COLORS.reset}`);
300
+ console.log(generateBadges(results));
301
+ }
302
+
303
+ return results;
304
+ } catch (error) {
305
+ spinner.fail(`Collection failed: ${error.message}`);
306
+ throw error;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Show specific metric details
312
+ */
313
+ async function showMetricDetail(metricId, projectRoot) {
314
+ const results = await metricsEngine.collectAllMetrics(projectRoot);
315
+ const metric = results.metrics[metricId];
316
+
317
+ if (!metric) {
318
+ console.log(`${utils.COLORS.red}Unknown metric: ${metricId}${utils.COLORS.reset}`);
319
+ console.log(`${utils.COLORS.dim}Available: ${Object.keys(results.metrics).join(', ')}${utils.COLORS.reset}`);
320
+ return;
321
+ }
322
+
323
+ const definition = metricsEngine.METRICS[metricId];
324
+ const color = getScoreColor(metric.score);
325
+
326
+ console.log(`
327
+ ${utils.COLORS.cyan}${utils.COLORS.bold}${definition?.icon || '📊'} ${definition?.label || metricId}${utils.COLORS.reset}
328
+
329
+ ${utils.COLORS.bold}Score${utils.COLORS.reset}
330
+ ${color}${metric.score}%${utils.COLORS.reset} ${createBar(metric.score)}
331
+
332
+ ${utils.COLORS.bold}Category${utils.COLORS.reset}
333
+ ${definition?.category || 'unknown'}
334
+
335
+ ${utils.COLORS.bold}Data${utils.COLORS.reset}
336
+ ${JSON.stringify(metric.data, null, 2).split('\n').map(l => ' ' + l).join('\n')}
337
+ `);
338
+
339
+ if (metric.source) {
340
+ console.log(`${utils.COLORS.dim}Source: ${metric.source}${utils.COLORS.reset}`);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Export metrics as JSON
346
+ */
347
+ async function exportMetrics(format, projectRoot) {
348
+ const results = await metricsEngine.collectAllMetrics(projectRoot);
349
+
350
+ if (format === 'json') {
351
+ console.log(JSON.stringify(results, null, 2));
352
+ } else if (format === 'csv') {
353
+ console.log('metric,score,category');
354
+ for (const [id, metric] of Object.entries(results.metrics)) {
355
+ const def = metricsEngine.METRICS[id];
356
+ console.log(`${id},${metric.score},${def?.category || ''}`);
357
+ }
358
+ } else if (format === 'markdown') {
359
+ console.log('# Project Metrics\n');
360
+ console.log(`**Overall Score:** ${results.overallScore}%\n`);
361
+ console.log('| Metric | Score | Category |');
362
+ console.log('|--------|-------|----------|');
363
+ for (const [id, metric] of Object.entries(results.metrics)) {
364
+ const def = metricsEngine.METRICS[id];
365
+ console.log(`| ${def?.label || id} | ${metric.score}% | ${def?.category || ''} |`);
366
+ }
367
+ console.log(`\n${generateBadges(results)}`);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Show help
373
+ */
374
+ function showHelp() {
375
+ console.log(`
376
+ ${utils.COLORS.cyan}${utils.COLORS.bold}📊 Bootspring Metrics${utils.COLORS.reset}
377
+ ${utils.COLORS.dim}Collect and track project metrics${utils.COLORS.reset}
378
+
379
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
380
+ bootspring metrics <command> [options]
381
+
382
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
383
+ ${utils.COLORS.cyan}collect${utils.COLORS.reset} Collect all metrics and store results
384
+ ${utils.COLORS.cyan}status${utils.COLORS.reset} Show current metrics dashboard
385
+ ${utils.COLORS.cyan}show <metric>${utils.COLORS.reset} Show details for specific metric
386
+ ${utils.COLORS.cyan}export${utils.COLORS.reset} Export metrics (json, csv, markdown)
387
+ ${utils.COLORS.cyan}badges${utils.COLORS.reset} Generate shields.io badge markdown
388
+ ${utils.COLORS.cyan}sync${utils.COLORS.reset} Sync metrics to remote dashboard
389
+
390
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
391
+ --compact Show compact output
392
+ --sync Sync to dashboard after collecting
393
+ --badges Generate badges with output
394
+ --format <f> Export format (json, csv, markdown)
395
+
396
+ ${utils.COLORS.bold}Available Metrics:${utils.COLORS.reset}
397
+ ${Object.entries(metricsEngine.METRICS).map(([id, m]) =>
398
+ ` ${m.icon} ${id.padEnd(18)} ${utils.COLORS.dim}${m.label}${utils.COLORS.reset}`
399
+ ).join('\n')}
400
+
401
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
402
+ bootspring metrics collect
403
+ bootspring metrics collect --sync
404
+ bootspring metrics status
405
+ bootspring metrics show testCoverage
406
+ bootspring metrics export --format markdown
407
+ bootspring metrics badges
408
+ `);
409
+ }
410
+
411
+ /**
412
+ * Main run function
413
+ */
414
+ async function run(args) {
415
+ const parsedArgs = utils.parseArgs(args);
416
+ const subcommand = parsedArgs._[0];
417
+ const cfg = config.load();
418
+ const projectRoot = cfg._projectRoot;
419
+
420
+ switch (subcommand) {
421
+ case 'collect':
422
+ case 'run':
423
+ await collectMetrics({
424
+ sync: parsedArgs.sync,
425
+ badges: parsedArgs.badges,
426
+ compact: parsedArgs.compact
427
+ });
428
+ break;
429
+
430
+ case 'status':
431
+ case 'dashboard':
432
+ displayDashboard(projectRoot);
433
+ break;
434
+
435
+ case 'show':
436
+ case 'detail': {
437
+ const metricId = parsedArgs._[1];
438
+ if (!metricId) {
439
+ console.log(`${utils.COLORS.red}Please specify a metric ID${utils.COLORS.reset}`);
440
+ console.log(`${utils.COLORS.dim}Example: bootspring metrics show testCoverage${utils.COLORS.reset}`);
441
+ return;
442
+ }
443
+ await showMetricDetail(metricId, projectRoot);
444
+ break;
445
+ }
446
+
447
+ case 'export':
448
+ await exportMetrics(parsedArgs.format || 'json', projectRoot);
449
+ break;
450
+
451
+ case 'badges': {
452
+ const results = await metricsEngine.collectAllMetrics(projectRoot);
453
+ console.log(generateBadges(results));
454
+ break;
455
+ }
456
+
457
+ case 'sync': {
458
+ const metrics = await metricsEngine.collectAllMetrics(projectRoot);
459
+ await syncToRemote(projectRoot, metrics);
460
+ break;
461
+ }
462
+
463
+ case 'help':
464
+ case '-h':
465
+ case '--help':
466
+ showHelp();
467
+ break;
468
+
469
+ default:
470
+ if (!subcommand) {
471
+ // Default to status
472
+ displayDashboard(projectRoot);
473
+ } else {
474
+ utils.print.error(`Unknown command: ${subcommand}`);
475
+ showHelp();
476
+ }
477
+ }
478
+ }
479
+
480
+ module.exports = { run, collectMetrics, displayMetrics, generateBadges };