fun_ci 2.0.0-x86_64-linux

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 (200) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +294 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +230 -0
  5. data/exe/fun-ci +7 -0
  6. data/lib/fun_ci/agent/age.rb +15 -0
  7. data/lib/fun_ci/agent/command_support.rb +27 -0
  8. data/lib/fun_ci/agent/commands.rb +27 -0
  9. data/lib/fun_ci/agent/context.rb +10 -0
  10. data/lib/fun_ci/agent/digest.rb +49 -0
  11. data/lib/fun_ci/agent/events.rb +47 -0
  12. data/lib/fun_ci/agent/events_command.rb +61 -0
  13. data/lib/fun_ci/agent/evidence_text.rb +46 -0
  14. data/lib/fun_ci/agent/exit_code.rb +11 -0
  15. data/lib/fun_ci/agent/git.rb +31 -0
  16. data/lib/fun_ci/agent/live_pipeline.rb +35 -0
  17. data/lib/fun_ci/agent/options.rb +75 -0
  18. data/lib/fun_ci/agent/output.rb +81 -0
  19. data/lib/fun_ci/agent/report_stage.rb +73 -0
  20. data/lib/fun_ci/agent/reports.rb +47 -0
  21. data/lib/fun_ci/agent/run_report.rb +30 -0
  22. data/lib/fun_ci/agent/runs_command.rb +34 -0
  23. data/lib/fun_ci/agent/runs_text.rb +27 -0
  24. data/lib/fun_ci/agent/snapshots.rb +39 -0
  25. data/lib/fun_ci/agent/stage_summary.rb +30 -0
  26. data/lib/fun_ci/agent/status_command.rb +36 -0
  27. data/lib/fun_ci/agent/status_json.rb +27 -0
  28. data/lib/fun_ci/agent/status_text.rb +37 -0
  29. data/lib/fun_ci/agent/system_clock.rb +11 -0
  30. data/lib/fun_ci/agent/verdict.rb +32 -0
  31. data/lib/fun_ci/agent/wait_command.rb +71 -0
  32. data/lib/fun_ci/agent/waiting.rb +32 -0
  33. data/lib/fun_ci/agent/why_command.rb +50 -0
  34. data/lib/fun_ci/agent/why_json.rb +35 -0
  35. data/lib/fun_ci/agent/why_text.rb +43 -0
  36. data/lib/fun_ci/cli.rb +128 -0
  37. data/lib/fun_ci/cli_help.rb +65 -0
  38. data/lib/fun_ci/console/board_data.rb +66 -0
  39. data/lib/fun_ci/console/board_heartbeat.rb +40 -0
  40. data/lib/fun_ci/console/console_events.rb +20 -0
  41. data/lib/fun_ci/console/console_log.rb +26 -0
  42. data/lib/fun_ci/console/console_loop.rb +41 -0
  43. data/lib/fun_ci/console/console_session.rb +82 -0
  44. data/lib/fun_ci/console/console_state.rb +42 -0
  45. data/lib/fun_ci/console/key_handler.rb +73 -0
  46. data/lib/fun_ci/console/launcher.rb +43 -0
  47. data/lib/fun_ci/console/milestone_events.rb +38 -0
  48. data/lib/fun_ci/console/milestones.rb +25 -0
  49. data/lib/fun_ci/console/renderer_lookup.rb +51 -0
  50. data/lib/fun_ci/console/renderer_process.rb +60 -0
  51. data/lib/fun_ci/console/run_message.rb +33 -0
  52. data/lib/fun_ci/console/stage_change_detector.rb +32 -0
  53. data/lib/fun_ci/console/stage_events.rb +31 -0
  54. data/lib/fun_ci/console/stop_report.rb +39 -0
  55. data/lib/fun_ci/console/streak_counter.rb +19 -0
  56. data/lib/fun_ci/console/view.rb +47 -0
  57. data/lib/fun_ci/evidence/about.rb +12 -0
  58. data/lib/fun_ci/evidence/about_stage.rb +29 -0
  59. data/lib/fun_ci/evidence/caps.rb +77 -0
  60. data/lib/fun_ci/evidence/catalog.rb +83 -0
  61. data/lib/fun_ci/evidence/collector.rb +95 -0
  62. data/lib/fun_ci/evidence/command_output.rb +45 -0
  63. data/lib/fun_ci/evidence/command_reading.rb +38 -0
  64. data/lib/fun_ci/evidence/command_runner.rb +75 -0
  65. data/lib/fun_ci/evidence/context.rb +21 -0
  66. data/lib/fun_ci/evidence/context_document.rb +29 -0
  67. data/lib/fun_ci/evidence/deadline.rb +15 -0
  68. data/lib/fun_ci/evidence/detection.rb +82 -0
  69. data/lib/fun_ci/evidence/document.rb +59 -0
  70. data/lib/fun_ci/evidence/extract_command.rb +83 -0
  71. data/lib/fun_ci/evidence/extract_options.rb +45 -0
  72. data/lib/fun_ci/evidence/extraction.rb +54 -0
  73. data/lib/fun_ci/evidence/extractors/command.rb +52 -0
  74. data/lib/fun_ci/evidence/extractors/grep.rb +49 -0
  75. data/lib/fun_ci/evidence/extractors/json_log.rb +45 -0
  76. data/lib/fun_ci/evidence/extractors/junit_files.rb +32 -0
  77. data/lib/fun_ci/evidence/extractors/last_lines.rb +25 -0
  78. data/lib/fun_ci/evidence/extractors/log_file.rb +62 -0
  79. data/lib/fun_ci/evidence/extractors/process_tree.rb +51 -0
  80. data/lib/fun_ci/evidence/extractors/section.rb +49 -0
  81. data/lib/fun_ci/evidence/findings.rb +14 -0
  82. data/lib/fun_ci/evidence/line_ranges.rb +21 -0
  83. data/lib/fun_ci/evidence/line_scan.rb +22 -0
  84. data/lib/fun_ci/evidence/log_record.rb +58 -0
  85. data/lib/fun_ci/evidence/masking.rb +55 -0
  86. data/lib/fun_ci/evidence/option_check.rb +53 -0
  87. data/lib/fun_ci/evidence/outcome.rb +15 -0
  88. data/lib/fun_ci/evidence/patterns.rb +22 -0
  89. data/lib/fun_ci/evidence/presets/README.md +7 -0
  90. data/lib/fun_ci/evidence/presets/bun-test.yml +6 -0
  91. data/lib/fun_ci/evidence/presets/cargo-test.yml +6 -0
  92. data/lib/fun_ci/evidence/presets/dart-test.yml +6 -0
  93. data/lib/fun_ci/evidence/presets/deno-test.yml +6 -0
  94. data/lib/fun_ci/evidence/presets/dotnet-test.yml +6 -0
  95. data/lib/fun_ci/evidence/presets/ecs.yml +3 -0
  96. data/lib/fun_ci/evidence/presets/eslint.yml +6 -0
  97. data/lib/fun_ci/evidence/presets/exunit.yml +6 -0
  98. data/lib/fun_ci/evidence/presets/gcc.yml +6 -0
  99. data/lib/fun_ci/evidence/presets/go-build.yml +5 -0
  100. data/lib/fun_ci/evidence/presets/go-test.yml +6 -0
  101. data/lib/fun_ci/evidence/presets/gradle.yml +6 -0
  102. data/lib/fun_ci/evidence/presets/gtest.yml +6 -0
  103. data/lib/fun_ci/evidence/presets/jest.yml +6 -0
  104. data/lib/fun_ci/evidence/presets/logstash.yml +3 -0
  105. data/lib/fun_ci/evidence/presets/maven.yml +6 -0
  106. data/lib/fun_ci/evidence/presets/minitest.yml +6 -0
  107. data/lib/fun_ci/evidence/presets/mocha.yml +6 -0
  108. data/lib/fun_ci/evidence/presets/mypy.yml +6 -0
  109. data/lib/fun_ci/evidence/presets/node-test.yml +6 -0
  110. data/lib/fun_ci/evidence/presets/phpunit.yml +6 -0
  111. data/lib/fun_ci/evidence/presets/pino.yml +5 -0
  112. data/lib/fun_ci/evidence/presets/prove.yml +6 -0
  113. data/lib/fun_ci/evidence/presets/pytest.yml +6 -0
  114. data/lib/fun_ci/evidence/presets/rspec.yml +6 -0
  115. data/lib/fun_ci/evidence/presets/rubocop.yml +6 -0
  116. data/lib/fun_ci/evidence/presets/ruff.yml +6 -0
  117. data/lib/fun_ci/evidence/presets/rustc.yml +6 -0
  118. data/lib/fun_ci/evidence/presets/shellcheck.yml +5 -0
  119. data/lib/fun_ci/evidence/presets/structlog.yml +4 -0
  120. data/lib/fun_ci/evidence/presets/swift-test.yml +5 -0
  121. data/lib/fun_ci/evidence/presets/tsc.yml +6 -0
  122. data/lib/fun_ci/evidence/presets/unittest.yml +6 -0
  123. data/lib/fun_ci/evidence/presets/vitest.yml +6 -0
  124. data/lib/fun_ci/evidence/presets.rb +30 -0
  125. data/lib/fun_ci/evidence/problem.rb +9 -0
  126. data/lib/fun_ci/evidence/process_table.rb +40 -0
  127. data/lib/fun_ci/evidence/saved_run.rb +22 -0
  128. data/lib/fun_ci/evidence/section_walk.rb +49 -0
  129. data/lib/fun_ci/evidence/settings.rb +64 -0
  130. data/lib/fun_ci/evidence/settings_check.rb +78 -0
  131. data/lib/fun_ci/evidence/source.rb +34 -0
  132. data/lib/fun_ci/evidence/sources.rb +21 -0
  133. data/lib/fun_ci/evidence/stage_context.rb +30 -0
  134. data/lib/fun_ci/evidence/stamp.rb +10 -0
  135. data/lib/fun_ci/evidence/start.rb +36 -0
  136. data/lib/fun_ci/evidence/worktree.rb +62 -0
  137. data/lib/fun_ci/persistence/active_runs.rb +69 -0
  138. data/lib/fun_ci/persistence/database.rb +81 -0
  139. data/lib/fun_ci/persistence/null_recorder.rb +25 -0
  140. data/lib/fun_ci/persistence/output_tail.rb +24 -0
  141. data/lib/fun_ci/persistence/pipeline_recorder.rb +120 -0
  142. data/lib/fun_ci/persistence/pipeline_run.rb +68 -0
  143. data/lib/fun_ci/persistence/project_runs.rb +52 -0
  144. data/lib/fun_ci/persistence/raw_outputs.rb +46 -0
  145. data/lib/fun_ci/persistence/retention.rb +25 -0
  146. data/lib/fun_ci/persistence/run_status.rb +31 -0
  147. data/lib/fun_ci/persistence/stage_job.rb +91 -0
  148. data/lib/fun_ci/persistence/state_dir.rb +26 -0
  149. data/lib/fun_ci/persistence/write_trouble.rb +46 -0
  150. data/lib/fun_ci/pipeline/background_fork.rb +42 -0
  151. data/lib/fun_ci/pipeline/command_executor.rb +58 -0
  152. data/lib/fun_ci/pipeline/gate.rb +35 -0
  153. data/lib/fun_ci/pipeline/git_environment.rb +16 -0
  154. data/lib/fun_ci/pipeline/head_part.rb +40 -0
  155. data/lib/fun_ci/pipeline/in_place.rb +17 -0
  156. data/lib/fun_ci/pipeline/output_window.rb +73 -0
  157. data/lib/fun_ci/pipeline/pipeline_forker.rb +40 -0
  158. data/lib/fun_ci/pipeline/process_runner.rb +101 -0
  159. data/lib/fun_ci/pipeline/progress_reporter.rb +31 -0
  160. data/lib/fun_ci/pipeline/run_canceller.rb +75 -0
  161. data/lib/fun_ci/pipeline/slot.rb +30 -0
  162. data/lib/fun_ci/pipeline/slot_lock.rb +13 -0
  163. data/lib/fun_ci/pipeline/slot_run.rb +86 -0
  164. data/lib/fun_ci/pipeline/stage_dir.rb +50 -0
  165. data/lib/fun_ci/pipeline/stage_end.rb +70 -0
  166. data/lib/fun_ci/pipeline/stage_execution.rb +67 -0
  167. data/lib/fun_ci/pipeline/stage_runner.rb +63 -0
  168. data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +36 -0
  169. data/lib/fun_ci/pipeline/tail_segments.rb +60 -0
  170. data/lib/fun_ci/pipeline/test_report.rb +48 -0
  171. data/lib/fun_ci/pipeline/trigger.rb +93 -0
  172. data/lib/fun_ci/pipeline/trigger_command.rb +62 -0
  173. data/lib/fun_ci/pipeline/trigger_params.rb +48 -0
  174. data/lib/fun_ci/pipeline/workspaces.rb +22 -0
  175. data/lib/fun_ci/pipeline/worktree_pool.rb +66 -0
  176. data/lib/fun_ci/pipeline/worktree_prune.rb +49 -0
  177. data/lib/fun_ci/pipeline/worktrees.rb +45 -0
  178. data/lib/fun_ci/setup/agent_instructions.rb +31 -0
  179. data/lib/fun_ci/setup/commands.rb +49 -0
  180. data/lib/fun_ci/setup/git_hooks.rb +20 -0
  181. data/lib/fun_ci/setup/hook_script.rb +47 -0
  182. data/lib/fun_ci/setup/hook_writer.rb +57 -0
  183. data/lib/fun_ci/setup/installer.rb +71 -0
  184. data/lib/fun_ci/setup/legacy_hooks.rb +38 -0
  185. data/lib/fun_ci/setup/maven_linter_detector.rb +26 -0
  186. data/lib/fun_ci/setup/project_config.rb +68 -0
  187. data/lib/fun_ci/setup/project_detector.rb +54 -0
  188. data/lib/fun_ci/setup/settings.rb +36 -0
  189. data/lib/fun_ci/setup/setup_checker.rb +36 -0
  190. data/lib/fun_ci/setup/stage_templates/javascript.rb +18 -0
  191. data/lib/fun_ci/setup/stage_templates/managed.rb +24 -0
  192. data/lib/fun_ci/setup/stage_templates/native.rb +25 -0
  193. data/lib/fun_ci/setup/stage_templates/scripting.rb +27 -0
  194. data/lib/fun_ci/setup/stage_templates/shell.rb +16 -0
  195. data/lib/fun_ci/setup/stage_templates.rb +25 -0
  196. data/lib/fun_ci/setup/template_writer.rb +28 -0
  197. data/lib/fun_ci/version.rb +6 -0
  198. data/lib/fun_ci.rb +13 -0
  199. data/libexec/fun-ci-renderer +0 -0
  200. metadata +286 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 98e558ae7dc888aac4569f5c7845765ac85c6edb2fae4da54f138777dd25b087
4
+ data.tar.gz: d8c10643d2f900cdff1779d8d94ffdc11c455b6277c8bc40ef0f194e97e31b48
5
+ SHA512:
6
+ metadata.gz: 8524f64bd537280fcf9e7411bd3772a1664ba7854a1015cff2edf8a1a5c6230e70abd20c9e2821045a03520f67ba754a9ee0e0549d4b4bc356a513d7b48c3c8c
7
+ data.tar.gz: 2443708f11f8da5dfc6c150ffb995a89c072f86615a4c832f450e2776e76fdbd3958f7a5a9c99da404f4615a60c525a59e12eabeb7fb2d0ad627f7d80916b9c3
data/CHANGELOG.md ADDED
@@ -0,0 +1,294 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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
+
249
+ ## [1.2.0] - 2026-02-23
250
+
251
+ ### Bug Fixes
252
+ - Replaced unreliable `Timeout.timeout` with process groups (`pgroup: true`) and `Thread#join` for reliable time budget enforcement on lint/build/fast/slow stages
253
+ - Fixed off-by-one in TUI row truncation that caused the top pipeline row to be hidden behind the header in short terminals
254
+ - Added `Screen#height=` to clear screen on terminal height changes, preventing the header from scrolling off-screen when tmux panes resize
255
+ - Fixed header flicker by skipping `println` when animation is active
256
+ - Allowed null SHA on root commits so pipeline still runs
257
+ - Detected multi-module Gradle projects from `settings.gradle`
258
+
259
+ ### Refactoring
260
+ - Extracted `TerminalInput`, `KeyHandler`, `BoardRenderer` from `AdminTui` (was 257 lines/12 ivars, now 4 focused classes all under 150 lines/4 ivars)
261
+ - Organized flat `lib/fun_ci/` into cohesive module subdirectories: `persistence/`, `pipeline/`, `setup/`, `tui/`
262
+
263
+ ### Internal
264
+ - Extracted shared `ProcessRunner` module for consistent process spawning across stages
265
+ - Updated `BackgroundWrapper` to use `[output, status, timed_out]` triples instead of exception-based timeout signaling
266
+ - Truncated TUI board rows to fit terminal height
267
+
268
+ ## [1.1.0] - 2026-02-22
269
+
270
+ ### Added
271
+ - Multi-line ASCII art header animations for pipeline events (explosion on failure, celebrations on success)
272
+ - Looping idle starfield animation in the TUI header
273
+ - Rocket animation while pipelines are running, with flickering exhaust and parallax starfield
274
+ - HeaderAnimationManager with idle/running/event state transitions
275
+ - AnimationLibrary with 8 animation data files (explosion, success, celebrate, flash, leprechauns, yay, idle, running)
276
+
277
+ ### Fixed
278
+ - Footer animations now display 8x slower so text is actually readable
279
+ - Animation refresh no longer blocks for 5 seconds between frames when no pipelines are running
280
+ - Removed stale single-line header banner that conflicted with multi-line animations
281
+ - Animation frames padded to global max width across all frames, preventing horizontal jitter
282
+
283
+ ## [1.0.0] - 2026-02-21
284
+
285
+ ### Added
286
+ - Four-stage local CI pipeline (lint, build, fast, slow) with strict time budgets (30s/30s/10s/5min)
287
+ - Unified CLI (`fun-ci`) with subcommands: trigger, console, init, install-hooks, check
288
+ - Pre-commit hook (background fork, non-blocking) and pre-push hook (synchronous, blocking)
289
+ - TUI dashboard with vim-style navigation (j/k/c/q), pagination, and project path display
290
+ - Project scaffolding with built-in templates for Ruby (Bundler), JVM (Gradle Kotlin/Groovy, Maven)
291
+ - SQLite persistence with WAL mode for pipeline results
292
+ - Parallel lint + build stages with background slow suite
293
+ - Smart Maven linter detection for template generation
294
+ - Stale pipeline auto-cancellation
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Erik T. Madsen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Fun-CI
2
+
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
+
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
+
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
+
9
+ There are two ways to use it, and both read the same results:
10
+
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.
13
+
14
+ ## Getting started
15
+
16
+ ```bash
17
+ gem install fun_ci
18
+ cd your-project
19
+ fun-ci init --everything
20
+ ```
21
+
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
36
+
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 |
43
+
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.
45
+
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
53
+
54
+ ```bash
55
+ fun-ci console
56
+ ```
57
+
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.
59
+
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.
61
+
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:
98
+
99
+ ```
100
+ fun-ci: testing 3f9c2ab. Verdict: fun-ci wait 3f9c2ab --need all
101
+ ```
102
+
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).
106
+
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 |
116
+
117
+ A failed stage comes with its evidence. `status` and `wait` end with a digest and the command that shows the rest:
118
+
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
+ ```
133
+
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`.
135
+
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
156
+ ```
157
+
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
188
+
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
+ ```
206
+
207
+ `fun-ci --help` lists every option.
208
+
209
+ ## Upgrading to 2.0
210
+
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)).
216
+
217
+ The whole list is in [CHANGELOG.md](CHANGELOG.md).
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ bundle install
223
+ bundle exec rake # the gate: tests, the renderer's tests, the binary contract, rubocop, clippy
224
+ ```
225
+
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.
227
+
228
+ ## License
229
+
230
+ MIT
data/exe/fun-ci ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/fun_ci"
5
+ require_relative "../lib/fun_ci/cli"
6
+
7
+ exit FunCi::Cli.run(ARGV)
@@ -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