tina4ruby 3.13.145 → 3.13.147
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 +20 -0
- data/lib/tina4/docs.rb +47 -2
- data/lib/tina4/mcp.rb +63 -15
- 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: e9a60268ad13cea010f5fc994563e37b77d62c93bf73a67d075cd8086b4fa431
|
|
4
|
+
data.tar.gz: 740402d29d856535fe82468ce329ea754760a180f24436b22ae36f1d16fc25b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a63f77fa42d0b2dfb1ce82a6c600c0b83b3336a68826302e27179dd92eb0bb79d57f0d4b5c183b279178d0d600fbac33a4f97ae0a1c415cae09cab3e2eaa2c5
|
|
7
|
+
data.tar.gz: 6bbd112086e7b380d4a9748ae96192fba1721b17bd7b57cff1a682e60c062c308b817e5053ee3d1fb7293f1a27e9f448857a526f1d4fd7fe72889308bbf56643
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ 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.147 — 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Dev MCP tools (fixes #271)
|
|
12
|
+
- `database_columns` reads schema metadata so an empty table returns its columns; a missing table returns a clear `table not found` error.
|
|
13
|
+
- Tool argument validation returns an actionable `missing/unknown argument` error instead of a raw Ruby error; `api_method` now populates `params` and `return`.
|
|
14
|
+
- `route_list` entries include each route's `middleware`.
|
|
15
|
+
- A new route file added at runtime surfaces a signal.
|
|
16
|
+
- Developer skill corrected: per-language hot-reload reality, and `api_method` shown with argument names.
|
|
17
|
+
|
|
18
|
+
## 3.13.146 — 2026-10-04
|
|
19
|
+
|
|
20
|
+
### AI skills
|
|
21
|
+
- Every skill now opens with a content list (table of contents) and a degrees-of-freedom legend (what is inviolable, what is a default, what is judgement).
|
|
22
|
+
- New `tina4-cli` skill documents every `tina4` command and flag and the scaffolding discipline.
|
|
23
|
+
- `tina4-maintainer` gains task checklists (PR review, release, installer signing, parity sweep); `tina4-design` split into focused reference files.
|
|
24
|
+
- Skills installer now supports one-level-nested references; `install-skills.ps1` re-signed.
|
|
25
|
+
|
|
26
|
+
### Framework
|
|
27
|
+
- No framework code changes - the version bump carries the skills release across all four frameworks at parity.
|
|
28
|
+
|
|
9
29
|
## 3.13.145 — 2026-10-03
|
|
10
30
|
|
|
11
31
|
Two fixes land together. The Api client no longer carries an application-configured header onto a different origin: credentials travel under many names (`X-Api-Key`, a custom bearer header), so on a cross-origin redirect only content-negotiation and transport headers cross, and every configured or per-call header is bound to the origin it was meant for; a same-origin redirect keeps them. And `tina4ruby migrate` now exits 1 when there is no usable database (unreachable or misconfigured), so CI no longer reads a failed migration as success. Both are proven against real dependencies, no mocks. No runtime gems.
|
data/lib/tina4/docs.rb
CHANGED
|
@@ -432,6 +432,7 @@ module Tina4
|
|
|
432
432
|
private_flag = visibility != "public" || underscored
|
|
433
433
|
|
|
434
434
|
summary, doc = split_doc(pending_doc)
|
|
435
|
+
returns = pending_doc.join(" ")[/@return\s+\[([^\]]+)\]/, 1].to_s
|
|
435
436
|
method_fqn = if is_static
|
|
436
437
|
"#{current_class[:fqn]}.#{mname}"
|
|
437
438
|
else
|
|
@@ -446,6 +447,7 @@ module Tina4
|
|
|
446
447
|
class_fqn: current_class[:fqn],
|
|
447
448
|
class: current_class[:fqn],
|
|
448
449
|
signature: signature,
|
|
450
|
+
returns: returns,
|
|
449
451
|
summary: summary,
|
|
450
452
|
docstring: doc,
|
|
451
453
|
file: rel_file,
|
|
@@ -620,11 +622,54 @@ module Tina4
|
|
|
620
622
|
static: entry[:static] || false,
|
|
621
623
|
source: entry[:source],
|
|
622
624
|
version: entry[:version],
|
|
623
|
-
params: [],
|
|
624
|
-
return:
|
|
625
|
+
params: params_from_signature(entry[:signature]),
|
|
626
|
+
return: entry[:returns].to_s,
|
|
625
627
|
}
|
|
626
628
|
end
|
|
627
629
|
|
|
630
|
+
# Structured params parsed from a Ruby signature such as
|
|
631
|
+
# "fetch(sql, params = [], limit: 100, **opts)" -- same shape Python
|
|
632
|
+
# reports: [{name, type, required, default}]. Ruby has no inline type
|
|
633
|
+
# annotations, so type is "" unless the docblock says otherwise.
|
|
634
|
+
def params_from_signature(signature)
|
|
635
|
+
inner = signature.to_s[/\A[^(]*\((.*)\)\z/m, 1]
|
|
636
|
+
return [] if inner.nil? || inner.strip.empty?
|
|
637
|
+
|
|
638
|
+
split_top_level_commas(inner).map { |piece| param_entry(piece.strip) }
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def param_entry(piece)
|
|
642
|
+
prefix = piece[/\A(\*\*|\*|&)/, 1]
|
|
643
|
+
return { name: piece, type: "", required: false, default: nil } if prefix
|
|
644
|
+
|
|
645
|
+
keyword = piece.match(/\A([A-Za-z_]\w*):\s*(.*)\z/m)
|
|
646
|
+
if keyword
|
|
647
|
+
default = keyword[2].strip
|
|
648
|
+
return { name: keyword[1], type: "", required: default.empty?, default: default.empty? ? nil : default }
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
name, default = piece.split(/\s*=\s*/, 2)
|
|
652
|
+
{ name: name.strip, type: "", required: default.nil?, default: default&.strip }
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def split_top_level_commas(text)
|
|
656
|
+
pieces = []
|
|
657
|
+
depth = 0
|
|
658
|
+
current = +""
|
|
659
|
+
text.each_char do |ch|
|
|
660
|
+
depth += 1 if "([{".include?(ch)
|
|
661
|
+
depth -= 1 if ")]}".include?(ch)
|
|
662
|
+
if ch == "," && depth.zero?
|
|
663
|
+
pieces << current
|
|
664
|
+
current = +""
|
|
665
|
+
else
|
|
666
|
+
current << ch
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
pieces << current unless current.strip.empty?
|
|
670
|
+
pieces
|
|
671
|
+
end
|
|
672
|
+
|
|
628
673
|
# ── Sync writer ──────────────────────────────────────────────────
|
|
629
674
|
|
|
630
675
|
def render_generated_block
|
data/lib/tina4/mcp.rb
CHANGED
|
@@ -139,6 +139,17 @@ module Tina4
|
|
|
139
139
|
schema
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
+
# Readable name for one attached route middleware (class, instance, string
|
|
143
|
+
# spec or lambda) -- what route_list reports under "middleware".
|
|
144
|
+
def self.middleware_label(attached)
|
|
145
|
+
case attached
|
|
146
|
+
when ::String then attached
|
|
147
|
+
when ::Module then attached.name || attached.inspect
|
|
148
|
+
when ::Proc, ::Method then "proc"
|
|
149
|
+
else attached.class.name || attached.inspect
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
142
153
|
# Informational only — whether the CONFIGURED host looks local.
|
|
143
154
|
#
|
|
144
155
|
# NOT the security gate. This reads TINA4_HOST_NAME (the configured bind
|
|
@@ -510,22 +521,47 @@ module Tina4
|
|
|
510
521
|
raise ArgumentError, "Unknown tool: #{tool_name}" if tool.nil?
|
|
511
522
|
|
|
512
523
|
arguments = params.fetch("arguments", {})
|
|
524
|
+
arguments = {} if arguments.nil?
|
|
513
525
|
handler = tool["handler"]
|
|
514
526
|
|
|
527
|
+
# Validate against the tool's input schema BEFORE invoking, so a missing
|
|
528
|
+
# or unknown argument is an actionable message, never a raw Ruby
|
|
529
|
+
# ArgumentError. Python parity (tina4_python.mcp).
|
|
530
|
+
argument_error = validate_tool_arguments(tool_name, tool["inputSchema"], arguments)
|
|
531
|
+
return mcp_text_content(argument_error) if argument_error
|
|
532
|
+
|
|
515
533
|
# Call the handler -- support both keyword and positional args
|
|
516
534
|
result = _invoke_handler(handler, arguments)
|
|
517
535
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
536
|
+
mcp_text_content(result)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Format a tool result as MCP content.
|
|
540
|
+
def mcp_text_content(result)
|
|
541
|
+
text = case result
|
|
542
|
+
when String then result
|
|
543
|
+
when Hash, Array then JSON.pretty_generate(result)
|
|
544
|
+
else result.to_s
|
|
545
|
+
end
|
|
546
|
+
{ "content" => [{ "type" => "text", "text" => text }] }
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Returns nil when the arguments satisfy the schema, else an
|
|
550
|
+
# { "error" => "missing required argument 'p' (tool takes p1, p2)" } hash
|
|
551
|
+
# (or "unknown argument ..." with the same shape).
|
|
552
|
+
def validate_tool_arguments(tool_name, schema, arguments)
|
|
553
|
+
return { "error" => "arguments for #{tool_name} must be an object" } unless arguments.is_a?(Hash)
|
|
527
554
|
|
|
528
|
-
|
|
555
|
+
properties = (schema || {}).fetch("properties", {}).keys
|
|
556
|
+
takes = "(#{tool_name} takes #{properties.join(', ')})"
|
|
557
|
+
|
|
558
|
+
missing = (schema || {}).fetch("required", []).find { |name| !arguments.key?(name) }
|
|
559
|
+
return { "error" => "missing required argument '#{missing}' #{takes}" } if missing
|
|
560
|
+
|
|
561
|
+
unknown = arguments.keys.map(&:to_s).find { |name| !properties.include?(name) }
|
|
562
|
+
return { "error" => "unknown argument '#{unknown}' #{takes}" } if unknown
|
|
563
|
+
|
|
564
|
+
nil
|
|
529
565
|
end
|
|
530
566
|
|
|
531
567
|
def _handle_resources_list(_params)
|
|
@@ -888,7 +924,13 @@ module Tina4
|
|
|
888
924
|
unless table.to_s =~ /\A[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?\z/
|
|
889
925
|
return { "error" => "Invalid table name" }
|
|
890
926
|
end
|
|
891
|
-
|
|
927
|
+
# Schema metadata (PRAGMA / information_schema), never the first row, so
|
|
928
|
+
# an EMPTY table still reports its columns. A table that does not exist
|
|
929
|
+
# has no columns: say so rather than answering [].
|
|
930
|
+
columns = db.columns(table)
|
|
931
|
+
next { "error" => "table not found: #{table}" } if columns.nil? || columns.empty?
|
|
932
|
+
|
|
933
|
+
columns
|
|
892
934
|
}, "Get column definitions for a table")
|
|
893
935
|
|
|
894
936
|
# ── Route Tools ───────────────────────────────────
|
|
@@ -899,7 +941,8 @@ module Tina4
|
|
|
899
941
|
{
|
|
900
942
|
"method" => route.method.to_s,
|
|
901
943
|
"path" => route.path.to_s,
|
|
902
|
-
"auth_required" => route.auth_required ? true : false
|
|
944
|
+
"auth_required" => route.auth_required ? true : false,
|
|
945
|
+
"middleware" => route.middleware.map { |attached| Tina4.middleware_label(attached) }
|
|
903
946
|
}
|
|
904
947
|
end
|
|
905
948
|
}, "List all registered routes")
|
|
@@ -1458,9 +1501,14 @@ module Tina4
|
|
|
1458
1501
|
Tina4::Docs.cached(project_root).class_spec(name.to_s)
|
|
1459
1502
|
}, "Full class reflection (methods, file, line) from the live API index")
|
|
1460
1503
|
|
|
1461
|
-
server.register_tool("api_method", lambda { |
|
|
1462
|
-
|
|
1463
|
-
|
|
1504
|
+
server.register_tool("api_method", lambda { |name:, **rest|
|
|
1505
|
+
class_name = rest[:class].to_s
|
|
1506
|
+
Tina4::Docs.cached(project_root).method_spec(class_name, name.to_s) ||
|
|
1507
|
+
{ "error" => "method not found: #{class_name}.#{name}" }
|
|
1508
|
+
}, "Single method spec (signature, params, return, file, line) from the live API index",
|
|
1509
|
+
{ "type" => "object",
|
|
1510
|
+
"properties" => { "class" => { "type" => "string" }, "name" => { "type" => "string" } },
|
|
1511
|
+
"required" => %w[class name] })
|
|
1464
1512
|
|
|
1465
1513
|
# ── Code/doc grounding (semantic FTS over this repo's source) ──
|
|
1466
1514
|
# code_search is the fuzzy DUAL of the structural api_* tools:
|
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.147
|
|
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-10-
|
|
11
|
+
date: 2026-10-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sqlite3
|