tina4ruby 3.13.118 → 3.13.120
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 +28 -0
- data/lib/tina4/cli.rb +233 -13
- data/lib/tina4/version.rb +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: 95ef84b463d6357070f1f1261556d5d6dc44e7355d279ee987919789b2b11ca2
|
|
4
|
+
data.tar.gz: dd408c271fd30de5ccb6f17c16ff116f0882e39f7f60775d298570e48c201294
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7a9dd4f5ecf64590747b052f67588194e4464d81dfe5f9455bb968132267951ed451915b6605d232688dd803c8deda7eb32f631fee4adb6672ca373acc8146e
|
|
7
|
+
data.tar.gz: 4431a908d43403601b76d82fb5bac6ad36948d532fce131847f14d0cbbc1c45f46a4b9785613cb5f3588553301f257eb42e6d5f68bd4b4473f7c24665d52dbde
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,34 @@ number means the same thing everywhere.
|
|
|
6
6
|
**The authoritative release notes for every shipped version live in the documentation:**
|
|
7
7
|
https://tina4.com/ruby/36-releases
|
|
8
8
|
|
|
9
|
+
## 3.13.119
|
|
10
|
+
|
|
11
|
+
Skill repair by @MichaelC8E (#44, merged as c1b8c43d5) plus a
|
|
12
|
+
small CLAUDE.md footer bump that the 3.13.118 release worker missed.
|
|
13
|
+
|
|
14
|
+
### Skill file repairs (#44)
|
|
15
|
+
|
|
16
|
+
Three defects fixed across the three skill trees this repo carries
|
|
17
|
+
(`.claude/skills/`, `.agents/skills/`, `.cursor/skills/`):
|
|
18
|
+
|
|
19
|
+
- The Codex and Cursor copies of `tina4-maintainer` were UTF-8-with-BOM
|
|
20
|
+
with both em dashes replaced by the cp1252 round-trip
|
|
21
|
+
`c3 a2 e2 82 ac e2 80 9d`. Corruption sat inside the `description`
|
|
22
|
+
frontmatter (the text that decides when a skill triggers), and the
|
|
23
|
+
BOM sat in front of the opening `---` (which some frontmatter
|
|
24
|
+
parsers reject outright). All eight tracked copies across the
|
|
25
|
+
language ports were byte-identical, so every diff-based check
|
|
26
|
+
reported them clean.
|
|
27
|
+
- Two shared files had gone stale against canonical in tina4-python.
|
|
28
|
+
- The Codex and Cursor copies of `tina4-developer-ruby` were around
|
|
29
|
+
60 lines behind `.claude`, and shipped none of the seven
|
|
30
|
+
`references/` files their own SKILL.md cites.
|
|
31
|
+
|
|
32
|
+
Parity: tina4-python 3.13.119 and tina4-nodejs 3.13.119 are
|
|
33
|
+
version-parity bumps. tina4-php 3.13.119 ships @MichaelC8E's
|
|
34
|
+
skill-repair PR (tina4-php#205), @cwvermaak-codeinfinity's Messenger
|
|
35
|
+
fix (tina4-php#204), and my ImportHelper autoload non-throw fix.
|
|
36
|
+
|
|
9
37
|
## 3.13.118
|
|
10
38
|
|
|
11
39
|
Version-parity bump. No framework code changes in Ruby for this
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "optparse"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "set"
|
|
6
6
|
require "json"
|
|
7
|
+
require "tmpdir" # Dir.mktmpdir — used by the ADR-0063 sandbox edit-hint scan
|
|
8
|
+
require "stringio" # StringIO — captures generator stdout during the sandbox run
|
|
7
9
|
require_relative "port_takeover"
|
|
8
10
|
|
|
9
11
|
module Tina4
|
|
@@ -289,6 +291,87 @@ module Tina4
|
|
|
289
291
|
# created_at. The first write then failed with "no such column: name".
|
|
290
292
|
DEFAULT_FIELDS = [["name", "string"]].freeze
|
|
291
293
|
|
|
294
|
+
# ADR-0063 (scaffolding envelope v1.1): `# tina4:edit <label>` in a generated
|
|
295
|
+
# Ruby file, or `-- tina4:edit <label>` in a generated SQL file, marks a
|
|
296
|
+
# first-edit spot for a developer / coding agent to fill in. The scanner
|
|
297
|
+
# surfaces every match as an `edit_hints` entry in the `generate_v1_1`
|
|
298
|
+
# envelope AND under "Edit these lines:" in the human stderr block. Any
|
|
299
|
+
# comment character (`#` or `--`) followed by `tina4:edit` and one or more
|
|
300
|
+
# spaces then the label matches — Twig `{# tina4:edit … #}` is Wave 2 and
|
|
301
|
+
# deliberately NOT matched here.
|
|
302
|
+
EDIT_MARKER_RE = /(?:#|--)\s*tina4:edit[ \t]+(.+?)\s*$/.freeze
|
|
303
|
+
|
|
304
|
+
# ADR-0063 next-step catalogue: 5 short actionable lines per resolution-aware
|
|
305
|
+
# verb. Rendered under "Next:" in the human block AND surfaced as
|
|
306
|
+
# `resolution.next[]` in the envelope. Each entry is a `Proc` that consumes
|
|
307
|
+
# the resolution so paths / class names substitute correctly. Curated (not
|
|
308
|
+
# generated) so a caller sees consistent, sensible next steps.
|
|
309
|
+
NEXT_STEPS = {
|
|
310
|
+
"model" => lambda do |res|
|
|
311
|
+
table = res["table_name"] || "resource"
|
|
312
|
+
class_name = res["class_name"] || "Model"
|
|
313
|
+
file_path = res["file_path"] || "src/orm/#{table}.rb"
|
|
314
|
+
route = (res["routes"] || []).first || "/#{table}"
|
|
315
|
+
[
|
|
316
|
+
"Edit #{file_path} to add fields beyond the default 'name'",
|
|
317
|
+
"Apply the migration: tina4ruby migrate",
|
|
318
|
+
"Try it: tina4ruby serve -> curl http://localhost:7147#{route}",
|
|
319
|
+
"Add CRUD scaffolding: tina4ruby generate crud #{class_name} --skip-model",
|
|
320
|
+
"Seed sample data: tina4ruby generate seeder #{class_name} && tina4ruby seed",
|
|
321
|
+
]
|
|
322
|
+
end,
|
|
323
|
+
"route" => lambda do |res|
|
|
324
|
+
file_path = res["file_path"] || "src/routes/resource.rb"
|
|
325
|
+
route = (res["routes"] || []).first || "/api/resource"
|
|
326
|
+
test_path = (res["test_paths"] || []).first || "spec/routes/resource_spec.rb"
|
|
327
|
+
[
|
|
328
|
+
"Edit #{file_path} to customise the response",
|
|
329
|
+
"Try it: tina4ruby serve -> curl http://localhost:7147#{route}",
|
|
330
|
+
"Add real assertions: #{test_path}",
|
|
331
|
+
"Bearer-gate is on: writes require a Bearer token (rerun with --public to open)",
|
|
332
|
+
"Browse the API: tina4ruby serve -> http://localhost:7147/swagger",
|
|
333
|
+
]
|
|
334
|
+
end,
|
|
335
|
+
"migration" => lambda do |res|
|
|
336
|
+
file_path = res["file_path"] || "migrations/new_migration.sql"
|
|
337
|
+
down_path = file_path.sub(/\.sql$/, ".down.sql")
|
|
338
|
+
[
|
|
339
|
+
"Edit #{file_path} to add columns beyond id + created_at",
|
|
340
|
+
"Mirror the change in: #{down_path}",
|
|
341
|
+
"Apply it: tina4ruby migrate",
|
|
342
|
+
"Check status: tina4ruby migrate status",
|
|
343
|
+
"Rollback if wrong: tina4ruby migrate rollback",
|
|
344
|
+
]
|
|
345
|
+
end,
|
|
346
|
+
"middleware" => lambda do |res|
|
|
347
|
+
class_name = res["class_name"] || "Middleware"
|
|
348
|
+
file_path = res["file_path"] || "src/middleware/middleware.rb"
|
|
349
|
+
[
|
|
350
|
+
"Edit #{file_path} to guard the request or shape the response",
|
|
351
|
+
"Wire it into a route: middleware: [#{class_name}] on Tina4.get/post/...",
|
|
352
|
+
"Wire it globally: Tina4::Router.use(#{class_name})",
|
|
353
|
+
"Prove the gate: add a real spec covering allow + deny",
|
|
354
|
+
"Chain more: order in the middleware: [] list is registration order",
|
|
355
|
+
]
|
|
356
|
+
end,
|
|
357
|
+
}.freeze
|
|
358
|
+
|
|
359
|
+
# ADR-0063: freeze `Time.now` for the duration of one `cmd_generate` call.
|
|
360
|
+
# Both the resolution builders (`build_migration_resolution`,
|
|
361
|
+
# `build_model_resolution`) and the actual generator (`generate_migration`)
|
|
362
|
+
# embed a timestamp in the migration filename. If they call `Time.now`
|
|
363
|
+
# independently, the sandbox pre-run (which drives edit_hints for dry-run
|
|
364
|
+
# mode) and the real run can pick different seconds, and the envelope's
|
|
365
|
+
# `file_path` would drift from the file that actually gets written. One
|
|
366
|
+
# memoised timestamp per command run fixes that.
|
|
367
|
+
def generate_timestamp
|
|
368
|
+
@generate_timestamp ||= Time.now
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def reset_generate_timestamp!
|
|
372
|
+
@generate_timestamp = nil
|
|
373
|
+
end
|
|
374
|
+
|
|
292
375
|
# Parsed --fields, or the default single `name` column when none given.
|
|
293
376
|
def fields_or_default(fields_str)
|
|
294
377
|
parsed = parse_fields(fields_str)
|
|
@@ -331,7 +414,11 @@ module Tina4
|
|
|
331
414
|
bar = "─" * 60
|
|
332
415
|
head = "#{indent}# ─── AI-FILL: #{fn} "
|
|
333
416
|
head += "─" * [4, 66 - head.length].max
|
|
334
|
-
|
|
417
|
+
# ADR-0063: prepend a `# tina4:edit <label>` machine-readable pointer so a
|
|
418
|
+
# tool can surface WHERE this stub needs custom logic without parsing the
|
|
419
|
+
# AI-FILL banner. `intent` is the developer-facing TODO — reuse it as the
|
|
420
|
+
# label (short, actionable, no period).
|
|
421
|
+
lines = ["#{indent}# tina4:edit #{intent}", head, "#{indent}# Intent: #{intent}"]
|
|
335
422
|
lines << "#{indent}# Given: #{given}" if given
|
|
336
423
|
lines << "#{indent}# Use: #{use}"
|
|
337
424
|
lines << "#{indent}# Return: #{ret}" if ret
|
|
@@ -348,7 +435,10 @@ module Tina4
|
|
|
348
435
|
def extend_marker(note, hint = "", indent: " ")
|
|
349
436
|
head = "#{indent}# ─── EXTEND: #{note} "
|
|
350
437
|
head += "─" * [4, 66 - head.length].max
|
|
351
|
-
|
|
438
|
+
# ADR-0063: `# tina4:edit <label>` is the machine-readable partner of the
|
|
439
|
+
# ─── EXTEND banner. `note` is the human-shaped TODO — reuse it verbatim.
|
|
440
|
+
out = "#{indent}# tina4:edit #{note}\n"
|
|
441
|
+
out += head + "\n"
|
|
352
442
|
out += "#{indent}# #{hint}\n" unless hint.to_s.empty?
|
|
353
443
|
out
|
|
354
444
|
end
|
|
@@ -1117,6 +1207,10 @@ module Tina4
|
|
|
1117
1207
|
# will produce before it runs.
|
|
1118
1208
|
|
|
1119
1209
|
def cmd_generate(argv)
|
|
1210
|
+
# ADR-0063: fresh timestamp per command run so the sandbox pre-run + the
|
|
1211
|
+
# real generator both agree on the migration filename.
|
|
1212
|
+
reset_generate_timestamp!
|
|
1213
|
+
|
|
1120
1214
|
what = argv.shift
|
|
1121
1215
|
all = GENERATORS.keys.join(", ") # single source: the GENERATORS registry
|
|
1122
1216
|
|
|
@@ -1126,7 +1220,7 @@ module Tina4
|
|
|
1126
1220
|
puts ' Options: --fields "name:string,price:float" --model ModelName'
|
|
1127
1221
|
puts ' --public open a route'"'"'s writes (default: secure)'
|
|
1128
1222
|
puts ' --every 5m | --cron "..." service schedule'
|
|
1129
|
-
puts ' --json emit the
|
|
1223
|
+
puts ' --json emit the generate_v1_1 resolution envelope'
|
|
1130
1224
|
puts ' --dry-run plan the scaffold; write no files'
|
|
1131
1225
|
exit 1
|
|
1132
1226
|
end
|
|
@@ -1164,6 +1258,10 @@ module Tina4
|
|
|
1164
1258
|
end
|
|
1165
1259
|
|
|
1166
1260
|
resolution = build_generate_resolution(what, name, flags)
|
|
1261
|
+
# ADR-0063 additive v1.1 keys: curated next-steps (pure fn of resolution)
|
|
1262
|
+
# and edit_hints (scan of generated files for `# tina4:edit <label>`).
|
|
1263
|
+
resolution["next"] = build_next_steps(what, resolution)
|
|
1264
|
+
resolution["edit_hints"] = collect_edit_hints_via_sandbox(gen_spec, name, flags)
|
|
1167
1265
|
|
|
1168
1266
|
if json_mode
|
|
1169
1267
|
actions_taken = dry_run ? [] : run_generator_capturing_actions(gen_spec, name, flags)
|
|
@@ -1183,6 +1281,78 @@ module Tina4
|
|
|
1183
1281
|
end
|
|
1184
1282
|
end
|
|
1185
1283
|
|
|
1284
|
+
# ADR-0063 helpers ────────────────────────────────────────────────────────
|
|
1285
|
+
|
|
1286
|
+
# Curated per-verb next-step list. Pure function of the resolution; never
|
|
1287
|
+
# scans the filesystem, so it works identically in dry-run and real-run.
|
|
1288
|
+
def build_next_steps(target, resolution)
|
|
1289
|
+
builder = NEXT_STEPS[target]
|
|
1290
|
+
return [] unless builder
|
|
1291
|
+
Array(builder.call(resolution))
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1294
|
+
# Scan a list of files for `# tina4:edit <label>` (Ruby) or
|
|
1295
|
+
# `-- tina4:edit <label>` (SQL) lines. Returns `[{file, line, label}]` in
|
|
1296
|
+
# the same order the files were passed. Missing / unreadable files are
|
|
1297
|
+
# skipped silently (a scaffold that already exists on disk simply produces
|
|
1298
|
+
# no hint for that path — that is the intended behaviour).
|
|
1299
|
+
def scan_edit_hints_from_files(paths)
|
|
1300
|
+
hints = []
|
|
1301
|
+
paths.each do |path|
|
|
1302
|
+
next unless path
|
|
1303
|
+
next unless File.file?(path)
|
|
1304
|
+
begin
|
|
1305
|
+
content = File.read(path, encoding: "UTF-8", invalid: :replace, undef: :replace)
|
|
1306
|
+
rescue SystemCallError
|
|
1307
|
+
next
|
|
1308
|
+
end
|
|
1309
|
+
content.each_line.with_index(1) do |line, lineno|
|
|
1310
|
+
next unless (m = line.match(EDIT_MARKER_RE))
|
|
1311
|
+
hints << { "file" => path, "line" => lineno, "label" => m[1].strip }
|
|
1312
|
+
end
|
|
1313
|
+
end
|
|
1314
|
+
hints
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
# Run the generator inside a throw-away tmpdir with `$stdout` captured, then
|
|
1318
|
+
# walk every written file and scan for `# tina4:edit` / `-- tina4:edit`
|
|
1319
|
+
# markers. Used for BOTH dry-run and real-run modes so the envelope always
|
|
1320
|
+
# ships accurate `edit_hints` before any live-cwd write happens (preserves
|
|
1321
|
+
# the "Ctrl-C between plan and write" contract of the human block).
|
|
1322
|
+
#
|
|
1323
|
+
# `generate_timestamp` is shared with the real run so a migration file
|
|
1324
|
+
# sandboxed here has the SAME timestamped basename as the file that lands
|
|
1325
|
+
# in the developer's cwd - the hint's `file` field matches disk verbatim.
|
|
1326
|
+
def collect_edit_hints_via_sandbox(gen_spec, name, flags)
|
|
1327
|
+
hints = []
|
|
1328
|
+
Dir.mktmpdir("tina4_edit_hints") do |sandbox|
|
|
1329
|
+
# Block-form Dir.chdir composes cleanly under an outer block-form
|
|
1330
|
+
# Dir.chdir (Ruby 3.x warns on nested non-block chdir). No `ensure`
|
|
1331
|
+
# cwd-restore needed - the block form guarantees it.
|
|
1332
|
+
Dir.chdir(sandbox) do
|
|
1333
|
+
buffer = StringIO.new
|
|
1334
|
+
original_stdout = $stdout
|
|
1335
|
+
$stdout = buffer
|
|
1336
|
+
begin
|
|
1337
|
+
# A generator failure inside the sandbox must never break the caller's
|
|
1338
|
+
# real run - rescue everything, return whatever hints we managed to
|
|
1339
|
+
# gather. The real run will error the same way if it is going to.
|
|
1340
|
+
send(gen_spec[:handler], name, flags.dup)
|
|
1341
|
+
rescue StandardError, LoadError, ScriptError
|
|
1342
|
+
# swallow - the sandbox is best-effort
|
|
1343
|
+
ensure
|
|
1344
|
+
$stdout = original_stdout
|
|
1345
|
+
end
|
|
1346
|
+
Dir.glob("**/*", File::FNM_DOTMATCH).sort.each do |rel_path|
|
|
1347
|
+
next if [".", ".."].include?(File.basename(rel_path))
|
|
1348
|
+
next unless File.file?(rel_path)
|
|
1349
|
+
hints.concat(scan_edit_hints_from_files([rel_path]))
|
|
1350
|
+
end
|
|
1351
|
+
end
|
|
1352
|
+
end
|
|
1353
|
+
hints
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1186
1356
|
# ── generate resolution helpers ────────────────────────────────────────
|
|
1187
1357
|
|
|
1188
1358
|
# One `resolution` per target, with the SAME KEY SET so a caller can
|
|
@@ -1227,7 +1397,10 @@ module Tina4
|
|
|
1227
1397
|
# The migration filename embeds a timestamp; predict it deterministically
|
|
1228
1398
|
# so a `--dry-run` and the real run agree on the same second. Two
|
|
1229
1399
|
# generations within the same second collide anyway (existing behaviour).
|
|
1230
|
-
|
|
1400
|
+
# ADR-0063: `generate_timestamp` memoises `Time.now` per `cmd_generate`
|
|
1401
|
+
# call so the sandbox pre-run + real run share one second, and the
|
|
1402
|
+
# envelope's `migration_path` matches the file that actually lands.
|
|
1403
|
+
timestamp = generate_timestamp.strftime("%Y%m%d%H%M%S")
|
|
1231
1404
|
{
|
|
1232
1405
|
"class_name" => name.to_s,
|
|
1233
1406
|
"table_name" => table,
|
|
@@ -1253,7 +1426,8 @@ module Tina4
|
|
|
1253
1426
|
end
|
|
1254
1427
|
|
|
1255
1428
|
def build_migration_resolution(name, _flags)
|
|
1256
|
-
timestamp
|
|
1429
|
+
# ADR-0063: shared memoised timestamp (see `generate_timestamp`).
|
|
1430
|
+
timestamp = generate_timestamp.strftime("%Y%m%d%H%M%S")
|
|
1257
1431
|
base = name.to_s.sub(/^create_/, "").sub(/^add_/, "").sub(/^drop_/, "")
|
|
1258
1432
|
snake_base = to_snake_case(base)
|
|
1259
1433
|
path = "migrations/#{timestamp}_#{name}.sql"
|
|
@@ -1313,12 +1487,33 @@ module Tina4
|
|
|
1313
1487
|
if resolution["migration_path"] && resolution["migration_path"] != resolution["file_path"]
|
|
1314
1488
|
lines << " migration #{resolution['migration_path']}"
|
|
1315
1489
|
end
|
|
1490
|
+
# ADR-0063: surface the pre-existing test_paths (v1 field, only now printed
|
|
1491
|
+
# in the human block) alongside the two new sections.
|
|
1492
|
+
if resolution["test_paths"] && !resolution["test_paths"].empty?
|
|
1493
|
+
lines << " tests #{resolution['test_paths'].join(', ')}"
|
|
1494
|
+
end
|
|
1316
1495
|
pluralized = resolution["transformations"].find { |t| t["kind"] == "reserved_word_pluralize" }
|
|
1317
1496
|
if pluralized
|
|
1318
1497
|
lines << ""
|
|
1319
1498
|
lines << " To keep the raw name '#{pluralized['from']}' as the table:"
|
|
1320
1499
|
lines << " tina4ruby generate #{target} #{name} --table #{pluralized['from']} --quote (opt-in, ADR-0062 forthcoming)"
|
|
1321
1500
|
end
|
|
1501
|
+
# ADR-0063 v1.1: surface `edit_hints[]` under "Edit these lines:" and
|
|
1502
|
+
# `next[]` under "Next:" — both additive; missing / empty arrays elide
|
|
1503
|
+
# cleanly. The `file:line label` shape is grep-friendly for humans and
|
|
1504
|
+
# machine-parseable for tools.
|
|
1505
|
+
if resolution["edit_hints"] && !resolution["edit_hints"].empty?
|
|
1506
|
+
lines << ""
|
|
1507
|
+
lines << " Edit these lines:"
|
|
1508
|
+
resolution["edit_hints"].each do |h|
|
|
1509
|
+
lines << " #{h['file']}:#{h['line']} #{h['label']}"
|
|
1510
|
+
end
|
|
1511
|
+
end
|
|
1512
|
+
if resolution["next"] && !resolution["next"].empty?
|
|
1513
|
+
lines << ""
|
|
1514
|
+
lines << " Next:"
|
|
1515
|
+
resolution["next"].each { |step| lines << " #{step}" }
|
|
1516
|
+
end
|
|
1322
1517
|
lines.join("\n")
|
|
1323
1518
|
end
|
|
1324
1519
|
|
|
@@ -1371,6 +1566,7 @@ module Tina4
|
|
|
1371
1566
|
class #{name} < Tina4::ORM
|
|
1372
1567
|
table_name "#{table}"
|
|
1373
1568
|
|
|
1569
|
+
# tina4:edit add fields beyond the default 'name'
|
|
1374
1570
|
#{field_lines.join("\n")}
|
|
1375
1571
|
end
|
|
1376
1572
|
RUBY
|
|
@@ -1628,7 +1824,10 @@ module Tina4
|
|
|
1628
1824
|
# ── Generator: migration ─────────────────────────────────────────────
|
|
1629
1825
|
|
|
1630
1826
|
def generate_migration(name, flags = {}, fields_override: nil, table_override: nil, emit_test: true)
|
|
1631
|
-
|
|
1827
|
+
# ADR-0063: `generate_timestamp` is the shared memoised `Time.now` for
|
|
1828
|
+
# this cmd_generate run; the resolution's `migration_path` uses the SAME
|
|
1829
|
+
# value, so envelope and disk agree.
|
|
1830
|
+
now = generate_timestamp
|
|
1632
1831
|
timestamp = now.strftime("%Y%m%d%H%M%S")
|
|
1633
1832
|
dir = "migrations"
|
|
1634
1833
|
FileUtils.mkdir_p(dir)
|
|
@@ -1661,11 +1860,18 @@ module Tina4
|
|
|
1661
1860
|
end
|
|
1662
1861
|
col_lines << " created_at TEXT DEFAULT CURRENT_TIMESTAMP"
|
|
1663
1862
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1863
|
+
# ADR-0063: `-- tina4:edit` marker for the SQL scanner (mirrors `# tina4:edit`
|
|
1864
|
+
# in Ruby files). The scanner regex accepts both comment prefixes.
|
|
1865
|
+
up_sql = "CREATE TABLE IF NOT EXISTS #{table} (\n" \
|
|
1866
|
+
" -- tina4:edit add columns beyond id + created_at\n" \
|
|
1867
|
+
"#{col_lines.join(",\n")}\n);"
|
|
1868
|
+
down_sql = "-- tina4:edit mirror the CREATE's added columns in the rollback\n" \
|
|
1869
|
+
"DROP TABLE IF EXISTS #{table};"
|
|
1666
1870
|
else
|
|
1667
|
-
up_sql = "--
|
|
1668
|
-
|
|
1871
|
+
up_sql = "-- tina4:edit write your UP migration SQL here\n" \
|
|
1872
|
+
"-- Example: ALTER TABLE #{table} ADD COLUMN new_col TEXT DEFAULT '';"
|
|
1873
|
+
down_sql = "-- tina4:edit write your DOWN rollback SQL here\n" \
|
|
1874
|
+
"-- Example: ALTER TABLE #{table} DROP COLUMN new_col;"
|
|
1669
1875
|
end
|
|
1670
1876
|
|
|
1671
1877
|
# The main .sql holds ONLY the UP migration. The runner executes the WHOLE
|
|
@@ -1727,12 +1933,14 @@ module Tina4
|
|
|
1727
1933
|
# Runs before the route handler.
|
|
1728
1934
|
# Return [request, response] to continue, or
|
|
1729
1935
|
# return [request, response.json({ error: "Unauthorized" }, 401)] to block.
|
|
1936
|
+
# tina4:edit guard the request here
|
|
1730
1937
|
Tina4::Log.info("#{name}: \#{request.method} \#{request.path}")
|
|
1731
1938
|
[request, response]
|
|
1732
1939
|
end
|
|
1733
1940
|
|
|
1734
1941
|
def self.after_#{snake}(request, response)
|
|
1735
1942
|
# Runs after the route handler.
|
|
1943
|
+
# tina4:edit post-process the response here
|
|
1736
1944
|
[request, response]
|
|
1737
1945
|
end
|
|
1738
1946
|
end
|
|
@@ -1836,6 +2044,7 @@ module Tina4
|
|
|
1836
2044
|
# Tests for #{name} CRUD operations
|
|
1837
2045
|
RSpec.describe "#{model}" do
|
|
1838
2046
|
before(:each) do
|
|
2047
|
+
# tina4:edit seed real fixtures for a real DB
|
|
1839
2048
|
# Set up test fixtures
|
|
1840
2049
|
end
|
|
1841
2050
|
|
|
@@ -1844,7 +2053,7 @@ module Tina4
|
|
|
1844
2053
|
end
|
|
1845
2054
|
|
|
1846
2055
|
it "lists #{snake}" do
|
|
1847
|
-
#
|
|
2056
|
+
# tina4:edit replace the tautology with a real assertion
|
|
1848
2057
|
expect(true).to be true
|
|
1849
2058
|
end
|
|
1850
2059
|
|
|
@@ -1875,6 +2084,7 @@ module Tina4
|
|
|
1875
2084
|
# Tests for #{name}
|
|
1876
2085
|
RSpec.describe "#{class_name}" do
|
|
1877
2086
|
before(:each) do
|
|
2087
|
+
# tina4:edit seed real fixtures for a real DB
|
|
1878
2088
|
# Set up test fixtures
|
|
1879
2089
|
end
|
|
1880
2090
|
|
|
@@ -1883,7 +2093,7 @@ module Tina4
|
|
|
1883
2093
|
end
|
|
1884
2094
|
|
|
1885
2095
|
it "works as expected" do
|
|
1886
|
-
#
|
|
2096
|
+
# tina4:edit replace the tautology with a real assertion
|
|
1887
2097
|
expect(true).to be true
|
|
1888
2098
|
end
|
|
1889
2099
|
end
|
|
@@ -1955,6 +2165,7 @@ module Tina4
|
|
|
1955
2165
|
end
|
|
1956
2166
|
|
|
1957
2167
|
content = <<~HTML
|
|
2168
|
+
{# tina4:edit restyle the form beyond the scaffolded defaults #}
|
|
1958
2169
|
{% extends "base.twig" %}
|
|
1959
2170
|
{% block title %}#{name} {% if item.id %}Edit{% else %}Create{% endif %}{% endblock %}
|
|
1960
2171
|
{% block content %}
|
|
@@ -1994,6 +2205,7 @@ module Tina4
|
|
|
1994
2205
|
td = cols.map { |c| "<td>{{ item.#{c} }}</td>" }.join("\n ")
|
|
1995
2206
|
|
|
1996
2207
|
list_content = <<~HTML
|
|
2208
|
+
{# tina4:edit add sort / filter / pagination controls to the list #}
|
|
1997
2209
|
{% extends "base.twig" %}
|
|
1998
2210
|
{% block title %}#{name}s{% endblock %}
|
|
1999
2211
|
{% block content %}
|
|
@@ -2044,6 +2256,7 @@ module Tina4
|
|
|
2044
2256
|
end.join("\n")
|
|
2045
2257
|
|
|
2046
2258
|
detail_content = <<~HTML
|
|
2259
|
+
{# tina4:edit extend the detail view with related records or actions #}
|
|
2047
2260
|
{% extends "base.twig" %}
|
|
2048
2261
|
{% block title %}#{name} Detail{% endblock %}
|
|
2049
2262
|
{% block content %}
|
|
@@ -2087,6 +2300,7 @@ module Tina4
|
|
|
2087
2300
|
# clears the router's auth_required).
|
|
2088
2301
|
Tina4.post "/api/auth/register", auth: false do |request, response|
|
|
2089
2302
|
# Register a new user
|
|
2303
|
+
# tina4:edit harden the register flow (rate limit / captcha / password rules)
|
|
2090
2304
|
email = request.body["email"].to_s
|
|
2091
2305
|
password = request.body["password"].to_s
|
|
2092
2306
|
|
|
@@ -2112,6 +2326,7 @@ module Tina4
|
|
|
2112
2326
|
# PUBLIC: login mints the token — clear BOTH write gates (see register).
|
|
2113
2327
|
Tina4.post "/api/auth/login", auth: false do |request, response|
|
|
2114
2328
|
# Login with email and password
|
|
2329
|
+
# tina4:edit harden the login flow (rate limit / lockout after N failures)
|
|
2115
2330
|
email = request.body["email"].to_s
|
|
2116
2331
|
password = request.body["password"].to_s
|
|
2117
2332
|
|
|
@@ -3146,7 +3361,12 @@ module Tina4
|
|
|
3146
3361
|
# KNOW the envelope shape before it invokes generate (see cmd_generate).
|
|
3147
3362
|
# Bump `version` when the envelope shape changes; add a new envelope
|
|
3148
3363
|
# name rather than mutating the existing one.
|
|
3149
|
-
|
|
3364
|
+
# ADR-0063: v1.1 adds two additive keys to `resolution` — `edit_hints[]`
|
|
3365
|
+
# (grep of every `# tina4:edit <label>` / `-- tina4:edit <label>` line
|
|
3366
|
+
# in the generated files) and `next[]` (curated actionable steps). Every
|
|
3367
|
+
# v1 key is preserved untouched, so a v1 caller reading v1.1 output
|
|
3368
|
+
# keeps working.
|
|
3369
|
+
"resolution_contract" => { "version" => "1.1", "envelope" => "generate_v1_1" }
|
|
3150
3370
|
}
|
|
3151
3371
|
end
|
|
3152
3372
|
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.120
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|