samagotchi 0.157.0 → 0.158.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 +27 -1
- data/README.md +1 -1
- data/bin/chi +3 -1
- data/docs/architecture.md +7 -6
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +2 -1
- data/docs/cli.md +21 -17
- data/docs/configuration.md +7 -5
- data/docs/context.md +5 -3
- data/docs/guardrails.md +8 -7
- data/docs/hooks.md +44 -80
- data/docs/internals/bridge.md +1 -1
- data/docs/internals/bundles.md +47 -34
- data/docs/internals/engine.md +7 -5
- data/docs/memory.md +39 -9
- data/docs/plugins.md +13 -9
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +5 -5
- data/docs/testing.md +4 -4
- data/lib/samagotchi/bridge/card_places.rb +5 -13
- data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
- data/lib/samagotchi/bridge/turn_drawings.rb +7 -1
- data/lib/samagotchi/bundle_command/check.rb +23 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command.rb +15 -43
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +6 -6
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +4 -2
- data/lib/samagotchi/engine.rb +1 -6
- data/lib/samagotchi/extension_load.rb +20 -41
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/memory_bundle/asset_installer.rb +39 -119
- data/lib/samagotchi/memory_bundle/builder.rb +10 -34
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +11 -18
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +2 -16
- data/lib/samagotchi/memory_bundle/installer.rb +32 -14
- data/lib/samagotchi/memory_bundle/manifest.rb +37 -54
- data/lib/samagotchi/memory_bundle/provenance.rb +11 -51
- data/lib/samagotchi/memory_bundle/remerge.rb +7 -3
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +30 -35
- data/lib/samagotchi/memory_bundle/status.rb +12 -10
- data/lib/samagotchi/memory_bundle/system_bundle.rb +16 -5
- data/lib/samagotchi/memory_bundle/trash.rb +7 -12
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/plugin/api.rb +1 -4
- data/lib/samagotchi/plugin/loader.rb +42 -5
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/recap_place.rb +3 -4
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +44 -18
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_field.rb +5 -4
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/join_replay.rb +6 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -5
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +12 -8
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +35 -0
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- metadata +3 -2
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
|
@@ -39,7 +39,7 @@ module Samagotchi
|
|
|
39
39
|
List installed bundles and the ones shipped with chi that aren't installed.
|
|
40
40
|
|
|
41
41
|
build [--scope system|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]
|
|
42
|
-
Build local memories and installed
|
|
42
|
+
Build local memories and an installed plugin, rules and scripts into a shareable bundle (dir or zip).
|
|
43
43
|
--scope selects source dir (default: system). --out inferred from extension; default <name>.zip.
|
|
44
44
|
FILES... optional allowlist of *.md basenames to include (default: all but installed bundles' ones);
|
|
45
45
|
a named memory brings its model overlays (<name>.<model-key>.md).
|
|
@@ -75,7 +75,7 @@ module Samagotchi
|
|
|
75
75
|
It also installs a bundle whose requires_chi this chi doesn't meet: without --force that install is
|
|
76
76
|
refused, and the bundle is not written anywhere.
|
|
77
77
|
install --dev PATH
|
|
78
|
-
Install the bundle in the local directory PATH as a link to it: its plugin,
|
|
78
|
+
Install the bundle in the local directory PATH as a link to it: its plugin, rules and scripts
|
|
79
79
|
are PATH's own files (an edit applies at the next worker or chi web restart, with no re-install) and
|
|
80
80
|
their sha256 isn't checked, for this bundle only. Its memories are copied; re-install with --force to
|
|
81
81
|
pick up memory edits. Upgrade refuses it; uninstall removes the link, never PATH's files.
|
|
@@ -90,7 +90,7 @@ module Samagotchi
|
|
|
90
90
|
Usage: chi bundle build [--scope system|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]
|
|
91
91
|
|
|
92
92
|
build [--scope system|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]
|
|
93
|
-
Build local memories and installed
|
|
93
|
+
Build local memories and an installed plugin, rules and scripts into a shareable bundle (dir or zip).
|
|
94
94
|
--scope selects source dir (default: system). --out inferred from extension; default <name>.zip.
|
|
95
95
|
FILES... optional allowlist of *.md basenames to include (default: all but installed bundles' ones);
|
|
96
96
|
a named memory brings its model overlays (<name>.<model-key>.md).
|
|
@@ -200,7 +200,7 @@ module Samagotchi
|
|
|
200
200
|
|
|
201
201
|
installer = installer_for(expanded_source, bundle_name, prepared: prepared, scope: scope, force: force, dev: dev)
|
|
202
202
|
run_installer(installer, failed: "Install") do |manifest|
|
|
203
|
-
|
|
203
|
+
print_plugin(manifest)
|
|
204
204
|
print_provenance(bundle_name, manifest)
|
|
205
205
|
end
|
|
206
206
|
end
|
|
@@ -236,7 +236,7 @@ module Samagotchi
|
|
|
236
236
|
|
|
237
237
|
unless Samagotchi::MemoryBundle::Provenance.new(name: bundle_name).installed?
|
|
238
238
|
@stderr.puts "Bundle '#{bundle_name}' not installed — falling back to install"
|
|
239
|
-
# (no
|
|
239
|
+
# (no Plugin line here)
|
|
240
240
|
installer = installer_for(expanded_source, bundle_name, prepared: prepared, scope: scope, force: force, dry_run: dry_run)
|
|
241
241
|
return run_installer(installer, failed: "Install") do |manifest|
|
|
242
242
|
next dry_run_done if dry_run
|
|
@@ -246,7 +246,7 @@ module Samagotchi
|
|
|
246
246
|
end
|
|
247
247
|
installer = installer_for(expanded_source, bundle_name, prepared: prepared, scope: scope, force: force, upgrade: true, dry_run: dry_run)
|
|
248
248
|
run_installer(installer, failed: "Upgrade") do |manifest|
|
|
249
|
-
|
|
249
|
+
print_plugin(manifest, plugin: !dry_run)
|
|
250
250
|
next dry_run_done if dry_run
|
|
251
251
|
|
|
252
252
|
# Its summary said so, to someone reading.
|
|
@@ -304,7 +304,7 @@ module Samagotchi
|
|
|
304
304
|
# Runs +installer+, prints its summary, then yields the manifest.
|
|
305
305
|
# Ensures the extracted source dir is cleaned up even if an exception
|
|
306
306
|
# occurs between installer.run and the block (e.g. summary,
|
|
307
|
-
#
|
|
307
|
+
# print_plugin). The block (upgrade_conflicts) still sees
|
|
308
308
|
# the files because cleanup happens after the block returns or raises.
|
|
309
309
|
# @return [Integer] the block's exit status, or 1 after "<failed> failed: …"
|
|
310
310
|
def run_installer(installer, failed:)
|
|
@@ -318,8 +318,7 @@ module Samagotchi
|
|
|
318
318
|
installer.cleanup_source!
|
|
319
319
|
end
|
|
320
320
|
|
|
321
|
-
def
|
|
322
|
-
@stdout.puts "Hooks: #{manifest.hooks.size} hook(s) (#{manifest.hooks.keys.join(", ")})" if manifest&.hooks&.any?
|
|
321
|
+
def print_plugin(manifest, plugin: true)
|
|
323
322
|
return unless plugin && manifest&.plugin
|
|
324
323
|
|
|
325
324
|
# Installed with --force past its requires_chi: the Engine won't load it.
|
|
@@ -425,8 +424,6 @@ module Samagotchi
|
|
|
425
424
|
puts_trashed(uninstaller.trashed_files, uninstaller.trash_dir)
|
|
426
425
|
@stdout.puts uninstaller.trash_copy_line if uninstaller.trash_copy_line
|
|
427
426
|
@stdout.puts "Removed: #{uninstaller.removed_files.join(", ")}" unless uninstaller.removed_files.empty?
|
|
428
|
-
hook_removed = uninstaller.removed_files.count { |f| f.start_with?("hooks/") }
|
|
429
|
-
@stdout.puts "Hooks removed: #{hook_removed}" if hook_removed > 0
|
|
430
427
|
uninstaller.warnings.each { |w| @stdout.puts w }
|
|
431
428
|
0
|
|
432
429
|
rescue Samagotchi::MemoryBundle::Uninstaller::UninstallError => e
|
|
@@ -519,22 +516,14 @@ module Samagotchi
|
|
|
519
516
|
@stdout.puts " #{k}: #{label}"
|
|
520
517
|
end
|
|
521
518
|
@stdout.puts " #{manifest_line(st.provenance)}"
|
|
522
|
-
# Hooks
|
|
523
519
|
manifest = st.provenance.manifest
|
|
524
|
-
hooks = manifest.hooks
|
|
525
520
|
trust = manifest.trust_level || "experimental"
|
|
526
521
|
commit = st.provenance.source_commit
|
|
527
|
-
@stdout.puts " trust_level: #{trust}"
|
|
522
|
+
@stdout.puts " trust_level: #{trust}"
|
|
528
523
|
@stdout.puts " source_commit: #{commit}" if commit
|
|
529
|
-
|
|
530
|
-
@stdout.puts " Hooks (#{
|
|
531
|
-
|
|
532
|
-
hook_path = File.join(Samagotchi::MemoryBundle::Provenance.new(name: name).hooks_dir, hk)
|
|
533
|
-
exists = File.exist?(hook_path) ? "ok" : "missing"
|
|
534
|
-
@stdout.puts " #{hk}: #{hook.events_field} on_error=#{hook.on_error} priority=#{hook.priority} [#{exists}]"
|
|
535
|
-
end
|
|
536
|
-
@stdout.puts " requires_chi: #{manifest.requires_chi}" if st.hooks_requires_failure
|
|
537
|
-
@stdout.puts " not loaded: #{st.hooks_requires_failure}" if st.hooks_requires_failure
|
|
524
|
+
unless st.retired_hooks.empty?
|
|
525
|
+
@stdout.puts " Hooks (#{st.retired_hooks.size}): not loaded: #{Samagotchi::MemoryBundle::Manifest::HooksRetired::WORDS}"
|
|
526
|
+
st.retired_hooks.each { |file, state| @stdout.puts " #{file} [#{state}]" }
|
|
538
527
|
end
|
|
539
528
|
if (plugin = st.plugin)
|
|
540
529
|
note = plugin[:state] == "modified" ? " (edited after install: not loaded; reinstall the bundle)" : ""
|
|
@@ -555,11 +544,10 @@ module Samagotchi
|
|
|
555
544
|
|
|
556
545
|
st = Samagotchi::MemoryBundle::Status.bundle_status(bname)
|
|
557
546
|
mods = st.files.values.count { |v| v[:behind] || v[:missing] || v[:unreadable] }
|
|
558
|
-
|
|
559
|
-
hook_info = hooks_count > 0 ? " hooks=#{hooks_count}" : ""
|
|
547
|
+
hook_info = st.retired_hooks.empty? ? "" : " hooks=#{st.retired_hooks.size}"
|
|
560
548
|
plugin = st.plugin
|
|
561
549
|
mods += 1 if plugin && (plugin[:state] != "ok" || plugin[:requires_failure])
|
|
562
|
-
mods += 1
|
|
550
|
+
mods += 1 unless st.retired_hooks.empty?
|
|
563
551
|
mods += 1 if st.scope_error
|
|
564
552
|
plugin_info = plugin ? " plugin=#{plugin[:file]}" : ""
|
|
565
553
|
includes = st.provenance.includes
|
|
@@ -606,15 +594,12 @@ module Samagotchi
|
|
|
606
594
|
rescue ArgumentError => e
|
|
607
595
|
@stderr.puts "Bundle '#{bname}': #{e.message}"; return 1
|
|
608
596
|
end
|
|
609
|
-
hooks = record.manifest.hooks
|
|
610
597
|
plugin_path = prov.plugin_path(record)
|
|
611
598
|
plugin_file = plugin_path && File.basename(plugin_path)
|
|
612
599
|
if file_arg
|
|
613
|
-
#
|
|
600
|
+
# A memory first, then the plugin (an older chi's hook is no file of the bundle's)
|
|
614
601
|
if record.owns?(file_arg)
|
|
615
602
|
diff_memory(prov, target_dir, file_arg)
|
|
616
|
-
elsif hooks.key?(file_arg)
|
|
617
|
-
diff_hook(prov, file_arg, hooks[file_arg])
|
|
618
603
|
elsif plugin_file && [plugin_file, "plugin/#{plugin_file}"].include?(file_arg)
|
|
619
604
|
diff_plugin(prov, record, plugin_path)
|
|
620
605
|
else
|
|
@@ -627,10 +612,6 @@ module Samagotchi
|
|
|
627
612
|
diff_memory(prov, target_dir, key)
|
|
628
613
|
@stdout.puts ""
|
|
629
614
|
end
|
|
630
|
-
hooks.each do |key, hook|
|
|
631
|
-
diff_hook(prov, key, hook)
|
|
632
|
-
@stdout.puts ""
|
|
633
|
-
end
|
|
634
615
|
if plugin_file
|
|
635
616
|
diff_plugin(prov, record, plugin_path)
|
|
636
617
|
@stdout.puts ""
|
|
@@ -666,11 +647,6 @@ module Samagotchi
|
|
|
666
647
|
end
|
|
667
648
|
end
|
|
668
649
|
|
|
669
|
-
def diff_hook(prov, key, hook)
|
|
670
|
-
print_base_and_current("hooks/#{key}", prov.base_path(key), File.join(prov.hooks_dir, key))
|
|
671
|
-
@stdout.puts hook_metadata_line(hook)
|
|
672
|
-
end
|
|
673
|
-
|
|
674
650
|
def diff_plugin(prov, record, plugin_path)
|
|
675
651
|
file = File.basename(plugin_path)
|
|
676
652
|
print_base_and_current("plugin/#{file}", prov.plugin_base_path(file), plugin_path)
|
|
@@ -733,10 +709,6 @@ module Samagotchi
|
|
|
733
709
|
"includes=#{here.join(",")}#{" left out=#{gone.join(",")}" unless gone.empty?}"
|
|
734
710
|
end
|
|
735
711
|
|
|
736
|
-
def hook_metadata_line(hook)
|
|
737
|
-
"--- metadata: #{hook.events_field} on_error=#{hook.on_error} priority=#{hook.priority} sha256=#{hook.sha256}"
|
|
738
|
-
end
|
|
739
|
-
|
|
740
712
|
def build(rest)
|
|
741
713
|
parsed = parse_flags(BUILD_FLAGS, rest, "build", help: BUILD_HELP)
|
|
742
714
|
return parsed if parsed.is_a?(Integer)
|
|
@@ -76,7 +76,7 @@ guardrails:
|
|
|
76
76
|
disable: [guardrails:git-rebase]
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
- `bundles:` map of installed bundle name → its settings (one Hash, string keys, handed to the bundle's
|
|
79
|
+
- `bundles:` map of installed bundle name → its settings (one Hash, string keys, handed to the bundle's plugin `initialize(settings)`). Read when a session's Engine starts: after a change restart the session's worker (`chi sessions stop <id>`). Unknown keys are ignored by the bundle, not validated. `chi bundle list` names the bundles; each bundle's keys are in `docs/bundles.md` / `docs/guardrails.md`.
|
|
80
80
|
|
|
81
81
|
```yaml
|
|
82
82
|
bundles:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.158.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
7
|
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
|
-
self_map.md: sha256:
|
|
9
|
-
config_modification_protocol.md: sha256:
|
|
10
|
-
memory_guide.md: sha256:
|
|
8
|
+
self_map.md: sha256:750822b3488b302c6e32638e6224f270d8e06b50ea85cd6e0bb5a6ff12eed1d8
|
|
9
|
+
config_modification_protocol.md: sha256:44cf7bdac8e526805bff36b76b54fefecd41db30ca525c2ed661d78fe9155dd8
|
|
10
|
+
memory_guide.md: sha256:91e8fd248a155afad6b825097d9ee0a0215ba0b79092c8ab25501c0fc19dedfc
|
|
11
11
|
delegated.md: sha256:a0d6ddcd4a8be06526d964898611775fd251cfd9d145a9d16afaccaa503597bd
|
|
@@ -15,7 +15,7 @@ This memory teaches you (the agent) how to use Samagotchi memories — persisten
|
|
|
15
15
|
| `system` | `$XDG_CONFIG_HOME/samagotchi/memories/`, default `~/.config/samagotchi/memories/` (`MemoryPaths.system_dir`) | User-wide preferences, identity, cross-project knowledge |
|
|
16
16
|
| `project` | `<system dir>/projects/<basename>_<hash>/` (`MemoryPaths.project_dir`: `basename(root)` + 8-char `MD5(root)`, root = `MemoryPaths.project_root`) | Repo-specific conventions, workflow, stack decisions |
|
|
17
17
|
|
|
18
|
-
- The project root is the git repository: its common git dir, which every linked worktree shares. All worktrees and subdirectories of one repository share one project folder; the system prompt shows that folder (not the root: in a linked worktree, work stays in the worktree). Outside a repository
|
|
18
|
+
- The project root is the git repository: its common git dir, which every linked worktree shares. All worktrees and subdirectories of one repository share one project folder; the system prompt shows that folder (not the root: in a linked worktree, work stays in the worktree). A separate clone is a different project. Outside a repository, a folder marked by `chi project init` (its `.chi/project.yml`) is the project for it and every subfolder; outside both, the root is the working directory.
|
|
19
19
|
- `index.md` lives in each scope dir and contains auto-managed lines like `- **name** · scope · date · bytes — description`; a memory a bundle installed adds `· from <bundle>` before the description. Your free-form sections in `index.md` are preserved but managed lines are owned by `memory_write` (and refreshed when `write`/`edit` change a memory's file).
|
|
20
20
|
|
|
21
21
|
## Tools you have
|
|
@@ -101,11 +101,11 @@ Writing or updating a toolkit memory is free like writing a skill; say so in the
|
|
|
101
101
|
|
|
102
102
|
## Memory Bundles — shareable packs
|
|
103
103
|
|
|
104
|
-
Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and any of: memories (`*.md`), `
|
|
104
|
+
Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and any of: memories (`*.md`), `guardrails/*.yml` (rules, `docs/guardrails.md`), a `plugin.rb` (commands, tools, hooks, services; `docs/plugins.md`; class-file `hooks/*.rb` are retired: chi refuses a source with them, `docs/hooks.md`). They are shareable and installable.
|
|
105
105
|
|
|
106
|
-
- Manifest: `files:` (memory → `sha256:`), `
|
|
107
|
-
- Integrity: each file's sha256 is recorded at install; a
|
|
108
|
-
- Installing = trusting its Ruby code (
|
|
106
|
+
- Manifest: `files:` (memory → `sha256:`), `plugin: {file: plugin.rb, sha256: sha256:…}`, `requires_chi: ">= 0.1.30"` (gem-style), `needs:` (outside commands looked up on `PATH`, advisory; `docs/memory.md` "Bundles that need outside commands").
|
|
107
|
+
- Integrity: each file's sha256 is recorded at install; a plugin/rule file changed afterwards is not loaded (rules: every call denied) until reinstalled. It is an integrity check, not proof of authorship.
|
|
108
|
+
- Installing = trusting its Ruby code (the plugin), like a gem. Install only copies; the code runs at the next session start (`Engine.new`), so a running worker needs a restart to pick it up.
|
|
109
109
|
- A bundle without `*.md` (btw, mcp, loop-guard) adds no line to the prompt's memory index.
|
|
110
110
|
- Profiles are shipped bundles with only `includes:` (bundle names): `core` (loop-guard, check-in, guardrails, slow-commands; `chi bootstrap` installs it) and `dev` (known-names, mcp, btw, skills, source-links, github-pr, coordinator, claude-peer, codex-peer). `chi bundle install core` installs the ones not installed and records them; a bundle the user uninstalled stays out on a later install, upgrade or `chi update`, which install only bundles new to the profile. `chi bundle uninstall core` removes its recorded bundles, then core (`docs/memory.md` "Bundle profiles").
|
|
111
111
|
|
|
@@ -119,7 +119,7 @@ Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and
|
|
|
119
119
|
| `status [<bundle>]` | Provenance + per-file `ok|modified|missing|unreadable|no-index` vs stored checksum and base snapshot, and what stays behind or merged at a chi start; `issues=` counts files behind, missing or unreadable, not edits; a model overlay (`<name>.<key>.md`) has no index line and reads `ok (model overlay)`. |
|
|
120
120
|
| `diff <bundle> [file]` | Prints `base` (provenance snapshot) vs `current` (on-disk) for each file. |
|
|
121
121
|
| `list` | Lists installed bundles (`name v<version> scope files installed_at`, plus the shipped version when newer) and the bundles shipped with chi that are not installed (`install <name>` installs one), grouped under their profile; an installed profile shows `includes=` and `left out=`. |
|
|
122
|
-
| `build [--scope system\|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]` | **Inverse of install** — builds a shareable bundle from local memories and installed
|
|
122
|
+
| `build [--scope system\|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]` | **Inverse of install** — builds a shareable bundle from local memories and an installed bundle's plugin, rules and scripts. Infers `zip` vs `dir`/`tar.gz` from `--out` extension; default `chi_system_memories.zip` (system) or `chi_<repo>_memories.zip` (project, named after the project root) v`1.0.0` in `Dir.pwd`. `FILES...` is an optional allowlist of memory basenames (`identity` or `identity.md`); if omitted, all `*.md` except `index.md`/hidden/non-md and the memories other installed bundles own (each named in a `Left out` line) are included. Computes `sha256:` checksums and writes `manifest.yml` via `Manifest.write`. No provenance write. |
|
|
123
123
|
|
|
124
124
|
**Scope resolution for install/build:**
|
|
125
125
|
- CLI `--scope` wins over `manifest.yml` `scope`. Default is `system` if none given (`Installer#run`). For `project`, target is the project folder `<system dir>/projects/<basename>_<hash>` of the project root (`MemoryPaths.scope_dir`).
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
normal session: it shows in `chi sessions list` with `↳ <parent>`, and the user can steer it
|
|
52
52
|
with `chi --attach <id>`. While I wait for a child, its tool approvals go to my user as my own
|
|
53
53
|
approvals (I see only how each was answered); see `docs/sessions.md` "Delegating".
|
|
54
|
-
- Bundles extend me: memories, `
|
|
54
|
+
- Bundles extend me: memories, `guardrails/*.yml`, and a `plugin.rb` that adds slash
|
|
55
55
|
commands (`anytime:` ones run mid-turn), model tools (they can return images I see), hooks,
|
|
56
56
|
cards, side answers (`ask_model`), System 1 judgments (`chi.judgment`, `ctx.judge`), HTTP calls (`ctx.http`),
|
|
57
57
|
child sessions and services (e.g. MCP server processes). Shipped: `btw` (`/btw` side question),
|
|
@@ -409,17 +409,19 @@ module Samagotchi
|
|
|
409
409
|
end
|
|
410
410
|
|
|
411
411
|
# A project's sources: local only (SessionAccess::Contract), this
|
|
412
|
-
# machine's checkout.
|
|
412
|
+
# machine's checkout or marked folder.
|
|
413
413
|
def project_target
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
found = MemoryPaths.lookup(@cwd)
|
|
415
|
+
unless found.project?
|
|
416
|
+
error_line("#{command_name}: --project needs a project (a git repository, or a folder marked with " \
|
|
417
|
+
"`chi project init`), and #{@cwd} is in none")
|
|
416
418
|
return nil
|
|
417
419
|
end
|
|
418
420
|
|
|
419
|
-
root =
|
|
421
|
+
root = found.root
|
|
420
422
|
# Its sources run in the root, or in this checkout when the root is a
|
|
421
423
|
# git dir (ContextSources.project_cwd).
|
|
422
|
-
Target.new(session_id: nil, project_root: root, cwd:
|
|
424
|
+
Target.new(session_id: nil, project_root: root, cwd: found.top || @cwd,
|
|
423
425
|
label: "project #{File.basename(root)}")
|
|
424
426
|
end
|
|
425
427
|
|
|
@@ -658,13 +658,15 @@ module Samagotchi
|
|
|
658
658
|
end
|
|
659
659
|
|
|
660
660
|
# Where a project's source runs: the project root when it is a checkout
|
|
661
|
-
# (its .git is there)
|
|
661
|
+
# (its .git is there) or a marked folder (MemoryPaths::MARKER), else
|
|
662
|
+
# +fallback+ (the session's folder): a bare
|
|
662
663
|
# repo, proj/.bare or --separate-git-dir makes the root the git dir
|
|
663
664
|
# itself (MemoryPaths.project_root), and a command run there sees no
|
|
664
665
|
# files.
|
|
665
666
|
def project_cwd(project_root, fallback)
|
|
666
667
|
root = project_root.to_s
|
|
667
|
-
!root.empty? && File.exist?(File.join(root,
|
|
668
|
+
checkout = !root.empty? && [".git", MemoryPaths::MARKER].any? { |name| File.exist?(File.join(root, name)) }
|
|
669
|
+
checkout ? root : fallback
|
|
668
670
|
end
|
|
669
671
|
|
|
670
672
|
# A deleted session's sources, snapshots, subscriptions and markers.
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -357,12 +357,7 @@ module Samagotchi
|
|
|
357
357
|
# toolkit, the tool index, peer sources), then the plugins themselves
|
|
358
358
|
# unless +load+ is false (see #initialize for when).
|
|
359
359
|
def build_tools_and_plugins(load:, task_finish_notes:)
|
|
360
|
-
|
|
361
|
-
# that raises at fire time is told the same way a chi.on handler is:
|
|
362
|
-
# once per hook in this worker. The guard covers a hook that raises
|
|
363
|
-
# before the routes are there (nothing in the Engine's path does).
|
|
364
|
-
@extension_load = ExtensionLoad.new(hook_failures: @guardrail_failures,
|
|
365
|
-
hook_failed: ->(hook, failed, effect: nil) { @hook_routes&.plugin_hook_failed(hook, failed, effect: effect) })
|
|
360
|
+
@extension_load = ExtensionLoad.new(hook_failures: @guardrail_failures)
|
|
366
361
|
@hooks = @extension_load.hooks
|
|
367
362
|
# The tools this session offers (the prompts' declarations and the
|
|
368
363
|
# kernel's dispatch): the built-ins, per Engine. execute.description
|
|
@@ -7,20 +7,13 @@ require_relative "plugin/loader"
|
|
|
7
7
|
|
|
8
8
|
module Samagotchi
|
|
9
9
|
# What an Engine loads from outside chi as it starts: config.yml's hooks,
|
|
10
|
-
# installed bundles'
|
|
11
|
-
# settings). It holds what plugins showed while they loaded (notices,
|
|
10
|
+
# installed bundles' plugins, and config.yml `bundles:` (their settings). It holds what plugins showed while they loaded (notices,
|
|
12
11
|
# cards): no UI is there yet, so the Engine announces them on the first
|
|
13
12
|
# turn (Engine#announce_load_events!).
|
|
14
13
|
class ExtensionLoad
|
|
15
14
|
# @param hook_failures [Guardrails::LoadFailures] where hooks that fail to load go
|
|
16
|
-
|
|
17
|
-
# an installed bundle's hook file raises at fire time: the Engine's
|
|
18
|
-
# HookRoutes, so the user is told once per hook. nil where there is no
|
|
19
|
-
# UI to tell (the test kit's sandbox): a hook that raises then says
|
|
20
|
-
# nothing, and raises nothing.
|
|
21
|
-
def initialize(hook_failures:, hook_failed: nil)
|
|
15
|
+
def initialize(hook_failures:)
|
|
22
16
|
@hook_failures = hook_failures
|
|
23
|
-
@hook_failed = hook_failed
|
|
24
17
|
@loading = false
|
|
25
18
|
@held_events = []
|
|
26
19
|
end
|
|
@@ -40,12 +33,13 @@ module Samagotchi
|
|
|
40
33
|
end
|
|
41
34
|
|
|
42
35
|
# The hooks: config.yml's (Hooks::Loader; an empty Registry without a
|
|
43
|
-
# hooks config)
|
|
36
|
+
# hooks config). An installed record that still has class-file hooks (a
|
|
37
|
+
# 0.157 install) adds a not-required failure each, which the Engine
|
|
38
|
+
# announces at the first turn: they don't load (#tell_retired_hooks).
|
|
44
39
|
# @return [Hooks::Registry]
|
|
45
40
|
def hooks
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
registry
|
|
41
|
+
tell_retired_hooks
|
|
42
|
+
hooks_from_config
|
|
49
43
|
end
|
|
50
44
|
|
|
51
45
|
# Load the installed bundles' plugins into +registries+; one that fails
|
|
@@ -62,8 +56,7 @@ module Samagotchi
|
|
|
62
56
|
@loading = false
|
|
63
57
|
end
|
|
64
58
|
|
|
65
|
-
# config.yml `bundles:` (ConfigFile.bundle_settings), read once
|
|
66
|
-
# bundle hooks and the plugins both want it.
|
|
59
|
+
# config.yml `bundles:` (ConfigFile.bundle_settings), read once.
|
|
67
60
|
def bundle_settings
|
|
68
61
|
@bundle_settings ||= Samagotchi::ConfigFile.bundle_settings
|
|
69
62
|
end
|
|
@@ -78,36 +71,22 @@ module Samagotchi
|
|
|
78
71
|
Hooks::Registry.new
|
|
79
72
|
end
|
|
80
73
|
|
|
81
|
-
|
|
74
|
+
# One failure per record whose hooks: map isn't empty; an empty
|
|
75
|
+
# hooks/ dir is not one, nor a manifest.json that doesn't read (the
|
|
76
|
+
# plugin loader and the rules report that: a record holding only hooks/
|
|
77
|
+
# with an unreadable manifest.json is silent at start, as its hooks
|
|
78
|
+
# wouldn't load anyway; `chi bundle status` lists it unreadable).
|
|
79
|
+
def tell_retired_hooks
|
|
82
80
|
require_relative "memory_bundle/provenance"
|
|
83
|
-
settings = bundle_settings
|
|
84
81
|
MemoryBundle::Provenance.each_installed(holding: :hooks) do |bundle_name, bundle|
|
|
85
|
-
if bundle.error?
|
|
86
|
-
Log.warn(:hooks, "bundle_manifest_invalid", echo: "[samagotchi:hooks] bundle '#{bundle_name}': #{bundle.error}; its hooks are not loaded",
|
|
87
|
-
bundle: bundle_name)
|
|
88
|
-
@hook_failures.add("hooks (bundle #{bundle_name})", bundle.error, required: false)
|
|
89
|
-
next
|
|
90
|
-
end
|
|
91
|
-
bundle_dir = File.join(MemoryBundle::Provenance.bundles_dir, bundle_name)
|
|
92
|
-
hooks_dir = File.join(bundle_dir, "hooks")
|
|
93
|
-
manifest = bundle.manifest
|
|
94
|
-
if manifest.experimental?
|
|
95
|
-
Log.info(:hooks, "experimental_bundle", echo: "[hooks] Bundle '#{bundle_name}' is experimental — its hooks may change or misbehave.", bundle: bundle_name)
|
|
96
|
-
end
|
|
97
|
-
begin
|
|
98
|
-
Hooks::BundleLoader.load(bundle_name: bundle_name, hooks_dir: hooks_dir, metadata: manifest.hooks, registry: registry,
|
|
99
|
-
failures: @hook_failures, settings: settings[bundle_name.to_s] || {},
|
|
100
|
-
manifest: manifest, hook_failed: @hook_failed)
|
|
101
|
-
rescue Exception => e # rubocop:disable Lint/RescueException -- a hook's SyntaxError or exit must not stop chi
|
|
102
|
-
raise if e.is_a?(SignalException) # Ctrl-C and kill signals are the user's
|
|
82
|
+
next if bundle.error?
|
|
103
83
|
|
|
104
|
-
|
|
105
|
-
|
|
84
|
+
Log.warn(:hooks, "bundle_hooks_retired", bundle: bundle_name, hooks: bundle.hooks.size)
|
|
85
|
+
@hook_failures.add("hooks (bundle #{bundle_name})", MemoryBundle::Manifest::HooksRetired::WORDS, required: false)
|
|
106
86
|
end
|
|
107
|
-
rescue
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Log.error(:hooks, "bundles_load_failed", echo: "[samagotchi:hooks] failed to load bundle hooks: #{e.class}: #{e.message}", error: e.class.name)
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
Log.error(:hooks, "bundles_load_failed", echo: "[samagotchi:hooks] failed to read the installed bundles: #{e.class}: #{e.message}",
|
|
89
|
+
error: e.class.name)
|
|
111
90
|
end
|
|
112
91
|
end
|
|
113
92
|
end
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Samagotchi
|
|
4
4
|
module Guardrails
|
|
5
|
-
# What failed to load at Engine start: hooks,
|
|
6
|
-
# rule files
|
|
5
|
+
# What failed to load at Engine start: config hooks, a required
|
|
6
|
+
# plugin, rule files, an older chi's class-file bundle hooks (which no
|
|
7
|
+
# longer load). A required one makes the gate deny every tool call (a
|
|
7
8
|
# guardrail that silently vanished is the failure this guards against);
|
|
8
9
|
# the rest are only announced.
|
|
9
10
|
class LoadFailures
|
|
@@ -153,6 +153,17 @@ module Samagotchi
|
|
|
153
153
|
(low == low_dir || low.start_with?(File.join(low_dir, ""))) && folds_case?(dir)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# Whether +root+ is the home folder or a folder above it (symlinks
|
|
157
|
+
# resolved on both; false for a blank one). ReviewPaths (a participant
|
|
158
|
+
# turn's project) and MemoryPaths (where a chi project marker is
|
|
159
|
+
# ignored) ask this.
|
|
160
|
+
# @param root [String, nil] @param home [String, nil]
|
|
161
|
+
def home_root?(root, home)
|
|
162
|
+
return false if home.to_s.empty? || root.to_s.empty?
|
|
163
|
+
|
|
164
|
+
under?(real(home.to_s), real(root.to_s))
|
|
165
|
+
end
|
|
166
|
+
|
|
156
167
|
# Whether +path+ is in +dir+, not +dir+ itself (.under?'s terms).
|
|
157
168
|
def strictly_under?(path, dir, fold: true)
|
|
158
169
|
return true if path.start_with?(File.join(dir, ""))
|
|
@@ -59,11 +59,7 @@ module Samagotchi
|
|
|
59
59
|
# review turn reads nothing there (#refusal: HOME_ROOT); a pair turn
|
|
60
60
|
# asks the owner before every write (PairBoundary).
|
|
61
61
|
# @param root [String] @param home [String, nil]
|
|
62
|
-
def self.home_root?(root, home)
|
|
63
|
-
return false if home.to_s.empty? || root.to_s.empty?
|
|
64
|
-
|
|
65
|
-
PathResolve.under?(PathResolve.real(home.to_s), PathResolve.real(root.to_s))
|
|
66
|
-
end
|
|
62
|
+
def self.home_root?(root, home) = PathResolve.home_root?(root, home)
|
|
67
63
|
|
|
68
64
|
# @param root [String, nil] the session's project root (the work tree
|
|
69
65
|
# top of its folder); nil or blank refuses every path
|
|
@@ -21,7 +21,7 @@ module Samagotchi
|
|
|
21
21
|
# How much of a failed hook's error #plugin_hook_failed shows.
|
|
22
22
|
HOOK_FAILURE_CHARS = 300
|
|
23
23
|
# What the notice says the hook now does, by the policy Hooks.wrap gave it
|
|
24
|
-
# (a chi.on handler
|
|
24
|
+
# (a chi.on handler :log, or :deny for an on_error: :fail_closed one).
|
|
25
25
|
HOOK_FAILURE_EFFECTS = {
|
|
26
26
|
log: "it's skipped, and only the log has its next failures",
|
|
27
27
|
skip: "it's skipped, and its next failures are silent",
|
|
@@ -98,7 +98,7 @@ module Samagotchi
|
|
|
98
98
|
nil
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# A
|
|
101
|
+
# A chi.on handler raised (Hooks.wrap; the log has
|
|
102
102
|
# every firing of a :log one). Its first failure on this Engine, so in
|
|
103
103
|
# this worker, tells the user, as a plugin that failed to load does; then
|
|
104
104
|
# only the log. One line per label — file, bundle and event — so a
|
data/lib/samagotchi/hooks.rb
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "hooks/registry"
|
|
4
4
|
require_relative "hooks/loader"
|
|
5
|
-
require_relative "hooks/bundle_loader"
|
|
6
5
|
require_relative "hooks/stream_watch"
|
|
7
6
|
|
|
8
7
|
module Samagotchi
|
|
9
|
-
# Thin wrapper that exposes the Hooks::Registry
|
|
10
|
-
#
|
|
8
|
+
# Thin wrapper that exposes the Hooks::Registry and Hooks::Loader
|
|
9
|
+
# (config.yml's hooks; a bundle's hook through its plugin, Plugin::Api#on).
|
|
11
10
|
#
|
|
12
11
|
# The Engine holds an instance of Hooks::Registry and provides:
|
|
13
12
|
# - #register_hook(name, &block) — register a turn-scoped hook
|