tina4ruby 3.11.14 → 3.11.15

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -80
  3. data/LICENSE.txt +21 -21
  4. data/README.md +137 -137
  5. data/exe/tina4ruby +5 -5
  6. data/lib/tina4/ai.rb +696 -696
  7. data/lib/tina4/api.rb +189 -189
  8. data/lib/tina4/auth.rb +305 -305
  9. data/lib/tina4/auto_crud.rb +244 -244
  10. data/lib/tina4/cache.rb +154 -154
  11. data/lib/tina4/cli.rb +1449 -1449
  12. data/lib/tina4/constants.rb +46 -46
  13. data/lib/tina4/container.rb +74 -74
  14. data/lib/tina4/cors.rb +74 -74
  15. data/lib/tina4/crud.rb +692 -692
  16. data/lib/tina4/database/sqlite3_adapter.rb +165 -165
  17. data/lib/tina4/database.rb +625 -625
  18. data/lib/tina4/database_result.rb +208 -208
  19. data/lib/tina4/debug.rb +8 -8
  20. data/lib/tina4/dev.rb +14 -14
  21. data/lib/tina4/dev_admin.rb +935 -935
  22. data/lib/tina4/dev_mailbox.rb +191 -191
  23. data/lib/tina4/drivers/firebird_driver.rb +124 -124
  24. data/lib/tina4/drivers/mongodb_driver.rb +561 -561
  25. data/lib/tina4/drivers/mssql_driver.rb +112 -112
  26. data/lib/tina4/drivers/mysql_driver.rb +90 -90
  27. data/lib/tina4/drivers/odbc_driver.rb +191 -191
  28. data/lib/tina4/drivers/postgres_driver.rb +116 -116
  29. data/lib/tina4/drivers/sqlite_driver.rb +122 -122
  30. data/lib/tina4/env.rb +95 -95
  31. data/lib/tina4/error_overlay.rb +252 -252
  32. data/lib/tina4/events.rb +109 -109
  33. data/lib/tina4/field_types.rb +154 -154
  34. data/lib/tina4/frond.rb +2025 -2025
  35. data/lib/tina4/gallery/auth/meta.json +1 -1
  36. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
  37. data/lib/tina4/gallery/database/meta.json +1 -1
  38. data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
  39. data/lib/tina4/gallery/error-overlay/meta.json +1 -1
  40. data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
  41. data/lib/tina4/gallery/orm/meta.json +1 -1
  42. data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
  43. data/lib/tina4/gallery/queue/meta.json +1 -1
  44. data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
  45. data/lib/tina4/gallery/rest-api/meta.json +1 -1
  46. data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
  47. data/lib/tina4/gallery/templates/meta.json +1 -1
  48. data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
  49. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
  50. data/lib/tina4/graphql.rb +966 -966
  51. data/lib/tina4/health.rb +39 -39
  52. data/lib/tina4/html_element.rb +170 -170
  53. data/lib/tina4/job.rb +80 -80
  54. data/lib/tina4/localization.rb +168 -168
  55. data/lib/tina4/log.rb +203 -203
  56. data/lib/tina4/mcp.rb +696 -696
  57. data/lib/tina4/messenger.rb +587 -587
  58. data/lib/tina4/metrics.rb +793 -793
  59. data/lib/tina4/middleware.rb +445 -445
  60. data/lib/tina4/migration.rb +451 -451
  61. data/lib/tina4/orm.rb +790 -790
  62. data/lib/tina4/public/css/tina4.css +2463 -2463
  63. data/lib/tina4/public/css/tina4.min.css +1 -1
  64. data/lib/tina4/public/images/logo.svg +5 -5
  65. data/lib/tina4/public/js/frond.min.js +2 -2
  66. data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
  67. data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
  68. data/lib/tina4/public/js/tina4.min.js +92 -92
  69. data/lib/tina4/public/js/tina4js.min.js +48 -48
  70. data/lib/tina4/public/swagger/index.html +90 -90
  71. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
  72. data/lib/tina4/query_builder.rb +380 -380
  73. data/lib/tina4/queue.rb +366 -366
  74. data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
  75. data/lib/tina4/queue_backends/lite_backend.rb +298 -298
  76. data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
  77. data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
  78. data/lib/tina4/rack_app.rb +817 -817
  79. data/lib/tina4/rate_limiter.rb +130 -130
  80. data/lib/tina4/request.rb +268 -255
  81. data/lib/tina4/response.rb +346 -346
  82. data/lib/tina4/response_cache.rb +551 -551
  83. data/lib/tina4/router.rb +406 -406
  84. data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
  85. data/lib/tina4/scss/tina4css/_badges.scss +22 -22
  86. data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
  87. data/lib/tina4/scss/tina4css/_cards.scss +49 -49
  88. data/lib/tina4/scss/tina4css/_forms.scss +156 -156
  89. data/lib/tina4/scss/tina4css/_grid.scss +81 -81
  90. data/lib/tina4/scss/tina4css/_modals.scss +84 -84
  91. data/lib/tina4/scss/tina4css/_nav.scss +149 -149
  92. data/lib/tina4/scss/tina4css/_reset.scss +94 -94
  93. data/lib/tina4/scss/tina4css/_tables.scss +54 -54
  94. data/lib/tina4/scss/tina4css/_typography.scss +55 -55
  95. data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
  96. data/lib/tina4/scss/tina4css/_variables.scss +117 -117
  97. data/lib/tina4/scss/tina4css/base.scss +1 -1
  98. data/lib/tina4/scss/tina4css/colors.scss +48 -48
  99. data/lib/tina4/scss/tina4css/tina4.scss +17 -17
  100. data/lib/tina4/scss_compiler.rb +178 -178
  101. data/lib/tina4/seeder.rb +567 -567
  102. data/lib/tina4/service_runner.rb +303 -303
  103. data/lib/tina4/session.rb +297 -297
  104. data/lib/tina4/session_handlers/database_handler.rb +72 -72
  105. data/lib/tina4/session_handlers/file_handler.rb +67 -67
  106. data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
  107. data/lib/tina4/session_handlers/redis_handler.rb +43 -43
  108. data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
  109. data/lib/tina4/shutdown.rb +84 -84
  110. data/lib/tina4/sql_translation.rb +158 -158
  111. data/lib/tina4/swagger.rb +124 -124
  112. data/lib/tina4/template.rb +894 -894
  113. data/lib/tina4/templates/base.twig +26 -26
  114. data/lib/tina4/templates/errors/302.twig +14 -14
  115. data/lib/tina4/templates/errors/401.twig +9 -9
  116. data/lib/tina4/templates/errors/403.twig +29 -29
  117. data/lib/tina4/templates/errors/404.twig +29 -29
  118. data/lib/tina4/templates/errors/500.twig +38 -38
  119. data/lib/tina4/templates/errors/502.twig +9 -9
  120. data/lib/tina4/templates/errors/503.twig +12 -12
  121. data/lib/tina4/templates/errors/base.twig +37 -37
  122. data/lib/tina4/test_client.rb +159 -159
  123. data/lib/tina4/testing.rb +340 -340
  124. data/lib/tina4/validator.rb +174 -174
  125. data/lib/tina4/version.rb +1 -1
  126. data/lib/tina4/webserver.rb +312 -312
  127. data/lib/tina4/websocket.rb +343 -343
  128. data/lib/tina4/websocket_backplane.rb +190 -190
  129. data/lib/tina4/wsdl.rb +564 -564
  130. data/lib/tina4.rb +458 -458
  131. data/lib/tina4ruby.rb +4 -4
  132. metadata +2 -2
data/lib/tina4/ai.rb CHANGED
@@ -1,696 +1,696 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
-
5
- module Tina4
6
- # Tina4 AI -- Install AI coding assistant context files.
7
- #
8
- # Simple menu-driven installer for AI tool context files.
9
- # The user picks which tools they use, we install the appropriate files.
10
- #
11
- # selection = Tina4::AI.show_menu(".")
12
- # Tina4::AI.install_selected(".", selection)
13
- #
14
- module AI
15
- # Ordered list of supported AI tools
16
- AI_TOOLS = [
17
- { name: "claude-code", description: "Claude Code", context_file: "CLAUDE.md", config_dir: ".claude" },
18
- { name: "cursor", description: "Cursor", context_file: ".cursorules", config_dir: ".cursor" },
19
- { name: "copilot", description: "GitHub Copilot", context_file: ".github/copilot-instructions.md", config_dir: ".github" },
20
- { name: "windsurf", description: "Windsurf", context_file: ".windsurfrules", config_dir: nil },
21
- { name: "aider", description: "Aider", context_file: "CONVENTIONS.md", config_dir: nil },
22
- { name: "cline", description: "Cline", context_file: ".clinerules", config_dir: nil },
23
- { name: "codex", description: "OpenAI Codex", context_file: "AGENTS.md", config_dir: nil }
24
- ].freeze
25
-
26
- class << self
27
- # Check if a tool's context file already exists.
28
- #
29
- # @param root [String] project root directory
30
- # @param tool [Hash] tool entry from AI_TOOLS
31
- # @return [Boolean]
32
- def is_installed(root, tool)
33
- File.exist?(File.join(File.expand_path(root), tool[:context_file]))
34
- end
35
-
36
- # Print the numbered menu and return user input.
37
- #
38
- # @param root [String] project root directory (default: ".")
39
- # @return [String] user input (comma-separated numbers or "all")
40
- def show_menu(root = ".")
41
- root = File.expand_path(root)
42
- green = "\e[32m"
43
- reset = "\e[0m"
44
-
45
- puts "\n Tina4 AI Context Installer\n"
46
- AI_TOOLS.each_with_index do |tool, i|
47
- marker = is_installed(root, tool) ? " #{green}[installed]#{reset}" : ""
48
- puts format(" %d. %-20s %s%s", i + 1, tool[:description], tool[:context_file], marker)
49
- end
50
-
51
- # tina4-ai tools option
52
- tina4_ai_installed = system("which mdview > /dev/null 2>&1")
53
- marker = tina4_ai_installed ? " #{green}[installed]#{reset}" : ""
54
- puts " 8. Install tina4-ai tools (requires Python)#{marker}"
55
- puts
56
-
57
- print " Select (comma-separated, or 'all'): "
58
- $stdin.gets&.strip || ""
59
- end
60
-
61
- # Install context files for the selected tools.
62
- #
63
- # @param root [String] project root directory
64
- # @param selection [String] comma-separated numbers like "1,2,3" or "all"
65
- # @return [Array<String>] list of created/updated file paths
66
- def install_selected(root, selection)
67
- root_path = File.expand_path(root)
68
- created = []
69
-
70
- if selection.downcase == "all"
71
- indices = (0...AI_TOOLS.length).to_a
72
- do_tina4_ai = true
73
- else
74
- parts = selection.split(",").map(&:strip).reject(&:empty?)
75
- indices = []
76
- do_tina4_ai = false
77
- parts.each do |p|
78
- n = Integer(p) rescue next
79
- if n == 8
80
- do_tina4_ai = true
81
- elsif n >= 1 && n <= AI_TOOLS.length
82
- indices << (n - 1)
83
- end
84
- end
85
- end
86
-
87
- indices.each do |idx|
88
- tool = AI_TOOLS[idx]
89
- context = generate_context(tool[:name])
90
- files = install_for_tool(root_path, tool, context)
91
- created.concat(files)
92
- end
93
-
94
- install_tina4_ai if do_tina4_ai
95
-
96
- created
97
- end
98
-
99
- # Install context for all AI tools (non-interactive).
100
- #
101
- # @param root [String] project root directory
102
- # @return [Array<String>] list of created/updated file paths
103
- def install_all(root = ".")
104
- install_selected(root, "all")
105
- end
106
-
107
- # Generate per-tool Tina4 Ruby context document.
108
- #
109
- # @param tool_name [String] AI tool name (default: "claude-code")
110
- # @return [String]
111
- def generate_context(tool_name = "claude-code")
112
- case tool_name
113
- when "claude-code"
114
- generate_claude_code_context
115
- when "cursor"
116
- generate_cursor_context
117
- when "copilot"
118
- generate_copilot_context
119
- when "windsurf"
120
- generate_windsurf_context
121
- when "aider"
122
- generate_aider_context
123
- when "cline"
124
- generate_cline_context
125
- when "codex"
126
- generate_codex_context
127
- else
128
- generate_claude_code_context
129
- end
130
- end
131
-
132
- # Install context file for a single tool.
133
- #
134
- # @param root [String] absolute project root path
135
- # @param tool [Hash] tool entry from AI_TOOLS
136
- # @param context [String] generated context content
137
- # @return [Array<String>] list of created/updated relative file paths
138
- def install_for_tool(root, tool, context)
139
- created = []
140
- context_path = File.join(root, tool[:context_file])
141
-
142
- # Create directories
143
- if tool[:config_dir]
144
- FileUtils.mkdir_p(File.join(root, tool[:config_dir]))
145
- end
146
- FileUtils.mkdir_p(File.dirname(context_path))
147
-
148
- # Always overwrite -- user chose to install
149
- action = File.exist?(context_path) ? "Updated" : "Installed"
150
- File.write(context_path, context)
151
- rel = context_path.sub("#{root}/", "")
152
- created << rel
153
- puts " \e[32m✓\e[0m #{action} #{rel}"
154
-
155
- # Claude-specific extras
156
- if tool[:name] == "claude-code"
157
- skills = install_claude_skills(root)
158
- created.concat(skills)
159
- end
160
-
161
- created
162
- end
163
-
164
- # Copy Claude Code skill files from the framework's templates.
165
- #
166
- # @param root [String] absolute project root path
167
- # @return [Array<String>] list of created/updated relative file paths
168
- def install_claude_skills(root)
169
- created = []
170
-
171
- # Determine the framework root (where lib/tina4/ lives)
172
- framework_root = File.expand_path("../../..", __FILE__)
173
-
174
- # Copy skill directories from .claude/skills/ in the framework to the project
175
- framework_skills_dir = File.join(framework_root, ".claude", "skills")
176
- if Dir.exist?(framework_skills_dir)
177
- target_skills_dir = File.join(root, ".claude", "skills")
178
- FileUtils.mkdir_p(target_skills_dir)
179
- Dir.children(framework_skills_dir).each do |entry|
180
- skill_dir = File.join(framework_skills_dir, entry)
181
- next unless File.directory?(skill_dir)
182
-
183
- target_dir = File.join(target_skills_dir, entry)
184
- FileUtils.rm_rf(target_dir) if Dir.exist?(target_dir)
185
- FileUtils.cp_r(skill_dir, target_dir)
186
- rel = target_dir.sub("#{root}/", "")
187
- created << rel
188
- puts " \e[32m✓\e[0m Updated #{rel}"
189
- end
190
- end
191
-
192
- # Copy claude-commands if they exist
193
- commands_source = File.join(framework_root, "templates", "ai", "claude-commands")
194
- if Dir.exist?(commands_source)
195
- commands_dir = File.join(root, ".claude", "commands")
196
- FileUtils.mkdir_p(commands_dir)
197
- Dir.glob(File.join(commands_source, "*.md")).each do |skill_file|
198
- target = File.join(commands_dir, File.basename(skill_file))
199
- FileUtils.cp(skill_file, target)
200
- rel = target.sub("#{root}/", "")
201
- created << rel
202
- end
203
- end
204
-
205
- created
206
- end
207
-
208
- # Install tina4-ai package (provides mdview for markdown viewing).
209
- def install_tina4_ai
210
- puts " Installing tina4-ai tools..."
211
- %w[pip3 pip].each do |cmd|
212
- next unless system("which #{cmd} > /dev/null 2>&1")
213
-
214
- result = `#{cmd} install --upgrade tina4-ai 2>&1`
215
- if $?.success?
216
- puts " \e[32m✓\e[0m Installed tina4-ai (mdview)"
217
- return
218
- else
219
- puts " \e[33m!\e[0m #{cmd} failed: #{result.strip[0..100]}"
220
- end
221
- end
222
- puts " \e[33m!\e[0m Python/pip not available -- skip tina4-ai"
223
- end
224
-
225
- private
226
-
227
- # Read existing CLAUDE.md from the framework root.
228
- #
229
- # @return [String]
230
- def generate_claude_code_context
231
- framework_root = File.expand_path("../../..", __FILE__)
232
- claude_md = File.join(framework_root, "CLAUDE.md")
233
- if File.exist?(claude_md)
234
- File.read(claude_md)
235
- else
236
- "# Tina4 Ruby #{Tina4::VERSION}\n\nSee https://tina4.com for documentation.\n"
237
- end
238
- end
239
-
240
- # Cursor context (~45 lines).
241
- #
242
- # @return [String]
243
- def generate_cursor_context
244
- <<~CONTEXT
245
- # Tina4 Ruby #{Tina4::VERSION} — Cursor Rules
246
-
247
- You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
248
- Documentation: https://tina4.com
249
-
250
- ## Project Structure
251
-
252
- ```
253
- src/routes/ — Route handlers (auto-discovered)
254
- src/orm/ — ORM models
255
- src/templates/ — Twig templates
256
- src/app/ — Service classes
257
- src/scss/ — SCSS (auto-compiled)
258
- src/public/ — Static assets
259
- src/seeds/ — Database seeders
260
- migrations/ — SQL migration files
261
- spec/ — RSpec tests
262
- ```
263
-
264
- ## Route Pattern
265
-
266
- ```ruby
267
- Tina4.get "/api/users" do |request, response|
268
- response.call({ users: [] }, Tina4::HTTP_OK)
269
- end
270
-
271
- Tina4.post "/api/users" do |request, response|
272
- response.call({ created: request.body["name"] }, 201)
273
- end
274
- ```
275
-
276
- ## ORM Pattern
277
-
278
- ```ruby
279
- class User < Tina4::ORM
280
- table_name "users"
281
- integer_field :id, primary_key: true, auto_increment: true
282
- string_field :name, required: true
283
- string_field :email
284
- end
285
- ```
286
-
287
- ## Conventions
288
-
289
- 1. Routes return `response.call(data, status)` — never `puts` or `render`
290
- 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
291
- 3. Every template extends `base.twig`
292
- 4. All schema changes via migrations — never create tables in route code
293
- 5. Use built-in features — never install gems for things Tina4 already provides
294
- 6. Service pattern — complex logic in `src/app/`, routes stay thin
295
- 7. Use `snake_case` for methods and variables
296
-
297
- ## Built-in Features (No Gems Needed)
298
-
299
- Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
300
- CONTEXT
301
- end
302
-
303
- # GitHub Copilot context (~30 lines).
304
- #
305
- # @return [String]
306
- def generate_copilot_context
307
- <<~CONTEXT
308
- # Tina4 Ruby #{Tina4::VERSION} — Copilot Instructions
309
-
310
- This is a **Tina4 Ruby** project. Tina4 is a zero-dependency web framework. Docs: https://tina4.com
311
-
312
- ## Structure
313
-
314
- Routes in `src/routes/`, ORM models in `src/orm/`, templates in `src/templates/`, services in `src/app/`, tests in `spec/`.
315
-
316
- ## Route Example
317
-
318
- ```ruby
319
- Tina4.get "/api/users" do |request, response|
320
- response.call({ users: [] }, Tina4::HTTP_OK)
321
- end
322
-
323
- Tina4.post "/api/users" do |request, response|
324
- response.call({ created: request.body["name"] }, 201)
325
- end
326
- ```
327
-
328
- ## ORM Example
329
-
330
- ```ruby
331
- class User < Tina4::ORM
332
- table_name "users"
333
- integer_field :id, primary_key: true, auto_increment: true
334
- string_field :name, required: true
335
- string_field :email
336
- end
337
- ```
338
-
339
- ## Rules
340
-
341
- - Always return `response.call(data, status)` from routes
342
- - GET is public; POST/PUT/PATCH/DELETE require auth by default
343
- - Templates extend `base.twig`; schema changes via migrations only
344
- - Use `snake_case`; never install gems for built-in features
345
- - Built-in: Router, ORM, Database, JWT auth, Sessions, GraphQL, WebSocket, Queue, Messenger, Migrations, SCSS, Swagger, i18n, Events, DI, Testing
346
- CONTEXT
347
- end
348
-
349
- # Windsurf context (~60 lines).
350
- #
351
- # @return [String]
352
- def generate_windsurf_context
353
- <<~CONTEXT
354
- # Tina4 Ruby #{Tina4::VERSION} — Windsurf Rules
355
-
356
- You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
357
- Documentation: https://tina4.com
358
-
359
- ## Project Structure
360
-
361
- ```
362
- src/routes/ — Route handlers (auto-discovered)
363
- src/orm/ — ORM models
364
- src/templates/ — Twig templates
365
- src/app/ — Service classes
366
- src/scss/ — SCSS (auto-compiled)
367
- src/public/ — Static assets
368
- src/seeds/ — Database seeders
369
- migrations/ — SQL migration files
370
- spec/ — RSpec tests
371
- ```
372
-
373
- ## CLI Commands
374
-
375
- ```bash
376
- tina4ruby init . # Scaffold project
377
- tina4ruby serve # Start dev server on port 7147
378
- tina4ruby migrate # Run database migrations
379
- tina4ruby test # Run test suite
380
- tina4ruby routes # List all registered routes
381
- ```
382
-
383
- ## Route Pattern
384
-
385
- ```ruby
386
- Tina4.get "/api/users" do |request, response|
387
- response.call({ users: [] }, Tina4::HTTP_OK)
388
- end
389
-
390
- Tina4.post "/api/users" do |request, response|
391
- response.call({ created: request.body["name"] }, 201)
392
- end
393
- ```
394
-
395
- ## ORM Pattern
396
-
397
- ```ruby
398
- class User < Tina4::ORM
399
- table_name "users"
400
- integer_field :id, primary_key: true, auto_increment: true
401
- string_field :name, required: true
402
- string_field :email
403
- end
404
- ```
405
-
406
- ## Template Pattern
407
-
408
- ```twig
409
- {% extends "base.twig" %}
410
- {% block content %}
411
- <div class="container">
412
- <h1>{{ title }}</h1>
413
- {% for item in items %}
414
- <p>{{ item.name }}</p>
415
- {% endfor %}
416
- </div>
417
- {% endblock %}
418
- ```
419
-
420
- ## Conventions
421
-
422
- 1. Routes return `response.call(data, status)` — never `puts` or `render`
423
- 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
424
- 3. Every template extends `base.twig`
425
- 4. All schema changes via migrations — never create tables in route code
426
- 5. Use built-in features — never install gems for things Tina4 already provides
427
- 6. Service pattern — complex logic in `src/app/`, routes stay thin
428
- 7. Use `snake_case` for methods and variables
429
-
430
- ## Built-in Features (No Gems Needed)
431
-
432
- Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
433
-
434
- ## Database Drivers
435
-
436
- SQLite, PostgreSQL, MySQL, MSSQL, Firebird. Connection string format: `driver://host:port/database`.
437
-
438
- ## Auth
439
-
440
- JWT auth built-in via `Tina4::Auth`. `secure_get` / `secure_post` for protected routes. Password hashing via `Tina4::Auth.hash_password` / `check_password`.
441
- CONTEXT
442
- end
443
-
444
- # Aider context (~58 lines).
445
- #
446
- # @return [String]
447
- def generate_aider_context
448
- <<~CONTEXT
449
- # Tina4 Ruby #{Tina4::VERSION} — Conventions
450
-
451
- ## Framework
452
-
453
- Tina4 Ruby is a zero-dependency, batteries-included web framework. Docs: https://tina4.com
454
-
455
- ## Project Structure
456
-
457
- ```
458
- src/routes/ — Route handlers (auto-discovered)
459
- src/orm/ — ORM models
460
- src/templates/ — Twig templates
461
- src/app/ — Service classes
462
- src/scss/ — SCSS (auto-compiled)
463
- src/public/ — Static assets
464
- src/seeds/ — Database seeders
465
- migrations/ — SQL migration files
466
- spec/ — RSpec tests
467
- ```
468
-
469
- ## CLI
470
-
471
- ```bash
472
- tina4ruby init . # Scaffold project
473
- tina4ruby serve # Start dev server on port 7147
474
- tina4ruby migrate # Run database migrations
475
- tina4ruby test # Run test suite
476
- tina4ruby routes # List all registered routes
477
- ```
478
-
479
- ## Route Pattern
480
-
481
- ```ruby
482
- Tina4.get "/api/users" do |request, response|
483
- response.call({ users: [] }, Tina4::HTTP_OK)
484
- end
485
-
486
- Tina4.post "/api/users" do |request, response|
487
- response.call({ created: request.body["name"] }, 201)
488
- end
489
- ```
490
-
491
- ## ORM Pattern
492
-
493
- ```ruby
494
- class User < Tina4::ORM
495
- table_name "users"
496
- integer_field :id, primary_key: true, auto_increment: true
497
- string_field :name, required: true
498
- string_field :email
499
- end
500
- ```
501
-
502
- ## Conventions
503
-
504
- 1. Routes return `response.call(data, status)` — never `puts` or `render`
505
- 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
506
- 3. Every template extends `base.twig`
507
- 4. All schema changes via migrations — never create tables in route code
508
- 5. Use built-in features — never install gems for things Tina4 already provides
509
- 6. Service pattern — complex logic in `src/app/`, routes stay thin
510
- 7. Use `snake_case` for methods and variables
511
- 8. Wrap route logic in `begin/rescue`, log with `Tina4::Log.error()`
512
-
513
- ## Built-in Features
514
-
515
- Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
516
-
517
- ## Testing
518
-
519
- Run: `bundle exec rspec` or `tina4ruby test`. Tests in `spec/`.
520
- CONTEXT
521
- end
522
-
523
- # Cline context (~42 lines).
524
- #
525
- # @return [String]
526
- def generate_cline_context
527
- <<~CONTEXT
528
- # Tina4 Ruby #{Tina4::VERSION} — Cline Rules
529
-
530
- Tina4 Ruby is a zero-dependency web framework. Docs: https://tina4.com
531
-
532
- ## Structure
533
-
534
- ```
535
- src/routes/ — Route handlers (auto-discovered)
536
- src/orm/ — ORM models
537
- src/templates/ — Twig templates
538
- src/app/ — Service classes
539
- src/scss/ — SCSS (auto-compiled)
540
- src/public/ — Static assets
541
- src/seeds/ — Database seeders
542
- migrations/ — SQL migration files
543
- spec/ — RSpec tests
544
- ```
545
-
546
- ## Route Pattern
547
-
548
- ```ruby
549
- Tina4.get "/api/users" do |request, response|
550
- response.call({ users: [] }, Tina4::HTTP_OK)
551
- end
552
-
553
- Tina4.post "/api/users" do |request, response|
554
- response.call({ created: request.body["name"] }, 201)
555
- end
556
- ```
557
-
558
- ## ORM Pattern
559
-
560
- ```ruby
561
- class User < Tina4::ORM
562
- table_name "users"
563
- integer_field :id, primary_key: true, auto_increment: true
564
- string_field :name, required: true
565
- string_field :email
566
- end
567
- ```
568
-
569
- ## Conventions
570
-
571
- 1. Routes return `response.call(data, status)` — never `puts` or `render`
572
- 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
573
- 3. Every template extends `base.twig`
574
- 4. All schema changes via migrations — never create tables in route code
575
- 5. Use built-in features — never install gems for things Tina4 already provides
576
- 6. Service pattern — complex logic in `src/app/`, routes stay thin
577
- 7. Use `snake_case` for methods and variables
578
-
579
- ## Built-in Features
580
-
581
- Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions, GraphQL, WebSocket, Queue, Messenger, Migrations, SCSS, Swagger, i18n, Events, Container/DI, Testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
582
- CONTEXT
583
- end
584
-
585
- # OpenAI Codex context (~70 lines).
586
- #
587
- # @return [String]
588
- def generate_codex_context
589
- <<~CONTEXT
590
- # Tina4 Ruby #{Tina4::VERSION} — Codex Agent Instructions
591
-
592
- You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
593
- Documentation: https://tina4.com
594
-
595
- ## Project Structure
596
-
597
- ```
598
- src/routes/ — Route handlers (auto-discovered)
599
- src/orm/ — ORM models
600
- src/templates/ — Twig templates
601
- src/app/ — Service classes
602
- src/scss/ — SCSS (auto-compiled)
603
- src/public/ — Static assets
604
- src/seeds/ — Database seeders
605
- migrations/ — SQL migration files
606
- spec/ — RSpec tests
607
- ```
608
-
609
- ## CLI Commands
610
-
611
- ```bash
612
- tina4ruby init . # Scaffold project
613
- tina4ruby serve # Start dev server on port 7147
614
- tina4ruby serve --dev # Dev mode with auto-reload
615
- tina4ruby migrate # Run database migrations
616
- tina4ruby test # Run test suite
617
- tina4ruby routes # List all registered routes
618
- tina4ruby seed # Run database seeders
619
- ```
620
-
621
- ## Route Pattern
622
-
623
- ```ruby
624
- Tina4.get "/api/users" do |request, response|
625
- response.call({ users: [] }, Tina4::HTTP_OK)
626
- end
627
-
628
- Tina4.post "/api/users" do |request, response|
629
- response.call({ created: request.body["name"] }, 201)
630
- end
631
-
632
- # Protected GET route
633
- Tina4.secure_get "/api/admin/users" do |request, response|
634
- response.call({ users: User.all }, Tina4::HTTP_OK)
635
- end
636
-
637
- # Route with template rendering
638
- Tina4::Router.get "/dashboard", template: "dashboard.twig" do |request, response|
639
- response.call({ title: "Dashboard" }, Tina4::HTTP_OK)
640
- end
641
- ```
642
-
643
- ## ORM Pattern
644
-
645
- ```ruby
646
- class User < Tina4::ORM
647
- table_name "users"
648
- integer_field :id, primary_key: true, auto_increment: true
649
- string_field :name, required: true
650
- string_field :email
651
- end
652
-
653
- # Usage
654
- user = User.create(name: "Alice", email: "alice@example.com")
655
- users = User.where("name LIKE ?", ["%ali%"])
656
- user = User.find(1)
657
- ```
658
-
659
- ## Template Pattern
660
-
661
- ```twig
662
- {% extends "base.twig" %}
663
- {% block content %}
664
- <div class="container">
665
- <h1>{{ title }}</h1>
666
- {% for item in items %}
667
- <p>{{ item.name }}</p>
668
- {% endfor %}
669
- </div>
670
- {% endblock %}
671
- ```
672
-
673
- ## Conventions
674
-
675
- 1. Routes return `response.call(data, status)` — never `puts` or `render`
676
- 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
677
- 3. Every template extends `base.twig`
678
- 4. All schema changes via migrations — never create tables in route code
679
- 5. Use built-in features — never install gems for things Tina4 already provides
680
- 6. Service pattern — complex logic in `src/app/`, routes stay thin
681
- 7. Use `snake_case` for methods and variables
682
- 8. Wrap route logic in `begin/rescue`, log with `Tina4::Log.error()`
683
- 9. Database drivers: SQLite, PostgreSQL, MySQL, MSSQL, Firebird
684
-
685
- ## Built-in Features (No Gems Needed)
686
-
687
- Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
688
-
689
- ## Testing
690
-
691
- Run: `bundle exec rspec` or `tina4ruby test`. Tests live in `spec/`. Use `Tina4::Testing` for inline tests.
692
- CONTEXT
693
- end
694
- end
695
- end
696
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Tina4
6
+ # Tina4 AI -- Install AI coding assistant context files.
7
+ #
8
+ # Simple menu-driven installer for AI tool context files.
9
+ # The user picks which tools they use, we install the appropriate files.
10
+ #
11
+ # selection = Tina4::AI.show_menu(".")
12
+ # Tina4::AI.install_selected(".", selection)
13
+ #
14
+ module AI
15
+ # Ordered list of supported AI tools
16
+ AI_TOOLS = [
17
+ { name: "claude-code", description: "Claude Code", context_file: "CLAUDE.md", config_dir: ".claude" },
18
+ { name: "cursor", description: "Cursor", context_file: ".cursorules", config_dir: ".cursor" },
19
+ { name: "copilot", description: "GitHub Copilot", context_file: ".github/copilot-instructions.md", config_dir: ".github" },
20
+ { name: "windsurf", description: "Windsurf", context_file: ".windsurfrules", config_dir: nil },
21
+ { name: "aider", description: "Aider", context_file: "CONVENTIONS.md", config_dir: nil },
22
+ { name: "cline", description: "Cline", context_file: ".clinerules", config_dir: nil },
23
+ { name: "codex", description: "OpenAI Codex", context_file: "AGENTS.md", config_dir: nil }
24
+ ].freeze
25
+
26
+ class << self
27
+ # Check if a tool's context file already exists.
28
+ #
29
+ # @param root [String] project root directory
30
+ # @param tool [Hash] tool entry from AI_TOOLS
31
+ # @return [Boolean]
32
+ def is_installed(root, tool)
33
+ File.exist?(File.join(File.expand_path(root), tool[:context_file]))
34
+ end
35
+
36
+ # Print the numbered menu and return user input.
37
+ #
38
+ # @param root [String] project root directory (default: ".")
39
+ # @return [String] user input (comma-separated numbers or "all")
40
+ def show_menu(root = ".")
41
+ root = File.expand_path(root)
42
+ green = "\e[32m"
43
+ reset = "\e[0m"
44
+
45
+ puts "\n Tina4 AI Context Installer\n"
46
+ AI_TOOLS.each_with_index do |tool, i|
47
+ marker = is_installed(root, tool) ? " #{green}[installed]#{reset}" : ""
48
+ puts format(" %d. %-20s %s%s", i + 1, tool[:description], tool[:context_file], marker)
49
+ end
50
+
51
+ # tina4-ai tools option
52
+ tina4_ai_installed = system("which mdview > /dev/null 2>&1")
53
+ marker = tina4_ai_installed ? " #{green}[installed]#{reset}" : ""
54
+ puts " 8. Install tina4-ai tools (requires Python)#{marker}"
55
+ puts
56
+
57
+ print " Select (comma-separated, or 'all'): "
58
+ $stdin.gets&.strip || ""
59
+ end
60
+
61
+ # Install context files for the selected tools.
62
+ #
63
+ # @param root [String] project root directory
64
+ # @param selection [String] comma-separated numbers like "1,2,3" or "all"
65
+ # @return [Array<String>] list of created/updated file paths
66
+ def install_selected(root, selection)
67
+ root_path = File.expand_path(root)
68
+ created = []
69
+
70
+ if selection.downcase == "all"
71
+ indices = (0...AI_TOOLS.length).to_a
72
+ do_tina4_ai = true
73
+ else
74
+ parts = selection.split(",").map(&:strip).reject(&:empty?)
75
+ indices = []
76
+ do_tina4_ai = false
77
+ parts.each do |p|
78
+ n = Integer(p) rescue next
79
+ if n == 8
80
+ do_tina4_ai = true
81
+ elsif n >= 1 && n <= AI_TOOLS.length
82
+ indices << (n - 1)
83
+ end
84
+ end
85
+ end
86
+
87
+ indices.each do |idx|
88
+ tool = AI_TOOLS[idx]
89
+ context = generate_context(tool[:name])
90
+ files = install_for_tool(root_path, tool, context)
91
+ created.concat(files)
92
+ end
93
+
94
+ install_tina4_ai if do_tina4_ai
95
+
96
+ created
97
+ end
98
+
99
+ # Install context for all AI tools (non-interactive).
100
+ #
101
+ # @param root [String] project root directory
102
+ # @return [Array<String>] list of created/updated file paths
103
+ def install_all(root = ".")
104
+ install_selected(root, "all")
105
+ end
106
+
107
+ # Generate per-tool Tina4 Ruby context document.
108
+ #
109
+ # @param tool_name [String] AI tool name (default: "claude-code")
110
+ # @return [String]
111
+ def generate_context(tool_name = "claude-code")
112
+ case tool_name
113
+ when "claude-code"
114
+ generate_claude_code_context
115
+ when "cursor"
116
+ generate_cursor_context
117
+ when "copilot"
118
+ generate_copilot_context
119
+ when "windsurf"
120
+ generate_windsurf_context
121
+ when "aider"
122
+ generate_aider_context
123
+ when "cline"
124
+ generate_cline_context
125
+ when "codex"
126
+ generate_codex_context
127
+ else
128
+ generate_claude_code_context
129
+ end
130
+ end
131
+
132
+ # Install context file for a single tool.
133
+ #
134
+ # @param root [String] absolute project root path
135
+ # @param tool [Hash] tool entry from AI_TOOLS
136
+ # @param context [String] generated context content
137
+ # @return [Array<String>] list of created/updated relative file paths
138
+ def install_for_tool(root, tool, context)
139
+ created = []
140
+ context_path = File.join(root, tool[:context_file])
141
+
142
+ # Create directories
143
+ if tool[:config_dir]
144
+ FileUtils.mkdir_p(File.join(root, tool[:config_dir]))
145
+ end
146
+ FileUtils.mkdir_p(File.dirname(context_path))
147
+
148
+ # Always overwrite -- user chose to install
149
+ action = File.exist?(context_path) ? "Updated" : "Installed"
150
+ File.write(context_path, context)
151
+ rel = context_path.sub("#{root}/", "")
152
+ created << rel
153
+ puts " \e[32m✓\e[0m #{action} #{rel}"
154
+
155
+ # Claude-specific extras
156
+ if tool[:name] == "claude-code"
157
+ skills = install_claude_skills(root)
158
+ created.concat(skills)
159
+ end
160
+
161
+ created
162
+ end
163
+
164
+ # Copy Claude Code skill files from the framework's templates.
165
+ #
166
+ # @param root [String] absolute project root path
167
+ # @return [Array<String>] list of created/updated relative file paths
168
+ def install_claude_skills(root)
169
+ created = []
170
+
171
+ # Determine the framework root (where lib/tina4/ lives)
172
+ framework_root = File.expand_path("../../..", __FILE__)
173
+
174
+ # Copy skill directories from .claude/skills/ in the framework to the project
175
+ framework_skills_dir = File.join(framework_root, ".claude", "skills")
176
+ if Dir.exist?(framework_skills_dir)
177
+ target_skills_dir = File.join(root, ".claude", "skills")
178
+ FileUtils.mkdir_p(target_skills_dir)
179
+ Dir.children(framework_skills_dir).each do |entry|
180
+ skill_dir = File.join(framework_skills_dir, entry)
181
+ next unless File.directory?(skill_dir)
182
+
183
+ target_dir = File.join(target_skills_dir, entry)
184
+ FileUtils.rm_rf(target_dir) if Dir.exist?(target_dir)
185
+ FileUtils.cp_r(skill_dir, target_dir)
186
+ rel = target_dir.sub("#{root}/", "")
187
+ created << rel
188
+ puts " \e[32m✓\e[0m Updated #{rel}"
189
+ end
190
+ end
191
+
192
+ # Copy claude-commands if they exist
193
+ commands_source = File.join(framework_root, "templates", "ai", "claude-commands")
194
+ if Dir.exist?(commands_source)
195
+ commands_dir = File.join(root, ".claude", "commands")
196
+ FileUtils.mkdir_p(commands_dir)
197
+ Dir.glob(File.join(commands_source, "*.md")).each do |skill_file|
198
+ target = File.join(commands_dir, File.basename(skill_file))
199
+ FileUtils.cp(skill_file, target)
200
+ rel = target.sub("#{root}/", "")
201
+ created << rel
202
+ end
203
+ end
204
+
205
+ created
206
+ end
207
+
208
+ # Install tina4-ai package (provides mdview for markdown viewing).
209
+ def install_tina4_ai
210
+ puts " Installing tina4-ai tools..."
211
+ %w[pip3 pip].each do |cmd|
212
+ next unless system("which #{cmd} > /dev/null 2>&1")
213
+
214
+ result = `#{cmd} install --upgrade tina4-ai 2>&1`
215
+ if $?.success?
216
+ puts " \e[32m✓\e[0m Installed tina4-ai (mdview)"
217
+ return
218
+ else
219
+ puts " \e[33m!\e[0m #{cmd} failed: #{result.strip[0..100]}"
220
+ end
221
+ end
222
+ puts " \e[33m!\e[0m Python/pip not available -- skip tina4-ai"
223
+ end
224
+
225
+ private
226
+
227
+ # Read existing CLAUDE.md from the framework root.
228
+ #
229
+ # @return [String]
230
+ def generate_claude_code_context
231
+ framework_root = File.expand_path("../../..", __FILE__)
232
+ claude_md = File.join(framework_root, "CLAUDE.md")
233
+ if File.exist?(claude_md)
234
+ File.read(claude_md)
235
+ else
236
+ "# Tina4 Ruby #{Tina4::VERSION}\n\nSee https://tina4.com for documentation.\n"
237
+ end
238
+ end
239
+
240
+ # Cursor context (~45 lines).
241
+ #
242
+ # @return [String]
243
+ def generate_cursor_context
244
+ <<~CONTEXT
245
+ # Tina4 Ruby #{Tina4::VERSION} — Cursor Rules
246
+
247
+ You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
248
+ Documentation: https://tina4.com
249
+
250
+ ## Project Structure
251
+
252
+ ```
253
+ src/routes/ — Route handlers (auto-discovered)
254
+ src/orm/ — ORM models
255
+ src/templates/ — Twig templates
256
+ src/app/ — Service classes
257
+ src/scss/ — SCSS (auto-compiled)
258
+ src/public/ — Static assets
259
+ src/seeds/ — Database seeders
260
+ migrations/ — SQL migration files
261
+ spec/ — RSpec tests
262
+ ```
263
+
264
+ ## Route Pattern
265
+
266
+ ```ruby
267
+ Tina4.get "/api/users" do |request, response|
268
+ response.call({ users: [] }, Tina4::HTTP_OK)
269
+ end
270
+
271
+ Tina4.post "/api/users" do |request, response|
272
+ response.call({ created: request.body["name"] }, 201)
273
+ end
274
+ ```
275
+
276
+ ## ORM Pattern
277
+
278
+ ```ruby
279
+ class User < Tina4::ORM
280
+ table_name "users"
281
+ integer_field :id, primary_key: true, auto_increment: true
282
+ string_field :name, required: true
283
+ string_field :email
284
+ end
285
+ ```
286
+
287
+ ## Conventions
288
+
289
+ 1. Routes return `response.call(data, status)` — never `puts` or `render`
290
+ 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
291
+ 3. Every template extends `base.twig`
292
+ 4. All schema changes via migrations — never create tables in route code
293
+ 5. Use built-in features — never install gems for things Tina4 already provides
294
+ 6. Service pattern — complex logic in `src/app/`, routes stay thin
295
+ 7. Use `snake_case` for methods and variables
296
+
297
+ ## Built-in Features (No Gems Needed)
298
+
299
+ Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
300
+ CONTEXT
301
+ end
302
+
303
+ # GitHub Copilot context (~30 lines).
304
+ #
305
+ # @return [String]
306
+ def generate_copilot_context
307
+ <<~CONTEXT
308
+ # Tina4 Ruby #{Tina4::VERSION} — Copilot Instructions
309
+
310
+ This is a **Tina4 Ruby** project. Tina4 is a zero-dependency web framework. Docs: https://tina4.com
311
+
312
+ ## Structure
313
+
314
+ Routes in `src/routes/`, ORM models in `src/orm/`, templates in `src/templates/`, services in `src/app/`, tests in `spec/`.
315
+
316
+ ## Route Example
317
+
318
+ ```ruby
319
+ Tina4.get "/api/users" do |request, response|
320
+ response.call({ users: [] }, Tina4::HTTP_OK)
321
+ end
322
+
323
+ Tina4.post "/api/users" do |request, response|
324
+ response.call({ created: request.body["name"] }, 201)
325
+ end
326
+ ```
327
+
328
+ ## ORM Example
329
+
330
+ ```ruby
331
+ class User < Tina4::ORM
332
+ table_name "users"
333
+ integer_field :id, primary_key: true, auto_increment: true
334
+ string_field :name, required: true
335
+ string_field :email
336
+ end
337
+ ```
338
+
339
+ ## Rules
340
+
341
+ - Always return `response.call(data, status)` from routes
342
+ - GET is public; POST/PUT/PATCH/DELETE require auth by default
343
+ - Templates extend `base.twig`; schema changes via migrations only
344
+ - Use `snake_case`; never install gems for built-in features
345
+ - Built-in: Router, ORM, Database, JWT auth, Sessions, GraphQL, WebSocket, Queue, Messenger, Migrations, SCSS, Swagger, i18n, Events, DI, Testing
346
+ CONTEXT
347
+ end
348
+
349
+ # Windsurf context (~60 lines).
350
+ #
351
+ # @return [String]
352
+ def generate_windsurf_context
353
+ <<~CONTEXT
354
+ # Tina4 Ruby #{Tina4::VERSION} — Windsurf Rules
355
+
356
+ You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
357
+ Documentation: https://tina4.com
358
+
359
+ ## Project Structure
360
+
361
+ ```
362
+ src/routes/ — Route handlers (auto-discovered)
363
+ src/orm/ — ORM models
364
+ src/templates/ — Twig templates
365
+ src/app/ — Service classes
366
+ src/scss/ — SCSS (auto-compiled)
367
+ src/public/ — Static assets
368
+ src/seeds/ — Database seeders
369
+ migrations/ — SQL migration files
370
+ spec/ — RSpec tests
371
+ ```
372
+
373
+ ## CLI Commands
374
+
375
+ ```bash
376
+ tina4ruby init . # Scaffold project
377
+ tina4ruby serve # Start dev server on port 7147
378
+ tina4ruby migrate # Run database migrations
379
+ tina4ruby test # Run test suite
380
+ tina4ruby routes # List all registered routes
381
+ ```
382
+
383
+ ## Route Pattern
384
+
385
+ ```ruby
386
+ Tina4.get "/api/users" do |request, response|
387
+ response.call({ users: [] }, Tina4::HTTP_OK)
388
+ end
389
+
390
+ Tina4.post "/api/users" do |request, response|
391
+ response.call({ created: request.body["name"] }, 201)
392
+ end
393
+ ```
394
+
395
+ ## ORM Pattern
396
+
397
+ ```ruby
398
+ class User < Tina4::ORM
399
+ table_name "users"
400
+ integer_field :id, primary_key: true, auto_increment: true
401
+ string_field :name, required: true
402
+ string_field :email
403
+ end
404
+ ```
405
+
406
+ ## Template Pattern
407
+
408
+ ```twig
409
+ {% extends "base.twig" %}
410
+ {% block content %}
411
+ <div class="container">
412
+ <h1>{{ title }}</h1>
413
+ {% for item in items %}
414
+ <p>{{ item.name }}</p>
415
+ {% endfor %}
416
+ </div>
417
+ {% endblock %}
418
+ ```
419
+
420
+ ## Conventions
421
+
422
+ 1. Routes return `response.call(data, status)` — never `puts` or `render`
423
+ 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
424
+ 3. Every template extends `base.twig`
425
+ 4. All schema changes via migrations — never create tables in route code
426
+ 5. Use built-in features — never install gems for things Tina4 already provides
427
+ 6. Service pattern — complex logic in `src/app/`, routes stay thin
428
+ 7. Use `snake_case` for methods and variables
429
+
430
+ ## Built-in Features (No Gems Needed)
431
+
432
+ Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
433
+
434
+ ## Database Drivers
435
+
436
+ SQLite, PostgreSQL, MySQL, MSSQL, Firebird. Connection string format: `driver://host:port/database`.
437
+
438
+ ## Auth
439
+
440
+ JWT auth built-in via `Tina4::Auth`. `secure_get` / `secure_post` for protected routes. Password hashing via `Tina4::Auth.hash_password` / `check_password`.
441
+ CONTEXT
442
+ end
443
+
444
+ # Aider context (~58 lines).
445
+ #
446
+ # @return [String]
447
+ def generate_aider_context
448
+ <<~CONTEXT
449
+ # Tina4 Ruby #{Tina4::VERSION} — Conventions
450
+
451
+ ## Framework
452
+
453
+ Tina4 Ruby is a zero-dependency, batteries-included web framework. Docs: https://tina4.com
454
+
455
+ ## Project Structure
456
+
457
+ ```
458
+ src/routes/ — Route handlers (auto-discovered)
459
+ src/orm/ — ORM models
460
+ src/templates/ — Twig templates
461
+ src/app/ — Service classes
462
+ src/scss/ — SCSS (auto-compiled)
463
+ src/public/ — Static assets
464
+ src/seeds/ — Database seeders
465
+ migrations/ — SQL migration files
466
+ spec/ — RSpec tests
467
+ ```
468
+
469
+ ## CLI
470
+
471
+ ```bash
472
+ tina4ruby init . # Scaffold project
473
+ tina4ruby serve # Start dev server on port 7147
474
+ tina4ruby migrate # Run database migrations
475
+ tina4ruby test # Run test suite
476
+ tina4ruby routes # List all registered routes
477
+ ```
478
+
479
+ ## Route Pattern
480
+
481
+ ```ruby
482
+ Tina4.get "/api/users" do |request, response|
483
+ response.call({ users: [] }, Tina4::HTTP_OK)
484
+ end
485
+
486
+ Tina4.post "/api/users" do |request, response|
487
+ response.call({ created: request.body["name"] }, 201)
488
+ end
489
+ ```
490
+
491
+ ## ORM Pattern
492
+
493
+ ```ruby
494
+ class User < Tina4::ORM
495
+ table_name "users"
496
+ integer_field :id, primary_key: true, auto_increment: true
497
+ string_field :name, required: true
498
+ string_field :email
499
+ end
500
+ ```
501
+
502
+ ## Conventions
503
+
504
+ 1. Routes return `response.call(data, status)` — never `puts` or `render`
505
+ 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
506
+ 3. Every template extends `base.twig`
507
+ 4. All schema changes via migrations — never create tables in route code
508
+ 5. Use built-in features — never install gems for things Tina4 already provides
509
+ 6. Service pattern — complex logic in `src/app/`, routes stay thin
510
+ 7. Use `snake_case` for methods and variables
511
+ 8. Wrap route logic in `begin/rescue`, log with `Tina4::Log.error()`
512
+
513
+ ## Built-in Features
514
+
515
+ Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
516
+
517
+ ## Testing
518
+
519
+ Run: `bundle exec rspec` or `tina4ruby test`. Tests in `spec/`.
520
+ CONTEXT
521
+ end
522
+
523
+ # Cline context (~42 lines).
524
+ #
525
+ # @return [String]
526
+ def generate_cline_context
527
+ <<~CONTEXT
528
+ # Tina4 Ruby #{Tina4::VERSION} — Cline Rules
529
+
530
+ Tina4 Ruby is a zero-dependency web framework. Docs: https://tina4.com
531
+
532
+ ## Structure
533
+
534
+ ```
535
+ src/routes/ — Route handlers (auto-discovered)
536
+ src/orm/ — ORM models
537
+ src/templates/ — Twig templates
538
+ src/app/ — Service classes
539
+ src/scss/ — SCSS (auto-compiled)
540
+ src/public/ — Static assets
541
+ src/seeds/ — Database seeders
542
+ migrations/ — SQL migration files
543
+ spec/ — RSpec tests
544
+ ```
545
+
546
+ ## Route Pattern
547
+
548
+ ```ruby
549
+ Tina4.get "/api/users" do |request, response|
550
+ response.call({ users: [] }, Tina4::HTTP_OK)
551
+ end
552
+
553
+ Tina4.post "/api/users" do |request, response|
554
+ response.call({ created: request.body["name"] }, 201)
555
+ end
556
+ ```
557
+
558
+ ## ORM Pattern
559
+
560
+ ```ruby
561
+ class User < Tina4::ORM
562
+ table_name "users"
563
+ integer_field :id, primary_key: true, auto_increment: true
564
+ string_field :name, required: true
565
+ string_field :email
566
+ end
567
+ ```
568
+
569
+ ## Conventions
570
+
571
+ 1. Routes return `response.call(data, status)` — never `puts` or `render`
572
+ 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
573
+ 3. Every template extends `base.twig`
574
+ 4. All schema changes via migrations — never create tables in route code
575
+ 5. Use built-in features — never install gems for things Tina4 already provides
576
+ 6. Service pattern — complex logic in `src/app/`, routes stay thin
577
+ 7. Use `snake_case` for methods and variables
578
+
579
+ ## Built-in Features
580
+
581
+ Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions, GraphQL, WebSocket, Queue, Messenger, Migrations, SCSS, Swagger, i18n, Events, Container/DI, Testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
582
+ CONTEXT
583
+ end
584
+
585
+ # OpenAI Codex context (~70 lines).
586
+ #
587
+ # @return [String]
588
+ def generate_codex_context
589
+ <<~CONTEXT
590
+ # Tina4 Ruby #{Tina4::VERSION} — Codex Agent Instructions
591
+
592
+ You are working in a **Tina4 Ruby** project — a zero-dependency, batteries-included web framework.
593
+ Documentation: https://tina4.com
594
+
595
+ ## Project Structure
596
+
597
+ ```
598
+ src/routes/ — Route handlers (auto-discovered)
599
+ src/orm/ — ORM models
600
+ src/templates/ — Twig templates
601
+ src/app/ — Service classes
602
+ src/scss/ — SCSS (auto-compiled)
603
+ src/public/ — Static assets
604
+ src/seeds/ — Database seeders
605
+ migrations/ — SQL migration files
606
+ spec/ — RSpec tests
607
+ ```
608
+
609
+ ## CLI Commands
610
+
611
+ ```bash
612
+ tina4ruby init . # Scaffold project
613
+ tina4ruby serve # Start dev server on port 7147
614
+ tina4ruby serve --dev # Dev mode with auto-reload
615
+ tina4ruby migrate # Run database migrations
616
+ tina4ruby test # Run test suite
617
+ tina4ruby routes # List all registered routes
618
+ tina4ruby seed # Run database seeders
619
+ ```
620
+
621
+ ## Route Pattern
622
+
623
+ ```ruby
624
+ Tina4.get "/api/users" do |request, response|
625
+ response.call({ users: [] }, Tina4::HTTP_OK)
626
+ end
627
+
628
+ Tina4.post "/api/users" do |request, response|
629
+ response.call({ created: request.body["name"] }, 201)
630
+ end
631
+
632
+ # Protected GET route
633
+ Tina4.secure_get "/api/admin/users" do |request, response|
634
+ response.call({ users: User.all }, Tina4::HTTP_OK)
635
+ end
636
+
637
+ # Route with template rendering
638
+ Tina4::Router.get "/dashboard", template: "dashboard.twig" do |request, response|
639
+ response.call({ title: "Dashboard" }, Tina4::HTTP_OK)
640
+ end
641
+ ```
642
+
643
+ ## ORM Pattern
644
+
645
+ ```ruby
646
+ class User < Tina4::ORM
647
+ table_name "users"
648
+ integer_field :id, primary_key: true, auto_increment: true
649
+ string_field :name, required: true
650
+ string_field :email
651
+ end
652
+
653
+ # Usage
654
+ user = User.create(name: "Alice", email: "alice@example.com")
655
+ users = User.where("name LIKE ?", ["%ali%"])
656
+ user = User.find(1)
657
+ ```
658
+
659
+ ## Template Pattern
660
+
661
+ ```twig
662
+ {% extends "base.twig" %}
663
+ {% block content %}
664
+ <div class="container">
665
+ <h1>{{ title }}</h1>
666
+ {% for item in items %}
667
+ <p>{{ item.name }}</p>
668
+ {% endfor %}
669
+ </div>
670
+ {% endblock %}
671
+ ```
672
+
673
+ ## Conventions
674
+
675
+ 1. Routes return `response.call(data, status)` — never `puts` or `render`
676
+ 2. GET routes are public; POST/PUT/PATCH/DELETE require auth by default
677
+ 3. Every template extends `base.twig`
678
+ 4. All schema changes via migrations — never create tables in route code
679
+ 5. Use built-in features — never install gems for things Tina4 already provides
680
+ 6. Service pattern — complex logic in `src/app/`, routes stay thin
681
+ 7. Use `snake_case` for methods and variables
682
+ 8. Wrap route logic in `begin/rescue`, log with `Tina4::Log.error()`
683
+ 9. Database drivers: SQLite, PostgreSQL, MySQL, MSSQL, Firebird
684
+
685
+ ## Built-in Features (No Gems Needed)
686
+
687
+ Router, ORM, Database (SQLite/PostgreSQL/MySQL/MSSQL/Firebird), Frond templates (Twig-compatible), JWT auth, Sessions (File/Redis/Valkey/MongoDB/DB), GraphQL + GraphiQL, WebSocket + Redis backplane, WSDL/SOAP, Queue (File/RabbitMQ/Kafka/MongoDB), HTTP client, Messenger (SMTP/IMAP), FakeData/Seeder, Migrations, SCSS compiler, Swagger/OpenAPI, i18n, Events, Container/DI, HtmlElement, Inline testing, Error overlay, Dev dashboard, Rate limiter, Response cache, Logging, MCP server
688
+
689
+ ## Testing
690
+
691
+ Run: `bundle exec rspec` or `tina4ruby test`. Tests live in `spec/`. Use `Tina4::Testing` for inline tests.
692
+ CONTEXT
693
+ end
694
+ end
695
+ end
696
+ end