@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,599 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Health Command
|
|
3
|
+
* Quick project health overview and status check
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @module cli/health
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
|
|
13
|
+
// Import JS modules with type interfaces
|
|
14
|
+
interface Config {
|
|
15
|
+
_projectRoot: string;
|
|
16
|
+
project?: { name: string };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ConfigModule {
|
|
20
|
+
load(): Config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Phase {
|
|
24
|
+
status: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface WorkflowState {
|
|
28
|
+
phases?: Record<string, Phase>;
|
|
29
|
+
lastUpdated?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface WorkflowStatus {
|
|
33
|
+
exists: boolean;
|
|
34
|
+
progress?: number | undefined;
|
|
35
|
+
lastUpdated?: string | undefined;
|
|
36
|
+
isComplete?: boolean | undefined;
|
|
37
|
+
error?: boolean | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface TodoSummary {
|
|
41
|
+
exists: boolean;
|
|
42
|
+
pending?: number;
|
|
43
|
+
completed?: number;
|
|
44
|
+
total?: number;
|
|
45
|
+
error?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Vulnerabilities {
|
|
49
|
+
total?: number;
|
|
50
|
+
critical?: number;
|
|
51
|
+
high?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface DependencyHealth {
|
|
55
|
+
exists: boolean;
|
|
56
|
+
dependencies?: number;
|
|
57
|
+
devDependencies?: number;
|
|
58
|
+
total?: number;
|
|
59
|
+
vulnerabilities?: Vulnerabilities;
|
|
60
|
+
error?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface QualityMetrics {
|
|
64
|
+
hasTypeScript: boolean;
|
|
65
|
+
hasEslint: boolean;
|
|
66
|
+
hasPrettier: boolean;
|
|
67
|
+
hasTests: boolean;
|
|
68
|
+
testCoverage: number | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface MonitorMetrics {
|
|
72
|
+
exists: boolean;
|
|
73
|
+
noData?: boolean | undefined;
|
|
74
|
+
bundleSize?: number | undefined;
|
|
75
|
+
dependencies?: number | undefined;
|
|
76
|
+
timestamp?: string | undefined;
|
|
77
|
+
error?: boolean | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface CheckpointProgress {
|
|
81
|
+
completed: number;
|
|
82
|
+
total: number;
|
|
83
|
+
percentage: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ProjectStateModule {
|
|
87
|
+
getCheckpointProgress(projectRoot: string): CheckpointProgress | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface CheckpointEngineModule {
|
|
91
|
+
getColoredProgressBar(percentage: number, width: number): string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface HealthData {
|
|
95
|
+
workflows: Record<string, WorkflowStatus>;
|
|
96
|
+
todos: TodoSummary;
|
|
97
|
+
quality: QualityMetrics;
|
|
98
|
+
deps: DependencyHealth;
|
|
99
|
+
monitor: MonitorMetrics;
|
|
100
|
+
checkpoints: CheckpointProgress | null;
|
|
101
|
+
score?: number;
|
|
102
|
+
grade?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ParsedArgs {
|
|
106
|
+
_: string[];
|
|
107
|
+
json?: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
111
|
+
const config = require('../../core/config') as ConfigModule;
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
113
|
+
const utils = require('../../core/utils') as typeof import('../core/utils');
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
115
|
+
const projectState = require('../../core/project-state') as ProjectStateModule;
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
117
|
+
const checkpointEngine = require('../../core/checkpoint-engine') as CheckpointEngineModule;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get workflow status
|
|
121
|
+
*/
|
|
122
|
+
export function getWorkflowStatus(projectRoot: string): Record<string, WorkflowStatus> {
|
|
123
|
+
const workflows = ['analyze', 'audit', 'onboard', 'deploy'];
|
|
124
|
+
const status: Record<string, WorkflowStatus> = {};
|
|
125
|
+
|
|
126
|
+
for (const workflow of workflows) {
|
|
127
|
+
const stateFile = path.join(projectRoot, '.bootspring', workflow, 'workflow-state.json');
|
|
128
|
+
if (fs.existsSync(stateFile)) {
|
|
129
|
+
try {
|
|
130
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8')) as WorkflowState;
|
|
131
|
+
const completedPhases = Object.values(state.phases || {})
|
|
132
|
+
.filter(p => p.status === 'completed').length;
|
|
133
|
+
const totalPhases = Object.keys(state.phases || {}).length;
|
|
134
|
+
|
|
135
|
+
status[workflow] = {
|
|
136
|
+
exists: true,
|
|
137
|
+
progress: totalPhases > 0 ? Math.round((completedPhases / totalPhases) * 100) : 0,
|
|
138
|
+
lastUpdated: state.lastUpdated,
|
|
139
|
+
isComplete: completedPhases === totalPhases && totalPhases > 0
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
status[workflow] = { exists: true, error: true };
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
status[workflow] = { exists: false };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return status;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get todo summary
|
|
154
|
+
*/
|
|
155
|
+
export function getTodoSummary(projectRoot: string): TodoSummary {
|
|
156
|
+
const todoPath = path.join(projectRoot, 'todo.md');
|
|
157
|
+
if (!fs.existsSync(todoPath)) {
|
|
158
|
+
return { exists: false };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const content = fs.readFileSync(todoPath, 'utf-8');
|
|
163
|
+
const pending = (content.match(/- \[ \]/g) || []).length;
|
|
164
|
+
const completed = (content.match(/- \[x\]/gi) || []).length;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
exists: true,
|
|
168
|
+
pending,
|
|
169
|
+
completed,
|
|
170
|
+
total: pending + completed
|
|
171
|
+
};
|
|
172
|
+
} catch {
|
|
173
|
+
return { exists: true, error: true };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface PackageJson {
|
|
178
|
+
dependencies?: Record<string, string>;
|
|
179
|
+
devDependencies?: Record<string, string>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface AuditResult {
|
|
183
|
+
metadata?: {
|
|
184
|
+
vulnerabilities?: Vulnerabilities;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get dependency health
|
|
190
|
+
*/
|
|
191
|
+
export function getDependencyHealth(projectRoot: string): DependencyHealth {
|
|
192
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
193
|
+
if (!fs.existsSync(pkgPath)) {
|
|
194
|
+
return { exists: false };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as PackageJson;
|
|
199
|
+
const deps = Object.keys(pkg.dependencies || {}).length;
|
|
200
|
+
const devDeps = Object.keys(pkg.devDependencies || {}).length;
|
|
201
|
+
|
|
202
|
+
let vulnerabilities: Vulnerabilities = { total: 0, critical: 0, high: 0 };
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const auditResult = execSync('npm audit --json 2>/dev/null', {
|
|
206
|
+
cwd: projectRoot,
|
|
207
|
+
encoding: 'utf-8',
|
|
208
|
+
timeout: 10000,
|
|
209
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
210
|
+
});
|
|
211
|
+
const audit = JSON.parse(auditResult) as AuditResult;
|
|
212
|
+
if (audit.metadata && audit.metadata.vulnerabilities) {
|
|
213
|
+
vulnerabilities = audit.metadata.vulnerabilities;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Audit failed or timed out
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
exists: true,
|
|
221
|
+
dependencies: deps,
|
|
222
|
+
devDependencies: devDeps,
|
|
223
|
+
total: deps + devDeps,
|
|
224
|
+
vulnerabilities
|
|
225
|
+
};
|
|
226
|
+
} catch {
|
|
227
|
+
return { exists: true, error: true };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface CoverageSummary {
|
|
232
|
+
total?: {
|
|
233
|
+
lines?: {
|
|
234
|
+
pct?: number;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get quality metrics
|
|
241
|
+
*/
|
|
242
|
+
export function getQualityMetrics(projectRoot: string): QualityMetrics {
|
|
243
|
+
const metrics: QualityMetrics = {
|
|
244
|
+
hasTypeScript: false,
|
|
245
|
+
hasEslint: false,
|
|
246
|
+
hasPrettier: false,
|
|
247
|
+
hasTests: false,
|
|
248
|
+
testCoverage: null
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Check for TypeScript
|
|
252
|
+
if (fs.existsSync(path.join(projectRoot, 'tsconfig.json'))) {
|
|
253
|
+
metrics.hasTypeScript = true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check for ESLint
|
|
257
|
+
const eslintConfigs = ['.eslintrc', '.eslintrc.json', '.eslintrc.js', 'eslint.config.js'];
|
|
258
|
+
metrics.hasEslint = eslintConfigs.some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
259
|
+
|
|
260
|
+
// Check for Prettier
|
|
261
|
+
const prettierConfigs = ['.prettierrc', '.prettierrc.json', 'prettier.config.js'];
|
|
262
|
+
metrics.hasPrettier = prettierConfigs.some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
263
|
+
|
|
264
|
+
// Check for tests
|
|
265
|
+
const testDirs = ['__tests__', 'tests', 'test', 'spec'];
|
|
266
|
+
metrics.hasTests = testDirs.some(d => fs.existsSync(path.join(projectRoot, d)));
|
|
267
|
+
|
|
268
|
+
// Check for coverage
|
|
269
|
+
const coveragePath = path.join(projectRoot, 'coverage', 'coverage-summary.json');
|
|
270
|
+
if (fs.existsSync(coveragePath)) {
|
|
271
|
+
try {
|
|
272
|
+
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf-8')) as CoverageSummary;
|
|
273
|
+
metrics.testCoverage = coverage.total?.lines?.pct ?? null;
|
|
274
|
+
} catch {
|
|
275
|
+
// Coverage file invalid
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return metrics;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
interface MetricsData {
|
|
283
|
+
bundleSize?: number;
|
|
284
|
+
dependencies?: number;
|
|
285
|
+
timestamp?: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get monitor metrics
|
|
290
|
+
*/
|
|
291
|
+
function getMonitorMetrics(projectRoot: string): MonitorMetrics {
|
|
292
|
+
const metricsDir = path.join(projectRoot, '.bootspring', 'monitor', 'metrics');
|
|
293
|
+
if (!fs.existsSync(metricsDir)) {
|
|
294
|
+
return { exists: false };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const files = fs.readdirSync(metricsDir).sort().slice(-1);
|
|
299
|
+
if (files.length === 0) {
|
|
300
|
+
return { exists: true, noData: true };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const fileName = files[0];
|
|
304
|
+
if (!fileName) {
|
|
305
|
+
return { exists: true, noData: true };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const latest = JSON.parse(fs.readFileSync(path.join(metricsDir, fileName), 'utf-8')) as MetricsData;
|
|
309
|
+
return {
|
|
310
|
+
exists: true,
|
|
311
|
+
bundleSize: latest.bundleSize,
|
|
312
|
+
dependencies: latest.dependencies,
|
|
313
|
+
timestamp: latest.timestamp
|
|
314
|
+
};
|
|
315
|
+
} catch {
|
|
316
|
+
return { exists: true, error: true };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Calculate overall health score
|
|
322
|
+
*/
|
|
323
|
+
export function calculateHealthScore(data: HealthData): number {
|
|
324
|
+
let score = 0;
|
|
325
|
+
let maxScore = 0;
|
|
326
|
+
|
|
327
|
+
// Checkpoints (20 points max)
|
|
328
|
+
if (data.checkpoints && data.checkpoints.total > 0) {
|
|
329
|
+
maxScore += 20;
|
|
330
|
+
score += (data.checkpoints.percentage / 100) * 20;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Workflows (15 points max)
|
|
334
|
+
maxScore += 15;
|
|
335
|
+
const workflowsDone = Object.values(data.workflows)
|
|
336
|
+
.filter(w => w.isComplete).length;
|
|
337
|
+
score += (workflowsDone / 4) * 15;
|
|
338
|
+
|
|
339
|
+
// Todos (10 points max)
|
|
340
|
+
if (data.todos.exists && data.todos.total && data.todos.total > 0 && data.todos.completed !== undefined) {
|
|
341
|
+
maxScore += 10;
|
|
342
|
+
const completionRate = data.todos.completed / data.todos.total;
|
|
343
|
+
score += completionRate * 10;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Quality tooling (25 points max)
|
|
347
|
+
maxScore += 25;
|
|
348
|
+
if (data.quality.hasTypeScript) score += 8;
|
|
349
|
+
if (data.quality.hasEslint) score += 8;
|
|
350
|
+
if (data.quality.hasPrettier) score += 4;
|
|
351
|
+
if (data.quality.hasTests) score += 5;
|
|
352
|
+
|
|
353
|
+
// Test coverage (15 points max)
|
|
354
|
+
if (data.quality.testCoverage !== null) {
|
|
355
|
+
maxScore += 15;
|
|
356
|
+
score += (data.quality.testCoverage / 100) * 15;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Dependencies (15 points max)
|
|
360
|
+
maxScore += 15;
|
|
361
|
+
if (data.deps.exists) {
|
|
362
|
+
const vulns = data.deps.vulnerabilities || {};
|
|
363
|
+
if ((vulns.critical || 0) === 0 && (vulns.high || 0) === 0) {
|
|
364
|
+
score += 15;
|
|
365
|
+
} else if ((vulns.critical || 0) === 0) {
|
|
366
|
+
score += 8;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return Math.round((score / maxScore) * 100);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get health grade
|
|
375
|
+
*/
|
|
376
|
+
function getHealthGrade(score: number): { grade: string; color: string } {
|
|
377
|
+
if (score >= 90) return { grade: 'A', color: utils.COLORS.green };
|
|
378
|
+
if (score >= 80) return { grade: 'B', color: utils.COLORS.green };
|
|
379
|
+
if (score >= 70) return { grade: 'C', color: utils.COLORS.yellow };
|
|
380
|
+
if (score >= 60) return { grade: 'D', color: utils.COLORS.yellow };
|
|
381
|
+
return { grade: 'F', color: utils.COLORS.red };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Show health report
|
|
386
|
+
*/
|
|
387
|
+
function showHealth(projectRoot: string): { score: number; grade: string; data: HealthData } {
|
|
388
|
+
const cfg = config.load();
|
|
389
|
+
|
|
390
|
+
console.log(`
|
|
391
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project Health Report${utils.COLORS.reset}
|
|
392
|
+
${utils.COLORS.dim}${cfg.project?.name || 'Unknown Project'}${utils.COLORS.reset}
|
|
393
|
+
`);
|
|
394
|
+
|
|
395
|
+
// Collect all data
|
|
396
|
+
const data: HealthData = {
|
|
397
|
+
workflows: getWorkflowStatus(projectRoot),
|
|
398
|
+
todos: getTodoSummary(projectRoot),
|
|
399
|
+
quality: getQualityMetrics(projectRoot),
|
|
400
|
+
deps: getDependencyHealth(projectRoot),
|
|
401
|
+
monitor: getMonitorMetrics(projectRoot),
|
|
402
|
+
checkpoints: projectState.getCheckpointProgress(projectRoot)
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Calculate score
|
|
406
|
+
const score = calculateHealthScore(data);
|
|
407
|
+
const { grade, color } = getHealthGrade(score);
|
|
408
|
+
|
|
409
|
+
// Health Score
|
|
410
|
+
console.log(`${utils.COLORS.bold}Overall Health${utils.COLORS.reset}`);
|
|
411
|
+
console.log(` ${color}${utils.COLORS.bold}${grade}${utils.COLORS.reset} - ${score}/100`);
|
|
412
|
+
console.log();
|
|
413
|
+
|
|
414
|
+
// Checkpoints
|
|
415
|
+
if (data.checkpoints && data.checkpoints.total > 0) {
|
|
416
|
+
console.log(`${utils.COLORS.bold}Checkpoints${utils.COLORS.reset}`);
|
|
417
|
+
const cpBar = checkpointEngine.getColoredProgressBar(data.checkpoints.percentage, 20);
|
|
418
|
+
console.log(` ${cpBar} ${data.checkpoints.completed}/${data.checkpoints.total} (${data.checkpoints.percentage}%)`);
|
|
419
|
+
console.log();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Workflows
|
|
423
|
+
console.log(`${utils.COLORS.bold}Workflows${utils.COLORS.reset}`);
|
|
424
|
+
for (const [name, status] of Object.entries(data.workflows)) {
|
|
425
|
+
if (status.error) {
|
|
426
|
+
console.log(` ${utils.COLORS.yellow}⚠${utils.COLORS.reset} ${name}: Error reading state`);
|
|
427
|
+
} else if (!status.exists) {
|
|
428
|
+
console.log(` ${utils.COLORS.dim}○${utils.COLORS.reset} ${name}: Not started`);
|
|
429
|
+
} else if (status.isComplete) {
|
|
430
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} ${name}: Complete`);
|
|
431
|
+
} else {
|
|
432
|
+
console.log(` ${utils.COLORS.yellow}◐${utils.COLORS.reset} ${name}: ${status.progress}% complete`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
console.log();
|
|
436
|
+
|
|
437
|
+
// Todos
|
|
438
|
+
console.log(`${utils.COLORS.bold}Tasks${utils.COLORS.reset}`);
|
|
439
|
+
if (!data.todos.exists) {
|
|
440
|
+
console.log(` ${utils.COLORS.dim}No todo.md found${utils.COLORS.reset}`);
|
|
441
|
+
} else {
|
|
442
|
+
const pendingIcon = data.todos.pending === 0 ?
|
|
443
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
444
|
+
`${utils.COLORS.yellow}●${utils.COLORS.reset}`;
|
|
445
|
+
console.log(` ${pendingIcon} ${data.todos.pending} pending, ${data.todos.completed} completed`);
|
|
446
|
+
}
|
|
447
|
+
console.log();
|
|
448
|
+
|
|
449
|
+
// Quality
|
|
450
|
+
console.log(`${utils.COLORS.bold}Quality Tooling${utils.COLORS.reset}`);
|
|
451
|
+
const qIcon = (has: boolean): string => has ?
|
|
452
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
453
|
+
`${utils.COLORS.dim}○${utils.COLORS.reset}`;
|
|
454
|
+
console.log(` ${qIcon(data.quality.hasTypeScript)} TypeScript`);
|
|
455
|
+
console.log(` ${qIcon(data.quality.hasEslint)} ESLint`);
|
|
456
|
+
console.log(` ${qIcon(data.quality.hasPrettier)} Prettier`);
|
|
457
|
+
console.log(` ${qIcon(data.quality.hasTests)} Tests`);
|
|
458
|
+
if (data.quality.testCoverage !== null) {
|
|
459
|
+
const covColor = data.quality.testCoverage >= 80 ? utils.COLORS.green :
|
|
460
|
+
data.quality.testCoverage >= 60 ? utils.COLORS.yellow : utils.COLORS.red;
|
|
461
|
+
console.log(` ${covColor}●${utils.COLORS.reset} Coverage: ${data.quality.testCoverage}%`);
|
|
462
|
+
}
|
|
463
|
+
console.log();
|
|
464
|
+
|
|
465
|
+
// Dependencies
|
|
466
|
+
console.log(`${utils.COLORS.bold}Dependencies${utils.COLORS.reset}`);
|
|
467
|
+
if (!data.deps.exists) {
|
|
468
|
+
console.log(` ${utils.COLORS.dim}No package.json found${utils.COLORS.reset}`);
|
|
469
|
+
} else {
|
|
470
|
+
console.log(` ${utils.COLORS.dim}●${utils.COLORS.reset} ${data.deps.dependencies} deps, ${data.deps.devDependencies} devDeps`);
|
|
471
|
+
const vulns = data.deps.vulnerabilities || {};
|
|
472
|
+
if ((vulns.critical || 0) > 0) {
|
|
473
|
+
console.log(` ${utils.COLORS.red}✗${utils.COLORS.reset} ${vulns.critical} critical vulnerabilities`);
|
|
474
|
+
} else if ((vulns.high || 0) > 0) {
|
|
475
|
+
console.log(` ${utils.COLORS.yellow}⚠${utils.COLORS.reset} ${vulns.high} high vulnerabilities`);
|
|
476
|
+
} else if ((vulns.total || 0) > 0) {
|
|
477
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} ${vulns.total} vulnerabilities (low/moderate)`);
|
|
478
|
+
} else {
|
|
479
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} No vulnerabilities`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
console.log();
|
|
483
|
+
|
|
484
|
+
// Metrics
|
|
485
|
+
if (data.monitor.exists && !data.monitor.noData && !data.monitor.error) {
|
|
486
|
+
console.log(`${utils.COLORS.bold}Latest Metrics${utils.COLORS.reset}`);
|
|
487
|
+
if (data.monitor.bundleSize) {
|
|
488
|
+
console.log(` Bundle size: ${data.monitor.bundleSize} KB`);
|
|
489
|
+
}
|
|
490
|
+
if (data.monitor.timestamp) {
|
|
491
|
+
const date = new Date(data.monitor.timestamp).toLocaleDateString();
|
|
492
|
+
console.log(` ${utils.COLORS.dim}Collected: ${date}${utils.COLORS.reset}`);
|
|
493
|
+
}
|
|
494
|
+
console.log();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Recommendations
|
|
498
|
+
const recommendations: string[] = [];
|
|
499
|
+
|
|
500
|
+
// Checkpoint recommendations
|
|
501
|
+
if (!data.checkpoints || data.checkpoints.total === 0) {
|
|
502
|
+
recommendations.push('Run `bootspring checkpoint init` to start tracking progress');
|
|
503
|
+
} else if (data.checkpoints.percentage < 50) {
|
|
504
|
+
recommendations.push('Complete more checkpoints with `bootspring checkpoint sync`');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!data.quality.hasTypeScript) {
|
|
508
|
+
recommendations.push('Add TypeScript for type safety');
|
|
509
|
+
}
|
|
510
|
+
if (!data.quality.hasEslint) {
|
|
511
|
+
recommendations.push('Add ESLint for code quality');
|
|
512
|
+
}
|
|
513
|
+
if (!data.quality.hasTests) {
|
|
514
|
+
recommendations.push('Add tests for reliability');
|
|
515
|
+
}
|
|
516
|
+
if (data.todos.pending && data.todos.pending > 5) {
|
|
517
|
+
recommendations.push(`Address ${data.todos.pending} pending tasks`);
|
|
518
|
+
}
|
|
519
|
+
if (!data.workflows.analyze?.isComplete) {
|
|
520
|
+
recommendations.push('Run `bootspring analyze` for codebase insights');
|
|
521
|
+
}
|
|
522
|
+
if (!data.workflows.audit?.isComplete) {
|
|
523
|
+
recommendations.push('Run `bootspring audit` for quality assessment');
|
|
524
|
+
}
|
|
525
|
+
const vulns = data.deps.vulnerabilities || {};
|
|
526
|
+
if ((vulns.critical || 0) > 0 || (vulns.high || 0) > 0) {
|
|
527
|
+
recommendations.push('Run `npm audit fix` to address vulnerabilities');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (recommendations.length > 0) {
|
|
531
|
+
console.log(`${utils.COLORS.bold}Recommendations${utils.COLORS.reset}`);
|
|
532
|
+
recommendations.slice(0, 5).forEach(rec => {
|
|
533
|
+
console.log(` ${utils.COLORS.cyan}→${utils.COLORS.reset} ${rec}`);
|
|
534
|
+
});
|
|
535
|
+
console.log();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return { score, grade, data };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Show help
|
|
543
|
+
*/
|
|
544
|
+
function showHelp(): void {
|
|
545
|
+
console.log(`
|
|
546
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Health${utils.COLORS.reset}
|
|
547
|
+
${utils.COLORS.dim}Quick project health overview${utils.COLORS.reset}
|
|
548
|
+
|
|
549
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
550
|
+
bootspring health [options]
|
|
551
|
+
|
|
552
|
+
${utils.COLORS.bold}Options:${utils.COLORS.reset}
|
|
553
|
+
--json Output as JSON
|
|
554
|
+
|
|
555
|
+
${utils.COLORS.bold}What it checks:${utils.COLORS.reset}
|
|
556
|
+
- Workflow completion status
|
|
557
|
+
- Todo/task progress
|
|
558
|
+
- Quality tooling (TypeScript, ESLint, Prettier)
|
|
559
|
+
- Test coverage
|
|
560
|
+
- Dependency vulnerabilities
|
|
561
|
+
- Recent metrics
|
|
562
|
+
|
|
563
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
564
|
+
bootspring health
|
|
565
|
+
bootspring health --json
|
|
566
|
+
`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Run health command
|
|
571
|
+
*/
|
|
572
|
+
export async function run(args: string[]): Promise<void> {
|
|
573
|
+
const parsedArgs = utils.parseArgs(args) as ParsedArgs;
|
|
574
|
+
const subcommand = parsedArgs._[0];
|
|
575
|
+
|
|
576
|
+
if (subcommand === 'help' || subcommand === '-h' || subcommand === '--help') {
|
|
577
|
+
showHelp();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const cfg = config.load();
|
|
582
|
+
const projectRoot = cfg._projectRoot;
|
|
583
|
+
|
|
584
|
+
if (parsedArgs.json) {
|
|
585
|
+
const data: HealthData = {
|
|
586
|
+
workflows: getWorkflowStatus(projectRoot),
|
|
587
|
+
todos: getTodoSummary(projectRoot),
|
|
588
|
+
quality: getQualityMetrics(projectRoot),
|
|
589
|
+
deps: getDependencyHealth(projectRoot),
|
|
590
|
+
monitor: getMonitorMetrics(projectRoot),
|
|
591
|
+
checkpoints: projectState.getCheckpointProgress(projectRoot)
|
|
592
|
+
};
|
|
593
|
+
data.score = calculateHealthScore(data);
|
|
594
|
+
data.grade = getHealthGrade(data.score).grade;
|
|
595
|
+
console.log(JSON.stringify(data, null, 2));
|
|
596
|
+
} else {
|
|
597
|
+
showHealth(projectRoot);
|
|
598
|
+
}
|
|
599
|
+
}
|