samagotchi 0.156.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 +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -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/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- 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 +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- 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/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- 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/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "file_state"
|
|
4
|
+
|
|
5
|
+
module Samagotchi
|
|
6
|
+
module MemoryBundle
|
|
7
|
+
# What each command that acts on a bundle memory does in each FileState:
|
|
8
|
+
# one table per command, so a reader sees one command on one screen. A cell
|
|
9
|
+
# is keyed [kind, mark] or [kind, :any]; a dropped file another bundle
|
|
10
|
+
# lists too is [kind, :shared] (FileState#shared_with, asked only there).
|
|
11
|
+
# A dry run reads its command's row and doesn't execute it.
|
|
12
|
+
#
|
|
13
|
+
# A new kind or a new command adds a row in each table it reaches
|
|
14
|
+
# (docs/internals/bundles.md).
|
|
15
|
+
module FileActions
|
|
16
|
+
# do: what happens to the file's bytes
|
|
17
|
+
# :write_incoming the bundle's text replaces it (or is put there)
|
|
18
|
+
# :merge the text merge's result replaces it (Merger::Outcome)
|
|
19
|
+
# :decide not a function of kind and mark: the caller asks
|
|
20
|
+
# Merger::Upgrade#decision, and FileActions.decided
|
|
21
|
+
# turns its Outcome into the action
|
|
22
|
+
# :keep left as it is (already the bundle's, or kept)
|
|
23
|
+
# :skip left as it is, the bundle doesn't take it
|
|
24
|
+
# :move_to_trash moved to the trash (a removal)
|
|
25
|
+
# :refuse the command stops
|
|
26
|
+
# :restore the bundle's text put back (SystemBundle at its version)
|
|
27
|
+
# save_first: a copy of the old bytes goes to the trash before a write
|
|
28
|
+
# (when they differ from what is written)
|
|
29
|
+
# next_base: :on_disk (the file as the command leaves it), :incoming,
|
|
30
|
+
# :recorded (the base it has; none stays none), :none (no entry left)
|
|
31
|
+
# -- Merger::Outcome#base's vocabulary
|
|
32
|
+
# word: the report bucket the file is on (Installer#summary)
|
|
33
|
+
# record: the entry afterwards: :own (rebuilt, no mark), :keep_entry
|
|
34
|
+
# (kept with its base, no mark), :mark (marked behind), :hold_mark (as
|
|
35
|
+
# it was, mark and all), :clear_mark, :drop, :not_owned (never recorded)
|
|
36
|
+
Action = Data.define(:do, :save_first, :next_base, :word, :record) do
|
|
37
|
+
def initialize(do:, word:, next_base: :recorded, record: :own, save_first: false) = super
|
|
38
|
+
|
|
39
|
+
# The file stays behind the bundle, marked (a conflict to the report).
|
|
40
|
+
def behind? = record == :mark
|
|
41
|
+
|
|
42
|
+
# The bundle's text goes into the file: written (installed, forced or
|
|
43
|
+
# fast-forwarded) or merged into a local edit. The source file is the
|
|
44
|
+
# download, so the manifest's sha checks it (Installer's integrity
|
|
45
|
+
# check). :restore is SystemBundle's; it checks its own copy.
|
|
46
|
+
def takes_source? = %i[write_incoming merge].include?(self.do)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
WRITE = Action.new(do: :write_incoming, word: :installed, next_base: :on_disk)
|
|
50
|
+
UP_TO_DATE = Action.new(do: :keep, word: :up_to_date, next_base: :on_disk)
|
|
51
|
+
USER_SAME = Action.new(do: :keep, word: :up_to_date, next_base: :none, record: :not_owned)
|
|
52
|
+
USERS_OWN = Action.new(do: :skip, word: :kept_own, next_base: :none, record: :not_owned)
|
|
53
|
+
KEPT_OWN = Action.new(do: :skip, word: :kept_own, record: :keep_entry)
|
|
54
|
+
HELD = Action.new(do: :skip, word: :kept_own, record: :hold_mark)
|
|
55
|
+
FAST_FORWARD = Action.new(do: :write_incoming, word: :updated, next_base: :on_disk)
|
|
56
|
+
KEEP = Action.new(do: :keep, word: :kept)
|
|
57
|
+
# No base: no merge is possible, so the file stays behind, marked.
|
|
58
|
+
BEHIND = Action.new(do: :keep, word: :behind, record: :mark)
|
|
59
|
+
DECIDE = Action.new(do: :decide, word: nil)
|
|
60
|
+
REFUSE = Action.new(do: :refuse, word: nil, record: :hold_mark)
|
|
61
|
+
FORCED = Action.new(do: :write_incoming, word: :installed, next_base: :on_disk, save_first: true)
|
|
62
|
+
# The bundle dropped it: a plain install keeps it and its entry (the
|
|
63
|
+
# upgrade it points at moves it), an upgrade moves it unless edited.
|
|
64
|
+
DROPPED_KEPT = Action.new(do: :keep, word: nil, record: :keep_entry)
|
|
65
|
+
DROPPED_GONE = Action.new(do: :skip, word: nil, next_base: :none, record: :drop)
|
|
66
|
+
REMOVE = Action.new(do: :move_to_trash, word: :removed, next_base: :none, record: :drop)
|
|
67
|
+
KEPT_PRUNED = Action.new(do: :keep, word: :kept_pruned, record: :keep_entry)
|
|
68
|
+
KEPT_SHARED = Action.new(do: :keep, word: :kept_shared, next_base: :none, record: :drop)
|
|
69
|
+
|
|
70
|
+
# Rows every installing command shares: files the bundle doesn't own.
|
|
71
|
+
NOT_OWNED = {
|
|
72
|
+
%i[new any] => WRITE, %i[user_same any] => USER_SAME, %i[user_file any] => USERS_OWN,
|
|
73
|
+
%i[missing any] => WRITE, %i[level any] => UP_TO_DATE, %i[unreadable any] => REFUSE
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
# A plain install (a re-install over the record, or a fresh one).
|
|
77
|
+
INSTALL = NOT_OWNED.merge(
|
|
78
|
+
# A file never edited takes the source's text, as an upgrade's
|
|
79
|
+
# fast-forward does (D4); an edited one stays (an upgrade merges it).
|
|
80
|
+
%i[pristine none] => FAST_FORWARD, %i[pristine_no_base none] => FAST_FORWARD,
|
|
81
|
+
%i[edited none] => KEPT_OWN, %i[diverged none] => KEPT_OWN, %i[edited_no_base none] => KEPT_OWN,
|
|
82
|
+
# A marked file it skips has missed the bundle's change as much as before.
|
|
83
|
+
%i[pristine any] => HELD, %i[edited any] => HELD, %i[diverged any] => HELD,
|
|
84
|
+
%i[pristine_no_base any] => HELD, %i[edited_no_base any] => HELD,
|
|
85
|
+
%i[dropped_pristine any] => DROPPED_KEPT, %i[dropped_edited any] => DROPPED_KEPT,
|
|
86
|
+
%i[dropped_missing any] => DROPPED_GONE
|
|
87
|
+
).freeze
|
|
88
|
+
|
|
89
|
+
# install --force and upgrade --force: the bundle's text everywhere, an
|
|
90
|
+
# edit copied to the trash first. Its dropped rows are upgrade --force's
|
|
91
|
+
# (a plain install --force never prunes: its dropped files are INSTALL's).
|
|
92
|
+
FORCE = NOT_OWNED.merge(
|
|
93
|
+
# A file that already has the bundle's text is written all the same
|
|
94
|
+
# (nothing to save): it is installed, and checked, as any other.
|
|
95
|
+
%i[new any] => FORCED, %i[user_same any] => FORCED, %i[user_file any] => FORCED, %i[level any] => FORCED,
|
|
96
|
+
%i[pristine any] => FORCED, %i[edited any] => FORCED, %i[diverged any] => FORCED,
|
|
97
|
+
%i[pristine_no_base any] => FORCED, %i[edited_no_base any] => FORCED,
|
|
98
|
+
%i[dropped_pristine any] => REMOVE, %i[dropped_edited any] => REMOVE, %i[dropped_missing any] => DROPPED_GONE,
|
|
99
|
+
%i[dropped_pristine shared] => KEPT_SHARED, %i[dropped_edited shared] => KEPT_SHARED
|
|
100
|
+
).freeze
|
|
101
|
+
|
|
102
|
+
# An upgrade's 3-way view (Merger::Upgrade#outcome).
|
|
103
|
+
UPGRADE = NOT_OWNED.merge(
|
|
104
|
+
%i[pristine none] => FAST_FORWARD,
|
|
105
|
+
# A marked file that equals its base takes the update; a copy is kept.
|
|
106
|
+
%i[pristine current] => FAST_FORWARD.with(save_first: true),
|
|
107
|
+
# An older chi's mark: fast-forward unless it takes a line out and
|
|
108
|
+
# ShippedHistory can't prove the base (the legacy guard).
|
|
109
|
+
%i[pristine legacy] => DECIDE,
|
|
110
|
+
%i[edited any] => KEEP,
|
|
111
|
+
# The text merge: merged, a conflict, or already merged.
|
|
112
|
+
%i[diverged any] => DECIDE,
|
|
113
|
+
# No base: no merge. One never edited (the checksum vouches for it)
|
|
114
|
+
# takes the update as a file equal to its base would (U4); a marked
|
|
115
|
+
# one, or an edit, stays behind.
|
|
116
|
+
%i[pristine_no_base none] => FAST_FORWARD,
|
|
117
|
+
%i[pristine_no_base any] => BEHIND, %i[edited_no_base any] => BEHIND,
|
|
118
|
+
%i[dropped_pristine any] => REMOVE, %i[dropped_edited any] => KEPT_PRUNED, %i[dropped_missing any] => DROPPED_GONE,
|
|
119
|
+
%i[dropped_pristine shared] => KEPT_SHARED, %i[dropped_edited shared] => KEPT_SHARED
|
|
120
|
+
).freeze
|
|
121
|
+
|
|
122
|
+
# Remerge: the retry of a marked file at the same version. Unmarked
|
|
123
|
+
# files are never asked. Before the table, a mark whose source moved
|
|
124
|
+
# stays behind as it is (FileState#source_moved?).
|
|
125
|
+
REMERGE = {
|
|
126
|
+
%i[level any] => Action.new(do: :keep, word: :settled, next_base: :on_disk, record: :clear_mark),
|
|
127
|
+
%i[edited any] => Action.new(do: :keep, word: :settled, record: :clear_mark),
|
|
128
|
+
%i[diverged any] => DECIDE,
|
|
129
|
+
# It matches its old base: the next upgrade takes the update.
|
|
130
|
+
%i[pristine current] => BEHIND,
|
|
131
|
+
%i[pristine legacy] => DECIDE,
|
|
132
|
+
%i[pristine_no_base any] => BEHIND, %i[edited_no_base any] => BEHIND,
|
|
133
|
+
# Not shipped now: behind, nothing to merge.
|
|
134
|
+
%i[dropped_pristine any] => BEHIND, %i[dropped_edited any] => BEHIND,
|
|
135
|
+
%i[dropped_missing any] => Action.new(do: :skip, word: nil, record: :hold_mark),
|
|
136
|
+
# The next upgrade or a restore puts it back.
|
|
137
|
+
%i[missing any] => Action.new(do: :skip, word: nil, record: :hold_mark),
|
|
138
|
+
%i[unreadable any] => Action.new(do: :keep, word: :behind, record: :hold_mark)
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
141
|
+
# SystemBundle at its version: a memory not on disk is put back, one the
|
|
142
|
+
# record doesn't own too (research Q2, today's behaviour).
|
|
143
|
+
RESTORE = {
|
|
144
|
+
%i[missing any] => Action.new(do: :restore, word: :restored, next_base: :on_disk),
|
|
145
|
+
%i[new any] => Action.new(do: :restore, word: :restored, next_base: :on_disk)
|
|
146
|
+
}.freeze
|
|
147
|
+
|
|
148
|
+
# Uninstall, on the source-less kinds: an edit refuses (Uninstaller's
|
|
149
|
+
# --force moves it instead); a file another bundle lists stays.
|
|
150
|
+
UNINSTALL = {
|
|
151
|
+
%i[pristine any] => REMOVE, %i[pristine_no_base any] => REMOVE,
|
|
152
|
+
%i[edited any] => REFUSE, %i[edited_no_base any] => REFUSE, %i[unreadable any] => REFUSE,
|
|
153
|
+
%i[missing any] => DROPPED_GONE,
|
|
154
|
+
%i[pristine shared] => KEPT_SHARED, %i[pristine_no_base shared] => KEPT_SHARED,
|
|
155
|
+
%i[edited shared] => KEPT_SHARED, %i[edited_no_base shared] => KEPT_SHARED
|
|
156
|
+
}.freeze
|
|
157
|
+
|
|
158
|
+
TABLES = { install: INSTALL, force: FORCE, upgrade: UPGRADE, remerge: REMERGE, restore: RESTORE,
|
|
159
|
+
uninstall: UNINSTALL }.freeze
|
|
160
|
+
# The kinds whose row depends on another bundle listing the file.
|
|
161
|
+
SHARED_ASKED = %i[dropped_pristine dropped_edited pristine pristine_no_base edited edited_no_base].freeze
|
|
162
|
+
|
|
163
|
+
# The command's action for +state+: [kind, :shared] when another bundle
|
|
164
|
+
# lists a file that has such a row, else [kind, mark], then [kind, :any].
|
|
165
|
+
# @param command [Symbol] a TABLES key
|
|
166
|
+
# @return [Action, nil] nil: the command never meets the state
|
|
167
|
+
def self.for(command, state)
|
|
168
|
+
table = TABLES.fetch(command)
|
|
169
|
+
if SHARED_ASKED.include?(state.kind) && table.key?([state.kind, :shared]) && state.shared_with.any?
|
|
170
|
+
return table.fetch([state.kind, :shared])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
table[[state.kind, state.mark]] || table[[state.kind, :any]]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# A :decide cell's answer: the action Merger::Upgrade#decision's Outcome
|
|
177
|
+
# stands for. Remerge never fast-forwards (the next upgrade does) and
|
|
178
|
+
# settles a file with nothing left to miss.
|
|
179
|
+
# @param outcome [Merger::Outcome]
|
|
180
|
+
# @param command [Symbol] :upgrade or :remerge
|
|
181
|
+
def self.decided(outcome, command = :upgrade)
|
|
182
|
+
case outcome.status
|
|
183
|
+
when :merged then Action.new(do: :merge, word: :merged, next_base: :incoming, save_first: true)
|
|
184
|
+
when :conflict then BEHIND
|
|
185
|
+
when :fast_forward then command == :remerge ? BEHIND : FAST_FORWARD.with(save_first: true)
|
|
186
|
+
when :keep, :noop
|
|
187
|
+
command == :remerge ? Action.new(do: :keep, word: :settled, next_base: outcome.base, record: :clear_mark) : KEEP.with(next_base: outcome.base)
|
|
188
|
+
else raise ArgumentError, "no action for outcome #{outcome.status.inspect}"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -26,6 +26,19 @@ module Samagotchi
|
|
|
26
26
|
hex = hex(path)
|
|
27
27
|
hex && "sha256:#{hex}"
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
# The integrity check's one warning: +path+'s bytes against the sha
|
|
31
|
+
# hex a manifest declares for it. nil when they match, or when there is
|
|
32
|
+
# nothing to check (no sha declared, no file).
|
|
33
|
+
# @param label [String] what the file is ("a.md", "hook guard.rb")
|
|
34
|
+
def self.mismatch(label, path, expected)
|
|
35
|
+
return nil if expected.nil? || expected.empty?
|
|
36
|
+
|
|
37
|
+
actual = hex(path)
|
|
38
|
+
return nil if actual.nil? || actual == expected
|
|
39
|
+
|
|
40
|
+
"Checksum mismatch for #{label}: expected #{expected[0..7]}..., got #{actual[0..7]}..."
|
|
41
|
+
end
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "file_digest"
|
|
4
|
+
require_relative "installed_bundle"
|
|
5
|
+
require_relative "provenance"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
module MemoryBundle
|
|
9
|
+
# One bundle memory as a command meets it: which of a few states it is in,
|
|
10
|
+
# computed once from the file (D), its base (B), the bundle's text (I) and
|
|
11
|
+
# the record's entry (R). Derived, never stored. FileActions says what each
|
|
12
|
+
# acting command does in each state.
|
|
13
|
+
#
|
|
14
|
+
# kind, with a source (install, upgrade, remerge, restore, plan):
|
|
15
|
+
# :new not the bundle's, not on disk
|
|
16
|
+
# :user_same not the bundle's, on disk with I's bytes
|
|
17
|
+
# :user_file not the bundle's, other bytes (the user's own file)
|
|
18
|
+
# :missing the bundle's, not on disk
|
|
19
|
+
# :level the bundle's, D == I
|
|
20
|
+
# :pristine never edited (#edited?: D == R's checksum, or with
|
|
21
|
+
# none D == B): the bundle's text as installed
|
|
22
|
+
# :edited edited, D != B, I == B: the user changed it, the
|
|
23
|
+
# bundle didn't
|
|
24
|
+
# :diverged edited, D != B, I != B: both changed it
|
|
25
|
+
# :pristine_no_base no B, never edited (D == R's checksum)
|
|
26
|
+
# :edited_no_base no B, edited (or no checksum to tell); or a B equal
|
|
27
|
+
# to D that the checksum calls edited: no base to
|
|
28
|
+
# merge from
|
|
29
|
+
# :dropped_pristine / :dropped_edited / :dropped_missing the bundle no
|
|
30
|
+
# longer ships it (no I): unedited, edited, gone
|
|
31
|
+
# :unreadable on disk, but it can't be read
|
|
32
|
+
# Edited or not is always the one rule (#edited?, the checksum first);
|
|
33
|
+
# the base then says what the user changed.
|
|
34
|
+
#
|
|
35
|
+
# kind, with no source (status, uninstall, diff): I is unknown, so only
|
|
36
|
+
# :pristine :edited :pristine_no_base :edited_no_base :missing :unreadable,
|
|
37
|
+
# from #edited? alone.
|
|
38
|
+
#
|
|
39
|
+
# mark: :none, :current (a mark this chi wrote) or :legacy (an older
|
|
40
|
+
# chi's, InstalledBundle.legacy_mark?).
|
|
41
|
+
FileState = Data.define(:key, :kind, :mark, :digests, :entry, :paths, :claimants) do
|
|
42
|
+
# Provenance.unchanged?'s answer negated: the one edited rule (checksum
|
|
43
|
+
# first, then the base). A file it can't read counts as edited.
|
|
44
|
+
def edited?
|
|
45
|
+
return false if entry.nil?
|
|
46
|
+
|
|
47
|
+
!Provenance.unchanged?(paths[:current], entry, paths[:base])
|
|
48
|
+
rescue SystemCallError
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def marked? = mark != :none
|
|
53
|
+
|
|
54
|
+
# A marked file whose source moved since the mark: the mark speaks of
|
|
55
|
+
# other text than the bundle ships now (Remerge's first question). A mark
|
|
56
|
+
# that names no text (an older chi's) never moved.
|
|
57
|
+
def source_moved?
|
|
58
|
+
marked? && !digests.marked_incoming.nil? && !digests.incoming.nil? && digests.marked_incoming != digests.incoming
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The other installed bundles that list the file too. Asked lazily: only
|
|
62
|
+
# a dropped file and an uninstall need it (Provenance.claimants reads
|
|
63
|
+
# every record).
|
|
64
|
+
def shared_with = claimants.call
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The digests of the file's texts, each read once: disk (:unreadable when
|
|
68
|
+
# it can't be read), base, incoming (nil with no source), the record's
|
|
69
|
+
# checksum, and the text a mark speaks of (nil without a mark or for an
|
|
70
|
+
# older chi's mark).
|
|
71
|
+
FileState::Digests = Data.define(:disk, :base, :incoming, :recorded, :marked_incoming)
|
|
72
|
+
|
|
73
|
+
class << FileState
|
|
74
|
+
# @param record [InstalledBundle, nil] the bundle's record
|
|
75
|
+
# @param provenance [Provenance] the record's (for the base path)
|
|
76
|
+
# @param source_dir [String, nil] the bundle's incoming dir; nil: the
|
|
77
|
+
# source-less kinds
|
|
78
|
+
# @param claimants [#call, nil] key => other bundles listing it
|
|
79
|
+
# (Provenance.claimants), called at most once
|
|
80
|
+
# @param shipped [Boolean] false: the bundle no longer ships the key
|
|
81
|
+
# (a dropped kind), whatever its source dir holds by that name
|
|
82
|
+
# @return [FileState]
|
|
83
|
+
def of(key, record:, provenance:, target_dir:, source_dir:, claimants: nil, shipped: true)
|
|
84
|
+
entry = record&.files&.fetch(key, nil)
|
|
85
|
+
paths = { base: provenance.base_path(key), current: File.join(target_dir, key),
|
|
86
|
+
incoming: source_dir && File.join(source_dir, key) }
|
|
87
|
+
mark = mark_of(entry)
|
|
88
|
+
digests = FileState::Digests.new(
|
|
89
|
+
disk: digest(paths[:current]), base: entry ? digest(paths[:base]) : nil,
|
|
90
|
+
incoming: source_dir && shipped ? digest(paths[:incoming]) : nil, recorded: entry && Provenance.recorded_sha(entry[:checksum]),
|
|
91
|
+
marked_incoming: mark == :none ? nil : entry[:incoming_sha]
|
|
92
|
+
)
|
|
93
|
+
shared = nil
|
|
94
|
+
lookup = -> { shared ||= claimants ? claimants.call(key) : [] }
|
|
95
|
+
state = FileState.new(key: key, kind: nil, mark: mark, digests: digests, entry: entry, paths: paths, claimants: lookup)
|
|
96
|
+
state.with(kind: source_dir ? kind_with_source(state) : kind_without_source(state))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def mark_of(entry)
|
|
102
|
+
return :none unless entry && entry[:conflict]
|
|
103
|
+
|
|
104
|
+
InstalledBundle.legacy_mark?(entry) ? :legacy : :current
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def digest(path)
|
|
108
|
+
FileDigest.hex(path)
|
|
109
|
+
rescue SystemCallError
|
|
110
|
+
:unreadable
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def kind_with_source(state)
|
|
114
|
+
d = state.digests
|
|
115
|
+
return not_owned(d) unless state.entry
|
|
116
|
+
return :unreadable if d.disk == :unreadable
|
|
117
|
+
return dropped(state) if d.incoming.nil?
|
|
118
|
+
return :missing if d.disk.nil?
|
|
119
|
+
return :level if d.disk == d.incoming
|
|
120
|
+
return state.edited? ? :edited_no_base : :pristine_no_base if d.base.nil?
|
|
121
|
+
return :pristine unless state.edited?
|
|
122
|
+
# Edited by the one rule, yet equal to its base: the base can't say
|
|
123
|
+
# what the user changed (it isn't the text the record vouches for),
|
|
124
|
+
# so there is none to merge from.
|
|
125
|
+
return :edited_no_base if d.disk == d.base
|
|
126
|
+
|
|
127
|
+
d.incoming == d.base ? :edited : :diverged
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def kind_without_source(state)
|
|
131
|
+
d = state.digests
|
|
132
|
+
return :unreadable if d.disk == :unreadable
|
|
133
|
+
return :missing if d.disk.nil?
|
|
134
|
+
return state.edited? ? :edited_no_base : :pristine_no_base if d.base.nil?
|
|
135
|
+
|
|
136
|
+
state.edited? ? :edited : :pristine
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def not_owned(digests)
|
|
140
|
+
return :new if digests.disk.nil?
|
|
141
|
+
|
|
142
|
+
digests.disk == digests.incoming ? :user_same : :user_file
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def dropped(state)
|
|
146
|
+
return :dropped_missing if state.digests.disk.nil?
|
|
147
|
+
|
|
148
|
+
state.edited? ? :dropped_edited : :dropped_pristine
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
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,
|
|
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,
|
|
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:,
|
|
19
|
-
#
|
|
20
|
-
# with no base,
|
|
21
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
|
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,20 +82,12 @@ 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
|
|
|
95
88
|
file = bundle.plugin.file
|
|
96
|
-
{ file: file, sha256: copy(provenance.plugin_path(bundle), File.join(dir, file), bundle.plugin.sha256)
|
|
89
|
+
{ file: file, sha256: copy(provenance.plugin_path(bundle), File.join(dir, file), bundle.plugin.sha256),
|
|
90
|
+
required: bundle.manifest.plugin_required }
|
|
97
91
|
end
|
|
98
92
|
private_class_method :plugin
|
|
99
93
|
|
|
@@ -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>
|
|
@@ -50,16 +47,21 @@ module Samagotchi
|
|
|
50
47
|
# as Manifest reads them (settings nil when
|
|
51
48
|
# the manifest said nothing of them)
|
|
52
49
|
# file_descriptions file key => the memory's description
|
|
50
|
+
# plugin_required plugin: required: true (a guardrail plugin:
|
|
51
|
+
# one that fails to load denies every tool
|
|
52
|
+
# call): the record's (PluginRef#required,
|
|
53
|
+
# recorded at install) or the kept manifest's,
|
|
54
|
+
# so a lost or edited kept file keeps it
|
|
53
55
|
# includes a profile's recorded members (the record's
|
|
54
56
|
# identity, never the kept file's), nil for a plain bundle
|
|
55
57
|
# unknown_keys the kept manifest's keys this chi doesn't know
|
|
56
58
|
# floor_keys the kept manifest's Manifest#floor_keys (key =>
|
|
57
59
|
# the first chi that read it), {} from the record
|
|
58
60
|
# origin :kept or :record
|
|
59
|
-
class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :
|
|
61
|
+
class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :needs,
|
|
60
62
|
:script_names, :context_providers, :block_renderers, :session_settings,
|
|
61
63
|
:session_setting_types, :settings, :setting_types, :file_descriptions,
|
|
62
|
-
:includes, :unknown_keys, :floor_keys, :problems, :origin)
|
|
64
|
+
:plugin_required, :includes, :unknown_keys, :floor_keys, :problems, :origin)
|
|
63
65
|
# The key problems names the kept file's standing under.
|
|
64
66
|
KEPT_FILE = "manifest.yml"
|
|
65
67
|
# How many manifests the cache holds before it starts again.
|
|
@@ -125,7 +127,6 @@ module Samagotchi
|
|
|
125
127
|
new(name: bundle.name, version: bundle.version,
|
|
126
128
|
requires_chi: pick.call("requires_chi", kept.requires_chi, mirrored.requires_chi),
|
|
127
129
|
trust_level: kept.trust_level, description: kept.description.empty? ? nil : kept.description,
|
|
128
|
-
hooks: pick.call("hooks", kept_hooks(bundle.hooks, kept.hooks), bundle.hooks),
|
|
129
130
|
needs: pick.call("needs", kept.needs, mirrored.needs),
|
|
130
131
|
script_names: pick.call("scripts", kept.scripts.keys, mirrored.script_names),
|
|
131
132
|
context_providers: pick.call("context_providers", kept.context_providers, mirrored.context_providers),
|
|
@@ -133,6 +134,7 @@ module Samagotchi
|
|
|
133
134
|
session_settings: session.session_settings, session_setting_types: session.session_setting_types,
|
|
134
135
|
settings: settings.settings, setting_types: settings.setting_types,
|
|
135
136
|
file_descriptions: pick.call("files", kept.file_descriptions, mirrored.file_descriptions),
|
|
137
|
+
plugin_required: kept.plugin&.dig(:required) == true || mirrored.plugin_required,
|
|
136
138
|
includes: bundle.includes,
|
|
137
139
|
unknown_keys: kept.unknown_keys.map(&:to_s), floor_keys: floors(kept), problems: problems.freeze, origin: :kept)
|
|
138
140
|
end
|
|
@@ -144,21 +146,11 @@ module Samagotchi
|
|
|
144
146
|
{}
|
|
145
147
|
end
|
|
146
148
|
|
|
147
|
-
# Each recorded hook (what is installed): the kept manifest's metadata
|
|
148
|
-
# for it with the record's sha; one the manifest doesn't list (a found
|
|
149
|
-
# hooks/*.rb) as recorded. A manifest hook the record lacks isn't installed.
|
|
150
|
-
def self.kept_hooks(recorded, declared)
|
|
151
|
-
recorded.to_h do |file, hook|
|
|
152
|
-
[file, declared.key?(file) ? declared[file].with(sha256: hook.sha256) : hook]
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
149
|
def self.from_record(bundle)
|
|
157
150
|
raw = recorded(bundle)
|
|
158
151
|
problems = {}
|
|
159
152
|
new(name: bundle.name, version: bundle.version,
|
|
160
153
|
requires_chi: raw["requires_chi"]&.to_s, trust_level: raw["trust_level"]&.to_s, description: nil,
|
|
161
|
-
hooks: bundle.hooks,
|
|
162
154
|
needs: parsed(problems, "needs", [], Manifest::ValidationError) { Manifest.parse_needs(raw["needs"]) },
|
|
163
155
|
script_names: bundle.scripts.keys,
|
|
164
156
|
context_providers: parsed(problems, "context_providers", []) { ContextProviders.parse_list(raw["context_providers"]) },
|
|
@@ -168,6 +160,7 @@ module Samagotchi
|
|
|
168
160
|
settings: list(raw["settings"]),
|
|
169
161
|
setting_types: types(raw["setting_types"]),
|
|
170
162
|
file_descriptions: file_descriptions(bundle.files),
|
|
163
|
+
plugin_required: bundle.plugin&.required == true,
|
|
171
164
|
includes: bundle.includes, unknown_keys: [], floor_keys: {}, problems: problems.freeze, origin: :record)
|
|
172
165
|
end
|
|
173
166
|
|
|
@@ -197,7 +190,7 @@ module Samagotchi
|
|
|
197
190
|
acc[key] = text unless text.empty?
|
|
198
191
|
end
|
|
199
192
|
end
|
|
200
|
-
private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :
|
|
193
|
+
private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :from_record, :recorded, :parsed,
|
|
201
194
|
:list, :types, :file_descriptions
|
|
202
195
|
|
|
203
196
|
# Why the kept manifest.yml wasn't read: :missing, :stale or :edited (the class
|