samagotchi 0.33.0 → 0.35.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -1
  3. data/README.md +6 -1
  4. data/bin/chi +3 -0
  5. data/docs/broadcast.md +129 -0
  6. data/docs/cli.md +8 -0
  7. data/docs/configuration.md +2 -0
  8. data/docs/context.md +5 -0
  9. data/docs/guardrails.md +35 -3
  10. data/docs/hooks.md +25 -4
  11. data/docs/memory.md +17 -1
  12. data/docs/plugins.md +78 -3
  13. data/docs/releasing.md +1 -1
  14. data/docs/sessions.md +15 -9
  15. data/lib/samagotchi/bridge/card_store.rb +1 -1
  16. data/lib/samagotchi/bridge/turn_notice.rb +1 -1
  17. data/lib/samagotchi/broadcast/recipients.rb +63 -0
  18. data/lib/samagotchi/broadcast/scope_card.rb +94 -0
  19. data/lib/samagotchi/broadcast/tags.rb +154 -0
  20. data/lib/samagotchi/broadcast_command.rb +228 -0
  21. data/lib/samagotchi/bundles/coordinator/manifest.yml +14 -0
  22. data/lib/samagotchi/bundles/coordinator/plugin.rb +114 -0
  23. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +60 -0
  24. data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
  25. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +11 -0
  26. data/lib/samagotchi/bundles/guardrails/manifest.yml +2 -2
  27. data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
  28. data/lib/samagotchi/bundles/mcp/plugin.rb +10 -11
  29. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +32 -12
  30. data/lib/samagotchi/bundles/source-links/manifest.yml +4 -3
  31. data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
  32. data/lib/samagotchi/bundles/system/delegated.md +1 -1
  33. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  34. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  35. data/lib/samagotchi/bundles/system/self_map.md +8 -2
  36. data/lib/samagotchi/children_status.rb +143 -0
  37. data/lib/samagotchi/config.rb +6 -0
  38. data/lib/samagotchi/cut_policy.rb +58 -0
  39. data/lib/samagotchi/engine.rb +45 -108
  40. data/lib/samagotchi/engine_peers.rb +30 -0
  41. data/lib/samagotchi/git_head.rb +24 -0
  42. data/lib/samagotchi/guardrail_wiring.rb +27 -2
  43. data/lib/samagotchi/guardrails/approvals.rb +7 -0
  44. data/lib/samagotchi/guardrails/child_boundary.rb +103 -0
  45. data/lib/samagotchi/guardrails/context.rb +32 -0
  46. data/lib/samagotchi/guardrails/shared_git.rb +124 -0
  47. data/lib/samagotchi/guardrails/verdict.rb +4 -3
  48. data/lib/samagotchi/guardrails.rb +1 -0
  49. data/lib/samagotchi/hooks/registry.rb +11 -5
  50. data/lib/samagotchi/kernel_loop.rb +9 -22
  51. data/lib/samagotchi/llm/chat_loop.rb +11 -22
  52. data/lib/samagotchi/log_subscriber.rb +1 -1
  53. data/lib/samagotchi/memory_bundle/builder.rb +14 -1
  54. data/lib/samagotchi/memory_bundle/installer.rb +6 -2
  55. data/lib/samagotchi/memory_bundle/manifest.rb +26 -5
  56. data/lib/samagotchi/memory_bundle/provenance.rb +2 -1
  57. data/lib/samagotchi/memory_paths.rb +10 -0
  58. data/lib/samagotchi/note_command.rb +13 -13
  59. data/lib/samagotchi/note_delivery.rb +42 -0
  60. data/lib/samagotchi/plugin/api.rb +2 -2
  61. data/lib/samagotchi/plugin/context.rb +4 -4
  62. data/lib/samagotchi/plugin/sessions.rb +45 -1
  63. data/lib/samagotchi/process_group.rb +2 -5
  64. data/lib/samagotchi/session_manager.rb +4 -3
  65. data/lib/samagotchi/sessions_command.rb +3 -2
  66. data/lib/samagotchi/steer_cut.rb +125 -0
  67. data/lib/samagotchi/system_prompt.rb +21 -8
  68. data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -2
  69. data/lib/samagotchi/terminal_ui/status_row.rb +23 -2
  70. data/lib/samagotchi/terminal_ui.rb +14 -0
  71. data/lib/samagotchi/tool_declarations.rb +9 -5
  72. data/lib/samagotchi/tools/builtins.rb +2 -2
  73. data/lib/samagotchi/tools/delegate.rb +103 -14
  74. data/lib/samagotchi/tools/delegate_wait.rb +55 -13
  75. data/lib/samagotchi/tools/send_note.rb +5 -11
  76. data/lib/samagotchi/turn_note.rb +9 -1
  77. data/lib/samagotchi/version.rb +1 -1
  78. data/lib/samagotchi/web/app.rb +3 -0
  79. data/lib/samagotchi/web/capabilities.rb +19 -0
  80. data/lib/samagotchi/web/public/activity.js +26 -6
  81. data/lib/samagotchi/web/public/app.js +515 -288
  82. data/lib/samagotchi/web/public/card.js +12 -3
  83. data/lib/samagotchi/web/public/command_view.js +2 -3
  84. data/lib/samagotchi/web/public/families_view.js +171 -0
  85. data/lib/samagotchi/web/public/format.js +52 -0
  86. data/lib/samagotchi/web/public/index.html +94 -21
  87. data/lib/samagotchi/web/public/live_timing.js +154 -0
  88. data/lib/samagotchi/web/public/live_turn.js +44 -0
  89. data/lib/samagotchi/web/public/sessions_list.js +228 -32
  90. data/lib/samagotchi/web/public/strip.js +5 -2
  91. data/lib/samagotchi/web/public/turn_html.js +107 -0
  92. data/lib/samagotchi/web/public/turn_model.js +5 -4
  93. data/lib/samagotchi/web/public/turn_view.js +43 -129
  94. data/lib/samagotchi/web/session_summary.rb +4 -0
  95. data/lib/samagotchi/worker.rb +173 -309
  96. data/lib/samagotchi/worker_inbound.rb +251 -0
  97. data/lib/samagotchi/worker_wakes.rb +120 -0
  98. metadata +24 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca8acdcb161f8d093ab1c860913dd3a1e826083bcfff0165039847e38a26c5ad
4
- data.tar.gz: 5c286ada590d84b793e95d9f2dec051132462469aace12c0faf1da0f1c4e3fdf
3
+ metadata.gz: b598dac8dadadd4dd37861ed690d09d09926520c2295bc998def4d75c3716ea7
4
+ data.tar.gz: 9d30ebb820024929bbb2dc1267011af7eb5710b33a4ba9625489a74d111edfbd
5
5
  SHA512:
6
- metadata.gz: 394a942740165c9ea457b3a3043a070ef9338a897c6eb41c387b1101e8426c8e21c938757868d87493216324d84441165a82601b796801be90feef5c8e6c7091
7
- data.tar.gz: 29d24ccdb0bd41d027ccdaf4b8c6e0a191d292b636fc3af1e27e994f30b1331192f59d3bf6aa5d858e78ec5d84d32d8760f2df03c8dace2feaad1ac87783c1c6
6
+ metadata.gz: 2f40ed32d227d1017bc7abcba8ef40f5e38f2f7dd7876aa5f78a9290e5443489b8109183655a151a1345fef1fa3f3f49e5deafbac38059cdba3bec70b0585d71
7
+ data.tar.gz: 55aca343a9dc45e78791863286768c356e815dc1b9783993f2c4477edb991883a3c88ae208dd642137cab4c6ead58fc1d49228f201fefcd6878dd421c719af70
data/CHANGELOG.md CHANGED
@@ -8,6 +8,94 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.35.0] - 2026-10-06
12
+
13
+ ### Added
14
+
15
+ - `chi broadcast -m TEXT` (or stdin) shares a note with every session it may concern, without picking them: your
16
+ own sessions a worker or a chi REPL runs, or that ended a turn in the last 8 hours (`broadcast.active_hours`),
17
+ delegate children and scratch sessions left out. One gets it when the note shares a tag with it: a ticket id
18
+ (`broadcast.ticket_pattern`) in its branch or your prompts there, a pull request attached to it, a link. The rest
19
+ are listed as skipped; `--all` reaches every one, `--dry-run` shows who would get it and why. The model mentions a
20
+ broadcast briefly when it affects its current work and doesn't act on it unless asked. For you, not for an agent:
21
+ refused inside a session, and the guardrails bundle's `chi-broadcast` rule asks. See
22
+ [docs/broadcast.md](docs/broadcast.md).
23
+ - `chi sessions list --format json` has `delegate`: true for a child the `delegate` tool started, false for a fork.
24
+ - A parent session shows its delegate children at a glance: a `⑂ 3` chip in its web info bar (how many run, wait,
25
+ are done or failed in the tooltip; the waiting ones in the attention colour; a click opens all sessions), and
26
+ `⑂ 2 running · 1 waiting` in the terminal's status row.
27
+ - A hook's `event[:notify]` and a plugin's `ctx.notify` take `fallback_for: :display`: the line repeats what the
28
+ answer's display (`event[:present]`) shows, so a UI that renders the display leaves it out. The web with markdown
29
+ on does; the REPL, the attached TUI, `chi -p` and the web with markdown off still show it. See
30
+ [docs/hooks.md](docs/hooks.md#what-a-hook-can-do-the-runtime).
31
+ - Web, all sessions: each card has an archive button (unarchive on an archived one), and Select picks many cards
32
+ (click, Shift-click a range, "Select all shown") for Archive N / Unarchive M. A session with a running turn is
33
+ skipped with the reason on its card; one toast at the end has Undo.
34
+
35
+ ### Changed
36
+
37
+ - source-links 0.4.0 (needs chi 0.35.0; run `chi update`): with markdown on, the web no longer shows the
38
+ `sources:` line under an answer that links every ref it names; it still shows when a ref is only in code or
39
+ markdown is off, and the terminals print it as before.
40
+ - `chi note --source broadcast` is refused: that source is `chi broadcast`'s.
41
+ - Web: a parent's delegates fold into its card, on the strip and in all sessions, instead of being cards of their
42
+ own. A `▸ 3 delegates · 1 live · 1 waiting` chip opens their list (in the card in all sessions, a popover on the
43
+ strip); it opens by itself while one of them waits on you, and the card then wears the warning colour. A family
44
+ takes one strip slot, a search finds a parent by its delegates, and select mode picks cards. A fork and a delegate
45
+ whose parent isn't listed stay cards of their own.
46
+ - guardrails 0.7.0: the `chi-broadcast` rule asks before an agent runs `chi broadcast`.
47
+
48
+ ### Fixed
49
+
50
+ - Web: a tool row shows how long the call took while you watch the turn, as it does after a reload.
51
+
52
+ Update with `chi update` (guardrails 0.7.0, source-links 0.4.0).
53
+
54
+ ## [0.34.0] - 2026-10-06
55
+
56
+ ### Added
57
+
58
+ - `delegate cwd:` starts a child in another folder of the session's repository: a worktree the model made with
59
+ `git worktree add`, or a subfolder. Another repository is refused.
60
+ - Plugins: `ctx.sessions.children` lists the session's children (state, branch, last reply and whether the parent
61
+ was given it), and `ctx.sessions.stop(id)` stops one of its own children.
62
+ - Bundles: a `files:` entry in `manifest.yml` may carry a `description:`, which the install writes into the
63
+ memory's `index.md` line.
64
+ - The `coordinator` bundle: chi coordinates parallel work. `/coordinate <goal>` (or asking for it) has chi split
65
+ the work into tasks, each in a child session in its own git worktree, check each report and ask you before each
66
+ merge. `/children` shows the session's children: state, branch, last reply and whether it was reported, with a
67
+ Stop button each.
68
+ - Guardrails: a delegate child asks before it changes anything outside its folder (its worktree): a write there,
69
+ git that changes the main checkout or a sibling worktree, a command run from or naming one of them. It's built in,
70
+ in every mode and without the guardrails bundle, and answered once or for the child's session.
71
+
72
+ ### Changed
73
+
74
+ - The `delegate` and `delegate_result` tools tell the model to start a child with `wait: false` unless its turn
75
+ can't go on without the reply, then end its turn or keep talking with you: the reply comes back by itself as a
76
+ delegate report. `delegate_result` is only for a reply the turn needs now.
77
+
78
+ ### Fixed
79
+
80
+ - A wake turn (a context source's change, a delegate's report) that fails no longer tells the model the user's
81
+ message went unanswered: it says the wake wasn't answered, and that no other wake turn starts until you write.
82
+ For a delegate's report it adds that the report comes again with your next message.
83
+ - A delegate wait that timed out or was canceled says the child's reply still comes as a delegate report, instead
84
+ of steering the model into waiting again with `delegate_result`; so does a question's result.
85
+ - `delegate` with `wait: false` in a session that gets no delegate reports (a `--no-shared` REPL, `-p`) says
86
+ `delegate_result` waits for the reply, instead of promising a report that never comes.
87
+ - In a linked git worktree the system prompt no longer names the repository's main checkout ("Project root"), which
88
+ led a model to work and commit there: it names this checkout's top and says to work only in it. Project memories
89
+ and session lists still follow the repository.
90
+ - `delegate` refused for too many running children tells a session that gets delegate reports to end its turn (a
91
+ report frees a slot), instead of to wait with `delegate_result`.
92
+ - At a repository's root the system prompt names the checkout the project's commands run in, and calls the project
93
+ memories folder your notes about the project, not its files: a model no longer goes looking for the repository
94
+ in the memories folder.
95
+
96
+ Update with `chi update`: it installs the new `coordinator` bundle for the `dev` profile (dev 0.3.0) and updates mcp
97
+ (0.4.5: stops an MCP server through chi's own process-group handling).
98
+
11
99
  ## [0.33.0] - 2026-10-06
12
100
 
13
101
  ### Changed
@@ -1663,7 +1751,9 @@ and long-lived sessions.
1663
1751
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
1664
1752
  hotkey panel that send selected text or the clipboard to your sessions.
1665
1753
 
1666
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.33.0...HEAD
1754
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.35.0...HEAD
1755
+ [0.35.0]: https://github.com/dm1try/samagotchi/compare/v0.34.0...v0.35.0
1756
+ [0.34.0]: https://github.com/dm1try/samagotchi/compare/v0.33.0...v0.34.0
1667
1757
  [0.33.0]: https://github.com/dm1try/samagotchi/compare/v0.32.0...v0.33.0
1668
1758
  [0.32.0]: https://github.com/dm1try/samagotchi/compare/v0.31.0...v0.32.0
1669
1759
  [0.31.0]: https://github.com/dm1try/samagotchi/compare/v0.30.0...v0.31.0
data/README.md CHANGED
@@ -120,6 +120,7 @@ chi web --open # web UI: this project's sessions (--s
120
120
  chi web --web-host lan # the web UI on your phone too: scan the QR code it prints
121
121
  chi sessions list # this project's saved sessions (--scope=all: every one)
122
122
  pbpaste | chi note --source slack <id> # background context for a session (no turn)
123
+ chi broadcast -m "payments API is down" # the same for every session it concerns (a shared ticket, PR or link)
123
124
  chi context add <PR URL> <id> # attached context: chi keeps it fresh, the agent hears when it changes
124
125
  pbpaste | chi send -m "same bug?" <id> # a message to a session, the clipboard quoted above it
125
126
  chi send --new --wait -m "review feat/x" # a new session you can watch in the web; prints the answer
@@ -145,7 +146,10 @@ never takes orders from. The web and the attached terminal show it as a dim
145
146
  a note reaches (`id<TAB>description`). Agents can do the same: `list_sessions`
146
147
  finds another session, `send_note` tells it something, and `delegate` hands a
147
148
  task to a child session that runs in parallel and reports back only its final
148
- reply (a normal session: `chi --attach <id>` steers it).
149
+ reply (a normal session: `chi --attach <id>` steers it). With the `coordinator`
150
+ bundle (in `dev`), `/coordinate <goal>` has chi split the work into tasks, each
151
+ in a child session in its own git worktree, check each report and ask you
152
+ before each merge; `/children` shows the children and their state.
149
153
 
150
154
  `chi send` is the other half: the text goes in as your message, the same as
151
155
  typing it in the attached terminal or the web composer, and a turn runs. Piped
@@ -199,6 +203,7 @@ once, for the session, for the repo, or for the whole rule in the repo.
199
203
  - [Memory](docs/memory.md): scopes and model-specific overlays
200
204
  - [Sessions](docs/sessions.md): storage, retention, `chi sessions`
201
205
  - [Attached context](docs/context.md): `chi context`, sources, scopes, waking, the github-pr bundle, safety
206
+ - [Broadcast](docs/broadcast.md): `chi broadcast`, who a note reaches, tags, what a session gets
202
207
  - [chi as a sub-agent](docs/sub-agent.md): `chi send --wait --format json`, `chi answer`, instructions for a parent agent
203
208
  - [Desktop helper](docs/desktop.md): `chi desktop`, the macOS "Send to chi" Service and hotkey
204
209
  - [Guardrails](docs/guardrails.md): allow / ask / deny for tool calls, rules, approvals
data/bin/chi CHANGED
@@ -40,6 +40,8 @@ SUBCOMMANDS = {
40
40
  "sessions" => ["samagotchi/sessions_command", "SessionsCommand"],
41
41
  # a context note for sessions (-m, --source, --all)
42
42
  "note" => ["samagotchi/note_command", "NoteCommand"],
43
+ # a note for every session it may concern (-m, --all, --dry-run)
44
+ "broadcast" => ["samagotchi/broadcast_command", "BroadcastCommand"],
43
45
  # live external text attached to sessions (add, push, ls, show, rm, mute)
44
46
  "context" => ["samagotchi/context_command", "ContextCommand"],
45
47
  # a message into sessions as the user's (-m)
@@ -95,6 +97,7 @@ parser = OptionParser.new do |opts|
95
97
  chi web [--port PORT] [--open] [--scope=all] the web UI
96
98
  chi sessions <list|stop|archive|unarchive|delete|prune|clean>
97
99
  chi note [--source NAME] [-m TEXT] ID...|--all background context for sessions
100
+ chi broadcast [-m TEXT] [--all] [--dry-run] a note for every session it may concern
98
101
  chi context <add|push|ls|show|refresh|rm|mute|unmute> live external text attached to sessions
99
102
  chi send [-m TEXT] ID...|--new a message into sessions, as if typed there
100
103
  chi answer ID --question QID --option N answer a question a session waits on
data/docs/broadcast.md ADDED
@@ -0,0 +1,129 @@
1
+ # Broadcast
2
+
3
+ `chi broadcast` shares one piece of information with every session it may
4
+ concern, without picking them: "payments API returns 500 since 14:00 (PAY-123)",
5
+ "the PRD for checkout v2 changed: no guest checkout". Each session it reaches
6
+ gets it as a [context note](sessions.md#context-notes) from `broadcast`, and
7
+ that session's agent decides whether it affects its work. Sessions it doesn't
8
+ concern never see it.
9
+
10
+ ```sh
11
+ chi broadcast -m "payments API returns 500 since 14:00 (PAY-123)"
12
+ pbpaste | chi broadcast # the note from stdin
13
+ chi broadcast --dry-run -m "…" # who would get it and why; nothing is delivered
14
+ chi broadcast --all -m "deploy freeze until 18:00" # every recipient, tag or not
15
+ ```
16
+
17
+ To tell one session or a few you picked, use `chi note ID...` instead.
18
+
19
+ ## Who it reaches
20
+
21
+ The recipients are your own sessions, in every project:
22
+
23
+ - a worker or a chi REPL runs them now, or their last turn ended within
24
+ `broadcast.active_hours` (default 8). A session with no turn yet counts
25
+ by its last save;
26
+ - not a delegate child: its parent relays what concerns it (the
27
+ `coordinator`'s builders included). A fork is your session and counts;
28
+ - not a `chi scratch` session, not an archived one, and not a test run
29
+ (unless `chi broadcast` itself runs as one, `SAMAGOTCHI_ENV=test`).
30
+
31
+ A session open in a plain chi REPL (`--no-shared`) can't take notes: it is
32
+ listed as skipped, "open in a chi REPL".
33
+
34
+ ## Tags: which sessions it concerns
35
+
36
+ A recipient gets the note when the note and the session share a tag:
37
+
38
+ | Tag | In the note | About the session |
39
+ |---|---|---|
40
+ | ticket | an id like `PAY-123` (`broadcast.ticket_pattern`) | its branch (any case: `feat/pay-123-retry`), your prompts there |
41
+ | pr | `PR #42`, a GitHub pull request URL | an attached `pr-42` source (the github-pr bundle), a PR URL in your prompts there |
42
+ | link | a URL, by host and path | a URL in your prompts there or in an attached context source's hint |
43
+
44
+ A link is compared without its scheme, `www.`, query, fragment or trailing
45
+ slash; a bare host (`https://github.com/`) is no tag. A PR URL names its repo,
46
+ so `acme/shop#42` doesn't match `acme/other#42`; `PR #42` with no repo matches
47
+ pull request 42 of any repo. Words the default ticket pattern would take that
48
+ are no ticket (`UTF-8`, `SHA-256`, `ISO-8601`, `GPT-4`, …) are left out.
49
+
50
+ A session that shares no tag is skipped for now, listed as "no tag match (not
51
+ checked: no triage yet)": a small triage model that reads the note and each
52
+ session's scope card is the next step. Until then, `--all` reaches them all.
53
+
54
+ ## What it prints
55
+
56
+ One line per recipient, the ones that got it first, then a summary:
57
+
58
+ ```
59
+ broadcast "payments API returns 500 since 14:00 (PAY-123)"
60
+ 3f2a1c9e delivered ticket PAY-123 matches (branch)
61
+ 91ab02c4 delivered link notion.so/team/checkout-v2 matches (messages); waits for its next start
62
+ c0ffee12 skipped no tag match (not checked: no triage yet)
63
+ d00dad00 skipped open in a chi REPL
64
+ delivered 2 · skipped 2
65
+ ```
66
+
67
+ A session a worker runs adds the note within a few seconds (after a running
68
+ turn); one with no worker gets it at its next start ("waits for its next
69
+ start"). Nothing starts a turn anywhere.
70
+
71
+ `--dry-run` prints the note's tags, each recipient's verdict ("would get it" or
72
+ "skipped") and its scope card: what chi knows about it from local state, and
73
+ what a triage model will read.
74
+
75
+ ```
76
+ 3f2a1c9e would get it ticket PAY-123 matches (branch)
77
+ project: shop (folder ../shop-pay, branch feat/pay-123-retry)
78
+ title: shop-pay · now the retry spec
79
+ tags: ticket PAY-123 · pr acme/shop#42
80
+ started: Fix the payments retry …
81
+ recap: Fixing the payments retry. Specs next.
82
+ recent: now the retry spec
83
+ ```
84
+
85
+ Exit status: 0 when it ran (skipped sessions included), 1 when it was refused,
86
+ no session is active or a delivery failed, 2 for a usage error.
87
+
88
+ ## What a session gets
89
+
90
+ The note is your text, then one line chi adds:
91
+
92
+ ```
93
+ [CONTEXT NOTE from broadcast, 14:20]
94
+ payments API returns 500 since 14:00 (PAY-123)
95
+ (Shared by your user on 2026-10-06 14:20 with the sessions it may concern; it reached you because ticket PAY-123 matches your branch.)
96
+ [END NOTE]
97
+ ```
98
+
99
+ With `--all` the line says "with every active session". The date is there
100
+ because a session with no worker may read it days later. The system prompt
101
+ tells the model what a note from `broadcast` is: if it affects its current
102
+ work, it says so briefly in its next answer, otherwise it ignores it, and it
103
+ doesn't act on it unless you ask. Other notes keep their rule (background,
104
+ not mentioned on their own). `chi note --source broadcast` is refused, so a
105
+ plain note can't pass for a broadcast.
106
+
107
+ The note and its line together are capped at 16 KiB like any note; the text
108
+ may take up to 15872 bytes.
109
+
110
+ ## For you, not for an agent
111
+
112
+ `chi broadcast` refuses to run inside a chi session (where
113
+ `SAMAGOTCHI_PARENT_SESSION` is set): "chi broadcast is for your user, not an
114
+ agent". With the guardrails bundle, the `chi-broadcast` rule also asks before
115
+ any shell command that runs it, once at a time, and only you may allow it
116
+ (a parent agent's `chi answer` can't). See [Guardrails](guardrails.md).
117
+
118
+ ## Settings
119
+
120
+ | Setting | Default | What it does |
121
+ |---|---|---|
122
+ | `broadcast.active_hours` | `8` | A session with no worker or REPL counts when its last turn ended within this many hours. |
123
+ | `broadcast.ticket_pattern` | `\b[A-Z][A-Z0-9]+-\d+\b` | A Ruby regex for ticket ids; an invalid one warns and the default is used. |
124
+
125
+ ```yaml
126
+ broadcast:
127
+ active_hours: 12
128
+ ticket_pattern: '\b(?:PAY|OPS)-\d+\b'
129
+ ```
data/docs/cli.md CHANGED
@@ -790,11 +790,19 @@ changes:
790
790
 
791
791
  ```
792
792
  status> model=Qwen3.6-35B | ↳ 3f2a1c9e | ctx=12.3% (under20) | mem: notes, cli_usage | muted: gh-helper
793
+ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | mem: skill_coordinator
793
794
  ```
794
795
 
795
796
  - `model=`: the model in use, `(default: …)` beside it when it isn't the config's default, and
796
797
  `model=<served> (served; asked <name>)` when the server said it served another model.
797
798
  - `↳ <id>`: the session that delegated this one.
799
+ - `⑂ …`: this session's delegate children (forks aren't counted): how many run and how many wait
800
+ for an answer; only the counts there are, nothing when none does (`/children` has the rest:
801
+ done, failed, whether a reply was reported). The terminal reads them from the children's files
802
+ (the delegates in the session's `delegates.json`): an attached one when it joins, when a turn
803
+ ends and when a delegate's report merges into a turn, the REPL whenever it draws the row. A
804
+ child that starts waiting on an approval (which doesn't wake the parent) shows at the next of
805
+ those.
798
806
  - `ctx=`: the kernel's context estimate and its bucket, updated during a turn (on hosts that
799
807
  report none, `api: openai`, at the turn's end).
800
808
  - `mem:`: the memories the session read, with its `--memory` list; `muted:` its `--mute` list.
@@ -882,6 +882,8 @@ described in their own sections.
882
882
  | `recap.timeout` | `30` | yes | Seconds a recap request may take. |
883
883
  | `recap.min_user_turns` | `2` | yes | Prompts a session needs before it gets a recap. |
884
884
  | `recap.sentences` | `2-4` | yes | Recap length, `N` or `N-M` (1–10). |
885
+ | `broadcast.active_hours` | `8` | | `chi broadcast` reaches a session a worker or a chi REPL runs, or whose last turn ended within this many hours. See [Broadcast](broadcast.md). |
886
+ | `broadcast.ticket_pattern` | `\b[A-Z][A-Z0-9]+-\d+\b` | | A Ruby regex for ticket ids; one in the note and in a session's branch (any case) or prompts delivers the broadcast there. See [Broadcast](broadcast.md). |
885
887
  | `session.shared` | `true` | | Plain `chi` runs its session in a worker and attaches; `--no-shared` per run. |
886
888
  | `session.idle_exit_minutes` | `30` | yes | An unused worker exits after this; `0` = never. |
887
889
  | `session.keep_empty` | `false` | | Keep sessions nothing happened in. |
data/docs/context.md CHANGED
@@ -154,6 +154,11 @@ update note, whose last line becomes:
154
154
  Every UI labels the turn `context <name> changed`, and the web's
155
155
  notifications count it as a finished turn however short.
156
156
 
157
+ A wake turn that fails goes back to before it and keeps the update as a
158
+ plain note; the model reads `[SYSTEM: the previous turn failed before any
159
+ answer: … The wake turn for the change in attached context <name> was not
160
+ answered; chi starts no other wake turn until the user writes.]`.
161
+
157
162
  ## GitHub PRs: the github-pr bundle
158
163
 
159
164
  ```sh
data/docs/guardrails.md CHANGED
@@ -5,13 +5,14 @@ loops (native and chat hosts). The verdict is **allow**, **ask** or
5
5
  **deny**; the strictest vote wins, and a deny can't be undone by a later
6
6
  voter. Of two asks the first one names the rule, except a rule only the
7
7
  user may allow (`chi-config`, `chi-hooks`, `shell-touches-chi`,
8
- `chi-context-cmd`): it takes over, and only the scopes both asks offer
8
+ `chi-context-cmd`, `chi-broadcast`): it takes over, and only the scopes both asks offer
9
9
  are offered.
10
10
 
11
11
  Who votes, in order:
12
12
 
13
13
  1. `before_tool_call` hooks (Ruby; see [Hooks](hooks.md#guardrails-from-a-hook)).
14
- 2. Core checks: a required guardrail that failed to load, then protected paths.
14
+ 2. Core checks: a required guardrail that failed to load, then protected paths, then (a delegate child only)
15
+ [the child boundary](#a-delegate-childs-boundary).
15
16
  3. YAML rules: `config.yml`'s `guardrails:` section, then installed bundles' rule files (by bundle name).
16
17
 
17
18
  The UI shows the tool line first, then the approval under it.
@@ -189,6 +190,35 @@ Built in, for `write`, `edit` and `memory_write` (symlinks resolved):
189
190
  `execute` can still reach them; the guardrails bundle asks about shell
190
191
  commands that name them.
191
192
 
193
+ ## A delegate child's boundary
194
+
195
+ Built in, for [delegate](sessions.md#delegating) children only, in every
196
+ mode (also with `guardrails.enabled: false`) and without the guardrails
197
+ bundle: a child asks before it changes anything outside its folder, the work
198
+ tree top of the folder it was started in (a worktree from `delegate cwd:`,
199
+ else the parent's checkout). Rule `child-boundary`, source `core`; it asks when
200
+
201
+ - `write` or `edit` names a path outside it (tmp dirs and memory files don't count);
202
+ - `execute` or `task_create` runs mutating git in another dir (`git -C`, `cd`,
203
+ `GIT_DIR`: what `git: outside_repo` matches), or a command that isn't
204
+ read-only starts outside it (its `cwd:`) or names a path in the repository's
205
+ other checkouts (the main one and sibling worktrees, read from
206
+ `<common git dir>/worktrees/*/gitdir`) or in its common git dir;
207
+ - `execute` or `task_create` runs git that changes what every checkout shares,
208
+ even from its own worktree: a `git config` write (not `--get`, `--list`,
209
+ `--worktree`), `update-ref`, `worktree add|remove|move|prune`, `stash
210
+ drop|clear|pop`, creating or deleting a tag, and deleting, forcing or
211
+ overwriting a branch other than its own.
212
+
213
+ Read-only commands (`git -C ../app log`, `cat ../app/x`) and paths elsewhere
214
+ (`/usr/bin`, `~/.gem`) never ask. The answer is once or for the child's
215
+ session only: a repo or rule answer would hold in every worktree, for every
216
+ child, so a stored repo or rule approval doesn't let it through either. Its
217
+ ask goes before the rules, so with the guardrails bundle in strict mode the
218
+ card still names `child-boundary`. The parent (never a delegate), forks and
219
+ ordinary sessions don't get it. Like the rules it reads command text: a
220
+ `sh -c` body, a script or `$(…)` isn't looked into.
221
+
192
222
  ## Rules in config.yml
193
223
 
194
224
  ```yaml
@@ -397,7 +427,9 @@ hooks, approvals, bundles or attached context sources, or a `.git/hooks`
397
427
  (`shell-touches-chi`, `touches: chi_dirs`), unless they only read
398
428
  (`skip_read_only`); `chi context add … --cmd` (`chi-context-cmd`, once at a
399
429
  time: a command chi will run every few minutes outside the guardrails; see
400
- [Attached context](context.md#safety)); and answering chi's
430
+ [Attached context](context.md#safety)); `chi broadcast` (`chi-broadcast`,
431
+ once at a time and only by the user: it is the user's command, which also
432
+ refuses to run inside a session; see [Broadcast](broadcast.md)); and answering chi's
401
433
  questions around `chi answer`: `chi --attach` or `chi -p` with stdin from a
402
434
  pipe, a here-string or a file (`chi-answer-piped`), and `curl`/`wget` to a
403
435
  session's `/answer` route (`chi-answer-http`), both once or for the session.
data/docs/hooks.md CHANGED
@@ -82,6 +82,8 @@ class Watchful
82
82
  when :after_generation
83
83
  # One line in the REPL, the attached TUI and the web ("<bundle>> text",
84
84
  # or "hook> text" for a config hook); level: :warn colours it.
85
+ # fallback_for: :display marks a line a UI that renders the answer's
86
+ # display may leave out (below).
85
87
  event[:notify].call("the model repeated itself", level: :warn)
86
88
  when :before_tool_call
87
89
  # A single-select question through the question flow (REPL, attached
@@ -114,6 +116,21 @@ end
114
116
  known-names)` for a bundle hook, `audit.rb (config)` for a config hook,
115
117
  `turn hook` for one registered at runtime.
116
118
 
119
+ `event[:notify]` also takes `fallback_for:`, what the line stands in for, so
120
+ that a UI that already shows it can leave the line out. The one value is
121
+ `:display`: the line repeats what the hook's
122
+ [`event[:present]`](#presenting-the-answer-display-only) display shows (its
123
+ links), for a UI that doesn't render that display. The hook never names a UI;
124
+ each decides by what it renders. The web with markdown on (`web.markdown` and
125
+ the commonmarker gem) leaves such a line out, live and after a reload; with
126
+ markdown off it shows, as it does in the REPL, the attached TUI and
127
+ `chi -p`, which print the answer as text. The log keeps the line with
128
+ `fallback_for=display`. Another value, or none, shows the line everywhere.
129
+ Mark a line only when the display really shows all of it (source-links marks
130
+ its note only when the answer links every URL the note names). It needs chi
131
+ 0.35.0: an older chi's `event[:notify]` raises `ArgumentError` on the keyword,
132
+ so a bundle that passes it sets `requires_chi: ">= 0.35.0"`.
133
+
117
134
  A steer is saved in the session as `{role: "user", kind: "steer", source:
118
135
  "<bundle>", content: "…"}`, its text raw. The model reads it as a user turn
119
136
  led by one line naming the sender: `[Steer from the <bundle> plugin,
@@ -420,7 +437,7 @@ stripped of surrounding whitespace, except file text, an edit's
420
437
  | `list_sessions` | — | — | `cwd` |
421
438
  | `send_note` | `text` | — | `session` |
422
439
  | `context_read` | `name` | — | `offset`, `limit` |
423
- | `delegate` | `task` | — | `model`, `session`, `wait`, `timeout` |
440
+ | `delegate` | `task` | — | `model`, `session`, `cwd`, `wait`, `timeout` |
424
441
  | `delegate_result` | — | — | `session`, `timeout` |
425
442
  | `ask_user_question` | `question` | — | `question`, `options`, `header`, `multi_select`, `allow_freeform` |
426
443
 
@@ -504,8 +521,8 @@ sources: JIRA JIRA-123 → https://myjira.com/browse/JIRA-123, JIRA JIRA-10 →
504
521
  ```
505
522
 
506
523
  The note is **not part of the conversation**: it is an event shown to the
507
- user, never stored in the session file. A UI replays it while the session's
508
- worker lives (a page reload keeps it; a stopped worker loses it). Only the
524
+ user, never stored in the session file. A UI replays it with the session's
525
+ cards (a page reload keeps it, after the worker stopped too). Only the
509
526
  model's final answer is scanned (the last `role: "model"` message), and only
510
527
  the first 20 000 characters of it. A ref that is already a link is skipped —
511
528
  inside a bare URL (`https://x.com/JIRA-123`), in a markdown link's target, or
@@ -543,7 +560,11 @@ text stays as it was, and the links survive a reload and a stopped worker
543
560
  apply, and a ref in code (a `` `span` `` or a fenced block) or anywhere in a
544
561
  markdown link is left as it is; past the first 20 000 characters the answer
545
562
  is unchanged. The terminals see only the line; `note: false` drops it and
546
- keeps the web links.
563
+ keeps the web links. With markdown on, the web leaves the line out when the
564
+ answer links every URL it names, since it would only repeat them as plain
565
+ text ([`fallback_for: :display`](#what-a-hook-can-do-the-runtime)); it shows
566
+ when it names one the answer doesn't link (a ref only in code, two sources on
567
+ one ref) and whenever markdown is off. Bundle 0.4.0 needs chi 0.35.0 for that.
547
568
 
548
569
  The `prefix:` form compiles to `\b<prefix>-(\d+)\b` and the URL is
549
570
  `base_url` + the full ref text (`JIRA-123`). The `pattern:` form takes a
data/docs/memory.md CHANGED
@@ -102,6 +102,22 @@ for a file another bundle installed.
102
102
  `- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system —
103
103
  …`. `memory_write`, `write` and `edit` keep it. A bundle installed by chi
104
104
  0.16 or earlier gets it on its next install or upgrade.
105
+ - **A bundle can give a memory its index description.** A `files:` entry
106
+ may be a mapping instead of a sha line:
107
+
108
+ ```yaml
109
+ files:
110
+ identity.md: sha256:…
111
+ skill_coordinator.md:
112
+ sha256: sha256:…
113
+ description: "Coordinate parallel work: …"
114
+ ```
115
+
116
+ The install writes that description into the memory's `index.md` line
117
+ (after `— `), so the model can tell what a shipped skill is for from the
118
+ index alone. The bundle's record keeps it, `chi bundle build` writes the
119
+ mapping back, and `rake bundles:sha` refreshes the nested sha. A file
120
+ with a plain sha line keeps the description its line already has.
105
121
  - **Uninstall moves, it doesn't delete.** The bundle's memory files go to
106
122
  `$XDG_CONFIG_HOME/samagotchi/memories/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/`,
107
123
  and the output names them and the dir:
@@ -133,7 +149,7 @@ profiles, meta bundles that install a set of them:
133
149
  | profile | bundles |
134
150
  |---|---|
135
151
  | `core` | loop-guard, check-in, guardrails: the recommended safety set |
136
- | `dev` | known-names, mcp, btw, skills, source-links |
152
+ | `dev` | known-names, mcp, btw, skills, source-links, github-pr, coordinator |
137
153
 
138
154
  `chi bootstrap` installs `core` and, on a terminal, offers `dev`. An existing
139
155
  install gets neither by itself: `chi bundle install core` (or `dev`).
data/docs/plugins.md CHANGED
@@ -488,7 +488,7 @@ full label, `plugin.rb (bundle my-bundle)`.
488
488
  | `ctx.log` | `ctx.log.info(:event, key: value)`: debug-log records tagged `plugins`, with `bundle=<bundle>` |
489
489
  | `ctx.messages` | the conversation, as a frozen copy, without the system prompt. While a turn runs, a session worker's (attached, web) adds that turn so far: its prompt, the model's text and the lines merged into it (no tool calls or thinking); the REPL's is the conversation before that turn |
490
490
  | `ctx.messages_partial?` | whether `ctx.messages` leaves out a running turn (the REPL mid-turn), so a plugin can say what its answer is about |
491
- | `ctx.notify(text, level: :info)` | one line to the user, labelled by the bundle (`my-bundle> …`). Every UI shows it, during a turn (a tool, a hook) or between turns (a command) |
491
+ | `ctx.notify(text, level: :info, fallback_for: nil)` | one line to the user, labelled by the bundle (`my-bundle> …`). Every UI shows it, during a turn (a tool, a hook) or between turns (a command). `fallback_for: :display` marks a line that repeats the answer's display (`event[:present]`), which a UI that renders the display leaves out: see [Hooks](hooks.md#what-a-hook-can-do-the-runtime). The keyword needs chi 0.35.0 (it raises on an older chi) |
492
492
  | `ctx.card(title:, body: "", actions: [], level: :info, id: nil)` | a card in every UI, returning its id: see [Cards](#cards) |
493
493
  | `ctx.ask_user(question:, options:, header: nil, allow_freeform: false)` | a single-select question through the question flow: `{selected:, freeform:, selected_indices:}`, or nil (no one to ask, cancelled, bad options) |
494
494
  | `ctx.cancelled?` | whether the running turn was cancelled (a long tool should stop) |
@@ -496,7 +496,7 @@ full label, `plugin.rb (bundle my-bundle)`.
496
496
  | `ctx.stop_turn(reason)` | stop the running turn after a warn notice with the reason; true when it stopped one now. In a `:before_tool_call` block it also denies that call |
497
497
  | `ctx.stop_generation(reason)` | cut the generation that is streaming: the turn goes on and the model is asked again ([hooks.md](hooks.md#watching-the-stream)). Shows nothing: post your own notice. True when it cut one now |
498
498
  | `ctx.ask_model(messages:, prompt:, …)` | a side answer from the session's model: see [Side answers](#side-answers-ctxask_model) |
499
- | `ctx.sessions` | fork, send to and read other sessions: see [Other sessions](#other-sessions-ctxsessions) |
499
+ | `ctx.sessions` | fork, send to, read, list and stop other sessions: see [Other sessions](#other-sessions-ctxsessions) |
500
500
  | `ctx.context` | attach outside text to this session and list it: see [Attached context](#attached-context-ctxcontext) |
501
501
 
502
502
  The Engine itself is never handed to a plugin.
@@ -608,7 +608,9 @@ llama.cpp serves on a native host too.
608
608
  id = ctx.sessions.fork(messages: ctx.messages + [{ role: "user", content: q }, { role: "model", content: a }],
609
609
  title: "btw: #{q}")
610
610
  ctx.sessions.send(id, "go on from here")
611
- ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, messages:}
611
+ ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, messages:}
612
+ ctx.sessions.children # => [{id:, short_id:, state:, branch:, last_reply:, reported:, ...}]
613
+ ctx.sessions.stop(id) # one of this session's own children
612
614
  ```
613
615
 
614
616
  - `fork(messages:, title: nil, prompt: nil)` starts a child session in its
@@ -629,6 +631,23 @@ ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, message
629
631
  - `read(id)` gives a session now: from its worker when one runs (with a
630
632
  running turn so far, `running: true`), else as saved. `messages` has no
631
633
  system prompt.
634
+ - `children(all: false)` lists this session's children, newest first, as
635
+ frozen Hashes: `{id:, short_id:, title:, state:, waiting:, live:,
636
+ delegate:, cwd:, branch:, last_reply:, last_reply_at:, reported:,
637
+ updated_at:, archived:}`.
638
+ - `state` is `waiting` (a question or approval is open; `waiting` says
639
+ which), `running`, `failed` (the last turn failed or the worker
640
+ crashed), `stopped`, `done` (the last turn ended with a reply) or `idle`.
641
+ - `delegate` is true for a `delegate` child, false for a fork. Archived
642
+ children are listed only with `all: true`.
643
+ - `branch` is the branch checked out in the child's folder (read from its
644
+ `HEAD`, a short sha when detached; nil outside git). `last_reply` is the
645
+ first line of its newest reply, and `reported` says whether this session
646
+ was already given that reply (a delegate report or a wait).
647
+ - It reads every session's file: call it on demand, not in a loop.
648
+ - `stop(id)` stops one of this session's own children, as
649
+ `chi sessions stop` does, and waits up to 2 s for its worker to let go. It
650
+ returns the child's id. Any other session raises.
632
651
  - Each raises `Samagotchi::Plugin::Sessions::Error` with the reason.
633
652
 
634
653
  ## Attached context: `ctx.context`
@@ -1120,6 +1139,62 @@ and a summary of what changed in counts, authors and states only; it wakes
1120
1139
  the session for a review requesting changes, checks turning red, or the PR
1121
1140
  merged or closed. See [Attached context](context.md#github-prs-the-github-pr-bundle).
1122
1141
 
1142
+ ## The coordinator bundle
1143
+
1144
+ ```sh
1145
+ chi bundle install coordinator # in the dev profile
1146
+ ```
1147
+
1148
+ chi as a coordinator of parallel work, the way you would run several agents
1149
+ yourself: split the work, one git worktree and child session per task, keep
1150
+ talking while they run, check each report, and merge only with your OK. It
1151
+ works in a session with a worker (plain `chi`, the web). A `--no-shared` REPL
1152
+ or `-p` gets no delegate reports, so the children's replies wouldn't come back
1153
+ by themselves there.
1154
+
1155
+ - **The skill.** `skill_coordinator` (system scope; its index line says what
1156
+ it is for, from the manifest's `description:`) holds the steps. The model
1157
+ creates each worktree with `execute` (`git worktree add ../<repo>-<task> -b
1158
+ <branch>`), starts a child there with `delegate wait: false, cwd:`, tells
1159
+ you what started and ends its turn. When a child's report comes, it checks
1160
+ the branch (`git log`, `git diff --stat`, the tests the child names) before
1161
+ telling you. It asks before each merge (`ask_user_question`), checks that its
1162
+ own folder is on the default branch and that branch's log first (another
1163
+ agent may have merged the branch already), merges `--ff-only` and never
1164
+ pushes unless you ask. After a merge it stops the
1165
+ child and removes only the worktrees and branches it made.
1166
+ - **`/coordinate <goal>`** sends this session a turn asking the model to
1167
+ follow the skill for the goal (in a `--no-shared` REPL it shows the text to
1168
+ send yourself). Asking "do this in parallel" works too: the index line leads
1169
+ the model to the skill.
1170
+ - **`/children [all]`** shows the session's children as a card, newest first,
1171
+ one line each: `` `ab12cd34` · running · fix/flaky · "fix the flaky spec" ``,
1172
+ `` `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass" `` (`not
1173
+ reported yet` when the parent wasn't given that reply), `` `77aa66bb` ·
1174
+ waiting (approval) · open it: chi --attach 77aa66bb ``. A fork is marked
1175
+ `fork`; `all` adds archived children. It runs during a turn too. The card
1176
+ has Refresh and a `Stop <id>` per running or waiting child (up to 5);
1177
+ `/children stop <id>` stops one of this session's own children (by session
1178
+ id, as `chi sessions stop` does) and shows the card again.
1179
+ - **Children stay in their worktree.** A child asks before it changes
1180
+ anything outside its worktree (your checkout, a sibling's), in every
1181
+ guardrails mode and without the guardrails bundle ([a delegate child's
1182
+ boundary](guardrails.md#a-delegate-childs-boundary)); you answer on its
1183
+ card, or on the parent's while it waits.
1184
+ - **Merges.** The parent's merge in its own checkout is a plain `git
1185
+ merge`; to be asked before every merge, add a rule ([Rules in
1186
+ config.yml](guardrails.md#rules-in-configyml)):
1187
+
1188
+ ```yaml
1189
+ guardrails:
1190
+ rules:
1191
+ - id: git-merge
1192
+ tool: shell
1193
+ command: '\bgit\s+merge\b'
1194
+ verdict: ask
1195
+ reason: merging a child's branch
1196
+ ```
1197
+
1123
1198
  ## Shutdown
1124
1199
 
1125
1200
  When the REPL exits, or a session's worker exits (an idle exit, `/exit`, a
data/docs/releasing.md CHANGED
@@ -13,7 +13,7 @@ can run the whole release; the user approves the notes before the tag and the
13
13
  and `rake release:check` enforce it). The system bundle upgrades itself when
14
14
  chi starts.
15
15
  - **Every other shipped bundle** (btw, check-in, guardrails, known-names,
16
- loop-guard, mcp, skills, source-links, github-pr, and the profiles core and dev) has its own semver in its
16
+ loop-guard, mcp, skills, source-links, github-pr, coordinator, and the profiles core and dev) has its own semver in its
17
17
  `manifest.yml` and, when it needs a newer chi, a `requires_chi:` line. They never upgrade by themselves: users run
18
18
  `chi update` (all of them, with the gem) or `chi bundle upgrade NAME`. So:
19
19
  - a change to a bundle's files bumps that bundle's `version:`