tina4-python 3.13.120__tar.gz → 3.13.121__tar.gz

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 (170) hide show
  1. {tina4_python-3.13.120 → tina4_python-3.13.121}/PKG-INFO +1 -1
  2. {tina4_python-3.13.120 → tina4_python-3.13.121}/pyproject.toml +1 -1
  3. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/cli/__init__.py +37 -6
  5. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/mcp/tools.py +93 -15
  6. {tina4_python-3.13.120 → tina4_python-3.13.121}/.gitignore +0 -0
  7. {tina4_python-3.13.120 → tina4_python-3.13.121}/README.md +0 -0
  8. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/CLAUDE.md +0 -0
  9. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/HtmlElement.py +0 -0
  10. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/Testing.py +0 -0
  11. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/_import_helper.py +0 -0
  12. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/ai/__init__.py +0 -0
  13. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/ai/client.py +0 -0
  14. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/api/__init__.py +0 -0
  15. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/auth/__init__.py +0 -0
  16. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/cache/__init__.py +0 -0
  17. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/container/__init__.py +0 -0
  18. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/context/__init__.py +0 -0
  19. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/context/chunker.py +0 -0
  20. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/__init__.py +0 -0
  21. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/cache.py +0 -0
  22. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/constants.py +0 -0
  23. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/events.py +0 -0
  24. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/middleware.py +0 -0
  25. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/port_takeover.py +0 -0
  26. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/rate_limiter.py +0 -0
  27. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/request.py +0 -0
  28. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/response.py +0 -0
  29. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/router.py +0 -0
  30. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/core/server.py +0 -0
  31. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/crud/__init__.py +0 -0
  32. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/__init__.py +0 -0
  33. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/adapter.py +0 -0
  34. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/connection.py +0 -0
  35. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/database_url.py +0 -0
  36. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/firebird.py +0 -0
  37. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/mongodb.py +0 -0
  38. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/mssql.py +0 -0
  39. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/mysql.py +0 -0
  40. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/odbc.py +0 -0
  41. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/postgres.py +0 -0
  42. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/sql_translator.py +0 -0
  43. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/database/sqlite.py +0 -0
  44. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/debug/__init__.py +0 -0
  45. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/debug/error_overlay.py +0 -0
  46. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/dev_admin/__init__.py +0 -0
  47. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/dev_admin/metrics.py +0 -0
  48. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/dev_admin/plan.py +0 -0
  49. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/dev_admin/project_index.py +0 -0
  50. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/docs.py +0 -0
  51. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/docstore/__init__.py +0 -0
  52. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/dotenv/__init__.py +0 -0
  53. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/env.py +0 -0
  54. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/frond/FROND.md +0 -0
  55. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/frond/__init__.py +0 -0
  56. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/frond/compiler.py +0 -0
  57. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/frond/engine.py +0 -0
  58. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/frond/parser.py +0 -0
  59. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/auth/meta.json +0 -0
  60. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  61. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/database/meta.json +0 -0
  62. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  63. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/error-overlay/meta.json +0 -0
  64. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  65. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/orm/meta.json +0 -0
  66. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  67. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  68. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/queue/meta.json +0 -0
  69. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  70. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/rest-api/meta.json +0 -0
  71. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  72. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/templates/meta.json +0 -0
  73. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  74. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  75. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/__init__.py +0 -0
  76. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/adapter.py +0 -0
  77. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/adapters/__init__.py +0 -0
  78. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/adapters/arango.py +0 -0
  79. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/adapters/bolt.py +0 -0
  80. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/adapters/ultipa.py +0 -0
  81. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graph/graph_url.py +0 -0
  82. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/graphql/__init__.py +0 -0
  83. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/i18n/__init__.py +0 -0
  84. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/mcp/__init__.py +0 -0
  85. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/mcp/protocol.py +0 -0
  86. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/messenger/__init__.py +0 -0
  87. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/migration/__init__.py +0 -0
  88. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/migration/runner.py +0 -0
  89. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/mqtt/__init__.py +0 -0
  90. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/mqtt/message.py +0 -0
  91. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/orm/__init__.py +0 -0
  92. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/orm/fields.py +0 -0
  93. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/orm/model.py +0 -0
  94. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/orm/point.py +0 -0
  95. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/__feedback/widget.js +0 -0
  96. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/css/tina4.css +0 -0
  97. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/css/tina4.min.css +0 -0
  98. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/favicon.ico +0 -0
  99. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/images/logo.svg +0 -0
  100. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  101. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/js/frond.js +0 -0
  102. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/js/frond.min.js +0 -0
  103. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  104. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/js/tina4.min.js +0 -0
  105. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/js/tina4js.min.js +0 -0
  106. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/swagger/index.html +0 -0
  107. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  108. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/query_builder/__init__.py +0 -0
  109. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/__init__.py +0 -0
  110. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/amqp_url.py +0 -0
  111. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/job.py +0 -0
  112. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/kafka_backend.py +0 -0
  113. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/lite_backend.py +0 -0
  114. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/mongo_backend.py +0 -0
  115. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue/rabbitmq_backend.py +0 -0
  116. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue_backends/__init__.py +0 -0
  117. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue_backends/kafka_backend.py +0 -0
  118. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue_backends/mongo_backend.py +0 -0
  119. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  120. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/__init__.py +0 -0
  121. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/Attachment.py +0 -0
  122. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/Channel.py +0 -0
  123. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/ChannelMember.py +0 -0
  124. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/Message.py +0 -0
  125. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/Workspace.py +0 -0
  126. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/models/__init__.py +0 -0
  127. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/realtime/storage.py +0 -0
  128. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/seeder/__init__.py +0 -0
  129. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/service/__init__.py +0 -0
  130. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session/__init__.py +0 -0
  131. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session_handlers/__init__.py +0 -0
  132. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session_handlers/memcached_handler.py +0 -0
  133. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  134. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session_handlers/redis_handler.py +0 -0
  135. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/session_handlers/valkey_handler.py +0 -0
  136. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/sso.py +0 -0
  137. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/swagger/__init__.py +0 -0
  138. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/components/crud.twig +0 -0
  139. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  140. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  141. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/docker/python/Dockerfile +0 -0
  142. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  143. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/302.twig +0 -0
  144. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/401.twig +0 -0
  145. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/403.twig +0 -0
  146. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/404.twig +0 -0
  147. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/500.twig +0 -0
  148. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/502.twig +0 -0
  149. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/503.twig +0 -0
  150. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/errors/base.twig +0 -0
  151. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/frontend/README.md +0 -0
  152. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/templates/readme.md +0 -0
  153. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/test/__init__.py +0 -0
  154. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/test_client/__init__.py +0 -0
  155. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  156. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  157. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  158. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  159. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  160. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  161. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  162. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  163. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  164. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  165. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  166. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  167. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/validator/__init__.py +0 -0
  168. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/websocket/__init__.py +0 -0
  169. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/websocket/backplane.py +0 -0
  170. {tina4_python-3.13.120 → tina4_python-3.13.121}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tina4-python
3
- Version: 3.13.120
3
+ Version: 3.13.121
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.120"
3
+ version = "3.13.121"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -51,7 +51,7 @@ def _resolve_version() -> str:
51
51
  #
52
52
  # test_version_constant.py now asserts this literal equals the pyproject
53
53
  # version, so the release bump cannot leave it behind again.
54
- return "3.13.120"
54
+ return "3.13.121"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -732,14 +732,35 @@ def _migrate(args):
732
732
 
733
733
 
734
734
  def _migrate_create(args):
735
- """Create a new migration file."""
735
+ """Create a new migration file.
736
+
737
+ Thin delegation to `_emit_resolution("migration", ...)` — the same code
738
+ path `tina4python generate migration <desc>` takes — so both CLI surfaces
739
+ emit the SAME ADR-0063 `generate_v1_1` envelope, the SAME `tina4:edit`
740
+ markers in the file, and the SAME next-steps block. The only intentional
741
+ difference is that `migrate:create` never co-emits a test file (a private
742
+ `_no_test` marker on the flags dict suppresses the test-emit path in
743
+ `_gen_migration` and drops `test_paths[]` in the resolution), preserving
744
+ the "just a migration, no test" contract of `migrate:create`. Neither
745
+ surface is deprecated; both stay first-class producers of the envelope.
746
+
747
+ The Python API `create_migration()` in `tina4_python.migration.runner` is
748
+ unchanged and still callable directly.
749
+ """
736
750
  if not args:
737
751
  print("Usage: tina4python migrate:create <description>")
738
752
  sys.exit(1)
739
- from tina4_python.migration import create_migration
740
- desc = " ".join(args)
741
- path = create_migration(desc, "migrations")
742
- print(f"Created: {path}")
753
+ # Split flags vs positional so `--json` / `--dry-run` flow through the
754
+ # envelope emitter (bare word args form the description).
755
+ flags, positional = _parse_flags(list(args))
756
+ if not positional:
757
+ print("Usage: tina4python migrate:create <description>")
758
+ sys.exit(1)
759
+ desc = " ".join(positional)
760
+ # Private marker read by _gen_migration + _resolve_generation to suppress
761
+ # the co-emitted test — see the `migrate:create` contract above.
762
+ flags["_no_test"] = True
763
+ _emit_resolution("migration", desc, flags)
743
764
 
744
765
 
745
766
  def _migrate_rollback(args):
@@ -1189,6 +1210,10 @@ def _resolve_generation(target: str, name: str, flags: dict,
1189
1210
  table = _to_snake(table)
1190
1211
  filename = f"{ts}_{name}.sql"
1191
1212
  is_create = name.startswith("create_")
1213
+ # Private `_no_test` marker (set by `migrate:create`, which is a
1214
+ # single-file operation): drop `test_paths[]` so the envelope
1215
+ # matches the on-disk effect. `_gen_migration` reads the same flag.
1216
+ want_test = is_create and not flags.get("_no_test")
1192
1217
  return {
1193
1218
  "class_name": None,
1194
1219
  "table_name": table,
@@ -1196,7 +1221,7 @@ def _resolve_generation(target: str, name: str, flags: dict,
1196
1221
  "migration_path": f"migrations/{filename}",
1197
1222
  "transformations": [],
1198
1223
  "routes": [],
1199
- "test_paths": [f"tests/test_{table}_migration.py"] if is_create else [],
1224
+ "test_paths": [f"tests/test_{table}_migration.py"] if want_test else [],
1200
1225
  }
1201
1226
 
1202
1227
  if target == "middleware":
@@ -1862,6 +1887,12 @@ def _gen_migration(name: str, flags: dict = None, *,
1862
1887
  # snapshotted timestamp; if it was supplied via the private `_timestamp`
1863
1888
  # flag, honour it so envelope and disk agree byte-for-byte.
1864
1889
  timestamp = flags.get("_timestamp") or now.strftime("%Y%m%d%H%M%S")
1890
+ # Private `_no_test` marker (set by `migrate:create`, which is a
1891
+ # single-file operation): suppress the co-emitted test regardless of the
1892
+ # keyword default. `_resolve_generation` reads the same marker so the
1893
+ # envelope's `test_paths[]` and the on-disk effect stay in agreement.
1894
+ if flags.get("_no_test"):
1895
+ emit_test = False
1865
1896
  target = Path("migrations")
1866
1897
  target.mkdir(parents=True, exist_ok=True)
1867
1898
 
@@ -506,28 +506,63 @@ def register_dev_tools(server):
506
506
  return status(db)
507
507
 
508
508
  def migration_create(description: str) -> dict:
509
- """Create a new migration file. Refuses to create a duplicate
510
- for a description whose slug already exists in migrations/ —
511
- the AI gets an error pointing at the existing file so it can
512
- edit that one rather than spawning a second migration for the
513
- same schema change."""
514
- from tina4_python.migration import create_migration
509
+ """Create a new migration file AND return the ADR-0063 `generate_v1_1`
510
+ envelope so an agent gets the SAME edit_hints[]/next[] guidance a CLI
511
+ user gets.
512
+
513
+ Delegates to the same resolution-aware code path the CLI's
514
+ `tina4python migrate:create` / `tina4python generate migration` uses.
515
+ The private `_no_test` flag matches the migrate:create contract (no
516
+ co-emitted test file — a migration is a single-file operation).
517
+
518
+ Refuses to create a duplicate for a description whose slug already
519
+ exists in migrations/ — returns `{"ok": False, "existing": [...]}`
520
+ so the AI edits the existing file instead of spawning a second
521
+ migration for the same schema change. On collision the envelope is
522
+ NOT emitted (the operation did not run).
523
+
524
+ Return shape (MCP wire contract):
525
+ {"ok": True, "created": "<relative-path>", "resolution": <envelope>}
526
+ — success. `created` is the primary UP file path (preserves
527
+ the historical MCP field). `resolution` is the full CLI
528
+ envelope with `command`, `target`, `input`, nested
529
+ `resolution` (edit_hints + next + file_path), actions_taken,
530
+ dry_run.
531
+ {"ok": False, "error": "...", "existing": [...]}
532
+ — refused collision. No file was written. Envelope omitted.
533
+ """
534
+ import contextlib
535
+ import io
515
536
  import re as _re
537
+ from datetime import datetime as _dt
538
+
539
+ # Slug the description before delegating so the on-disk filename is
540
+ # well-formed even when the caller passed a natural-language phrase
541
+ # ("add users"). Matches the historical `create_migration()` behaviour
542
+ # this tool used to delegate to.
516
543
  slug = _re.sub(r"[^a-z0-9]+", "_", (description or "").lower()).strip("_")
544
+ if not slug:
545
+ return {
546
+ "ok": False,
547
+ "error": "description is empty; provide a short snake_case name",
548
+ }
549
+
517
550
  mig_dir = project_root / "migrations"
518
- if mig_dir.is_dir() and slug:
519
- # Compare against existing migration slugs (strip the
520
- # leading timestamp and .sql/.down.sql extension). Reuse
521
- # if an exact slug OR a clearly-equivalent slug exists.
551
+ if mig_dir.is_dir():
552
+ # Compare against existing migration slugs (strip the leading
553
+ # timestamp and .sql/.down.sql extension). Reuse if an exact
554
+ # slug OR a clearly-equivalent slug exists.
522
555
  existing: list[str] = []
523
556
  for p in sorted(mig_dir.glob("*.sql")):
524
- name = p.name
557
+ fname = p.name
525
558
  # Files look like "20260417175842_create_contact_messages_table.sql".
526
559
  # Strip the timestamp prefix (14 digits + underscore) and the suffix.
527
- after = _re.sub(r"^\d{14}_", "", name)
560
+ after = _re.sub(r"^\d{14}_", "", fname)
528
561
  after = _re.sub(r"\.(down\.)?sql$", "", after)
529
562
  existing_slug = _re.sub(r"[^a-z0-9]+", "_", after.lower()).strip("_")
530
- if existing_slug == slug or existing_slug == slug + "_table" or slug == existing_slug + "_table":
563
+ if (existing_slug == slug
564
+ or existing_slug == slug + "_table"
565
+ or slug == existing_slug + "_table"):
531
566
  existing.append(str(p.relative_to(project_root)))
532
567
  if existing:
533
568
  return {
@@ -538,9 +573,52 @@ def register_dev_tools(server):
538
573
  ),
539
574
  "existing": existing,
540
575
  }
541
- filename = create_migration(description)
576
+
577
+ # Delegate to the same resolution-aware helpers the CLI uses so both
578
+ # surfaces produce byte-for-byte equivalent envelopes (modulo timestamp
579
+ # + file paths). Imported lazily to avoid pulling the CLI into every
580
+ # MCP tool-registration path.
581
+ from tina4_python.cli import (
582
+ _actions_from_resolution,
583
+ _gen_migration,
584
+ _hint_paths_from_resolution,
585
+ _input_fields,
586
+ _next_steps_for,
587
+ _resolve_generation,
588
+ _scan_edit_hints,
589
+ )
590
+
591
+ flags = {"_no_test": True}
592
+ # Lock the timestamp so `resolution.file_path` and the on-disk filename
593
+ # agree byte-for-byte (threaded to `_gen_migration` via `_timestamp`).
594
+ timestamp = _dt.now().strftime("%Y%m%d%H%M%S")
595
+ resolution = _resolve_generation("migration", slug, flags, timestamp=timestamp)
596
+
597
+ generator_flags = dict(flags)
598
+ generator_flags["_timestamp"] = timestamp
599
+ # Swallow the generator's own " ✓ Created …" prints — MCP returns
600
+ # JSON to the caller, never text on stdout.
601
+ with contextlib.redirect_stdout(io.StringIO()):
602
+ _gen_migration(slug, generator_flags)
603
+
604
+ actions_taken = _actions_from_resolution("migration", resolution)
605
+ hint_paths = _hint_paths_from_resolution("migration", resolution)
606
+ edit_hints = _scan_edit_hints(project_root, hint_paths)
607
+ next_steps = _next_steps_for("migration", slug, resolution, flags)
608
+ resolution["edit_hints"] = edit_hints
609
+ resolution["next"] = next_steps
610
+
611
+ envelope = {
612
+ "command": "generate",
613
+ "target": "migration",
614
+ "input": {"name": slug, "fields": _input_fields(flags)},
615
+ "resolution": resolution,
616
+ "actions_taken": actions_taken,
617
+ "dry_run": False,
618
+ }
619
+ filename = resolution["file_path"] # "migrations/{ts}_{slug}.sql"
542
620
  _record_plan_action("migration", filename, note=description)
543
- return {"created": filename}
621
+ return {"ok": True, "created": filename, "resolution": envelope}
544
622
 
545
623
  def _record_plan_action(action: str, path: str, note: str = "") -> None:
546
624
  """Forward a file event to the active plan's execution ledger.