tina4ruby 3.13.7 → 3.13.9

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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tina4/ai.rb +114 -3
  3. data/lib/tina4/version.rb +1 -1
  4. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6526c615d8d8dd6c8bdba089bceb5969917b2c4400d44886f3c3d757ad4ef14f
4
- data.tar.gz: ddebcbd9f847f2660b6ed32e6f7ef86b7e3bd5aa1df3f37f2827d4a2549d9efb
3
+ metadata.gz: c247ddb7851f9f5e0f8cfb8a7a74a4e7690cd9fe606d5d32f7603aa9547aff1d
4
+ data.tar.gz: 75d979c50632935abe4c4864d3c0f28065b3ad87e2f13a81db999f04379fcfb8
5
5
  SHA512:
6
- metadata.gz: 066d4124d585f5e79c3b41e3a763dc655c68d45e5c4afb19c01e0c5dc50fec29f50e21c0fe2eb3b6c8a9941664e45134eb46ef21c42827298c729a77801e766e
7
- data.tar.gz: '0148be259f7b00d815abfc04b35f7bbca09e0e5561ff24e72e4a94a4e9e2c7d94236f67949e31d48daba76d383550a12f0c67d690a03a107db7642ef2a9e5570'
6
+ metadata.gz: ed1956eb82a7ce8242fcbb6c95b14613d72a6931c62bda8bf2e6944f7f77f61e838f40f0a2aeac031f8392d4230407129e2d0edd958c2ab4db2ecc5831fb0570
7
+ data.tar.gz: a8fed6c6985d6f717c083c977ef26df48ddf55af67816e4595918ef4183a5d78c3cea7c98e0849a8b1407cf5ef34f67b69840de0465d4b086561c2fa2ab3e1ac
data/lib/tina4/ai.rb CHANGED
@@ -135,6 +135,118 @@ module Tina4
135
135
  # @param tool [Hash] tool entry from AI_TOOLS
136
136
  # @param context [String] generated context content
137
137
  # @return [Array<String>] list of created/updated relative file paths
138
+ # ── v3.13.9: non-destructive context-file writer ─────────────────
139
+ #
140
+ # Pre-v3.13.9 the installer wrote a full developer guide to
141
+ # CLAUDE.md (and the other context files) on every run, clobbering
142
+ # whatever the user had put there. Now it writes only a marker-
143
+ # bracketed Tina4 skill block -- pointing the assistant at
144
+ # .claude/skills/tina4-*/SKILL.md -- and leaves the rest alone.
145
+
146
+ # Return [start, end] markers for the given context file.
147
+ def markers_for(context_file)
148
+ if context_file.downcase.end_with?(".md")
149
+ ["<!-- tina4-skills:start -->", "<!-- tina4-skills:end -->"]
150
+ else
151
+ ["# tina4-skills:start", "# tina4-skills:end"]
152
+ end
153
+ end
154
+
155
+ # Return the marker-bracketed Tina4 skill registration block.
156
+ def skill_block(context_file)
157
+ start, finish = markers_for(context_file)
158
+ body = if context_file.downcase.end_with?(".md")
159
+ "## Tina4 Skills\n\n" \
160
+ "When working on this Tina4 project, these skills give the assistant project-aware behaviour:\n\n" \
161
+ "- **tina4-developer** -- Read `.claude/skills/tina4-developer/SKILL.md` before building features.\n" \
162
+ "- **tina4-js** -- Read `.claude/skills/tina4-js/SKILL.md` for frontend work.\n" \
163
+ "- **tina4-maintainer** -- Read `.claude/skills/tina4-maintainer/SKILL.md` for framework-level changes.\n\n" \
164
+ "See https://tina4.com for full docs."
165
+ else
166
+ "Tina4 Skills -- read these files before working on this project:\n" \
167
+ " .claude/skills/tina4-developer/SKILL.md (feature development)\n" \
168
+ " .claude/skills/tina4-js/SKILL.md (frontend / tina4-js)\n" \
169
+ " .claude/skills/tina4-maintainer/SKILL.md (framework-level changes)\n" \
170
+ "Docs: https://tina4.com"
171
+ end
172
+ "#{start}\n#{body}\n#{finish}"
173
+ end
174
+
175
+ # True iff both start and end markers appear in order.
176
+ def has_markers?(existing, start, finish)
177
+ s_idx = existing.index(start)
178
+ return false unless s_idx
179
+ !existing.index(finish, s_idx + start.length).nil?
180
+ end
181
+
182
+ # Replace the bracketed block in `existing` with `block`.
183
+ def replace_marker_block(existing, block, start, finish)
184
+ s_idx = existing.index(start)
185
+ return existing.rstrip + "\n\n" + block + "\n" unless s_idx
186
+ e_idx = existing.index(finish, s_idx + start.length)
187
+ return existing.rstrip + "\n\n" + block + "\n" unless e_idx
188
+ before = existing[0...s_idx].rstrip
189
+ after = existing[(e_idx + finish.length)..].sub(/\A\n+/, "")
190
+ glue_before = before.empty? ? "" : "\n\n"
191
+ glue_after = after.empty? ? "\n" : "\n" + after
192
+ "#{before}#{glue_before}#{block}#{glue_after}"
193
+ end
194
+
195
+ # Headers the pre-v3.13.9 installer wrote at the top of CLAUDE.md.
196
+ OLD_FRAMEWORK_HEADERS = [
197
+ "# Tina4 Python",
198
+ "# Tina4 PHP",
199
+ "# Tina4 Ruby",
200
+ "# CLAUDE.md -- AI Developer Guide for tina4-nodejs",
201
+ "# CLAUDE.md — AI Developer Guide for tina4-nodejs",
202
+ ].freeze
203
+
204
+ def looks_like_old_framework_install?(existing)
205
+ head = existing.lstrip[0, 400] || ""
206
+ OLD_FRAMEWORK_HEADERS.any? { |h| head.start_with?(h) }
207
+ end
208
+
209
+ # Write the context file non-destructively. Returns a human-readable
210
+ # action verb for the caller's log line.
211
+ #
212
+ # Four branches:
213
+ # 1. Doesn't exist -> write framework guide + skill block
214
+ # 2. Has markers -> refresh just the skill block (idempotent)
215
+ # 3. Old header -> migrate: replace old dump with new guide + block
216
+ # 4. User content -> append the skill block, preserve everything else
217
+ def write_or_merge(context_path, context_file, framework_guide)
218
+ # Force UTF-8 — CLAUDE.md and the framework guide both contain
219
+ # non-ASCII (em-dashes, ✓, etc.). Without this, File.read may
220
+ # return ASCII-8BIT and string concat raises CompatibilityError.
221
+ block = skill_block(context_file).dup.force_encoding("UTF-8")
222
+ guide = framework_guide.dup.force_encoding("UTF-8")
223
+ start, finish = markers_for(context_file)
224
+
225
+ unless File.exist?(context_path)
226
+ File.write(context_path, guide.rstrip + "\n\n" + block + "\n", encoding: "UTF-8")
227
+ return "Installed"
228
+ end
229
+
230
+ existing = File.read(context_path, encoding: "UTF-8")
231
+
232
+ if has_markers?(existing, start, finish)
233
+ File.write(context_path, replace_marker_block(existing, block, start, finish), encoding: "UTF-8")
234
+ return "Refreshed skill block in"
235
+ end
236
+
237
+ if looks_like_old_framework_install?(existing)
238
+ head = existing.lstrip
239
+ preamble = existing[0, existing.length - head.length] || ""
240
+ new_content = (preamble.strip.empty? ? "" : preamble.rstrip + "\n\n") +
241
+ guide.rstrip + "\n\n" + block + "\n"
242
+ File.write(context_path, new_content, encoding: "UTF-8")
243
+ return "Migrated (replaced old framework dump in)"
244
+ end
245
+
246
+ File.write(context_path, existing.rstrip + "\n\n" + block + "\n", encoding: "UTF-8")
247
+ "Appended skill block to"
248
+ end
249
+
138
250
  def install_for_tool(root, tool, context)
139
251
  created = []
140
252
  context_path = File.join(root, tool[:context_file])
@@ -145,9 +257,8 @@ module Tina4
145
257
  end
146
258
  FileUtils.mkdir_p(File.dirname(context_path))
147
259
 
148
- # Always overwrite -- user chose to install
149
- action = File.exist?(context_path) ? "Updated" : "Installed"
150
- File.write(context_path, context)
260
+ # v3.13.9: non-destructive write -- see write_or_merge below.
261
+ action = write_or_merge(context_path, tool[:context_file], context)
151
262
  rel = context_path.sub("#{root}/", "")
152
263
  created << rel
153
264
  puts " \e[32m✓\e[0m #{action} #{rel}"
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.7"
4
+ VERSION = "3.13.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.7
4
+ version: 3.13.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team