samagotchi 0.34.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -1
  3. data/README.md +2 -0
  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 +37 -0
  9. data/docs/guardrails.md +14 -4
  10. data/docs/hooks.md +25 -4
  11. data/docs/memory.md +3 -0
  12. data/docs/plugins.md +29 -3
  13. data/docs/sessions.md +6 -6
  14. data/lib/samagotchi/bridge/card_store.rb +1 -1
  15. data/lib/samagotchi/bridge/turn_notice.rb +1 -1
  16. data/lib/samagotchi/broadcast/recipients.rb +63 -0
  17. data/lib/samagotchi/broadcast/scope_card.rb +94 -0
  18. data/lib/samagotchi/broadcast/tags.rb +154 -0
  19. data/lib/samagotchi/broadcast_command.rb +228 -0
  20. data/lib/samagotchi/bundles/coordinator/manifest.yml +7 -6
  21. data/lib/samagotchi/bundles/coordinator/plugin.rb +72 -4
  22. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +59 -11
  23. data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
  24. data/lib/samagotchi/bundles/github-pr/plugin.rb +444 -3
  25. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +22 -0
  26. data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
  27. data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
  28. data/lib/samagotchi/bundles/mcp/plugin.rb +44 -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/manifest.yml +3 -3
  33. data/lib/samagotchi/bundles/system/memory_guide.md +4 -2
  34. data/lib/samagotchi/bundles/system/self_map.md +4 -0
  35. data/lib/samagotchi/children_status.rb +2 -16
  36. data/lib/samagotchi/config.rb +6 -0
  37. data/lib/samagotchi/engine.rb +29 -5
  38. data/lib/samagotchi/git_head.rb +24 -0
  39. data/lib/samagotchi/guardrails/parent_approvals.rb +6 -0
  40. data/lib/samagotchi/guardrails/rules.rb +15 -4
  41. data/lib/samagotchi/guardrails/targets.rb +9 -2
  42. data/lib/samagotchi/guardrails/verdict.rb +4 -3
  43. data/lib/samagotchi/hooks/registry.rb +11 -5
  44. data/lib/samagotchi/log_subscriber.rb +1 -1
  45. data/lib/samagotchi/memory_bundle/index_updater.rb +6 -3
  46. data/lib/samagotchi/note_command.rb +13 -13
  47. data/lib/samagotchi/note_delivery.rb +42 -0
  48. data/lib/samagotchi/plugin/context.rb +4 -4
  49. data/lib/samagotchi/session_commands.rb +1 -0
  50. data/lib/samagotchi/session_manager.rb +4 -3
  51. data/lib/samagotchi/sessions_command.rb +3 -2
  52. data/lib/samagotchi/system_prompt.rb +1 -0
  53. data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -2
  54. data/lib/samagotchi/terminal_ui/status_row.rb +23 -2
  55. data/lib/samagotchi/terminal_ui.rb +14 -0
  56. data/lib/samagotchi/tool_activity.rb +14 -3
  57. data/lib/samagotchi/tool_declarations.rb +8 -4
  58. data/lib/samagotchi/tools/builtins.rb +1 -1
  59. data/lib/samagotchi/tools/memory.rb +103 -2
  60. data/lib/samagotchi/tools/send_note.rb +5 -11
  61. data/lib/samagotchi/version.rb +1 -1
  62. data/lib/samagotchi/web/app.rb +3 -0
  63. data/lib/samagotchi/web/capabilities.rb +19 -0
  64. data/lib/samagotchi/web/public/activity.js +26 -6
  65. data/lib/samagotchi/web/public/app.js +515 -288
  66. data/lib/samagotchi/web/public/card.js +12 -3
  67. data/lib/samagotchi/web/public/command_view.js +2 -3
  68. data/lib/samagotchi/web/public/families_view.js +171 -0
  69. data/lib/samagotchi/web/public/format.js +52 -0
  70. data/lib/samagotchi/web/public/index.html +94 -21
  71. data/lib/samagotchi/web/public/live_timing.js +154 -0
  72. data/lib/samagotchi/web/public/live_turn.js +44 -0
  73. data/lib/samagotchi/web/public/sessions_list.js +228 -32
  74. data/lib/samagotchi/web/public/strip.js +5 -2
  75. data/lib/samagotchi/web/public/turn_html.js +107 -0
  76. data/lib/samagotchi/web/public/turn_model.js +5 -4
  77. data/lib/samagotchi/web/public/turn_view.js +43 -129
  78. data/lib/samagotchi/web/session_summary.rb +4 -0
  79. data/lib/samagotchi/worker.rb +166 -307
  80. data/lib/samagotchi/worker_inbound.rb +251 -0
  81. data/lib/samagotchi/worker_wakes.rb +120 -0
  82. metadata +15 -1
@@ -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: