@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.
- package/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Build State Management
|
|
3
|
+
*
|
|
4
|
+
* Manages BUILD_STATE.json for continuous build loop persistence.
|
|
5
|
+
* Tracks phases, implementation queue, MVP criteria, and loop session.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module core/build-state
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const BUILD_STATE_FILE = 'BUILD_STATE.json';
|
|
15
|
+
const PLANNING_DIR = 'planning';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default build state schema
|
|
19
|
+
*/
|
|
20
|
+
function getDefaultState(projectName = 'Project') {
|
|
21
|
+
return {
|
|
22
|
+
version: '1.0.0',
|
|
23
|
+
projectName,
|
|
24
|
+
status: 'pending', // pending, in_progress, paused, completed, failed
|
|
25
|
+
currentPhase: null,
|
|
26
|
+
|
|
27
|
+
phases: {
|
|
28
|
+
foundation: { status: 'pending', tasks: [] },
|
|
29
|
+
mvp: { status: 'pending', tasks: [] },
|
|
30
|
+
launch: { status: 'pending', tasks: [] }
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
implementationQueue: [],
|
|
34
|
+
|
|
35
|
+
mvpCriteria: {
|
|
36
|
+
features: [],
|
|
37
|
+
completionPercentage: 0,
|
|
38
|
+
allCriteriaMet: false
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
loopSession: {
|
|
42
|
+
sessionId: null,
|
|
43
|
+
currentIteration: 0,
|
|
44
|
+
maxIterations: 50,
|
|
45
|
+
startedAt: null,
|
|
46
|
+
lastUpdated: null,
|
|
47
|
+
pausedAt: null
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
metadata: {
|
|
51
|
+
createdAt: new Date().toISOString(),
|
|
52
|
+
updatedAt: new Date().toISOString(),
|
|
53
|
+
seedSource: null,
|
|
54
|
+
preseedDocs: []
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the planning directory path
|
|
61
|
+
* @param {string} projectRoot - Project root path
|
|
62
|
+
* @returns {string} Planning directory path
|
|
63
|
+
*/
|
|
64
|
+
function getPlanningDir(projectRoot) {
|
|
65
|
+
return path.join(projectRoot, PLANNING_DIR);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the build state file path
|
|
70
|
+
* @param {string} projectRoot - Project root path
|
|
71
|
+
* @returns {string} Build state file path
|
|
72
|
+
*/
|
|
73
|
+
function getStatePath(projectRoot) {
|
|
74
|
+
return path.join(getPlanningDir(projectRoot), BUILD_STATE_FILE);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if build state exists
|
|
79
|
+
* @param {string} projectRoot - Project root path
|
|
80
|
+
* @returns {boolean} Whether build state exists
|
|
81
|
+
*/
|
|
82
|
+
function exists(projectRoot) {
|
|
83
|
+
return fs.existsSync(getStatePath(projectRoot));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Initialize a new build state
|
|
88
|
+
* @param {string} projectRoot - Project root path
|
|
89
|
+
* @param {object} config - Initial configuration
|
|
90
|
+
* @returns {object} Initialized build state
|
|
91
|
+
*/
|
|
92
|
+
function initialize(projectRoot, config = {}) {
|
|
93
|
+
const planningDir = getPlanningDir(projectRoot);
|
|
94
|
+
|
|
95
|
+
// Create planning directory if it doesn't exist
|
|
96
|
+
if (!fs.existsSync(planningDir)) {
|
|
97
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const state = getDefaultState(config.projectName || 'Project');
|
|
101
|
+
|
|
102
|
+
// Apply config overrides
|
|
103
|
+
if (config.phases) {
|
|
104
|
+
state.phases = { ...state.phases, ...config.phases };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (config.implementationQueue) {
|
|
108
|
+
state.implementationQueue = config.implementationQueue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (config.mvpCriteria) {
|
|
112
|
+
state.mvpCriteria = { ...state.mvpCriteria, ...config.mvpCriteria };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (config.maxIterations) {
|
|
116
|
+
state.loopSession.maxIterations = config.maxIterations;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (config.seedSource) {
|
|
120
|
+
state.metadata.seedSource = config.seedSource;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (config.preseedDocs) {
|
|
124
|
+
state.metadata.preseedDocs = config.preseedDocs;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Generate session ID
|
|
128
|
+
state.loopSession.sessionId = generateSessionId();
|
|
129
|
+
|
|
130
|
+
// Save initial state
|
|
131
|
+
save(projectRoot, state);
|
|
132
|
+
|
|
133
|
+
return state;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate a unique session ID
|
|
138
|
+
* @returns {string} Session ID
|
|
139
|
+
*/
|
|
140
|
+
function generateSessionId() {
|
|
141
|
+
return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Load build state from disk
|
|
146
|
+
* @param {string} projectRoot - Project root path
|
|
147
|
+
* @returns {object|null} Build state or null if not found
|
|
148
|
+
*/
|
|
149
|
+
function load(projectRoot) {
|
|
150
|
+
const statePath = getStatePath(projectRoot);
|
|
151
|
+
|
|
152
|
+
if (!fs.existsSync(statePath)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const content = fs.readFileSync(statePath, 'utf-8');
|
|
158
|
+
return JSON.parse(content);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Failed to load build state: ${error.message}`);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Save build state to disk
|
|
167
|
+
* @param {string} projectRoot - Project root path
|
|
168
|
+
* @param {object} state - Build state to save
|
|
169
|
+
* @returns {boolean} Success
|
|
170
|
+
*/
|
|
171
|
+
function save(projectRoot, state) {
|
|
172
|
+
const statePath = getStatePath(projectRoot);
|
|
173
|
+
const planningDir = getPlanningDir(projectRoot);
|
|
174
|
+
|
|
175
|
+
// Ensure planning directory exists
|
|
176
|
+
if (!fs.existsSync(planningDir)) {
|
|
177
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Update timestamp
|
|
181
|
+
state.metadata.updatedAt = new Date().toISOString();
|
|
182
|
+
state.loopSession.lastUpdated = new Date().toISOString();
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`Failed to save build state: ${error.message}`);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Update task progress
|
|
195
|
+
* @param {string} projectRoot - Project root path
|
|
196
|
+
* @param {string} taskId - Task ID
|
|
197
|
+
* @param {string} status - New status (pending, in_progress, completed, blocked, skipped)
|
|
198
|
+
* @param {object} details - Additional details
|
|
199
|
+
* @returns {object|null} Updated state or null on failure
|
|
200
|
+
*/
|
|
201
|
+
function updateProgress(projectRoot, taskId, status, details = {}) {
|
|
202
|
+
const state = load(projectRoot);
|
|
203
|
+
if (!state) return null;
|
|
204
|
+
|
|
205
|
+
// Find task in queue
|
|
206
|
+
const taskIndex = state.implementationQueue.findIndex(t => t.id === taskId);
|
|
207
|
+
if (taskIndex === -1) return null;
|
|
208
|
+
|
|
209
|
+
// Update task status
|
|
210
|
+
state.implementationQueue[taskIndex].status = status;
|
|
211
|
+
state.implementationQueue[taskIndex].updatedAt = new Date().toISOString();
|
|
212
|
+
|
|
213
|
+
// Add any additional details
|
|
214
|
+
if (details.completedAt) {
|
|
215
|
+
state.implementationQueue[taskIndex].completedAt = details.completedAt;
|
|
216
|
+
}
|
|
217
|
+
if (details.error) {
|
|
218
|
+
state.implementationQueue[taskIndex].error = details.error;
|
|
219
|
+
}
|
|
220
|
+
if (details.output) {
|
|
221
|
+
state.implementationQueue[taskIndex].lastOutput = details.output;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Update phase progress
|
|
225
|
+
const task = state.implementationQueue[taskIndex];
|
|
226
|
+
if (task.phase && state.phases[task.phase]) {
|
|
227
|
+
updatePhaseProgress(state, task.phase);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Update MVP criteria progress
|
|
231
|
+
updateMvpProgress(state);
|
|
232
|
+
|
|
233
|
+
// Update iteration count if completing
|
|
234
|
+
if (status === 'completed' || status === 'skipped') {
|
|
235
|
+
state.loopSession.currentIteration++;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Save updated state
|
|
239
|
+
save(projectRoot, state);
|
|
240
|
+
|
|
241
|
+
return state;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Update phase progress based on task statuses
|
|
246
|
+
* @param {object} state - Build state
|
|
247
|
+
* @param {string} phaseName - Phase name
|
|
248
|
+
*/
|
|
249
|
+
function updatePhaseProgress(state, phaseName) {
|
|
250
|
+
const phaseTasks = state.implementationQueue.filter(t => t.phase === phaseName);
|
|
251
|
+
const completedTasks = phaseTasks.filter(t => t.status === 'completed');
|
|
252
|
+
|
|
253
|
+
if (phaseTasks.length === 0) return;
|
|
254
|
+
|
|
255
|
+
const progress = completedTasks.length / phaseTasks.length;
|
|
256
|
+
|
|
257
|
+
if (progress === 0) {
|
|
258
|
+
state.phases[phaseName].status = 'pending';
|
|
259
|
+
} else if (progress === 1) {
|
|
260
|
+
state.phases[phaseName].status = 'completed';
|
|
261
|
+
} else {
|
|
262
|
+
state.phases[phaseName].status = 'in_progress';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
state.phases[phaseName].tasks = phaseTasks.map(t => ({
|
|
266
|
+
id: t.id,
|
|
267
|
+
title: t.title,
|
|
268
|
+
status: t.status
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Update MVP criteria progress
|
|
274
|
+
* @param {object} state - Build state
|
|
275
|
+
*/
|
|
276
|
+
function updateMvpProgress(state) {
|
|
277
|
+
const mvpTasks = state.implementationQueue.filter(t => t.phase === 'mvp');
|
|
278
|
+
const completedMvpTasks = mvpTasks.filter(t => t.status === 'completed');
|
|
279
|
+
|
|
280
|
+
if (mvpTasks.length > 0) {
|
|
281
|
+
state.mvpCriteria.completionPercentage = Math.round(
|
|
282
|
+
(completedMvpTasks.length / mvpTasks.length) * 100
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Update feature completion status
|
|
287
|
+
state.mvpCriteria.features = state.mvpCriteria.features.map(feature => {
|
|
288
|
+
const relatedTasks = mvpTasks.filter(t =>
|
|
289
|
+
t.title.toLowerCase().includes(feature.name.toLowerCase()) ||
|
|
290
|
+
t.sourceSection?.toLowerCase().includes(feature.name.toLowerCase())
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const allComplete = relatedTasks.length > 0 &&
|
|
294
|
+
relatedTasks.every(t => t.status === 'completed');
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
...feature,
|
|
298
|
+
status: allComplete ? 'completed' : 'pending'
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Check if all MVP criteria are met
|
|
303
|
+
state.mvpCriteria.allCriteriaMet =
|
|
304
|
+
state.mvpCriteria.completionPercentage === 100 ||
|
|
305
|
+
state.mvpCriteria.features.every(f => f.status === 'completed');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get the next pending task
|
|
310
|
+
* @param {string} projectRoot - Project root path
|
|
311
|
+
* @returns {object|null} Next task or null if none
|
|
312
|
+
*/
|
|
313
|
+
function getNextTask(projectRoot) {
|
|
314
|
+
const state = load(projectRoot);
|
|
315
|
+
if (!state) return null;
|
|
316
|
+
|
|
317
|
+
// First, check for any in_progress tasks
|
|
318
|
+
const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
|
|
319
|
+
if (inProgress) return inProgress;
|
|
320
|
+
|
|
321
|
+
// Then get next pending task (respecting dependencies)
|
|
322
|
+
const pendingTasks = state.implementationQueue.filter(t => t.status === 'pending');
|
|
323
|
+
|
|
324
|
+
for (const task of pendingTasks) {
|
|
325
|
+
// Check if all dependencies are completed
|
|
326
|
+
if (!task.dependencies || task.dependencies.length === 0) {
|
|
327
|
+
return task;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const depsCompleted = task.dependencies.every(depId => {
|
|
331
|
+
const depTask = state.implementationQueue.find(t => t.id === depId);
|
|
332
|
+
return depTask && depTask.status === 'completed';
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (depsCompleted) {
|
|
336
|
+
return task;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Add a task to the implementation queue
|
|
345
|
+
* @param {string} projectRoot - Project root path
|
|
346
|
+
* @param {object} task - Task to add
|
|
347
|
+
* @returns {object|null} Updated state or null on failure
|
|
348
|
+
*/
|
|
349
|
+
function addTask(projectRoot, task) {
|
|
350
|
+
const state = load(projectRoot);
|
|
351
|
+
if (!state) return null;
|
|
352
|
+
|
|
353
|
+
// Generate task ID if not provided
|
|
354
|
+
if (!task.id) {
|
|
355
|
+
const count = state.implementationQueue.length + 1;
|
|
356
|
+
task.id = `task-${count.toString().padStart(3, '0')}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Set defaults
|
|
360
|
+
task.status = task.status || 'pending';
|
|
361
|
+
task.createdAt = new Date().toISOString();
|
|
362
|
+
|
|
363
|
+
state.implementationQueue.push(task);
|
|
364
|
+
save(projectRoot, state);
|
|
365
|
+
|
|
366
|
+
return state;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Set tasks in batch
|
|
371
|
+
* @param {string} projectRoot - Project root path
|
|
372
|
+
* @param {array} tasks - Tasks to set
|
|
373
|
+
* @returns {object|null} Updated state or null on failure
|
|
374
|
+
*/
|
|
375
|
+
function setTasks(projectRoot, tasks) {
|
|
376
|
+
const state = load(projectRoot);
|
|
377
|
+
if (!state) return null;
|
|
378
|
+
|
|
379
|
+
// Process tasks with defaults
|
|
380
|
+
state.implementationQueue = tasks.map((task, index) => ({
|
|
381
|
+
id: task.id || `task-${(index + 1).toString().padStart(3, '0')}`,
|
|
382
|
+
title: task.title,
|
|
383
|
+
description: task.description || '',
|
|
384
|
+
source: task.source || 'manual',
|
|
385
|
+
sourceSection: task.sourceSection || null,
|
|
386
|
+
phase: task.phase || 'mvp',
|
|
387
|
+
status: task.status || 'pending',
|
|
388
|
+
acceptanceCriteria: task.acceptanceCriteria || [],
|
|
389
|
+
dependencies: task.dependencies || [],
|
|
390
|
+
estimatedComplexity: task.estimatedComplexity || 'medium',
|
|
391
|
+
createdAt: new Date().toISOString()
|
|
392
|
+
}));
|
|
393
|
+
|
|
394
|
+
save(projectRoot, state);
|
|
395
|
+
return state;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Set MVP features
|
|
400
|
+
* @param {string} projectRoot - Project root path
|
|
401
|
+
* @param {array} features - MVP features
|
|
402
|
+
* @returns {object|null} Updated state or null on failure
|
|
403
|
+
*/
|
|
404
|
+
function setMvpFeatures(projectRoot, features) {
|
|
405
|
+
const state = load(projectRoot);
|
|
406
|
+
if (!state) return null;
|
|
407
|
+
|
|
408
|
+
state.mvpCriteria.features = features.map(f => ({
|
|
409
|
+
name: typeof f === 'string' ? f : f.name,
|
|
410
|
+
status: 'pending'
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
save(projectRoot, state);
|
|
414
|
+
return state;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Update loop session
|
|
419
|
+
* @param {string} projectRoot - Project root path
|
|
420
|
+
* @param {object} updates - Session updates
|
|
421
|
+
* @returns {object|null} Updated state or null on failure
|
|
422
|
+
*/
|
|
423
|
+
function updateSession(projectRoot, updates) {
|
|
424
|
+
const state = load(projectRoot);
|
|
425
|
+
if (!state) return null;
|
|
426
|
+
|
|
427
|
+
state.loopSession = {
|
|
428
|
+
...state.loopSession,
|
|
429
|
+
...updates
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
save(projectRoot, state);
|
|
433
|
+
return state;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Pause the build loop
|
|
438
|
+
* @param {string} projectRoot - Project root path
|
|
439
|
+
* @returns {object|null} Updated state or null on failure
|
|
440
|
+
*/
|
|
441
|
+
function pause(projectRoot) {
|
|
442
|
+
const state = load(projectRoot);
|
|
443
|
+
if (!state) return null;
|
|
444
|
+
|
|
445
|
+
state.status = 'paused';
|
|
446
|
+
state.loopSession.pausedAt = new Date().toISOString();
|
|
447
|
+
|
|
448
|
+
save(projectRoot, state);
|
|
449
|
+
return state;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Resume the build loop
|
|
454
|
+
* @param {string} projectRoot - Project root path
|
|
455
|
+
* @returns {object|null} Updated state or null on failure
|
|
456
|
+
*/
|
|
457
|
+
function resume(projectRoot) {
|
|
458
|
+
const state = load(projectRoot);
|
|
459
|
+
if (!state) return null;
|
|
460
|
+
|
|
461
|
+
state.status = 'in_progress';
|
|
462
|
+
state.loopSession.pausedAt = null;
|
|
463
|
+
|
|
464
|
+
// Generate new session ID if resuming after long pause
|
|
465
|
+
const pausedAt = state.loopSession.pausedAt ? new Date(state.loopSession.pausedAt) : null;
|
|
466
|
+
const now = new Date();
|
|
467
|
+
if (pausedAt && (now - pausedAt) > 3600000) { // 1 hour
|
|
468
|
+
state.loopSession.sessionId = generateSessionId();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
save(projectRoot, state);
|
|
472
|
+
return state;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Mark build as complete
|
|
477
|
+
* @param {string} projectRoot - Project root path
|
|
478
|
+
* @returns {object|null} Updated state or null on failure
|
|
479
|
+
*/
|
|
480
|
+
function complete(projectRoot) {
|
|
481
|
+
const state = load(projectRoot);
|
|
482
|
+
if (!state) return null;
|
|
483
|
+
|
|
484
|
+
state.status = 'completed';
|
|
485
|
+
state.loopSession.completedAt = new Date().toISOString();
|
|
486
|
+
|
|
487
|
+
save(projectRoot, state);
|
|
488
|
+
return state;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Mark build as failed
|
|
493
|
+
* @param {string} projectRoot - Project root path
|
|
494
|
+
* @param {string} reason - Failure reason
|
|
495
|
+
* @returns {object|null} Updated state or null on failure
|
|
496
|
+
*/
|
|
497
|
+
function fail(projectRoot, reason) {
|
|
498
|
+
const state = load(projectRoot);
|
|
499
|
+
if (!state) return null;
|
|
500
|
+
|
|
501
|
+
state.status = 'failed';
|
|
502
|
+
state.loopSession.failedAt = new Date().toISOString();
|
|
503
|
+
state.loopSession.failureReason = reason;
|
|
504
|
+
|
|
505
|
+
save(projectRoot, state);
|
|
506
|
+
return state;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Get build statistics
|
|
511
|
+
* @param {string} projectRoot - Project root path
|
|
512
|
+
* @returns {object|null} Statistics or null if no state
|
|
513
|
+
*/
|
|
514
|
+
function getStats(projectRoot) {
|
|
515
|
+
const state = load(projectRoot);
|
|
516
|
+
if (!state) return null;
|
|
517
|
+
|
|
518
|
+
const tasks = state.implementationQueue;
|
|
519
|
+
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
520
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
521
|
+
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
522
|
+
const blocked = tasks.filter(t => t.status === 'blocked').length;
|
|
523
|
+
const skipped = tasks.filter(t => t.status === 'skipped').length;
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
total: tasks.length,
|
|
527
|
+
completed,
|
|
528
|
+
pending,
|
|
529
|
+
inProgress,
|
|
530
|
+
blocked,
|
|
531
|
+
skipped,
|
|
532
|
+
progress: tasks.length > 0 ? Math.round((completed / tasks.length) * 100) : 0,
|
|
533
|
+
currentPhase: state.currentPhase,
|
|
534
|
+
mvpProgress: state.mvpCriteria.completionPercentage,
|
|
535
|
+
mvpComplete: state.mvpCriteria.allCriteriaMet,
|
|
536
|
+
iteration: state.loopSession.currentIteration,
|
|
537
|
+
maxIterations: state.loopSession.maxIterations,
|
|
538
|
+
status: state.status
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Reset build state
|
|
544
|
+
* @param {string} projectRoot - Project root path
|
|
545
|
+
* @returns {object} Fresh state
|
|
546
|
+
*/
|
|
547
|
+
function reset(projectRoot) {
|
|
548
|
+
const state = load(projectRoot);
|
|
549
|
+
const projectName = state?.projectName || 'Project';
|
|
550
|
+
|
|
551
|
+
return initialize(projectRoot, { projectName });
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
module.exports = {
|
|
555
|
+
BUILD_STATE_FILE,
|
|
556
|
+
PLANNING_DIR,
|
|
557
|
+
getDefaultState,
|
|
558
|
+
getPlanningDir,
|
|
559
|
+
getStatePath,
|
|
560
|
+
exists,
|
|
561
|
+
initialize,
|
|
562
|
+
generateSessionId,
|
|
563
|
+
load,
|
|
564
|
+
save,
|
|
565
|
+
updateProgress,
|
|
566
|
+
getNextTask,
|
|
567
|
+
addTask,
|
|
568
|
+
setTasks,
|
|
569
|
+
setMvpFeatures,
|
|
570
|
+
updateSession,
|
|
571
|
+
pause,
|
|
572
|
+
resume,
|
|
573
|
+
complete,
|
|
574
|
+
fail,
|
|
575
|
+
getStats,
|
|
576
|
+
reset
|
|
577
|
+
};
|