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 +4 -4
- data/CHANGELOG.md +33 -1
- data/docs/context.md +37 -0
- data/docs/guardrails.md +11 -3
- data/docs/hooks.md +1 -1
- data/docs/memory.md +3 -0
- data/docs/plugins.md +28 -2
- data/lib/samagotchi/bundles/coordinator/manifest.yml +7 -6
- data/lib/samagotchi/bundles/coordinator/plugin.rb +72 -4
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +59 -11
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +444 -3
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +11 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +44 -11
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +4 -2
- data/lib/samagotchi/guardrails/parent_approvals.rb +6 -0
- data/lib/samagotchi/guardrails/rules.rb +15 -4
- data/lib/samagotchi/guardrails/targets.rb +9 -2
- data/lib/samagotchi/memory_bundle/index_updater.rb +6 -3
- data/lib/samagotchi/session_commands.rb +1 -0
- data/lib/samagotchi/tool_activity.rb +14 -3
- data/lib/samagotchi/tool_declarations.rb +8 -4
- data/lib/samagotchi/tools/builtins.rb +1 -1
- data/lib/samagotchi/tools/memory.rb +103 -2
- data/lib/samagotchi/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a970676ddf9dacd7bd644f66dcdf28f68a49c6cb24caf2bc785656cb6a2d286
|
|
4
|
+
data.tar.gz: 8babf6d8562a213f07c3053b7efde79aae43ba4d51fef8ce16b933d17894acd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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 `
|
|
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));
|
|
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.
|
|
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.
|
|
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
|
|
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:
|
|
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:
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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.
|
|
39
|
-
|
|
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.
|
|
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:
|
|
14
|
+
sha256: sha256:f8b9794f84681f3a16a4d49da3b857a59fec3ccdc2d0a2b16d1192fd2a26bdf3
|
|
15
15
|
scripts:
|
|
16
16
|
pr_context.rb: sha256:a883d2780e89c68c0d7b6d5e0fcb22c273f17bb167e79064e0e99db6bd2ee6f0
|
|
17
17
|
context_providers:
|