@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,694 @@
1
+ /**
2
+ * Bootspring Project State
3
+ * Manages PROJECT_STATE.json in the planning folder
4
+ * Single source of truth for project checkpoints and state
5
+ *
6
+ * @package bootspring
7
+ * @module core/project-state
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const utils = require('./utils');
13
+
14
+ // ============================================================================
15
+ // Constants
16
+ // ============================================================================
17
+
18
+ const VERSION = '1.0.0';
19
+ const STATE_FILE = 'PROJECT_STATE.json';
20
+ const HISTORY_FILE = 'CHECKPOINT_HISTORY.json';
21
+ const GITHUB_SYNC_FILE = 'GITHUB_SYNC.json';
22
+ const PLANNING_DIR = 'planning';
23
+
24
+ // Project types
25
+ const PROJECT_TYPES = {
26
+ DEVELOPMENT: 'development',
27
+ CONTENT: 'content',
28
+ BUSINESS: 'business'
29
+ };
30
+
31
+ // Checkpoints by project type
32
+ const CHECKPOINTS = {
33
+ development: [
34
+ { id: 'initialized', label: 'Project Initialized', sourceFile: 'PROJECT_STATE.json', criteria: 'config_exists' },
35
+ { id: 'prd', label: 'PRD Created', sourceFile: 'PRD.md', criteria: 'file_exists_min_500' },
36
+ { id: 'technical_spec', label: 'Technical Spec', sourceFile: 'TECHNICAL_SPEC.md', criteria: 'file_exists' },
37
+ { id: 'architecture', label: 'Architecture Defined', sourceFile: 'ARCHITECTURE.md', criteria: 'file_exists' },
38
+ { id: 'database_schema', label: 'Database Schema', sourceFile: 'DATABASE_SCHEMA.md', criteria: 'file_exists' },
39
+ { id: 'api_contracts', label: 'API Contracts', sourceFile: 'API_CONTRACTS.md', criteria: 'file_exists' },
40
+ { id: 'tests_planned', label: 'Tests Planned', sourceFile: 'TEST_PLAN.md', criteria: 'file_exists' },
41
+ { id: 'deployed', label: 'Deployed', sourceFile: 'DEPLOYMENT.md', criteria: 'contains_url' },
42
+ { id: 'github_connected', label: 'GitHub Connected', sourceFile: 'PROJECT_STATE.json', criteria: 'github_connected' }
43
+ ],
44
+ content: [
45
+ { id: 'initialized', label: 'Project Initialized', sourceFile: 'PROJECT_STATE.json', criteria: 'config_exists' },
46
+ { id: 'content_strategy', label: 'Content Strategy', sourceFile: 'CONTENT_STRATEGY.md', criteria: 'file_exists' },
47
+ { id: 'editorial_calendar', label: 'Editorial Calendar', sourceFile: 'EDITORIAL_CALENDAR.md', criteria: 'file_exists' },
48
+ { id: 'style_guide', label: 'Style Guide', sourceFile: 'STYLE_GUIDE.md', criteria: 'file_exists' },
49
+ { id: 'seo_plan', label: 'SEO Plan', sourceFile: 'SEO_PLAN.md', criteria: 'file_exists' },
50
+ { id: 'published', label: 'Published', sourceFile: 'PROJECT_STATE.json', criteria: 'content_published' },
51
+ { id: 'github_connected', label: 'GitHub Connected', sourceFile: 'PROJECT_STATE.json', criteria: 'github_connected' }
52
+ ],
53
+ business: [
54
+ { id: 'initialized', label: 'Project Initialized', sourceFile: 'PROJECT_STATE.json', criteria: 'config_exists' },
55
+ { id: 'business_plan', label: 'Business Plan', sourceFile: 'BUSINESS_PLAN.md', criteria: 'file_exists' },
56
+ { id: 'market_analysis', label: 'Market Analysis', sourceFile: 'MARKET_ANALYSIS.md', criteria: 'file_exists' },
57
+ { id: 'competitor_analysis', label: 'Competitor Analysis', sourceFile: 'COMPETITOR_ANALYSIS.md', criteria: 'file_exists' },
58
+ { id: 'financial_model', label: 'Financial Model', sourceFile: 'FINANCIAL_MODEL.md', criteria: 'file_exists' },
59
+ { id: 'pitch_deck', label: 'Pitch Deck', sourceFile: 'PITCH_DECK.md', criteria: 'file_exists' },
60
+ { id: 'investor_list', label: 'Investor List', sourceFile: 'INVESTOR_LIST.md', criteria: 'file_exists' },
61
+ { id: 'github_connected', label: 'GitHub Connected', sourceFile: 'PROJECT_STATE.json', criteria: 'github_connected' }
62
+ ]
63
+ };
64
+
65
+ // ============================================================================
66
+ // State Management
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Get the planning directory path
71
+ * @param {string} projectRoot - Project root directory
72
+ * @returns {string} Planning directory path
73
+ */
74
+ function getPlanningDir(projectRoot) {
75
+ return path.join(projectRoot, PLANNING_DIR);
76
+ }
77
+
78
+ /**
79
+ * Get the state file path
80
+ * @param {string} projectRoot - Project root directory
81
+ * @returns {string} State file path
82
+ */
83
+ function getStateFilePath(projectRoot) {
84
+ return path.join(getPlanningDir(projectRoot), STATE_FILE);
85
+ }
86
+
87
+ /**
88
+ * Get the history file path
89
+ * @param {string} projectRoot - Project root directory
90
+ * @returns {string} History file path
91
+ */
92
+ function getHistoryFilePath(projectRoot) {
93
+ return path.join(getPlanningDir(projectRoot), HISTORY_FILE);
94
+ }
95
+
96
+ /**
97
+ * Get the GitHub sync file path
98
+ * @param {string} projectRoot - Project root directory
99
+ * @returns {string} GitHub sync file path
100
+ */
101
+ function getGitHubSyncFilePath(projectRoot) {
102
+ return path.join(getPlanningDir(projectRoot), GITHUB_SYNC_FILE);
103
+ }
104
+
105
+ /**
106
+ * Create default state
107
+ * @param {string} projectType - Project type
108
+ * @returns {object} Default state object
109
+ */
110
+ function createDefaultState(projectType = PROJECT_TYPES.DEVELOPMENT) {
111
+ const now = new Date().toISOString();
112
+ const checkpoints = {};
113
+
114
+ // Initialize all checkpoints as not completed
115
+ const checkpointDefs = CHECKPOINTS[projectType] || CHECKPOINTS.development;
116
+ for (const checkpoint of checkpointDefs) {
117
+ checkpoints[checkpoint.id] = {
118
+ completed: false,
119
+ completedAt: null,
120
+ completedBy: null
121
+ };
122
+ }
123
+
124
+ // Mark initialized as completed by default
125
+ checkpoints.initialized = {
126
+ completed: true,
127
+ completedAt: now,
128
+ completedBy: 'system'
129
+ };
130
+
131
+ return {
132
+ version: VERSION,
133
+ projectType,
134
+ autoTagged: false,
135
+ taggedBy: null,
136
+ createdAt: now,
137
+ updatedAt: now,
138
+ checkpoints,
139
+ github: {
140
+ connected: false,
141
+ repositoryUrl: null,
142
+ owner: null,
143
+ repo: null,
144
+ defaultBranch: null,
145
+ lastSync: null,
146
+ stats: {
147
+ totalCommits: 0,
148
+ openPRs: 0,
149
+ closedPRs: 0,
150
+ contributors: 0,
151
+ lastCommit: null,
152
+ lastCommitMessage: null
153
+ }
154
+ },
155
+ health: {
156
+ score: 0,
157
+ lastCalculated: null,
158
+ breakdown: {
159
+ checkpoints: 0,
160
+ github: 0,
161
+ docs: 0,
162
+ quality: 0
163
+ }
164
+ },
165
+ usage: {
166
+ currentPeriod: new Date().toISOString().slice(0, 7),
167
+ contextGenerations: 0,
168
+ agentInvocations: 0,
169
+ skillSearches: 0,
170
+ mcpCalls: 0
171
+ }
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Load project state
177
+ * @param {string} projectRoot - Project root directory
178
+ * @returns {object|null} State object or null if not found
179
+ */
180
+ function loadState(projectRoot) {
181
+ const stateFile = getStateFilePath(projectRoot);
182
+
183
+ if (!fs.existsSync(stateFile)) {
184
+ return null;
185
+ }
186
+
187
+ try {
188
+ const content = fs.readFileSync(stateFile, 'utf-8');
189
+ return JSON.parse(content);
190
+ } catch (error) {
191
+ utils.print.debug(`Error loading state: ${error.message}`);
192
+ return null;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Save project state
198
+ * @param {string} projectRoot - Project root directory
199
+ * @param {object} state - State object to save
200
+ * @returns {boolean} Success status
201
+ */
202
+ function saveState(projectRoot, state) {
203
+ const planningDir = getPlanningDir(projectRoot);
204
+ const stateFile = getStateFilePath(projectRoot);
205
+
206
+ try {
207
+ // Ensure planning directory exists
208
+ if (!fs.existsSync(planningDir)) {
209
+ fs.mkdirSync(planningDir, { recursive: true });
210
+ }
211
+
212
+ // Update timestamp
213
+ state.updatedAt = new Date().toISOString();
214
+
215
+ // Write state
216
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8');
217
+ return true;
218
+ } catch (error) {
219
+ utils.print.debug(`Error saving state: ${error.message}`);
220
+ return false;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Initialize project state
226
+ * @param {string} projectRoot - Project root directory
227
+ * @param {object} options - Options
228
+ * @param {string} options.projectType - Project type
229
+ * @param {boolean} options.autoTagged - Whether auto-tagged
230
+ * @param {string} options.taggedBy - What triggered the tag
231
+ * @returns {object} Created state
232
+ */
233
+ function initState(projectRoot, options = {}) {
234
+ const projectType = options.projectType || PROJECT_TYPES.DEVELOPMENT;
235
+ const state = createDefaultState(projectType);
236
+
237
+ if (options.autoTagged) {
238
+ state.autoTagged = true;
239
+ state.taggedBy = options.taggedBy || null;
240
+ }
241
+
242
+ saveState(projectRoot, state);
243
+ return state;
244
+ }
245
+
246
+ /**
247
+ * Get or create project state
248
+ * @param {string} projectRoot - Project root directory
249
+ * @param {object} options - Options for creation if state doesn't exist
250
+ * @returns {object} State object
251
+ */
252
+ function getOrCreateState(projectRoot, options = {}) {
253
+ let state = loadState(projectRoot);
254
+
255
+ if (!state) {
256
+ state = initState(projectRoot, options);
257
+ }
258
+
259
+ return state;
260
+ }
261
+
262
+ // ============================================================================
263
+ // Checkpoint Management
264
+ // ============================================================================
265
+
266
+ /**
267
+ * Get checkpoint definitions for a project type
268
+ * @param {string} projectType - Project type
269
+ * @returns {Array} Checkpoint definitions
270
+ */
271
+ function getCheckpointDefinitions(projectType) {
272
+ return CHECKPOINTS[projectType] || CHECKPOINTS.development;
273
+ }
274
+
275
+ /**
276
+ * Mark a checkpoint as complete
277
+ * @param {string} projectRoot - Project root directory
278
+ * @param {string} checkpointId - Checkpoint ID
279
+ * @param {object} options - Options
280
+ * @param {string} options.completedBy - Who completed it
281
+ * @param {string} options.notes - Optional notes
282
+ * @returns {object|null} Updated state or null on error
283
+ */
284
+ function completeCheckpoint(projectRoot, checkpointId, options = {}) {
285
+ const state = loadState(projectRoot);
286
+
287
+ if (!state) {
288
+ return null;
289
+ }
290
+
291
+ const now = new Date().toISOString();
292
+
293
+ // Update checkpoint
294
+ if (!state.checkpoints[checkpointId]) {
295
+ state.checkpoints[checkpointId] = {};
296
+ }
297
+
298
+ state.checkpoints[checkpointId] = {
299
+ completed: true,
300
+ completedAt: now,
301
+ completedBy: options.completedBy || 'manual'
302
+ };
303
+
304
+ // Save state
305
+ saveState(projectRoot, state);
306
+
307
+ // Record to history
308
+ recordCheckpointHistory(projectRoot, {
309
+ checkpointId,
310
+ completedAt: now,
311
+ completedBy: options.completedBy || 'manual',
312
+ notes: options.notes || null
313
+ });
314
+
315
+ return state;
316
+ }
317
+
318
+ /**
319
+ * Record checkpoint completion to history
320
+ * @param {string} projectRoot - Project root directory
321
+ * @param {object} record - History record
322
+ */
323
+ function recordCheckpointHistory(projectRoot, record) {
324
+ const historyFile = getHistoryFilePath(projectRoot);
325
+ let history = [];
326
+
327
+ if (fs.existsSync(historyFile)) {
328
+ try {
329
+ history = JSON.parse(fs.readFileSync(historyFile, 'utf-8'));
330
+ } catch {
331
+ history = [];
332
+ }
333
+ }
334
+
335
+ history.push({
336
+ ...record,
337
+ timestamp: new Date().toISOString()
338
+ });
339
+
340
+ try {
341
+ fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf-8');
342
+ } catch (error) {
343
+ utils.print.debug(`Error saving history: ${error.message}`);
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Get checkpoint history
349
+ * @param {string} projectRoot - Project root directory
350
+ * @returns {Array} History records
351
+ */
352
+ function getCheckpointHistory(projectRoot) {
353
+ const historyFile = getHistoryFilePath(projectRoot);
354
+
355
+ if (!fs.existsSync(historyFile)) {
356
+ return [];
357
+ }
358
+
359
+ try {
360
+ return JSON.parse(fs.readFileSync(historyFile, 'utf-8'));
361
+ } catch {
362
+ return [];
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Get checkpoint progress summary
368
+ * @param {string} projectRoot - Project root directory
369
+ * @returns {object} Progress summary
370
+ */
371
+ function getCheckpointProgress(projectRoot) {
372
+ const state = loadState(projectRoot);
373
+
374
+ if (!state) {
375
+ return {
376
+ total: 0,
377
+ completed: 0,
378
+ percentage: 0,
379
+ checkpoints: []
380
+ };
381
+ }
382
+
383
+ const definitions = getCheckpointDefinitions(state.projectType);
384
+ const checkpoints = definitions.map(def => ({
385
+ ...def,
386
+ completed: state.checkpoints[def.id]?.completed || false,
387
+ completedAt: state.checkpoints[def.id]?.completedAt || null
388
+ }));
389
+
390
+ const completed = checkpoints.filter(c => c.completed).length;
391
+ const total = checkpoints.length;
392
+
393
+ return {
394
+ total,
395
+ completed,
396
+ percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
397
+ checkpoints
398
+ };
399
+ }
400
+
401
+ // ============================================================================
402
+ // Project Type Management
403
+ // ============================================================================
404
+
405
+ /**
406
+ * Set project type
407
+ * @param {string} projectRoot - Project root directory
408
+ * @param {string} projectType - Project type
409
+ * @param {object} options - Options
410
+ * @returns {object|null} Updated state
411
+ */
412
+ function setProjectType(projectRoot, projectType, options = {}) {
413
+ if (!Object.values(PROJECT_TYPES).includes(projectType)) {
414
+ return null;
415
+ }
416
+
417
+ let state = loadState(projectRoot);
418
+
419
+ if (!state) {
420
+ state = initState(projectRoot, { projectType, ...options });
421
+ return state;
422
+ }
423
+
424
+ // If changing type, reset checkpoints
425
+ if (state.projectType !== projectType) {
426
+ const definitions = getCheckpointDefinitions(projectType);
427
+ const oldCheckpoints = state.checkpoints;
428
+ state.checkpoints = {};
429
+
430
+ // Initialize new checkpoints
431
+ for (const checkpoint of definitions) {
432
+ // Preserve initialized and github_connected if they exist
433
+ if ((checkpoint.id === 'initialized' || checkpoint.id === 'github_connected') && oldCheckpoints[checkpoint.id]) {
434
+ state.checkpoints[checkpoint.id] = oldCheckpoints[checkpoint.id];
435
+ } else {
436
+ state.checkpoints[checkpoint.id] = {
437
+ completed: false,
438
+ completedAt: null,
439
+ completedBy: null
440
+ };
441
+ }
442
+ }
443
+ }
444
+
445
+ state.projectType = projectType;
446
+
447
+ if (options.autoTagged) {
448
+ state.autoTagged = true;
449
+ state.taggedBy = options.taggedBy || null;
450
+ }
451
+
452
+ saveState(projectRoot, state);
453
+ return state;
454
+ }
455
+
456
+ /**
457
+ * Detect project type from files
458
+ * @param {string} projectRoot - Project root directory
459
+ * @returns {string} Detected project type
460
+ */
461
+ function detectProjectType(projectRoot) {
462
+ const planningDir = getPlanningDir(projectRoot);
463
+
464
+ // Check for development indicators
465
+ const devIndicators = [
466
+ 'TECHNICAL_SPEC.md', 'ARCHITECTURE.md', 'DATABASE_SCHEMA.md',
467
+ 'API_CONTRACTS.md', 'TEST_PLAN.md'
468
+ ];
469
+ const devCount = devIndicators.filter(f => fs.existsSync(path.join(planningDir, f))).length;
470
+
471
+ // Check for content indicators
472
+ const contentIndicators = [
473
+ 'CONTENT_STRATEGY.md', 'EDITORIAL_CALENDAR.md', 'STYLE_GUIDE.md', 'SEO_PLAN.md'
474
+ ];
475
+ const contentCount = contentIndicators.filter(f => fs.existsSync(path.join(planningDir, f))).length;
476
+
477
+ // Check for business indicators
478
+ const businessIndicators = [
479
+ 'BUSINESS_PLAN.md', 'MARKET_ANALYSIS.md', 'COMPETITOR_ANALYSIS.md',
480
+ 'FINANCIAL_MODEL.md', 'PITCH_DECK.md', 'INVESTOR_LIST.md'
481
+ ];
482
+ const businessCount = businessIndicators.filter(f => fs.existsSync(path.join(planningDir, f))).length;
483
+
484
+ // Return type with most indicators
485
+ if (contentCount > devCount && contentCount > businessCount) {
486
+ return PROJECT_TYPES.CONTENT;
487
+ }
488
+
489
+ if (businessCount > devCount && businessCount > contentCount) {
490
+ return PROJECT_TYPES.BUSINESS;
491
+ }
492
+
493
+ return PROJECT_TYPES.DEVELOPMENT;
494
+ }
495
+
496
+ // ============================================================================
497
+ // GitHub State Management
498
+ // ============================================================================
499
+
500
+ /**
501
+ * Update GitHub state
502
+ * @param {string} projectRoot - Project root directory
503
+ * @param {object} githubData - GitHub data
504
+ * @returns {object|null} Updated state
505
+ */
506
+ function updateGitHubState(projectRoot, githubData) {
507
+ const state = getOrCreateState(projectRoot);
508
+
509
+ state.github = {
510
+ ...state.github,
511
+ ...githubData,
512
+ lastSync: new Date().toISOString()
513
+ };
514
+
515
+ // Mark github_connected checkpoint if connected
516
+ if (githubData.connected) {
517
+ if (!state.checkpoints.github_connected?.completed) {
518
+ state.checkpoints.github_connected = {
519
+ completed: true,
520
+ completedAt: new Date().toISOString(),
521
+ completedBy: 'github-sync'
522
+ };
523
+
524
+ recordCheckpointHistory(projectRoot, {
525
+ checkpointId: 'github_connected',
526
+ completedAt: state.checkpoints.github_connected.completedAt,
527
+ completedBy: 'github-sync',
528
+ notes: `Connected to ${githubData.repositoryUrl}`
529
+ });
530
+ }
531
+ }
532
+
533
+ saveState(projectRoot, state);
534
+ return state;
535
+ }
536
+
537
+ /**
538
+ * Clear GitHub connection
539
+ * @param {string} projectRoot - Project root directory
540
+ * @returns {object|null} Updated state
541
+ */
542
+ function clearGitHubState(projectRoot) {
543
+ const state = loadState(projectRoot);
544
+
545
+ if (!state) {
546
+ return null;
547
+ }
548
+
549
+ state.github = {
550
+ connected: false,
551
+ repositoryUrl: null,
552
+ owner: null,
553
+ repo: null,
554
+ defaultBranch: null,
555
+ lastSync: null,
556
+ stats: {
557
+ totalCommits: 0,
558
+ openPRs: 0,
559
+ closedPRs: 0,
560
+ contributors: 0,
561
+ lastCommit: null,
562
+ lastCommitMessage: null
563
+ }
564
+ };
565
+
566
+ // Reset github_connected checkpoint
567
+ if (state.checkpoints.github_connected) {
568
+ state.checkpoints.github_connected = {
569
+ completed: false,
570
+ completedAt: null,
571
+ completedBy: null
572
+ };
573
+ }
574
+
575
+ saveState(projectRoot, state);
576
+ return state;
577
+ }
578
+
579
+ // ============================================================================
580
+ // Health Score Management
581
+ // ============================================================================
582
+
583
+ /**
584
+ * Update health score
585
+ * @param {string} projectRoot - Project root directory
586
+ * @param {object} healthData - Health data
587
+ * @returns {object|null} Updated state
588
+ */
589
+ function updateHealthScore(projectRoot, healthData) {
590
+ const state = getOrCreateState(projectRoot);
591
+
592
+ state.health = {
593
+ ...healthData,
594
+ lastCalculated: new Date().toISOString()
595
+ };
596
+
597
+ saveState(projectRoot, state);
598
+ return state;
599
+ }
600
+
601
+ /**
602
+ * Calculate checkpoint health score
603
+ * @param {string} projectRoot - Project root directory
604
+ * @returns {number} Checkpoint score (0-100)
605
+ */
606
+ function calculateCheckpointScore(projectRoot) {
607
+ const progress = getCheckpointProgress(projectRoot);
608
+ return progress.percentage;
609
+ }
610
+
611
+ // ============================================================================
612
+ // Usage Tracking
613
+ // ============================================================================
614
+
615
+ /**
616
+ * Increment usage counter
617
+ * @param {string} projectRoot - Project root directory
618
+ * @param {string} metric - Metric to increment
619
+ * @param {number} count - Amount to increment (default 1)
620
+ * @returns {object|null} Updated state
621
+ */
622
+ function incrementUsage(projectRoot, metric, count = 1) {
623
+ const state = getOrCreateState(projectRoot);
624
+
625
+ const currentPeriod = new Date().toISOString().slice(0, 7);
626
+
627
+ // Reset counters if new period
628
+ if (state.usage.currentPeriod !== currentPeriod) {
629
+ state.usage = {
630
+ currentPeriod,
631
+ contextGenerations: 0,
632
+ agentInvocations: 0,
633
+ skillSearches: 0,
634
+ mcpCalls: 0
635
+ };
636
+ }
637
+
638
+ // Increment counter
639
+ if (state.usage[metric] !== undefined) {
640
+ state.usage[metric] += count;
641
+ }
642
+
643
+ saveState(projectRoot, state);
644
+ return state;
645
+ }
646
+
647
+ // ============================================================================
648
+ // Exports
649
+ // ============================================================================
650
+
651
+ module.exports = {
652
+ // Constants
653
+ VERSION,
654
+ PROJECT_TYPES,
655
+ CHECKPOINTS,
656
+ PLANNING_DIR,
657
+ STATE_FILE,
658
+ HISTORY_FILE,
659
+
660
+ // Paths
661
+ getPlanningDir,
662
+ getStateFilePath,
663
+ getHistoryFilePath,
664
+ getGitHubSyncFilePath,
665
+
666
+ // State management
667
+ createDefaultState,
668
+ loadState,
669
+ saveState,
670
+ initState,
671
+ getOrCreateState,
672
+
673
+ // Checkpoint management
674
+ getCheckpointDefinitions,
675
+ completeCheckpoint,
676
+ recordCheckpointHistory,
677
+ getCheckpointHistory,
678
+ getCheckpointProgress,
679
+
680
+ // Project type
681
+ setProjectType,
682
+ detectProjectType,
683
+
684
+ // GitHub
685
+ updateGitHubState,
686
+ clearGitHubState,
687
+
688
+ // Health
689
+ updateHealthScore,
690
+ calculateCheckpointScore,
691
+
692
+ // Usage
693
+ incrementUsage
694
+ };