@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
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Bootspring Checkpoint Engine
3
+ * Detects and syncs project checkpoints based on file existence and criteria
4
+ *
5
+ * @package bootspring
6
+ * @module core/checkpoint-engine
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const utils = require('./utils');
12
+ const projectState = require('./project-state');
13
+
14
+ // ============================================================================
15
+ // Criteria Evaluators
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Check if a file exists
20
+ * @param {string} filePath - File path
21
+ * @returns {boolean} True if file exists
22
+ */
23
+ function criteriaFileExists(filePath) {
24
+ return fs.existsSync(filePath);
25
+ }
26
+
27
+ /**
28
+ * Check if a file exists and has minimum content length
29
+ * @param {string} filePath - File path
30
+ * @param {number} minLength - Minimum content length
31
+ * @returns {boolean} True if file exists with minimum content
32
+ */
33
+ function criteriaFileExistsMinLength(filePath, minLength = 500) {
34
+ if (!fs.existsSync(filePath)) {
35
+ return false;
36
+ }
37
+
38
+ try {
39
+ const content = fs.readFileSync(filePath, 'utf-8');
40
+ return content.length >= minLength;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Check if a file contains a URL
48
+ * @param {string} filePath - File path
49
+ * @returns {boolean} True if file contains a URL
50
+ */
51
+ function criteriaContainsUrl(filePath) {
52
+ if (!fs.existsSync(filePath)) {
53
+ return false;
54
+ }
55
+
56
+ try {
57
+ const content = fs.readFileSync(filePath, 'utf-8');
58
+ // Match common URL patterns
59
+ const urlPattern = /https?:\/\/[^\s)\]"'`]+/i;
60
+ return urlPattern.test(content);
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if PROJECT_STATE.json exists (config exists)
68
+ * @param {string} projectRoot - Project root directory
69
+ * @returns {boolean} True if state file exists
70
+ */
71
+ function criteriaConfigExists(projectRoot) {
72
+ const stateFile = projectState.getStateFilePath(projectRoot);
73
+ return fs.existsSync(stateFile);
74
+ }
75
+
76
+ /**
77
+ * Check if GitHub is connected in state
78
+ * @param {string} projectRoot - Project root directory
79
+ * @returns {boolean} True if GitHub is connected
80
+ */
81
+ function criteriaGitHubConnected(projectRoot) {
82
+ const state = projectState.loadState(projectRoot);
83
+ return state?.github?.connected === true;
84
+ }
85
+
86
+ /**
87
+ * Check if content is published in state
88
+ * @param {string} projectRoot - Project root directory
89
+ * @returns {boolean} True if content is published
90
+ */
91
+ function criteriaContentPublished(projectRoot) {
92
+ const state = projectState.loadState(projectRoot);
93
+ return state?.content?.published === true;
94
+ }
95
+
96
+ // ============================================================================
97
+ // Checkpoint Evaluation
98
+ // ============================================================================
99
+
100
+ /**
101
+ * Evaluate a single checkpoint
102
+ * @param {string} projectRoot - Project root directory
103
+ * @param {object} checkpoint - Checkpoint definition
104
+ * @returns {object} Evaluation result
105
+ */
106
+ function evaluateCheckpoint(projectRoot, checkpoint) {
107
+ const planningDir = projectState.getPlanningDir(projectRoot);
108
+ const filePath = path.join(planningDir, checkpoint.sourceFile);
109
+
110
+ let completed = false;
111
+ let reason = null;
112
+
113
+ switch (checkpoint.criteria) {
114
+ case 'file_exists':
115
+ completed = criteriaFileExists(filePath);
116
+ reason = completed ? 'File exists' : 'File not found';
117
+ break;
118
+
119
+ case 'file_exists_min_500':
120
+ completed = criteriaFileExistsMinLength(filePath, 500);
121
+ reason = completed ? 'File exists with sufficient content' : 'File missing or too short (<500 chars)';
122
+ break;
123
+
124
+ case 'contains_url':
125
+ completed = criteriaContainsUrl(filePath);
126
+ reason = completed ? 'Deployment URL found' : 'No deployment URL found';
127
+ break;
128
+
129
+ case 'config_exists':
130
+ completed = criteriaConfigExists(projectRoot);
131
+ reason = completed ? 'Project initialized' : 'PROJECT_STATE.json not found';
132
+ break;
133
+
134
+ case 'github_connected':
135
+ completed = criteriaGitHubConnected(projectRoot);
136
+ reason = completed ? 'GitHub repository connected' : 'GitHub not connected';
137
+ break;
138
+
139
+ case 'content_published':
140
+ completed = criteriaContentPublished(projectRoot);
141
+ reason = completed ? 'Content published' : 'Content not yet published';
142
+ break;
143
+
144
+ default:
145
+ reason = `Unknown criteria: ${checkpoint.criteria}`;
146
+ }
147
+
148
+ return {
149
+ id: checkpoint.id,
150
+ label: checkpoint.label,
151
+ sourceFile: checkpoint.sourceFile,
152
+ criteria: checkpoint.criteria,
153
+ completed,
154
+ reason
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Evaluate all checkpoints for a project
160
+ * @param {string} projectRoot - Project root directory
161
+ * @param {string} projectType - Optional project type override
162
+ * @returns {Array} Array of evaluation results
163
+ */
164
+ function evaluateAllCheckpoints(projectRoot, projectType = null) {
165
+ // Get project type from state if not provided
166
+ if (!projectType) {
167
+ const state = projectState.loadState(projectRoot);
168
+ projectType = state?.projectType || projectState.PROJECT_TYPES.DEVELOPMENT;
169
+ }
170
+
171
+ const checkpointDefs = projectState.getCheckpointDefinitions(projectType);
172
+ return checkpointDefs.map(checkpoint => evaluateCheckpoint(projectRoot, checkpoint));
173
+ }
174
+
175
+ /**
176
+ * Sync checkpoints - detect completed checkpoints from files
177
+ * @param {string} projectRoot - Project root directory
178
+ * @param {object} options - Options
179
+ * @param {boolean} options.dryRun - If true, don't save changes
180
+ * @param {boolean} options.verbose - If true, log changes
181
+ * @returns {object} Sync result
182
+ */
183
+ function syncCheckpoints(projectRoot, options = {}) {
184
+ const state = projectState.getOrCreateState(projectRoot);
185
+ const evaluations = evaluateAllCheckpoints(projectRoot, state.projectType);
186
+
187
+ const changes = [];
188
+ const unchanged = [];
189
+
190
+ for (const evaluation of evaluations) {
191
+ const currentState = state.checkpoints[evaluation.id];
192
+ const wasCompleted = currentState?.completed || false;
193
+
194
+ if (evaluation.completed && !wasCompleted) {
195
+ // Newly completed
196
+ changes.push({
197
+ id: evaluation.id,
198
+ label: evaluation.label,
199
+ type: 'completed',
200
+ reason: evaluation.reason
201
+ });
202
+
203
+ if (!options.dryRun) {
204
+ state.checkpoints[evaluation.id] = {
205
+ completed: true,
206
+ completedAt: new Date().toISOString(),
207
+ completedBy: 'sync'
208
+ };
209
+
210
+ projectState.recordCheckpointHistory(projectRoot, {
211
+ checkpointId: evaluation.id,
212
+ completedAt: state.checkpoints[evaluation.id].completedAt,
213
+ completedBy: 'sync',
214
+ notes: evaluation.reason
215
+ });
216
+ }
217
+ } else if (!evaluation.completed && wasCompleted) {
218
+ // Was completed but no longer meets criteria (rare case)
219
+ changes.push({
220
+ id: evaluation.id,
221
+ label: evaluation.label,
222
+ type: 'uncompleted',
223
+ reason: evaluation.reason
224
+ });
225
+
226
+ // Note: We don't uncomplete checkpoints automatically - they stay completed once done
227
+ unchanged.push(evaluation);
228
+ } else {
229
+ unchanged.push(evaluation);
230
+ }
231
+ }
232
+
233
+ // Save state if not dry run
234
+ if (!options.dryRun && changes.length > 0) {
235
+ projectState.saveState(projectRoot, state);
236
+ }
237
+
238
+ // Log if verbose
239
+ if (options.verbose) {
240
+ for (const change of changes) {
241
+ if (change.type === 'completed') {
242
+ utils.print.success(`Checkpoint completed: ${change.label}`);
243
+ }
244
+ }
245
+ }
246
+
247
+ return {
248
+ changes,
249
+ unchanged,
250
+ total: evaluations.length,
251
+ completed: evaluations.filter(e => e.completed).length,
252
+ state
253
+ };
254
+ }
255
+
256
+ // ============================================================================
257
+ // Progress Reporting
258
+ // ============================================================================
259
+
260
+ /**
261
+ * Get checkpoint status with visual indicators
262
+ * @param {string} projectRoot - Project root directory
263
+ * @returns {object} Status report
264
+ */
265
+ function getCheckpointStatus(projectRoot) {
266
+ const state = projectState.loadState(projectRoot);
267
+
268
+ if (!state) {
269
+ return {
270
+ exists: false,
271
+ message: 'Project state not initialized. Run `bootspring checkpoint init` to start.'
272
+ };
273
+ }
274
+
275
+ const evaluations = evaluateAllCheckpoints(projectRoot, state.projectType);
276
+ const completed = evaluations.filter(e => e.completed);
277
+ const pending = evaluations.filter(e => !e.completed);
278
+
279
+ return {
280
+ exists: true,
281
+ projectType: state.projectType,
282
+ autoTagged: state.autoTagged,
283
+ taggedBy: state.taggedBy,
284
+ total: evaluations.length,
285
+ completed: completed.length,
286
+ pending: pending.length,
287
+ percentage: Math.round((completed.length / evaluations.length) * 100),
288
+ checkpoints: evaluations,
289
+ createdAt: state.createdAt,
290
+ updatedAt: state.updatedAt
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Get next steps - what checkpoints should be completed next
296
+ * @param {string} projectRoot - Project root directory
297
+ * @returns {Array} Array of suggested next checkpoints
298
+ */
299
+ function getNextSteps(projectRoot) {
300
+ const state = projectState.loadState(projectRoot);
301
+
302
+ if (!state) {
303
+ return [];
304
+ }
305
+
306
+ const evaluations = evaluateAllCheckpoints(projectRoot, state.projectType);
307
+ const pending = evaluations.filter(e => !e.completed);
308
+
309
+ // Return first 3 incomplete checkpoints as suggestions
310
+ return pending.slice(0, 3).map(checkpoint => ({
311
+ id: checkpoint.id,
312
+ label: checkpoint.label,
313
+ sourceFile: checkpoint.sourceFile,
314
+ action: getActionSuggestion(checkpoint)
315
+ }));
316
+ }
317
+
318
+ /**
319
+ * Get action suggestion for a checkpoint
320
+ * @param {object} checkpoint - Checkpoint evaluation
321
+ * @returns {string} Suggested action
322
+ */
323
+ function getActionSuggestion(checkpoint) {
324
+ switch (checkpoint.criteria) {
325
+ case 'file_exists':
326
+ case 'file_exists_min_500':
327
+ return `Create planning/${checkpoint.sourceFile}`;
328
+ case 'contains_url':
329
+ return `Add deployment URL to planning/${checkpoint.sourceFile}`;
330
+ case 'github_connected':
331
+ return 'Run `bootspring github connect`';
332
+ case 'content_published':
333
+ return 'Mark content as published';
334
+ default:
335
+ return 'Complete this checkpoint';
336
+ }
337
+ }
338
+
339
+ // ============================================================================
340
+ // Progress Bar Generation
341
+ // ============================================================================
342
+
343
+ /**
344
+ * Generate a progress bar string
345
+ * @param {number} percentage - Progress percentage (0-100)
346
+ * @param {number} width - Bar width in characters
347
+ * @returns {string} Progress bar string
348
+ */
349
+ function generateProgressBar(percentage, width = 20) {
350
+ const filled = Math.round((percentage / 100) * width);
351
+ const empty = width - filled;
352
+
353
+ const filledChar = '█';
354
+ const emptyChar = '░';
355
+
356
+ return filledChar.repeat(filled) + emptyChar.repeat(empty);
357
+ }
358
+
359
+ /**
360
+ * Get colored progress bar
361
+ * @param {number} percentage - Progress percentage
362
+ * @param {number} width - Bar width
363
+ * @returns {string} Colored progress bar
364
+ */
365
+ function getColoredProgressBar(percentage, width = 20) {
366
+ const bar = generateProgressBar(percentage, width);
367
+ let color;
368
+
369
+ if (percentage >= 80) {
370
+ color = utils.COLORS.green;
371
+ } else if (percentage >= 50) {
372
+ color = utils.COLORS.yellow;
373
+ } else if (percentage >= 25) {
374
+ color = utils.COLORS.cyan;
375
+ } else {
376
+ color = utils.COLORS.dim;
377
+ }
378
+
379
+ return `${color}${bar}${utils.COLORS.reset}`;
380
+ }
381
+
382
+ // ============================================================================
383
+ // Exports
384
+ // ============================================================================
385
+
386
+ module.exports = {
387
+ // Criteria evaluators
388
+ criteriaFileExists,
389
+ criteriaFileExistsMinLength,
390
+ criteriaContainsUrl,
391
+ criteriaConfigExists,
392
+ criteriaGitHubConnected,
393
+ criteriaContentPublished,
394
+
395
+ // Evaluation
396
+ evaluateCheckpoint,
397
+ evaluateAllCheckpoints,
398
+ syncCheckpoints,
399
+
400
+ // Status
401
+ getCheckpointStatus,
402
+ getNextSteps,
403
+ getActionSuggestion,
404
+
405
+ // Progress
406
+ generateProgressBar,
407
+ getColoredProgressBar
408
+ };