appydave-tools 0.71.1 → 0.73.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 +14 -0
- data/docs/backlog.md +1 -1
- data/lib/appydave/tools/jump/cli.rb +5 -3
- data/lib/appydave/tools/jump/commands/generate.rb +278 -14
- data/lib/appydave/tools/version.rb +1 -1
- data/package.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53447fe92cfd604234875affe143a083bc07b267ccf92ad11782e6640e440fa6
|
|
4
|
+
data.tar.gz: 74ed454547b2f4a3a5d859ca1bc41228b7efc340dc31c8f3a174b6ff4e1774dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61ab02e5529f5a95be3aeeb85674e2f92d9db9d13571aafefc6569cec9f30f967a5d79b48885ad2f636816b50380c39ecdaf67d05b3d403c25ce06517efb8761
|
|
7
|
+
data.tar.gz: db15735eb6b14e5060a9f533231067222402edff360e5034e997c03483b8755692751ce54e95628eea2893e2f649c75a7f9af6e625c7c1be661c8f392d2908e9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [0.72.0](https://github.com/appydave/appydave-tools/compare/v0.71.1...v0.72.0) (2025-12-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add ah-help generate target for aliases-help.zsh format ([874fde9](https://github.com/appydave/appydave-tools/commit/874fde982a2b66ba7d9ec8f8078d79fcde7db4f6))
|
|
7
|
+
|
|
8
|
+
## [0.71.1](https://github.com/appydave/appydave-tools/compare/v0.71.0...v0.71.1) (2025-12-15)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* resolve jump get/remove returning 'No locations found' by making Search#get return results array consistent with search/list ([8ea39b5](https://github.com/appydave/appydave-tools/commit/8ea39b5fdd9bb9fe55c22f9cb86c8b66782c0f09))
|
|
14
|
+
|
|
1
15
|
# [0.71.0](https://github.com/appydave/appydave-tools/compare/v0.70.0...v0.71.0) (2025-12-14)
|
|
2
16
|
|
|
3
17
|
|
data/docs/backlog.md
CHANGED
|
@@ -129,7 +129,7 @@ When `get agent-workflow-build` failed, it suggested: "Did you mean: test-minima
|
|
|
129
129
|
- Personal skill at `~/.claude/skills/jump/SKILL.md`
|
|
130
130
|
- Description includes brand names (appydave, voz, supportsignal, joy, aitldr, kiros), products (flivideo, storyline, klueless, dam), and trigger keywords
|
|
131
131
|
- Primary mode: Jump CLI commands (`jump search`, `jump get`, `jump list`)
|
|
132
|
-
- Fallback mode: Direct JSON read from `/ad/brains/brand-
|
|
132
|
+
- Fallback mode: Direct JSON read from `/ad/brains/brand-dave/data-systems/collections/jump/current.json`
|
|
133
133
|
- Tool restrictions: `allowed-tools: Read, Bash, Grep, Glob`
|
|
134
134
|
- Includes natural language examples and key location reference table
|
|
135
135
|
|
|
@@ -245,7 +245,7 @@ module Appydave
|
|
|
245
245
|
|
|
246
246
|
unless target
|
|
247
247
|
output.puts 'Usage: jump generate <target> [--output <file>] [--output-dir <dir>]'
|
|
248
|
-
output.puts 'Targets: aliases, help, all'
|
|
248
|
+
output.puts 'Targets: aliases, help, ah-help, all'
|
|
249
249
|
return EXIT_INVALID_INPUT
|
|
250
250
|
end
|
|
251
251
|
|
|
@@ -542,16 +542,18 @@ module Appydave
|
|
|
542
542
|
Targets:
|
|
543
543
|
aliases Generate shell alias file (aliases-jump.zsh)
|
|
544
544
|
help Generate help content for fzf (jump-help.txt)
|
|
545
|
-
|
|
545
|
+
ah-help Generate content for aliases-help.zsh (ah function format)
|
|
546
|
+
all Generate both aliases and help files
|
|
546
547
|
|
|
547
548
|
Options:
|
|
548
|
-
--output <file> Write to specific file (for aliases, help)
|
|
549
|
+
--output <file> Write to specific file (for aliases, help, ah-help)
|
|
549
550
|
--output-dir <dir> Write to directory (for all)
|
|
550
551
|
|
|
551
552
|
Examples:
|
|
552
553
|
jump generate aliases
|
|
553
554
|
jump generate aliases --output ~/.oh-my-zsh/custom/aliases-jump.zsh
|
|
554
555
|
jump generate help --output ~/.oh-my-zsh/custom/data/jump-help.txt
|
|
556
|
+
jump generate ah-help --output ~/.oh-my-zsh/custom/aliases-help.zsh
|
|
555
557
|
jump generate all --output-dir ~/.oh-my-zsh/custom/
|
|
556
558
|
HELP
|
|
557
559
|
end
|
|
@@ -6,7 +6,19 @@ module Appydave
|
|
|
6
6
|
module Commands
|
|
7
7
|
# Generate command creates shell aliases and help content
|
|
8
8
|
class Generate < Base
|
|
9
|
-
VALID_TARGETS = %w[aliases help all].freeze
|
|
9
|
+
VALID_TARGETS = %w[aliases help ah-help all].freeze
|
|
10
|
+
MAX_BACKUPS = 10
|
|
11
|
+
|
|
12
|
+
TITLE_MAPPINGS = {
|
|
13
|
+
'appydave' => 'Appy Dave',
|
|
14
|
+
'supportsignal' => 'SupportSignal',
|
|
15
|
+
'beauty-and-joy' => 'Beauty & Joy',
|
|
16
|
+
'david-cruwys' => 'David Cruwys',
|
|
17
|
+
'guy-monroe' => 'Guy Monroe',
|
|
18
|
+
'voz' => 'VOZ',
|
|
19
|
+
'kiros' => 'Kiros',
|
|
20
|
+
'aitldr' => 'AITLDR'
|
|
21
|
+
}.freeze
|
|
10
22
|
|
|
11
23
|
attr_reader :target, :output_path, :output_dir
|
|
12
24
|
|
|
@@ -28,7 +40,9 @@ module Appydave
|
|
|
28
40
|
)
|
|
29
41
|
end
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
# Convert hyphenated targets to underscored method names
|
|
44
|
+
method_name = "generate_#{target.tr('-', '_')}"
|
|
45
|
+
send(method_name)
|
|
32
46
|
end
|
|
33
47
|
|
|
34
48
|
private
|
|
@@ -43,6 +57,11 @@ module Appydave
|
|
|
43
57
|
write_or_return(content, output_path, 'jump-help.txt')
|
|
44
58
|
end
|
|
45
59
|
|
|
60
|
+
def generate_ah_help
|
|
61
|
+
content = build_ah_help_content
|
|
62
|
+
write_or_return(content, output_path, 'aliases-help.zsh')
|
|
63
|
+
end
|
|
64
|
+
|
|
46
65
|
def generate_all
|
|
47
66
|
aliases_content = build_aliases_content
|
|
48
67
|
help_content = build_help_content
|
|
@@ -75,20 +94,25 @@ module Appydave
|
|
|
75
94
|
lines << "# Generated by: jump generate aliases (#{Time.now.strftime('%Y-%m-%d %H:%M')})"
|
|
76
95
|
lines << '# Source: ~/.config/appydave/locations.json'
|
|
77
96
|
lines << ''
|
|
78
|
-
lines << '# Usage: source this file in your .zshrc'
|
|
79
|
-
lines << '# source ~/.oh-my-zsh/custom/aliases-jump.zsh'
|
|
80
|
-
lines << ''
|
|
81
97
|
|
|
82
|
-
#
|
|
83
|
-
|
|
98
|
+
# Build grouped sections using smart logic
|
|
99
|
+
sections = build_smart_alias_sections
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
lines <<
|
|
101
|
+
sections.each do |section|
|
|
102
|
+
# Add major section separator if needed
|
|
103
|
+
if section[:major]
|
|
104
|
+
lines << '# ========================================'
|
|
105
|
+
lines << "# #{section[:title]}"
|
|
106
|
+
lines << '# ========================================'
|
|
107
|
+
else
|
|
108
|
+
lines << "# #{section[:title]}"
|
|
91
109
|
end
|
|
110
|
+
|
|
111
|
+
section[:locations].each do |loc|
|
|
112
|
+
path = format_display_path(loc.path)
|
|
113
|
+
lines << "alias #{loc.jump}=\"cd #{path}\""
|
|
114
|
+
end
|
|
115
|
+
|
|
92
116
|
lines << ''
|
|
93
117
|
end
|
|
94
118
|
|
|
@@ -115,6 +139,99 @@ module Appydave
|
|
|
115
139
|
lines.join("\n")
|
|
116
140
|
end
|
|
117
141
|
|
|
142
|
+
def build_ah_help_content
|
|
143
|
+
lines = []
|
|
144
|
+
|
|
145
|
+
# Group locations into logical sections
|
|
146
|
+
sections = group_locations_for_ah_help
|
|
147
|
+
|
|
148
|
+
sections.each do |section_name, locations|
|
|
149
|
+
lines << "## #{section_name}"
|
|
150
|
+
|
|
151
|
+
locations.each do |loc|
|
|
152
|
+
path = format_display_path(loc.path)
|
|
153
|
+
tags = build_ah_tags(loc)
|
|
154
|
+
|
|
155
|
+
# Format: alias (24 chars) + path + tags
|
|
156
|
+
alias_col = loc.jump.ljust(24)
|
|
157
|
+
path_with_tags = "#{path}#{' ' * [1, 48 - path.length].max}#{tags}"
|
|
158
|
+
|
|
159
|
+
lines << "#{alias_col}#{path_with_tags}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
lines << ''
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
lines.join("\n")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def format_display_path(path)
|
|
169
|
+
# Collapse home directory to ~
|
|
170
|
+
expanded = path_validator.expand(path)
|
|
171
|
+
home = Dir.home
|
|
172
|
+
expanded.start_with?(home) ? expanded.sub(home, '~') : expanded
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def build_ah_tags(loc)
|
|
176
|
+
tags = [loc.type, loc.brand, loc.client].compact.reject(&:empty?)
|
|
177
|
+
tags.concat(loc.tags) if loc.tags
|
|
178
|
+
tags.uniq.map { |t| "##{t}" }.join(' ')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def group_locations_for_ah_help
|
|
182
|
+
sections = {
|
|
183
|
+
'Base Directories' => [],
|
|
184
|
+
'Brand Projects - AppyDave' => [],
|
|
185
|
+
'Brand Projects - Other' => [],
|
|
186
|
+
'Client Projects' => [],
|
|
187
|
+
'Video Projects' => [],
|
|
188
|
+
'Ruby Gems' => [],
|
|
189
|
+
'Reference & Archives' => [],
|
|
190
|
+
'Other' => []
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
config.locations.each do |loc|
|
|
194
|
+
section = determine_ah_section(loc)
|
|
195
|
+
sections[section] << loc
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Remove empty sections and sort locations within each
|
|
199
|
+
sections.reject { |_, locs| locs.empty? }.transform_values do |locs|
|
|
200
|
+
locs.sort_by(&:jump)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
205
|
+
def determine_ah_section(loc)
|
|
206
|
+
type = loc.type&.downcase
|
|
207
|
+
brand = loc.brand&.downcase
|
|
208
|
+
|
|
209
|
+
case type
|
|
210
|
+
when 'monorepo', 'config'
|
|
211
|
+
return 'Base Directories' if loc.client.nil? && loc.brand.nil?
|
|
212
|
+
when 'video'
|
|
213
|
+
return 'Video Projects'
|
|
214
|
+
when 'gem'
|
|
215
|
+
return 'Ruby Gems'
|
|
216
|
+
when 'client'
|
|
217
|
+
return 'Client Projects'
|
|
218
|
+
when 'archive', 'reference'
|
|
219
|
+
return 'Reference & Archives'
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
return 'Client Projects' if loc.client
|
|
223
|
+
|
|
224
|
+
case brand
|
|
225
|
+
when 'appydave'
|
|
226
|
+
'Brand Projects - AppyDave'
|
|
227
|
+
when 'beauty-and-joy', 'david-cruwys', 'aitldr'
|
|
228
|
+
'Brand Projects - Other'
|
|
229
|
+
else
|
|
230
|
+
'Other'
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
234
|
+
|
|
118
235
|
def group_locations_for_aliases
|
|
119
236
|
grouped = {}
|
|
120
237
|
|
|
@@ -130,15 +247,132 @@ module Appydave
|
|
|
130
247
|
end
|
|
131
248
|
end
|
|
132
249
|
|
|
250
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
251
|
+
def build_smart_alias_sections
|
|
252
|
+
sections = []
|
|
253
|
+
locations = config.locations.dup
|
|
254
|
+
|
|
255
|
+
# 1. JUMPS - Base directories (monorepo/config without brand/client)
|
|
256
|
+
jumps = locations.select { |l| %w[monorepo config].include?(l.type) && l.brand.nil? && l.client.nil? }
|
|
257
|
+
add_section(sections, 'JUMPS', jumps) unless jumps.empty?
|
|
258
|
+
locations -= jumps
|
|
259
|
+
|
|
260
|
+
# 2. Brain / Second Brain (only non-client brains)
|
|
261
|
+
brains = locations.select { |l| l.type == 'brain' && l.client.nil? }
|
|
262
|
+
add_section(sections, 'Brain / Second Brain', brains) unless brains.empty?
|
|
263
|
+
locations -= brains
|
|
264
|
+
|
|
265
|
+
# 3. Appy Dave (including david-cruwys ecosystem docs, excluding video, archive, and subprojects)
|
|
266
|
+
appydave_all = locations.select do |l|
|
|
267
|
+
%w[appydave david-cruwys].include?(l.brand) && !%w[video archive brain].include?(l.type)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Extract subgroups
|
|
271
|
+
flivideo = appydave_all.select { |l| l.key.start_with?('flivideo') }
|
|
272
|
+
storyline = appydave_all.select { |l| l.key.start_with?('storyline') }
|
|
273
|
+
awb = appydave_all.select { |l| l.key.start_with?('agent-workflow', 'awb') }
|
|
274
|
+
klueless = appydave_all.select { |l| l.key == 'klueless' }
|
|
275
|
+
|
|
276
|
+
appydave_main = appydave_all - flivideo - storyline - awb - klueless
|
|
277
|
+
|
|
278
|
+
add_section(sections, 'Appy Dave', appydave_main) unless appydave_main.empty?
|
|
279
|
+
add_section(sections, 'KlueLess', klueless) unless klueless.empty?
|
|
280
|
+
add_section(sections, 'FliVideo', flivideo) unless flivideo.empty?
|
|
281
|
+
add_section(sections, 'Storyline App', storyline) unless storyline.empty?
|
|
282
|
+
add_section(sections, 'Agent Workflow Builder', awb) unless awb.empty?
|
|
283
|
+
|
|
284
|
+
locations -= appydave_all
|
|
285
|
+
|
|
286
|
+
# 4. CLIENT WORK (major section)
|
|
287
|
+
clients = locations.select { |l| l.client && l.type != 'video' }
|
|
288
|
+
unless clients.empty?
|
|
289
|
+
# Add major separator
|
|
290
|
+
sections << { title: 'CLIENT WORK', major: true, locations: [] }
|
|
291
|
+
|
|
292
|
+
# Group by client
|
|
293
|
+
clients.group_by(&:client).sort.each do |client_name, client_locs|
|
|
294
|
+
title = format_title(client_name, 'Client')
|
|
295
|
+
add_section(sections, title, client_locs)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
locations -= clients
|
|
299
|
+
|
|
300
|
+
# 5. VIDEO PROJECTS (major section)
|
|
301
|
+
videos = locations.select { |l| l.type == 'video' }
|
|
302
|
+
unless videos.empty?
|
|
303
|
+
sections << { title: 'VIDEO PROJECTS', major: true, locations: [] }
|
|
304
|
+
|
|
305
|
+
# Split into brand and client videos
|
|
306
|
+
brand_videos = videos.select(&:brand)
|
|
307
|
+
client_videos = videos.select(&:client)
|
|
308
|
+
other_videos = videos - brand_videos - client_videos
|
|
309
|
+
|
|
310
|
+
add_section(sections, 'Brand Video Projects', brand_videos) unless brand_videos.empty?
|
|
311
|
+
add_section(sections, 'Client Video Projects', client_videos) unless client_videos.empty?
|
|
312
|
+
add_section(sections, 'Video Projects', other_videos) unless other_videos.empty?
|
|
313
|
+
end
|
|
314
|
+
locations -= videos
|
|
315
|
+
|
|
316
|
+
# 6. Beauty & Joy (non-video)
|
|
317
|
+
joy = locations.select { |l| l.brand == 'beauty-and-joy' }
|
|
318
|
+
add_section(sections, 'Beauty & Joy', joy, major: true) unless joy.empty?
|
|
319
|
+
locations -= joy
|
|
320
|
+
|
|
321
|
+
# 7. Archive
|
|
322
|
+
archives = locations.select { |l| l.type == 'archive' }
|
|
323
|
+
add_section(sections, 'Archive', archives) unless archives.empty?
|
|
324
|
+
locations -= archives
|
|
325
|
+
|
|
326
|
+
# 8. GEMs
|
|
327
|
+
gems = locations.select { |l| l.type == 'gem' }
|
|
328
|
+
add_section(sections, 'GEMs', gems) unless gems.empty?
|
|
329
|
+
locations -= gems
|
|
330
|
+
|
|
331
|
+
# 9. Reference / 3rd Party
|
|
332
|
+
refs = locations.select { |l| l.type == 'reference' }
|
|
333
|
+
add_section(sections, 'Reference / 3rd Party', refs) unless refs.empty?
|
|
334
|
+
locations -= refs
|
|
335
|
+
|
|
336
|
+
# 10. Experiments
|
|
337
|
+
experiments = locations.select { |l| l.type == 'experiments' }
|
|
338
|
+
add_section(sections, 'Experiments', experiments) unless experiments.empty?
|
|
339
|
+
locations -= experiments
|
|
340
|
+
|
|
341
|
+
# 11. Other (anything remaining)
|
|
342
|
+
add_section(sections, 'Other', locations) unless locations.empty?
|
|
343
|
+
|
|
344
|
+
sections.reject { |s| s[:locations].empty? && !s[:major] }
|
|
345
|
+
end
|
|
346
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
347
|
+
|
|
348
|
+
def add_section(sections, title, locations, major: false)
|
|
349
|
+
return if locations.empty?
|
|
350
|
+
|
|
351
|
+
sections << {
|
|
352
|
+
title: title,
|
|
353
|
+
major: major,
|
|
354
|
+
locations: locations.sort_by(&:jump)
|
|
355
|
+
}
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def format_title(name, suffix = nil)
|
|
359
|
+
title = TITLE_MAPPINGS[name] || name.split('-').map(&:capitalize).join(' ')
|
|
360
|
+
suffix ? "#{title} #{suffix}" : title
|
|
361
|
+
end
|
|
362
|
+
|
|
133
363
|
def write_or_return(content, path, default_filename)
|
|
134
364
|
if path
|
|
365
|
+
backup_result = backup_existing_file(path)
|
|
135
366
|
FileUtils.mkdir_p(File.dirname(path))
|
|
136
367
|
File.write(path, content)
|
|
137
|
-
|
|
368
|
+
|
|
369
|
+
result = success_result(
|
|
138
370
|
message: "Generated #{default_filename}",
|
|
139
371
|
path: path,
|
|
140
372
|
lines: content.lines.count
|
|
141
373
|
)
|
|
374
|
+
result[:backup] = backup_result if backup_result
|
|
375
|
+
result
|
|
142
376
|
else
|
|
143
377
|
success_result(
|
|
144
378
|
content: content,
|
|
@@ -146,6 +380,36 @@ module Appydave
|
|
|
146
380
|
)
|
|
147
381
|
end
|
|
148
382
|
end
|
|
383
|
+
|
|
384
|
+
def backup_existing_file(path)
|
|
385
|
+
return nil unless File.exist?(path)
|
|
386
|
+
|
|
387
|
+
# Create backup directory
|
|
388
|
+
dir = File.dirname(path)
|
|
389
|
+
filename = File.basename(path, '.*')
|
|
390
|
+
backup_dir = File.join(dir, 'backups', filename)
|
|
391
|
+
FileUtils.mkdir_p(backup_dir)
|
|
392
|
+
|
|
393
|
+
# Generate timestamped backup filename
|
|
394
|
+
timestamp = Time.now.strftime('%Y-%m-%d-%H%M%S')
|
|
395
|
+
ext = File.extname(path)
|
|
396
|
+
backup_path = File.join(backup_dir, "#{timestamp}#{ext}")
|
|
397
|
+
|
|
398
|
+
# Copy existing file to backup
|
|
399
|
+
FileUtils.cp(path, backup_path)
|
|
400
|
+
|
|
401
|
+
# Clean up old backups (keep last N)
|
|
402
|
+
cleanup_old_backups(backup_dir, ext)
|
|
403
|
+
|
|
404
|
+
backup_path
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def cleanup_old_backups(backup_dir, ext)
|
|
408
|
+
backups = Dir.glob(File.join(backup_dir, "*#{ext}")).sort.reverse
|
|
409
|
+
return if backups.length <= MAX_BACKUPS
|
|
410
|
+
|
|
411
|
+
backups[MAX_BACKUPS..].each { |f| File.delete(f) }
|
|
412
|
+
end
|
|
149
413
|
end
|
|
150
414
|
end
|
|
151
415
|
end
|
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: appydave-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.73.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cruwys
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activemodel
|