fun_ci 1.2.1 → 2.0.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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +236 -0
  3. data/README.md +182 -59
  4. data/lib/fun_ci/agent/age.rb +15 -0
  5. data/lib/fun_ci/agent/command_support.rb +27 -0
  6. data/lib/fun_ci/agent/commands.rb +27 -0
  7. data/lib/fun_ci/agent/context.rb +10 -0
  8. data/lib/fun_ci/agent/digest.rb +49 -0
  9. data/lib/fun_ci/agent/events.rb +47 -0
  10. data/lib/fun_ci/agent/events_command.rb +61 -0
  11. data/lib/fun_ci/agent/evidence_text.rb +46 -0
  12. data/lib/fun_ci/agent/exit_code.rb +11 -0
  13. data/lib/fun_ci/agent/git.rb +32 -0
  14. data/lib/fun_ci/agent/live_pipeline.rb +35 -0
  15. data/lib/fun_ci/agent/options.rb +75 -0
  16. data/lib/fun_ci/agent/output.rb +81 -0
  17. data/lib/fun_ci/agent/report_stage.rb +71 -0
  18. data/lib/fun_ci/agent/reports.rb +47 -0
  19. data/lib/fun_ci/agent/run_report.rb +30 -0
  20. data/lib/fun_ci/agent/runs_command.rb +34 -0
  21. data/lib/fun_ci/agent/runs_text.rb +27 -0
  22. data/lib/fun_ci/agent/snapshots.rb +39 -0
  23. data/lib/fun_ci/agent/stage_summary.rb +30 -0
  24. data/lib/fun_ci/agent/status_command.rb +36 -0
  25. data/lib/fun_ci/agent/status_json.rb +27 -0
  26. data/lib/fun_ci/agent/status_text.rb +37 -0
  27. data/lib/fun_ci/agent/system_clock.rb +11 -0
  28. data/lib/fun_ci/agent/verdict.rb +32 -0
  29. data/lib/fun_ci/agent/wait_command.rb +71 -0
  30. data/lib/fun_ci/agent/waiting.rb +32 -0
  31. data/lib/fun_ci/agent/why_command.rb +50 -0
  32. data/lib/fun_ci/agent/why_json.rb +35 -0
  33. data/lib/fun_ci/agent/why_text.rb +43 -0
  34. data/lib/fun_ci/cli.rb +72 -94
  35. data/lib/fun_ci/cli_help.rb +65 -0
  36. data/lib/fun_ci/console/board_data.rb +66 -0
  37. data/lib/fun_ci/console/board_heartbeat.rb +40 -0
  38. data/lib/fun_ci/console/console_events.rb +20 -0
  39. data/lib/fun_ci/console/console_log.rb +26 -0
  40. data/lib/fun_ci/console/console_loop.rb +41 -0
  41. data/lib/fun_ci/console/console_session.rb +82 -0
  42. data/lib/fun_ci/console/console_state.rb +42 -0
  43. data/lib/fun_ci/console/key_handler.rb +73 -0
  44. data/lib/fun_ci/console/launcher.rb +43 -0
  45. data/lib/fun_ci/console/milestone_events.rb +38 -0
  46. data/lib/fun_ci/console/milestones.rb +25 -0
  47. data/lib/fun_ci/console/renderer_lookup.rb +51 -0
  48. data/lib/fun_ci/console/renderer_process.rb +60 -0
  49. data/lib/fun_ci/console/run_message.rb +33 -0
  50. data/lib/fun_ci/console/stage_change_detector.rb +32 -0
  51. data/lib/fun_ci/console/stage_events.rb +31 -0
  52. data/lib/fun_ci/console/stop_report.rb +39 -0
  53. data/lib/fun_ci/console/streak_counter.rb +19 -0
  54. data/lib/fun_ci/console/view.rb +47 -0
  55. data/lib/fun_ci/evidence/about.rb +12 -0
  56. data/lib/fun_ci/evidence/about_stage.rb +29 -0
  57. data/lib/fun_ci/evidence/caps.rb +77 -0
  58. data/lib/fun_ci/evidence/catalog.rb +83 -0
  59. data/lib/fun_ci/evidence/collector.rb +95 -0
  60. data/lib/fun_ci/evidence/command_output.rb +45 -0
  61. data/lib/fun_ci/evidence/command_reading.rb +38 -0
  62. data/lib/fun_ci/evidence/command_runner.rb +75 -0
  63. data/lib/fun_ci/evidence/context.rb +21 -0
  64. data/lib/fun_ci/evidence/context_document.rb +29 -0
  65. data/lib/fun_ci/evidence/deadline.rb +15 -0
  66. data/lib/fun_ci/evidence/detection.rb +82 -0
  67. data/lib/fun_ci/evidence/document.rb +59 -0
  68. data/lib/fun_ci/evidence/extract_command.rb +83 -0
  69. data/lib/fun_ci/evidence/extract_options.rb +45 -0
  70. data/lib/fun_ci/evidence/extraction.rb +54 -0
  71. data/lib/fun_ci/evidence/extractors/command.rb +52 -0
  72. data/lib/fun_ci/evidence/extractors/grep.rb +49 -0
  73. data/lib/fun_ci/evidence/extractors/json_log.rb +45 -0
  74. data/lib/fun_ci/evidence/extractors/junit_files.rb +32 -0
  75. data/lib/fun_ci/evidence/extractors/last_lines.rb +25 -0
  76. data/lib/fun_ci/evidence/extractors/log_file.rb +62 -0
  77. data/lib/fun_ci/evidence/extractors/process_tree.rb +51 -0
  78. data/lib/fun_ci/evidence/extractors/section.rb +49 -0
  79. data/lib/fun_ci/evidence/findings.rb +14 -0
  80. data/lib/fun_ci/evidence/line_ranges.rb +21 -0
  81. data/lib/fun_ci/evidence/line_scan.rb +22 -0
  82. data/lib/fun_ci/evidence/log_record.rb +58 -0
  83. data/lib/fun_ci/evidence/masking.rb +55 -0
  84. data/lib/fun_ci/evidence/option_check.rb +53 -0
  85. data/lib/fun_ci/evidence/outcome.rb +15 -0
  86. data/lib/fun_ci/evidence/patterns.rb +22 -0
  87. data/lib/fun_ci/evidence/presets/README.md +7 -0
  88. data/lib/fun_ci/evidence/presets/bun-test.yml +6 -0
  89. data/lib/fun_ci/evidence/presets/cargo-test.yml +6 -0
  90. data/lib/fun_ci/evidence/presets/dart-test.yml +6 -0
  91. data/lib/fun_ci/evidence/presets/deno-test.yml +6 -0
  92. data/lib/fun_ci/evidence/presets/dotnet-test.yml +6 -0
  93. data/lib/fun_ci/evidence/presets/ecs.yml +3 -0
  94. data/lib/fun_ci/evidence/presets/eslint.yml +6 -0
  95. data/lib/fun_ci/evidence/presets/exunit.yml +6 -0
  96. data/lib/fun_ci/evidence/presets/gcc.yml +6 -0
  97. data/lib/fun_ci/evidence/presets/go-build.yml +5 -0
  98. data/lib/fun_ci/evidence/presets/go-test.yml +6 -0
  99. data/lib/fun_ci/evidence/presets/gradle.yml +6 -0
  100. data/lib/fun_ci/evidence/presets/gtest.yml +6 -0
  101. data/lib/fun_ci/evidence/presets/jest.yml +6 -0
  102. data/lib/fun_ci/evidence/presets/logstash.yml +3 -0
  103. data/lib/fun_ci/evidence/presets/maven.yml +6 -0
  104. data/lib/fun_ci/evidence/presets/minitest.yml +6 -0
  105. data/lib/fun_ci/evidence/presets/mocha.yml +6 -0
  106. data/lib/fun_ci/evidence/presets/mypy.yml +6 -0
  107. data/lib/fun_ci/evidence/presets/node-test.yml +6 -0
  108. data/lib/fun_ci/evidence/presets/phpunit.yml +6 -0
  109. data/lib/fun_ci/evidence/presets/pino.yml +5 -0
  110. data/lib/fun_ci/evidence/presets/prove.yml +6 -0
  111. data/lib/fun_ci/evidence/presets/pytest.yml +6 -0
  112. data/lib/fun_ci/evidence/presets/rspec.yml +6 -0
  113. data/lib/fun_ci/evidence/presets/rubocop.yml +6 -0
  114. data/lib/fun_ci/evidence/presets/ruff.yml +6 -0
  115. data/lib/fun_ci/evidence/presets/rustc.yml +6 -0
  116. data/lib/fun_ci/evidence/presets/shellcheck.yml +5 -0
  117. data/lib/fun_ci/evidence/presets/structlog.yml +4 -0
  118. data/lib/fun_ci/evidence/presets/swift-test.yml +5 -0
  119. data/lib/fun_ci/evidence/presets/tsc.yml +6 -0
  120. data/lib/fun_ci/evidence/presets/unittest.yml +6 -0
  121. data/lib/fun_ci/evidence/presets/vitest.yml +6 -0
  122. data/lib/fun_ci/evidence/presets.rb +30 -0
  123. data/lib/fun_ci/evidence/problem.rb +9 -0
  124. data/lib/fun_ci/evidence/process_table.rb +40 -0
  125. data/lib/fun_ci/evidence/saved_run.rb +22 -0
  126. data/lib/fun_ci/evidence/section_walk.rb +49 -0
  127. data/lib/fun_ci/evidence/settings.rb +64 -0
  128. data/lib/fun_ci/evidence/settings_check.rb +78 -0
  129. data/lib/fun_ci/evidence/source.rb +34 -0
  130. data/lib/fun_ci/evidence/sources.rb +21 -0
  131. data/lib/fun_ci/evidence/stage_context.rb +30 -0
  132. data/lib/fun_ci/evidence/stamp.rb +10 -0
  133. data/lib/fun_ci/evidence/start.rb +36 -0
  134. data/lib/fun_ci/evidence/worktree.rb +62 -0
  135. data/lib/fun_ci/persistence/active_runs.rb +69 -0
  136. data/lib/fun_ci/persistence/database.rb +63 -30
  137. data/lib/fun_ci/persistence/null_recorder.rb +25 -0
  138. data/lib/fun_ci/persistence/output_tail.rb +24 -0
  139. data/lib/fun_ci/persistence/pipeline_recorder.rb +78 -32
  140. data/lib/fun_ci/persistence/pipeline_run.rb +26 -17
  141. data/lib/fun_ci/persistence/project_runs.rb +52 -0
  142. data/lib/fun_ci/persistence/raw_outputs.rb +46 -0
  143. data/lib/fun_ci/persistence/retention.rb +25 -0
  144. data/lib/fun_ci/persistence/run_status.rb +31 -0
  145. data/lib/fun_ci/persistence/stage_job.rb +61 -14
  146. data/lib/fun_ci/persistence/state_dir.rb +26 -0
  147. data/lib/fun_ci/persistence/write_trouble.rb +46 -0
  148. data/lib/fun_ci/pipeline/background_fork.rb +42 -0
  149. data/lib/fun_ci/pipeline/command_executor.rb +58 -0
  150. data/lib/fun_ci/pipeline/gate.rb +35 -0
  151. data/lib/fun_ci/pipeline/git_environment.rb +16 -0
  152. data/lib/fun_ci/pipeline/head_part.rb +40 -0
  153. data/lib/fun_ci/pipeline/in_place.rb +17 -0
  154. data/lib/fun_ci/pipeline/output_window.rb +73 -0
  155. data/lib/fun_ci/pipeline/pipeline_forker.rb +18 -16
  156. data/lib/fun_ci/pipeline/process_runner.rb +90 -17
  157. data/lib/fun_ci/pipeline/run_canceller.rb +75 -0
  158. data/lib/fun_ci/pipeline/slot.rb +30 -0
  159. data/lib/fun_ci/pipeline/slot_lock.rb +13 -0
  160. data/lib/fun_ci/pipeline/slot_run.rb +86 -0
  161. data/lib/fun_ci/pipeline/stage_dir.rb +50 -0
  162. data/lib/fun_ci/pipeline/stage_end.rb +70 -0
  163. data/lib/fun_ci/pipeline/stage_execution.rb +67 -0
  164. data/lib/fun_ci/pipeline/stage_runner.rb +42 -65
  165. data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +16 -33
  166. data/lib/fun_ci/pipeline/tail_segments.rb +60 -0
  167. data/lib/fun_ci/pipeline/test_report.rb +48 -0
  168. data/lib/fun_ci/pipeline/trigger.rb +55 -115
  169. data/lib/fun_ci/pipeline/trigger_command.rb +62 -0
  170. data/lib/fun_ci/pipeline/trigger_params.rb +56 -0
  171. data/lib/fun_ci/pipeline/workspaces.rb +22 -0
  172. data/lib/fun_ci/pipeline/worktree_pool.rb +66 -0
  173. data/lib/fun_ci/pipeline/worktree_prune.rb +49 -0
  174. data/lib/fun_ci/pipeline/worktrees.rb +45 -0
  175. data/lib/fun_ci/setup/agent_instructions.rb +31 -0
  176. data/lib/fun_ci/setup/commands.rb +49 -0
  177. data/lib/fun_ci/setup/git_hooks.rb +20 -0
  178. data/lib/fun_ci/setup/hook_script.rb +47 -0
  179. data/lib/fun_ci/setup/hook_writer.rb +13 -31
  180. data/lib/fun_ci/setup/installer.rb +39 -23
  181. data/lib/fun_ci/setup/legacy_hooks.rb +38 -0
  182. data/lib/fun_ci/setup/project_config.rb +43 -17
  183. data/lib/fun_ci/setup/project_detector.rb +39 -7
  184. data/lib/fun_ci/setup/settings.rb +36 -0
  185. data/lib/fun_ci/setup/setup_checker.rb +18 -12
  186. data/lib/fun_ci/setup/stage_templates/javascript.rb +18 -0
  187. data/lib/fun_ci/setup/stage_templates/managed.rb +24 -0
  188. data/lib/fun_ci/setup/stage_templates/native.rb +25 -0
  189. data/lib/fun_ci/setup/stage_templates/scripting.rb +27 -0
  190. data/lib/fun_ci/setup/stage_templates/shell.rb +16 -0
  191. data/lib/fun_ci/setup/stage_templates.rb +25 -0
  192. data/lib/fun_ci/setup/template_writer.rb +11 -36
  193. data/lib/fun_ci/version.rb +1 -1
  194. data/lib/fun_ci.rb +0 -1
  195. metadata +205 -41
  196. data/exe/fun-ci-trigger +0 -19
  197. data/exe/fun-ci-tui +0 -23
  198. data/lib/fun_ci/animations/celebrate.rb +0 -210
  199. data/lib/fun_ci/animations/explosion.rb +0 -138
  200. data/lib/fun_ci/animations/flash.rb +0 -205
  201. data/lib/fun_ci/animations/idle.rb +0 -127
  202. data/lib/fun_ci/animations/leprechauns.rb +0 -240
  203. data/lib/fun_ci/animations/running.rb +0 -129
  204. data/lib/fun_ci/animations/success.rb +0 -281
  205. data/lib/fun_ci/animations/yay.rb +0 -169
  206. data/lib/fun_ci/persistence/state_machine.rb +0 -50
  207. data/lib/fun_ci/pipeline/background_wrapper.rb +0 -27
  208. data/lib/fun_ci/tui/admin_tui.rb +0 -90
  209. data/lib/fun_ci/tui/animation.rb +0 -49
  210. data/lib/fun_ci/tui/animation_compositor.rb +0 -107
  211. data/lib/fun_ci/tui/animation_frames.rb +0 -112
  212. data/lib/fun_ci/tui/animation_library.rb +0 -46
  213. data/lib/fun_ci/tui/animation_renderer.rb +0 -144
  214. data/lib/fun_ci/tui/ansi.rb +0 -34
  215. data/lib/fun_ci/tui/board_data.rb +0 -53
  216. data/lib/fun_ci/tui/board_renderer.rb +0 -105
  217. data/lib/fun_ci/tui/duration_formatter.rb +0 -24
  218. data/lib/fun_ci/tui/header_animation_manager.rb +0 -71
  219. data/lib/fun_ci/tui/header_animation_player.rb +0 -45
  220. data/lib/fun_ci/tui/key_handler.rb +0 -86
  221. data/lib/fun_ci/tui/looping_animation_player.rb +0 -45
  222. data/lib/fun_ci/tui/relative_time.rb +0 -22
  223. data/lib/fun_ci/tui/row_formatter.rb +0 -108
  224. data/lib/fun_ci/tui/screen.rb +0 -103
  225. data/lib/fun_ci/tui/spinner.rb +0 -24
  226. data/lib/fun_ci/tui/stage_change_detector.rb +0 -58
  227. data/lib/fun_ci/tui/streak_counter.rb +0 -29
  228. data/lib/fun_ci/tui/terminal_input.rb +0 -69
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1bd2edd6e0a768308688dfcb34f135f1d7393ba7cae981e5f9f54ddc43be6cd
4
- data.tar.gz: 75470d2c249f468c57135a1dde2f1689a804d293426221c700c6a6be75328447
3
+ metadata.gz: c30d5baf69419b87d2fd79cd45ac0f0f38de132b8d75e97cd7da0d0deef0c7fc
4
+ data.tar.gz: 504335aa06c6c3f3a2b2e067349bfaac1a90402843a206a7da76bbf51afd34ee
5
5
  SHA512:
6
- metadata.gz: 47dac0d9da317d60dbee680db3bcb3dffa1ba3a89a63013fdce1cc45294cfcd59343169a60e9207ae2eed768a065e17eeeb5b133f41e5a45076e89a8063f026f
7
- data.tar.gz: 01e8bb0008a6de1cd77d3b8c72e4079dcc3d2977ab3fd039597d545404bfbba46606166b6014c5a2950e334ee30791d58edf782e848d228cd407187be6a2c5dc
6
+ metadata.gz: 8a2e2817e52d36a4cd27743727b25c98b1063396bb64777fe3fd1d3d4323765073433dbe5366b622092bdabca0944fb62818ac64ebd441b468467040c84aa9e6
7
+ data.tar.gz: c04951b18e20491ce6388c5a51cbda72ee4417fc44fb21bb7484d0ad82dd3c137a6e3657c313281a6c6ebb40da185972ad7a57e019a0ee46004db9caae59dbb0
data/CHANGELOG.md CHANGED
@@ -5,6 +5,242 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [2.0.1] - 2026-09-29
11
+
12
+ ### Fixed
13
+ - `fun-ci console` could show a blank screen and stay that way. When a new
14
+ commit cancelled a run in the same second one of its stages started, the
15
+ stage was recorded as ending before it began. The renderer rejected the
16
+ negative duration and, with it, every board Ruby sent, so nothing was drawn
17
+ while that run was listed; the only sign was a line per second in
18
+ `.fun-ci/console.log`. Ends are now recorded to the millisecond, and a stage
19
+ already recorded that way reads as taking no time, so an existing database
20
+ needs no repair. `fun-ci status`, `why` and `events` no longer report a
21
+ negative time for such a stage either.
22
+ - A commit made on a detached HEAD, as every commit a rebase replays is, was
23
+ recorded with no branch name, so `fun-ci status` ended in "on" and the
24
+ console showed a gap where the branch goes. Such runs are now on the branch
25
+ `detached`, whether a hook or `fun-ci wait` started them, and runs already
26
+ recorded without a name are renamed when fun-ci next opens its database.
27
+
28
+ ## [2.0.0] - 2026-09-29
29
+
30
+ fun-ci 2.0 has two ways in. People watch a new console, drawn by a Rust
31
+ renderer in 24-bit colour, with a scene for each milestone a run passes. Coding
32
+ agents and scripts ask with new commands (`status`, `wait`, `why`, `runs`,
33
+ `events`) that answer with exit codes and keep enough of each failure to say
34
+ why it failed without running the suite again. Pipelines now run in git
35
+ worktrees at the commit they test, started after the commit rather than
36
+ before it. See "Upgrading to 2.0" in the README.
37
+
38
+ ### Changed
39
+ - The hook that runs a pipeline in the background is now `post-commit`, so it
40
+ tests the commit you just made; the `pre-commit` hook tested the commit
41
+ before it. `fun-ci install-hooks` writes `post-commit` and `pre-push`, and
42
+ removes the `pre-commit` hook fun-ci 1.x wrote. A `pre-commit` hook of your
43
+ own is left alone, and if it still calls fun-ci, `fun-ci check` warns you
44
+ to take that out. Run `fun-ci install-hooks` again to update your hooks;
45
+ until you do, `fun-ci check` warns that each run tests the commit before
46
+ yours.
47
+ - The pre-push hook waits for the fast verdict of each commit the push sends
48
+ (`fun-ci wait SHA --need fast`) instead of running the pipeline again, so a
49
+ push after a finished post-commit run goes through at once. It reads the
50
+ commits from what git passes it, so pushing a branch other than the one
51
+ checked out tests the right commits.
52
+ - Every pipeline runs in a git worktree of its own, checked out at the commit
53
+ it tests, under `.git/fun-ci/worktrees/`. Before, the stages ran in your
54
+ checkout while you kept editing it. Ignored files such as `vendor/bundle`
55
+ stay in a worktree from one run to the next. The stage scripts come from
56
+ the commit too, unless it has no `.fun-ci/`, in which case the checkout's
57
+ are used. Two pipelines run at once by default; set `worktree_slots: 3` (or
58
+ any number above 0) in `.fun-ci/config` for more.
59
+ - fun-ci keeps its database in `$XDG_STATE_HOME/fun-ci/`, or
60
+ `~/.local/state/fun-ci/` without it, instead of under `$TMPDIR`, so every
61
+ process of a user finds the same database, including an agent in a sandbox
62
+ with a `TMPDIR` of its own. Runs recorded by 1.x are not carried over. The
63
+ state directory is readable by its user alone (0700).
64
+ - A newer commit cancels an older unfinished run on its branch only in its own
65
+ project, never a run an agent is waiting on, and never a run of the same
66
+ commit.
67
+ - `fun-ci console` is drawn by a separate program, `fun-ci-renderer`, written
68
+ in Rust. The gems for Linux (x86_64, aarch64, musl) and macOS (arm64,
69
+ x86_64) include it. Elsewhere, install it with `cargo install
70
+ fun-ci-renderer` or point `FUN_CI_RENDERER` at a copy you built; without
71
+ it, `console` says so and every other command works as before. What goes
72
+ wrong between the two ends up in `.fun-ci/console.log`, since the renderer
73
+ owns the terminal.
74
+ - The console's header is a picture rather than ASCII art: soft light,
75
+ gradients and particles in 24-bit colour across the whole width, or the
76
+ nearest of 256 colours when `COLORTERM` doesn't say `truecolor` or `24bit`.
77
+ Only the parts that change are sent to the terminal.
78
+ - The header plays a scene for each milestone a run passes, each milestone
79
+ with scenes of its own, so you can tell which it was without reading: a teal
80
+ sweep, ripple or spirit level for lint; amber blocks, gears or a ringing
81
+ anvil for the build; a lightning strike, YAY or a jump to warp speed for the
82
+ fast suite; fireworks, a trophy, dancing leprechauns or a sunrise for the
83
+ whole run; an explosion or shattering glass for a failure. Scenes queue and
84
+ each plays to its end, so a failure no longer cuts a celebration short, and
85
+ a run gets one failure scene however many of its stages fail.
86
+ - When the scenes are done, the header rests on how the latest run went: a
87
+ calm dusk with a tick after a pass, a pulsing hazard sign after a failure.
88
+ Five minutes after the latest run finished it goes quiet: a starry night,
89
+ an aurora, fireflies, a fire in a medieval stone hearth with a storm at the
90
+ window, a moonlit island with a palm, or snowfall over pines, a different
91
+ one every five minutes while nothing runs, with a small lamp in the corner
92
+ that stays green after a pass and flickers red after a failure. The streak
93
+ ("7 in a row!", or "streak broken") sits in the top right.
94
+ - The console's board fits the terminal's height and scrolls to keep the
95
+ cursor on screen.
96
+ - The console's cancel prompt names the run it would cancel, as in
97
+ `Cancel feat/search (d4e5f67)? y / n`.
98
+ - With no runs yet, the console suggests `fun-ci init --everything`. It used to
99
+ suggest `fun-ci trigger HEAD`, which fails without a branch, and
100
+ `fun-ci install-hook pre-push`, which does not exist.
101
+ - When a stage fails, `fun-ci trigger` says what to do next, such as "Fix the
102
+ failing tests above, then try again."
103
+ - A project's colour in the console stays the same from one start to the next.
104
+
105
+ ### Added
106
+ - `fun-ci status [REV]` says where a commit's run stands, stage by stage, and
107
+ exits with a verdict an agent can branch on: 0 passed, 1 failed, 2 over
108
+ budget, 3 undecided, 4 superseded, 5 no run, 64 usage error. `--need
109
+ build|fast|all` says which stages must pass (fast by default), and `--json`
110
+ prints one JSON document.
111
+ - `fun-ci wait [REV]` waits for that verdict and exits with it: as soon as the
112
+ stages `--need` names have passed, or at the first of them to fail or
113
+ overrun. `--within 30s` gives up undecided (exit 3). A commit with no run
114
+ after 5 seconds gets one started. `wait` on a superseded run exits 4 naming
115
+ the newer commit, and `--follow-branch` moves on to that commit's run.
116
+ - `fun-ci runs` lists the project's recent runs, one line each; `-n N`,
117
+ `--branch NAME` and `--json`.
118
+ - `fun-ci events` prints the project's runs' events as JSON lines (run
119
+ started, stage finished, run finished, run superseded); `--follow` keeps
120
+ printing them as they happen, and `--only failures` keeps to failed or
121
+ over-budget stages and superseded runs.
122
+ - After each commit, the hook's output ends with the `fun-ci wait` command
123
+ that gets its verdict, and `fun-ci init` adds a short section to the
124
+ project's `AGENTS.md` (or `CLAUDE.md` when that is the only one) telling
125
+ agents to run it in the background.
126
+ - `fun-ci why [REV] [STAGE]` prints everything fun-ci kept about a failed
127
+ stage: how it exited, how long it took against its budget, each reported
128
+ failure with its whole message and its own output, what the extractors
129
+ picked out, and the last lines of its output. `status` and `wait` end with
130
+ a digest of it and the `why` command. `why --raw` prints the stage's whole
131
+ output, kept for a project's 10 newest runs as its first 1 MB and last
132
+ 7 MB, compressed. The end of a failed stage's output (200 lines, at most
133
+ 64 KB) is kept for the 50 newest.
134
+ - A `junit-files` entry under `evidence:` keeps each failure in the JUnit XML
135
+ a stage wrote (`build/test-results/**/*.xml`, `target/surefire-reports/*.xml`),
136
+ with the output each failure printed, and leaves alone reports an earlier
137
+ run left behind. `status --json` gives them to a program. fun-ci now depends
138
+ on `rexml` to read them.
139
+ - Every stage script finds its stage's name in `FUN_CI_STAGE`.
140
+ - Secrets are masked before fun-ci keeps anything a stage printed: the value
141
+ of any variable in the stage's environment whose name holds TOKEN, SECRET,
142
+ PASSWORD, PASSWD, API_KEY, PRIVATE_KEY or CREDENTIAL (8 characters or more)
143
+ shows as `[masked:NAME]`, and GitHub, AWS and Slack tokens, private keys and
144
+ `Authorization:` headers as `[masked]`. `mask:` under `evidence:` adds
145
+ patterns.
146
+ - Presets pick out a tool's failures from a stage's output, for 34 tools,
147
+ each checked against the recorded output of a real failing run: rspec,
148
+ Minitest, Gradle, Maven, pytest, unittest, Jest, Vitest, Mocha, Node's test
149
+ runner, Bun, Deno, go test, cargo test, dotnet test, PHPUnit, ExUnit, swift
150
+ test, dart test, GoogleTest, tsc, ESLint, RuboCop, Ruff, mypy, go build,
151
+ rustc, gcc, ShellCheck, Perl's prove, and JSON logs from
152
+ logstash-logback-encoder, Elastic's ECS encoders, pino and structlog. With
153
+ no configuration fun-ci runs those whose marker files the project has
154
+ (`Gemfile`, `pom.xml`, ...) and whose tool's output the failure shows;
155
+ `fun-ci why` says why each was chosen and `fun-ci check` lists them.
156
+ - `fun-ci init` writes stage scripts for every stack a preset reads, not only
157
+ Ruby and the JVM: rspec projects, Rust, Go, Elixir, Dart, Swift, PHP, .NET,
158
+ Python (uv, Poetry, pip), Deno, Bun, Node (pnpm, Yarn, npm), Perl, CMake and
159
+ make.
160
+ - A project says what else to keep when a stage fails under `evidence:` in
161
+ `.fun-ci/config`, within a budget of 2 seconds: `grep`, `section` (with a
162
+ `preset:`), `log-file` (what the stage wrote to a log file, and only that),
163
+ `json-log` (records at or above a level, with the fields of a named preset or
164
+ your own), `junit-files` (JUnit XML a build tool writes in its own place),
165
+ and `run:`, your own extractor in any language, which gets the context as
166
+ JSON on stdin and prints text or JSON. `detect: false` and `skip: [name]`
167
+ turn presets off. The documents a `run:` extractor reads and prints are
168
+ pinned in `contract/evidence/`.
169
+ - A stage that runs over budget says what it was doing: before the kill,
170
+ fun-ci lists its processes and the deepest one still running, and `status`
171
+ and `wait` lead with it (`fast ran over budget: running java ...
172
+ GradleWorkerMain (9.8s)`). A `run:` entry with `on: overrun` runs then, with
173
+ the stage's process group in `FUN_CI_PGID`, for a thread dump.
174
+ - When another stage shared the worktree while a stage ran (lint beside
175
+ build, the slow suite beside the fast one), `fun-ci why` says so
176
+ (`alongside: slow`), since a log file may hold both stages' lines.
177
+ - `fun-ci extract STAGE --output FILE` runs a stage's extractors against a
178
+ saved output and prints what `why` would, so an extractor can be tried
179
+ without making a commit.
180
+ - `fun-ci check` reports a mistake under `evidence:` or a `run:` script that
181
+ is missing or can't be run. A mistake never stops a pipeline: the entry is
182
+ left out and `fun-ci why` names it as a problem.
183
+ - `fun-ci prune` deletes the worktrees fun-ci keeps and has git forget them.
184
+ It refuses, and deletes nothing, while a pipeline is running.
185
+ - `.fun-ci/console.log` notes when the console starts and stops and why it
186
+ stopped, what it sent every ten minutes, and when the renderer stops
187
+ reading or a frame takes a second or more to reach the terminal.
188
+ - `fun-ci-renderer --colours 24bit|256` chooses the colours to draw in.
189
+
190
+ ### Deprecated
191
+ - `fun-ci trigger --no-validate` is the old name for `--background`. It works
192
+ until 2.1 and says so on stderr.
193
+
194
+ ### Removed
195
+ - The `fun-ci-trigger` and `fun-ci-tui` executables. Use `fun-ci trigger` and
196
+ `fun-ci console`, which have done the same job since 1.0.
197
+
198
+ ### Fixed
199
+ - In a pipeline started in the background, as the post-commit hook starts
200
+ them (and 1.x's pre-commit hook did), the fast suite waited for the slow
201
+ suite to finish before it began. It now runs beside it, so the verdict a
202
+ push waits for comes when the fast suite is done, not the slow one.
203
+ - A commit on a branch cancelled every unfinished run on a branch of that
204
+ name in every project, because all projects share one database. It now
205
+ cancels only its own project's runs.
206
+ - Cancelling a stale pipeline left its stage scripts running and could let
207
+ the old run record itself as failed after it was cancelled. fun-ci now
208
+ records every process a run starts and stops all of them, and cancels runs
209
+ still waiting for a worktree as well. A run that had already died is only
210
+ marked cancelled, since the process ids it left behind may belong to
211
+ something else by now.
212
+ - Cancelling a run from the console (`c`, then `y`) only marked it
213
+ cancelled; its stages kept running. It now stops the run.
214
+ - A run whose fast suite failed could end up PASSED when its slow suite passed
215
+ afterwards, and a run showed PASSED for a moment when the slow suite
216
+ finished before the fast suite. A run's status now follows from all four
217
+ stages, whichever finishes first.
218
+ - A slow suite whose process died without finishing left its run RUNNING for
219
+ ever. The console now records it failed on its next poll.
220
+ - A database that stays busy, or can't be written because the disk is full,
221
+ no longer stops `fun-ci trigger`: the stages still run, the exit code is
222
+ theirs, and fun-ci says once that the run wasn't recorded.
223
+ - Two fun-ci processes starting at once against a database that didn't exist
224
+ yet (the first commit and push on a machine, say) could die with `database
225
+ is locked` or `duplicate column name: project_path`. They now take turns.
226
+ - The git hooks blocked a push when fun-ci wasn't installed, failing with
227
+ "fun-ci: command not found". Now they say fun-ci is missing, how to install
228
+ it, and let the push go ahead.
229
+ - Git runs hooks with variables such as `GIT_INDEX_FILE` pointing at the
230
+ checkout. fun-ci passed them on to the git commands it runs and to your
231
+ stage scripts, which then read the checkout's index instead of the
232
+ commit's. They are cleared now.
233
+ - `fun-ci install-hooks` wrote into `.git/hooks` even when `core.hooksPath`
234
+ sends git elsewhere (as husky sets it), so git never ran the hooks; and in
235
+ a linked worktree, whose `.git` is a file, it said the project wasn't a git
236
+ repository. It now asks git where the hooks go.
237
+ - Every stage failed in a project whose path held a space, with "cannot
238
+ execute: No such file or directory". The stage script's path is quoted now.
239
+ - A stage that printed without end grew fun-ci's memory without end, since
240
+ its output was held in memory. It is written to disk as it runs now.
241
+ - The phase 1 line printed lint and build in whichever order they finished.
242
+ Lint always comes first now.
243
+
8
244
  ## [1.2.1] - 2026-09-20
9
245
 
10
246
  ### Bug Fixes
data/README.md CHANGED
@@ -1,20 +1,19 @@
1
- # Fun-CI
1
+ [![Gem Version](https://badge.fury.io/rb/fun_ci.svg)](https://badge.fury.io/rb/fun_ci)
2
2
 
3
- Opinionated local CI that checks your code before it leaves your machine. Runs a four-stage pipeline on every commit with strict time budgets, so your feedback loop stays fast.
3
+ # Fun-CI
4
4
 
5
- ## How It Works
5
+ Opinionated local CI that checks your code before it leaves your machine. Every commit runs lint, build, a fast suite and a slow suite on your own computer, each held to a strict time budget, so the feedback stays fast.
6
6
 
7
- Fun-CI hooks into git and runs your pipeline locally:
7
+ ![The fun-ci console following a run: a rocket while it runs, then a scene as lint, the build, the fast suite and the whole run pass](docs/screenshots/console.png)
8
8
 
9
- 1. **Lint + Build** (parallel, 30s budget each) -- static analysis and compilation
10
- 2. **Slow suite** (spawned in background, 5min budget) -- integration/end-to-end tests
11
- 3. **Fast suite** (synchronous, 10s budget) -- unit tests
9
+ Fun-CI has two goals. It should be extremely easy to tell whether all is well, and it should be fun. The design behind both is in [docs/design.md](docs/design.md).
12
10
 
13
- On **pre-commit**, the entire pipeline forks to the background so your commit is not blocked. On **pre-push**, lint, build, and fast suite must pass before the push proceeds. The slow suite always runs in the background.
11
+ There are two ways to use it, and both read the same results:
14
12
 
15
- Results are stored in a local SQLite database. A terminal dashboard lets you monitor pipeline status across branches.
13
+ - **You watch.** `fun-ci console` sits on a second screen. Colour and motion tell you from the corner of your eye that lint passed, the build passed, the fast suite passed, or that something failed and you should look closer. A pass gets a celebration, a failure gets an explosion.
14
+ - **Your agent asks.** A coding agent can't glance at a screen, so it runs commands. `fun-ci wait` blocks until a commit's verdict is in and exits with it, and `fun-ci why` prints everything fun-ci kept about a failure, so the agent never has to rerun a suite to find out what broke.
16
15
 
17
- ## Getting Started
16
+ ## Getting started
18
17
 
19
18
  ```bash
20
19
  gem install fun_ci
@@ -22,87 +21,211 @@ cd your-project
22
21
  fun-ci init --everything
23
22
  ```
24
23
 
25
- This does three things:
26
- 1. Detects your project type and creates `.fun-ci/` with template scripts
27
- 2. Installs pre-commit and pre-push git hooks
28
- 3. Verifies the setup is valid
24
+ This detects your project type, writes four stage scripts into `.fun-ci/`, installs a `post-commit` and a `pre-push` git hook, and checks the setup. It also adds a short section to your `AGENTS.md` (or `CLAUDE.md`, if that is the only one) telling coding agents what to do after a commit.
25
+
26
+ `fun-ci init` has templates for Ruby, Gradle, Maven, Rust, Go, Elixir, Dart, Swift, PHP, .NET, Python, Deno, Bun, Node, Perl, and C or C++ built with CMake or make. [docs/stacks.md](docs/stacks.md) shows how it recognises each one and the commands it writes. Fun-CI works with any project: the stages are shell scripts, so edit them to run whatever your project uses.
27
+
28
+ - `lint.sh`: linter and static analysis
29
+ - `build.sh`: compile or build
30
+ - `fast.sh`: the fast suite (unit tests)
31
+ - `slow.sh`: the slow suite (integration tests)
32
+
33
+ Each script gets the commit hash as its first argument, its stage's name in `FUN_CI_STAGE`, and exits 0 to pass.
34
+
35
+ Upgrading from 1.x? Read [Upgrading to 2.0](#upgrading-to-20) first.
29
36
 
30
- `fun-ci init` has built-in templates for Ruby (Bundler), JVM (Gradle Kotlin, Gradle Groovy, Maven), but Fun-CI works with any project -- just write your own shell scripts.
37
+ ## How a run works
31
38
 
32
- ### Manual Setup
39
+ | Stage | Budget | Runs | Blocks |
40
+ |-------|--------|------|--------|
41
+ | Lint | 30 s | in parallel with build | the push |
42
+ | Build | 30 s | in parallel with lint | the push |
43
+ | Fast | 10 s | after lint and build pass | the push |
44
+ | Slow | 5 min | in the background, beside the fast suite | nothing |
33
45
 
34
- If you prefer to set things up step by step:
46
+ A stage that overruns its budget is killed and reported as over budget, in yellow rather than red: a fast suite that takes 12 seconds has grown too heavy, which is a different problem from a failing test.
47
+
48
+ After each commit, the `post-commit` hook starts the pipeline in the background and returns at once, so a commit is never held up. The `pre-push` hook waits for the verdict of each commit you push and stops the push if lint, build or the fast suite failed. A commit whose run has already finished goes straight through. If fun-ci isn't installed, both hooks say so and let git carry on.
49
+
50
+ Each run happens in a git worktree of its own under `.git/fun-ci/worktrees/`, checked out at the commit it tests, so you can keep editing while it runs. Ignored files such as `vendor/bundle` or `node_modules` stay in a worktree from one run to the next, which keeps builds quick. Two runs can go at once; set `worktree_slots: 3` in `.fun-ci/config` for more, and `fun-ci prune` removes the worktrees when you want the space back. A newer commit on a branch cancels the older run that is still going, unless an agent is waiting on it.
51
+
52
+ Results are kept in SQLite under `$XDG_STATE_HOME/fun-ci/` (`~/.local/state/fun-ci/` by default), shared by every project on the machine.
53
+
54
+ ## Watching: the console
35
55
 
36
56
  ```bash
37
- fun-ci init # Create .fun-ci/ with template scripts
38
- fun-ci install-hooks # Install git hooks
39
- fun-ci check # Verify everything is configured
57
+ fun-ci console
40
58
  ```
41
59
 
42
- Then edit the generated scripts in `.fun-ci/` to match your project:
43
- - `lint.sh` -- linter/static analysis
44
- - `build.sh` -- build/compile step
45
- - `fast.sh` -- fast test suite (unit tests)
46
- - `slow.sh` -- slow test suite (integration tests)
60
+ The console lists your runs across branches, newest first, one row each: the commit, the branch, each stage with its time, and the outcome. Above them is a picture that tells you what is happening without reading a word.
47
61
 
48
- Each script receives the commit hash as its first argument.
62
+ While a run runs, a rocket flies. Each milestone a run passes has its own set of scenes, so you can tell which one it was from across the room, and a failure has its own.
49
63
 
50
- ## Commands
64
+ | | |
65
+ |---|---|
66
+ | Lint passed: a teal sweep, a ripple or a spirit level | The build passed: amber blocks, gears or a ringing anvil |
67
+ | ![A teal line sweeping across the header](docs/screenshots/lint-sweep.png) | ![Two amber gears turning](docs/screenshots/build-gears.png) |
68
+ | The fast suite passed: a lightning strike, a very happy YAY or a jump to warp speed | The whole run passed: fireworks, a trophy, dancing leprechauns or a sunrise |
69
+ | ![YAY in rainbow letters with balloons](docs/screenshots/fast-yay.png) | ![The sun rising over hills](docs/screenshots/run-sunrise.png) |
70
+
71
+ Scenes queue and each plays to its end, so a failure never cuts a celebration short, and a run gets one explosion however many of its stages fail.
72
+
73
+ ![The console after a run's fast suite failed: an explosion reading BOOM over the runs](docs/screenshots/failure.png)
74
+
75
+ When the scenes are done, the header rests on how the latest run went, so a glance a minute later still tells you: a calm tick after a pass, a pulsing hazard sign after a failure. The top right keeps count of the passes in a row.
76
+
77
+ | | |
78
+ |---|---|
79
+ | ![A tick over a calm teal horizon](docs/screenshots/rest-passed.png) | ![A pulsing hazard triangle](docs/screenshots/rest-failed.png) |
80
+
81
+ Five minutes after the latest run finished, the header goes quiet: a starry night, an aurora, fireflies, a fire in a medieval stone hearth while a storm rages at the window, a moonlit island with a lone palm, or snow falling on a pine forest, changing every five minutes while nothing runs. A small lamp in the corner stays green after a pass and flickers red after a failure.
82
+
83
+ | | |
84
+ |---|---|
85
+ | ![A fire in a stone hearth, with a green lamp in the corner](docs/screenshots/quiet-fireplace.png) | ![A moonlit island with a palm, with a red lamp in the corner](docs/screenshots/quiet-island.png) |
86
+
87
+ Keys:
88
+
89
+ - `j` and `k`, or the arrow keys, move the cursor down and up
90
+ - `c` cancels the run under the cursor: a scheduled one at once, a running one once you answer `y` (`n` or `Esc` keeps it running)
91
+ - `q` quits
92
+
93
+ The console is drawn in 24-bit colour when `COLORTERM` says the terminal has it (`truecolor` or `24bit`), and in 256 colours otherwise. The pictures above come from the renderer's headless mode, drawn with its own bitmap font; in your terminal the text is in your terminal's font.
94
+
95
+ The drawing is done by a separate program, `fun-ci-renderer`, written in Rust. The gem for Linux (x86_64, aarch64, musl) and macOS (arm64, x86_64) includes it. On other Unix-like systems you get the plain gem, where every command except `console` works; install the renderer with `cargo install fun-ci-renderer` (Rust 1.88 or newer), or point `FUN_CI_RENDERER` at a copy you built. Windows is not supported, because fun-ci forks its background pipeline, but WSL runs the Linux gem. When something goes wrong between the two, the details are in `.fun-ci/console.log`.
96
+
97
+ ## Asking: commands for agents and scripts
98
+
99
+ Every commit's output ends with the command that gets its verdict:
51
100
 
52
101
  ```
53
- fun-ci trigger <commit> <branch> Run the full pipeline
54
- fun-ci trigger --no-validate <commit> <branch> Fork pipeline to background (used by pre-commit)
55
- fun-ci console Launch the TUI dashboard
56
- fun-ci init Scaffold .fun-ci/ for detected project type
57
- fun-ci init --everything init + install-hooks + check in one step
58
- fun-ci install-hooks Install pre-commit and pre-push hooks
59
- fun-ci install-hooks pre-commit Install a single hook type
60
- fun-ci check Verify .fun-ci/ setup
102
+ fun-ci: testing 3f9c2ab. Verdict: fun-ci wait 3f9c2ab --need all
61
103
  ```
62
104
 
63
- ## Git Hooks
105
+ The section `fun-ci init` writes into `AGENTS.md` tells an agent to run that command in the background after each commit. Agent harnesses wake the agent when a background command exits, so it hears about a failure without having to remember to ask.
64
106
 
65
- After `fun-ci install-hooks`, two hooks are active:
107
+ `status` says where a commit's run stands and `wait` blocks until it is decided. Both exit with the verdict for the stages you need: `--need build` (lint and build), `fast` (the default) or `all` (the slow suite too).
66
108
 
67
- **pre-commit** -- Runs `fun-ci trigger --no-validate <commit> <branch>`. This forks the pipeline into a background process and returns immediately, so commits are never blocked.
109
+ | Exit code | Meaning |
110
+ |---|---|
111
+ | 0 | Passed |
112
+ | 1 | A needed stage failed |
113
+ | 2 | A needed stage ran over budget |
114
+ | 3 | Still undecided (`wait --within 30s` gives up at your deadline) |
115
+ | 4 | Superseded by a newer commit on the branch (`wait --follow-branch` moves on to it) |
116
+ | 5 | No run for the commit |
117
+ | 64 | Usage error |
68
118
 
69
- **pre-push** -- Runs `fun-ci trigger <commit> <branch>`. This validates the project config, runs lint + build + fast suite synchronously, and blocks the push if any stage fails. The slow suite runs in the background.
119
+ A failed stage comes with its evidence. `status` and `wait` end with a digest and the command that shows the rest:
70
120
 
71
- ## Admin Dashboard
121
+ ```
122
+ $ fun-ci status
123
+ fun-ci: 2330979 "Add shipping to the cart total" on main
124
+ lint passed 0.6s
125
+ build passed 0.3s
126
+ fast FAILED 0.3s
127
+ slow passed 0.5s (not needed)
128
+ fast failed:
129
+ 1) Failure:
130
+ CartTest#test_total_includes_shipping [test/cart_test.rb:16]:
131
+ Expected: 104.95
132
+ Actual: 99.95
133
+ fun-ci why 2330979 fast
134
+ ```
72
135
 
73
- ```bash
74
- fun-ci console
136
+ `why` prints how the stage ended, each failure with its whole message and its own output, what the extractors picked out of the output and the log files, and anything that went wrong collecting it. `why --raw` prints the whole output. Every command takes `--json`.
137
+
138
+ ```
139
+ $ fun-ci why 2330979 fast
140
+ fun-ci: 2330979 "Add shipping to the cart total" on main
141
+ fast failed (exit 1) after 0.2s, budget 10s
142
+
143
+ Minitest's failures and errors (output:9-12), from section:minitest:
144
+ 1) Failure:
145
+ CartTest#test_total_includes_shipping [test/cart_test.rb:16]:
146
+ Expected: 104.95
147
+ Actual: 99.95
148
+
149
+ The output's last lines, from output-tail:
150
+ Run options: --seed 20502
151
+ ...
152
+ 3 runs, 3 assertions, 1 failures, 0 errors, 0 skips
153
+ rake aborted!
154
+ Command failed with status (1)
155
+ ...
156
+
157
+ The whole output: fun-ci why 2330979 fast --raw
75
158
  ```
76
159
 
77
- Opens a terminal UI showing pipeline status across all branches. The header area displays animated ASCII art: a gentle starfield when idle, a rocket while pipelines run, and celebration/explosion animations on success or failure.
160
+ `fun-ci runs` lists recent runs one line each, and `fun-ci events --follow` prints each run's events as JSON lines as they happen, for a supervising agent or a status bar.
161
+
162
+ ### What fun-ci keeps when a stage fails
163
+
164
+ With no configuration, fun-ci runs the presets for the tools your project has when a failure shows them. There are presets for the test runners, compilers, linters and type checkers of every stack `fun-ci init` knows, and for JSON logs from logstash-logback-encoder, ECS, pino and structlog; [docs/stacks.md](docs/stacks.md#presets) lists each one with what it picks out and when it runs. Each preset is checked against the recorded output of a real failing run of its tool. `fun-ci check` lists the ones that apply to your project.
165
+
166
+ To keep more, add entries under `evidence:` in `.fun-ci/config`:
167
+
168
+ ```yaml
169
+ evidence:
170
+ stages:
171
+ fast:
172
+ - use: junit-files # each failure in the JUnit XML the stage wrote,
173
+ paths: [build/test-results/test/*.xml] # as file, line, test and message
174
+ - use: log-file # what the stage wrote to it, and only that
175
+ path: log/test.log
176
+ grep: ["ERROR"]
177
+ context: 5
178
+ slow:
179
+ - run: .fun-ci/evidence/problems # your own extractor, in any language:
180
+ format: json # the context on stdin, JSON on stdout
181
+ - run: pkill -QUIT -g "$FUN_CI_PGID" java # a thread dump before an overrun is killed
182
+ on: overrun
183
+ ```
184
+
185
+ `fun-ci extract fast --output saved.log` runs a stage's extractors against a saved output (`fun-ci why --raw > saved.log`), so you can try an entry without making a commit. A mistake under `evidence:` never stops a pipeline: `fun-ci check` reports it and `fun-ci why` names it.
186
+
187
+ Secrets in the stage's environment are masked before anything is kept: the value of any variable whose name holds TOKEN, SECRET, PASSWORD, PASSWD, API_KEY, PRIVATE_KEY or CREDENTIAL, and GitHub, AWS and Slack tokens, private keys and `Authorization:` headers wherever they appear. The state directory is readable by your user alone.
188
+
189
+ ## Commands
78
190
 
79
- Navigation:
191
+ ```
192
+ fun-ci init Scaffold .fun-ci/ for the detected project type
193
+ fun-ci init --everything init + install-hooks + check in one step
194
+ fun-ci install-hooks Install post-commit and pre-push hooks
195
+ fun-ci install-hooks post-commit Install a single hook type
196
+ fun-ci check Verify .fun-ci/ setup
197
+ fun-ci console Watch the runs
198
+ fun-ci status [commit] Where a commit's run stands; the verdict is the exit code
199
+ fun-ci wait [commit] Wait until that verdict is decided, then exit with it
200
+ fun-ci why [commit] [stage] Everything kept about why a stage failed
201
+ fun-ci runs This project's recent runs, newest first
202
+ fun-ci events [--follow] The runs' events as JSON lines
203
+ fun-ci extract <stage> --output <file> Try a stage's extractors on a saved output
204
+ fun-ci trigger <commit> <branch> Run the whole pipeline in the foreground
205
+ fun-ci trigger --background <commit> <branch> Start the pipeline in the background (what post-commit runs)
206
+ fun-ci prune Remove fun-ci's worktrees when no pipeline is running
207
+ ```
80
208
 
81
- - `j` / `k` -- scroll up/down
82
- - `c` -- cancel a running pipeline
83
- - `q` -- quit
209
+ `fun-ci --help` lists every option.
84
210
 
85
- ## Time Budgets
211
+ ## Upgrading to 2.0
86
212
 
87
- | Stage | Budget | Blocks |
88
- |-------|--------|--------|
89
- | Lint | 30s | push |
90
- | Build | 30s | push |
91
- | Fast | 10s | push |
92
- | Slow | 5min | nothing (background) |
213
+ - Run `fun-ci install-hooks` again. The background pipeline now runs after the commit (`post-commit`), so it tests the commit you just made; the `pre-commit` hook tested the one before it. Until you do, each run tests the commit before yours, and `fun-ci check` warns you. Installing removes the `pre-commit` hook fun-ci 1.x wrote and leaves a hook of your own alone (`fun-ci check` warns if yours still calls fun-ci). The new `pre-push` hook waits for the verdict instead of running the pipeline again.
214
+ - The stages run in a worktree at the commit, not in your checkout. A script that relied on uncommitted files in your checkout won't see them any more.
215
+ - The database moved from `$TMPDIR` to `$XDG_STATE_HOME/fun-ci/`. Runs recorded by 1.x are not carried over.
216
+ - `fun-ci-trigger` and `fun-ci-tui` are gone; use `fun-ci trigger` and `fun-ci console`. `fun-ci trigger --no-validate` still works until 2.1, as the old name for `--background`.
217
+ - The console needs `fun-ci-renderer`, which the platform gems include (see [Watching: the console](#watching-the-console)).
93
218
 
94
- If a stage exceeds its budget, it is killed and reported as timed out.
219
+ The whole list is in [CHANGELOG.md](CHANGELOG.md).
95
220
 
96
221
  ## Development
97
222
 
98
223
  ```bash
99
224
  bundle install
100
- rake test
225
+ bundle exec rake # the gate: tests, the renderer's tests, the binary contract, rubocop, clippy
101
226
  ```
102
227
 
103
- ## Changelog
104
-
105
- See [CHANGELOG.md](CHANGELOG.md) for release history.
228
+ How it is built is in [docs/architecture.md](docs/architecture.md), and the requirements are in [docs/acceptance-tests.md](docs/acceptance-tests.md). The renderer is written in Rust; install Rust with [rustup](https://rustup.rs), which picks up the version the repository pins in `rust-toolchain.toml`. `ruby script/readme_screenshots.rb` draws the pictures in this README again.
106
229
 
107
230
  ## License
108
231
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FunCi
4
+ module Agent
5
+ # How long ago something happened, in the largest whole unit.
6
+ module Age
7
+ UNITS = [[86_400, "d"], [3600, "h"], [60, "m"]].freeze
8
+
9
+ def self.words(seconds)
10
+ size, unit = UNITS.find { |unit_seconds, _| seconds >= unit_seconds }
11
+ size ? "#{(seconds / size).floor}#{unit} ago" : "just now"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "options"
4
+ require_relative "reports"
5
+ require_relative "exit_code"
6
+
7
+ module FunCi
8
+ module Agent
9
+ # What the agent commands share: the commit a revision names, the reports
10
+ # of its runs, and refusing arguments they can't use. Expects @context and
11
+ # a NAME constant.
12
+ module CommandSupport
13
+ private
14
+
15
+ def resolve(rev)
16
+ @context.git.resolve(rev) || raise(Options::Invalid, "git can't find the commit '#{rev}'")
17
+ end
18
+
19
+ def reports = @reports ||= Reports.new(@context.db, @context.git)
20
+
21
+ def usage(message)
22
+ @context.io.stderr.puts "fun-ci #{self.class::NAME}: #{message}"
23
+ ExitCode::USAGE
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../pipeline/trigger_params"
4
+ require_relative "context"
5
+ require_relative "git"
6
+ require_relative "system_clock"
7
+ require_relative "live_pipeline"
8
+ require_relative "status_command"
9
+ require_relative "runs_command"
10
+ require_relative "wait_command"
11
+ require_relative "events_command"
12
+ require_relative "why_command"
13
+
14
+ module FunCi
15
+ module Agent
16
+ # The commands an agent runs in a project, each answered from the
17
+ # database and the project's git.
18
+ module Commands
19
+ ALL = { "status" => StatusCommand, "runs" => RunsCommand, "wait" => WaitCommand,
20
+ "events" => EventsCommand, "why" => WhyCommand }.freeze
21
+
22
+ def self.run(name, args, context)
23
+ ALL.fetch(name).new(context).run(args)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FunCi
4
+ module Agent
5
+ # What every agent command answers from: the database, the project's git,
6
+ # where to print, the time (#now, #pause) and the pipeline (#start a run,
7
+ # #watch the database for slow suites that died).
8
+ Context = Data.define(:db, :git, :io, :clock, :pipeline)
9
+ end
10
+ end