@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.
Files changed (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,878 @@
1
+ /**
2
+ * Bootspring Manager Command
3
+ * Overview of all projects without entering each one
4
+ *
5
+ * Commands:
6
+ * list [--sort] [--type] List all projects with health scores (default)
7
+ * show <name> Detailed project view
8
+ * export Export report as markdown
9
+ * scan Scan for local projects in workspace
10
+ *
11
+ * @package bootspring
12
+ * @command manager
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as os from 'os';
18
+
19
+ // Type interfaces for JS modules
20
+ interface Colors {
21
+ reset: string;
22
+ bold: string;
23
+ dim: string;
24
+ cyan: string;
25
+ green: string;
26
+ yellow: string;
27
+ red: string;
28
+ magenta: string;
29
+ }
30
+
31
+ interface PrintModule {
32
+ error(msg: string): void;
33
+ dim(msg: string): void;
34
+ warning(msg: string): void;
35
+ success(msg: string): void;
36
+ }
37
+
38
+ interface Spinner {
39
+ start(): Spinner;
40
+ stop(): void;
41
+ succeed(text: string): void;
42
+ fail(text: string): void;
43
+ }
44
+
45
+ interface ParsedArgs {
46
+ _: string[];
47
+ sort?: string | undefined;
48
+ type?: string | undefined;
49
+ json?: boolean | undefined;
50
+ output?: string | undefined;
51
+ dir?: string | undefined;
52
+ }
53
+
54
+ interface UtilsModule {
55
+ COLORS: Colors;
56
+ print: PrintModule;
57
+ parseArgs(args: string[]): ParsedArgs;
58
+ createSpinner(text: string): Spinner;
59
+ formatRelativeTime(date: Date): string;
60
+ }
61
+
62
+ interface SessionProject {
63
+ name?: string | undefined;
64
+ path?: string | undefined;
65
+ }
66
+
67
+ interface SessionModule {
68
+ getRecentProjects(): SessionProject[] | null;
69
+ getCurrentProject(): SessionProject | null;
70
+ }
71
+
72
+ interface GitHubStats {
73
+ totalCommits: number;
74
+ openPRs: number;
75
+ contributors: number;
76
+ lastCommitMessage?: string | undefined;
77
+ }
78
+
79
+ interface GitHubInfo {
80
+ connected: boolean;
81
+ owner?: string | undefined;
82
+ repo?: string | undefined;
83
+ repositoryUrl?: string | undefined;
84
+ stats?: GitHubStats | undefined;
85
+ }
86
+
87
+ interface UsageInfo {
88
+ currentPeriod: string;
89
+ contextGenerations: number;
90
+ agentInvocations: number;
91
+ skillSearches: number;
92
+ mcpCalls: number;
93
+ }
94
+
95
+ interface HealthInfo {
96
+ score?: number | undefined;
97
+ }
98
+
99
+ interface ProjectStateData {
100
+ projectType?: string | undefined;
101
+ autoTagged?: boolean | undefined;
102
+ github?: GitHubInfo | undefined;
103
+ usage?: UsageInfo | undefined;
104
+ health?: HealthInfo | undefined;
105
+ }
106
+
107
+ interface ProjectStateModule {
108
+ loadState(projectRoot: string): ProjectStateData | null;
109
+ getCheckpointProgress(projectRoot: string): CheckpointProgress;
110
+ }
111
+
112
+ interface CheckpointProgress {
113
+ percentage: number;
114
+ completed: number;
115
+ total: number;
116
+ }
117
+
118
+ interface Checkpoint {
119
+ label: string;
120
+ completed: boolean;
121
+ }
122
+
123
+ interface CheckpointStatus {
124
+ exists: boolean;
125
+ checkpoints?: Checkpoint[] | undefined;
126
+ }
127
+
128
+ interface CheckpointEngineModule {
129
+ getColoredProgressBar(percentage: number, width: number): string;
130
+ getCheckpointStatus(projectRoot: string): CheckpointStatus;
131
+ }
132
+
133
+ interface TelemetryModule {
134
+ emitEvent(event: string, data: Record<string, unknown>): void;
135
+ }
136
+
137
+ const config = require('../core/config');
138
+ const utils = require('../core/utils') as UtilsModule;
139
+ const session = require('../core/session') as SessionModule;
140
+ const projectState = require('../core/project-state') as ProjectStateModule;
141
+ const checkpointEngine = require('../core/checkpoint-engine') as CheckpointEngineModule;
142
+ const telemetry = require('../core/telemetry') as TelemetryModule;
143
+
144
+ // ============================================================================
145
+ // Interfaces
146
+ // ============================================================================
147
+
148
+ interface Project {
149
+ name?: string | undefined;
150
+ path?: string | undefined;
151
+ source?: string | undefined;
152
+ }
153
+
154
+ interface HealthGrade {
155
+ grade: string;
156
+ color: string;
157
+ }
158
+
159
+ interface EnrichedProject extends Project {
160
+ exists: boolean;
161
+ health: number;
162
+ healthGrade?: HealthGrade | undefined;
163
+ checkpointProgress: CheckpointProgress;
164
+ projectType: string | null;
165
+ autoTagged?: boolean | undefined;
166
+ github: GitHubInfo | null;
167
+ usage?: UsageInfo | null | undefined;
168
+ lastActivity: Date | null;
169
+ stateExists?: boolean | undefined;
170
+ }
171
+
172
+ interface WorkspaceFile {
173
+ projects?: string[] | undefined;
174
+ }
175
+
176
+ interface ListOptions {
177
+ sort?: string | null | undefined;
178
+ type?: string | null | undefined;
179
+ json?: boolean | undefined;
180
+ }
181
+
182
+ interface ShowOptions {
183
+ json?: boolean | undefined;
184
+ }
185
+
186
+ interface ExportOptions {
187
+ output?: string | null | undefined;
188
+ }
189
+
190
+ interface ScanOptions {
191
+ dir?: string | null | undefined;
192
+ }
193
+
194
+ interface ScanIndicators {
195
+ config: boolean;
196
+ planning: boolean;
197
+ bootspringDir: boolean;
198
+ packageJson: boolean;
199
+ }
200
+
201
+ interface ScannedProject {
202
+ name: string;
203
+ path: string;
204
+ indicators: ScanIndicators;
205
+ }
206
+
207
+ // ============================================================================
208
+ // Project Discovery
209
+ // ============================================================================
210
+
211
+ /**
212
+ * Get all known projects
213
+ * Combines recent projects from session with workspace projects
214
+ */
215
+ function getKnownProjects(): Project[] {
216
+ const projects: Project[] = [];
217
+ const seenPaths = new Set<string>();
218
+
219
+ // Get recent projects from session
220
+ const recentProjects = session.getRecentProjects() || [];
221
+ for (const project of recentProjects) {
222
+ if (project.path && !seenPaths.has(project.path)) {
223
+ seenPaths.add(project.path);
224
+ projects.push({
225
+ ...project,
226
+ source: 'recent'
227
+ });
228
+ }
229
+ }
230
+
231
+ // Get current project
232
+ const currentProject = session.getCurrentProject();
233
+ if (currentProject?.path && !seenPaths.has(currentProject.path)) {
234
+ seenPaths.add(currentProject.path);
235
+ projects.push({
236
+ ...currentProject,
237
+ source: 'current'
238
+ });
239
+ }
240
+
241
+ // Scan workspace file if exists
242
+ const workspaceFile = path.join(os.homedir(), '.bootspring', 'workspace.json');
243
+ if (fs.existsSync(workspaceFile)) {
244
+ try {
245
+ const workspace = JSON.parse(fs.readFileSync(workspaceFile, 'utf-8')) as WorkspaceFile;
246
+ for (const projectPath of workspace.projects || []) {
247
+ if (!seenPaths.has(projectPath)) {
248
+ seenPaths.add(projectPath);
249
+ const projectName = path.basename(projectPath);
250
+ projects.push({
251
+ name: projectName,
252
+ path: projectPath,
253
+ source: 'workspace'
254
+ });
255
+ }
256
+ }
257
+ } catch {
258
+ // Ignore workspace read errors
259
+ }
260
+ }
261
+
262
+ return projects;
263
+ }
264
+
265
+ /**
266
+ * Get detailed info for a project
267
+ */
268
+ function getProjectDetails(project: Project): EnrichedProject {
269
+ const projectRoot = project.path;
270
+
271
+ if (!projectRoot || !fs.existsSync(projectRoot)) {
272
+ return {
273
+ ...project,
274
+ exists: false,
275
+ health: 0,
276
+ checkpointProgress: { percentage: 0, completed: 0, total: 0 },
277
+ projectType: null,
278
+ github: null,
279
+ lastActivity: null
280
+ };
281
+ }
282
+
283
+ // Load state
284
+ const state = projectState.loadState(projectRoot);
285
+
286
+ // Get checkpoint progress
287
+ let checkpointProgress: CheckpointProgress = { percentage: 0, completed: 0, total: 0 };
288
+ if (state) {
289
+ checkpointProgress = projectState.getCheckpointProgress(projectRoot);
290
+ }
291
+
292
+ // Get last activity from git or file timestamps
293
+ let lastActivity: Date | null = null;
294
+ try {
295
+ const gitHeadPath = path.join(projectRoot, '.git', 'HEAD');
296
+ if (fs.existsSync(gitHeadPath)) {
297
+ const stats = fs.statSync(gitHeadPath);
298
+ lastActivity = stats.mtime;
299
+ }
300
+ } catch {
301
+ // Ignore git errors
302
+ }
303
+
304
+ // Calculate health score
305
+ let health = 0;
306
+ if (state?.health?.score) {
307
+ health = state.health.score;
308
+ } else if (checkpointProgress.percentage > 0) {
309
+ // Calculate basic health from checkpoint progress
310
+ health = Math.round(checkpointProgress.percentage * 0.4); // 40% weight
311
+ if (state?.github?.connected) health += 20;
312
+ // Check for quality files
313
+ if (fs.existsSync(path.join(projectRoot, 'tsconfig.json'))) health += 10;
314
+ if (fs.existsSync(path.join(projectRoot, '.eslintrc')) ||
315
+ fs.existsSync(path.join(projectRoot, 'eslint.config.js'))) health += 10;
316
+ if (fs.existsSync(path.join(projectRoot, '__tests__')) ||
317
+ fs.existsSync(path.join(projectRoot, 'tests'))) health += 10;
318
+ }
319
+
320
+ return {
321
+ ...project,
322
+ exists: true,
323
+ health,
324
+ healthGrade: getHealthGrade(health),
325
+ checkpointProgress,
326
+ projectType: state?.projectType || 'development',
327
+ autoTagged: state?.autoTagged || false,
328
+ github: state?.github || null,
329
+ usage: state?.usage || null,
330
+ lastActivity,
331
+ stateExists: !!state
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Get health grade
337
+ */
338
+ function getHealthGrade(score: number): HealthGrade {
339
+ if (score >= 90) return { grade: 'A', color: utils.COLORS.green };
340
+ if (score >= 80) return { grade: 'B', color: utils.COLORS.green };
341
+ if (score >= 70) return { grade: 'C', color: utils.COLORS.yellow };
342
+ if (score >= 60) return { grade: 'D', color: utils.COLORS.yellow };
343
+ return { grade: 'F', color: utils.COLORS.red };
344
+ }
345
+
346
+ // ============================================================================
347
+ // Command Handlers
348
+ // ============================================================================
349
+
350
+ /**
351
+ * Get type badge
352
+ */
353
+ function getTypeBadge(projectType: string | null): string {
354
+ const badges: Record<string, string> = {
355
+ development: `${utils.COLORS.cyan}[DEV]${utils.COLORS.reset}`,
356
+ content: `${utils.COLORS.magenta}[CONTENT]${utils.COLORS.reset}`,
357
+ business: `${utils.COLORS.yellow}[BUSINESS]${utils.COLORS.reset}`
358
+ };
359
+ return badges[projectType || 'development'] || badges.development || '';
360
+ }
361
+
362
+ /**
363
+ * List all projects
364
+ */
365
+ function handleList(options: ListOptions = {}): void {
366
+ const projects = getKnownProjects();
367
+
368
+ if (projects.length === 0) {
369
+ console.log(`
370
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project Manager${utils.COLORS.reset}
371
+
372
+ ${utils.COLORS.dim}No projects found.${utils.COLORS.reset}
373
+
374
+ ${utils.COLORS.bold}To add projects:${utils.COLORS.reset}
375
+ 1. Use ${utils.COLORS.cyan}bootspring init${utils.COLORS.reset} in a project directory
376
+ 2. Or add to workspace: ${utils.COLORS.cyan}bootspring workspace add <path>${utils.COLORS.reset}
377
+ `);
378
+ return;
379
+ }
380
+
381
+ // Enhance with details
382
+ const enrichedProjects = projects.map(getProjectDetails);
383
+
384
+ // Filter by type if specified
385
+ let filteredProjects = enrichedProjects;
386
+ if (options.type) {
387
+ filteredProjects = enrichedProjects.filter(p => p.projectType === options.type);
388
+ }
389
+
390
+ // Sort projects
391
+ if (options.sort === 'health') {
392
+ filteredProjects.sort((a, b) => b.health - a.health);
393
+ } else if (options.sort === 'activity') {
394
+ filteredProjects.sort((a, b) => {
395
+ if (!a.lastActivity) return 1;
396
+ if (!b.lastActivity) return -1;
397
+ return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime();
398
+ });
399
+ } else if (options.sort === 'name') {
400
+ filteredProjects.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
401
+ }
402
+
403
+ // JSON output
404
+ if (options.json) {
405
+ console.log(JSON.stringify(filteredProjects, null, 2));
406
+ return;
407
+ }
408
+
409
+ // Display
410
+ console.log(`
411
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project Manager${utils.COLORS.reset}
412
+ ${utils.COLORS.dim}${filteredProjects.length} project(s)${utils.COLORS.reset}
413
+ `);
414
+
415
+ // Get current project for highlighting
416
+ const currentProject = session.getCurrentProject();
417
+ const currentPath = currentProject?.path;
418
+
419
+ for (const project of filteredProjects) {
420
+ const isCurrent = project.path === currentPath;
421
+ const marker = isCurrent ? `${utils.COLORS.green}›${utils.COLORS.reset} ` : ' ';
422
+
423
+ // Type badge
424
+ const typeBadge = getTypeBadge(project.projectType);
425
+
426
+ // Health indicator
427
+ const healthGrade = project.healthGrade || getHealthGrade(0);
428
+ const healthBadge = `${healthGrade.color}${healthGrade.grade}${utils.COLORS.reset}`;
429
+
430
+ // Checkpoint progress
431
+ const progress = project.checkpointProgress;
432
+ const progressBar = checkpointEngine.getColoredProgressBar(progress.percentage, 10);
433
+
434
+ // Name with current indicator
435
+ const nameStyle = isCurrent ? utils.COLORS.bold : '';
436
+ const name = project.name || path.basename(project.path || 'Unknown');
437
+
438
+ console.log(`${marker}${nameStyle}${name}${utils.COLORS.reset} ${typeBadge}`);
439
+ console.log(` Health: ${healthBadge} ${project.health}/100 Checkpoints: ${progressBar} ${progress.completed}/${progress.total}`);
440
+
441
+ // GitHub status
442
+ if (project.github?.connected) {
443
+ console.log(` ${utils.COLORS.dim}GitHub: ${project.github.owner}/${project.github.repo}${utils.COLORS.reset}`);
444
+ }
445
+
446
+ // Last activity
447
+ if (project.lastActivity) {
448
+ console.log(` ${utils.COLORS.dim}Last activity: ${utils.formatRelativeTime(project.lastActivity)}${utils.COLORS.reset}`);
449
+ }
450
+
451
+ console.log();
452
+ }
453
+
454
+ // Summary
455
+ const totalHealth = filteredProjects.reduce((sum, p) => sum + p.health, 0);
456
+ const avgHealth = filteredProjects.length > 0 ? Math.round(totalHealth / filteredProjects.length) : 0;
457
+ const connectedCount = filteredProjects.filter(p => p.github?.connected).length;
458
+
459
+ console.log(`${utils.COLORS.bold}Summary:${utils.COLORS.reset}`);
460
+ console.log(` Average health: ${avgHealth}/100`);
461
+ console.log(` GitHub connected: ${connectedCount}/${filteredProjects.length}`);
462
+ console.log();
463
+
464
+ // Emit telemetry
465
+ telemetry.emitEvent('manager:list', {
466
+ projectCount: filteredProjects.length,
467
+ avgHealth,
468
+ sortBy: options.sort || 'default'
469
+ });
470
+ }
471
+
472
+ /**
473
+ * Show detailed project view
474
+ */
475
+ function handleShow(nameOrPath: string | undefined, options: ShowOptions = {}): void {
476
+ if (!nameOrPath) {
477
+ utils.print.error('Please specify a project name or path.');
478
+ console.log(`\n${utils.COLORS.bold}Usage:${utils.COLORS.reset} bootspring manager show <name>`);
479
+ return;
480
+ }
481
+
482
+ const projects = getKnownProjects();
483
+
484
+ // Find project by name or path
485
+ let project: Project | undefined = projects.find(p =>
486
+ p.name === nameOrPath ||
487
+ p.path === nameOrPath ||
488
+ (p.path && path.basename(p.path) === nameOrPath)
489
+ );
490
+
491
+ // Try as absolute path
492
+ if (!project && fs.existsSync(nameOrPath)) {
493
+ project = { path: nameOrPath, name: path.basename(nameOrPath) };
494
+ }
495
+
496
+ if (!project) {
497
+ utils.print.error(`Project not found: ${nameOrPath}`);
498
+ console.log(`\n${utils.COLORS.bold}Available projects:${utils.COLORS.reset}`);
499
+ for (const p of projects) {
500
+ console.log(` - ${p.name || path.basename(p.path || '')}`);
501
+ }
502
+ return;
503
+ }
504
+
505
+ const details = getProjectDetails(project);
506
+
507
+ // JSON output
508
+ if (options.json) {
509
+ console.log(JSON.stringify(details, null, 2));
510
+ return;
511
+ }
512
+
513
+ const name = details.name || path.basename(details.path || '');
514
+ const typeBadge = getTypeBadge(details.projectType);
515
+ const healthGrade = details.healthGrade || getHealthGrade(0);
516
+
517
+ console.log(`
518
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Project: ${name}${utils.COLORS.reset} ${typeBadge}
519
+ ${utils.COLORS.dim}${details.path}${utils.COLORS.reset}
520
+ `);
521
+
522
+ // Health score
523
+ console.log(`${utils.COLORS.bold}Health Score${utils.COLORS.reset}`);
524
+ console.log(` ${healthGrade.color}${healthGrade.grade}${utils.COLORS.reset} ${details.health}/100`);
525
+ console.log();
526
+
527
+ // Checkpoint progress
528
+ const progress = details.checkpointProgress;
529
+ console.log(`${utils.COLORS.bold}Checkpoint Progress${utils.COLORS.reset}`);
530
+ console.log(` ${checkpointEngine.getColoredProgressBar(progress.percentage, 30)} ${progress.percentage}%`);
531
+ console.log(` ${progress.completed}/${progress.total} complete`);
532
+ console.log();
533
+
534
+ // If we have full state, show checkpoints
535
+ if (details.stateExists && details.path) {
536
+ const checkpointStatus = checkpointEngine.getCheckpointStatus(details.path);
537
+ if (checkpointStatus.exists && checkpointStatus.checkpoints) {
538
+ console.log(`${utils.COLORS.bold}Checkpoints:${utils.COLORS.reset}`);
539
+ for (const cp of checkpointStatus.checkpoints) {
540
+ const icon = cp.completed
541
+ ? `${utils.COLORS.green}✓${utils.COLORS.reset}`
542
+ : `${utils.COLORS.dim}○${utils.COLORS.reset}`;
543
+ console.log(` ${icon} ${cp.label}`);
544
+ }
545
+ console.log();
546
+ }
547
+ }
548
+
549
+ // GitHub
550
+ console.log(`${utils.COLORS.bold}GitHub${utils.COLORS.reset}`);
551
+ if (details.github?.connected) {
552
+ console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Connected: ${details.github.owner}/${details.github.repo}`);
553
+ if (details.github.stats) {
554
+ console.log(` Commits: ${details.github.stats.totalCommits}`);
555
+ console.log(` Open PRs: ${details.github.stats.openPRs}`);
556
+ console.log(` Contributors: ${details.github.stats.contributors}`);
557
+ if (details.github.stats.lastCommitMessage) {
558
+ console.log(` Latest: ${details.github.stats.lastCommitMessage}`);
559
+ }
560
+ }
561
+ } else {
562
+ console.log(` ${utils.COLORS.dim}Not connected${utils.COLORS.reset}`);
563
+ }
564
+ console.log();
565
+
566
+ // Usage
567
+ if (details.usage) {
568
+ console.log(`${utils.COLORS.bold}Usage (${details.usage.currentPeriod})${utils.COLORS.reset}`);
569
+ console.log(` Context generations: ${details.usage.contextGenerations}`);
570
+ console.log(` Agent invocations: ${details.usage.agentInvocations}`);
571
+ console.log(` Skill searches: ${details.usage.skillSearches}`);
572
+ console.log(` MCP calls: ${details.usage.mcpCalls}`);
573
+ console.log();
574
+ }
575
+
576
+ // Commands
577
+ console.log(`${utils.COLORS.bold}Commands:${utils.COLORS.reset}`);
578
+ console.log(` bootspring switch "${name}" Switch to this project`);
579
+ console.log(' bootspring checkpoint View checkpoints');
580
+ console.log(' bootspring github status View GitHub status');
581
+ console.log();
582
+
583
+ // Emit telemetry
584
+ telemetry.emitEvent('manager:show', {
585
+ projectName: name,
586
+ health: details.health
587
+ });
588
+ }
589
+
590
+ /**
591
+ * Export project report as markdown
592
+ */
593
+ function handleExport(options: ExportOptions = {}): void {
594
+ const projects = getKnownProjects().map(getProjectDetails);
595
+
596
+ if (projects.length === 0) {
597
+ utils.print.warning('No projects to export.');
598
+ return;
599
+ }
600
+
601
+ const date = new Date().toISOString().split('T')[0] || '';
602
+ const lines: string[] = [];
603
+
604
+ lines.push('# Project Manager Report');
605
+ lines.push('');
606
+ lines.push(`Generated: ${date}`);
607
+ lines.push(`Projects: ${projects.length}`);
608
+ lines.push('');
609
+ lines.push('---');
610
+ lines.push('');
611
+
612
+ // Summary table
613
+ lines.push('## Summary');
614
+ lines.push('');
615
+ lines.push('| Project | Type | Health | Checkpoints | GitHub |');
616
+ lines.push('|---------|------|--------|-------------|--------|');
617
+
618
+ for (const project of projects) {
619
+ const name = project.name || path.basename(project.path || 'Unknown');
620
+ const type = project.projectType || 'development';
621
+ const health = `${project.healthGrade?.grade || '-'} (${project.health})`;
622
+ const checkpoints = `${project.checkpointProgress.completed}/${project.checkpointProgress.total}`;
623
+ const github = project.github?.connected ? `${project.github.owner}/${project.github.repo}` : '-';
624
+
625
+ lines.push(`| ${name} | ${type} | ${health} | ${checkpoints} | ${github} |`);
626
+ }
627
+
628
+ lines.push('');
629
+ lines.push('---');
630
+ lines.push('');
631
+
632
+ // Detailed sections
633
+ lines.push('## Project Details');
634
+ lines.push('');
635
+
636
+ for (const project of projects) {
637
+ const name = project.name || path.basename(project.path || 'Unknown');
638
+
639
+ lines.push(`### ${name}`);
640
+ lines.push('');
641
+ lines.push(`- **Type:** ${project.projectType}`);
642
+ lines.push(`- **Health:** ${project.health}/100 (${project.healthGrade?.grade || '-'})`);
643
+ lines.push(`- **Checkpoints:** ${project.checkpointProgress.completed}/${project.checkpointProgress.total} (${project.checkpointProgress.percentage}%)`);
644
+
645
+ if (project.github?.connected) {
646
+ lines.push(`- **GitHub:** [${project.github.owner}/${project.github.repo}](${project.github.repositoryUrl})`);
647
+ if (project.github.stats?.lastCommitMessage) {
648
+ lines.push(`- **Latest Commit:** ${project.github.stats.lastCommitMessage}`);
649
+ }
650
+ }
651
+
652
+ if (project.lastActivity) {
653
+ lines.push(`- **Last Activity:** ${utils.formatRelativeTime(project.lastActivity)}`);
654
+ }
655
+
656
+ lines.push('');
657
+ }
658
+
659
+ // Statistics
660
+ lines.push('## Statistics');
661
+ lines.push('');
662
+
663
+ const totalHealth = projects.reduce((sum, p) => sum + p.health, 0);
664
+ const avgHealth = projects.length > 0 ? Math.round(totalHealth / projects.length) : 0;
665
+ const connectedCount = projects.filter(p => p.github?.connected).length;
666
+ const byType: Record<string, number> = {};
667
+ for (const p of projects) {
668
+ const type = p.projectType || 'development';
669
+ byType[type] = (byType[type] || 0) + 1;
670
+ }
671
+
672
+ lines.push(`- **Total Projects:** ${projects.length}`);
673
+ lines.push(`- **Average Health:** ${avgHealth}/100`);
674
+ lines.push(`- **GitHub Connected:** ${connectedCount}/${projects.length}`);
675
+ lines.push('');
676
+ lines.push('**By Type:**');
677
+ for (const [type, count] of Object.entries(byType)) {
678
+ lines.push(`- ${type}: ${count}`);
679
+ }
680
+ lines.push('');
681
+
682
+ lines.push('---');
683
+ lines.push('');
684
+ lines.push('*Generated by Bootspring Project Manager*');
685
+
686
+ const content = lines.join('\n');
687
+
688
+ // Output to file or console
689
+ if (options.output) {
690
+ try {
691
+ fs.writeFileSync(options.output, content, 'utf-8');
692
+ utils.print.success(`Report exported to: ${options.output}`);
693
+ } catch (error) {
694
+ utils.print.error(`Failed to write file: ${(error as Error).message}`);
695
+ }
696
+ } else {
697
+ const defaultPath = path.join(process.cwd(), `project-report-${date}.md`);
698
+ fs.writeFileSync(defaultPath, content, 'utf-8');
699
+ utils.print.success(`Report exported to: ${defaultPath}`);
700
+ }
701
+
702
+ // Emit telemetry
703
+ telemetry.emitEvent('manager:export', {
704
+ projectCount: projects.length
705
+ });
706
+ }
707
+
708
+ /**
709
+ * Scan for local projects
710
+ */
711
+ function handleScan(options: ScanOptions = {}): void {
712
+ const searchDir = options.dir || process.cwd();
713
+
714
+ const spinner = utils.createSpinner(`Scanning ${searchDir} for projects...`);
715
+ spinner.start();
716
+
717
+ const found: ScannedProject[] = [];
718
+
719
+ try {
720
+ // Look for directories with bootspring indicators
721
+ const entries = fs.readdirSync(searchDir, { withFileTypes: true });
722
+
723
+ for (const entry of entries) {
724
+ if (!entry.isDirectory()) continue;
725
+ if (entry.name.startsWith('.')) continue;
726
+ if (entry.name === 'node_modules') continue;
727
+
728
+ const dirPath = path.join(searchDir, entry.name);
729
+
730
+ // Check for bootspring indicators
731
+ const hasConfig = fs.existsSync(path.join(dirPath, 'bootspring.config.js'));
732
+ const hasPlanning = fs.existsSync(path.join(dirPath, 'planning'));
733
+ const hasBootstrapDir = fs.existsSync(path.join(dirPath, '.bootspring'));
734
+ const hasPackageJson = fs.existsSync(path.join(dirPath, 'package.json'));
735
+
736
+ if (hasConfig || hasPlanning || hasBootstrapDir) {
737
+ found.push({
738
+ name: entry.name,
739
+ path: dirPath,
740
+ indicators: {
741
+ config: hasConfig,
742
+ planning: hasPlanning,
743
+ bootspringDir: hasBootstrapDir,
744
+ packageJson: hasPackageJson
745
+ }
746
+ });
747
+ }
748
+ }
749
+ } catch (error) {
750
+ spinner.fail(`Scan failed: ${(error as Error).message}`);
751
+ return;
752
+ }
753
+
754
+ spinner.succeed(`Found ${found.length} project(s)`);
755
+
756
+ if (found.length === 0) {
757
+ console.log(`\n${utils.COLORS.dim}No Bootspring projects found in ${searchDir}${utils.COLORS.reset}`);
758
+ return;
759
+ }
760
+
761
+ console.log();
762
+
763
+ for (const project of found) {
764
+ console.log(` ${utils.COLORS.cyan}●${utils.COLORS.reset} ${project.name}`);
765
+ console.log(` ${utils.COLORS.dim}${project.path}${utils.COLORS.reset}`);
766
+ }
767
+
768
+ console.log(`
769
+ ${utils.COLORS.bold}To add to workspace:${utils.COLORS.reset}
770
+ bootspring workspace add <path>
771
+ `);
772
+ }
773
+
774
+ /**
775
+ * Show help
776
+ */
777
+ function showHelp(): void {
778
+ console.log(`
779
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Manager${utils.COLORS.reset}
780
+ ${utils.COLORS.dim}Overview of all projects${utils.COLORS.reset}
781
+
782
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
783
+ bootspring manager [command] [options]
784
+
785
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
786
+ list List all projects with health scores (default)
787
+ show <name> Detailed project view
788
+ export Export report as markdown
789
+ scan Scan for local projects
790
+
791
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
792
+ --sort=<field> Sort by: health, activity, name
793
+ --type=<type> Filter by type: dev, content, business
794
+ --json Output as JSON
795
+ --output=<file> Export output file path
796
+ --dir=<path> Directory to scan
797
+
798
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
799
+ bootspring manager # List all projects
800
+ bootspring manager --sort=health # Sort by health score
801
+ bootspring manager show my-project # View project details
802
+ bootspring manager export # Generate markdown report
803
+ bootspring manager scan --dir=~/projects # Find projects
804
+ `);
805
+ }
806
+
807
+ // ============================================================================
808
+ // Main Runner
809
+ // ============================================================================
810
+
811
+ /**
812
+ * Run manager command
813
+ */
814
+ export async function run(args: string[]): Promise<void> {
815
+ // Parse arguments
816
+ const parsed = utils.parseArgs(args);
817
+ const subcommand = parsed._[0] || 'list';
818
+ const options = {
819
+ sort: parsed.sort || null,
820
+ type: parsed.type || null,
821
+ json: parsed.json || false,
822
+ output: parsed.output || null,
823
+ dir: parsed.dir || null
824
+ };
825
+
826
+ switch (subcommand) {
827
+ case 'list':
828
+ case 'ls':
829
+ case 'l':
830
+ handleList(options);
831
+ break;
832
+
833
+ case 'show':
834
+ case 'view':
835
+ case 's':
836
+ handleShow(parsed._[1], options);
837
+ break;
838
+
839
+ case 'export':
840
+ case 'report':
841
+ case 'e':
842
+ handleExport(options);
843
+ break;
844
+
845
+ case 'scan':
846
+ case 'find':
847
+ handleScan(options);
848
+ break;
849
+
850
+ case 'help':
851
+ case '-h':
852
+ case '--help':
853
+ showHelp();
854
+ break;
855
+
856
+ default:
857
+ // If it looks like a project name, treat as show
858
+ if (!subcommand.startsWith('-')) {
859
+ handleShow(subcommand, options);
860
+ } else {
861
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
862
+ showHelp();
863
+ }
864
+ }
865
+ }
866
+
867
+ // ============================================================================
868
+ // Exports
869
+ // ============================================================================
870
+
871
+ export {
872
+ getKnownProjects,
873
+ getProjectDetails,
874
+ handleList,
875
+ handleShow,
876
+ handleExport,
877
+ handleScan
878
+ };