@girardmedia/bootspring 2.0.21 → 2.0.23
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/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,621 @@
|
|
|
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
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
|
|
14
|
+
export const BUILD_STATE_FILE = 'BUILD_STATE.json';
|
|
15
|
+
export const PLANNING_DIR = 'planning';
|
|
16
|
+
|
|
17
|
+
// Type definitions
|
|
18
|
+
export interface Task {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
description?: string | undefined;
|
|
22
|
+
source?: string | undefined;
|
|
23
|
+
sourceSection?: string | null | undefined;
|
|
24
|
+
phase?: string | undefined;
|
|
25
|
+
status: 'pending' | 'in_progress' | 'completed' | 'blocked' | 'skipped';
|
|
26
|
+
acceptanceCriteria?: string[] | undefined;
|
|
27
|
+
dependencies?: string[] | undefined;
|
|
28
|
+
estimatedComplexity?: string | undefined;
|
|
29
|
+
createdAt?: string | undefined;
|
|
30
|
+
updatedAt?: string | undefined;
|
|
31
|
+
completedAt?: string | undefined;
|
|
32
|
+
error?: string | undefined;
|
|
33
|
+
lastOutput?: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MvpFeature {
|
|
37
|
+
name: string;
|
|
38
|
+
status: 'pending' | 'completed';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PhaseData {
|
|
42
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
43
|
+
tasks: { id: string; title: string; status: string }[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LoopSession {
|
|
47
|
+
sessionId: string | null;
|
|
48
|
+
currentIteration: number;
|
|
49
|
+
maxIterations: number;
|
|
50
|
+
startedAt: string | null;
|
|
51
|
+
lastUpdated: string | null;
|
|
52
|
+
pausedAt: string | null;
|
|
53
|
+
completedAt?: string | undefined;
|
|
54
|
+
failedAt?: string | undefined;
|
|
55
|
+
failureReason?: string | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MvpCriteria {
|
|
59
|
+
features: MvpFeature[];
|
|
60
|
+
completionPercentage: number;
|
|
61
|
+
allCriteriaMet: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface BuildMetadata {
|
|
65
|
+
createdAt: string;
|
|
66
|
+
updatedAt: string;
|
|
67
|
+
seedSource: string | null;
|
|
68
|
+
preseedDocs: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface BuildState {
|
|
72
|
+
version: string;
|
|
73
|
+
projectName: string;
|
|
74
|
+
status: 'pending' | 'in_progress' | 'paused' | 'completed' | 'failed';
|
|
75
|
+
currentPhase: string | null;
|
|
76
|
+
phases: Record<string, PhaseData>;
|
|
77
|
+
implementationQueue: Task[];
|
|
78
|
+
mvpCriteria: MvpCriteria;
|
|
79
|
+
loopSession: LoopSession;
|
|
80
|
+
metadata: BuildMetadata;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BuildStats {
|
|
84
|
+
total: number;
|
|
85
|
+
completed: number;
|
|
86
|
+
pending: number;
|
|
87
|
+
inProgress: number;
|
|
88
|
+
blocked: number;
|
|
89
|
+
skipped: number;
|
|
90
|
+
progress: number;
|
|
91
|
+
currentPhase: string | null;
|
|
92
|
+
mvpProgress: number;
|
|
93
|
+
mvpComplete: boolean;
|
|
94
|
+
iteration: number;
|
|
95
|
+
maxIterations: number;
|
|
96
|
+
status: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface InitConfig {
|
|
100
|
+
projectName?: string | undefined;
|
|
101
|
+
phases?: Record<string, PhaseData> | undefined;
|
|
102
|
+
implementationQueue?: Task[] | undefined;
|
|
103
|
+
mvpCriteria?: Partial<MvpCriteria> | undefined;
|
|
104
|
+
maxIterations?: number | undefined;
|
|
105
|
+
seedSource?: string | undefined;
|
|
106
|
+
preseedDocs?: string[] | undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface UpdateDetails {
|
|
110
|
+
completedAt?: string | undefined;
|
|
111
|
+
error?: string | undefined;
|
|
112
|
+
output?: string | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Default build state schema
|
|
117
|
+
*/
|
|
118
|
+
export function getDefaultState(projectName: string = 'Project'): BuildState {
|
|
119
|
+
return {
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
projectName,
|
|
122
|
+
status: 'pending',
|
|
123
|
+
currentPhase: null,
|
|
124
|
+
|
|
125
|
+
phases: {
|
|
126
|
+
foundation: { status: 'pending', tasks: [] },
|
|
127
|
+
mvp: { status: 'pending', tasks: [] },
|
|
128
|
+
launch: { status: 'pending', tasks: [] }
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
implementationQueue: [],
|
|
132
|
+
|
|
133
|
+
mvpCriteria: {
|
|
134
|
+
features: [],
|
|
135
|
+
completionPercentage: 0,
|
|
136
|
+
allCriteriaMet: false
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
loopSession: {
|
|
140
|
+
sessionId: null,
|
|
141
|
+
currentIteration: 0,
|
|
142
|
+
maxIterations: 50,
|
|
143
|
+
startedAt: null,
|
|
144
|
+
lastUpdated: null,
|
|
145
|
+
pausedAt: null
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
metadata: {
|
|
149
|
+
createdAt: new Date().toISOString(),
|
|
150
|
+
updatedAt: new Date().toISOString(),
|
|
151
|
+
seedSource: null,
|
|
152
|
+
preseedDocs: []
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the planning directory path
|
|
159
|
+
*/
|
|
160
|
+
export function getPlanningDir(projectRoot: string): string {
|
|
161
|
+
return path.join(projectRoot, PLANNING_DIR);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the build state file path
|
|
166
|
+
*/
|
|
167
|
+
export function getStatePath(projectRoot: string): string {
|
|
168
|
+
return path.join(getPlanningDir(projectRoot), BUILD_STATE_FILE);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if build state exists
|
|
173
|
+
*/
|
|
174
|
+
export function exists(projectRoot: string): boolean {
|
|
175
|
+
return fs.existsSync(getStatePath(projectRoot));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate a unique session ID
|
|
180
|
+
*/
|
|
181
|
+
export function generateSessionId(): string {
|
|
182
|
+
return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Initialize a new build state
|
|
187
|
+
*/
|
|
188
|
+
export function initialize(projectRoot: string, config: InitConfig = {}): BuildState {
|
|
189
|
+
const planningDir = getPlanningDir(projectRoot);
|
|
190
|
+
|
|
191
|
+
// Create planning directory if it doesn't exist
|
|
192
|
+
if (!fs.existsSync(planningDir)) {
|
|
193
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const state = getDefaultState(config.projectName || 'Project');
|
|
197
|
+
|
|
198
|
+
// Apply config overrides
|
|
199
|
+
if (config.phases) {
|
|
200
|
+
state.phases = { ...state.phases, ...config.phases };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (config.implementationQueue) {
|
|
204
|
+
state.implementationQueue = config.implementationQueue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (config.mvpCriteria) {
|
|
208
|
+
state.mvpCriteria = { ...state.mvpCriteria, ...config.mvpCriteria };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (config.maxIterations) {
|
|
212
|
+
state.loopSession.maxIterations = config.maxIterations;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (config.seedSource) {
|
|
216
|
+
state.metadata.seedSource = config.seedSource;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (config.preseedDocs) {
|
|
220
|
+
state.metadata.preseedDocs = config.preseedDocs;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Generate session ID
|
|
224
|
+
state.loopSession.sessionId = generateSessionId();
|
|
225
|
+
|
|
226
|
+
// Save initial state
|
|
227
|
+
save(projectRoot, state);
|
|
228
|
+
|
|
229
|
+
return state;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Load build state from disk
|
|
234
|
+
*/
|
|
235
|
+
export function load(projectRoot: string): BuildState | null {
|
|
236
|
+
const statePath = getStatePath(projectRoot);
|
|
237
|
+
|
|
238
|
+
if (!fs.existsSync(statePath)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const content = fs.readFileSync(statePath, 'utf-8');
|
|
244
|
+
return JSON.parse(content) as BuildState;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error(`Failed to load build state: ${(error as Error).message}`);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Save build state to disk
|
|
253
|
+
*/
|
|
254
|
+
export function save(projectRoot: string, state: BuildState): boolean {
|
|
255
|
+
const statePath = getStatePath(projectRoot);
|
|
256
|
+
const planningDir = getPlanningDir(projectRoot);
|
|
257
|
+
|
|
258
|
+
// Ensure planning directory exists
|
|
259
|
+
if (!fs.existsSync(planningDir)) {
|
|
260
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Update timestamp
|
|
264
|
+
state.metadata.updatedAt = new Date().toISOString();
|
|
265
|
+
state.loopSession.lastUpdated = new Date().toISOString();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
269
|
+
return true;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(`Failed to save build state: ${(error as Error).message}`);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Update phase progress based on task statuses
|
|
278
|
+
*/
|
|
279
|
+
function updatePhaseProgress(state: BuildState, phaseName: string): void {
|
|
280
|
+
const phaseTasks = state.implementationQueue.filter(t => t.phase === phaseName);
|
|
281
|
+
const completedTasks = phaseTasks.filter(t => t.status === 'completed');
|
|
282
|
+
|
|
283
|
+
if (phaseTasks.length === 0) return;
|
|
284
|
+
|
|
285
|
+
const progress = completedTasks.length / phaseTasks.length;
|
|
286
|
+
const phase = state.phases[phaseName];
|
|
287
|
+
|
|
288
|
+
if (!phase) return;
|
|
289
|
+
|
|
290
|
+
if (progress === 0) {
|
|
291
|
+
phase.status = 'pending';
|
|
292
|
+
} else if (progress === 1) {
|
|
293
|
+
phase.status = 'completed';
|
|
294
|
+
} else {
|
|
295
|
+
phase.status = 'in_progress';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
phase.tasks = phaseTasks.map(t => ({
|
|
299
|
+
id: t.id,
|
|
300
|
+
title: t.title,
|
|
301
|
+
status: t.status
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Update MVP criteria progress
|
|
307
|
+
*/
|
|
308
|
+
function updateMvpProgress(state: BuildState): void {
|
|
309
|
+
const mvpTasks = state.implementationQueue.filter(t => t.phase === 'mvp');
|
|
310
|
+
const completedMvpTasks = mvpTasks.filter(t => t.status === 'completed');
|
|
311
|
+
|
|
312
|
+
if (mvpTasks.length > 0) {
|
|
313
|
+
state.mvpCriteria.completionPercentage = Math.round(
|
|
314
|
+
(completedMvpTasks.length / mvpTasks.length) * 100
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Update feature completion status
|
|
319
|
+
state.mvpCriteria.features = state.mvpCriteria.features.map(feature => {
|
|
320
|
+
const relatedTasks = mvpTasks.filter(t =>
|
|
321
|
+
t.title.toLowerCase().includes(feature.name.toLowerCase()) ||
|
|
322
|
+
t.sourceSection?.toLowerCase().includes(feature.name.toLowerCase())
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const allComplete = relatedTasks.length > 0 &&
|
|
326
|
+
relatedTasks.every(t => t.status === 'completed');
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
...feature,
|
|
330
|
+
status: allComplete ? 'completed' as const : 'pending' as const
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Check if all MVP criteria are met
|
|
335
|
+
state.mvpCriteria.allCriteriaMet =
|
|
336
|
+
state.mvpCriteria.completionPercentage === 100 ||
|
|
337
|
+
state.mvpCriteria.features.every(f => f.status === 'completed');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Update task progress
|
|
342
|
+
*/
|
|
343
|
+
export function updateProgress(
|
|
344
|
+
projectRoot: string,
|
|
345
|
+
taskId: string,
|
|
346
|
+
status: Task['status'],
|
|
347
|
+
details: UpdateDetails = {}
|
|
348
|
+
): BuildState | null {
|
|
349
|
+
const state = load(projectRoot);
|
|
350
|
+
if (!state) return null;
|
|
351
|
+
|
|
352
|
+
// Find task in queue
|
|
353
|
+
const taskIndex = state.implementationQueue.findIndex(t => t.id === taskId);
|
|
354
|
+
if (taskIndex === -1) return null;
|
|
355
|
+
|
|
356
|
+
const task = state.implementationQueue[taskIndex];
|
|
357
|
+
if (!task) return null;
|
|
358
|
+
|
|
359
|
+
// Update task status
|
|
360
|
+
task.status = status;
|
|
361
|
+
task.updatedAt = new Date().toISOString();
|
|
362
|
+
|
|
363
|
+
// Add any additional details
|
|
364
|
+
if (details.completedAt) {
|
|
365
|
+
task.completedAt = details.completedAt;
|
|
366
|
+
}
|
|
367
|
+
if (details.error) {
|
|
368
|
+
task.error = details.error;
|
|
369
|
+
}
|
|
370
|
+
if (details.output) {
|
|
371
|
+
task.lastOutput = details.output;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Update phase progress
|
|
375
|
+
if (task.phase && state.phases[task.phase]) {
|
|
376
|
+
updatePhaseProgress(state, task.phase);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Update MVP criteria progress
|
|
380
|
+
updateMvpProgress(state);
|
|
381
|
+
|
|
382
|
+
// Update iteration count if completing
|
|
383
|
+
if (status === 'completed' || status === 'skipped') {
|
|
384
|
+
state.loopSession.currentIteration++;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Save updated state
|
|
388
|
+
save(projectRoot, state);
|
|
389
|
+
|
|
390
|
+
return state;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get the next pending task
|
|
395
|
+
*/
|
|
396
|
+
export function getNextTask(projectRoot: string): Task | null {
|
|
397
|
+
const state = load(projectRoot);
|
|
398
|
+
if (!state) return null;
|
|
399
|
+
|
|
400
|
+
// First, check for any in_progress tasks
|
|
401
|
+
const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
|
|
402
|
+
if (inProgress) return inProgress;
|
|
403
|
+
|
|
404
|
+
// Then get next pending task (respecting dependencies)
|
|
405
|
+
const pendingTasks = state.implementationQueue.filter(t => t.status === 'pending');
|
|
406
|
+
|
|
407
|
+
for (const task of pendingTasks) {
|
|
408
|
+
// Check if all dependencies are completed
|
|
409
|
+
if (!task.dependencies || task.dependencies.length === 0) {
|
|
410
|
+
return task;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const depsCompleted = task.dependencies.every(depId => {
|
|
414
|
+
const depTask = state.implementationQueue.find(t => t.id === depId);
|
|
415
|
+
return depTask && depTask.status === 'completed';
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
if (depsCompleted) {
|
|
419
|
+
return task;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Add a task to the implementation queue
|
|
428
|
+
*/
|
|
429
|
+
export function addTask(projectRoot: string, task: Partial<Task>): BuildState | null {
|
|
430
|
+
const state = load(projectRoot);
|
|
431
|
+
if (!state) return null;
|
|
432
|
+
|
|
433
|
+
// Generate task ID if not provided
|
|
434
|
+
if (!task.id) {
|
|
435
|
+
const count = state.implementationQueue.length + 1;
|
|
436
|
+
task.id = `task-${count.toString().padStart(3, '0')}`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Set defaults
|
|
440
|
+
const newTask: Task = {
|
|
441
|
+
id: task.id,
|
|
442
|
+
title: task.title || 'Untitled Task',
|
|
443
|
+
description: task.description,
|
|
444
|
+
source: task.source,
|
|
445
|
+
sourceSection: task.sourceSection,
|
|
446
|
+
phase: task.phase,
|
|
447
|
+
status: task.status || 'pending',
|
|
448
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
449
|
+
dependencies: task.dependencies,
|
|
450
|
+
estimatedComplexity: task.estimatedComplexity,
|
|
451
|
+
createdAt: new Date().toISOString()
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
state.implementationQueue.push(newTask);
|
|
455
|
+
save(projectRoot, state);
|
|
456
|
+
|
|
457
|
+
return state;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Set tasks in batch
|
|
462
|
+
*/
|
|
463
|
+
export function setTasks(projectRoot: string, tasks: Partial<Task>[]): BuildState | null {
|
|
464
|
+
const state = load(projectRoot);
|
|
465
|
+
if (!state) return null;
|
|
466
|
+
|
|
467
|
+
// Process tasks with defaults
|
|
468
|
+
state.implementationQueue = tasks.map((task, index) => ({
|
|
469
|
+
id: task.id || `task-${(index + 1).toString().padStart(3, '0')}`,
|
|
470
|
+
title: task.title || 'Untitled Task',
|
|
471
|
+
description: task.description || '',
|
|
472
|
+
source: task.source || 'manual',
|
|
473
|
+
sourceSection: task.sourceSection || null,
|
|
474
|
+
phase: task.phase || 'mvp',
|
|
475
|
+
status: task.status || 'pending',
|
|
476
|
+
acceptanceCriteria: task.acceptanceCriteria || [],
|
|
477
|
+
dependencies: task.dependencies || [],
|
|
478
|
+
estimatedComplexity: task.estimatedComplexity || 'medium',
|
|
479
|
+
createdAt: new Date().toISOString()
|
|
480
|
+
}));
|
|
481
|
+
|
|
482
|
+
save(projectRoot, state);
|
|
483
|
+
return state;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Set MVP features
|
|
488
|
+
*/
|
|
489
|
+
export function setMvpFeatures(projectRoot: string, features: (string | { name: string })[]): BuildState | null {
|
|
490
|
+
const state = load(projectRoot);
|
|
491
|
+
if (!state) return null;
|
|
492
|
+
|
|
493
|
+
state.mvpCriteria.features = features.map(f => ({
|
|
494
|
+
name: typeof f === 'string' ? f : f.name,
|
|
495
|
+
status: 'pending' as const
|
|
496
|
+
}));
|
|
497
|
+
|
|
498
|
+
save(projectRoot, state);
|
|
499
|
+
return state;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Update loop session
|
|
504
|
+
*/
|
|
505
|
+
export function updateSession(projectRoot: string, updates: Partial<LoopSession>): BuildState | null {
|
|
506
|
+
const state = load(projectRoot);
|
|
507
|
+
if (!state) return null;
|
|
508
|
+
|
|
509
|
+
state.loopSession = {
|
|
510
|
+
...state.loopSession,
|
|
511
|
+
...updates
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
save(projectRoot, state);
|
|
515
|
+
return state;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Pause the build loop
|
|
520
|
+
*/
|
|
521
|
+
export function pause(projectRoot: string): BuildState | null {
|
|
522
|
+
const state = load(projectRoot);
|
|
523
|
+
if (!state) return null;
|
|
524
|
+
|
|
525
|
+
state.status = 'paused';
|
|
526
|
+
state.loopSession.pausedAt = new Date().toISOString();
|
|
527
|
+
|
|
528
|
+
save(projectRoot, state);
|
|
529
|
+
return state;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Resume the build loop
|
|
534
|
+
*/
|
|
535
|
+
export function resume(projectRoot: string): BuildState | null {
|
|
536
|
+
const state = load(projectRoot);
|
|
537
|
+
if (!state) return null;
|
|
538
|
+
|
|
539
|
+
state.status = 'in_progress';
|
|
540
|
+
state.loopSession.pausedAt = null;
|
|
541
|
+
|
|
542
|
+
// Generate new session ID if resuming after long pause
|
|
543
|
+
const pausedAt = state.loopSession.pausedAt ? new Date(state.loopSession.pausedAt) : null;
|
|
544
|
+
const now = new Date();
|
|
545
|
+
if (pausedAt && (now.getTime() - pausedAt.getTime()) > 3600000) { // 1 hour
|
|
546
|
+
state.loopSession.sessionId = generateSessionId();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
save(projectRoot, state);
|
|
550
|
+
return state;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Mark build as complete
|
|
555
|
+
*/
|
|
556
|
+
export function complete(projectRoot: string): BuildState | null {
|
|
557
|
+
const state = load(projectRoot);
|
|
558
|
+
if (!state) return null;
|
|
559
|
+
|
|
560
|
+
state.status = 'completed';
|
|
561
|
+
state.loopSession.completedAt = new Date().toISOString();
|
|
562
|
+
|
|
563
|
+
save(projectRoot, state);
|
|
564
|
+
return state;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Mark build as failed
|
|
569
|
+
*/
|
|
570
|
+
export function fail(projectRoot: string, reason: string): BuildState | null {
|
|
571
|
+
const state = load(projectRoot);
|
|
572
|
+
if (!state) return null;
|
|
573
|
+
|
|
574
|
+
state.status = 'failed';
|
|
575
|
+
state.loopSession.failedAt = new Date().toISOString();
|
|
576
|
+
state.loopSession.failureReason = reason;
|
|
577
|
+
|
|
578
|
+
save(projectRoot, state);
|
|
579
|
+
return state;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Get build statistics
|
|
584
|
+
*/
|
|
585
|
+
export function getStats(projectRoot: string): BuildStats | null {
|
|
586
|
+
const state = load(projectRoot);
|
|
587
|
+
if (!state) return null;
|
|
588
|
+
|
|
589
|
+
const tasks = state.implementationQueue;
|
|
590
|
+
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
591
|
+
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
592
|
+
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
593
|
+
const blocked = tasks.filter(t => t.status === 'blocked').length;
|
|
594
|
+
const skipped = tasks.filter(t => t.status === 'skipped').length;
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
total: tasks.length,
|
|
598
|
+
completed,
|
|
599
|
+
pending,
|
|
600
|
+
inProgress,
|
|
601
|
+
blocked,
|
|
602
|
+
skipped,
|
|
603
|
+
progress: tasks.length > 0 ? Math.round((completed / tasks.length) * 100) : 0,
|
|
604
|
+
currentPhase: state.currentPhase,
|
|
605
|
+
mvpProgress: state.mvpCriteria.completionPercentage,
|
|
606
|
+
mvpComplete: state.mvpCriteria.allCriteriaMet,
|
|
607
|
+
iteration: state.loopSession.currentIteration,
|
|
608
|
+
maxIterations: state.loopSession.maxIterations,
|
|
609
|
+
status: state.status
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Reset build state
|
|
615
|
+
*/
|
|
616
|
+
export function reset(projectRoot: string): BuildState {
|
|
617
|
+
const state = load(projectRoot);
|
|
618
|
+
const projectName = state?.projectName || 'Project';
|
|
619
|
+
|
|
620
|
+
return initialize(projectRoot, { projectName });
|
|
621
|
+
}
|