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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/README.md +1 -1
  4. data/bin/chi +3 -1
  5. data/docs/architecture.md +7 -6
  6. data/docs/bundle-authoring.md +5 -5
  7. data/docs/bundles.md +2 -1
  8. data/docs/cli.md +21 -17
  9. data/docs/configuration.md +7 -5
  10. data/docs/context.md +5 -3
  11. data/docs/guardrails.md +8 -7
  12. data/docs/hooks.md +44 -80
  13. data/docs/internals/bridge.md +1 -1
  14. data/docs/internals/bundles.md +47 -34
  15. data/docs/internals/engine.md +7 -5
  16. data/docs/memory.md +39 -9
  17. data/docs/plugins.md +13 -9
  18. data/docs/releasing.md +6 -5
  19. data/docs/sessions.md +5 -5
  20. data/docs/testing.md +4 -4
  21. data/lib/samagotchi/bridge/card_places.rb +5 -13
  22. data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
  23. data/lib/samagotchi/bridge/turn_drawings.rb +7 -1
  24. data/lib/samagotchi/bundle_command/check.rb +23 -10
  25. data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
  26. data/lib/samagotchi/bundle_command/try/run.rb +4 -1
  27. data/lib/samagotchi/bundle_command.rb +15 -43
  28. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  29. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  30. data/lib/samagotchi/bundles/system/memory_guide.md +6 -6
  31. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  32. data/lib/samagotchi/context_command.rb +7 -5
  33. data/lib/samagotchi/context_sources.rb +4 -2
  34. data/lib/samagotchi/engine.rb +1 -6
  35. data/lib/samagotchi/extension_load.rb +20 -41
  36. data/lib/samagotchi/guardrails/load_failures.rb +3 -2
  37. data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
  38. data/lib/samagotchi/guardrails/review_paths.rb +1 -5
  39. data/lib/samagotchi/hook_routes.rb +2 -2
  40. data/lib/samagotchi/hooks.rb +2 -3
  41. data/lib/samagotchi/memory_bundle/asset_installer.rb +39 -119
  42. data/lib/samagotchi/memory_bundle/builder.rb +10 -34
  43. data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
  44. data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
  45. data/lib/samagotchi/memory_bundle/installed_copy.rb +11 -18
  46. data/lib/samagotchi/memory_bundle/installed_manifest.rb +2 -16
  47. data/lib/samagotchi/memory_bundle/installer.rb +32 -14
  48. data/lib/samagotchi/memory_bundle/manifest.rb +37 -54
  49. data/lib/samagotchi/memory_bundle/provenance.rb +11 -51
  50. data/lib/samagotchi/memory_bundle/remerge.rb +7 -3
  51. data/lib/samagotchi/memory_bundle/shas.rb +5 -8
  52. data/lib/samagotchi/memory_bundle/shipped_update.rb +30 -35
  53. data/lib/samagotchi/memory_bundle/status.rb +12 -10
  54. data/lib/samagotchi/memory_bundle/system_bundle.rb +16 -5
  55. data/lib/samagotchi/memory_bundle/trash.rb +7 -12
  56. data/lib/samagotchi/memory_orphans.rb +4 -4
  57. data/lib/samagotchi/memory_paths.rb +102 -13
  58. data/lib/samagotchi/plugin/api.rb +1 -4
  59. data/lib/samagotchi/plugin/loader.rb +42 -5
  60. data/lib/samagotchi/project_command.rb +167 -0
  61. data/lib/samagotchi/project_init.rb +69 -0
  62. data/lib/samagotchi/project_scope.rb +6 -4
  63. data/lib/samagotchi/prompt_files.rb +10 -7
  64. data/lib/samagotchi/recap_place.rb +3 -4
  65. data/lib/samagotchi/run_options.rb +1 -0
  66. data/lib/samagotchi/session.rb +44 -18
  67. data/lib/samagotchi/session_access/contract.rb +1 -1
  68. data/lib/samagotchi/session_field.rb +5 -4
  69. data/lib/samagotchi/steer.rb +10 -0
  70. data/lib/samagotchi/system_prompt.rb +20 -11
  71. data/lib/samagotchi/terminal_ui/join_replay.rb +6 -1
  72. data/lib/samagotchi/testing/bundle.rb +5 -12
  73. data/lib/samagotchi/testing/install.rb +5 -5
  74. data/lib/samagotchi/testing/recorder.rb +1 -1
  75. data/lib/samagotchi/testing.rb +1 -1
  76. data/lib/samagotchi/tool_declarations.rb +1 -1
  77. data/lib/samagotchi/toolkit_binding.rb +1 -4
  78. data/lib/samagotchi/toolkit_command.rb +1 -4
  79. data/lib/samagotchi/tools/delegate.rb +12 -8
  80. data/lib/samagotchi/version.rb +1 -1
  81. data/lib/samagotchi/web/app.rb +35 -0
  82. data/lib/samagotchi/web/project_list.rb +3 -2
  83. data/lib/samagotchi/web/public/index.html +6 -1
  84. data/lib/samagotchi/web/public/project_picker.js +86 -4
  85. metadata +3 -2
  86. data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "fileutils"
4
4
  require_relative "file_digest"
5
- require_relative "bundle_hook"
6
5
  require_relative "manifest"
7
6
  require_relative "provenance"
8
7
  require_relative "../version"
@@ -13,22 +12,25 @@ module Samagotchi
13
12
 
14
13
  # The install of a bundle's parts that live in its own dir
15
14
  # (<bundles>/<name>/, Provenance#bundle_dir) rather than in the
16
- # memories dir: hooks/*.rb, guardrails/*.yml, the plugin file and
17
- # scripts/*. Each replaces what an earlier version installed. Installer
15
+ # memories dir: guardrails/*.yml, the plugin file and scripts/*. Each
16
+ # replaces what an earlier version installed. An older chi's record
17
+ # with class-file hooks has them removed (#retire_hooks): chi no longer
18
+ # loads them, and the Installer refuses a source that has any. Installer
18
19
  # runs it after the memories; it adds to the installer's results and
19
20
  # warnings, and #run returns what the record (Provenance#write) keeps.
20
21
  # A dev install (+dev+, `chi bundle install --dev`) links each file to
21
22
  # the source instead of copying it, and warns of no checksum mismatch.
22
23
  # An edited file it replaces or removes is copied to the trash first,
23
- # under its subfolder (Trash#copy_edit); a link never is.
24
+ # under its subfolder (Trash#copy_edits); a link never is.
24
25
  class AssetInstaller
25
26
  # What was installed: the paths Provenance#write records (each
26
27
  # empty / nil on a dry run).
27
- # hooks every hook basename the bundle ships
28
- # hook_files, guardrail_files, script_files
29
- # basename => installed path
28
+ # guardrail_files, script_files basename => installed path
30
29
  # plugin_file the installed plugin file, nil without one
31
- Installed = Data.define(:hooks, :hook_files, :guardrail_files, :plugin_file, :script_files)
30
+ Installed = Data.define(:guardrail_files, :plugin_file, :script_files)
31
+
32
+ # The retire's summary words (Installer#summary): why the hooks went.
33
+ RETIRED = "chi no longer loads class-file bundle hooks"
32
34
 
33
35
  # @param source_dir [String] the normalized bundle source
34
36
  # @param manifest [Manifest, nil] nil for a non-strict install without one
@@ -61,18 +63,16 @@ module Samagotchi
61
63
  # @raise [Installer::InstallError] the manifest names a plugin or
62
64
  # script the bundle doesn't have
63
65
  def run
64
- install_hooks
65
- prune_dropped_hooks if (@upgrade || (@dev && @existing&.dev)) && @existing && !@dry_run
66
- verify_hook_checksums if @manifest && @strict && !@dev
67
66
  incoming_rules = Dir.glob(File.join(@bundle_dir, "guardrails", "*.yml")).sort
68
67
  .reject { |src| linked_out?(src, "guardrails/#{File.basename(src)}") }
69
68
  guardrail_files = install_guardrails(incoming_rules)
70
69
  plugin_file = install_plugin
71
70
  script_files = install_scripts
72
- warn_hooks_requires_chi
71
+ # After the plugin and scripts, which raise for a file the manifest
72
+ # names and the bundle lacks: a refused install leaves hooks/ as it was.
73
+ retire_hooks
73
74
  warn_rules_requires_chi(incoming_rules)
74
- Installed.new(hooks: @bundle_hooks, hook_files: @hook_files, guardrail_files: guardrail_files,
75
- plugin_file: plugin_file, script_files: script_files)
75
+ Installed.new(guardrail_files: guardrail_files, plugin_file: plugin_file, script_files: script_files)
76
76
  end
77
77
 
78
78
  # Whether the bundle file +src+ reads through a symlink: it is one, or
@@ -91,99 +91,28 @@ module Samagotchi
91
91
 
92
92
  private
93
93
 
94
- # Copies the bundle's hooks/*.rb into <bundle_dir>/hooks/: the ones
95
- # its manifest lists, else (no hooks: in it) every hooks/*.rb, which is
96
- # then added to manifest.hooks with no event (the loader skips it, the
97
- # record keeps it). Without a manifest every hooks/*.rb is copied.
98
- # Sets @bundle_hooks (every hook the bundle ships) and @hook_files
99
- # (basename => installed path, for the record).
100
- def install_hooks
101
- @bundle_hooks = []
102
- @hook_files = {}
103
- return install_unlisted_hooks unless @manifest
104
-
105
- hook_entries = @manifest.hooks
106
- if hook_entries.empty?
107
- Dir.glob(File.join(@bundle_dir, "hooks", "*.rb")).each { |p| hook_entries[File.basename(p)] = BundleHook.new }
108
- end
109
- hook_entries.keys.each do |basename|
110
- next unless basename.is_a?(String) && !basename.empty?
111
-
112
- src = File.join(@bundle_dir, "hooks", basename)
113
- # A linked hook isn't the bundle's: not installed, counted or recorded.
114
- next hook_entries.delete(basename) if (File.symlink?(src) || File.exist?(src)) && linked_out?(src, "hooks/#{basename}")
115
-
116
- @bundle_hooks << basename
117
- install_hook(basename, src) if File.exist?(src)
118
- end
119
- end
120
-
121
- # One listed hook; an upgrade overwrites a local edit with a warning.
122
- def install_hook(basename, src)
123
- dest = File.join(@provenance.hooks_dir, basename)
124
- result = result_for(src, dest, updated: @upgrade && @existing)
125
- return @results[basename] = planned(result) if @dry_run
126
-
127
- prev_sha = @existing&.hooks&.dig(basename)&.sha256
128
- if @upgrade && File.exist?(dest) && !@force && !Provenance.recorded_sha(prev_sha).empty? && !Provenance.sha_matches?(dest, prev_sha)
129
- @warnings << "Hook #{basename} was locally modified; overwriting"
94
+ # The hooks/ an older chi installed (class-file hooks, which no longer
95
+ # load): every file in it goes, an edited one (or one the record
96
+ # doesn't know) copied to the trash first, a dev link never (it points
97
+ # at the source, which stays). Each is a :removed result (a dry run's
98
+ # :would_remove, through #planned) with the RETIRED note; an empty
99
+ # hooks/ dir goes without a word. The record keeps no hooks
100
+ # (Provenance#write).
101
+ def retire_hooks
102
+ dir = @provenance.hooks_dir
103
+ return unless File.directory?(dir) || File.symlink?(dir)
104
+
105
+ files = File.symlink?(dir) ? [] : Dir.glob("**/*", File::FNM_DOTMATCH, base: dir).sort.reject { |f| File.directory?(File.join(dir, f)) }
106
+ files.each do |file|
107
+ @results["hooks/#{file}"] = @dry_run ? planned(retired) : retired
130
108
  end
109
+ return if @dry_run
131
110
 
132
- FileUtils.mkdir_p(@provenance.hooks_dir)
133
- @trash.copy_edit(dest, as: "hooks/#{basename}", recorded: prev_sha, incoming: src)
134
- place(src, dest)
135
- @results[basename] = result
136
- @hook_files[basename] = dest
111
+ @trash.copy_edits(dir, "hooks", @existing&.hooks&.transform_values(&:sha256))
112
+ FileUtils.rm_rf(dir)
137
113
  end
138
114
 
139
- # No manifest (a non-strict install): every hooks/*.rb is copied.
140
- def install_unlisted_hooks
141
- Dir.glob(File.join(@bundle_dir, "hooks", "*.rb")).each do |src|
142
- basename = File.basename(src)
143
- next if linked_out?(src, "hooks/#{basename}")
144
-
145
- @bundle_hooks << basename
146
- if @dry_run
147
- @results[basename] = Installer::FileResult.new(word: :would_install)
148
- else
149
- FileUtils.mkdir_p(@provenance.hooks_dir)
150
- dest = File.join(@provenance.hooks_dir, basename)
151
- @trash.copy_edit(dest, as: "hooks/#{basename}", recorded: @existing&.hooks&.dig(basename)&.sha256, incoming: src)
152
- place(src, dest)
153
- @results[basename] = Installer::FileResult.new(word: :installed)
154
- @hook_files[basename] = dest
155
- end
156
- end
157
- end
158
-
159
- # An upgrade removes the hooks the previous version shipped and this
160
- # one doesn't (a local file too, with a warning; a dev link silently).
161
- def prune_dropped_hooks
162
- @existing.hooks.each_key do |old_key_str|
163
- next if @bundle_hooks.include?(old_key_str)
164
-
165
- old_dest = File.join(@provenance.hooks_dir, old_key_str)
166
- if File.exist?(old_dest) && !@force && !File.symlink?(old_dest)
167
- @warnings << "Bundle no longer includes hook #{old_key_str} but local file exists — removing"
168
- end
169
- @trash.copy_edit(old_dest, as: "hooks/#{old_key_str}", recorded: @existing.hooks[old_key_str]&.sha256)
170
- FileUtils.rm_f(old_dest)
171
- end
172
- end
173
-
174
- # Strict mode: a hook whose sha256 differs from the one its manifest
175
- # declares warns (as verify_memory_checksums does). The bundle's file is
176
- # checked, the one the install copies, so a dry run checks what it
177
- # would install, not the old hook in place.
178
- def verify_hook_checksums
179
- @bundle_hooks.each do |basename|
180
- next if @conflicts.key?(basename)
181
-
182
- warning = FileDigest.mismatch("hook #{basename}", File.join(@bundle_dir, "hooks", basename),
183
- @manifest.checksum_for_hook(basename))
184
- @warnings << warning if warning
185
- end
186
- end
115
+ def retired = Installer::FileResult.new(word: :removed, note: RETIRED)
187
116
 
188
117
  # The bundle's guardrails/*.yml into <bundle_dir>/guardrails/: the
189
118
  # set replaces what an earlier version installed. Same bytes as
@@ -289,22 +218,24 @@ module Samagotchi
289
218
 
290
219
  # What the real run reports for +src+ over +dest+: up to date when the
291
220
  # bytes are the same, else updated (+updated+: an upgrade over an
292
- # install) or installed. A dry run records #planned of it, so the two
221
+ # install, of a file that is there) or installed. A dry run records #planned of it, so the two
293
222
  # can't drift.
294
223
  def result_for(src, dest, updated:)
295
224
  word = if File.file?(dest) && FileUtils.identical?(src, dest) then :up_to_date
296
- elsif updated then :updated
225
+ elsif updated && File.exist?(dest) then :updated
297
226
  else :installed
298
227
  end
299
228
  Installer::FileResult.new(word: word)
300
229
  end
301
230
 
302
- # A dry run's word for a result: would_install, would_update, or the
303
- # up to date result as it is (nothing would be written).
231
+ # A dry run's word for a result: would_install, would_update,
232
+ # would_remove, or the up to date result as it is (nothing would be
233
+ # written).
304
234
  def planned(result)
305
235
  case result.word
306
236
  when :installed then result.with(word: :would_install)
307
237
  when :updated then result.with(word: :would_update)
238
+ when :removed then result.with(word: :would_remove)
308
239
  else result
309
240
  end
310
241
  end
@@ -333,21 +264,10 @@ module Samagotchi
333
264
  true
334
265
  end
335
266
 
336
- # The hook loader skips every hook of a bundle whose requires_chi this
337
- # chi doesn't meet (Hooks::BundleLoader); say so at install, as
338
- # install_plugin does for the plugin.
339
- def warn_hooks_requires_chi
340
- return if @manifest.nil? || @bundle_hooks.empty?
341
-
342
- if (failure = Manifest.requires_chi_failure(@manifest.requires_chi, @chi_version))
343
- @warnings << "Bundle #{@name}: its hooks won't load: #{failure}"
344
- end
345
- end
346
-
347
267
  # GuardrailWiring#bundle_rules doesn't read the rules of a bundle whose
348
268
  # requires_chi this chi doesn't meet, and a session that never loaded
349
269
  # them records a required load failure (the gate refuses guarded calls
350
- # until chi is updated): say so at install, as the hooks warning does.
270
+ # until chi is updated): say so at install, as install_plugin does.
351
271
  def warn_rules_requires_chi(rule_files)
352
272
  return if @manifest.nil? || rule_files.empty?
353
273
 
@@ -4,7 +4,6 @@ require "digest"
4
4
  require_relative "file_digest"
5
5
  require "fileutils"
6
6
  require "tmpdir"
7
- require_relative "bundle_hook"
8
7
  require_relative "manifest"
9
8
  require_relative "placeholder"
10
9
  require_relative "../memory_paths"
@@ -12,7 +11,9 @@ require_relative "../model_overlay"
12
11
 
13
12
  module Samagotchi
14
13
  module MemoryBundle
15
- # Builds local memories and installed hooks into a shareable bundle.
14
+ # Builds local memories and an installed bundle's rules, plugin and
15
+ # scripts into a shareable bundle (class-file hooks are retired: an older
16
+ # record's hooks/ isn't built).
16
17
  #
17
18
  # Inverse of Installer: reads from the resolved scope directory,
18
19
  # computes SHA256 checksums, writes manifest.yml via Manifest.write,
@@ -27,11 +28,10 @@ module Samagotchi
27
28
  # record and its dir), else from the scope dir:
28
29
  # record the InstalledBundle, nil when none is installed (or
29
30
  # it doesn't read)
30
- # hooks basename => BundleHook; hook_files basename => path
31
31
  # rules guardrails/*.yml paths
32
32
  # plugin the installed plugin file, nil without one
33
33
  # scripts file name => path; context_providers, block_renderers the parsed lists
34
- InstalledParts = Data.define(:record, :hooks, :hook_files, :rules, :plugin, :scripts, :context_providers, :block_renderers)
34
+ InstalledParts = Data.define(:record, :rules, :plugin, :scripts, :context_providers, :block_renderers)
35
35
 
36
36
  attr_reader :built_files, :warnings, :placeholder_warnings, :out_path, :staging_dir
37
37
 
@@ -138,12 +138,10 @@ module Samagotchi
138
138
  end
139
139
  end
140
140
 
141
- # The installed bundle's hooks (a recorded one missing on disk left
142
- # out; one with no recorded sha gets its file's), guardrail rules,
143
- # plugin, scripts, context providers and block renderers (its
144
- # InstalledManifest's). Without hooks or rules in a
145
- # record, the scope dir's hooks/*.rb and guardrails/*.yml (building
146
- # from a raw bundle dir that is also the memories dir).
141
+ # The installed bundle's guardrail rules, plugin, scripts, context
142
+ # providers and block renderers (its InstalledManifest's). Without
143
+ # rules in a record, the scope dir's guardrails/*.yml (building from a
144
+ # raw bundle dir that is also the memories dir).
147
145
  # @return [InstalledParts]
148
146
  def installed_parts(name, target_dir)
149
147
  prov = nil
@@ -156,23 +154,6 @@ module Samagotchi
156
154
  record = nil
157
155
  end
158
156
 
159
- hooks = {}
160
- hook_files = {}
161
- if record&.hooks&.any?
162
- record.hooks.each do |basename, hook|
163
- src = File.join(prov.hooks_dir, basename)
164
- next unless File.exist?(src)
165
-
166
- hooks[basename] = hook.sha256.empty? ? hook.with(sha256: BundleHook.of_file(src).sha256) : hook
167
- hook_files[basename] = src
168
- end
169
- else
170
- Dir.glob(File.join(target_dir, "hooks", "*.rb")).sort.each do |src|
171
- hooks[File.basename(src)] = BundleHook.of_file(src)
172
- hook_files[File.basename(src)] = src
173
- end
174
- end
175
-
176
157
  rules_dir = record&.guardrails&.any? ? prov.guardrails_dir : File.join(target_dir, "guardrails")
177
158
  plugin = record ? prov.plugin_path(record) : nil
178
159
  manifest = record&.manifest
@@ -181,7 +162,7 @@ module Samagotchi
181
162
  raise BuildError, "the installed #{name}'s record: #{problem}" if problem
182
163
 
183
164
  InstalledParts.new(
184
- record: record, hooks: hooks, hook_files: hook_files,
165
+ record: record,
185
166
  rules: Dir.exist?(rules_dir) ? Dir.glob(File.join(rules_dir, "*.yml")).sort : [],
186
167
  plugin: plugin && File.file?(plugin) ? plugin : nil,
187
168
  scripts: installed_scripts(prov, record),
@@ -190,14 +171,10 @@ module Samagotchi
190
171
  )
191
172
  end
192
173
 
193
- # The memories, hooks/, guardrails/, the plugin and scripts/ into the
174
+ # The memories, guardrails/, the plugin and scripts/ into the
194
175
  # staging dir, laid out as Installer reads a bundle.
195
176
  def stage_files(staging, candidates, parts)
196
177
  candidates.each { |src| FileUtils.cp(src, File.join(staging, File.basename(src))) }
197
- unless parts.hook_files.empty?
198
- FileUtils.mkdir_p(File.join(staging, "hooks"))
199
- parts.hook_files.each { |basename, src| FileUtils.cp(src, File.join(staging, "hooks", basename)) }
200
- end
201
178
  unless parts.rules.empty?
202
179
  FileUtils.mkdir_p(File.join(staging, "guardrails"))
203
180
  parts.rules.each { |src| FileUtils.cp(src, File.join(staging, "guardrails", File.basename(src))) }
@@ -225,7 +202,6 @@ module Samagotchi
225
202
  scope: @scope,
226
203
  description: description,
227
204
  files: files_map,
228
- hooks: parts.hooks.empty? ? nil : parts.hooks,
229
205
  trust_level: trust_level,
230
206
  plugin: parts.plugin && { file: File.basename(parts.plugin), sha256: FileDigest.sha256(parts.plugin),
231
207
  required: manifest&.plugin_required },
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "file_digest"
4
-
5
3
  module Samagotchi
6
4
  module MemoryBundle
7
- # One hook's metadata, as a bundle's manifest.yml declares it and its
8
- # installed record (manifest.json) keeps it: the file's sha256
5
+ # One class-file hook's metadata, as an older chi's installed record
6
+ # (manifest.json) keeps it (chi no longer installs or loads them:
7
+ # Manifest::HooksRetired; it reads them to retire, uninstall and name
8
+ # them, and the trash selection takes #sha256): the file's sha256
9
9
  # ("sha256:<hex>", "" when none is recorded), the events it registers
10
10
  # for (manifest `event:` for one, `events:` a list; none: it isn't
11
- # loaded), on_error (skip, log or fail_closed) and priority. One
12
- # instance of the hook serves every event it lists. The one place the
13
- # defaults live.
11
+ # loaded), on_error (skip, log or fail_closed) and priority. The one
12
+ # place the defaults live.
14
13
  class BundleHook < Data.define(:sha256, :events, :on_error, :priority)
15
14
  DEFAULT_ON_ERROR = "skip"
16
15
  DEFAULT_PRIORITY = 100
@@ -37,35 +36,11 @@ module Samagotchi
37
36
  priority: (h["priority"] || DEFAULT_PRIORITY).to_i)
38
37
  end
39
38
 
40
- # A hook known only by its file (no declared event): its sha, the defaults.
41
- # Callers pass a file that is there (FileDigest is nil for one that isn't).
42
- def self.of_file(path)
43
- new(sha256: FileDigest.sha256(path))
44
- end
45
-
46
39
  # "sha256:<hex>" from either form, "" for none.
47
40
  def self.prefixed(sha)
48
41
  sha = sha.to_s.strip
49
42
  sha.empty? || sha.start_with?("sha256:") ? sha : "sha256:#{sha}"
50
43
  end
51
-
52
- # The sha's hex, "" when none is recorded.
53
- def hex = sha256.delete_prefix("sha256:")
54
-
55
- # Whether the hook fails closed: on_error fail_closed applies to its
56
- # before_tool_call registration only.
57
- def fail_closed?(event) = on_error == "fail_closed" && event.to_s == "before_tool_call"
58
-
59
- # How `chi bundle status` / `diff` name its events: `event=x`, or
60
- # `events=x,y` for more than one.
61
- def events_field = events.size > 1 ? "events=#{events.join(",")}" : "event=#{events.first}"
62
-
63
- # The string-keyed form manifest.yml and manifest.json hold: `event`
64
- # for one event (as an older chi reads it), `events` for more.
65
- def to_record
66
- listed = events.size > 1 ? { "events" => events.dup } : { "event" => events.first.to_s }
67
- { "sha256" => sha256, **listed, "on_error" => on_error, "priority" => priority }
68
- end
69
44
  end
70
45
  end
71
46
  end
@@ -119,7 +119,7 @@ module Samagotchi
119
119
  # recorded is gone (:missing). false with no dir.
120
120
  def manifest_unkept? = !dir.nil? && (manifest_sha.to_s.empty? || !File.file?(kept_manifest_path))
121
121
 
122
- # What its manifest says (requires_chi, hook metadata, needs, providers,
122
+ # What its manifest says (requires_chi, needs, providers,
123
123
  # renderers, settings…): the one way to read manifest data of an
124
124
  # installed bundle.
125
125
  # @return [InstalledManifest]
@@ -3,27 +3,28 @@
3
3
  require "fileutils"
4
4
  require "tmpdir"
5
5
  require "yaml"
6
- require_relative "bundle_hook"
7
6
  require_relative "manifest"
8
7
  require_relative "provenance"
9
8
 
10
9
  module Samagotchi
11
10
  module MemoryBundle
12
- # An installed bundle laid out as a source folder again: the plugin, hooks,
11
+ # An installed bundle laid out as a source folder again: the plugin,
13
12
  # rules and scripts as they are installed (a dev install's links
14
13
  # followed), the memories as their bases (the bundle's bytes, not the
15
14
  # user's edits). A bundle's source may be gone or be a URL; this is what
16
15
  # `chi update` can check of what really runs.
17
16
  #
18
- # manifest.yml: its files:, hooks:, plugin: and scripts: are the record's
19
- # and the copy's (what is installed: a memory the install left out, or one
20
- # with no base, and a manifest hook the record lacks aren't listed, so the
21
- # check finds no missing-file for them). Every other key is the kept
17
+ # manifest.yml: its files:, plugin: and scripts: are the record's and
18
+ # the copy's (what is installed: a memory the install left out, or one
19
+ # with no base, isn't listed, so the check finds no missing-file for it).
20
+ # It has no hooks: and no hooks/, whatever the record or the kept
21
+ # manifest says: class-file hooks don't load (InstalledCheck names a
22
+ # record's once). Every other key is the kept
22
23
  # manifest.yml's when it is read (InstalledManifest origin :kept), so the
23
24
  # check sees its unknown keys too; else the record's mirror's.
24
25
  #
25
26
  # The shas are the files' own, so an edited plugin is checked as written.
26
- # A plugin, hook or script the record lists that isn't there stays
27
+ # A plugin or script the record lists that isn't there stays
27
28
  # listed, for the check's missing-file.
28
29
  module InstalledCopy
29
30
  module_function
@@ -45,7 +46,7 @@ module Samagotchi
45
46
  dir: dir, name: bundle.name, version: bundle.version.to_s.empty? ? "0" : bundle.version, trust_level: manifest.trust_level,
46
47
  files: memories(bundle, provenance, dir),
47
48
  file_descriptions: manifest.file_descriptions,
48
- hooks: hooks(bundle, provenance, dir), plugin: plugin(bundle, provenance, dir),
49
+ plugin: plugin(bundle, provenance, dir),
49
50
  scripts: copies(bundle.scripts, provenance.scripts_dir, File.join(dir, "scripts")),
50
51
  requires_chi: manifest.requires_chi, needs: manifest.needs,
51
52
  context_providers: manifest.context_providers, block_renderers: manifest.block_renderers,
@@ -57,7 +58,8 @@ module Samagotchi
57
58
  end
58
59
  private_class_method :write
59
60
 
60
- # What is installed, which the copy's manifest.yml takes from the record.
61
+ # What is installed, which the copy's manifest.yml takes from the
62
+ # record (hooks: none, so the kept manifest's isn't taken either).
61
63
  INSTALLED_KEYS = %w[name version files hooks plugin scripts].freeze
62
64
 
63
65
  # The written manifest's INSTALLED_KEYS and +failed+ keys (the value
@@ -80,15 +82,6 @@ module Samagotchi
80
82
  end
81
83
  private_class_method :memories
82
84
 
83
- # The recorded hooks with the metadata they load with (InstalledManifest#hooks).
84
- def hooks(bundle, provenance, dir)
85
- bundle.manifest.hooks.to_h do |file, hook|
86
- sha = copy(File.join(provenance.hooks_dir, file), File.join(dir, "hooks", file), hook.sha256)
87
- [file, hook.with(sha256: sha)]
88
- end
89
- end
90
- private_class_method :hooks
91
-
92
85
  def plugin(bundle, provenance, dir)
93
86
  return nil unless bundle.plugin
94
87
 
@@ -39,9 +39,6 @@ module Samagotchi
39
39
  #
40
40
  # requires_chi, trust_level the strings as recorded, nil when none
41
41
  # description the kept manifest's, nil from the record
42
- # hooks basename => BundleHook: each recorded hook,
43
- # the kept manifest's events, on_error and
44
- # priority with the record's sha
45
42
  # needs Manifest.parse_needs'
46
43
  # script_names the scripts/ files it lists
47
44
  # context_providers Array<ContextProviders::Provider>
@@ -61,7 +58,7 @@ module Samagotchi
61
58
  # floor_keys the kept manifest's Manifest#floor_keys (key =>
62
59
  # the first chi that read it), {} from the record
63
60
  # origin :kept or :record
64
- class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :hooks, :needs,
61
+ class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :needs,
65
62
  :script_names, :context_providers, :block_renderers, :session_settings,
66
63
  :session_setting_types, :settings, :setting_types, :file_descriptions,
67
64
  :plugin_required, :includes, :unknown_keys, :floor_keys, :problems, :origin)
@@ -130,7 +127,6 @@ module Samagotchi
130
127
  new(name: bundle.name, version: bundle.version,
131
128
  requires_chi: pick.call("requires_chi", kept.requires_chi, mirrored.requires_chi),
132
129
  trust_level: kept.trust_level, description: kept.description.empty? ? nil : kept.description,
133
- hooks: pick.call("hooks", kept_hooks(bundle.hooks, kept.hooks), bundle.hooks),
134
130
  needs: pick.call("needs", kept.needs, mirrored.needs),
135
131
  script_names: pick.call("scripts", kept.scripts.keys, mirrored.script_names),
136
132
  context_providers: pick.call("context_providers", kept.context_providers, mirrored.context_providers),
@@ -150,21 +146,11 @@ module Samagotchi
150
146
  {}
151
147
  end
152
148
 
153
- # Each recorded hook (what is installed): the kept manifest's metadata
154
- # for it with the record's sha; one the manifest doesn't list (a found
155
- # hooks/*.rb) as recorded. A manifest hook the record lacks isn't installed.
156
- def self.kept_hooks(recorded, declared)
157
- recorded.to_h do |file, hook|
158
- [file, declared.key?(file) ? declared[file].with(sha256: hook.sha256) : hook]
159
- end
160
- end
161
-
162
149
  def self.from_record(bundle)
163
150
  raw = recorded(bundle)
164
151
  problems = {}
165
152
  new(name: bundle.name, version: bundle.version,
166
153
  requires_chi: raw["requires_chi"]&.to_s, trust_level: raw["trust_level"]&.to_s, description: nil,
167
- hooks: bundle.hooks,
168
154
  needs: parsed(problems, "needs", [], Manifest::ValidationError) { Manifest.parse_needs(raw["needs"]) },
169
155
  script_names: bundle.scripts.keys,
170
156
  context_providers: parsed(problems, "context_providers", []) { ContextProviders.parse_list(raw["context_providers"]) },
@@ -204,7 +190,7 @@ module Samagotchi
204
190
  acc[key] = text unless text.empty?
205
191
  end
206
192
  end
207
- private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :kept_hooks, :from_record, :recorded, :parsed,
193
+ private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :from_record, :recorded, :parsed,
208
194
  :list, :types, :file_descriptions
209
195
 
210
196
  # Why the kept manifest.yml wasn't read: :missing, :stale or :edited (the class