solokit 0.1.1__py3-none-any.whl

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 (323) hide show
  1. solokit/__init__.py +10 -0
  2. solokit/__version__.py +3 -0
  3. solokit/cli.py +374 -0
  4. solokit/core/__init__.py +1 -0
  5. solokit/core/cache.py +102 -0
  6. solokit/core/command_runner.py +278 -0
  7. solokit/core/config.py +453 -0
  8. solokit/core/config_validator.py +204 -0
  9. solokit/core/constants.py +291 -0
  10. solokit/core/error_formatter.py +279 -0
  11. solokit/core/error_handlers.py +346 -0
  12. solokit/core/exceptions.py +1567 -0
  13. solokit/core/file_ops.py +309 -0
  14. solokit/core/logging_config.py +166 -0
  15. solokit/core/output.py +99 -0
  16. solokit/core/performance.py +57 -0
  17. solokit/core/protocols.py +141 -0
  18. solokit/core/types.py +312 -0
  19. solokit/deployment/__init__.py +1 -0
  20. solokit/deployment/executor.py +411 -0
  21. solokit/git/__init__.py +1 -0
  22. solokit/git/integration.py +619 -0
  23. solokit/init/__init__.py +41 -0
  24. solokit/init/claude_commands_installer.py +87 -0
  25. solokit/init/dependency_installer.py +313 -0
  26. solokit/init/docs_structure.py +90 -0
  27. solokit/init/env_generator.py +160 -0
  28. solokit/init/environment_validator.py +334 -0
  29. solokit/init/git_hooks_installer.py +71 -0
  30. solokit/init/git_setup.py +188 -0
  31. solokit/init/gitignore_updater.py +195 -0
  32. solokit/init/initial_commit.py +145 -0
  33. solokit/init/initial_scans.py +109 -0
  34. solokit/init/orchestrator.py +246 -0
  35. solokit/init/readme_generator.py +207 -0
  36. solokit/init/session_structure.py +239 -0
  37. solokit/init/template_installer.py +424 -0
  38. solokit/learning/__init__.py +1 -0
  39. solokit/learning/archiver.py +115 -0
  40. solokit/learning/categorizer.py +126 -0
  41. solokit/learning/curator.py +428 -0
  42. solokit/learning/extractor.py +352 -0
  43. solokit/learning/reporter.py +351 -0
  44. solokit/learning/repository.py +254 -0
  45. solokit/learning/similarity.py +342 -0
  46. solokit/learning/validator.py +144 -0
  47. solokit/project/__init__.py +1 -0
  48. solokit/project/init.py +1162 -0
  49. solokit/project/stack.py +436 -0
  50. solokit/project/sync_plugin.py +438 -0
  51. solokit/project/tree.py +375 -0
  52. solokit/quality/__init__.py +1 -0
  53. solokit/quality/api_validator.py +424 -0
  54. solokit/quality/checkers/__init__.py +25 -0
  55. solokit/quality/checkers/base.py +114 -0
  56. solokit/quality/checkers/context7.py +221 -0
  57. solokit/quality/checkers/custom.py +162 -0
  58. solokit/quality/checkers/deployment.py +323 -0
  59. solokit/quality/checkers/documentation.py +179 -0
  60. solokit/quality/checkers/formatting.py +161 -0
  61. solokit/quality/checkers/integration.py +394 -0
  62. solokit/quality/checkers/linting.py +159 -0
  63. solokit/quality/checkers/security.py +261 -0
  64. solokit/quality/checkers/spec_completeness.py +127 -0
  65. solokit/quality/checkers/tests.py +184 -0
  66. solokit/quality/env_validator.py +306 -0
  67. solokit/quality/gates.py +655 -0
  68. solokit/quality/reporters/__init__.py +10 -0
  69. solokit/quality/reporters/base.py +25 -0
  70. solokit/quality/reporters/console.py +98 -0
  71. solokit/quality/reporters/json_reporter.py +34 -0
  72. solokit/quality/results.py +98 -0
  73. solokit/session/__init__.py +1 -0
  74. solokit/session/briefing/__init__.py +245 -0
  75. solokit/session/briefing/documentation_loader.py +53 -0
  76. solokit/session/briefing/formatter.py +476 -0
  77. solokit/session/briefing/git_context.py +282 -0
  78. solokit/session/briefing/learning_loader.py +212 -0
  79. solokit/session/briefing/milestone_builder.py +78 -0
  80. solokit/session/briefing/orchestrator.py +137 -0
  81. solokit/session/briefing/stack_detector.py +51 -0
  82. solokit/session/briefing/tree_generator.py +52 -0
  83. solokit/session/briefing/work_item_loader.py +209 -0
  84. solokit/session/briefing.py +353 -0
  85. solokit/session/complete.py +1188 -0
  86. solokit/session/status.py +246 -0
  87. solokit/session/validate.py +452 -0
  88. solokit/templates/.claude/commands/end.md +109 -0
  89. solokit/templates/.claude/commands/init.md +159 -0
  90. solokit/templates/.claude/commands/learn-curate.md +88 -0
  91. solokit/templates/.claude/commands/learn-search.md +62 -0
  92. solokit/templates/.claude/commands/learn-show.md +69 -0
  93. solokit/templates/.claude/commands/learn.md +136 -0
  94. solokit/templates/.claude/commands/start.md +114 -0
  95. solokit/templates/.claude/commands/status.md +22 -0
  96. solokit/templates/.claude/commands/validate.md +27 -0
  97. solokit/templates/.claude/commands/work-delete.md +119 -0
  98. solokit/templates/.claude/commands/work-graph.md +139 -0
  99. solokit/templates/.claude/commands/work-list.md +26 -0
  100. solokit/templates/.claude/commands/work-new.md +114 -0
  101. solokit/templates/.claude/commands/work-next.md +25 -0
  102. solokit/templates/.claude/commands/work-show.md +24 -0
  103. solokit/templates/.claude/commands/work-update.md +141 -0
  104. solokit/templates/CHANGELOG.md +17 -0
  105. solokit/templates/WORK_ITEM_TYPES.md +141 -0
  106. solokit/templates/__init__.py +1 -0
  107. solokit/templates/bug_spec.md +217 -0
  108. solokit/templates/config.schema.json +150 -0
  109. solokit/templates/dashboard_refine/base/.gitignore +36 -0
  110. solokit/templates/dashboard_refine/base/app/(dashboard)/layout.tsx +22 -0
  111. solokit/templates/dashboard_refine/base/app/(dashboard)/page.tsx +68 -0
  112. solokit/templates/dashboard_refine/base/app/(dashboard)/users/page.tsx +77 -0
  113. solokit/templates/dashboard_refine/base/app/globals.css +60 -0
  114. solokit/templates/dashboard_refine/base/app/layout.tsx +23 -0
  115. solokit/templates/dashboard_refine/base/app/page.tsx +9 -0
  116. solokit/templates/dashboard_refine/base/components/client-refine-wrapper.tsx +21 -0
  117. solokit/templates/dashboard_refine/base/components/layout/header.tsx +44 -0
  118. solokit/templates/dashboard_refine/base/components/layout/sidebar.tsx +82 -0
  119. solokit/templates/dashboard_refine/base/components/ui/button.tsx +53 -0
  120. solokit/templates/dashboard_refine/base/components/ui/card.tsx +78 -0
  121. solokit/templates/dashboard_refine/base/components/ui/table.tsx +116 -0
  122. solokit/templates/dashboard_refine/base/components.json +16 -0
  123. solokit/templates/dashboard_refine/base/lib/refine.tsx +65 -0
  124. solokit/templates/dashboard_refine/base/lib/utils.ts +13 -0
  125. solokit/templates/dashboard_refine/base/next.config.ts +10 -0
  126. solokit/templates/dashboard_refine/base/package.json.template +40 -0
  127. solokit/templates/dashboard_refine/base/postcss.config.mjs +8 -0
  128. solokit/templates/dashboard_refine/base/providers/refine-provider.tsx +26 -0
  129. solokit/templates/dashboard_refine/base/tailwind.config.ts +57 -0
  130. solokit/templates/dashboard_refine/base/tsconfig.json +27 -0
  131. solokit/templates/dashboard_refine/docker/Dockerfile +57 -0
  132. solokit/templates/dashboard_refine/docker/docker-compose.prod.yml +31 -0
  133. solokit/templates/dashboard_refine/docker/docker-compose.yml +21 -0
  134. solokit/templates/dashboard_refine/tier-1-essential/.eslintrc.json +7 -0
  135. solokit/templates/dashboard_refine/tier-1-essential/jest.config.ts +17 -0
  136. solokit/templates/dashboard_refine/tier-1-essential/jest.setup.ts +1 -0
  137. solokit/templates/dashboard_refine/tier-1-essential/package.json.tier1.template +57 -0
  138. solokit/templates/dashboard_refine/tier-1-essential/tests/setup.ts +26 -0
  139. solokit/templates/dashboard_refine/tier-1-essential/tests/unit/example.test.tsx +73 -0
  140. solokit/templates/dashboard_refine/tier-2-standard/package.json.tier2.template +62 -0
  141. solokit/templates/dashboard_refine/tier-3-comprehensive/eslint.config.mjs +22 -0
  142. solokit/templates/dashboard_refine/tier-3-comprehensive/package.json.tier3.template +79 -0
  143. solokit/templates/dashboard_refine/tier-3-comprehensive/playwright.config.ts +66 -0
  144. solokit/templates/dashboard_refine/tier-3-comprehensive/stryker.conf.json +38 -0
  145. solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/dashboard.spec.ts +88 -0
  146. solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/user-management.spec.ts +102 -0
  147. solokit/templates/dashboard_refine/tier-3-comprehensive/tests/integration/dashboard.test.tsx +90 -0
  148. solokit/templates/dashboard_refine/tier-3-comprehensive/type-coverage.json +16 -0
  149. solokit/templates/dashboard_refine/tier-4-production/instrumentation.ts +9 -0
  150. solokit/templates/dashboard_refine/tier-4-production/k6/dashboard-load-test.js +70 -0
  151. solokit/templates/dashboard_refine/tier-4-production/next.config.ts +46 -0
  152. solokit/templates/dashboard_refine/tier-4-production/package.json.tier4.template +89 -0
  153. solokit/templates/dashboard_refine/tier-4-production/sentry.client.config.ts +26 -0
  154. solokit/templates/dashboard_refine/tier-4-production/sentry.edge.config.ts +11 -0
  155. solokit/templates/dashboard_refine/tier-4-production/sentry.server.config.ts +11 -0
  156. solokit/templates/deployment_spec.md +500 -0
  157. solokit/templates/feature_spec.md +248 -0
  158. solokit/templates/fullstack_nextjs/base/.gitignore +36 -0
  159. solokit/templates/fullstack_nextjs/base/app/api/example/route.ts +65 -0
  160. solokit/templates/fullstack_nextjs/base/app/globals.css +27 -0
  161. solokit/templates/fullstack_nextjs/base/app/layout.tsx +20 -0
  162. solokit/templates/fullstack_nextjs/base/app/page.tsx +32 -0
  163. solokit/templates/fullstack_nextjs/base/components/example-component.tsx +20 -0
  164. solokit/templates/fullstack_nextjs/base/lib/prisma.ts +17 -0
  165. solokit/templates/fullstack_nextjs/base/lib/utils.ts +13 -0
  166. solokit/templates/fullstack_nextjs/base/lib/validations.ts +20 -0
  167. solokit/templates/fullstack_nextjs/base/next.config.ts +7 -0
  168. solokit/templates/fullstack_nextjs/base/package.json.template +32 -0
  169. solokit/templates/fullstack_nextjs/base/postcss.config.mjs +8 -0
  170. solokit/templates/fullstack_nextjs/base/prisma/schema.prisma +21 -0
  171. solokit/templates/fullstack_nextjs/base/tailwind.config.ts +19 -0
  172. solokit/templates/fullstack_nextjs/base/tsconfig.json +27 -0
  173. solokit/templates/fullstack_nextjs/docker/Dockerfile +60 -0
  174. solokit/templates/fullstack_nextjs/docker/docker-compose.prod.yml +57 -0
  175. solokit/templates/fullstack_nextjs/docker/docker-compose.yml +47 -0
  176. solokit/templates/fullstack_nextjs/tier-1-essential/.eslintrc.json +7 -0
  177. solokit/templates/fullstack_nextjs/tier-1-essential/jest.config.ts +17 -0
  178. solokit/templates/fullstack_nextjs/tier-1-essential/jest.setup.ts +1 -0
  179. solokit/templates/fullstack_nextjs/tier-1-essential/package.json.tier1.template +48 -0
  180. solokit/templates/fullstack_nextjs/tier-1-essential/tests/api/example.test.ts +88 -0
  181. solokit/templates/fullstack_nextjs/tier-1-essential/tests/setup.ts +22 -0
  182. solokit/templates/fullstack_nextjs/tier-1-essential/tests/unit/example.test.tsx +22 -0
  183. solokit/templates/fullstack_nextjs/tier-2-standard/package.json.tier2.template +52 -0
  184. solokit/templates/fullstack_nextjs/tier-3-comprehensive/eslint.config.mjs +39 -0
  185. solokit/templates/fullstack_nextjs/tier-3-comprehensive/package.json.tier3.template +68 -0
  186. solokit/templates/fullstack_nextjs/tier-3-comprehensive/playwright.config.ts +66 -0
  187. solokit/templates/fullstack_nextjs/tier-3-comprehensive/stryker.conf.json +33 -0
  188. solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/e2e/flow.spec.ts +59 -0
  189. solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/integration/api.test.ts +165 -0
  190. solokit/templates/fullstack_nextjs/tier-3-comprehensive/type-coverage.json +12 -0
  191. solokit/templates/fullstack_nextjs/tier-4-production/instrumentation.ts +9 -0
  192. solokit/templates/fullstack_nextjs/tier-4-production/k6/load-test.js +45 -0
  193. solokit/templates/fullstack_nextjs/tier-4-production/next.config.ts +46 -0
  194. solokit/templates/fullstack_nextjs/tier-4-production/package.json.tier4.template +77 -0
  195. solokit/templates/fullstack_nextjs/tier-4-production/sentry.client.config.ts +26 -0
  196. solokit/templates/fullstack_nextjs/tier-4-production/sentry.edge.config.ts +11 -0
  197. solokit/templates/fullstack_nextjs/tier-4-production/sentry.server.config.ts +11 -0
  198. solokit/templates/git-hooks/prepare-commit-msg +24 -0
  199. solokit/templates/integration_test_spec.md +363 -0
  200. solokit/templates/learnings.json +15 -0
  201. solokit/templates/ml_ai_fastapi/base/.gitignore +104 -0
  202. solokit/templates/ml_ai_fastapi/base/alembic/env.py +96 -0
  203. solokit/templates/ml_ai_fastapi/base/alembic.ini +114 -0
  204. solokit/templates/ml_ai_fastapi/base/pyproject.toml.template +91 -0
  205. solokit/templates/ml_ai_fastapi/base/requirements.txt.template +28 -0
  206. solokit/templates/ml_ai_fastapi/base/src/__init__.py +5 -0
  207. solokit/templates/ml_ai_fastapi/base/src/api/__init__.py +3 -0
  208. solokit/templates/ml_ai_fastapi/base/src/api/dependencies.py +20 -0
  209. solokit/templates/ml_ai_fastapi/base/src/api/routes/__init__.py +3 -0
  210. solokit/templates/ml_ai_fastapi/base/src/api/routes/example.py +134 -0
  211. solokit/templates/ml_ai_fastapi/base/src/api/routes/health.py +66 -0
  212. solokit/templates/ml_ai_fastapi/base/src/core/__init__.py +3 -0
  213. solokit/templates/ml_ai_fastapi/base/src/core/config.py +64 -0
  214. solokit/templates/ml_ai_fastapi/base/src/core/database.py +50 -0
  215. solokit/templates/ml_ai_fastapi/base/src/main.py +64 -0
  216. solokit/templates/ml_ai_fastapi/base/src/models/__init__.py +7 -0
  217. solokit/templates/ml_ai_fastapi/base/src/models/example.py +61 -0
  218. solokit/templates/ml_ai_fastapi/base/src/services/__init__.py +3 -0
  219. solokit/templates/ml_ai_fastapi/base/src/services/example.py +115 -0
  220. solokit/templates/ml_ai_fastapi/docker/Dockerfile +59 -0
  221. solokit/templates/ml_ai_fastapi/docker/docker-compose.prod.yml +112 -0
  222. solokit/templates/ml_ai_fastapi/docker/docker-compose.yml +77 -0
  223. solokit/templates/ml_ai_fastapi/tier-1-essential/pyproject.toml.tier1.template +112 -0
  224. solokit/templates/ml_ai_fastapi/tier-1-essential/pyrightconfig.json +41 -0
  225. solokit/templates/ml_ai_fastapi/tier-1-essential/pytest.ini +69 -0
  226. solokit/templates/ml_ai_fastapi/tier-1-essential/requirements-dev.txt +17 -0
  227. solokit/templates/ml_ai_fastapi/tier-1-essential/ruff.toml +81 -0
  228. solokit/templates/ml_ai_fastapi/tier-1-essential/tests/__init__.py +3 -0
  229. solokit/templates/ml_ai_fastapi/tier-1-essential/tests/conftest.py +72 -0
  230. solokit/templates/ml_ai_fastapi/tier-1-essential/tests/test_main.py +49 -0
  231. solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/__init__.py +3 -0
  232. solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/test_example.py +113 -0
  233. solokit/templates/ml_ai_fastapi/tier-2-standard/pyproject.toml.tier2.template +130 -0
  234. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/locustfile.py +99 -0
  235. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/mutmut_config.py +53 -0
  236. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/pyproject.toml.tier3.template +150 -0
  237. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/__init__.py +3 -0
  238. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/conftest.py +74 -0
  239. solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/test_api.py +131 -0
  240. solokit/templates/ml_ai_fastapi/tier-4-production/pyproject.toml.tier4.template +162 -0
  241. solokit/templates/ml_ai_fastapi/tier-4-production/requirements-prod.txt +25 -0
  242. solokit/templates/ml_ai_fastapi/tier-4-production/src/api/routes/metrics.py +19 -0
  243. solokit/templates/ml_ai_fastapi/tier-4-production/src/core/logging.py +74 -0
  244. solokit/templates/ml_ai_fastapi/tier-4-production/src/core/monitoring.py +68 -0
  245. solokit/templates/ml_ai_fastapi/tier-4-production/src/core/sentry.py +66 -0
  246. solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/__init__.py +3 -0
  247. solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/logging.py +79 -0
  248. solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/tracing.py +60 -0
  249. solokit/templates/refactor_spec.md +287 -0
  250. solokit/templates/saas_t3/base/.gitignore +36 -0
  251. solokit/templates/saas_t3/base/app/api/trpc/[trpc]/route.ts +33 -0
  252. solokit/templates/saas_t3/base/app/globals.css +27 -0
  253. solokit/templates/saas_t3/base/app/layout.tsx +23 -0
  254. solokit/templates/saas_t3/base/app/page.tsx +31 -0
  255. solokit/templates/saas_t3/base/lib/api.tsx +77 -0
  256. solokit/templates/saas_t3/base/lib/utils.ts +13 -0
  257. solokit/templates/saas_t3/base/next.config.ts +7 -0
  258. solokit/templates/saas_t3/base/package.json.template +38 -0
  259. solokit/templates/saas_t3/base/postcss.config.mjs +8 -0
  260. solokit/templates/saas_t3/base/prisma/schema.prisma +20 -0
  261. solokit/templates/saas_t3/base/server/api/root.ts +19 -0
  262. solokit/templates/saas_t3/base/server/api/routers/example.ts +28 -0
  263. solokit/templates/saas_t3/base/server/api/trpc.ts +52 -0
  264. solokit/templates/saas_t3/base/server/db.ts +17 -0
  265. solokit/templates/saas_t3/base/tailwind.config.ts +19 -0
  266. solokit/templates/saas_t3/base/tsconfig.json +27 -0
  267. solokit/templates/saas_t3/docker/Dockerfile +60 -0
  268. solokit/templates/saas_t3/docker/docker-compose.prod.yml +59 -0
  269. solokit/templates/saas_t3/docker/docker-compose.yml +49 -0
  270. solokit/templates/saas_t3/tier-1-essential/.eslintrc.json +7 -0
  271. solokit/templates/saas_t3/tier-1-essential/jest.config.ts +17 -0
  272. solokit/templates/saas_t3/tier-1-essential/jest.setup.ts +1 -0
  273. solokit/templates/saas_t3/tier-1-essential/package.json.tier1.template +54 -0
  274. solokit/templates/saas_t3/tier-1-essential/tests/setup.ts +22 -0
  275. solokit/templates/saas_t3/tier-1-essential/tests/unit/example.test.tsx +24 -0
  276. solokit/templates/saas_t3/tier-2-standard/package.json.tier2.template +58 -0
  277. solokit/templates/saas_t3/tier-3-comprehensive/eslint.config.mjs +39 -0
  278. solokit/templates/saas_t3/tier-3-comprehensive/package.json.tier3.template +74 -0
  279. solokit/templates/saas_t3/tier-3-comprehensive/playwright.config.ts +66 -0
  280. solokit/templates/saas_t3/tier-3-comprehensive/stryker.conf.json +34 -0
  281. solokit/templates/saas_t3/tier-3-comprehensive/tests/e2e/home.spec.ts +41 -0
  282. solokit/templates/saas_t3/tier-3-comprehensive/tests/integration/api.test.ts +44 -0
  283. solokit/templates/saas_t3/tier-3-comprehensive/type-coverage.json +12 -0
  284. solokit/templates/saas_t3/tier-4-production/instrumentation.ts +9 -0
  285. solokit/templates/saas_t3/tier-4-production/k6/load-test.js +51 -0
  286. solokit/templates/saas_t3/tier-4-production/next.config.ts +46 -0
  287. solokit/templates/saas_t3/tier-4-production/package.json.tier4.template +83 -0
  288. solokit/templates/saas_t3/tier-4-production/sentry.client.config.ts +26 -0
  289. solokit/templates/saas_t3/tier-4-production/sentry.edge.config.ts +11 -0
  290. solokit/templates/saas_t3/tier-4-production/sentry.server.config.ts +11 -0
  291. solokit/templates/saas_t3/tier-4-production/vercel.json +37 -0
  292. solokit/templates/security_spec.md +287 -0
  293. solokit/templates/stack-versions.yaml +617 -0
  294. solokit/templates/status_update.json +6 -0
  295. solokit/templates/template-registry.json +257 -0
  296. solokit/templates/work_items.json +11 -0
  297. solokit/testing/__init__.py +1 -0
  298. solokit/testing/integration_runner.py +550 -0
  299. solokit/testing/performance.py +637 -0
  300. solokit/visualization/__init__.py +1 -0
  301. solokit/visualization/dependency_graph.py +788 -0
  302. solokit/work_items/__init__.py +1 -0
  303. solokit/work_items/creator.py +217 -0
  304. solokit/work_items/delete.py +264 -0
  305. solokit/work_items/get_dependencies.py +185 -0
  306. solokit/work_items/get_dependents.py +113 -0
  307. solokit/work_items/get_metadata.py +121 -0
  308. solokit/work_items/get_next_recommendations.py +133 -0
  309. solokit/work_items/manager.py +235 -0
  310. solokit/work_items/milestones.py +137 -0
  311. solokit/work_items/query.py +376 -0
  312. solokit/work_items/repository.py +267 -0
  313. solokit/work_items/scheduler.py +184 -0
  314. solokit/work_items/spec_parser.py +838 -0
  315. solokit/work_items/spec_validator.py +493 -0
  316. solokit/work_items/updater.py +157 -0
  317. solokit/work_items/validator.py +205 -0
  318. solokit-0.1.1.dist-info/METADATA +640 -0
  319. solokit-0.1.1.dist-info/RECORD +323 -0
  320. solokit-0.1.1.dist-info/WHEEL +5 -0
  321. solokit-0.1.1.dist-info/entry_points.txt +2 -0
  322. solokit-0.1.1.dist-info/licenses/LICENSE +21 -0
  323. solokit-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,655 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quality gate validation for session completion.
4
+
5
+ Refactored to use pluggable checker architecture for better maintainability.
6
+
7
+ Provides comprehensive validation including:
8
+ - Test execution with coverage
9
+ - Linting and formatting
10
+ - Security scanning
11
+ - Documentation validation
12
+ - Custom validation rules
13
+
14
+ Updated to use modular checker architecture while maintaining backward compatibility.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from solokit.core.command_runner import CommandRunner
24
+ from solokit.core.config import get_config_manager
25
+ from solokit.core.constants import QUALITY_CHECK_VERY_LONG_TIMEOUT
26
+ from solokit.core.error_handlers import log_errors
27
+ from solokit.core.exceptions import (
28
+ FileOperationError,
29
+ )
30
+ from solokit.core.logging_config import get_logger
31
+ from solokit.quality.checkers import (
32
+ CustomValidationChecker,
33
+ DocumentationChecker,
34
+ ExecutionChecker,
35
+ FormattingChecker,
36
+ LintingChecker,
37
+ SecurityChecker,
38
+ SpecCompletenessChecker,
39
+ )
40
+ from solokit.quality.reporters import ConsoleReporter
41
+ from solokit.quality.results import ResultAggregator
42
+
43
+ logger = get_logger(__name__)
44
+
45
+ # Import spec validator for spec completeness quality gate
46
+ try:
47
+ from solokit.core.exceptions import SpecValidationError
48
+ from solokit.work_items.spec_validator import validate_spec_file
49
+ except ImportError:
50
+ validate_spec_file = None # type: ignore[assignment]
51
+ SpecValidationError = None # type: ignore[assignment, misc]
52
+
53
+
54
+ class QualityGates:
55
+ """Quality gate validation using modular checker architecture.
56
+
57
+ This class maintains backward compatibility with the original QualityGates interface
58
+ while delegating to specialized checker classes internally.
59
+ """
60
+
61
+ def __init__(self, config_path: Path | None = None):
62
+ """Initialize quality gates with configuration."""
63
+ if config_path is None:
64
+ config_path = Path(".session/config.json")
65
+ self._config_path = config_path
66
+
67
+ # Use ConfigManager for centralized config management
68
+ config_manager = get_config_manager()
69
+ config_manager.load_config(config_path)
70
+ self.config = config_manager.quality_gates # QualityGatesConfig dataclass
71
+
72
+ # Initialize command runner (for backward compatibility)
73
+ self.runner = CommandRunner(default_timeout=QUALITY_CHECK_VERY_LONG_TIMEOUT)
74
+
75
+ # Project root
76
+ self.project_root = Path.cwd()
77
+
78
+ # Result aggregator and reporter
79
+ self.aggregator = ResultAggregator()
80
+ self.reporter = ConsoleReporter()
81
+
82
+ @log_errors()
83
+ def _load_full_config(self) -> dict[str, Any]:
84
+ """Load full configuration file for optional sections (context7, custom_validations, etc.)."""
85
+ if self._config_path.exists():
86
+ try:
87
+ with open(self._config_path) as f:
88
+ return json.load(f) # type: ignore[no-any-return]
89
+ except OSError as e:
90
+ raise FileOperationError(
91
+ operation="read",
92
+ file_path=str(self._config_path),
93
+ details="Failed to read configuration file",
94
+ cause=e,
95
+ ) from e
96
+ except json.JSONDecodeError as e:
97
+ raise FileOperationError(
98
+ operation="parse",
99
+ file_path=str(self._config_path),
100
+ details="Invalid JSON in configuration file",
101
+ cause=e,
102
+ ) from e
103
+ return {}
104
+
105
+ def _detect_language(self) -> str:
106
+ """Detect primary project language."""
107
+ # Check for common files
108
+ if Path("pyproject.toml").exists() or Path("setup.py").exists():
109
+ return "python"
110
+ elif Path("package.json").exists():
111
+ # Check if TypeScript
112
+ if Path("tsconfig.json").exists():
113
+ return "typescript"
114
+ return "javascript"
115
+
116
+ return "python" # default
117
+
118
+ def run_tests(self, language: str | None = None) -> tuple[bool, dict[str, Any]]:
119
+ """
120
+ Run test suite with coverage.
121
+
122
+ Args:
123
+ language: Programming language (optional, will be detected if not provided)
124
+
125
+ Returns:
126
+ (passed: bool, results: dict)
127
+ """
128
+ logger.info("Running test quality gate")
129
+
130
+ # Convert config dataclass to dict for checker
131
+ test_config = {
132
+ "enabled": self.config.test_execution.enabled,
133
+ "commands": self.config.test_execution.commands,
134
+ "coverage_threshold": self.config.test_execution.coverage_threshold,
135
+ }
136
+
137
+ # Create and run test checker (pass runner for test compatibility)
138
+ checker = ExecutionChecker(
139
+ test_config, self.project_root, language=language, runner=self.runner
140
+ )
141
+ result = checker.run()
142
+
143
+ # Convert CheckResult to legacy format
144
+ # Extract reason from errors if present (for coverage failures)
145
+ reason = result.info.get("reason")
146
+ if not reason and result.errors:
147
+ # Check if any error is about coverage
148
+ for error in result.errors:
149
+ if isinstance(error, dict):
150
+ msg = error.get("message", "")
151
+ if "coverage" in msg.lower() and "threshold" in msg.lower():
152
+ reason = msg
153
+ break
154
+
155
+ return result.passed, {
156
+ "status": result.status,
157
+ "coverage": result.info.get("coverage"),
158
+ "returncode": result.info.get("returncode", 0),
159
+ "output": result.info.get("output", ""),
160
+ "errors": "\n".join(str(e) for e in result.errors) if result.errors else "",
161
+ "reason": reason,
162
+ }
163
+
164
+ def run_security_scan(self, language: str | None = None) -> tuple[bool, dict[str, Any]]:
165
+ """
166
+ Run security vulnerability scanning.
167
+
168
+ Args:
169
+ language: Programming language (optional, will be detected if not provided)
170
+
171
+ Returns:
172
+ (passed: bool, results: dict)
173
+ """
174
+ logger.info("Running security scan quality gate")
175
+
176
+ # Convert config dataclass to dict for checker
177
+ security_config = {
178
+ "enabled": self.config.security.enabled,
179
+ "fail_on": self.config.security.fail_on,
180
+ }
181
+
182
+ # Create and run security checker (pass runner for test compatibility)
183
+ checker = SecurityChecker(
184
+ security_config, self.project_root, language=language, runner=self.runner
185
+ )
186
+ result = checker.run()
187
+
188
+ # Convert CheckResult to legacy format
189
+ return result.passed, {
190
+ "status": result.status,
191
+ "vulnerabilities": result.errors,
192
+ "by_severity": result.info.get("by_severity", {}),
193
+ }
194
+
195
+ def run_linting(
196
+ self, language: str | None = None, auto_fix: bool | None = None
197
+ ) -> tuple[bool, dict[str, Any]]:
198
+ """Run linting with optional auto-fix.
199
+
200
+ Args:
201
+ language: Programming language (optional, will be detected if not provided)
202
+ auto_fix: Whether to automatically fix issues (overrides config)
203
+
204
+ Returns:
205
+ (passed: bool, results: dict)
206
+ """
207
+ logger.info("Running linting quality gate")
208
+
209
+ # Convert config dataclass to dict for checker
210
+ linting_config = {
211
+ "enabled": self.config.linting.enabled,
212
+ "commands": self.config.linting.commands,
213
+ "auto_fix": self.config.linting.auto_fix,
214
+ "required": self.config.linting.required,
215
+ }
216
+
217
+ # Create and run linting checker (pass runner for test compatibility)
218
+ checker = LintingChecker(
219
+ linting_config,
220
+ self.project_root,
221
+ language=language,
222
+ auto_fix=auto_fix,
223
+ runner=self.runner,
224
+ )
225
+ result = checker.run()
226
+
227
+ # Convert CheckResult to legacy format
228
+ return result.passed, {
229
+ "status": result.status,
230
+ "issues_found": result.info.get("issues_found", 0),
231
+ "output": result.info.get("output", ""),
232
+ "fixed": result.info.get("auto_fixed", False),
233
+ "reason": result.info.get("reason"),
234
+ }
235
+
236
+ def run_formatting(
237
+ self, language: str | None = None, auto_fix: bool | None = None
238
+ ) -> tuple[bool, dict[str, Any]]:
239
+ """Run code formatting.
240
+
241
+ Args:
242
+ language: Programming language (optional, will be detected if not provided)
243
+ auto_fix: Whether to automatically format (overrides config)
244
+
245
+ Returns:
246
+ (passed: bool, results: dict)
247
+ """
248
+ logger.info("Running formatting quality gate")
249
+
250
+ # Convert config dataclass to dict for checker
251
+ formatting_config = {
252
+ "enabled": self.config.formatting.enabled,
253
+ "commands": self.config.formatting.commands,
254
+ "auto_fix": self.config.formatting.auto_fix,
255
+ "required": self.config.formatting.required,
256
+ }
257
+
258
+ # Create and run formatting checker (pass runner for test compatibility)
259
+ checker = FormattingChecker(
260
+ formatting_config,
261
+ self.project_root,
262
+ language=language,
263
+ auto_fix=auto_fix,
264
+ runner=self.runner,
265
+ )
266
+ result = checker.run()
267
+
268
+ # Convert CheckResult to legacy format
269
+ return result.passed, {
270
+ "status": result.status,
271
+ "formatted": result.info.get("formatted", False),
272
+ "output": result.info.get("output", ""),
273
+ }
274
+
275
+ def validate_documentation(self, work_item: dict | None = None) -> tuple[bool, dict[str, Any]]:
276
+ """Validate documentation requirements.
277
+
278
+ Args:
279
+ work_item: Work item dictionary (optional)
280
+
281
+ Returns:
282
+ (passed: bool, results: dict)
283
+ """
284
+ logger.info("Running documentation validation quality gate")
285
+
286
+ # Convert config dataclass to dict for checker
287
+ doc_config = {
288
+ "enabled": self.config.documentation.enabled,
289
+ "check_changelog": self.config.documentation.check_changelog,
290
+ "check_docstrings": self.config.documentation.check_docstrings,
291
+ "check_readme": self.config.documentation.check_readme,
292
+ }
293
+
294
+ # Create and run documentation checker (pass runner for test compatibility)
295
+ checker = DocumentationChecker(
296
+ doc_config, self.project_root, work_item=work_item, runner=self.runner
297
+ )
298
+ result = checker.run()
299
+
300
+ # Convert CheckResult to legacy format
301
+ return result.passed, {
302
+ "status": result.status,
303
+ "checks": result.info.get("checks", []),
304
+ "passed": result.passed,
305
+ }
306
+
307
+ def validate_spec_completeness(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
308
+ """
309
+ Validate that the work item specification file is complete.
310
+
311
+ Part of Phase 5.7.5: Spec File Validation System
312
+
313
+ Args:
314
+ work_item: Work item dictionary with 'id' and 'type' fields
315
+
316
+ Returns:
317
+ Tuple of (passed, results)
318
+ """
319
+ logger.info(f"Running spec completeness validation for work item: {work_item.get('id')}")
320
+
321
+ # Convert config dataclass to dict for checker
322
+ spec_config = {
323
+ "enabled": self.config.spec_completeness.enabled,
324
+ }
325
+
326
+ # Create and run spec completeness checker
327
+ checker = SpecCompletenessChecker(spec_config, self.project_root, work_item=work_item)
328
+ result = checker.run()
329
+
330
+ # Convert CheckResult to legacy format
331
+ if result.status == "skipped":
332
+ return True, {"status": "skipped", "reason": result.info.get("reason", "")}
333
+
334
+ if result.passed:
335
+ return True, {
336
+ "status": "passed",
337
+ "message": result.info.get("message", ""),
338
+ }
339
+ else:
340
+ return False, {
341
+ "status": "failed",
342
+ "errors": [
343
+ e.get("message", str(e)) if isinstance(e, dict) else str(e)
344
+ for e in result.errors
345
+ ],
346
+ "message": result.info.get("message", ""),
347
+ "suggestion": result.info.get("suggestion", ""),
348
+ }
349
+
350
+ def run_custom_validations(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
351
+ """Run custom validation rules for work item.
352
+
353
+ Args:
354
+ work_item: Work item dictionary
355
+
356
+ Returns:
357
+ (passed: bool, results: dict)
358
+ """
359
+ logger.info("Running custom validations quality gate")
360
+
361
+ # Get custom rules from full config
362
+ full_config = self._load_full_config()
363
+ custom_config = full_config.get("custom_validations", {})
364
+
365
+ # Create and run custom validation checker (pass runner for test compatibility)
366
+ checker = CustomValidationChecker(
367
+ custom_config, self.project_root, work_item=work_item, runner=self.runner
368
+ )
369
+ result = checker.run()
370
+
371
+ # Convert CheckResult to legacy format
372
+ return result.passed, {
373
+ "status": result.status,
374
+ "validations": result.info.get("validations", []),
375
+ "passed": result.passed,
376
+ }
377
+
378
+ def check_required_gates(self) -> tuple[bool, list[str]]:
379
+ """
380
+ Check if all required gates are configured.
381
+
382
+ Returns:
383
+ (all_required_met: bool, missing_gates: List[str])
384
+ """
385
+ missing = []
386
+
387
+ # Check each quality gate
388
+ gates = {
389
+ "test_execution": self.config.test_execution,
390
+ "linting": self.config.linting,
391
+ "formatting": self.config.formatting,
392
+ "security": self.config.security,
393
+ "documentation": self.config.documentation,
394
+ "spec_completeness": self.config.spec_completeness,
395
+ }
396
+
397
+ for gate_name, gate_config in gates.items():
398
+ if gate_config.required and not gate_config.enabled: # type: ignore[attr-defined]
399
+ missing.append(gate_name)
400
+
401
+ return len(missing) == 0, missing
402
+
403
+ # ========================================================================
404
+ # Integration Test Gates
405
+ # ========================================================================
406
+
407
+ def run_integration_tests(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
408
+ """Run integration tests for integration test work items."""
409
+ from solokit.quality.checkers.integration import IntegrationChecker
410
+
411
+ checker = IntegrationChecker(
412
+ work_item=work_item,
413
+ config=self.config.integration.__dict__,
414
+ runner=self.runner,
415
+ config_path=self._config_path,
416
+ )
417
+ result = checker.run()
418
+ return result.passed, result.details
419
+
420
+ def validate_integration_environment(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
421
+ """Validate integration test environment requirements."""
422
+ from solokit.quality.checkers.integration import IntegrationChecker
423
+
424
+ checker = IntegrationChecker(
425
+ work_item=work_item,
426
+ config=self.config.integration.__dict__,
427
+ runner=self.runner,
428
+ config_path=self._config_path,
429
+ )
430
+ result = checker.validate_environment()
431
+ return result.passed, result.details
432
+
433
+ def validate_integration_documentation(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
434
+ """Validate integration test documentation requirements."""
435
+ from solokit.quality.checkers.integration import IntegrationChecker
436
+
437
+ checker = IntegrationChecker(
438
+ work_item=work_item,
439
+ config=self.config.integration.__dict__,
440
+ runner=self.runner,
441
+ config_path=self._config_path,
442
+ )
443
+ result = checker.validate_documentation()
444
+ return result.passed, result.details
445
+
446
+ def verify_context7_libraries(self) -> tuple[bool, dict[str, Any]]:
447
+ """Verify important libraries via Context7 MCP."""
448
+ from solokit.quality.checkers.context7 import Context7Checker
449
+
450
+ checker = Context7Checker(
451
+ config=self.config.context7.__dict__,
452
+ project_root=self.project_root,
453
+ runner=self.runner,
454
+ )
455
+ result = checker.run()
456
+ return result.passed, result.details
457
+
458
+ def run_deployment_gates(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
459
+ """Run deployment-specific quality gates."""
460
+ from solokit.quality.checkers.deployment import DeploymentChecker
461
+
462
+ checker = DeploymentChecker(
463
+ work_item=work_item,
464
+ config=self.config.deployment.__dict__,
465
+ project_root=self.project_root,
466
+ runner=self.runner,
467
+ )
468
+ result = checker.run()
469
+ return result.passed, result.details
470
+
471
+ # ========================================================================
472
+ # Report Generation
473
+ # ========================================================================
474
+
475
+ def generate_report(self, all_results: dict) -> str:
476
+ """Generate comprehensive quality gate report.
477
+
478
+ Args:
479
+ all_results: Dictionary of results from all quality gates
480
+
481
+ Returns:
482
+ Formatted report string
483
+ """
484
+ report = []
485
+ report.append("=" * 60)
486
+ report.append("QUALITY GATE RESULTS")
487
+ report.append("=" * 60)
488
+
489
+ # Test results
490
+ if "tests" in all_results:
491
+ test_results = all_results["tests"]
492
+ status = "✓ PASSED" if test_results.get("status") == "passed" else "✗ FAILED"
493
+ report.append(f"\nTests: {status}")
494
+ if test_results.get("coverage"):
495
+ report.append(f" Coverage: {test_results['coverage']}%")
496
+
497
+ # Security results
498
+ if "security" in all_results:
499
+ sec_results = all_results["security"]
500
+ status = "✓ PASSED" if sec_results.get("status") == "passed" else "✗ FAILED"
501
+ report.append(f"\nSecurity: {status}")
502
+ if sec_results.get("by_severity"):
503
+ for severity, count in sec_results["by_severity"].items():
504
+ report.append(f" {severity}: {count}")
505
+
506
+ # Linting results
507
+ if "linting" in all_results:
508
+ lint_results = all_results["linting"]
509
+ if lint_results.get("status") == "passed":
510
+ status = "✓ PASSED"
511
+ elif lint_results.get("status") == "skipped":
512
+ status = "⊘ SKIPPED"
513
+ else:
514
+ status = "✗ FAILED"
515
+ report.append(f"\nLinting: {status}")
516
+ if lint_results.get("fixed"):
517
+ report.append(" Auto-fix applied")
518
+
519
+ # Formatting results
520
+ if "formatting" in all_results:
521
+ fmt_results = all_results["formatting"]
522
+ if fmt_results.get("status") == "passed":
523
+ status = "✓ PASSED"
524
+ elif fmt_results.get("status") == "skipped":
525
+ status = "⊘ SKIPPED"
526
+ else:
527
+ status = "✗ FAILED"
528
+ report.append(f"\nFormatting: {status}")
529
+ if fmt_results.get("formatted"):
530
+ report.append(" Auto-format applied")
531
+
532
+ # Documentation results
533
+ if "documentation" in all_results:
534
+ doc_results = all_results["documentation"]
535
+ status = "✓ PASSED" if doc_results.get("status") == "passed" else "✗ FAILED"
536
+ report.append(f"\nDocumentation: {status}")
537
+ for check in doc_results.get("checks", []):
538
+ check_status = "✓" if check["passed"] else "✗"
539
+ report.append(f" {check_status} {check['name']}")
540
+
541
+ # Context7 results
542
+ if "context7" in all_results:
543
+ ctx_results = all_results["context7"]
544
+ if ctx_results.get("status") != "skipped":
545
+ status = "✓ PASSED" if ctx_results.get("status") == "passed" else "✗ FAILED"
546
+ report.append(f"\nContext7: {status}")
547
+ report.append(f" Verified: {ctx_results.get('verified', 0)}")
548
+ report.append(f" Failed: {ctx_results.get('failed', 0)}")
549
+
550
+ # Custom validations results
551
+ if "custom" in all_results:
552
+ custom_results = all_results["custom"]
553
+ if custom_results.get("status") == "passed":
554
+ status = "✓ PASSED"
555
+ elif custom_results.get("status") == "skipped":
556
+ status = "⊘ SKIPPED"
557
+ else:
558
+ status = "✗ FAILED"
559
+ report.append(f"\nCustom Validations: {status}")
560
+ for validation in custom_results.get("validations", []):
561
+ val_status = "✓" if validation["passed"] else "✗"
562
+ required_mark = " (required)" if validation["required"] else ""
563
+ report.append(f" {val_status} {validation['name']}{required_mark}")
564
+
565
+ report.append("\n" + "=" * 60)
566
+
567
+ return "\n".join(report)
568
+
569
+ def get_remediation_guidance(self, failed_gates: list[str]) -> str:
570
+ """Get remediation guidance for failed gates.
571
+
572
+ Args:
573
+ failed_gates: List of failed gate names
574
+
575
+ Returns:
576
+ Formatted remediation guidance string
577
+ """
578
+ guidance = []
579
+ guidance.append("\nREMEDIATION GUIDANCE:")
580
+ guidance.append("-" * 60)
581
+
582
+ for gate in failed_gates:
583
+ if gate == "tests":
584
+ guidance.append("\n• Tests Failed:")
585
+ guidance.append(" - Review test output above")
586
+ guidance.append(" - Fix failing tests")
587
+ guidance.append(" - Improve coverage if below threshold")
588
+
589
+ elif gate == "security":
590
+ guidance.append("\n• Security Issues Found:")
591
+ guidance.append(" - Review vulnerability details above")
592
+ guidance.append(" - Update vulnerable dependencies")
593
+ guidance.append(" - Fix high/critical issues immediately")
594
+
595
+ elif gate == "linting":
596
+ guidance.append("\n• Linting Issues:")
597
+ guidance.append(" - Run with --auto-fix to fix automatically")
598
+ guidance.append(" - Review remaining issues manually")
599
+
600
+ elif gate == "formatting":
601
+ guidance.append("\n• Formatting Issues:")
602
+ guidance.append(" - Run with --auto-format to fix automatically")
603
+ guidance.append(" - Ensure consistent code style")
604
+
605
+ elif gate == "documentation":
606
+ guidance.append("\n• Documentation Issues:")
607
+ guidance.append(" Update CHANGELOG.md with your changes:")
608
+ guidance.append("")
609
+ guidance.append(" ## [Unreleased]")
610
+ guidance.append(" ### Added")
611
+ guidance.append(" - Feature: User authentication with JWT")
612
+ guidance.append(" - Tests: Comprehensive auth endpoint tests")
613
+ guidance.append("")
614
+ guidance.append(" Then commit:")
615
+ guidance.append(" git add CHANGELOG.md")
616
+ guidance.append(" git commit -m 'docs: Update CHANGELOG'")
617
+ guidance.append("")
618
+ guidance.append(" - Add docstrings to new functions")
619
+ guidance.append(" - Update README if needed")
620
+
621
+ elif gate == "context7":
622
+ guidance.append("\n• Context7 Library Verification Failed:")
623
+ guidance.append(" - Review failed library versions")
624
+ guidance.append(" - Update outdated libraries")
625
+ guidance.append(" - Check for security updates")
626
+
627
+ elif gate == "custom":
628
+ guidance.append("\n• Custom Validation Failed:")
629
+ guidance.append(" - Review failed validation rules")
630
+ guidance.append(" - Address required validations")
631
+ guidance.append(" - Check work item requirements")
632
+
633
+ return "\n".join(guidance)
634
+
635
+
636
+ def main() -> None:
637
+ """CLI entry point."""
638
+ gates = QualityGates()
639
+
640
+ logger.info("Running quality gates...")
641
+
642
+ # Run tests
643
+ passed, results = gates.run_tests()
644
+ logger.info(f"\nTest Execution: {'✓ PASSED' if passed else '✗ FAILED'}")
645
+ if results.get("coverage"):
646
+ logger.info(f" Coverage: {results['coverage']}%")
647
+
648
+ # Check required gates
649
+ all_met, missing = gates.check_required_gates()
650
+ if not all_met:
651
+ logger.error(f"\n✗ Missing required gates: {', '.join(missing)}")
652
+
653
+
654
+ if __name__ == "__main__":
655
+ main()
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python3
2
+ """Quality gate reporters."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from solokit.quality.reporters.base import Reporter
7
+ from solokit.quality.reporters.console import ConsoleReporter
8
+ from solokit.quality.reporters.json_reporter import JSONReporter
9
+
10
+ __all__ = ["Reporter", "ConsoleReporter", "JSONReporter"]
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Base reporter interface for quality gate results.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+
12
+ class Reporter(ABC):
13
+ """Abstract base class for result reporters."""
14
+
15
+ @abstractmethod
16
+ def generate(self, aggregated_results: dict[str, Any]) -> str:
17
+ """Generate a report from aggregated results.
18
+
19
+ Args:
20
+ aggregated_results: Aggregated results from ResultAggregator
21
+
22
+ Returns:
23
+ Formatted report string
24
+ """
25
+ pass