@blockspool/cli 0.4.1 → 0.4.2

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,42 @@
1
+ /**
2
+ * Logger implementation for CLI
3
+ */
4
+ import chalk from 'chalk';
5
+ /**
6
+ * Create a logger instance
7
+ */
8
+ export function createLogger(opts = {}) {
9
+ const { verbose = false, quiet = false } = opts;
10
+ return {
11
+ debug(msg, data) {
12
+ if (verbose && !quiet) {
13
+ const dataStr = data ? ` ${JSON.stringify(data)}` : '';
14
+ console.log(chalk.gray(`[debug] ${msg}${dataStr}`));
15
+ }
16
+ },
17
+ info(msg, data) {
18
+ if (!quiet) {
19
+ const dataStr = data && verbose ? ` ${JSON.stringify(data)}` : '';
20
+ console.log(chalk.blue(`[info] ${msg}${dataStr}`));
21
+ }
22
+ },
23
+ warn(msg, data) {
24
+ const dataStr = data ? ` ${JSON.stringify(data)}` : '';
25
+ console.log(chalk.yellow(`[warn] ${msg}${dataStr}`));
26
+ },
27
+ error(msg, data) {
28
+ const dataStr = data ? ` ${JSON.stringify(data)}` : '';
29
+ console.error(chalk.red(`[error] ${msg}${dataStr}`));
30
+ },
31
+ };
32
+ }
33
+ /**
34
+ * Silent logger (for tests or quiet mode)
35
+ */
36
+ export const silentLogger = {
37
+ debug: () => { },
38
+ info: () => { },
39
+ warn: () => { },
40
+ error: () => { },
41
+ };
42
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAsB,EAAE;IACnD,MAAM,EAAE,OAAO,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC;IAEhD,OAAO;QACL,KAAK,CAAC,GAAW,EAAE,IAA8B;YAC/C,IAAI,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,GAAW,EAAE,IAA8B;YAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,GAAW,EAAE,IAA8B;YAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,KAAK,CAAC,GAAW,EAAE,IAA8B;YAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAW;IAClC,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;CAChB,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Retention & Cleanup
3
+ *
4
+ * Prune logic for all unbounded state accumulation points.
5
+ * Called automatically on session start and via `blockspool prune`.
6
+ */
7
+ import { type RetentionConfig, type SoloConfig } from './solo-config.js';
8
+ import type { DatabaseAdapter } from '@blockspool/core/db';
9
+ export interface PruneReport {
10
+ runFoldersRemoved: number;
11
+ historyLinesRemoved: number;
12
+ artifactsRemoved: number;
13
+ spoolArchivesRemoved: number;
14
+ deferredProposalsRemoved: number;
15
+ completedTicketsRemoved: number;
16
+ mergedBranchesRemoved: number;
17
+ totalPruned: number;
18
+ }
19
+ export declare function getRetentionConfig(config: SoloConfig | null): RetentionConfig;
20
+ /**
21
+ * Sort .blockspool/runs/ by mtime, delete oldest beyond cap.
22
+ */
23
+ export declare function pruneRunFolders(repoRoot: string, maxRuns: number, dryRun?: boolean): number;
24
+ /**
25
+ * Keep last N lines in history.ndjson, rewrite file.
26
+ */
27
+ export declare function pruneHistory(repoRoot: string, maxEntries: number, dryRun?: boolean): number;
28
+ /**
29
+ * Within each run folder, keep newest N artifact files.
30
+ */
31
+ export declare function pruneArtifacts(repoRoot: string, maxPerRun: number, dryRun?: boolean): number;
32
+ /**
33
+ * Delete oldest *.archived.ndjson in spool/ beyond cap.
34
+ */
35
+ export declare function pruneSpoolArchives(repoRoot: string, maxArchives: number, dryRun?: boolean): number;
36
+ /**
37
+ * Trim oldest deferred proposals from run-state.json beyond cap.
38
+ */
39
+ export declare function pruneDeferredProposals(repoRoot: string, maxDeferred: number, dryRun?: boolean): number;
40
+ /**
41
+ * Hard-delete oldest completed tickets beyond cap.
42
+ * Uses the DatabaseAdapter interface (Postgres $1 placeholders).
43
+ */
44
+ export declare function pruneCompletedTickets(adapter: DatabaseAdapter | null, maxCompleted: number, dryRun?: boolean): Promise<number>;
45
+ /**
46
+ * Delete local blockspool/* branches that are fully merged, keeping the
47
+ * newest N merged branches. Only touches local branches — never deletes
48
+ * remote branches, and never touches unmerged branches.
49
+ *
50
+ * NOT called during auto-prune-on-start — only via `blockspool prune`.
51
+ */
52
+ export declare function pruneMergedBranches(repoRoot: string, maxMergedBranches: number, dryRun?: boolean): number;
53
+ export declare function pruneAll(repoRoot: string, config: RetentionConfig, dryRun?: boolean): PruneReport;
54
+ /**
55
+ * Full prune including async DB operations.
56
+ */
57
+ export declare function pruneAllAsync(repoRoot: string, config: RetentionConfig, adapter?: DatabaseAdapter | null, dryRun?: boolean): Promise<PruneReport>;
58
+ /**
59
+ * Format a prune report for display.
60
+ */
61
+ export declare function formatPruneReport(report: PruneReport, dryRun?: boolean): string;
62
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/lib/retention.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,UAAU,EAGhB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAM3D,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAmBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,eAAe,CAK7E;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,UAAQ,GACb,MAAM,CAsBR;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,UAAQ,GACb,MAAM,CAeR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,UAAQ,GACb,MAAM,CA+BR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,UAAQ,GACb,MAAM,CAsBR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,UAAQ,GACb,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,eAAe,GAAG,IAAI,EAC/B,YAAY,EAAE,MAAM,EACpB,MAAM,UAAQ,GACb,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,EACzB,MAAM,UAAQ,GACb,MAAM,CA0CR;AAMD,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,EACvB,MAAM,UAAQ,GACb,WAAW,CAkBb;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,eAAe,GAAG,IAAW,EACtC,MAAM,UAAQ,GACb,OAAO,CAAC,WAAW,CAAC,CAQtB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,UAAQ,GAAG,MAAM,CA+B7E"}
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Retention & Cleanup
3
+ *
4
+ * Prune logic for all unbounded state accumulation points.
5
+ * Called automatically on session start and via `blockspool prune`.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { DEFAULT_RETENTION_CONFIG, getBlockspoolDir, } from './solo-config.js';
11
+ import { readRunState, writeRunState } from './run-state.js';
12
+ function emptyReport() {
13
+ return {
14
+ runFoldersRemoved: 0,
15
+ historyLinesRemoved: 0,
16
+ artifactsRemoved: 0,
17
+ spoolArchivesRemoved: 0,
18
+ deferredProposalsRemoved: 0,
19
+ completedTicketsRemoved: 0,
20
+ mergedBranchesRemoved: 0,
21
+ totalPruned: 0,
22
+ };
23
+ }
24
+ // =============================================================================
25
+ // Config helper
26
+ // =============================================================================
27
+ export function getRetentionConfig(config) {
28
+ return {
29
+ ...DEFAULT_RETENTION_CONFIG,
30
+ ...(config?.retention ?? {}),
31
+ };
32
+ }
33
+ // =============================================================================
34
+ // Individual prune functions
35
+ // =============================================================================
36
+ /**
37
+ * Sort .blockspool/runs/ by mtime, delete oldest beyond cap.
38
+ */
39
+ export function pruneRunFolders(repoRoot, maxRuns, dryRun = false) {
40
+ const runsDir = path.join(getBlockspoolDir(repoRoot), 'runs');
41
+ if (!fs.existsSync(runsDir))
42
+ return 0;
43
+ const entries = fs.readdirSync(runsDir, { withFileTypes: true })
44
+ .filter(e => e.isDirectory())
45
+ .map(e => ({
46
+ name: e.name,
47
+ path: path.join(runsDir, e.name),
48
+ mtime: fs.statSync(path.join(runsDir, e.name)).mtimeMs,
49
+ }))
50
+ .sort((a, b) => b.mtime - a.mtime); // newest first
51
+ if (entries.length <= maxRuns)
52
+ return 0;
53
+ const toRemove = entries.slice(maxRuns);
54
+ if (!dryRun) {
55
+ for (const entry of toRemove) {
56
+ fs.rmSync(entry.path, { recursive: true, force: true });
57
+ }
58
+ }
59
+ return toRemove.length;
60
+ }
61
+ /**
62
+ * Keep last N lines in history.ndjson, rewrite file.
63
+ */
64
+ export function pruneHistory(repoRoot, maxEntries, dryRun = false) {
65
+ const historyPath = path.join(getBlockspoolDir(repoRoot), 'history.ndjson');
66
+ if (!fs.existsSync(historyPath))
67
+ return 0;
68
+ const content = fs.readFileSync(historyPath, 'utf-8');
69
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
70
+ if (lines.length <= maxEntries)
71
+ return 0;
72
+ const removed = lines.length - maxEntries;
73
+ if (!dryRun) {
74
+ const kept = lines.slice(-maxEntries);
75
+ fs.writeFileSync(historyPath, kept.join('\n') + '\n');
76
+ }
77
+ return removed;
78
+ }
79
+ /**
80
+ * Within each run folder, keep newest N artifact files.
81
+ */
82
+ export function pruneArtifacts(repoRoot, maxPerRun, dryRun = false) {
83
+ const runsDir = path.join(getBlockspoolDir(repoRoot), 'runs');
84
+ if (!fs.existsSync(runsDir))
85
+ return 0;
86
+ let totalRemoved = 0;
87
+ const runDirs = fs.readdirSync(runsDir, { withFileTypes: true })
88
+ .filter(e => e.isDirectory());
89
+ for (const runDir of runDirs) {
90
+ const runPath = path.join(runsDir, runDir.name);
91
+ const files = fs.readdirSync(runPath, { withFileTypes: true })
92
+ .filter(e => e.isFile())
93
+ .map(e => ({
94
+ name: e.name,
95
+ path: path.join(runPath, e.name),
96
+ mtime: fs.statSync(path.join(runPath, e.name)).mtimeMs,
97
+ }))
98
+ .sort((a, b) => b.mtime - a.mtime);
99
+ if (files.length <= maxPerRun)
100
+ continue;
101
+ const toRemove = files.slice(maxPerRun);
102
+ if (!dryRun) {
103
+ for (const file of toRemove) {
104
+ fs.unlinkSync(file.path);
105
+ }
106
+ }
107
+ totalRemoved += toRemove.length;
108
+ }
109
+ return totalRemoved;
110
+ }
111
+ /**
112
+ * Delete oldest *.archived.ndjson in spool/ beyond cap.
113
+ */
114
+ export function pruneSpoolArchives(repoRoot, maxArchives, dryRun = false) {
115
+ const spoolDir = path.join(getBlockspoolDir(repoRoot), 'spool');
116
+ if (!fs.existsSync(spoolDir))
117
+ return 0;
118
+ const archives = fs.readdirSync(spoolDir)
119
+ .filter(f => f.endsWith('.archived.ndjson'))
120
+ .map(f => ({
121
+ name: f,
122
+ path: path.join(spoolDir, f),
123
+ mtime: fs.statSync(path.join(spoolDir, f)).mtimeMs,
124
+ }))
125
+ .sort((a, b) => b.mtime - a.mtime);
126
+ if (archives.length <= maxArchives)
127
+ return 0;
128
+ const toRemove = archives.slice(maxArchives);
129
+ if (!dryRun) {
130
+ for (const archive of toRemove) {
131
+ fs.unlinkSync(archive.path);
132
+ }
133
+ }
134
+ return toRemove.length;
135
+ }
136
+ /**
137
+ * Trim oldest deferred proposals from run-state.json beyond cap.
138
+ */
139
+ export function pruneDeferredProposals(repoRoot, maxDeferred, dryRun = false) {
140
+ const state = readRunState(repoRoot);
141
+ if (state.deferredProposals.length <= maxDeferred)
142
+ return 0;
143
+ const removed = state.deferredProposals.length - maxDeferred;
144
+ if (!dryRun) {
145
+ // Keep the newest (last added)
146
+ state.deferredProposals = state.deferredProposals.slice(-maxDeferred);
147
+ writeRunState(repoRoot, state);
148
+ }
149
+ return removed;
150
+ }
151
+ /**
152
+ * Hard-delete oldest completed tickets beyond cap.
153
+ * Uses the DatabaseAdapter interface (Postgres $1 placeholders).
154
+ */
155
+ export async function pruneCompletedTickets(adapter, maxCompleted, dryRun = false) {
156
+ if (!adapter)
157
+ return 0;
158
+ try {
159
+ const countResult = await adapter.query(`SELECT COUNT(*) as cnt FROM tickets WHERE status = 'done'`);
160
+ const count = countResult.rows[0]?.cnt ?? 0;
161
+ if (count <= maxCompleted)
162
+ return 0;
163
+ const toRemove = count - maxCompleted;
164
+ if (!dryRun) {
165
+ // Subquery to find oldest N completed tickets, then delete them.
166
+ // Works on both SQLite and Postgres.
167
+ await adapter.query(`DELETE FROM tickets WHERE id IN (
168
+ SELECT id FROM tickets WHERE status = 'done'
169
+ ORDER BY updated_at ASC LIMIT $1
170
+ )`, [toRemove]);
171
+ }
172
+ return toRemove;
173
+ }
174
+ catch {
175
+ // Table might not exist or schema differs — non-fatal
176
+ return 0;
177
+ }
178
+ }
179
+ /**
180
+ * Delete local blockspool/* branches that are fully merged, keeping the
181
+ * newest N merged branches. Only touches local branches — never deletes
182
+ * remote branches, and never touches unmerged branches.
183
+ *
184
+ * NOT called during auto-prune-on-start — only via `blockspool prune`.
185
+ */
186
+ export function pruneMergedBranches(repoRoot, maxMergedBranches, dryRun = false) {
187
+ try {
188
+ // Get the list of blockspool/* branches that are fully merged into HEAD
189
+ const mergedResult = spawnSync('git', ['branch', '--merged', 'HEAD', '--list', 'blockspool/*', '--sort=-committerdate', '--format=%(refname:short)'], { cwd: repoRoot, encoding: 'utf-8' });
190
+ if (mergedResult.status !== 0 || !mergedResult.stdout)
191
+ return 0;
192
+ // Check current branch so we never delete it
193
+ const headResult = spawnSync('git', ['branch', '--show-current'], {
194
+ cwd: repoRoot,
195
+ encoding: 'utf-8',
196
+ });
197
+ const currentBranch = headResult.stdout?.trim() ?? '';
198
+ const mergedBranches = mergedResult.stdout.trim().split('\n')
199
+ .filter(b => b && b !== currentBranch);
200
+ if (mergedBranches.length <= maxMergedBranches)
201
+ return 0;
202
+ // Keep the newest N, delete the rest
203
+ const toDelete = mergedBranches.slice(maxMergedBranches);
204
+ let removed = 0;
205
+ if (!dryRun) {
206
+ for (const branch of toDelete) {
207
+ const delResult = spawnSync('git', ['branch', '-d', branch], {
208
+ cwd: repoRoot,
209
+ encoding: 'utf-8',
210
+ });
211
+ if (delResult.status === 0)
212
+ removed++;
213
+ }
214
+ }
215
+ else {
216
+ removed = toDelete.length;
217
+ }
218
+ return removed;
219
+ }
220
+ catch {
221
+ return 0;
222
+ }
223
+ }
224
+ // =============================================================================
225
+ // Main prune (sync — file-system only)
226
+ // =============================================================================
227
+ export function pruneAll(repoRoot, config, dryRun = false) {
228
+ const report = emptyReport();
229
+ report.runFoldersRemoved = pruneRunFolders(repoRoot, config.maxRuns, dryRun);
230
+ report.historyLinesRemoved = pruneHistory(repoRoot, config.maxHistoryEntries, dryRun);
231
+ report.artifactsRemoved = pruneArtifacts(repoRoot, config.maxArtifactsPerRun, dryRun);
232
+ report.spoolArchivesRemoved = pruneSpoolArchives(repoRoot, config.maxSpoolArchives, dryRun);
233
+ report.deferredProposalsRemoved = pruneDeferredProposals(repoRoot, config.maxDeferredProposals, dryRun);
234
+ // Branch pruning is NOT run here — only via explicit `blockspool prune`
235
+ report.totalPruned =
236
+ report.runFoldersRemoved +
237
+ report.historyLinesRemoved +
238
+ report.artifactsRemoved +
239
+ report.spoolArchivesRemoved +
240
+ report.deferredProposalsRemoved;
241
+ return report;
242
+ }
243
+ /**
244
+ * Full prune including async DB operations.
245
+ */
246
+ export async function pruneAllAsync(repoRoot, config, adapter = null, dryRun = false) {
247
+ const report = pruneAll(repoRoot, config, dryRun);
248
+ report.completedTicketsRemoved = await pruneCompletedTickets(adapter, config.maxCompletedTickets, dryRun);
249
+ report.mergedBranchesRemoved = pruneMergedBranches(repoRoot, config.maxMergedBranches, dryRun);
250
+ report.totalPruned += report.completedTicketsRemoved + report.mergedBranchesRemoved;
251
+ return report;
252
+ }
253
+ /**
254
+ * Format a prune report for display.
255
+ */
256
+ export function formatPruneReport(report, dryRun = false) {
257
+ const prefix = dryRun ? 'Would remove' : 'Removed';
258
+ const lines = [];
259
+ if (report.runFoldersRemoved > 0) {
260
+ lines.push(` ${prefix} ${report.runFoldersRemoved} run folder(s)`);
261
+ }
262
+ if (report.historyLinesRemoved > 0) {
263
+ lines.push(` ${prefix} ${report.historyLinesRemoved} history line(s)`);
264
+ }
265
+ if (report.artifactsRemoved > 0) {
266
+ lines.push(` ${prefix} ${report.artifactsRemoved} artifact file(s)`);
267
+ }
268
+ if (report.spoolArchivesRemoved > 0) {
269
+ lines.push(` ${prefix} ${report.spoolArchivesRemoved} spool archive(s)`);
270
+ }
271
+ if (report.deferredProposalsRemoved > 0) {
272
+ lines.push(` ${prefix} ${report.deferredProposalsRemoved} deferred proposal(s)`);
273
+ }
274
+ if (report.completedTicketsRemoved > 0) {
275
+ lines.push(` ${prefix} ${report.completedTicketsRemoved} completed ticket(s)`);
276
+ }
277
+ if (report.mergedBranchesRemoved > 0) {
278
+ lines.push(` ${prefix} ${report.mergedBranchesRemoved} merged branch(es)`);
279
+ }
280
+ if (lines.length === 0) {
281
+ return ' Nothing to prune.';
282
+ }
283
+ return lines.join('\n');
284
+ }
285
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/lib/retention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Run History - Audit trail for auto runs
3
+ *
4
+ * Appends a structured summary after each auto run to
5
+ * .blockspool/history.ndjson for post-run analysis.
6
+ */
7
+ export interface RunHistoryEntry {
8
+ timestamp: string;
9
+ mode: 'auto' | 'manual' | 'ci';
10
+ scope: string;
11
+ formula?: string;
12
+ ticketsProposed: number;
13
+ ticketsApproved: number;
14
+ ticketsCompleted: number;
15
+ ticketsFailed: number;
16
+ prsCreated: number;
17
+ prsMerged: number;
18
+ durationMs: number;
19
+ parallel: number;
20
+ stoppedReason?: string;
21
+ errors?: string[];
22
+ /** Individual ticket outcomes */
23
+ tickets?: TicketOutcome[];
24
+ }
25
+ export interface TicketOutcome {
26
+ id: string;
27
+ title: string;
28
+ category?: string;
29
+ status: 'completed' | 'failed' | 'spindle_abort' | 'skipped';
30
+ prUrl?: string;
31
+ durationMs?: number;
32
+ error?: string;
33
+ }
34
+ /**
35
+ * Append a run history entry to the NDJSON file
36
+ */
37
+ export declare function appendRunHistory(entry: RunHistoryEntry, repoPath?: string): string;
38
+ /**
39
+ * Read all run history entries
40
+ */
41
+ export declare function readRunHistory(repoPath?: string, limit?: number): RunHistoryEntry[];
42
+ /**
43
+ * Format a history entry for display
44
+ */
45
+ export declare function formatHistoryEntry(entry: RunHistoryEntry): string;
46
+ /**
47
+ * Check run history and return a billing reminder banner if the user has hit
48
+ * a multiple of BILLING_REMINDER_INTERVAL completed tickets while using a
49
+ * paid API key. Returns null if no reminder is due.
50
+ */
51
+ export declare function getBillingReminder(repoPath?: string): string | null;
52
+ //# sourceMappingURL=run-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-history.d.ts","sourceRoot":"","sources":["../../src/lib/run-history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,eAAe,GAAG,SAAS,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,eAAe,EAAE,CAoBnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAqBjE;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBnE"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Run History - Audit trail for auto runs
3
+ *
4
+ * Appends a structured summary after each auto run to
5
+ * .blockspool/history.ndjson for post-run analysis.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ // =============================================================================
10
+ // History File Operations
11
+ // =============================================================================
12
+ /**
13
+ * Append a run history entry to the NDJSON file
14
+ */
15
+ export function appendRunHistory(entry, repoPath) {
16
+ const dir = path.join(repoPath || process.cwd(), '.blockspool');
17
+ const filePath = path.join(dir, 'history.ndjson');
18
+ // Ensure directory exists
19
+ if (!fs.existsSync(dir)) {
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ }
22
+ const line = JSON.stringify(entry) + '\n';
23
+ fs.appendFileSync(filePath, line, 'utf-8');
24
+ return filePath;
25
+ }
26
+ /**
27
+ * Read all run history entries
28
+ */
29
+ export function readRunHistory(repoPath, limit) {
30
+ const filePath = path.join(repoPath || process.cwd(), '.blockspool', 'history.ndjson');
31
+ if (!fs.existsSync(filePath))
32
+ return [];
33
+ const content = fs.readFileSync(filePath, 'utf-8');
34
+ const lines = content.trim().split('\n').filter(l => l.length > 0);
35
+ // Most recent first
36
+ const entries = [];
37
+ for (let i = lines.length - 1; i >= 0; i--) {
38
+ try {
39
+ entries.push(JSON.parse(lines[i]));
40
+ }
41
+ catch {
42
+ // Skip malformed lines
43
+ }
44
+ if (limit && entries.length >= limit)
45
+ break;
46
+ }
47
+ return entries;
48
+ }
49
+ /**
50
+ * Format a history entry for display
51
+ */
52
+ export function formatHistoryEntry(entry) {
53
+ const date = new Date(entry.timestamp);
54
+ const duration = formatDuration(entry.durationMs);
55
+ const formula = entry.formula ? ` (${entry.formula})` : '';
56
+ const parts = [
57
+ `${date.toLocaleDateString()} ${date.toLocaleTimeString()} | ${entry.mode}${formula}`,
58
+ ` Scope: ${entry.scope} | Duration: ${duration} | Parallel: ${entry.parallel}`,
59
+ ` Tickets: ${entry.ticketsCompleted} completed, ${entry.ticketsFailed} failed (${entry.ticketsProposed} proposed)`,
60
+ ` PRs: ${entry.prsCreated} created, ${entry.prsMerged} merged`,
61
+ ];
62
+ if (entry.stoppedReason) {
63
+ parts.push(` Stopped: ${entry.stoppedReason}`);
64
+ }
65
+ if (entry.errors?.length) {
66
+ parts.push(` Errors: ${entry.errors.length}`);
67
+ }
68
+ return parts.join('\n');
69
+ }
70
+ // =============================================================================
71
+ // Billing Reminder
72
+ // =============================================================================
73
+ const BILLING_REMINDER_INTERVAL = 10;
74
+ /**
75
+ * Check run history and return a billing reminder banner if the user has hit
76
+ * a multiple of BILLING_REMINDER_INTERVAL completed tickets while using a
77
+ * paid API key. Returns null if no reminder is due.
78
+ */
79
+ export function getBillingReminder(repoPath) {
80
+ const hasAnthropicKey = !!process.env.ANTHROPIC_API_KEY;
81
+ const hasOpenaiKey = !!process.env.OPENAI_API_KEY;
82
+ if (!hasAnthropicKey && !hasOpenaiKey)
83
+ return null;
84
+ const entries = readRunHistory(repoPath);
85
+ const totalCompleted = entries.reduce((sum, e) => sum + e.ticketsCompleted, 0);
86
+ if (totalCompleted === 0)
87
+ return null;
88
+ if (totalCompleted % BILLING_REMINDER_INTERVAL !== 0)
89
+ return null;
90
+ const provider = hasAnthropicKey ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY';
91
+ return [
92
+ '┌──────────────────────────────────────────────────────────────┐',
93
+ '│ Billing reminder: You are using a paid API key. │',
94
+ `│ Auth: ${provider.padEnd(50)}│`,
95
+ `│ Tickets completed this project: ${String(totalCompleted).padEnd(27)}│`,
96
+ '│ API usage is billed per-token by your provider. │',
97
+ '│ Alternatives to reduce cost: │',
98
+ '│ - BlockSpool plugin (/blockspool:auto) — uses your │',
99
+ '│ Claude Pro/Max subscription, no API key needed │',
100
+ '│ - --provider codex — uses your Codex subscription │',
101
+ '└──────────────────────────────────────────────────────────────┘',
102
+ ].join('\n');
103
+ }
104
+ function formatDuration(ms) {
105
+ const seconds = Math.floor(ms / 1000);
106
+ if (seconds < 60)
107
+ return `${seconds}s`;
108
+ const minutes = Math.floor(seconds / 60);
109
+ const remainingSeconds = seconds % 60;
110
+ if (minutes < 60)
111
+ return `${minutes}m ${remainingSeconds}s`;
112
+ const hours = Math.floor(minutes / 60);
113
+ const remainingMinutes = minutes % 60;
114
+ return `${hours}h ${remainingMinutes}m`;
115
+ }
116
+ //# sourceMappingURL=run-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-history.js","sourceRoot":"","sources":["../../src/lib/run-history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Persistent run state for cross-session cycle tracking.
3
+ *
4
+ * Stored in `.blockspool/run-state.json`. Tracks how many scout cycles
5
+ * have run so periodic tasks (like docs-audit) can trigger automatically.
6
+ */
7
+ export interface DeferredProposal {
8
+ category: string;
9
+ title: string;
10
+ description: string;
11
+ files: string[];
12
+ allowed_paths: string[];
13
+ confidence: number;
14
+ impact_score: number;
15
+ original_scope: string;
16
+ deferredAt: number;
17
+ }
18
+ export interface RunState {
19
+ /** Total scout cycles completed (persists across sessions) */
20
+ totalCycles: number;
21
+ /** Cycle number of the last docs-audit run */
22
+ lastDocsAuditCycle: number;
23
+ /** Timestamp of last run */
24
+ lastRunAt: number;
25
+ /** Proposals deferred because they were outside the session scope */
26
+ deferredProposals: DeferredProposal[];
27
+ }
28
+ /**
29
+ * Read the current run state from disk.
30
+ */
31
+ export declare function readRunState(repoRoot: string): RunState;
32
+ /**
33
+ * Write the run state to disk.
34
+ */
35
+ export declare function writeRunState(repoRoot: string, state: RunState): void;
36
+ /**
37
+ * Increment the cycle counter and return the new state.
38
+ */
39
+ export declare function recordCycle(repoRoot: string): RunState;
40
+ /**
41
+ * Check if a docs-audit cycle is due.
42
+ * Returns true every N cycles since the last docs-audit.
43
+ */
44
+ export declare function isDocsAuditDue(repoRoot: string, interval?: number): boolean;
45
+ /**
46
+ * Record that a docs-audit was run.
47
+ */
48
+ export declare function recordDocsAudit(repoRoot: string): void;
49
+ /**
50
+ * Defer a proposal for later when the scope matches.
51
+ */
52
+ export declare function deferProposal(repoRoot: string, proposal: DeferredProposal): void;
53
+ /**
54
+ * Retrieve and remove deferred proposals that now match the given scope.
55
+ * Also prunes proposals older than 7 days.
56
+ */
57
+ export declare function popDeferredForScope(repoRoot: string, scope: string): DeferredProposal[];
58
+ //# sourceMappingURL=run-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../src/lib/run-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;CACvC;AAeD;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAgBvD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAMtD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG,OAAO,CAG9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAItD;AAKD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAMhF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA8BvF"}