fun_ci 1.2.0 → 2.0.0

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 +241 -0
  3. data/README.md +180 -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 +31 -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 +73 -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 +59 -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 +58 -13
  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 -64
  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 +48 -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 +15 -33
  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 +6 -0
  194. data/lib/fun_ci.rb +1 -3
  195. metadata +212 -43
  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: 8de275f366537fcf143b9dccdd10002a9cbff42632ab7e2549b695b4a3ba6240
4
- data.tar.gz: 8723aee6ea508e2f9bf44c59cb717a83a08dc60381f389550f714b0b0f0de722
3
+ metadata.gz: 8c32c8feba3da028e17991565e2221423f97cab25535eb940667306b8f21205b
4
+ data.tar.gz: 83ec5dc09376e04feb8d08e7d863c6a574c2814754e9e57f3745d8719d4fb01e
5
5
  SHA512:
6
- metadata.gz: f4122b18c73b6434eedbe7124fa19b79c64b03fce502a34fdc978aa0d97006091477495d6d9ccfe1cb3ca8eba8be0fa9d53341d328a3bd20ecff43e00c482ddc
7
- data.tar.gz: fde4e5346d6bb6038ad35137456a6b1188a61c16f75973e9a55e44b73bc751afed551e1ea063a6a58a38adfbde4eefc1919297d870810ec8ddadfabd6106ce38
6
+ metadata.gz: 352c02313f711498642a148a7639e559ab4c1dbbd92c12f3c83f6a1cefd78263545c617428c0293ab5c2da4e6f2b70984f6bbe8db402538c84bb3d243cce6314
7
+ data.tar.gz: 6d873b080a2d5417b046aab0516cb116a92ae705b72207ab634b14196d8d18856540411f864bd424c1eb995d34018b89a07e7059eef47f1eeacdf4a5851b2088
data/CHANGELOG.md CHANGED
@@ -5,6 +5,247 @@ 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.0] - 2026-09-29
11
+
12
+ fun-ci 2.0 has two ways in. People watch a new console, drawn by a Rust
13
+ renderer in 24-bit colour, with a scene for each milestone a run passes. Coding
14
+ agents and scripts ask with new commands (`status`, `wait`, `why`, `runs`,
15
+ `events`) that answer with exit codes and keep enough of each failure to say
16
+ why it failed without running the suite again. Pipelines now run in git
17
+ worktrees at the commit they test, started after the commit rather than
18
+ before it. See "Upgrading to 2.0" in the README.
19
+
20
+ ### Changed
21
+ - The hook that runs a pipeline in the background is now `post-commit`, so it
22
+ tests the commit you just made; the `pre-commit` hook tested the commit
23
+ before it. `fun-ci install-hooks` writes `post-commit` and `pre-push`, and
24
+ removes the `pre-commit` hook fun-ci 1.x wrote. A `pre-commit` hook of your
25
+ own is left alone, and if it still calls fun-ci, `fun-ci check` warns you
26
+ to take that out. Run `fun-ci install-hooks` again to update your hooks;
27
+ until you do, `fun-ci check` warns that each run tests the commit before
28
+ yours.
29
+ - The pre-push hook waits for the fast verdict of each commit the push sends
30
+ (`fun-ci wait SHA --need fast`) instead of running the pipeline again, so a
31
+ push after a finished post-commit run goes through at once. It reads the
32
+ commits from what git passes it, so pushing a branch other than the one
33
+ checked out tests the right commits.
34
+ - Every pipeline runs in a git worktree of its own, checked out at the commit
35
+ it tests, under `.git/fun-ci/worktrees/`. Before, the stages ran in your
36
+ checkout while you kept editing it. Ignored files such as `vendor/bundle`
37
+ stay in a worktree from one run to the next. The stage scripts come from
38
+ the commit too, unless it has no `.fun-ci/`, in which case the checkout's
39
+ are used. Two pipelines run at once by default; set `worktree_slots: 3` (or
40
+ any number above 0) in `.fun-ci/config` for more.
41
+ - fun-ci keeps its database in `$XDG_STATE_HOME/fun-ci/`, or
42
+ `~/.local/state/fun-ci/` without it, instead of under `$TMPDIR`, so every
43
+ process of a user finds the same database, including an agent in a sandbox
44
+ with a `TMPDIR` of its own. Runs recorded by 1.x are not carried over. The
45
+ state directory is readable by its user alone (0700).
46
+ - A newer commit cancels an older unfinished run on its branch only in its own
47
+ project, never a run an agent is waiting on, and never a run of the same
48
+ commit.
49
+ - `fun-ci console` is drawn by a separate program, `fun-ci-renderer`, written
50
+ in Rust. The gems for Linux (x86_64, aarch64, musl) and macOS (arm64,
51
+ x86_64) include it. Elsewhere, install it with `cargo install
52
+ fun-ci-renderer` or point `FUN_CI_RENDERER` at a copy you built; without
53
+ it, `console` says so and every other command works as before. What goes
54
+ wrong between the two ends up in `.fun-ci/console.log`, since the renderer
55
+ owns the terminal.
56
+ - The console's header is a picture rather than ASCII art: soft light,
57
+ gradients and particles in 24-bit colour across the whole width, or the
58
+ nearest of 256 colours when `COLORTERM` doesn't say `truecolor` or `24bit`.
59
+ Only the parts that change are sent to the terminal.
60
+ - The header plays a scene for each milestone a run passes, each milestone
61
+ with scenes of its own, so you can tell which it was without reading: a teal
62
+ sweep, ripple or spirit level for lint; amber blocks, gears or a ringing
63
+ anvil for the build; a lightning strike, YAY or a jump to warp speed for the
64
+ fast suite; fireworks, a trophy, dancing leprechauns or a sunrise for the
65
+ whole run; an explosion or shattering glass for a failure. Scenes queue and
66
+ each plays to its end, so a failure no longer cuts a celebration short, and
67
+ a run gets one failure scene however many of its stages fail.
68
+ - When the scenes are done, the header rests on how the latest run went: a
69
+ calm dusk with a tick after a pass, a pulsing hazard sign after a failure.
70
+ Five minutes after the latest run finished it goes quiet: a starry night,
71
+ an aurora, fireflies, a fire in a medieval stone hearth with a storm at the
72
+ window, a moonlit island with a palm, or snowfall over pines, a different
73
+ one every five minutes while nothing runs, with a small lamp in the corner
74
+ that stays green after a pass and flickers red after a failure. The streak
75
+ ("7 in a row!", or "streak broken") sits in the top right.
76
+ - The console's board fits the terminal's height and scrolls to keep the
77
+ cursor on screen.
78
+ - The console's cancel prompt names the run it would cancel, as in
79
+ `Cancel feat/search (d4e5f67)? y / n`.
80
+ - With no runs yet, the console suggests `fun-ci init --everything`. It used to
81
+ suggest `fun-ci trigger HEAD`, which fails without a branch, and
82
+ `fun-ci install-hook pre-push`, which does not exist.
83
+ - When a stage fails, `fun-ci trigger` says what to do next, such as "Fix the
84
+ failing tests above, then try again."
85
+ - A project's colour in the console stays the same from one start to the next.
86
+
87
+ ### Added
88
+ - `fun-ci status [REV]` says where a commit's run stands, stage by stage, and
89
+ exits with a verdict an agent can branch on: 0 passed, 1 failed, 2 over
90
+ budget, 3 undecided, 4 superseded, 5 no run, 64 usage error. `--need
91
+ build|fast|all` says which stages must pass (fast by default), and `--json`
92
+ prints one JSON document.
93
+ - `fun-ci wait [REV]` waits for that verdict and exits with it: as soon as the
94
+ stages `--need` names have passed, or at the first of them to fail or
95
+ overrun. `--within 30s` gives up undecided (exit 3). A commit with no run
96
+ after 5 seconds gets one started. `wait` on a superseded run exits 4 naming
97
+ the newer commit, and `--follow-branch` moves on to that commit's run.
98
+ - `fun-ci runs` lists the project's recent runs, one line each; `-n N`,
99
+ `--branch NAME` and `--json`.
100
+ - `fun-ci events` prints the project's runs' events as JSON lines (run
101
+ started, stage finished, run finished, run superseded); `--follow` keeps
102
+ printing them as they happen, and `--only failures` keeps to failed or
103
+ over-budget stages and superseded runs.
104
+ - After each commit, the hook's output ends with the `fun-ci wait` command
105
+ that gets its verdict, and `fun-ci init` adds a short section to the
106
+ project's `AGENTS.md` (or `CLAUDE.md` when that is the only one) telling
107
+ agents to run it in the background.
108
+ - `fun-ci why [REV] [STAGE]` prints everything fun-ci kept about a failed
109
+ stage: how it exited, how long it took against its budget, each reported
110
+ failure with its whole message and its own output, what the extractors
111
+ picked out, and the last lines of its output. `status` and `wait` end with
112
+ a digest of it and the `why` command. `why --raw` prints the stage's whole
113
+ output, kept for a project's 10 newest runs as its first 1 MB and last
114
+ 7 MB, compressed. The end of a failed stage's output (200 lines, at most
115
+ 64 KB) is kept for the 50 newest.
116
+ - A `junit-files` entry under `evidence:` keeps each failure in the JUnit XML
117
+ a stage wrote (`build/test-results/**/*.xml`, `target/surefire-reports/*.xml`),
118
+ with the output each failure printed, and leaves alone reports an earlier
119
+ run left behind. `status --json` gives them to a program. fun-ci now depends
120
+ on `rexml` to read them.
121
+ - Every stage script finds its stage's name in `FUN_CI_STAGE`.
122
+ - Secrets are masked before fun-ci keeps anything a stage printed: the value
123
+ of any variable in the stage's environment whose name holds TOKEN, SECRET,
124
+ PASSWORD, PASSWD, API_KEY, PRIVATE_KEY or CREDENTIAL (8 characters or more)
125
+ shows as `[masked:NAME]`, and GitHub, AWS and Slack tokens, private keys and
126
+ `Authorization:` headers as `[masked]`. `mask:` under `evidence:` adds
127
+ patterns.
128
+ - Presets pick out a tool's failures from a stage's output, for 34 tools,
129
+ each checked against the recorded output of a real failing run: rspec,
130
+ Minitest, Gradle, Maven, pytest, unittest, Jest, Vitest, Mocha, Node's test
131
+ runner, Bun, Deno, go test, cargo test, dotnet test, PHPUnit, ExUnit, swift
132
+ test, dart test, GoogleTest, tsc, ESLint, RuboCop, Ruff, mypy, go build,
133
+ rustc, gcc, ShellCheck, Perl's prove, and JSON logs from
134
+ logstash-logback-encoder, Elastic's ECS encoders, pino and structlog. With
135
+ no configuration fun-ci runs those whose marker files the project has
136
+ (`Gemfile`, `pom.xml`, ...) and whose tool's output the failure shows;
137
+ `fun-ci why` says why each was chosen and `fun-ci check` lists them.
138
+ - `fun-ci init` writes stage scripts for every stack a preset reads, not only
139
+ Ruby and the JVM: rspec projects, Rust, Go, Elixir, Dart, Swift, PHP, .NET,
140
+ Python (uv, Poetry, pip), Deno, Bun, Node (pnpm, Yarn, npm), Perl, CMake and
141
+ make.
142
+ - A project says what else to keep when a stage fails under `evidence:` in
143
+ `.fun-ci/config`, within a budget of 2 seconds: `grep`, `section` (with a
144
+ `preset:`), `log-file` (what the stage wrote to a log file, and only that),
145
+ `json-log` (records at or above a level, with the fields of a named preset or
146
+ your own), `junit-files` (JUnit XML a build tool writes in its own place),
147
+ and `run:`, your own extractor in any language, which gets the context as
148
+ JSON on stdin and prints text or JSON. `detect: false` and `skip: [name]`
149
+ turn presets off. The documents a `run:` extractor reads and prints are
150
+ pinned in `contract/evidence/`.
151
+ - A stage that runs over budget says what it was doing: before the kill,
152
+ fun-ci lists its processes and the deepest one still running, and `status`
153
+ and `wait` lead with it (`fast ran over budget: running java ...
154
+ GradleWorkerMain (9.8s)`). A `run:` entry with `on: overrun` runs then, with
155
+ the stage's process group in `FUN_CI_PGID`, for a thread dump.
156
+ - When another stage shared the worktree while a stage ran (lint beside
157
+ build, the slow suite beside the fast one), `fun-ci why` says so
158
+ (`alongside: slow`), since a log file may hold both stages' lines.
159
+ - `fun-ci extract STAGE --output FILE` runs a stage's extractors against a
160
+ saved output and prints what `why` would, so an extractor can be tried
161
+ without making a commit.
162
+ - `fun-ci check` reports a mistake under `evidence:` or a `run:` script that
163
+ is missing or can't be run. A mistake never stops a pipeline: the entry is
164
+ left out and `fun-ci why` names it as a problem.
165
+ - `fun-ci prune` deletes the worktrees fun-ci keeps and has git forget them.
166
+ It refuses, and deletes nothing, while a pipeline is running.
167
+ - `.fun-ci/console.log` notes when the console starts and stops and why it
168
+ stopped, what it sent every ten minutes, and when the renderer stops
169
+ reading or a frame takes a second or more to reach the terminal.
170
+ - `fun-ci-renderer --colours 24bit|256` chooses the colours to draw in.
171
+
172
+ ### Deprecated
173
+ - `fun-ci trigger --no-validate` is the old name for `--background`. It works
174
+ until 2.1 and says so on stderr.
175
+
176
+ ### Removed
177
+ - The `fun-ci-trigger` and `fun-ci-tui` executables. Use `fun-ci trigger` and
178
+ `fun-ci console`, which have done the same job since 1.0.
179
+
180
+ ### Fixed
181
+ - In a pipeline started in the background, as the post-commit hook starts
182
+ them (and 1.x's pre-commit hook did), the fast suite waited for the slow
183
+ suite to finish before it began. It now runs beside it, so the verdict a
184
+ push waits for comes when the fast suite is done, not the slow one.
185
+ - A commit on a branch cancelled every unfinished run on a branch of that
186
+ name in every project, because all projects share one database. It now
187
+ cancels only its own project's runs.
188
+ - Cancelling a stale pipeline left its stage scripts running and could let
189
+ the old run record itself as failed after it was cancelled. fun-ci now
190
+ records every process a run starts and stops all of them, and cancels runs
191
+ still waiting for a worktree as well. A run that had already died is only
192
+ marked cancelled, since the process ids it left behind may belong to
193
+ something else by now.
194
+ - Cancelling a run from the console (`c`, then `y`) only marked it
195
+ cancelled; its stages kept running. It now stops the run.
196
+ - A run whose fast suite failed could end up PASSED when its slow suite passed
197
+ afterwards, and a run showed PASSED for a moment when the slow suite
198
+ finished before the fast suite. A run's status now follows from all four
199
+ stages, whichever finishes first.
200
+ - A slow suite whose process died without finishing left its run RUNNING for
201
+ ever. The console now records it failed on its next poll.
202
+ - A database that stays busy, or can't be written because the disk is full,
203
+ no longer stops `fun-ci trigger`: the stages still run, the exit code is
204
+ theirs, and fun-ci says once that the run wasn't recorded.
205
+ - Two fun-ci processes starting at once against a database that didn't exist
206
+ yet (the first commit and push on a machine, say) could die with `database
207
+ is locked` or `duplicate column name: project_path`. They now take turns.
208
+ - The git hooks blocked a push when fun-ci wasn't installed, failing with
209
+ "fun-ci: command not found". Now they say fun-ci is missing, how to install
210
+ it, and let the push go ahead.
211
+ - Git runs hooks with variables such as `GIT_INDEX_FILE` pointing at the
212
+ checkout. fun-ci passed them on to the git commands it runs and to your
213
+ stage scripts, which then read the checkout's index instead of the
214
+ commit's. They are cleared now.
215
+ - `fun-ci install-hooks` wrote into `.git/hooks` even when `core.hooksPath`
216
+ sends git elsewhere (as husky sets it), so git never ran the hooks; and in
217
+ a linked worktree, whose `.git` is a file, it said the project wasn't a git
218
+ repository. It now asks git where the hooks go.
219
+ - Every stage failed in a project whose path held a space, with "cannot
220
+ execute: No such file or directory". The stage script's path is quoted now.
221
+ - A stage that printed without end grew fun-ci's memory without end, since
222
+ its output was held in memory. It is written to disk as it runs now.
223
+ - The phase 1 line printed lint and build in whichever order they finished.
224
+ Lint always comes first now.
225
+
226
+ ## [1.2.1] - 2026-09-20
227
+
228
+ ### Bug Fixes
229
+ - `StageRunner` raised `NameError: uninitialized constant FunCi::NullRecorder`
230
+ when built without an explicit recorder. The module reorganisation in 1.2.0
231
+ moved `NullRecorder` under `Persistence` and the default argument was left
232
+ pointing at the old name. The file also never required the recorder it names,
233
+ so the constant was only ever resolvable by luck of load order.
234
+
235
+ ### Changed
236
+ - The gemspec reads the version from a new `lib/fun_ci/version.rb` instead of
237
+ loading the whole library. Loading the library pulls in sqlite3, and Bundler
238
+ reads the gemspec before it installs anything, so `bundle install` failed on
239
+ any machine that did not already happen to have sqlite3.
240
+ - `required_ruby_version` is now `>= 3.2`. The stated floor of 3.0 was never
241
+ reachable: the TUI uses `Data.define`, which arrived in Ruby 3.2.
242
+
243
+ ### Internal
244
+ - The cucumber suite had been broken since the 1.2.0 reorganisation, which moved
245
+ the files and constants it loads. It runs again, `rake` runs it, and it runs
246
+ with `--strict` so an undefined step fails instead of passing quietly.
247
+ - `StageRunner` has tests. It had none, which is how the broken default survived.
248
+
8
249
  ## [1.2.0] - 2026-02-23
9
250
 
10
251
  ### Bug Fixes
data/README.md CHANGED
@@ -1,20 +1,17 @@
1
1
  # 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
+ 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.
4
4
 
5
- ## How It Works
5
+ ![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)
6
6
 
7
- Fun-CI hooks into git and runs your pipeline locally:
7
+ 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).
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
+ There are two ways to use it, and both read the same results:
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
+ - **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.
12
+ - **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.
14
13
 
15
- Results are stored in a local SQLite database. A terminal dashboard lets you monitor pipeline status across branches.
16
-
17
- ## Getting Started
14
+ ## Getting started
18
15
 
19
16
  ```bash
20
17
  gem install fun_ci
@@ -22,87 +19,211 @@ cd your-project
22
19
  fun-ci init --everything
23
20
  ```
24
21
 
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
22
+ 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.
23
+
24
+ `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.
25
+
26
+ - `lint.sh`: linter and static analysis
27
+ - `build.sh`: compile or build
28
+ - `fast.sh`: the fast suite (unit tests)
29
+ - `slow.sh`: the slow suite (integration tests)
30
+
31
+ Each script gets the commit hash as its first argument, its stage's name in `FUN_CI_STAGE`, and exits 0 to pass.
32
+
33
+ Upgrading from 1.x? Read [Upgrading to 2.0](#upgrading-to-20) first.
34
+
35
+ ## How a run works
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
+ | Stage | Budget | Runs | Blocks |
38
+ |-------|--------|------|--------|
39
+ | Lint | 30 s | in parallel with build | the push |
40
+ | Build | 30 s | in parallel with lint | the push |
41
+ | Fast | 10 s | after lint and build pass | the push |
42
+ | Slow | 5 min | in the background, beside the fast suite | nothing |
31
43
 
32
- ### Manual Setup
44
+ 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.
33
45
 
34
- If you prefer to set things up step by step:
46
+ 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.
47
+
48
+ 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.
49
+
50
+ Results are kept in SQLite under `$XDG_STATE_HOME/fun-ci/` (`~/.local/state/fun-ci/` by default), shared by every project on the machine.
51
+
52
+ ## Watching: the console
35
53
 
36
54
  ```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
55
+ fun-ci console
40
56
  ```
41
57
 
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)
58
+ 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
59
 
48
- Each script receives the commit hash as its first argument.
60
+ 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
61
 
50
- ## Commands
62
+ | | |
63
+ |---|---|
64
+ | Lint passed: a teal sweep, a ripple or a spirit level | The build passed: amber blocks, gears or a ringing anvil |
65
+ | ![A teal line sweeping across the header](docs/screenshots/lint-sweep.png) | ![Two amber gears turning](docs/screenshots/build-gears.png) |
66
+ | 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 |
67
+ | ![YAY in rainbow letters with balloons](docs/screenshots/fast-yay.png) | ![The sun rising over hills](docs/screenshots/run-sunrise.png) |
68
+
69
+ 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.
70
+
71
+ ![The console after a run's fast suite failed: an explosion reading BOOM over the runs](docs/screenshots/failure.png)
72
+
73
+ 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.
74
+
75
+ | | |
76
+ |---|---|
77
+ | ![A tick over a calm teal horizon](docs/screenshots/rest-passed.png) | ![A pulsing hazard triangle](docs/screenshots/rest-failed.png) |
78
+
79
+ 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.
80
+
81
+ | | |
82
+ |---|---|
83
+ | ![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) |
84
+
85
+ Keys:
86
+
87
+ - `j` and `k`, or the arrow keys, move the cursor down and up
88
+ - `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)
89
+ - `q` quits
90
+
91
+ 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.
92
+
93
+ 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`.
94
+
95
+ ## Asking: commands for agents and scripts
96
+
97
+ Every commit's output ends with the command that gets its verdict:
51
98
 
52
99
  ```
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
100
+ fun-ci: testing 3f9c2ab. Verdict: fun-ci wait 3f9c2ab --need all
61
101
  ```
62
102
 
63
- ## Git Hooks
103
+ 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.
104
+
105
+ `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).
64
106
 
65
- After `fun-ci install-hooks`, two hooks are active:
107
+ | Exit code | Meaning |
108
+ |---|---|
109
+ | 0 | Passed |
110
+ | 1 | A needed stage failed |
111
+ | 2 | A needed stage ran over budget |
112
+ | 3 | Still undecided (`wait --within 30s` gives up at your deadline) |
113
+ | 4 | Superseded by a newer commit on the branch (`wait --follow-branch` moves on to it) |
114
+ | 5 | No run for the commit |
115
+ | 64 | Usage error |
66
116
 
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.
117
+ A failed stage comes with its evidence. `status` and `wait` end with a digest and the command that shows the rest:
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
+ ```
120
+ $ fun-ci status
121
+ fun-ci: 2330979 "Add shipping to the cart total" on main
122
+ lint passed 0.6s
123
+ build passed 0.3s
124
+ fast FAILED 0.3s
125
+ slow passed 0.5s (not needed)
126
+ fast failed:
127
+ 1) Failure:
128
+ CartTest#test_total_includes_shipping [test/cart_test.rb:16]:
129
+ Expected: 104.95
130
+ Actual: 99.95
131
+ fun-ci why 2330979 fast
132
+ ```
70
133
 
71
- ## Admin Dashboard
134
+ `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`.
72
135
 
73
- ```bash
74
- fun-ci console
136
+ ```
137
+ $ fun-ci why 2330979 fast
138
+ fun-ci: 2330979 "Add shipping to the cart total" on main
139
+ fast failed (exit 1) after 0.2s, budget 10s
140
+
141
+ Minitest's failures and errors (output:9-12), from section:minitest:
142
+ 1) Failure:
143
+ CartTest#test_total_includes_shipping [test/cart_test.rb:16]:
144
+ Expected: 104.95
145
+ Actual: 99.95
146
+
147
+ The output's last lines, from output-tail:
148
+ Run options: --seed 20502
149
+ ...
150
+ 3 runs, 3 assertions, 1 failures, 0 errors, 0 skips
151
+ rake aborted!
152
+ Command failed with status (1)
153
+ ...
154
+
155
+ The whole output: fun-ci why 2330979 fast --raw
75
156
  ```
76
157
 
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.
158
+ `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.
159
+
160
+ ### What fun-ci keeps when a stage fails
161
+
162
+ 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.
163
+
164
+ To keep more, add entries under `evidence:` in `.fun-ci/config`:
165
+
166
+ ```yaml
167
+ evidence:
168
+ stages:
169
+ fast:
170
+ - use: junit-files # each failure in the JUnit XML the stage wrote,
171
+ paths: [build/test-results/test/*.xml] # as file, line, test and message
172
+ - use: log-file # what the stage wrote to it, and only that
173
+ path: log/test.log
174
+ grep: ["ERROR"]
175
+ context: 5
176
+ slow:
177
+ - run: .fun-ci/evidence/problems # your own extractor, in any language:
178
+ format: json # the context on stdin, JSON on stdout
179
+ - run: pkill -QUIT -g "$FUN_CI_PGID" java # a thread dump before an overrun is killed
180
+ on: overrun
181
+ ```
182
+
183
+ `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.
184
+
185
+ 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.
186
+
187
+ ## Commands
78
188
 
79
- Navigation:
189
+ ```
190
+ fun-ci init Scaffold .fun-ci/ for the detected project type
191
+ fun-ci init --everything init + install-hooks + check in one step
192
+ fun-ci install-hooks Install post-commit and pre-push hooks
193
+ fun-ci install-hooks post-commit Install a single hook type
194
+ fun-ci check Verify .fun-ci/ setup
195
+ fun-ci console Watch the runs
196
+ fun-ci status [commit] Where a commit's run stands; the verdict is the exit code
197
+ fun-ci wait [commit] Wait until that verdict is decided, then exit with it
198
+ fun-ci why [commit] [stage] Everything kept about why a stage failed
199
+ fun-ci runs This project's recent runs, newest first
200
+ fun-ci events [--follow] The runs' events as JSON lines
201
+ fun-ci extract <stage> --output <file> Try a stage's extractors on a saved output
202
+ fun-ci trigger <commit> <branch> Run the whole pipeline in the foreground
203
+ fun-ci trigger --background <commit> <branch> Start the pipeline in the background (what post-commit runs)
204
+ fun-ci prune Remove fun-ci's worktrees when no pipeline is running
205
+ ```
80
206
 
81
- - `j` / `k` -- scroll up/down
82
- - `c` -- cancel a running pipeline
83
- - `q` -- quit
207
+ `fun-ci --help` lists every option.
84
208
 
85
- ## Time Budgets
209
+ ## Upgrading to 2.0
86
210
 
87
- | Stage | Budget | Blocks |
88
- |-------|--------|--------|
89
- | Lint | 30s | push |
90
- | Build | 30s | push |
91
- | Fast | 10s | push |
92
- | Slow | 5min | nothing (background) |
211
+ - 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.
212
+ - 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.
213
+ - The database moved from `$TMPDIR` to `$XDG_STATE_HOME/fun-ci/`. Runs recorded by 1.x are not carried over.
214
+ - `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`.
215
+ - The console needs `fun-ci-renderer`, which the platform gems include (see [Watching: the console](#watching-the-console)).
93
216
 
94
- If a stage exceeds its budget, it is killed and reported as timed out.
217
+ The whole list is in [CHANGELOG.md](CHANGELOG.md).
95
218
 
96
219
  ## Development
97
220
 
98
221
  ```bash
99
222
  bundle install
100
- rake test
223
+ bundle exec rake # the gate: tests, the renderer's tests, the binary contract, rubocop, clippy
101
224
  ```
102
225
 
103
- ## Changelog
104
-
105
- See [CHANGELOG.md](CHANGELOG.md) for release history.
226
+ 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
227
 
107
228
  ## License
108
229
 
@@ -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