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