@blockspool/cli 0.4.1 → 0.4.3

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/dist/bin/blockspool.d.ts +16 -0
  2. package/dist/bin/blockspool.d.ts.map +1 -0
  3. package/dist/bin/blockspool.js +45 -0
  4. package/dist/bin/blockspool.js.map +1 -0
  5. package/dist/commands/solo-auto.d.ts +6 -0
  6. package/dist/commands/solo-auto.d.ts.map +1 -0
  7. package/dist/commands/solo-auto.js +418 -0
  8. package/dist/commands/solo-auto.js.map +1 -0
  9. package/dist/commands/solo-exec.d.ts +6 -0
  10. package/dist/commands/solo-exec.d.ts.map +1 -0
  11. package/dist/commands/solo-exec.js +656 -0
  12. package/dist/commands/solo-exec.js.map +1 -0
  13. package/dist/commands/solo-inspect.d.ts +6 -0
  14. package/dist/commands/solo-inspect.d.ts.map +1 -0
  15. package/dist/commands/solo-inspect.js +690 -0
  16. package/dist/commands/solo-inspect.js.map +1 -0
  17. package/dist/commands/solo-lifecycle.d.ts +6 -0
  18. package/dist/commands/solo-lifecycle.d.ts.map +1 -0
  19. package/dist/commands/solo-lifecycle.js +188 -0
  20. package/dist/commands/solo-lifecycle.js.map +1 -0
  21. package/dist/commands/solo-nudge.d.ts +6 -0
  22. package/dist/commands/solo-nudge.d.ts.map +1 -0
  23. package/dist/commands/solo-nudge.js +49 -0
  24. package/dist/commands/solo-nudge.js.map +1 -0
  25. package/dist/commands/solo-qa.d.ts +6 -0
  26. package/dist/commands/solo-qa.d.ts.map +1 -0
  27. package/dist/commands/solo-qa.js +254 -0
  28. package/dist/commands/solo-qa.js.map +1 -0
  29. package/dist/commands/solo.d.ts +11 -0
  30. package/dist/commands/solo.d.ts.map +1 -0
  31. package/dist/commands/solo.js +43 -0
  32. package/dist/commands/solo.js.map +1 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +18 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/lib/artifacts.d.ts +136 -0
  38. package/dist/lib/artifacts.d.ts.map +1 -0
  39. package/dist/lib/artifacts.js +146 -0
  40. package/dist/lib/artifacts.js.map +1 -0
  41. package/dist/lib/doctor.d.ts +45 -0
  42. package/dist/lib/doctor.d.ts.map +1 -0
  43. package/dist/lib/doctor.js +383 -0
  44. package/dist/lib/doctor.js.map +1 -0
  45. package/dist/lib/exec.d.ts +24 -0
  46. package/dist/lib/exec.d.ts.map +1 -0
  47. package/dist/lib/exec.js +295 -0
  48. package/dist/lib/exec.js.map +1 -0
  49. package/dist/lib/formulas.d.ts +78 -0
  50. package/dist/lib/formulas.d.ts.map +1 -0
  51. package/dist/lib/formulas.js +295 -0
  52. package/dist/lib/formulas.js.map +1 -0
  53. package/dist/lib/git.d.ts +9 -0
  54. package/dist/lib/git.d.ts.map +1 -0
  55. package/dist/lib/git.js +60 -0
  56. package/dist/lib/git.js.map +1 -0
  57. package/dist/lib/guidelines.d.ts +43 -0
  58. package/dist/lib/guidelines.d.ts.map +1 -0
  59. package/dist/lib/guidelines.js +195 -0
  60. package/dist/lib/guidelines.js.map +1 -0
  61. package/dist/lib/logger.d.ts +17 -0
  62. package/dist/lib/logger.d.ts.map +1 -0
  63. package/dist/lib/logger.js +42 -0
  64. package/dist/lib/logger.js.map +1 -0
  65. package/dist/lib/retention.d.ts +62 -0
  66. package/dist/lib/retention.d.ts.map +1 -0
  67. package/dist/lib/retention.js +285 -0
  68. package/dist/lib/retention.js.map +1 -0
  69. package/dist/lib/run-history.d.ts +52 -0
  70. package/dist/lib/run-history.d.ts.map +1 -0
  71. package/dist/lib/run-history.js +116 -0
  72. package/dist/lib/run-history.js.map +1 -0
  73. package/dist/lib/run-state.d.ts +58 -0
  74. package/dist/lib/run-state.d.ts.map +1 -0
  75. package/dist/lib/run-state.js +119 -0
  76. package/dist/lib/run-state.js.map +1 -0
  77. package/dist/lib/scope.d.ts +95 -0
  78. package/dist/lib/scope.d.ts.map +1 -0
  79. package/dist/lib/scope.js +291 -0
  80. package/dist/lib/scope.js.map +1 -0
  81. package/dist/lib/selection.d.ts +35 -0
  82. package/dist/lib/selection.d.ts.map +1 -0
  83. package/dist/lib/selection.js +110 -0
  84. package/dist/lib/selection.js.map +1 -0
  85. package/dist/lib/solo-auto.d.ts +87 -0
  86. package/dist/lib/solo-auto.d.ts.map +1 -0
  87. package/dist/lib/solo-auto.js +1230 -0
  88. package/dist/lib/solo-auto.js.map +1 -0
  89. package/dist/lib/solo-ci.d.ts +84 -0
  90. package/dist/lib/solo-ci.d.ts.map +1 -0
  91. package/dist/lib/solo-ci.js +300 -0
  92. package/dist/lib/solo-ci.js.map +1 -0
  93. package/dist/lib/solo-config.d.ts +155 -0
  94. package/dist/lib/solo-config.d.ts.map +1 -0
  95. package/dist/lib/solo-config.js +236 -0
  96. package/dist/lib/solo-config.js.map +1 -0
  97. package/dist/lib/solo-git.d.ts +44 -0
  98. package/dist/lib/solo-git.d.ts.map +1 -0
  99. package/dist/lib/solo-git.js +174 -0
  100. package/dist/lib/solo-git.js.map +1 -0
  101. package/dist/lib/solo-hints.d.ts +32 -0
  102. package/dist/lib/solo-hints.d.ts.map +1 -0
  103. package/dist/lib/solo-hints.js +98 -0
  104. package/dist/lib/solo-hints.js.map +1 -0
  105. package/dist/lib/solo-remote.d.ts +14 -0
  106. package/dist/lib/solo-remote.d.ts.map +1 -0
  107. package/dist/lib/solo-remote.js +48 -0
  108. package/dist/lib/solo-remote.js.map +1 -0
  109. package/dist/lib/solo-stdin.d.ts +13 -0
  110. package/dist/lib/solo-stdin.d.ts.map +1 -0
  111. package/dist/lib/solo-stdin.js +33 -0
  112. package/dist/lib/solo-stdin.js.map +1 -0
  113. package/dist/lib/solo-ticket.d.ts +213 -0
  114. package/dist/lib/solo-ticket.d.ts.map +1 -0
  115. package/dist/lib/solo-ticket.js +850 -0
  116. package/dist/lib/solo-ticket.js.map +1 -0
  117. package/dist/lib/solo-utils.d.ts +133 -0
  118. package/dist/lib/solo-utils.d.ts.map +1 -0
  119. package/dist/lib/solo-utils.js +300 -0
  120. package/dist/lib/solo-utils.js.map +1 -0
  121. package/dist/lib/spindle.d.ts +144 -0
  122. package/dist/lib/spindle.d.ts.map +1 -0
  123. package/dist/lib/spindle.js +388 -0
  124. package/dist/lib/spindle.js.map +1 -0
  125. package/dist/tui/app.d.ts +17 -0
  126. package/dist/tui/app.d.ts.map +1 -0
  127. package/dist/tui/app.js +139 -0
  128. package/dist/tui/app.js.map +1 -0
  129. package/dist/tui/index.d.ts +8 -0
  130. package/dist/tui/index.d.ts.map +1 -0
  131. package/dist/tui/index.js +7 -0
  132. package/dist/tui/index.js.map +1 -0
  133. package/dist/tui/poller.d.ts +42 -0
  134. package/dist/tui/poller.d.ts.map +1 -0
  135. package/dist/tui/poller.js +62 -0
  136. package/dist/tui/poller.js.map +1 -0
  137. package/dist/tui/screens/overview.d.ts +9 -0
  138. package/dist/tui/screens/overview.d.ts.map +1 -0
  139. package/dist/tui/screens/overview.js +189 -0
  140. package/dist/tui/screens/overview.js.map +1 -0
  141. package/dist/tui/state.d.ts +93 -0
  142. package/dist/tui/state.d.ts.map +1 -0
  143. package/dist/tui/state.js +169 -0
  144. package/dist/tui/state.js.map +1 -0
  145. package/dist/tui/types.d.ts +18 -0
  146. package/dist/tui/types.d.ts.map +1 -0
  147. package/dist/tui/types.js +5 -0
  148. package/dist/tui/types.js.map +1 -0
  149. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spindle.d.ts","sourceRoot":"","sources":["../../src/lib/spindle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Spindle Loop Detection
3
+ *
4
+ * Monitors agent execution for unproductive patterns:
5
+ * - Oscillation: Similar diffs being applied and reverted
6
+ * - Spinning: High tool call rate without progress
7
+ * - Stalling: Verbose output without meaningful changes
8
+ * - Repetition: Same errors or phrases repeated
9
+ *
10
+ * Named "Spindle" after the spinning wheel component — when the spindle
11
+ * jams, it spins without producing thread.
12
+ */
13
+ /**
14
+ * Default Spindle configuration
15
+ */
16
+ export const DEFAULT_SPINDLE_CONFIG = {
17
+ enabled: true,
18
+ similarityThreshold: 0.8,
19
+ maxSimilarOutputs: 3,
20
+ maxStallIterations: 5,
21
+ verbosityThreshold: 10,
22
+ tokenBudgetWarning: 100000,
23
+ tokenBudgetAbort: 140000,
24
+ };
25
+ /**
26
+ * Create initial Spindle state
27
+ */
28
+ export function createSpindleState() {
29
+ return {
30
+ outputs: [],
31
+ diffs: [],
32
+ iterationsSinceChange: 0,
33
+ estimatedTokens: 0,
34
+ warnings: [],
35
+ totalOutputChars: 0,
36
+ totalChangeChars: 0,
37
+ };
38
+ }
39
+ /**
40
+ * Estimate token count from text
41
+ * Rough approximation: ~4 characters per token for English text
42
+ */
43
+ export function estimateTokens(text) {
44
+ if (!text)
45
+ return 0;
46
+ return Math.ceil(text.length / 4);
47
+ }
48
+ /**
49
+ * Compute similarity between two strings using Jaccard index on word tokens
50
+ *
51
+ * @returns Similarity score from 0 (completely different) to 1 (identical)
52
+ */
53
+ export function computeSimilarity(a, b) {
54
+ if (!a && !b)
55
+ return 1;
56
+ if (!a || !b)
57
+ return 0;
58
+ // Tokenize on whitespace and punctuation, lowercase
59
+ const tokenize = (s) => {
60
+ const tokens = s
61
+ .toLowerCase()
62
+ .split(/[\s.,;:!?\-()[\]{}"']+/)
63
+ .filter(t => t.length > 0);
64
+ return new Set(tokens);
65
+ };
66
+ const tokensA = tokenize(a);
67
+ const tokensB = tokenize(b);
68
+ if (tokensA.size === 0 && tokensB.size === 0)
69
+ return 1;
70
+ if (tokensA.size === 0 || tokensB.size === 0)
71
+ return 0;
72
+ // Jaccard index: |intersection| / |union|
73
+ let intersectionSize = 0;
74
+ for (const token of tokensA) {
75
+ if (tokensB.has(token)) {
76
+ intersectionSize++;
77
+ }
78
+ }
79
+ const unionSize = tokensA.size + tokensB.size - intersectionSize;
80
+ return intersectionSize / unionSize;
81
+ }
82
+ /**
83
+ * Extract added and removed lines from a unified diff
84
+ */
85
+ function extractDiffLines(diff) {
86
+ const lines = diff.split('\n');
87
+ const added = [];
88
+ const removed = [];
89
+ for (const line of lines) {
90
+ // Skip diff headers
91
+ if (line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@')) {
92
+ continue;
93
+ }
94
+ if (line.startsWith('+')) {
95
+ added.push(line.slice(1).trim());
96
+ }
97
+ else if (line.startsWith('-')) {
98
+ removed.push(line.slice(1).trim());
99
+ }
100
+ }
101
+ return { added, removed };
102
+ }
103
+ /**
104
+ * Detect oscillation pattern in diffs
105
+ *
106
+ * Looks for add→remove→add or remove→add→remove patterns where similar
107
+ * content is being repeatedly changed back and forth.
108
+ *
109
+ * @returns Object with detected flag and pattern description
110
+ */
111
+ export function detectOscillation(diffs, similarityThreshold = 0.8) {
112
+ if (diffs.length < 2) {
113
+ return { detected: false, confidence: 0 };
114
+ }
115
+ // Analyze last 3 diffs (or 2 if only 2 available)
116
+ const recentDiffs = diffs.slice(-3);
117
+ const patterns = recentDiffs.map(d => extractDiffLines(d));
118
+ // With 2 diffs: check if what was added is now removed (or vice versa)
119
+ if (patterns.length >= 2) {
120
+ const [prev, curr] = patterns.slice(-2);
121
+ // Check: lines added in prev are now removed in curr
122
+ for (const addedLine of prev.added) {
123
+ if (addedLine.length < 3)
124
+ continue; // Skip trivial lines
125
+ for (const removedLine of curr.removed) {
126
+ const sim = computeSimilarity(addedLine, removedLine);
127
+ if (sim >= similarityThreshold) {
128
+ return {
129
+ detected: true,
130
+ pattern: `Added then removed: "${addedLine.slice(0, 50)}..."`,
131
+ confidence: sim,
132
+ };
133
+ }
134
+ }
135
+ }
136
+ // Check: lines removed in prev are now added in curr
137
+ for (const removedLine of prev.removed) {
138
+ if (removedLine.length < 3)
139
+ continue;
140
+ for (const addedLine of curr.added) {
141
+ const sim = computeSimilarity(removedLine, addedLine);
142
+ if (sim >= similarityThreshold) {
143
+ return {
144
+ detected: true,
145
+ pattern: `Removed then re-added: "${removedLine.slice(0, 50)}..."`,
146
+ confidence: sim,
147
+ };
148
+ }
149
+ }
150
+ }
151
+ }
152
+ // With 3 diffs: check for A→B→A pattern
153
+ if (patterns.length === 3) {
154
+ const [first, , third] = patterns;
155
+ // Check if first additions match third additions (came back to same state)
156
+ for (const line1 of first.added) {
157
+ if (line1.length < 3)
158
+ continue;
159
+ for (const line3 of third.added) {
160
+ const sim = computeSimilarity(line1, line3);
161
+ if (sim >= similarityThreshold) {
162
+ return {
163
+ detected: true,
164
+ pattern: `Oscillating: same content added in iterations 1 and 3`,
165
+ confidence: sim,
166
+ };
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return { detected: false, confidence: 0 };
172
+ }
173
+ /**
174
+ * Detect repetition in agent outputs
175
+ *
176
+ * Looks for consecutive similar outputs that indicate the agent is stuck
177
+ * in a loop saying the same things.
178
+ */
179
+ export function detectRepetition(outputs, latestOutput, config) {
180
+ const patterns = [];
181
+ let maxSimilarity = 0;
182
+ // Compare latest output with recent outputs
183
+ for (const prevOutput of outputs.slice(-config.maxSimilarOutputs)) {
184
+ const sim = computeSimilarity(latestOutput, prevOutput);
185
+ if (sim >= config.similarityThreshold) {
186
+ maxSimilarity = Math.max(maxSimilarity, sim);
187
+ // Extract repeated phrases
188
+ const phrases = findRepeatedPhrases(latestOutput, prevOutput);
189
+ patterns.push(...phrases);
190
+ }
191
+ }
192
+ // Check for common "stuck" phrases
193
+ const stuckPhrases = [
194
+ 'let me try',
195
+ 'i apologize',
196
+ "i'll try again",
197
+ 'let me attempt',
198
+ 'trying again',
199
+ 'one more time',
200
+ 'another approach',
201
+ ];
202
+ const lowerOutput = latestOutput.toLowerCase();
203
+ for (const phrase of stuckPhrases) {
204
+ if (lowerOutput.includes(phrase)) {
205
+ const occurrences = outputs.filter(o => o.toLowerCase().includes(phrase)).length;
206
+ if (occurrences >= 2) {
207
+ patterns.push(`Repeated phrase: "${phrase}" (${occurrences + 1} times)`);
208
+ // Set high similarity since stuck phrases are a strong signal
209
+ maxSimilarity = Math.max(maxSimilarity, 0.85);
210
+ }
211
+ }
212
+ }
213
+ const detected = patterns.length > 0 && maxSimilarity >= config.similarityThreshold;
214
+ return {
215
+ detected,
216
+ patterns: [...new Set(patterns)].slice(0, 5), // Dedupe and limit
217
+ confidence: maxSimilarity,
218
+ };
219
+ }
220
+ /**
221
+ * Find repeated phrases between two texts
222
+ */
223
+ function findRepeatedPhrases(a, b) {
224
+ const phrases = [];
225
+ // Split into sentences/fragments
226
+ const fragmentsA = a.split(/[.!?\n]+/).filter(f => f.trim().length > 20);
227
+ const fragmentsB = b.split(/[.!?\n]+/).filter(f => f.trim().length > 20);
228
+ for (const fragA of fragmentsA) {
229
+ for (const fragB of fragmentsB) {
230
+ const sim = computeSimilarity(fragA, fragB);
231
+ if (sim >= 0.9) {
232
+ phrases.push(fragA.trim().slice(0, 60) + '...');
233
+ }
234
+ }
235
+ }
236
+ return phrases;
237
+ }
238
+ /**
239
+ * Check if agent is in a Spindle loop
240
+ *
241
+ * Updates state in-place and returns detection result.
242
+ *
243
+ * @param state - Current Spindle state (will be mutated)
244
+ * @param latestOutput - Agent's latest output text
245
+ * @param latestDiff - Latest git diff (null if no changes)
246
+ * @param config - Spindle configuration
247
+ * @returns Detection result
248
+ */
249
+ export function checkSpindleLoop(state, latestOutput, latestDiff, config) {
250
+ // If disabled, always pass
251
+ if (!config.enabled) {
252
+ return { shouldAbort: false, confidence: 0, diagnostics: {} };
253
+ }
254
+ // Update state with latest data
255
+ const outputTokens = estimateTokens(latestOutput);
256
+ const diffTokens = estimateTokens(latestDiff ?? '');
257
+ state.estimatedTokens += outputTokens + diffTokens;
258
+ state.totalOutputChars += latestOutput.length;
259
+ state.totalChangeChars += (latestDiff ?? '').length;
260
+ // Store for pattern detection (keep last N)
261
+ state.outputs.push(latestOutput);
262
+ if (state.outputs.length > config.maxSimilarOutputs + 1) {
263
+ state.outputs.shift();
264
+ }
265
+ if (latestDiff) {
266
+ state.diffs.push(latestDiff);
267
+ if (state.diffs.length > 5) {
268
+ state.diffs.shift();
269
+ }
270
+ }
271
+ // Track iterations without changes
272
+ if (!latestDiff || latestDiff.trim() === '') {
273
+ state.iterationsSinceChange++;
274
+ }
275
+ else {
276
+ state.iterationsSinceChange = 0;
277
+ }
278
+ // Check 1: Token budget
279
+ if (state.estimatedTokens >= config.tokenBudgetAbort) {
280
+ return {
281
+ shouldAbort: true,
282
+ reason: 'token_budget',
283
+ confidence: 1.0,
284
+ diagnostics: {
285
+ estimatedTokens: state.estimatedTokens,
286
+ },
287
+ };
288
+ }
289
+ // Token budget warning (don't abort, just warn)
290
+ if (state.estimatedTokens >= config.tokenBudgetWarning) {
291
+ const warning = `Approaching token budget: ~${state.estimatedTokens} tokens`;
292
+ if (!state.warnings.includes(warning)) {
293
+ state.warnings.push(warning);
294
+ }
295
+ }
296
+ // Check 2: Stalling (no changes for too many iterations)
297
+ if (state.iterationsSinceChange >= config.maxStallIterations) {
298
+ return {
299
+ shouldAbort: true,
300
+ reason: 'stalling',
301
+ confidence: 0.9,
302
+ diagnostics: {
303
+ iterationsWithoutChange: state.iterationsSinceChange,
304
+ },
305
+ };
306
+ }
307
+ // Check 3: Oscillation in diffs
308
+ if (state.diffs.length >= 2) {
309
+ const oscillation = detectOscillation(state.diffs, config.similarityThreshold);
310
+ if (oscillation.detected) {
311
+ return {
312
+ shouldAbort: true,
313
+ reason: 'oscillation',
314
+ confidence: oscillation.confidence,
315
+ diagnostics: {
316
+ oscillationPattern: oscillation.pattern,
317
+ },
318
+ };
319
+ }
320
+ }
321
+ // Check 4: Repetition in outputs
322
+ if (state.outputs.length >= 2) {
323
+ const repetition = detectRepetition(state.outputs.slice(0, -1), latestOutput, config);
324
+ if (repetition.detected) {
325
+ return {
326
+ shouldAbort: true,
327
+ reason: 'repetition',
328
+ confidence: repetition.confidence,
329
+ diagnostics: {
330
+ repeatedPatterns: repetition.patterns,
331
+ similarityScore: repetition.confidence,
332
+ },
333
+ };
334
+ }
335
+ }
336
+ // Check 5: Verbosity ratio (lots of output, few changes)
337
+ if (state.totalOutputChars > 5000 && state.totalChangeChars > 0) {
338
+ const verbosityRatio = state.totalOutputChars / state.totalChangeChars;
339
+ if (verbosityRatio >= config.verbosityThreshold) {
340
+ // Only warn, don't abort on verbosity alone
341
+ const warning = `High verbosity ratio: ${verbosityRatio.toFixed(1)}x output vs changes`;
342
+ if (!state.warnings.includes(warning)) {
343
+ state.warnings.push(warning);
344
+ }
345
+ }
346
+ }
347
+ // No issues detected
348
+ return {
349
+ shouldAbort: false,
350
+ confidence: 0,
351
+ diagnostics: {
352
+ estimatedTokens: state.estimatedTokens,
353
+ iterationsWithoutChange: state.iterationsSinceChange,
354
+ },
355
+ };
356
+ }
357
+ /**
358
+ * Format Spindle result for display
359
+ */
360
+ export function formatSpindleResult(result) {
361
+ if (!result.shouldAbort) {
362
+ return 'No spindle loop detected';
363
+ }
364
+ const parts = [`Spindle loop detected: ${result.reason}`];
365
+ parts.push(`Confidence: ${(result.confidence * 100).toFixed(0)}%`);
366
+ if (result.diagnostics.estimatedTokens) {
367
+ parts.push(`Tokens: ~${result.diagnostics.estimatedTokens}`);
368
+ }
369
+ if (result.diagnostics.iterationsWithoutChange) {
370
+ parts.push(`Iterations without change: ${result.diagnostics.iterationsWithoutChange}`);
371
+ }
372
+ if (result.diagnostics.oscillationPattern) {
373
+ parts.push(`Pattern: ${result.diagnostics.oscillationPattern}`);
374
+ }
375
+ if (result.diagnostics.repeatedPatterns?.length) {
376
+ parts.push(`Repeated: ${result.diagnostics.repeatedPatterns.join(', ')}`);
377
+ }
378
+ return parts.join('\n');
379
+ }
380
+ /** @deprecated Use DEFAULT_SPINDLE_CONFIG */
381
+ export const DEFAULT_RALPH_CONFIG = DEFAULT_SPINDLE_CONFIG;
382
+ /** @deprecated Use createSpindleState */
383
+ export const createRalphState = createSpindleState;
384
+ /** @deprecated Use checkSpindleLoop */
385
+ export const checkRalphLoop = checkSpindleLoop;
386
+ /** @deprecated Use formatSpindleResult */
387
+ export const formatRalphResult = formatSpindleResult;
388
+ //# sourceMappingURL=spindle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spindle.js","sourceRoot":"","sources":["../../src/lib/spindle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * TUI main application
3
+ *
4
+ * Keyboard-first terminal UI for solo mode.
5
+ * Uses blessed for rendering, repos for state, services for actions.
6
+ */
7
+ import type { DatabaseAdapter } from '@blockspool/core/db';
8
+ import type { TuiActions } from './types.js';
9
+ export type TuiAppDeps = {
10
+ db: DatabaseAdapter;
11
+ repoRoot: string;
12
+ actions?: TuiActions;
13
+ };
14
+ export declare function startTuiApp(deps: TuiAppDeps): Promise<{
15
+ stop: () => Promise<void>;
16
+ }>;
17
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAI3D,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,eAAe,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,UAAU,CAAC;CACtB,CAAC;AA4CF,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAsG1F"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * TUI main application
3
+ *
4
+ * Keyboard-first terminal UI for solo mode.
5
+ * Uses blessed for rendering, repos for state, services for actions.
6
+ */
7
+ import blessed from 'neo-blessed';
8
+ import { AdaptivePoller } from './poller.js';
9
+ import { buildSnapshot } from './state.js';
10
+ import { createOverviewScreen } from './screens/overview.js';
11
+ /**
12
+ * Manages screen lifecycle - mount/destroy/update
13
+ */
14
+ class ScreenManager {
15
+ current = null;
16
+ screen;
17
+ constructor(screen) {
18
+ this.screen = screen;
19
+ }
20
+ set(next) {
21
+ if (this.current) {
22
+ this.current.destroy();
23
+ }
24
+ this.current = next;
25
+ next.mount(this.screen);
26
+ next.focus?.();
27
+ this.screen.render();
28
+ }
29
+ update(snapshot) {
30
+ if (!this.current)
31
+ return;
32
+ this.current.update(snapshot);
33
+ this.screen.render();
34
+ }
35
+ getCurrentName() {
36
+ return this.current?.name ?? null;
37
+ }
38
+ destroy() {
39
+ this.current?.destroy();
40
+ this.current = null;
41
+ }
42
+ }
43
+ function safeString(err) {
44
+ if (err instanceof Error)
45
+ return err.stack || err.message;
46
+ return String(err);
47
+ }
48
+ export async function startTuiApp(deps) {
49
+ const { db, repoRoot, actions } = deps;
50
+ const screen = blessed.screen({
51
+ smartCSR: true,
52
+ fullUnicode: true,
53
+ title: 'BlockSpool',
54
+ });
55
+ // Toast bar at bottom for status messages
56
+ const toast = blessed.box({
57
+ parent: screen,
58
+ bottom: 0,
59
+ height: 1,
60
+ width: '100%',
61
+ tags: true,
62
+ style: { fg: 'white' },
63
+ content: ' {gray-fg}Starting...{/gray-fg}',
64
+ });
65
+ const manager = new ScreenManager(screen);
66
+ const overview = createOverviewScreen({
67
+ onHint: (msg) => {
68
+ toast.setContent(` {gray-fg}${msg}{/gray-fg}`);
69
+ },
70
+ });
71
+ manager.set(overview);
72
+ let actionInFlight = null;
73
+ async function runAction(name, fn) {
74
+ if (!fn) {
75
+ toast.setContent(` {yellow-fg}${name} not configured{/yellow-fg}`);
76
+ screen.render();
77
+ return;
78
+ }
79
+ if (actionInFlight) {
80
+ toast.setContent(` {gray-fg}Action already running...{/gray-fg}`);
81
+ screen.render();
82
+ return;
83
+ }
84
+ toast.setContent(` {cyan-fg}${name}...{/cyan-fg}`);
85
+ screen.render();
86
+ actionInFlight = (async () => {
87
+ try {
88
+ await fn();
89
+ toast.setContent(` {green-fg}${name} complete{/green-fg}`);
90
+ }
91
+ catch (e) {
92
+ toast.setContent(` {red-fg}${name} failed: ${safeString(e).slice(0, 50)}{/red-fg}`);
93
+ }
94
+ finally {
95
+ actionInFlight = null;
96
+ screen.render();
97
+ poller.tickNow();
98
+ }
99
+ })();
100
+ }
101
+ // Global keybindings
102
+ screen.key(['C-c'], async () => {
103
+ await stop();
104
+ });
105
+ screen.key(['s'], () => void runAction('Scout', actions?.runScout));
106
+ screen.key(['q'], () => void runAction('QA', actions?.runQa));
107
+ screen.key(['r'], () => {
108
+ toast.setContent(' {gray-fg}Refreshing...{/gray-fg}');
109
+ screen.render();
110
+ poller.tickNow();
111
+ });
112
+ // Poller wiring
113
+ const poller = new AdaptivePoller({
114
+ fetch: () => buildSnapshot({ db, repoRoot }),
115
+ hash: (snap) => snap.etag,
116
+ isActive: (snap) => snap.runs.activeCount > 0 || snap.runs.runningStep !== null,
117
+ intervals: {
118
+ idleMs: 1000,
119
+ activeMs: 300,
120
+ errorMs: 2000,
121
+ },
122
+ onUpdate: async (snap) => {
123
+ manager.update(snap);
124
+ toast.setContent(` {gray-fg}${snap.hintLine}{/gray-fg}`);
125
+ },
126
+ onError: (err) => {
127
+ toast.setContent(` {red-fg}Poll error: ${safeString(err).slice(0, 50)}{/red-fg}`);
128
+ screen.render();
129
+ },
130
+ });
131
+ poller.start();
132
+ async function stop() {
133
+ poller.stop();
134
+ manager.destroy();
135
+ screen.destroy();
136
+ }
137
+ return { stop };
138
+ }
139
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,OAAoB,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAS7D;;GAEG;AACH,MAAM,aAAa;IACT,OAAO,GAAqB,IAAI,CAAC;IACjC,MAAM,CAAiB;IAE/B,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,GAAG,CAAC,IAAe;QACjB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QACf,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,QAAqB;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,OAAO;QACL,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;CACF;AAED,SAAS,UAAU,CAAC,GAAY;IAC9B,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC;IAC1D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAgB;IAChD,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEvC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC5B,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,IAAI;QACjB,KAAK,EAAE,YAAY;KACpB,CAAC,CAAC;IAEH,0CAA0C;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC;QACxB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;QACtB,OAAO,EAAE,iCAAiC;KAC3C,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,oBAAoB,CAAC;QACpC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,KAAK,CAAC,UAAU,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;QACjD,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtB,IAAI,cAAc,GAAyB,IAAI,CAAC;IAEhD,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,EAAwB;QAC7D,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,KAAK,CAAC,UAAU,CAAC,eAAe,IAAI,6BAA6B,CAAC,CAAC;YACnE,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QACD,IAAI,cAAc,EAAE,CAAC;YACnB,KAAK,CAAC,UAAU,CAAC,+CAA+C,CAAC,CAAC;YAClE,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,aAAa,IAAI,eAAe,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,EAAE,CAAC;QAEhB,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;YAC3B,IAAI,CAAC;gBACH,MAAM,EAAE,EAAE,CAAC;gBACX,KAAK,CAAC,UAAU,CAAC,cAAc,IAAI,sBAAsB,CAAC,CAAC;YAC7D,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,KAAK,CAAC,UAAU,CAAC,YAAY,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC;YACtF,CAAC;oBAAS,CAAC;gBACT,cAAc,GAAG,IAAI,CAAC;gBACtB,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IAED,qBAAqB;IACrB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE;QAC7B,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE;QACrB,KAAK,CAAC,UAAU,CAAC,mCAAmC,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,gBAAgB;IAChB,MAAM,MAAM,GAAG,IAAI,cAAc,CAAc;QAC7C,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC;QAC5C,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI;QACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,IAAI;QAC/E,SAAS,EAAE;YACT,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,GAAG;YACb,OAAO,EAAE,IAAI;SACd;QACD,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACvB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrB,KAAK,CAAC,UAAU,CAAC,aAAa,IAAI,CAAC,QAAQ,YAAY,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACf,KAAK,CAAC,UAAU,CAAC,wBAAwB,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC;YAClF,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,EAAE,CAAC;IAEf,KAAK,UAAU,IAAI;QACjB,MAAM,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * TUI module exports
3
+ */
4
+ export { startTuiApp, type TuiAppDeps } from './app.js';
5
+ export { buildSnapshot, type TuiSnapshot, type BuildSnapshotDeps } from './state.js';
6
+ export { AdaptivePoller, type AdaptivePollerOptions, type AdaptivePollerIntervals } from './poller.js';
7
+ export type { TuiScreen, TuiActions } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACvG,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * TUI module exports
3
+ */
4
+ export { startTuiApp } from './app.js';
5
+ export { buildSnapshot } from './state.js';
6
+ export { AdaptivePoller } from './poller.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAmB,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,aAAa,EAA4C,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,cAAc,EAA4D,MAAM,aAAa,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Adaptive poller for TUI
3
+ *
4
+ * Polls faster when there's activity (running steps),
5
+ * slows down when idle. Uses hash-based dedupe to avoid
6
+ * unnecessary re-renders.
7
+ */
8
+ export type AdaptivePollerIntervals = {
9
+ idleMs: number;
10
+ activeMs: number;
11
+ errorMs: number;
12
+ };
13
+ export type AdaptivePollerOptions<T> = {
14
+ fetch: () => Promise<T>;
15
+ onUpdate: (snapshot: T) => void | Promise<void>;
16
+ onError?: (err: unknown) => void;
17
+ /**
18
+ * Determines whether the app is "active" (e.g. a run is running),
19
+ * which switches polling to activeMs.
20
+ */
21
+ isActive?: (snapshot: T) => boolean;
22
+ /**
23
+ * Used to avoid re-rendering when nothing changed.
24
+ * If omitted, every tick calls onUpdate.
25
+ */
26
+ hash?: (snapshot: T) => string;
27
+ intervals: AdaptivePollerIntervals;
28
+ };
29
+ export declare class AdaptivePoller<T> {
30
+ private opts;
31
+ private stopped;
32
+ private timer;
33
+ private lastHash;
34
+ constructor(opts: AdaptivePollerOptions<T>);
35
+ start(): void;
36
+ stop(): void;
37
+ /** Force an immediate poll (e.g., after user action) */
38
+ tickNow(): void;
39
+ private schedule;
40
+ private loop;
41
+ }
42
+ //# sourceMappingURL=poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poller.d.ts","sourceRoot":"","sources":["../../src/tui/poller.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAEjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC;IAEpC;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,MAAM,CAAC;IAE/B,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAEF,qBAAa,cAAc,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAAuB;gBAE3B,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAI1C,KAAK;IAML,IAAI;IAMJ,wDAAwD;IACxD,OAAO;IAOP,OAAO,CAAC,QAAQ;YAKF,IAAI;CAqBnB"}