stkit 17.0.0__tar.gz → 17.0.2__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 (162) hide show
  1. {stkit-17.0.0 → stkit-17.0.2}/CHANGELOG.md +21 -1
  2. {stkit-17.0.0 → stkit-17.0.2}/PKG-INFO +2 -3
  3. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/uv.lock +24 -54
  4. {stkit-17.0.0 → stkit-17.0.2}/pyproject.toml +3 -4
  5. {stkit-17.0.0 → stkit-17.0.2}/stk/__init__.py +1 -1
  6. {stkit-17.0.0 → stkit-17.0.2}/stk/checks.py +3 -2
  7. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/agent.py +4 -1
  8. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/database.py +4 -1
  9. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/main.py +13 -2
  10. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/smoke.py +3 -3
  11. {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/__init__.py +9 -2
  12. {stkit-17.0.0 → stkit-17.0.2}/stk/public/views.py +4 -4
  13. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +4 -1
  14. stkit-17.0.2/stk/scaffold/app/github/workflows/checks.yml +35 -0
  15. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/generator.py +1 -0
  16. stkit-17.0.2/tests/test_app_index.py +43 -0
  17. {stkit-17.0.0 → stkit-17.0.2}/tests/test_production_security.py +4 -4
  18. {stkit-17.0.0 → stkit-17.0.2}/tests/test_scaffolder.py +7 -1
  19. {stkit-17.0.0 → stkit-17.0.2}/.gitignore +0 -0
  20. {stkit-17.0.0 → stkit-17.0.2}/LICENSE +0 -0
  21. {stkit-17.0.0 → stkit-17.0.2}/README.md +0 -0
  22. {stkit-17.0.0 → stkit-17.0.2}/SECURITY.md +0 -0
  23. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/.env-sample +0 -0
  24. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/.gitignore +0 -0
  25. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/README.md +0 -0
  26. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/__init__.py +0 -0
  27. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/app.py +0 -0
  28. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/migrations/364232ae77d5_add_note.py +0 -0
  29. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/__init__.py +0 -0
  30. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/models.py +0 -0
  31. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/templates/note/index.html +0 -0
  32. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/views.py +0 -0
  33. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/settings.py +0 -0
  34. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/templates/layout.html +0 -0
  35. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/pyproject.toml +0 -0
  36. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/tests/__init__.py +0 -0
  37. {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/tests/test_app.py +0 -0
  38. {stkit-17.0.0 → stkit-17.0.2}/stk/__main__.py +0 -0
  39. {stkit-17.0.0 → stkit-17.0.2}/stk/agent_login.py +0 -0
  40. {stkit-17.0.0 → stkit-17.0.2}/stk/app.py +0 -0
  41. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/__init__.py +0 -0
  42. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/base.py +0 -0
  43. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/develop.py +0 -0
  44. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/doctor.py +0 -0
  45. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/reports.py +0 -0
  46. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/users.py +0 -0
  47. {stkit-17.0.0 → stkit-17.0.2}/stk/cli/watch.py +0 -0
  48. {stkit-17.0.0 → stkit-17.0.2}/stk/extensions.py +0 -0
  49. {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/env.py +0 -0
  50. {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/script.py.mako +0 -0
  51. {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/versions/stk_0001_initial_schema.py +0 -0
  52. {stkit-17.0.0 → stkit-17.0.2}/stk/portal/views.py +0 -0
  53. {stkit-17.0.0 → stkit-17.0.2}/stk/public/__init__.py +0 -0
  54. {stkit-17.0.0 → stkit-17.0.2}/stk/public/models.py +0 -0
  55. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/__init__.py +0 -0
  56. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/CLAUDE.md +0 -0
  57. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/Dockerfile +0 -0
  58. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/README.md +0 -0
  59. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/__init__.py +0 -0
  60. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/app.py +0 -0
  61. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/settings.py +0 -0
  62. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-api/SKILL.md +0 -0
  63. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +0 -0
  64. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +0 -0
  65. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +0 -0
  66. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/docker-compose.yml +0 -0
  67. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/dockerignore +0 -0
  68. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/env-sample +0 -0
  69. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/gitignore +0 -0
  70. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/nginx/nginx.conf +0 -0
  71. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/nginx/stk.conf +0 -0
  72. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/postgres/init-db.sh +0 -0
  73. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/pyproject.toml +0 -0
  74. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/tests/__init__.py +0 -0
  75. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/tests/test_app.py +0 -0
  76. {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/templates.py +0 -0
  77. {stkit-17.0.0 → stkit-17.0.2}/stk/security.py +0 -0
  78. {stkit-17.0.0 → stkit-17.0.2}/stk/settings.py +0 -0
  79. {stkit-17.0.0 → stkit-17.0.2}/stk/shell.py +0 -0
  80. {stkit-17.0.0 → stkit-17.0.2}/stk/static/VERSIONS.txt +0 -0
  81. {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/app.css +0 -0
  82. {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/layout.css +0 -0
  83. {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/vuetify.min.css +0 -0
  84. {stkit-17.0.0 → stkit-17.0.2}/stk/static/icons/fonts/tabler-icons.woff2 +0 -0
  85. {stkit-17.0.0 → stkit-17.0.2}/stk/static/icons/tabler-icons.min.css +0 -0
  86. {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/auth-bg.webp +0 -0
  87. {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/favicon.ico +0 -0
  88. {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/stk.svg +0 -0
  89. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/axios.min.js +0 -0
  90. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/NotificationDropdown.js +0 -0
  91. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/ThemeSwitcher.js +0 -0
  92. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/TransitionExpand.js +0 -0
  93. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavGroup.js +0 -0
  94. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavLink.js +0 -0
  95. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavSectionTitle.js +0 -0
  96. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/index.js +0 -0
  97. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/config.js +0 -0
  98. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/vue.min.js +0 -0
  99. {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/vuetify.min.js +0 -0
  100. {stkit-17.0.0 → stkit-17.0.2}/stk/static/robots.txt +0 -0
  101. {stkit-17.0.0 → stkit-17.0.2}/stk/tasks/__init__.py +0 -0
  102. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/401.html +0 -0
  103. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/404.html +0 -0
  104. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/500.html +0 -0
  105. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/_brand.html +0 -0
  106. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/auth_layout.html +0 -0
  107. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/activities.html +0 -0
  108. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/roles.html +0 -0
  109. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/users.html +0 -0
  110. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/dashboard.html +0 -0
  111. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/index.html +0 -0
  112. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/layout.html +0 -0
  113. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/login-layout.html +0 -0
  114. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/_macros.html +0 -0
  115. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/_messages.html +0 -0
  116. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/change_password.html +0 -0
  117. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/confirm_email.html +0 -0
  118. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/change_notice.html +0 -0
  119. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/change_notice.txt +0 -0
  120. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/confirmation_instructions.html +0 -0
  121. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/confirmation_instructions.txt +0 -0
  122. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/login_instructions.html +0 -0
  123. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/login_instructions.txt +0 -0
  124. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_instructions.html +0 -0
  125. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_instructions.txt +0 -0
  126. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_notice.html +0 -0
  127. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_notice.txt +0 -0
  128. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/welcome.html +0 -0
  129. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/welcome.txt +0 -0
  130. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/forgot_password.html +0 -0
  131. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/login_user.html +0 -0
  132. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/mf_recovery.html +0 -0
  133. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/mf_recovery_codes.html +0 -0
  134. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/register_user.html +0 -0
  135. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/reset_password.html +0 -0
  136. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/send_confirmation.html +0 -0
  137. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/send_login.html +0 -0
  138. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_select.html +0 -0
  139. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_setup.html +0 -0
  140. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_verify_code.html +0 -0
  141. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_register.html +0 -0
  142. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_signin.html +0 -0
  143. {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_verify.html +0 -0
  144. {stkit-17.0.0 → stkit-17.0.2}/stk/user/__init__.py +0 -0
  145. {stkit-17.0.0 → stkit-17.0.2}/stk/user/forms.py +0 -0
  146. {stkit-17.0.0 → stkit-17.0.2}/stk/user/models.py +0 -0
  147. {stkit-17.0.0 → stkit-17.0.2}/stk/user/views.py +0 -0
  148. {stkit-17.0.0 → stkit-17.0.2}/stk/utils/email.py +0 -0
  149. {stkit-17.0.0 → stkit-17.0.2}/stk/utils/ratelimit.py +0 -0
  150. {stkit-17.0.0 → stkit-17.0.2}/stk/websocket.py +0 -0
  151. {stkit-17.0.0 → stkit-17.0.2}/tests/test_agent_operability.py +0 -0
  152. {stkit-17.0.0 → stkit-17.0.2}/tests/test_branding.py +0 -0
  153. {stkit-17.0.0 → stkit-17.0.2}/tests/test_builder_ux.py +0 -0
  154. {stkit-17.0.0 → stkit-17.0.2}/tests/test_cli.py +0 -0
  155. {stkit-17.0.0 → stkit-17.0.2}/tests/test_composition.py +0 -0
  156. {stkit-17.0.0 → stkit-17.0.2}/tests/test_csrf_exempt.py +0 -0
  157. {stkit-17.0.0 → stkit-17.0.2}/tests/test_error_handler.py +0 -0
  158. {stkit-17.0.0 → stkit-17.0.2}/tests/test_migration_drift.py +0 -0
  159. {stkit-17.0.0 → stkit-17.0.2}/tests/test_security_compatibility.py +0 -0
  160. {stkit-17.0.0 → stkit-17.0.2}/tests/test_shell.py +0 -0
  161. {stkit-17.0.0 → stkit-17.0.2}/tests/test_smoke_pages.py +0 -0
  162. {stkit-17.0.0 → stkit-17.0.2}/tests/test_static_versioning.py +0 -0
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## v17.0.0 (unreleased)
3
+ ## v17.0.2 (2026-10-25)
4
+
5
+ No migration required.
6
+
7
+ - `stk verify` follows redirects from `/` and checks that the page it lands on answers 200, so an app whose `/` sends people to sign-in or to its own home passes. A redirect to a broken page still fails.
8
+ - Replacing stk's landing view is documented: `app.view_functions["public.index"] = home` in an extension's `init_app` (Architecture, Public).
9
+ - `STK_CLI_SECTIONS` groups an app's own commands in `stk --help`, after stk's sections, instead of listing them all under Other.
10
+
11
+ ## v17.0.1 (2026-10-25)
12
+
13
+ No migration required.
14
+
15
+ - Every dependency is locked at its latest release and tested: SQLAlchemy 2.1, Quart 0.23, webauthn 3, uvicorn 0.54, cryptography 50.0.2, and the rest. `pip-audit` finds no known vulnerabilities.
16
+ - Requires quart-security 2.0.3, which declares the passkey usage choices the way wtforms 3.3 expects (tuple choices are removed in wtforms 3.4).
17
+ - The pre-commit ruff hook matches the locked ruff (0.17.2). Ruff now also formats the Python samples in the docs.
18
+
19
+ ## v17.0.0 (2026-10-25)
4
20
 
5
21
  stk becomes an installed, versioned package. An app is a separate, thin package that composes it and never edits a framework file, so upgrading stk is a version bump plus whatever its release note says.
6
22
 
@@ -22,6 +38,10 @@ stk becomes an installed, versioned package. An app is a separate, thin package
22
38
  - `stk db check` and `stk smoke` use a throwaway database of the configured dialect, so Postgres apps are checked on Postgres (the role needs CREATEDB). Autogenerate never offers to drop PostGIS's `spatial_ref_sys`. `stk.migrations.portable_tables()` returns the tables SQLite can build.
23
39
  - Autogenerated revisions import what their column types need, and pass ruff without per-file exceptions.
24
40
  - `stk doctor` reports the app it loaded and understands two lineages.
41
+ - `stk init` gives each app a `CLAUDE.md` (with `AGENTS.md` linked) carrying stk's rules and pointing at the docs, the stk skills, and a CI workflow running tests, `stk verify`, and `stk smoke`. `stk verify` in stk's repository fails when the template's rules drift from stk's.
42
+ - `stk new` uses English plurals for pages and APIs (`category` gives `/categories/`). Pass the singular name.
43
+ - stk moves from httpx to `httpx2`, with `authlib>=1.9.1` and `quart-security>=2.0.2` doing the same, so authlib no longer warns about its deprecated httpx fallback on every command.
44
+ - The unused `sidebar` and `layout_classes` blocks are gone from stk's templates and the scaffold.
25
45
 
26
46
  ### Upgrade considerations
27
47
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stkit
3
- Version: 17.0.0
3
+ Version: 17.0.2
4
4
  Summary: An async Quart framework optimized for AI-assisted development workflows
5
5
  Author-email: Level09 <hello@level09.com>
6
6
  License: MIT
@@ -16,7 +16,6 @@ Requires-Dist: click>=8.3.1
16
16
  Requires-Dist: cryptography>=45.0.5
17
17
  Requires-Dist: email-validator>=2.2.0
18
18
  Requires-Dist: httpx2>=2.16.0
19
- Requires-Dist: httpx>=0.27.0
20
19
  Requires-Dist: jinja2>=3.1.6
21
20
  Requires-Dist: joserfc<2,>=1.7.1
22
21
  Requires-Dist: libpass>=1.9.0
@@ -25,7 +24,7 @@ Requires-Dist: pillow>=13.0.0
25
24
  Requires-Dist: psycopg2-binary>=2.9.10
26
25
  Requires-Dist: python-dotenv>=1.1.1
27
26
  Requires-Dist: qrcode>=8.2
28
- Requires-Dist: quart-security<3,>=2.0.1
27
+ Requires-Dist: quart-security<3,>=2.0.3
29
28
  Requires-Dist: quart-session>=3.0.0
30
29
  Requires-Dist: quart>=0.20.0
31
30
  Requires-Dist: rich>=14.0.0
@@ -2,9 +2,12 @@ version = 1
2
2
  revision = 3
3
3
  requires-python = ">=3.11"
4
4
  resolution-markers = [
5
- "python_full_version >= '3.14'",
6
- "python_full_version == '3.13.*'",
7
- "python_full_version == '3.12.*'",
5
+ "python_full_version >= '3.14' and sys_platform == 'emscripten'",
6
+ "python_full_version >= '3.14' and sys_platform != 'emscripten'",
7
+ "python_full_version == '3.13.*' and sys_platform == 'emscripten'",
8
+ "python_full_version == '3.13.*' and sys_platform != 'emscripten'",
9
+ "python_full_version == '3.12.*' and sys_platform == 'emscripten'",
10
+ "python_full_version == '3.12.*' and sys_platform != 'emscripten'",
8
11
  "python_full_version < '3.12'",
9
12
  ]
10
13
 
@@ -366,15 +369,6 @@ wheels = [
366
369
  { url = "https://files.pythonhosted.org/packages/d4/c0/e27a1e516a89af7194fc497f4b96d9601771ca41bb66fd5738113df80282/cbor2-6.1.5-cp315-cp315t-win_arm64.whl", hash = "sha256:fd34b35b0a2b366f5b4bd53489ccd10d7576b0d4dd68db38ef64b4e617ea8f76", size = 294495, upload-time = "2026-10-01T18:09:32.192Z" },
367
370
  ]
368
371
 
369
- [[package]]
370
- name = "certifi"
371
- version = "2026.10.23"
372
- source = { registry = "https://pypi.org/simple" }
373
- sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
374
- wheels = [
375
- { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
376
- ]
377
-
378
372
  [[package]]
379
373
  name = "cffi"
380
374
  version = "2.1.1"
@@ -734,19 +728,6 @@ wheels = [
734
728
  { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" },
735
729
  ]
736
730
 
737
- [[package]]
738
- name = "httpcore"
739
- version = "1.0.9"
740
- source = { registry = "https://pypi.org/simple" }
741
- dependencies = [
742
- { name = "certifi" },
743
- { name = "h11" },
744
- ]
745
- sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
746
- wheels = [
747
- { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
748
- ]
749
-
750
731
  [[package]]
751
732
  name = "httpcore2"
752
733
  version = "2.16.0"
@@ -760,21 +741,6 @@ wheels = [
760
741
  { url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" },
761
742
  ]
762
743
 
763
- [[package]]
764
- name = "httpx"
765
- version = "0.28.1"
766
- source = { registry = "https://pypi.org/simple" }
767
- dependencies = [
768
- { name = "anyio" },
769
- { name = "certifi" },
770
- { name = "httpcore" },
771
- { name = "idna" },
772
- ]
773
- sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
774
- wheels = [
775
- { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
776
- ]
777
-
778
744
  [[package]]
779
745
  name = "httpx2"
780
746
  version = "2.16.0"
@@ -1460,9 +1426,12 @@ name = "pyee"
1460
1426
  version = "14.0.0"
1461
1427
  source = { registry = "https://pypi.org/simple" }
1462
1428
  resolution-markers = [
1463
- "python_full_version >= '3.14'",
1464
- "python_full_version == '3.13.*'",
1465
- "python_full_version == '3.12.*'",
1429
+ "python_full_version >= '3.14' and sys_platform == 'emscripten'",
1430
+ "python_full_version >= '3.14' and sys_platform != 'emscripten'",
1431
+ "python_full_version == '3.13.*' and sys_platform == 'emscripten'",
1432
+ "python_full_version == '3.13.*' and sys_platform != 'emscripten'",
1433
+ "python_full_version == '3.12.*' and sys_platform == 'emscripten'",
1434
+ "python_full_version == '3.12.*' and sys_platform != 'emscripten'",
1466
1435
  ]
1467
1436
  dependencies = [
1468
1437
  { name = "typing-extensions" },
@@ -1601,7 +1570,8 @@ name = "quart"
1601
1570
  version = "0.22.0"
1602
1571
  source = { registry = "https://pypi.org/simple" }
1603
1572
  resolution-markers = [
1604
- "python_full_version == '3.12.*'",
1573
+ "python_full_version == '3.12.*' and sys_platform == 'emscripten'",
1574
+ "python_full_version == '3.12.*' and sys_platform != 'emscripten'",
1605
1575
  "python_full_version < '3.12'",
1606
1576
  ]
1607
1577
  dependencies = [
@@ -1625,8 +1595,10 @@ name = "quart"
1625
1595
  version = "0.23.1"
1626
1596
  source = { registry = "https://pypi.org/simple" }
1627
1597
  resolution-markers = [
1628
- "python_full_version >= '3.14'",
1629
- "python_full_version == '3.13.*'",
1598
+ "python_full_version >= '3.14' and sys_platform == 'emscripten'",
1599
+ "python_full_version >= '3.14' and sys_platform != 'emscripten'",
1600
+ "python_full_version == '3.13.*' and sys_platform == 'emscripten'",
1601
+ "python_full_version == '3.13.*' and sys_platform != 'emscripten'",
1630
1602
  ]
1631
1603
  dependencies = [
1632
1604
  { name = "aiofiles" },
@@ -1646,7 +1618,7 @@ wheels = [
1646
1618
 
1647
1619
  [[package]]
1648
1620
  name = "quart-security"
1649
- version = "2.0.1"
1621
+ version = "2.0.3"
1650
1622
  source = { registry = "https://pypi.org/simple" }
1651
1623
  dependencies = [
1652
1624
  { name = "aiosmtplib" },
@@ -1655,7 +1627,7 @@ dependencies = [
1655
1627
  { name = "cbor2" },
1656
1628
  { name = "cryptography" },
1657
1629
  { name = "email-validator" },
1658
- { name = "httpx" },
1630
+ { name = "httpx2" },
1659
1631
  { name = "libpass", extra = ["bcrypt"] },
1660
1632
  { name = "pyotp" },
1661
1633
  { name = "qrcode", extra = ["pil"] },
@@ -1665,9 +1637,9 @@ dependencies = [
1665
1637
  { name = "webauthn" },
1666
1638
  { name = "wtforms" },
1667
1639
  ]
1668
- sdist = { url = "https://files.pythonhosted.org/packages/a6/de/e2b0f19adf6dc2f781551264a0041f4683386c7dee5927d6f702d8a04433/quart_security-2.0.1.tar.gz", hash = "sha256:fa0fe168a819483229aa7d7292af84506c89fef0efd7d32b92e9314a96ea7d0b", size = 35667, upload-time = "2026-10-06T12:59:04.506Z" }
1640
+ sdist = { url = "https://files.pythonhosted.org/packages/2b/88/43c45890dd40db6fa5e57d09705b35c42f3a36981997ab3269a2c33403a0/quart_security-2.0.3.tar.gz", hash = "sha256:74be1c5177afca54cc90bafd10d7ae69a1f4923e1786ff106fb9990dd8f7eb5c", size = 35687, upload-time = "2026-10-25T10:39:02.875Z" }
1669
1641
  wheels = [
1670
- { url = "https://files.pythonhosted.org/packages/ab/2d/ac5e2c7bfe78b3103d5dc78db8139bf0a1d2867553b2f64cf87355eed1b1/quart_security-2.0.1-py3-none-any.whl", hash = "sha256:6a90561b3cbd05a1769985358a3e453caf8683b89b3fd12374c95314f93fb5ef", size = 41794, upload-time = "2026-10-06T12:59:03.231Z" },
1642
+ { url = "https://files.pythonhosted.org/packages/ef/b5/798c32a417ecba01152a4633cc4552b7ae0a4d70285f003ea7e5f1799e61/quart_security-2.0.3-py3-none-any.whl", hash = "sha256:08ac07b53242b70b519515299e89912cca2c21fabf12a6d25b87f6e4c8601268", size = 41815, upload-time = "2026-10-25T10:39:01.684Z" },
1671
1643
  ]
1672
1644
 
1673
1645
  [[package]]
@@ -1819,7 +1791,7 @@ asyncio = [
1819
1791
 
1820
1792
  [[package]]
1821
1793
  name = "stkit"
1822
- version = "17.0.0"
1794
+ version = "17.0.1"
1823
1795
  source = { editable = "../../" }
1824
1796
  dependencies = [
1825
1797
  { name = "aiosmtplib" },
@@ -1831,7 +1803,6 @@ dependencies = [
1831
1803
  { name = "click" },
1832
1804
  { name = "cryptography" },
1833
1805
  { name = "email-validator" },
1834
- { name = "httpx" },
1835
1806
  { name = "httpx2" },
1836
1807
  { name = "jinja2" },
1837
1808
  { name = "joserfc" },
@@ -1876,7 +1847,6 @@ requires-dist = [
1876
1847
  { name = "click", specifier = ">=8.3.1" },
1877
1848
  { name = "cryptography", specifier = ">=45.0.5" },
1878
1849
  { name = "email-validator", specifier = ">=2.2.0" },
1879
- { name = "httpx", specifier = ">=0.27.0" },
1880
1850
  { name = "httpx2", specifier = ">=2.16.0" },
1881
1851
  { name = "hypercorn", marker = "extra == 'asgi'", specifier = ">=0.17.0" },
1882
1852
  { name = "jinja2", specifier = ">=3.1.6" },
@@ -1891,7 +1861,7 @@ requires-dist = [
1891
1861
  { name = "python-dotenv", specifier = ">=1.1.1" },
1892
1862
  { name = "qrcode", specifier = ">=8.2" },
1893
1863
  { name = "quart", specifier = ">=0.20.0" },
1894
- { name = "quart-security", specifier = ">=2.0.1,<3" },
1864
+ { name = "quart-security", specifier = ">=2.0.3,<3" },
1895
1865
  { name = "quart-session", specifier = ">=3.0.0" },
1896
1866
  { name = "redis", marker = "extra == 'full'", specifier = ">=7.1.0" },
1897
1867
  { name = "rich", specifier = ">=14.0.0" },
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stkit"
3
- version = "17.0.0"
3
+ version = "17.0.2"
4
4
  description = "An async Quart framework optimized for AI-assisted development workflows"
5
5
  authors = [
6
6
  {name = "Level09", email = "hello@level09.com"}
@@ -19,8 +19,7 @@ dependencies = [
19
19
  "click>=8.3.1",
20
20
  "cryptography>=45.0.5",
21
21
  "email-validator>=2.2.0",
22
- "httpx>=0.27.0",
23
- "httpx2>=2.16.0", # authlib's HTTP client; plain httpx is its deprecated fallback
22
+ "httpx2>=2.16.0",
24
23
  "jinja2>=3.1.6",
25
24
  "joserfc>=1.7.1,<2",
26
25
  "libpass>=1.9.0",
@@ -30,7 +29,7 @@ dependencies = [
30
29
  "python-dotenv>=1.1.1",
31
30
  "qrcode>=8.2",
32
31
  "quart>=0.20.0",
33
- "quart-security>=2.0.1,<3",
32
+ "quart-security>=2.0.3,<3",
34
33
  "quart-session>=3.0.0",
35
34
  "rich>=14.0.0",
36
35
  "sqlalchemy[asyncio]>=2.0.41",
@@ -3,7 +3,7 @@ stk Framework
3
3
  A modern Python web framework optimized for AI-assisted development.
4
4
  """
5
5
 
6
- __version__ = "17.0.0"
6
+ __version__ = "17.0.2"
7
7
 
8
8
 
9
9
  def __getattr__(name):
@@ -286,10 +286,11 @@ def check_admin_reset_route(app):
286
286
  )
287
287
 
288
288
 
289
- @check("GET / returns 200")
289
+ @check("GET / answers 200, after any redirects")
290
290
  async def check_http_index(app):
291
+ # An app may send / to its sign-in or home page; what it lands on must work.
291
292
  client = app.test_client()
292
- response = await client.get("/")
293
+ response = await client.get("/", follow_redirects=True)
293
294
  assert response.status_code == 200, f"Expected 200, got {response.status_code}"
294
295
 
295
296
 
@@ -147,7 +147,10 @@ def _watch_and_verify(interval=0.5):
147
147
  @click.option("--json", "as_json", is_flag=True, help="Emit JSON.")
148
148
  def smoke(as_json):
149
149
  """Run a real-browser smoke check against a temporary development app."""
150
- report = run_smoke()
150
+ try:
151
+ report = run_smoke()
152
+ except RuntimeError as exc:
153
+ raise click.ClickException(str(exc)) from exc
151
154
  if as_json:
152
155
  click.echo(json.dumps(report, indent=2))
153
156
  else:
@@ -167,7 +167,10 @@ def db_revision(message, autogenerate, head):
167
167
  @db.command("check")
168
168
  def db_check():
169
169
  """Fail if models have drifted away from the migrations."""
170
- drift = build_migration_drift_report(cli_app())
170
+ try:
171
+ drift = build_migration_drift_report(cli_app())
172
+ except RuntimeError as exc:
173
+ raise click.ClickException(str(exc)) from exc
171
174
  if not drift:
172
175
  console.print("[green]No migration drift: models match migrations[/]")
173
176
  return
@@ -10,7 +10,7 @@ import inspect
10
10
  from importlib.metadata import version
11
11
 
12
12
  import click
13
- from quart.cli import QuartGroup
13
+ from quart.cli import NoAppException, QuartGroup, ScriptInfo
14
14
 
15
15
  import stk.cli as cli
16
16
  from stk.cli.base import load_app
@@ -44,7 +44,7 @@ class StkGroup(QuartGroup):
44
44
  if command is not None and not command.hidden:
45
45
  commands[name] = command
46
46
 
47
- for title, names in SECTIONS:
47
+ for title, names in [*SECTIONS, *_app_sections(ctx)]:
48
48
  rows = [
49
49
  (name, commands.pop(name).get_short_help_str(limit=60))
50
50
  for name in names
@@ -64,6 +64,17 @@ class StkGroup(QuartGroup):
64
64
  )
65
65
 
66
66
 
67
+ def _app_sections(ctx):
68
+ """The app's own help sections, `STK_CLI_SECTIONS`; none outside an app."""
69
+ try:
70
+ app = ctx.ensure_object(ScriptInfo).load_app()
71
+ except NoAppException:
72
+ return []
73
+ return [
74
+ (title, list(names)) for title, names in app.config.get("STK_CLI_SECTIONS", ())
75
+ ]
76
+
77
+
67
78
  def _create_app(**_kwargs):
68
79
  return load_app()
69
80
 
@@ -10,7 +10,7 @@ import time
10
10
  from pathlib import Path
11
11
 
12
12
  import click
13
- import httpx
13
+ import httpx2
14
14
 
15
15
  from stk.agent_login import create_agent_login_token
16
16
  from stk.cli.base import cli_app, run_async
@@ -255,10 +255,10 @@ def _wait_for_smoke_server(base_url, process):
255
255
  f"{stdout.strip()}\n{stderr.strip()}"
256
256
  )
257
257
  try:
258
- response = httpx.get(f"{base_url}/login", timeout=1)
258
+ response = httpx2.get(f"{base_url}/login", timeout=1)
259
259
  if response.status_code < 500:
260
260
  return
261
- except httpx.HTTPError:
261
+ except httpx2.HTTPError:
262
262
  pass
263
263
  time.sleep(0.2)
264
264
  raise click.ClickException("smoke server did not start within 20 seconds")
@@ -107,6 +107,7 @@ def throwaway_database_url(database_url: str, *, sync: bool = False):
107
107
  SQLite cannot render. Postgres needs a role with CREATEDB.
108
108
  """
109
109
  from sqlalchemy import create_engine, text
110
+ from sqlalchemy.exc import ProgrammingError
110
111
 
111
112
  url = make_url(database_url)
112
113
  if url.get_backend_name() != "postgresql":
@@ -119,8 +120,14 @@ def throwaway_database_url(database_url: str, *, sync: bool = False):
119
120
  _render(url.set(database="postgres"), sync=True), isolation_level="AUTOCOMMIT"
120
121
  )
121
122
  try:
122
- with admin.connect() as connection:
123
- connection.execute(text(f'create database "{name}"'))
123
+ try:
124
+ with admin.connect() as connection:
125
+ connection.execute(text(f'create database "{name}"'))
126
+ except ProgrammingError as exc:
127
+ raise RuntimeError(
128
+ f"role {url.username!r} cannot create a throwaway database: grant it"
129
+ " CREATEDB, or run this check with a role that has it"
130
+ ) from exc
124
131
  try:
125
132
  yield _render(url.set(database=name), sync=sync)
126
133
  finally:
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  import secrets
3
3
 
4
- import httpx
4
+ import httpx2
5
5
  from authlib.integrations.httpx_client import AsyncOAuth2Client
6
6
  from authlib.oidc.core import CodeIDToken
7
7
  from joserfc import jwt
@@ -180,7 +180,7 @@ def get_github_client():
180
180
 
181
181
 
182
182
  async def google_discovery():
183
- async with httpx.AsyncClient(timeout=10) as client:
183
+ async with httpx2.AsyncClient(timeout=10) as client:
184
184
  response = await client.get(GOOGLE_DISCOVERY_URL)
185
185
  response.raise_for_status()
186
186
  return response.json()
@@ -265,7 +265,7 @@ async def google_callback():
265
265
  redirect_uri=transaction["redirect_uri"],
266
266
  code_verifier=transaction["verifier"],
267
267
  )
268
- async with httpx.AsyncClient(timeout=10) as client:
268
+ async with httpx2.AsyncClient(timeout=10) as client:
269
269
  response = await client.get(discovery["jwks_uri"])
270
270
  response.raise_for_status()
271
271
  decoded = jwt.decode(
@@ -315,7 +315,7 @@ async def github_callback():
315
315
  redirect_uri=transaction["redirect_uri"],
316
316
  code_verifier=transaction["verifier"],
317
317
  )
318
- async with httpx.AsyncClient(timeout=10) as client:
318
+ async with httpx2.AsyncClient(timeout=10) as client:
319
319
  headers = {
320
320
  "Authorization": f"Bearer {token['access_token']}",
321
321
  "Accept": "application/json",
@@ -79,6 +79,7 @@ op.create_index("ix_products_name", "products", ["name"])
79
79
  from alembic import op
80
80
  from sqlalchemy import text
81
81
 
82
+
82
83
  def upgrade():
83
84
  op.add_column("users", sa.Column("display_name", sa.String(255)))
84
85
  op.execute(text("UPDATE users SET display_name = name WHERE display_name IS NULL"))
@@ -92,5 +93,7 @@ with op.batch_alter_table("users") as batch_op:
92
93
 
93
94
  **Add a composite unique constraint:**
94
95
  ```python
95
- op.create_unique_constraint("uq_oauth_provider_user", "oauth", ["provider", "provider_user_id"])
96
+ op.create_unique_constraint(
97
+ "uq_oauth_provider_user", "oauth", ["provider", "provider_user_id"]
98
+ )
96
99
  ```
@@ -0,0 +1,35 @@
1
+ name: Checks
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ env:
8
+ SECRET_KEY: ci-only-secret-key-not-for-production
9
+ SECURITY_PASSWORD_SALT: ci-only-password-salt-not-for-production
10
+
11
+ jobs:
12
+ checks:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: astral-sh/setup-uv@v9.0.0
17
+ - name: Install dependencies
18
+ run: uv sync
19
+ - name: Install Chromium
20
+ run: uv run playwright install --with-deps chromium
21
+ - name: Migrate
22
+ run: uv run stk db upgrade
23
+ - name: Unit tests
24
+ run: uv run python -m unittest discover -s tests -v
25
+ - name: Verify (lint, sanity, migrations)
26
+ run: uv run stk verify --json
27
+ - name: Browser smoke (behavioral)
28
+ run: uv run stk smoke --json
29
+ - name: Upload smoke artifacts on failure
30
+ if: failure()
31
+ uses: actions/upload-artifact@v7
32
+ with:
33
+ name: smoke-artifacts
34
+ path: .stk/smoke/
35
+ if-no-files-found: ignore
@@ -27,6 +27,7 @@ DOTFILES = {
27
27
  "dockerignore": ".dockerignore",
28
28
  "env-sample": ".env-sample",
29
29
  "claude": ".claude",
30
+ "github": ".github",
30
31
  }
31
32
 
32
33
  # Names that conflict with existing blueprints, SQLAlchemy reserved words, or
@@ -0,0 +1,43 @@
1
+ """An app may replace stk's landing page, and verify judges where / lands."""
2
+
3
+ import unittest
4
+ from types import SimpleNamespace
5
+
6
+ from quart import redirect
7
+
8
+ from stk.app import Stk
9
+ from stk.checks import check_http_index
10
+ from stk.cli.main import _app_sections
11
+ from stk.settings import TestingConfig
12
+
13
+
14
+ class AppIndexTest(unittest.IsolatedAsyncioTestCase):
15
+ async def test_index_can_be_replaced_and_a_redirect_passes_the_check(self):
16
+ app = Stk(__name__, config=TestingConfig)
17
+
18
+ async def home():
19
+ return redirect("/login")
20
+
21
+ app.view_functions["public.index"] = home
22
+ response = await app.test_client().get("/")
23
+ self.assertEqual(response.status_code, 302)
24
+ self.assertTrue(await check_http_index(app))
25
+
26
+ async def test_a_redirect_to_a_broken_page_fails_the_check(self):
27
+ app = Stk(__name__, config=TestingConfig)
28
+
29
+ async def home():
30
+ return redirect("/nowhere")
31
+
32
+ app.view_functions["public.index"] = home
33
+ self.assertFalse(await check_http_index(app))
34
+
35
+
36
+ class AppHelpSectionsTest(unittest.TestCase):
37
+ def test_app_sections_come_from_config(self):
38
+ app = Stk(__name__, config=TestingConfig)
39
+ app.config["STK_CLI_SECTIONS"] = [("Runs", ("worker", "enrich"))]
40
+ ctx = SimpleNamespace(
41
+ ensure_object=lambda _: SimpleNamespace(load_app=lambda: app)
42
+ )
43
+ self.assertEqual(_app_sections(ctx), [("Runs", ["worker", "enrich"])])
@@ -385,7 +385,7 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
385
385
  "token_endpoint": "https://accounts.google.com/token",
386
386
  "jwks_uri": "https://accounts.google.com/keys",
387
387
  }
388
- import httpx
388
+ import httpx2
389
389
 
390
390
  for invalid in (None, "nonce", "iss", "aud", "exp", "signature"):
391
391
  with self.subTest(invalid=invalid):
@@ -429,8 +429,8 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
429
429
  "id_token": encoded,
430
430
  "access_token": "fixture",
431
431
  }
432
- response = httpx.Response(
433
- 200, request=httpx.Request("GET", discovery["jwks_uri"]), json={}
432
+ response = httpx2.Response(
433
+ 200, request=httpx2.Request("GET", discovery["jwks_uri"]), json={}
434
434
  )
435
435
  response.json = lambda: {"keys": [RSAKey.import_key(public).as_dict()]}
436
436
  http = AsyncMock()
@@ -442,7 +442,7 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
442
442
  new=AsyncMock(return_value=discovery),
443
443
  ),
444
444
  patch("stk.public.views.get_google_client", return_value=oauth),
445
- patch("stk.public.views.httpx.AsyncClient", return_value=http),
445
+ patch("stk.public.views.httpx2.AsyncClient", return_value=http),
446
446
  ):
447
447
  await client.get(
448
448
  "/login/google/callback?code=fixture&state=" + args["state"][0]
@@ -281,7 +281,13 @@ class InitAndNewTests(unittest.TestCase):
281
281
  self.assertIn('app = "acme.app:app"', pyproject)
282
282
  self.assertIn(f'"stkit>={stk.__version__},<', pyproject)
283
283
  self.assertNotIn("[tool.uv.sources]", pyproject)
284
- for name in (".gitignore", ".env-sample", "acme/settings.py", "Dockerfile"):
284
+ for name in (
285
+ ".gitignore",
286
+ ".env-sample",
287
+ "acme/settings.py",
288
+ "Dockerfile",
289
+ ".github/workflows/checks.yml",
290
+ ):
285
291
  self.assertTrue((self.app / name).is_file(), name)
286
292
  env = (self.app / ".env").read_text()
287
293
  self.assertNotIn("\nSECRET_KEY=\n", env)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes