@blockspool/cli 0.4.0 → 0.4.1

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 (149) hide show
  1. package/package.json +1 -1
  2. package/dist/bin/blockspool.d.ts +0 -16
  3. package/dist/bin/blockspool.d.ts.map +0 -1
  4. package/dist/bin/blockspool.js +0 -45
  5. package/dist/bin/blockspool.js.map +0 -1
  6. package/dist/commands/solo-auto.d.ts +0 -6
  7. package/dist/commands/solo-auto.d.ts.map +0 -1
  8. package/dist/commands/solo-auto.js +0 -418
  9. package/dist/commands/solo-auto.js.map +0 -1
  10. package/dist/commands/solo-exec.d.ts +0 -6
  11. package/dist/commands/solo-exec.d.ts.map +0 -1
  12. package/dist/commands/solo-exec.js +0 -656
  13. package/dist/commands/solo-exec.js.map +0 -1
  14. package/dist/commands/solo-inspect.d.ts +0 -6
  15. package/dist/commands/solo-inspect.d.ts.map +0 -1
  16. package/dist/commands/solo-inspect.js +0 -690
  17. package/dist/commands/solo-inspect.js.map +0 -1
  18. package/dist/commands/solo-lifecycle.d.ts +0 -6
  19. package/dist/commands/solo-lifecycle.d.ts.map +0 -1
  20. package/dist/commands/solo-lifecycle.js +0 -188
  21. package/dist/commands/solo-lifecycle.js.map +0 -1
  22. package/dist/commands/solo-nudge.d.ts +0 -6
  23. package/dist/commands/solo-nudge.d.ts.map +0 -1
  24. package/dist/commands/solo-nudge.js +0 -49
  25. package/dist/commands/solo-nudge.js.map +0 -1
  26. package/dist/commands/solo-qa.d.ts +0 -6
  27. package/dist/commands/solo-qa.d.ts.map +0 -1
  28. package/dist/commands/solo-qa.js +0 -254
  29. package/dist/commands/solo-qa.js.map +0 -1
  30. package/dist/commands/solo.d.ts +0 -11
  31. package/dist/commands/solo.d.ts.map +0 -1
  32. package/dist/commands/solo.js +0 -43
  33. package/dist/commands/solo.js.map +0 -1
  34. package/dist/index.d.ts +0 -18
  35. package/dist/index.d.ts.map +0 -1
  36. package/dist/index.js +0 -18
  37. package/dist/index.js.map +0 -1
  38. package/dist/lib/artifacts.d.ts +0 -136
  39. package/dist/lib/artifacts.d.ts.map +0 -1
  40. package/dist/lib/artifacts.js +0 -146
  41. package/dist/lib/artifacts.js.map +0 -1
  42. package/dist/lib/doctor.d.ts +0 -45
  43. package/dist/lib/doctor.d.ts.map +0 -1
  44. package/dist/lib/doctor.js +0 -383
  45. package/dist/lib/doctor.js.map +0 -1
  46. package/dist/lib/exec.d.ts +0 -24
  47. package/dist/lib/exec.d.ts.map +0 -1
  48. package/dist/lib/exec.js +0 -295
  49. package/dist/lib/exec.js.map +0 -1
  50. package/dist/lib/formulas.d.ts +0 -78
  51. package/dist/lib/formulas.d.ts.map +0 -1
  52. package/dist/lib/formulas.js +0 -295
  53. package/dist/lib/formulas.js.map +0 -1
  54. package/dist/lib/git.d.ts +0 -9
  55. package/dist/lib/git.d.ts.map +0 -1
  56. package/dist/lib/git.js +0 -60
  57. package/dist/lib/git.js.map +0 -1
  58. package/dist/lib/guidelines.d.ts +0 -43
  59. package/dist/lib/guidelines.d.ts.map +0 -1
  60. package/dist/lib/guidelines.js +0 -195
  61. package/dist/lib/guidelines.js.map +0 -1
  62. package/dist/lib/logger.d.ts +0 -17
  63. package/dist/lib/logger.d.ts.map +0 -1
  64. package/dist/lib/logger.js +0 -42
  65. package/dist/lib/logger.js.map +0 -1
  66. package/dist/lib/retention.d.ts +0 -62
  67. package/dist/lib/retention.d.ts.map +0 -1
  68. package/dist/lib/retention.js +0 -285
  69. package/dist/lib/retention.js.map +0 -1
  70. package/dist/lib/run-history.d.ts +0 -52
  71. package/dist/lib/run-history.d.ts.map +0 -1
  72. package/dist/lib/run-history.js +0 -116
  73. package/dist/lib/run-history.js.map +0 -1
  74. package/dist/lib/run-state.d.ts +0 -58
  75. package/dist/lib/run-state.d.ts.map +0 -1
  76. package/dist/lib/run-state.js +0 -119
  77. package/dist/lib/run-state.js.map +0 -1
  78. package/dist/lib/scope.d.ts +0 -95
  79. package/dist/lib/scope.d.ts.map +0 -1
  80. package/dist/lib/scope.js +0 -291
  81. package/dist/lib/scope.js.map +0 -1
  82. package/dist/lib/selection.d.ts +0 -35
  83. package/dist/lib/selection.d.ts.map +0 -1
  84. package/dist/lib/selection.js +0 -110
  85. package/dist/lib/selection.js.map +0 -1
  86. package/dist/lib/solo-auto.d.ts +0 -87
  87. package/dist/lib/solo-auto.d.ts.map +0 -1
  88. package/dist/lib/solo-auto.js +0 -1230
  89. package/dist/lib/solo-auto.js.map +0 -1
  90. package/dist/lib/solo-ci.d.ts +0 -84
  91. package/dist/lib/solo-ci.d.ts.map +0 -1
  92. package/dist/lib/solo-ci.js +0 -300
  93. package/dist/lib/solo-ci.js.map +0 -1
  94. package/dist/lib/solo-config.d.ts +0 -155
  95. package/dist/lib/solo-config.d.ts.map +0 -1
  96. package/dist/lib/solo-config.js +0 -236
  97. package/dist/lib/solo-config.js.map +0 -1
  98. package/dist/lib/solo-git.d.ts +0 -44
  99. package/dist/lib/solo-git.d.ts.map +0 -1
  100. package/dist/lib/solo-git.js +0 -174
  101. package/dist/lib/solo-git.js.map +0 -1
  102. package/dist/lib/solo-hints.d.ts +0 -32
  103. package/dist/lib/solo-hints.d.ts.map +0 -1
  104. package/dist/lib/solo-hints.js +0 -98
  105. package/dist/lib/solo-hints.js.map +0 -1
  106. package/dist/lib/solo-remote.d.ts +0 -14
  107. package/dist/lib/solo-remote.d.ts.map +0 -1
  108. package/dist/lib/solo-remote.js +0 -48
  109. package/dist/lib/solo-remote.js.map +0 -1
  110. package/dist/lib/solo-stdin.d.ts +0 -13
  111. package/dist/lib/solo-stdin.d.ts.map +0 -1
  112. package/dist/lib/solo-stdin.js +0 -33
  113. package/dist/lib/solo-stdin.js.map +0 -1
  114. package/dist/lib/solo-ticket.d.ts +0 -213
  115. package/dist/lib/solo-ticket.d.ts.map +0 -1
  116. package/dist/lib/solo-ticket.js +0 -850
  117. package/dist/lib/solo-ticket.js.map +0 -1
  118. package/dist/lib/solo-utils.d.ts +0 -133
  119. package/dist/lib/solo-utils.d.ts.map +0 -1
  120. package/dist/lib/solo-utils.js +0 -300
  121. package/dist/lib/solo-utils.js.map +0 -1
  122. package/dist/lib/spindle.d.ts +0 -144
  123. package/dist/lib/spindle.d.ts.map +0 -1
  124. package/dist/lib/spindle.js +0 -388
  125. package/dist/lib/spindle.js.map +0 -1
  126. package/dist/tui/app.d.ts +0 -17
  127. package/dist/tui/app.d.ts.map +0 -1
  128. package/dist/tui/app.js +0 -139
  129. package/dist/tui/app.js.map +0 -1
  130. package/dist/tui/index.d.ts +0 -8
  131. package/dist/tui/index.d.ts.map +0 -1
  132. package/dist/tui/index.js +0 -7
  133. package/dist/tui/index.js.map +0 -1
  134. package/dist/tui/poller.d.ts +0 -42
  135. package/dist/tui/poller.d.ts.map +0 -1
  136. package/dist/tui/poller.js +0 -62
  137. package/dist/tui/poller.js.map +0 -1
  138. package/dist/tui/screens/overview.d.ts +0 -9
  139. package/dist/tui/screens/overview.d.ts.map +0 -1
  140. package/dist/tui/screens/overview.js +0 -189
  141. package/dist/tui/screens/overview.js.map +0 -1
  142. package/dist/tui/state.d.ts +0 -93
  143. package/dist/tui/state.d.ts.map +0 -1
  144. package/dist/tui/state.js +0 -169
  145. package/dist/tui/state.js.map +0 -1
  146. package/dist/tui/types.d.ts +0 -18
  147. package/dist/tui/types.d.ts.map +0 -1
  148. package/dist/tui/types.js +0 -5
  149. package/dist/tui/types.js.map +0 -1
@@ -1,690 +0,0 @@
1
- /**
2
- * Solo inspect commands: scout, status, history, formulas, export, artifacts, approve
3
- */
4
- import * as path from 'node:path';
5
- import * as fs from 'node:fs';
6
- import chalk from 'chalk';
7
- import { scoutRepo, approveProposals, } from '@blockspool/core/services';
8
- import { projects, tickets, runs } from '@blockspool/core/repos';
9
- import { createGitService } from '../lib/git.js';
10
- import { writeJsonArtifact, getLatestArtifact, getAllArtifacts, getArtifactsForRun, getArtifactByRunId, } from '../lib/artifacts.js';
11
- import { parseSelection } from '../lib/selection.js';
12
- import { getBlockspoolDir, getDbPath, isInitialized, initSolo, getAdapter, createScoutDeps, formatProgress, displayProposal, } from '../lib/solo-config.js';
13
- import { formatDuration, formatRelativeTime, } from '../lib/solo-utils.js';
14
- export function registerInspectCommands(solo) {
15
- /**
16
- * solo scout - Scan codebase and create tickets
17
- */
18
- solo
19
- .command('scout [path]')
20
- .description('Scan a codebase and create improvement tickets')
21
- .option('-v, --verbose', 'Show detailed output')
22
- .option('-q, --quiet', 'Suppress non-essential output')
23
- .option('--json', 'Output as JSON')
24
- .option('-s, --scope <pattern>', 'Glob pattern for files to scan', 'src/**')
25
- .option('-t, --types <categories>', 'Comma-separated categories: refactor,docs,test,perf,security')
26
- .option('-m, --max <count>', 'Maximum proposals to generate', '10')
27
- .option('-c, --min-confidence <percent>', 'Minimum confidence threshold', '50')
28
- .option('--model <model>', 'Model to use: haiku, sonnet, opus', 'opus')
29
- .option('--auto-approve', 'Automatically create tickets for all proposals')
30
- .option('--dry-run', 'Show proposals without saving')
31
- .option('--max-files <count>', 'Maximum files to scan (default: 500)')
32
- .action(async (targetPath, options) => {
33
- const isJsonMode = options.json;
34
- const isQuiet = options.quiet || isJsonMode;
35
- const scope = targetPath ?? options.scope ?? 'src/**';
36
- if (!isQuiet) {
37
- console.log(chalk.blue('🔍 BlockSpool Solo Scout'));
38
- console.log();
39
- }
40
- const git = createGitService();
41
- const repoRoot = await git.findRepoRoot('.');
42
- if (!repoRoot) {
43
- if (isJsonMode) {
44
- console.log(JSON.stringify({ success: false, error: 'Not a git repository' }));
45
- }
46
- else {
47
- console.error(chalk.red('✗ Not a git repository'));
48
- console.error(' Run this command from within a git repository');
49
- }
50
- process.exit(1);
51
- }
52
- if (!isInitialized(repoRoot)) {
53
- if (!isQuiet) {
54
- console.log(chalk.gray('Initializing local state...'));
55
- }
56
- await initSolo(repoRoot);
57
- }
58
- if (!isQuiet) {
59
- console.log(chalk.gray(`Project: ${path.basename(repoRoot)}`));
60
- console.log(chalk.gray(`Scope: ${scope}`));
61
- console.log();
62
- }
63
- const adapter = await getAdapter(repoRoot);
64
- const deps = createScoutDeps(adapter, options);
65
- try {
66
- const types = options.types?.split(',').map(t => t.trim());
67
- const maxProposals = parseInt(options.max || '10', 10);
68
- const minConfidence = parseInt(options.minConfidence || '50', 10);
69
- const model = (options.model || 'opus');
70
- const maxFiles = options.maxFiles ? parseInt(options.maxFiles, 10) : undefined;
71
- // eslint-disable-next-line no-undef
72
- const controller = new AbortController();
73
- process.on('SIGINT', () => {
74
- if (!isQuiet) {
75
- console.log(chalk.yellow('\n\nAborting scan...'));
76
- }
77
- controller.abort();
78
- });
79
- let lastProgress = '';
80
- const scoutOptions = {
81
- path: repoRoot,
82
- scope,
83
- types,
84
- maxProposals,
85
- minConfidence,
86
- model,
87
- signal: controller.signal,
88
- autoApprove: options.autoApprove && !options.dryRun,
89
- onProgress: (progress) => {
90
- if (!isQuiet) {
91
- const formatted = formatProgress(progress);
92
- if (formatted !== lastProgress) {
93
- process.stdout.write(`\r${formatted.padEnd(80)}`);
94
- lastProgress = formatted;
95
- }
96
- }
97
- },
98
- ...(maxFiles !== undefined && { maxFiles }),
99
- };
100
- const result = await scoutRepo(deps, scoutOptions);
101
- if (!isQuiet) {
102
- process.stdout.write('\r' + ' '.repeat(80) + '\r');
103
- }
104
- let proposalsArtifactPath = null;
105
- if (result.proposals.length > 0) {
106
- proposalsArtifactPath = writeJsonArtifact({
107
- baseDir: getBlockspoolDir(repoRoot),
108
- type: 'proposals',
109
- id: result.run.id,
110
- data: {
111
- runId: result.run.id,
112
- projectId: result.project.id,
113
- projectName: result.project.name,
114
- createdAt: new Date().toISOString(),
115
- proposals: result.proposals,
116
- },
117
- });
118
- }
119
- if (isJsonMode) {
120
- const output = {
121
- success: result.success,
122
- project: path.basename(repoRoot),
123
- scannedFiles: result.scannedFiles,
124
- durationMs: result.durationMs,
125
- proposals: result.proposals.map(p => ({
126
- title: p.title,
127
- category: p.category,
128
- description: p.description,
129
- files: p.files,
130
- estimated_complexity: p.estimated_complexity,
131
- confidence: p.confidence,
132
- })),
133
- tickets: result.tickets.map(t => ({
134
- id: t.id,
135
- title: t.title,
136
- status: t.status,
137
- })),
138
- errors: result.errors,
139
- };
140
- console.log(JSON.stringify(output, null, 2));
141
- return;
142
- }
143
- if (!result.success) {
144
- console.error(chalk.red('✗ Scout failed'));
145
- for (const error of result.errors) {
146
- console.error(chalk.red(` ${error}`));
147
- }
148
- process.exit(1);
149
- }
150
- console.log(chalk.green(`✓ Scanned ${result.scannedFiles} files in ` +
151
- `${(result.durationMs / 1000).toFixed(1)}s`));
152
- if (result.proposals.length === 0) {
153
- console.log(chalk.yellow('\nNo improvement opportunities found.'));
154
- console.log(chalk.gray('Try broadening your scope or lowering the confidence threshold.'));
155
- return;
156
- }
157
- console.log(chalk.blue(`\nFound ${result.proposals.length} proposals:`));
158
- for (let i = 0; i < result.proposals.length; i++) {
159
- displayProposal(result.proposals[i], i);
160
- }
161
- if (options.dryRun) {
162
- console.log(chalk.yellow('\n--dry-run: Proposals not saved'));
163
- return;
164
- }
165
- if (result.tickets.length > 0) {
166
- console.log(chalk.green(`\n✓ Created ${result.tickets.length} tickets`));
167
- console.log(chalk.gray(` IDs: ${result.tickets.map(t => t.id).join(', ')}`));
168
- }
169
- else if (!options.autoApprove && result.proposals.length > 0) {
170
- console.log(chalk.blue('\nNext steps:'));
171
- console.log(' blockspool solo approve 1-3 # Approve proposals 1-3');
172
- console.log(' blockspool solo approve all # Approve all proposals');
173
- if (proposalsArtifactPath) {
174
- console.log(chalk.gray(`\n Proposals saved: ${proposalsArtifactPath}`));
175
- }
176
- }
177
- if (result.errors.length > 0) {
178
- console.log(chalk.yellow('\nWarnings:'));
179
- for (const error of result.errors) {
180
- console.log(chalk.yellow(` ${error}`));
181
- }
182
- }
183
- }
184
- finally {
185
- await adapter.close();
186
- }
187
- });
188
- /**
189
- * solo status - Show current state
190
- */
191
- solo
192
- .command('status')
193
- .description('Show local state and active tickets')
194
- .option('-v, --verbose', 'Show detailed output')
195
- .option('--json', 'Output as JSON')
196
- .action(async (options) => {
197
- const git = createGitService();
198
- const repoRoot = await git.findRepoRoot(process.cwd());
199
- if (!repoRoot) {
200
- if (options.json) {
201
- console.log(JSON.stringify({ error: 'Not a git repository' }));
202
- }
203
- else {
204
- console.error(chalk.red('✗ Not a git repository'));
205
- }
206
- process.exit(1);
207
- }
208
- const dbPath = getDbPath(repoRoot);
209
- const adapter = await getAdapter(repoRoot);
210
- try {
211
- const projectList = await projects.list(adapter);
212
- if (options.json) {
213
- const output = {
214
- dbPath,
215
- projects: [],
216
- };
217
- for (const project of projectList) {
218
- const counts = await tickets.countByStatus(adapter, project.id);
219
- const summary = await runs.getSummary(adapter, project.id);
220
- let lastExecuteCompletionOutcome = null;
221
- if (summary.lastExecute?.id) {
222
- const fullRun = await runs.getById(adapter, summary.lastExecute.id);
223
- if (fullRun?.metadata?.completionOutcome) {
224
- lastExecuteCompletionOutcome = fullRun.metadata.completionOutcome;
225
- }
226
- }
227
- output.projects.push({
228
- id: project.id,
229
- name: project.name,
230
- ticketCounts: counts,
231
- lastScout: summary.lastScout ? {
232
- ...summary.lastScout,
233
- completedAt: summary.lastScout.completedAt?.toISOString() ?? null,
234
- } : null,
235
- lastQa: summary.lastQa ? {
236
- ...summary.lastQa,
237
- completedAt: summary.lastQa.completedAt?.toISOString() ?? null,
238
- } : null,
239
- lastExecute: summary.lastExecute ? {
240
- ...summary.lastExecute,
241
- completedAt: summary.lastExecute.completedAt?.toISOString() ?? null,
242
- completionOutcome: lastExecuteCompletionOutcome,
243
- } : null,
244
- activeRuns: summary.activeRuns,
245
- });
246
- }
247
- console.log(JSON.stringify(output, null, 2));
248
- return;
249
- }
250
- console.log(chalk.blue('📊 BlockSpool Solo Status'));
251
- console.log();
252
- console.log(chalk.gray(`Database: ${dbPath}`));
253
- console.log();
254
- console.log(`Projects: ${projectList.length}`);
255
- if (projectList.length === 0) {
256
- console.log(chalk.yellow('\nNo projects found. Run: blockspool solo scout .'));
257
- return;
258
- }
259
- for (const project of projectList) {
260
- console.log();
261
- console.log(chalk.bold(project.name));
262
- const summary = await runs.getSummary(adapter, project.id);
263
- if (summary.lastScout) {
264
- const scout = summary.lastScout;
265
- const statusColor = scout.status === 'success' ? chalk.green :
266
- scout.status === 'failure' ? chalk.red : chalk.yellow;
267
- const timeAgo = scout.completedAt ? formatRelativeTime(scout.completedAt) : 'running';
268
- console.log();
269
- console.log(` ${chalk.cyan('Last Scout:')}`);
270
- console.log(` ${statusColor(scout.status)} | ${timeAgo}`);
271
- console.log(` ${scout.scannedFiles} files scanned, ${scout.proposalCount} proposals, ${scout.ticketCount} tickets`);
272
- if (scout.durationMs > 0) {
273
- console.log(` Duration: ${formatDuration(scout.durationMs)}`);
274
- }
275
- }
276
- if (summary.lastQa) {
277
- const qa = summary.lastQa;
278
- const statusColor = qa.status === 'success' ? chalk.green :
279
- qa.status === 'failure' ? chalk.red : chalk.yellow;
280
- const timeAgo = qa.completedAt ? formatRelativeTime(qa.completedAt) : 'running';
281
- const passFailText = `${qa.stepsPassed} passed, ${qa.stepsFailed} failed`;
282
- console.log();
283
- console.log(` ${chalk.cyan('Last QA:')}`);
284
- console.log(` ${statusColor(qa.status)} | ${timeAgo}`);
285
- console.log(` ${passFailText}`);
286
- if (qa.durationMs > 0) {
287
- console.log(` Duration: ${formatDuration(qa.durationMs)}`);
288
- }
289
- }
290
- if (summary.lastExecute) {
291
- const exec = summary.lastExecute;
292
- const baseDir = getBlockspoolDir(repoRoot);
293
- let spindleInfo = null;
294
- if (exec.status === 'failure' && exec.id) {
295
- const spindleArtifact = getArtifactByRunId(baseDir, exec.id, 'spindle');
296
- if (spindleArtifact) {
297
- const data = spindleArtifact.data;
298
- spindleInfo = {
299
- reason: data.reason,
300
- artifactPath: spindleArtifact.path,
301
- };
302
- }
303
- }
304
- let completionOutcome = null;
305
- if (exec.status === 'success' && exec.id) {
306
- const fullRun = await runs.getById(adapter, exec.id);
307
- if (fullRun?.metadata?.completionOutcome) {
308
- completionOutcome = fullRun.metadata.completionOutcome;
309
- }
310
- }
311
- const isNoChangesNeeded = completionOutcome === 'no_changes_needed';
312
- const isSpindleFailure = exec.status === 'failure' && spindleInfo?.reason;
313
- const statusColor = exec.status === 'success' ? chalk.green :
314
- isSpindleFailure ? chalk.yellow :
315
- exec.status === 'failure' ? chalk.red : chalk.yellow;
316
- const timeAgo = exec.completedAt ? formatRelativeTime(exec.completedAt) : 'running';
317
- console.log();
318
- console.log(` ${chalk.cyan('Last Execute:')}`);
319
- if (isSpindleFailure) {
320
- console.log(` ${statusColor('failed')} (Spindle: ${spindleInfo.reason}) | ${timeAgo}`);
321
- console.log(chalk.gray(` See artifacts: ${spindleInfo.artifactPath}`));
322
- }
323
- else if (isNoChangesNeeded) {
324
- console.log(` ${statusColor('success')} (no changes needed) | ${timeAgo}`);
325
- }
326
- else {
327
- console.log(` ${statusColor(exec.status)} | ${timeAgo}`);
328
- }
329
- if (exec.ticketId) {
330
- console.log(` Ticket: ${exec.ticketId}`);
331
- }
332
- if (exec.branchName) {
333
- console.log(` Branch: ${exec.branchName}`);
334
- }
335
- if (exec.prUrl) {
336
- console.log(` PR: ${chalk.cyan(exec.prUrl)}`);
337
- }
338
- if (exec.durationMs > 0) {
339
- console.log(` Duration: ${formatDuration(exec.durationMs)}`);
340
- }
341
- }
342
- const counts = await tickets.countByStatus(adapter, project.id);
343
- const total = Object.values(counts).reduce((a, b) => a + b, 0);
344
- console.log();
345
- console.log(` ${chalk.cyan('Tickets:')}`);
346
- if (total === 0) {
347
- console.log(chalk.gray(' No tickets'));
348
- }
349
- else {
350
- for (const [status, count] of Object.entries(counts)) {
351
- if (count === 0)
352
- continue;
353
- const color = status === 'done' ? chalk.green :
354
- status === 'blocked' || status === 'aborted' ? chalk.red :
355
- status === 'in_progress' || status === 'leased' ? chalk.yellow :
356
- chalk.gray;
357
- console.log(` ${color(status)}: ${count}`);
358
- }
359
- }
360
- if (summary.activeRuns > 0) {
361
- console.log(` ${chalk.cyan('active runs')}: ${summary.activeRuns}`);
362
- }
363
- }
364
- }
365
- finally {
366
- await adapter.close();
367
- }
368
- });
369
- /**
370
- * solo history - View auto run history
371
- */
372
- solo
373
- .command('history')
374
- .description('View auto run history')
375
- .option('-n, --limit <n>', 'Number of entries to show', '10')
376
- .option('--json', 'Output as JSON')
377
- .action(async (options) => {
378
- const { readRunHistory, formatHistoryEntry } = await import('../lib/run-history.js');
379
- const git = createGitService();
380
- const repoRoot = await git.findRepoRoot(process.cwd());
381
- const entries = readRunHistory(repoRoot || undefined, parseInt(options.limit || '10', 10));
382
- if (entries.length === 0) {
383
- console.log(chalk.gray('No history yet. Run `blockspool solo auto` to get started.'));
384
- return;
385
- }
386
- if (options.json) {
387
- console.log(JSON.stringify(entries, null, 2));
388
- return;
389
- }
390
- console.log(chalk.bold(`Run History (${entries.length} entries):\n`));
391
- for (const entry of entries) {
392
- console.log(formatHistoryEntry(entry));
393
- console.log();
394
- }
395
- });
396
- /**
397
- * solo formulas - List available formulas
398
- */
399
- solo
400
- .command('formulas')
401
- .description('List available auto formulas')
402
- .action(async () => {
403
- const { listFormulas } = await import('../lib/formulas.js');
404
- const git = createGitService();
405
- const repoRoot = await git.findRepoRoot(process.cwd());
406
- const formulas = listFormulas(repoRoot || undefined);
407
- if (formulas.length === 0) {
408
- console.log(chalk.gray('No formulas available'));
409
- return;
410
- }
411
- console.log(chalk.bold('Available formulas:\n'));
412
- for (const formula of formulas) {
413
- const tags = formula.tags?.length ? chalk.gray(` [${formula.tags.join(', ')}]`) : '';
414
- console.log(` ${chalk.cyan(formula.name)}${tags}`);
415
- console.log(` ${formula.description}`);
416
- if (formula.categories?.length) {
417
- console.log(chalk.gray(` Categories: ${formula.categories.join(', ')}`));
418
- }
419
- if (formula.minConfidence) {
420
- console.log(chalk.gray(` Min confidence: ${formula.minConfidence}%`));
421
- }
422
- console.log();
423
- }
424
- console.log(chalk.gray('Usage: blockspool solo auto --formula <name>'));
425
- console.log(chalk.gray('Custom: Create .blockspool/formulas/<name>.yaml'));
426
- });
427
- /**
428
- * solo export - Export state for debugging
429
- */
430
- solo
431
- .command('export')
432
- .description('Export local state for debugging or migration')
433
- .option('-o, --output <file>', 'Output file', 'blockspool-export.json')
434
- .action(async (options) => {
435
- const git = createGitService();
436
- const repoRoot = await git.findRepoRoot(process.cwd());
437
- if (!repoRoot) {
438
- console.error(chalk.red('✗ Not a git repository'));
439
- process.exit(1);
440
- }
441
- const adapter = await getAdapter(repoRoot);
442
- try {
443
- const projectList = await projects.list(adapter);
444
- const data = {
445
- exportedAt: new Date().toISOString(),
446
- version: 1,
447
- projects: [],
448
- };
449
- for (const project of projectList) {
450
- const projectTickets = await tickets.listByProject(adapter, project.id);
451
- const projectRuns = await runs.listByProject(adapter, project.id);
452
- data.projects.push({
453
- ...project,
454
- tickets: projectTickets,
455
- runs: projectRuns,
456
- });
457
- }
458
- fs.writeFileSync(options.output, JSON.stringify(data, null, 2));
459
- console.log(chalk.green(`✓ Exported to ${options.output}`));
460
- }
461
- finally {
462
- await adapter.close();
463
- }
464
- });
465
- /**
466
- * solo artifacts - List and view run artifacts
467
- */
468
- solo
469
- .command('artifacts')
470
- .description('List and view run artifacts')
471
- .option('--run <runId>', 'Show artifacts for a specific run')
472
- .option('--type <type>', 'Filter by artifact type (proposals, executions, diffs, runs, violations)')
473
- .option('--show <path>', 'Display contents of a specific artifact file')
474
- .option('--json', 'Output in JSON format')
475
- .action(async (options) => {
476
- const repoRoot = process.cwd();
477
- const baseDir = getBlockspoolDir(repoRoot);
478
- const artifactsDir = path.join(baseDir, 'artifacts');
479
- if (options.show) {
480
- const filePath = options.show.startsWith('/') ? options.show : path.join(process.cwd(), options.show);
481
- if (!fs.existsSync(filePath)) {
482
- console.error(chalk.red(`Artifact not found: ${filePath}`));
483
- process.exit(1);
484
- }
485
- const content = fs.readFileSync(filePath, 'utf-8');
486
- if (options.json) {
487
- console.log(content);
488
- }
489
- else {
490
- console.log(chalk.cyan(`\n─── ${path.basename(filePath)} ───\n`));
491
- try {
492
- const data = JSON.parse(content);
493
- console.log(JSON.stringify(data, null, 2));
494
- }
495
- catch {
496
- console.log(content);
497
- }
498
- }
499
- return;
500
- }
501
- if (options.run) {
502
- const artifacts = getArtifactsForRun(baseDir, options.run);
503
- const found = Object.entries(artifacts).filter(([, v]) => v !== null);
504
- if (options.json) {
505
- console.log(JSON.stringify({
506
- runId: options.run,
507
- artifacts: Object.fromEntries(found.map(([type, artifact]) => [type, artifact?.path])),
508
- }, null, 2));
509
- return;
510
- }
511
- if (found.length === 0) {
512
- console.log(chalk.yellow(`No artifacts found for run: ${options.run}`));
513
- return;
514
- }
515
- console.log(chalk.cyan(`\nArtifacts for run ${options.run}:\n`));
516
- for (const [type, artifact] of found) {
517
- if (artifact) {
518
- console.log(` ${chalk.bold(type)}: ${artifact.path}`);
519
- }
520
- }
521
- console.log();
522
- return;
523
- }
524
- if (!fs.existsSync(artifactsDir)) {
525
- console.log(chalk.yellow('No artifacts found. Run a ticket to generate artifacts.'));
526
- return;
527
- }
528
- const allArtifacts = getAllArtifacts(baseDir);
529
- const types = options.type
530
- ? [options.type]
531
- : ['runs', 'executions', 'diffs', 'violations', 'proposals', 'spindle'];
532
- if (options.json) {
533
- const output = {};
534
- for (const type of types) {
535
- output[type] = allArtifacts[type] ?? [];
536
- }
537
- console.log(JSON.stringify(output, null, 2));
538
- return;
539
- }
540
- let totalCount = 0;
541
- for (const type of types) {
542
- const artifacts = allArtifacts[type] ?? [];
543
- if (artifacts.length === 0)
544
- continue;
545
- console.log(chalk.cyan(`\n${type.toUpperCase()} (${artifacts.length}):`));
546
- for (const artifact of artifacts.slice(0, 10)) {
547
- const date = new Date(artifact.timestamp).toISOString().slice(0, 19).replace('T', ' ');
548
- console.log(` ${chalk.gray(date)} ${artifact.id}`);
549
- console.log(` ${chalk.dim(artifact.path)}`);
550
- totalCount++;
551
- }
552
- if (artifacts.length > 10) {
553
- console.log(chalk.dim(` ... and ${artifacts.length - 10} more`));
554
- }
555
- }
556
- if (totalCount === 0) {
557
- console.log(chalk.yellow('No artifacts found. Run a ticket to generate artifacts.'));
558
- }
559
- else {
560
- console.log(chalk.dim(`\nUse --show <path> to view an artifact, or --run <id> to see all artifacts for a run.\n`));
561
- }
562
- });
563
- /**
564
- * solo approve - Convert proposals to tickets
565
- */
566
- solo
567
- .command('approve <selection>')
568
- .description('Approve proposals and create tickets')
569
- .addHelpText('after', `
570
- Selection formats:
571
- 1 Single proposal (1-indexed)
572
- 1,3,5 Multiple specific proposals
573
- 1-3 Range of proposals
574
- 1-3,5,7 Mixed selection
575
- all All proposals
576
-
577
- Examples:
578
- blockspool solo approve 1 # Approve first proposal
579
- blockspool solo approve 1-3 # Approve proposals 1, 2, 3
580
- blockspool solo approve all # Approve all proposals
581
- `)
582
- .option('-v, --verbose', 'Show detailed output')
583
- .option('--json', 'Output as JSON')
584
- .action(async (selection, options) => {
585
- const isJsonMode = options.json;
586
- const git = createGitService();
587
- const repoRoot = await git.findRepoRoot(process.cwd());
588
- if (!repoRoot) {
589
- if (isJsonMode) {
590
- console.log(JSON.stringify({ success: false, error: 'Not a git repository' }));
591
- }
592
- else {
593
- console.error(chalk.red('✗ Not a git repository'));
594
- }
595
- process.exit(1);
596
- }
597
- const baseDir = getBlockspoolDir(repoRoot);
598
- const artifact = getLatestArtifact(baseDir, 'proposals');
599
- if (!artifact) {
600
- if (isJsonMode) {
601
- console.log(JSON.stringify({
602
- success: false,
603
- error: 'No proposals found. Run: blockspool solo scout .',
604
- }));
605
- }
606
- else {
607
- console.error(chalk.red('✗ No proposals found'));
608
- console.log(chalk.gray(' Run: blockspool solo scout .'));
609
- }
610
- process.exit(1);
611
- }
612
- const { proposals } = artifact.data;
613
- if (proposals.length === 0) {
614
- if (isJsonMode) {
615
- console.log(JSON.stringify({ success: false, error: 'No proposals in artifact' }));
616
- }
617
- else {
618
- console.error(chalk.red('✗ No proposals in artifact'));
619
- }
620
- process.exit(1);
621
- }
622
- let selectedIndices;
623
- try {
624
- selectedIndices = parseSelection(selection, proposals.length);
625
- }
626
- catch (err) {
627
- if (isJsonMode) {
628
- console.log(JSON.stringify({
629
- success: false,
630
- error: err instanceof Error ? err.message : String(err),
631
- }));
632
- }
633
- else {
634
- console.error(chalk.red(`✗ Invalid selection: ${err instanceof Error ? err.message : err}`));
635
- console.log(chalk.gray(` Valid range: 1-${proposals.length}`));
636
- }
637
- process.exit(1);
638
- }
639
- if (selectedIndices.length === 0) {
640
- if (isJsonMode) {
641
- console.log(JSON.stringify({ success: false, error: 'No proposals selected' }));
642
- }
643
- else {
644
- console.error(chalk.red('✗ No proposals selected'));
645
- console.log(chalk.gray(` Valid range: 1-${proposals.length}`));
646
- }
647
- process.exit(1);
648
- }
649
- const selectedProposals = selectedIndices.map(i => proposals[i]);
650
- if (!isJsonMode) {
651
- console.log(chalk.blue('📋 BlockSpool Solo Approve'));
652
- console.log();
653
- console.log(`Selected ${selectedProposals.length} proposal(s):`);
654
- for (const idx of selectedIndices) {
655
- const p = proposals[idx];
656
- console.log(` ${chalk.bold(idx + 1)}. ${p.title}`);
657
- }
658
- console.log();
659
- }
660
- const adapter = await getAdapter(repoRoot);
661
- try {
662
- const deps = createScoutDeps(adapter, options);
663
- const createdTickets = await approveProposals(deps, artifact.data.projectId, selectedProposals);
664
- if (isJsonMode) {
665
- console.log(JSON.stringify({
666
- success: true,
667
- tickets: createdTickets.map(t => ({
668
- id: t.id,
669
- title: t.title,
670
- status: t.status,
671
- })),
672
- }));
673
- }
674
- else {
675
- console.log(chalk.green(`✓ Created ${createdTickets.length} ticket(s)`));
676
- for (const t of createdTickets) {
677
- console.log(` ${chalk.gray(t.id)} ${t.title}`);
678
- }
679
- console.log();
680
- console.log(chalk.blue('Next steps:'));
681
- console.log(` blockspool solo run ${createdTickets[0]?.id} # Run a ticket`);
682
- console.log(' blockspool solo status # View all tickets');
683
- }
684
- }
685
- finally {
686
- await adapter.close();
687
- }
688
- });
689
- }
690
- //# sourceMappingURL=solo-inspect.js.map