samagotchi 0.35.0 → 0.36.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b598dac8dadadd4dd37861ed690d09d09926520c2295bc998def4d75c3716ea7
4
- data.tar.gz: 9d30ebb820024929bbb2dc1267011af7eb5710b33a4ba9625489a74d111edfbd
3
+ metadata.gz: 1a970676ddf9dacd7bd644f66dcdf28f68a49c6cb24caf2bc785656cb6a2d286
4
+ data.tar.gz: 8babf6d8562a213f07c3053b7efde79aae43ba4d51fef8ce16b933d17894acd0
5
5
  SHA512:
6
- metadata.gz: 2f40ed32d227d1017bc7abcba8ef40f5e38f2f7dd7876aa5f78a9290e5443489b8109183655a151a1345fef1fa3f3f49e5deafbac38059cdba3bec70b0585d71
7
- data.tar.gz: 55aca343a9dc45e78791863286768c356e815dc1b9783993f2c4477edb991883a3c88ae208dd642137cab4c6ead58fc1d49228f201fefcd6878dd421c719af70
6
+ metadata.gz: 9727a14cff5153a0a061d9e787aebd0e0ea7494781e63386405b261fb4391d04bef56d4fefa346c9d0f2081d49fe7eddeadc3f6bb1b4ef8edc6478f5d273db6f
7
+ data.tar.gz: 3dc9840ef7eba85ea3160bb2ac84412e9be6219c4e9bdbabc89796d2ee677adeb7903d4ef1002d82cae12daefac3ca6545f1f6519466bcfda164e2c126fd699e
data/CHANGELOG.md CHANGED
@@ -8,6 +8,37 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.36.0] - 2026-10-07
12
+
13
+ ### Added
14
+
15
+ - `memory_write` with `name`, `scope` and `description` but no `content` changes only that memory's index line and
16
+ leaves its file as it is (see [docs/memory.md](docs/memory.md)).
17
+ - `memory_write` with `name`, `scope` and `remove: true` removes a memory: its file (and model overlays) move to the
18
+ bundle trash, which `chi bundle trash` lists and empties, and its index line goes; a line left after the file was
19
+ deleted by hand goes too. A memory a bundle installed is refused (`chi bundle uninstall` owns it).
20
+ - The coordinator bundle (0.2.0) keeps a project memory `handoff_<epic>` with what git and the session list can't
21
+ rebuild (the split, your decisions, its verdicts, follow-ups), its status in the index description, and resumes
22
+ from it in a new session after checking git and the session list; `/coordinate resume` picks up an open handoff.
23
+ When the work is done it marks the handoff DONE and asks whether to remove it. See
24
+ [docs/plugins.md](docs/plugins.md#the-coordinator-bundle).
25
+ - The guardrails bundle (0.8.0) asks before every memory removal (`memory-remove`, once at a time). Guardrail rules
26
+ take `memory: remove`.
27
+ - In the web, a file reference in an answer (`lib/foo.rb:28`, `lib/foo.rb:28-34`, in inline code too) links to that
28
+ line of the pull request the session reviews: the PR attached to it or named in your messages (a delegate child's
29
+ task too). Inside a changed hunk it opens the PR's Files changed view at the line, highlighted; elsewhere the file
30
+ at the PR's head. Display only; `bundles: github-pr: line_links: false` turns it off (github-pr 0.2.0; run
31
+ `chi update`). See [docs/context.md](docs/context.md#line-links).
32
+ - `/mcp` shows roughly how many tokens each MCP server's tool definitions take in every request, and the total
33
+ (mcp 0.5.0; run `chi update`). See [docs/plugins.md](docs/plugins.md#the-mcp-bundle).
34
+
35
+ ### Fixed
36
+
37
+ - `memory_write` with a blank `description` keeps the stored one instead of wiping it; a multi-line description is
38
+ written on one line (it left orphan lines in `index.md`), and one longer than 200 characters is refused.
39
+
40
+ Update with `chi update` (coordinator 0.2.0 and guardrails 0.8.0, which need chi 0.36.0; github-pr 0.2.0, mcp 0.5.0).
41
+
11
42
  ## [0.35.0] - 2026-10-06
12
43
 
13
44
  ### Added
@@ -1751,7 +1782,8 @@ and long-lived sessions.
1751
1782
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
1752
1783
  hotkey panel that send selected text or the clipboard to your sessions.
1753
1784
 
1754
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.35.0...HEAD
1785
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.36.0...HEAD
1786
+ [0.36.0]: https://github.com/dm1try/samagotchi/compare/v0.35.0...v0.36.0
1755
1787
  [0.35.0]: https://github.com/dm1try/samagotchi/compare/v0.34.0...v0.35.0
1756
1788
  [0.34.0]: https://github.com/dm1try/samagotchi/compare/v0.33.0...v0.34.0
1757
1789
  [0.33.0]: https://github.com/dm1try/samagotchi/compare/v0.32.0...v0.33.0
data/docs/context.md CHANGED
@@ -184,6 +184,43 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
184
184
  - It wakes for a review requesting changes, checks turning red, and the PR
185
185
  merged or closed. Comments alone never wake.
186
186
 
187
+ ### Line links
188
+
189
+ In the web, a file reference in an answer links to that line of the pull
190
+ request the session reviews: `lib/foo.rb:28` and `lib/foo.rb:28-34`, also in
191
+ inline code (`` `lib/foo.rb:28` ``). The model writes plain references; the
192
+ links are display only (the model's text stays as it was), and terminals
193
+ show the answer as it is.
194
+
195
+ - **Which PR**: the session's attached PRs (the branch's, `chi context add
196
+ <PR URL>`, "+ URL") and the PR URLs in your messages, newest first (a
197
+ delegate child's task counts: a parent that names the PR there gets links
198
+ in the child's answers). PR URLs in tool output or answers don't count.
199
+ - **Which file**: a file the PR changes, by its path, a renamed file's old
200
+ path, a bare name only one PR file has (`source_links.rb:28`), or an
201
+ absolute path into a checkout (a child's worktree). Any other file isn't
202
+ linked, nor is a path several of the session's PRs change.
203
+ - **Where to**: the line in the PR's **Files changed**, highlighted, when it
204
+ is inside a changed hunk (a removed file: on its old side); else the file
205
+ at the PR's head commit (a removed one: at its base), which has every
206
+ line.
207
+ - Code blocks, markdown links and URLs are left alone.
208
+ - The PR's files are read with `gh api` in the background when a turn
209
+ starts (and when a PR is attached while it runs), at most once a minute,
210
+ and read again only when the PR's head moved; the answer is linked from
211
+ what was read.
212
+ So the first answer of a very short turn may come without links, and
213
+ after a push links may be a few lines off until the next read. Without
214
+ `gh` or its login, nothing is linked.
215
+
216
+ Turn it off:
217
+
218
+ ```yaml
219
+ bundles:
220
+ github-pr:
221
+ line_links: false
222
+ ```
223
+
187
224
  ## Safety
188
225
 
189
226
  A command source runs **outside the guardrails gate**, every few minutes,
data/docs/guardrails.md CHANGED
@@ -244,7 +244,12 @@ guardrails:
244
244
  scopes: [once, session, rule]
245
245
  ```
246
246
 
247
- A rule gives at least one of `tool`, `command`, `path`, `git`, `touches` and `rm`.
247
+ A rule gives at least one of `tool`, `command`, `path`, `git`, `touches`, `rm` and `memory`.
248
+
249
+ `memory: remove` matches a `memory_write` with `remove: true` (it moves a
250
+ memory into the bundle trash). Nothing else asks about it: a memory's file is
251
+ never outside the repo (below), so without such a rule any session, a delegate
252
+ child included, removes a memory unasked.
248
253
 
249
254
  `rm: outside_tmp` (shell tools) matches a command whose `rm -r -f` reaches
250
255
  outside the tmp folders (`$TMPDIR`, `/tmp`, `/var/tmp`): every such `rm` in a
@@ -408,7 +413,7 @@ rules (`models: small`) are untagged, so they ask a small model in both modes.
408
413
  leaves out `(strict only)`.
409
414
 
410
415
  A rule in config.yml that uses a key this chi doesn't know yet (`modes:`,
411
- `touches:`, `rm:`, `skip_read_only:` before 0.20) doesn't parse, and a rule
416
+ `touches:`, `rm:`, `skip_read_only:` before 0.20, `memory:` before 0.36) doesn't parse, and a rule
412
417
  that doesn't parse denies every tool call (see Failing closed). Bundles say
413
418
  which chi they need (`requires_chi`), so their rules don't hit this.
414
419
 
@@ -429,7 +434,10 @@ hooks, approvals, bundles or attached context sources, or a `.git/hooks`
429
434
  time: a command chi will run every few minutes outside the guardrails; see
430
435
  [Attached context](context.md#safety)); `chi broadcast` (`chi-broadcast`,
431
436
  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
437
+ refuses to run inside a session; see [Broadcast](broadcast.md)); removing a
438
+ memory with `memory_write remove: true` (`memory-remove`, once at a time:
439
+ a delegate child could otherwise remove its parent's handoff unasked; see
440
+ [Memory](memory.md)); and answering chi's
433
441
  questions around `chi answer`: `chi --attach` or `chi -p` with stdin from a
434
442
  pipe, a here-string or a file (`chi-answer-piped`), and `curl`/`wget` to a
435
443
  session's `/answer` route (`chi-answer-http`), both once or for the session.
data/docs/hooks.md CHANGED
@@ -424,7 +424,7 @@ stripped of surrounding whitespace, except file text, an edit's
424
424
  | `write` | — | `path` | `content` |
425
425
  | `edit` | — | `path` | `old_text`, `new_text`, `start_line`, `end_line` |
426
426
  | `memory_read` | `name` | — | `scope` |
427
- | `memory_write` | — | `name` | `content`, `scope`, `description`, `current_model_only` |
427
+ | `memory_write` | — | `name` | `content`, `scope`, `description`, `current_model_only`, `remove` |
428
428
  | `task_create` | `command` | — | `cwd`, `env` |
429
429
  | `task_get` | `id` or `task_id` | — | — |
430
430
  | `task_list` | — | — | — |
data/docs/memory.md CHANGED
@@ -15,6 +15,9 @@ Tool behavior:
15
15
  - If `scope` is provided (`project` or `system`), only that scope is read.
16
16
  - If `scope` is omitted, read falls back from project to system.
17
17
  - `memory_write`: `scope` is required (`project` or `system`). The entry name is passed via the `name` parameter (not `path` — the file tools use `path`). On success, the return value includes the full file path. For a small change to an existing memory (a step, a line) the model is told to `edit` that file rather than rewrite it all with `memory_write`.
18
+ - `memory_write`'s `description` is the managed `index.md` line's text: one line (whitespace and newlines collapse to single spaces), at most 200 characters (longer is refused, since every session's prompt for the repo carries it). A missing or blank one keeps the line's description.
19
+ - `memory_write` with `name`, `scope` and `description` but no `content` changes only that entry's index line (its size and date are refreshed; the date becomes today) and leaves the file as it is: a skill that keeps its status in a memory's description changes it without resending the body. The memory must exist in that scope; the form is refused for `index` and with `current_model_only`.
20
+ - `memory_write` with `name`, `scope` and `remove: true` (no content, description or `current_model_only`) removes a memory: its file and its model overlays (`<name>.<key>.md`, not a dotted memory with an index line of its own) move into one dir of the bundle trash, `memory-<name>-<time>` under `memories/.bundles/.trash/`, which `chi bundle trash` lists and empties, and its index line is dropped. When the file is already gone (deleted by hand), the dangling line is dropped alone. A memory a bundle owns (an installed bundle's record lists it, or its line says `· from <bundle>`) is refused: `chi bundle uninstall` removes it. `index` can't be removed. The guardrails bundle's `memory-remove` rule asks before every removal, once at a time (see [Guardrails](guardrails.md#the-guardrails-bundle)).
18
21
  - `write`/`edit` that change a `*.md` right in a scope dir refresh its managed `index.md` line (date, bytes; the description is kept), as `memory_write` does. A memory created with `write` gets a line with no description; any other `.md` written there gets a line too. Model overlays (`<name>.<key>.md` next to `<name>.md`), files in another project's folder and files changed by `execute` are left out.
19
22
 
20
23
  ## Model-Specific Memory Overlays
data/docs/plugins.md CHANGED
@@ -836,6 +836,13 @@ bundles:
836
836
  closed, then TERM and KILL go to the server's process group.
837
837
  - **`/mcp`** (anytime) shows a card with the servers, their state (cached
838
838
  (not started), running with its pid, failed, stopped) and their tools.
839
+ A cached or running server's line says roughly how many tokens its tool
840
+ definitions take (`~4,232 tokens`), and the last line totals the servers
841
+ whose tools the model has: what every request carries. It is an estimate:
842
+ the definitions' JSON as the chat API gets it (`api: openai`), divided by
843
+ 4. The native prompts (Gemma, Qwen) render a flatter schema, so there they
844
+ take less. The log records each server's estimate when it changes
845
+ (`mcp_tools_estimated`).
839
846
  - The server's stderr goes to the debug log (`plugins` records, bundle=mcp).
840
847
  - **Guardrails.** A rule's `tool:` can be a glob, so one rule covers every
841
848
  MCP tool:
@@ -1137,7 +1144,9 @@ nothing without `gh`, a repository or an open PR), and resolves PR URLs for
1137
1144
  `chi context add` and the web's "+ URL". Its script prints the PR as text
1138
1145
  and a summary of what changed in counts, authors and states only; it wakes
1139
1146
  the session for a review requesting changes, checks turning red, or the PR
1140
- merged or closed. See [Attached context](context.md#github-prs-the-github-pr-bundle).
1147
+ merged or closed. In the web, a `path:line` in an answer links to that line
1148
+ of the session's PR ([Line links](context.md#line-links)). See [Attached
1149
+ context](context.md#github-prs-the-github-pr-bundle).
1141
1150
 
1142
1151
  ## The coordinator bundle
1143
1152
 
@@ -1163,10 +1172,27 @@ by themselves there.
1163
1172
  agent may have merged the branch already), merges `--ff-only` and never
1164
1173
  pushes unless you ask. After a merge it stops the
1165
1174
  child and removes only the worktrees and branches it made.
1175
+ - **A handoff memory.** The conversation is lost when the session ends, so
1176
+ the skill keeps a project memory `handoff_<epic-slug>` with only what git
1177
+ and the session list can't rebuild: the split (branch, worktree, child id
1178
+ per task), your decisions, its verdicts and the follow-ups. Its index
1179
+ description is the status, descriptive only and naming the session that
1180
+ owns it ("OPEN coordinator handoff calc-v2 (session ab12cd34): 2/3 merged",
1181
+ later "DONE: …"): every session in the repo sees that line. The model saves
1182
+ a decision there before acting on it, and changes the status with
1183
+ `memory_write`'s description-only form. When everything is done it marks
1184
+ the handoff DONE and asks whether to remove it (`memory_write remove:
1185
+ true`, which the guardrails bundle asks you to confirm). Children are told
1186
+ not to touch it.
1187
+ - **Resume.** A new coordinator session (or one asked "where were we?")
1188
+ reads the open handoff, checks it against git and the session list (git
1189
+ wins; mismatches are reported) and goes on from the first open step.
1166
1190
  - **`/coordinate <goal>`** sends this session a turn asking the model to
1167
1191
  follow the skill for the goal (in a `--no-shared` REPL it shows the text to
1168
1192
  send yourself). Asking "do this in parallel" works too: the index line leads
1169
- the model to the skill.
1193
+ the model to the skill. **`/coordinate resume`** asks it to resume this
1194
+ project's open handoff; with several open it shows a card with a Resume
1195
+ action each (`/coordinate resume <name>` picks one), with none it says so.
1170
1196
  - **`/children [all]`** shows the session's children as a card, newest first,
1171
1197
  one line each: `` `ab12cd34` · running · fix/flaky · "fix the flaky spec" ``,
1172
1198
  `` `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass" `` (`not
@@ -1,14 +1,15 @@
1
1
  ---
2
2
  name: coordinator
3
- version: 0.1.2
3
+ version: 0.2.0
4
4
  scope: system
5
- description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, ask before each merge), /coordinate <goal> and /children"
5
+ description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, ask before each merge, a handoff memory to resume from), /coordinate <goal>, /coordinate resume and /children"
6
6
  trust_level: reviewed
7
7
  files:
8
8
  skill_coordinator.md:
9
- sha256: sha256:b21067c3f152c8dfd1b1f70001c6654ad1911616543a36b625727fb13d4ce3f6
10
- description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, ask the user before each merge"
9
+ sha256: sha256:a24b91ae27737027125cc0d627bf31edbacdd1be842f4daf82ddf062c9160413
10
+ description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, ask the user before each merge; resume from a handoff_* memory"
11
11
  plugin:
12
12
  file: plugin.rb
13
- sha256: sha256:5173faeb6311984ad4f2a40b9052dcc577a90790fc5c5a8409b95d4f114e66a7
14
- requires_chi: ">= 0.34.0"
13
+ sha256: sha256:2b11a24f22398f4d00132c2342f7c02eacfc6cec0e517e8241a17241affc8ca7
14
+ # the handoff memory needs memory_write's description-only form and remove: (0.36.0)
15
+ requires_chi: ">= 0.36.0"
@@ -18,6 +18,13 @@
18
18
  # for the goal. A REPL session (--no-shared) takes no messages: the request
19
19
  # is shown for the user to send.
20
20
  #
21
+ # /coordinate resume picks up a coordinator handoff: the open handoff_*
22
+ # memories of this project (an index line whose description doesn't start
23
+ # with DONE, its file there). One: the model is asked to resume it (the
24
+ # skill's step 0); several: a card with a Resume action each
25
+ # (/coordinate resume <name>); none: says so. It reads the project's
26
+ # memories folder, nothing else.
27
+ #
21
28
  # A child is asked before it changes anything outside its worktree by chi
22
29
  # itself (the core child-boundary guardrail), in every mode: nothing to set up.
23
30
  class Plugin
@@ -27,7 +34,12 @@ class Plugin
27
34
  STOPPABLE = %w[running waiting].freeze
28
35
  TEXT_CHARS = 80
29
36
  USAGE = "usage: /children [all] | /children stop <id>"
30
- COORDINATE_USAGE = "usage: /coordinate <goal> — chi splits it into tasks, one child session and worktree each"
37
+ COORDINATE_USAGE = "usage: /coordinate <goal> — chi splits it into tasks, one child session and worktree each; " \
38
+ "/coordinate resume [name] — pick up an open handoff"
39
+ RESUME_CARD_ID = "coordinate-resume"
40
+ # A card takes 6 actions.
41
+ MAX_RESUMES = 6
42
+ HANDOFF_LINE = /^- \*\*(handoff_[^*\s]+)\*\*(.*)$/
31
43
 
32
44
  def initialize(_settings = {}); end
33
45
 
@@ -36,7 +48,8 @@ class Plugin
36
48
  anytime: true do |args, ctx|
37
49
  children_command(args.to_s.strip, ctx)
38
50
  end
39
- chi.command "/coordinate", "run work in parallel: chi splits <goal> into tasks, each in a child session and worktree",
51
+ chi.command "/coordinate", "run work in parallel: chi splits <goal> into tasks, each in a child session and worktree; " \
52
+ "resume: pick up an open handoff",
40
53
  anytime: true do |args, ctx|
41
54
  coordinate(args.to_s.strip, ctx)
42
55
  end
@@ -97,13 +110,68 @@ class Plugin
97
110
  def coordinate(goal, ctx)
98
111
  return COORDINATE_USAGE if goal.empty?
99
112
 
100
- request = "Read the skill_coordinator memory and follow it for this goal:\n\n#{goal}"
113
+ verb, rest = goal.split(/\s+/, 2)
114
+ # "resume" and at most a name; "resume the old work" is a goal.
115
+ return resume(rest.to_s.strip, ctx) if verb == "resume" && !rest.to_s.strip.match?(/\s/)
116
+
117
+ ask(ctx, "Read the skill_coordinator memory and follow it for this goal:\n\n#{goal}",
118
+ "asked chi to coordinate it; a running turn gets it at its next step")
119
+ end
120
+
121
+ # /coordinate resume [name]
122
+ def resume(name, ctx)
123
+ open = open_handoffs(ctx)
124
+ if name.empty?
125
+ return "no open coordinator handoff (handoff_* memory) in this project" if open.empty?
126
+ return resume_card(open, ctx) if open.size > 1
127
+
128
+ name = open.first[:name]
129
+ end
130
+ name = "handoff_#{name}" unless name.start_with?("handoff_")
131
+ unless open.any? { |handoff| handoff[:name] == name }
132
+ return "/coordinate resume: no open handoff #{name} in this project (#{open.empty? ? "none is open" : "open: #{open.map { |h| h[:name] }.join(", ")}"})"
133
+ end
134
+
135
+ ask(ctx, "Read the skill_coordinator memory and resume the coordinator handoff #{name}: follow the skill's " \
136
+ "Resume (step 0) before anything else.",
137
+ "asked chi to resume #{name}; a running turn gets it at its next step")
138
+ end
139
+
140
+ def resume_card(open, ctx)
141
+ body = open.map { |handoff| "- #{handoff[:name]}#{" — #{handoff[:description]}" unless handoff[:description].empty?}" }
142
+ actions = open.first(MAX_RESUMES).map do |handoff|
143
+ { label: "Resume #{handoff[:name].delete_prefix("handoff_")}", command: "/coordinate resume #{handoff[:name]}" }
144
+ end
145
+ ctx.card(id: RESUME_CARD_ID, title: "open handoffs (#{open.size})", body: body.join("\n"), actions: actions)
146
+ nil
147
+ end
148
+
149
+ # The project's handoff_* memories whose index line isn't DONE, in index
150
+ # order: [{name:, description:}]. A line without its file is left out.
151
+ def open_handoffs(ctx)
152
+ dir = Samagotchi::MemoryPaths.scope_dir("project", cwd: ctx.cwd)
153
+ index = File.join(dir.to_s, "index.md")
154
+ return [] unless File.file?(index)
155
+
156
+ File.read(index, encoding: "UTF-8").each_line.filter_map do |line|
157
+ match = line.chomp.match(HANDOFF_LINE) or next
158
+ description = match[2].split(" — ", 2)[1].to_s.strip
159
+ next if description.match?(/\ADONE\b/i)
160
+ next unless File.file?(File.join(dir, "#{match[1]}.md"))
161
+
162
+ { name: match[1], description: description }
163
+ end
164
+ rescue SystemCallError
165
+ []
166
+ end
167
+
168
+ def ask(ctx, request, done)
101
169
  begin
102
170
  ctx.sessions.send(ctx.session_id, request)
103
171
  rescue Samagotchi::Plugin::Sessions::Error => e
104
172
  return "/coordinate: #{e.message}. Send this yourself:\n\n#{request}"
105
173
  end
106
- "asked chi to coordinate it; a running turn gets it at its next step"
174
+ done
107
175
  end
108
176
 
109
177
  def quote(text)
@@ -3,6 +3,43 @@
3
3
  Use when the user hands over work with several independent parts, or asks to "run it in parallel",
4
4
  "delegate", "coordinate". The user decides; you do the legwork and keep them in the loop briefly.
5
5
 
6
+ ## Handoff memory
7
+ Your conversation is lost when this session ends; git and the session list are not. Keep a handoff in a
8
+ project-scope memory named `handoff_<epic-slug>` (the epic or goal in a few lowercase words joined by
9
+ `-`, e.g. `handoff_calc-v2`). Create it with memory_write (scope: project). Its description is the status:
10
+ one line under 200 characters that names its owner and says where things stand, never what to do (every
11
+ session in this repo sees it, children too): "OPEN coordinator handoff calc-v2 (session <your id>): 2/3
12
+ merged, docs kept (user)", later "DONE: …". Change the body with `edit` on the memory's file (in the
13
+ Project memories folder), and after every change of state change the status with memory_write name,
14
+ scope and description only (no content). It holds ONLY what git and the session list can't rebuild:
15
+ - Goal: the goal in one line and the split (task → branch, worktree, child session id).
16
+ - Decisions: what the user said per branch (merge / keep / drop / "don't touch X"), their words short.
17
+ - Verdicts: your verdict per report (verified / doubts / failed) and why, in one line.
18
+ - Follow-ups: found but not fixed.
19
+ Not commit lists, diffs, test output or anything a git command shows again. Update it right after:
20
+ the split started (step 3), each verdict (step 4), each user decision (an ask_user_question answer or a
21
+ plain message: save it before you act on it), each merge or cleanup (steps 6, 7). Before you end a turn
22
+ in which any of these changed, check the memory and its description have it. Short: under 40 lines.
23
+ One handoff per epic: never write another epic's handoff_* memory.
24
+
25
+ ## Resume (step 0)
26
+ Before step 1, look at the project memory index for `handoff_*` entries not marked DONE (`/coordinate
27
+ resume` picks one for the user). If one is open and the user's request continues it, or doesn't name new
28
+ work ("hi", "what's next?", "where were we?"), do all of this before you answer:
29
+ 1. Read this skill if you haven't in this session, and memory_read the handoff: its body is the state,
30
+ not the index line (that line is from when this session started). Then rebuild the truth:
31
+ `git worktree list`, `git branch --list`, for each branch in it `git log --oneline <default>..<branch>`
32
+ and `git merge-base --is-ancestor <branch> <default>`, and the list_sessions tool for the child ids
33
+ on record (not listed is not proof a child is gone).
34
+ 2. Git and the sessions are the truth; the memory is your notes. Tell the user, per branch: its state
35
+ now, the decision and verdict on record, and every mismatch (merged by hand, worktree gone, branch
36
+ deleted, a child that ended without a verdict on record), in one short list.
37
+ 3. Update the memory and its description to match the truth (keep the decisions; mark the mismatches;
38
+ the owner is now you), then go on from the first open step. Don't redo a merge or cleanup git shows
39
+ done.
40
+ If the request is a new goal, say which handoffs are open (one line each) and go on with a new
41
+ `handoff_<slug>` of its own; don't touch the others.
42
+
6
43
  ## Steps
7
44
  1. Find the repository's default branch, <default> below: `git symbolic-ref --short
8
45
  refs/remotes/origin/HEAD` (drop the `origin/`), else `main` or `master`, whichever exists; ask the
@@ -13,34 +50,43 @@ Use when the user hands over work with several independent parts, or asks to "ru
13
50
  self-contained task. The task starts with "Work only in <absolute worktree path>: every cd, read,
14
51
  write, test and commit goes there, never the project root" and says what to report (the result,
15
52
  evidence: commands and what they printed, what isn't done) and "commit on the branch, don't merge,
16
- don't push".
53
+ don't push, don't write or edit any handoff_* memory".
17
54
  3. Tell the user what started (one line per child: its id, branch and task) and end your turn or keep
18
55
  talking. Don't call delegate_result: chi brings each child's reply as a delegate report, also when
19
56
  you are idle.
20
57
  4. On each report, check it before telling the user: `git -C <worktree> log --oneline <default>..` and
21
58
  `git diff --stat <default>...<branch>`; run the tests the child names (execute with cwd: the worktree).
22
- Say the result in one or two lines: done and verified / done with doubts / failed.
23
- 5. Then one of: a follow-up (`delegate session: <id>`, narrower, `wait: false`; then end your turn:
24
- its report comes by itself, so don't wait for it with delegate_result or task_wait), stop it
59
+ Say the result in one or two lines: done and verified / done with doubts / failed. In the same turn,
60
+ write that verdict into the handoff body and change its description to match (description only).
61
+ 5. Then one of (a user's decision about it goes into the handoff first): a follow-up (`delegate
62
+ session: <id>`, narrower, `wait: false`; then end your turn: its report comes by itself, so don't
63
+ wait for it with delegate_result or task_wait), stop it
25
64
  (`chi sessions stop <session id>` with execute; task_stop is for task ids, not children; the user
26
65
  can use /children), or tell the user it is ready to merge.
27
- 6. Merging is the user's call: ask (ask_user_question) per branch, "Merge <branch> into <default>?".
66
+ 6. Merging is the user's call: ask (ask_user_question) per branch, "Merge <branch> into <default>?", and
67
+ save the answer in the handoff before you act on it.
28
68
  Before merging, in your own folder: `git branch --show-current` must print <default> (if it
29
69
  doesn't, stop and ask the user; don't switch branches yourself); then check <default>:
30
70
  `git log --oneline -3 <default>` and `git merge-base --is-ancestor <branch> <default>`; if the branch is already in, say so and don't merge
31
71
  again. Merge with `git merge --ff-only <branch>` yourself, in your own folder. If that fails, ask the
32
72
  child to rebase its branch on <default> (a follow-up that says "rebase only, don't merge") or ask the
33
73
  user; don't rebase it from here (the branch is checked out in the child's worktree). Children
34
- never merge. Never push unless asked.
35
- 7. Clean up only a branch that was merged or that the user said to drop: stop its child
36
- (`chi sessions stop <session id>`), then `git worktree remove ../<repo>-<task>`, then
74
+ never merge. Never push unless asked. Don't tell the user a branch can fast-forward unless
75
+ `git merge-base --is-ancestor <default> <branch>` says so now (after each merge <default> moved);
76
+ after a child rebases, check it again as in step 4.
77
+ 7. Clean up only a branch that was merged or that the user said to drop (that decision saved in the
78
+ handoff first): stop its child (`chi sessions stop <session id>`), then `git worktree remove
79
+ ../<repo>-<task>`, then
37
80
  `git branch -d <branch>` (in that order: a branch checked out in a worktree can't be deleted). If
38
- `-d` refuses, say why to the user; never `-D` unless they say so. Remove only worktrees and
39
- branches you created in this session. Every other branch stays as it is, its child, worktree and
81
+ `-d` refuses, say why to the user; never `-D` unless they say so. If `git worktree remove` refuses
82
+ over untracked or ignored files, show `git -C <worktree> status --short --ignored` and ask; never
83
+ `rm -rf`. Remove only worktrees and branches you created in this session. Every other branch stays as it is, its child, worktree and
40
84
  branch too (the user said "keep it", or didn't say): leave them and tell the user they remain.
41
85
  8. When all are done, report: what merged, what didn't and why, what you cleaned up and what remains,
42
86
  follow-ups found but not fixed. Report only what the commands' output showed: a step you planned
43
- but didn't run, or whose output you didn't see, is not done; say so.
87
+ but didn't run, or whose output you didn't see, is not done; say so. Then set the handoff's
88
+ description to "DONE: …" and ask the user whether to remove it (memory_write name, scope and
89
+ remove: true).
44
90
 
45
91
  ## Gotchas
46
92
  - Children are asked before changing anything outside their worktree (your checkout, a sibling's):
@@ -58,3 +104,5 @@ Use when the user hands over work with several independent parts, or asks to "ru
58
104
  ## Changelog
59
105
  - 2026-10-06 created (coordinator bundle 0.1.0)
60
106
  - 2026-10-06 clean up only merged or dropped branches; report only what commands showed (0.1.2)
107
+ - 2026-10-07 handoff memory with its status in the description, resume (step 0, /coordinate resume),
108
+ save decisions first, fast-forward and worktree-remove checks, remove the handoff when done (0.2.0)
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: github-pr
3
- version: 0.1.0
3
+ version: 0.2.0
4
4
  scope: system
5
- description: Attaches the branch's open GitHub pull request to a session as context (and any PR URL with chi context add or the web's "+ URL"); the agent gets a note when reviews, comments, checks or its state change
5
+ description: Attaches the branch's open GitHub pull request to a session as context (and any PR URL with chi context add or the web's "+ URL"); the agent gets a note when reviews, comments, checks or its state change; in the web, a path:line in an answer links to that line of the PR
6
6
  trust_level: reviewed
7
7
  requires_chi: ">= 0.32.0"
8
8
  needs:
@@ -11,7 +11,7 @@ needs:
11
11
  hint: brew install gh, then gh auth login
12
12
  plugin:
13
13
  file: plugin.rb
14
- sha256: sha256:b825731983a402d0cd006a17236794ef03e31ab1b160e853756f4d63b295f664
14
+ sha256: sha256:f8b9794f84681f3a16a4d49da3b857a59fec3ccdc2d0a2b16d1192fd2a26bdf3
15
15
  scripts:
16
16
  pr_context.rb: sha256:a883d2780e89c68c0d7b6d5e0fcb22c273f17bb167e79064e0e99db6bd2ee6f0
17
17
  context_providers: