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,261 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Security vulnerability scanner.
4
+
5
+ Runs security scans using Bandit (Python) and Safety (Python dependencies),
6
+ or npm audit (JavaScript/TypeScript).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import tempfile
14
+ import time
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from solokit.core.command_runner import CommandRunner
19
+ from solokit.core.constants import QUALITY_CHECK_LONG_TIMEOUT
20
+ from solokit.core.logging_config import get_logger
21
+ from solokit.quality.checkers.base import CheckResult, QualityChecker
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class SecurityChecker(QualityChecker):
27
+ """Security vulnerability scanning for Python and JavaScript/TypeScript."""
28
+
29
+ def __init__(
30
+ self,
31
+ config: dict[str, Any],
32
+ project_root: Path | None = None,
33
+ language: str | None = None,
34
+ runner: CommandRunner | None = None,
35
+ ):
36
+ """Initialize security checker.
37
+
38
+ Args:
39
+ config: Security configuration
40
+ project_root: Project root directory
41
+ language: Programming language (python, javascript, typescript)
42
+ runner: Optional CommandRunner instance (for testing)
43
+ """
44
+ super().__init__(config, project_root)
45
+ self.runner = (
46
+ runner
47
+ if runner is not None
48
+ else CommandRunner(default_timeout=QUALITY_CHECK_LONG_TIMEOUT)
49
+ )
50
+ self.language = language or self._detect_language()
51
+
52
+ def name(self) -> str:
53
+ """Return checker name."""
54
+ return "security"
55
+
56
+ def is_enabled(self) -> bool:
57
+ """Check if security scanning is enabled."""
58
+ return bool(self.config.get("enabled", True))
59
+
60
+ def _detect_language(self) -> str:
61
+ """Detect primary project language."""
62
+ if (self.project_root / "pyproject.toml").exists() or (
63
+ self.project_root / "setup.py"
64
+ ).exists():
65
+ return "python"
66
+ elif (self.project_root / "package.json").exists():
67
+ if (self.project_root / "tsconfig.json").exists():
68
+ return "typescript"
69
+ return "javascript"
70
+ return "python" # default
71
+
72
+ def run(self) -> CheckResult:
73
+ """Run security vulnerability scan."""
74
+ start_time = time.time()
75
+
76
+ if not self.is_enabled():
77
+ return self._create_skipped_result()
78
+
79
+ logger.info(f"Running security scan for {self.language}")
80
+
81
+ if self.language == "python":
82
+ results = self._scan_python()
83
+ elif self.language in ["javascript", "typescript"]:
84
+ results = self._scan_javascript()
85
+ else:
86
+ return self._create_skipped_result(reason=f"unsupported language: {self.language}")
87
+
88
+ # Check if passed based on fail_on threshold
89
+ fail_on = self.config.get("fail_on", "high").upper()
90
+ severity_levels = ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
91
+
92
+ if fail_on not in severity_levels:
93
+ fail_on = "HIGH"
94
+
95
+ fail_threshold = severity_levels.index(fail_on)
96
+
97
+ passed = True
98
+ for severity, count in results.get("by_severity", {}).items():
99
+ if (
100
+ severity in severity_levels
101
+ and severity_levels.index(severity) >= fail_threshold
102
+ and count > 0
103
+ ):
104
+ passed = False
105
+ break
106
+
107
+ execution_time = time.time() - start_time
108
+
109
+ return CheckResult(
110
+ checker_name=self.name(),
111
+ passed=passed,
112
+ status="passed" if passed else "failed",
113
+ errors=results.get("vulnerabilities", []),
114
+ warnings=[],
115
+ info={
116
+ "by_severity": results.get("by_severity", {}),
117
+ "fail_threshold": fail_on,
118
+ "language": self.language,
119
+ },
120
+ execution_time=execution_time,
121
+ )
122
+
123
+ def _scan_python(self) -> dict[str, Any]:
124
+ """Run Python security scans (Bandit + Safety)."""
125
+ results: dict[str, Any] = {"vulnerabilities": [], "by_severity": {}}
126
+
127
+ # Run Bandit
128
+ bandit_results = self._run_bandit()
129
+ if bandit_results:
130
+ results["bandit"] = bandit_results
131
+ # Count by severity
132
+ for issue in bandit_results.get("results", []):
133
+ severity = issue.get("issue_severity", "LOW")
134
+ results["by_severity"][severity] = results["by_severity"].get(severity, 0) + 1
135
+ # Add to vulnerabilities list
136
+ results["vulnerabilities"].append(
137
+ {
138
+ "source": "bandit",
139
+ "file": issue.get("filename", ""),
140
+ "line": issue.get("line_number", 0),
141
+ "issue": issue.get("issue_text", ""),
142
+ "severity": severity,
143
+ "confidence": issue.get("issue_confidence", ""),
144
+ }
145
+ )
146
+
147
+ # Run Safety
148
+ safety_results = self._run_safety()
149
+ if safety_results:
150
+ results["safety"] = safety_results
151
+ results["vulnerabilities"].extend(safety_results)
152
+
153
+ return results
154
+
155
+ def _run_bandit(self) -> dict[str, Any] | None:
156
+ """Run Bandit security scanner."""
157
+ try:
158
+ # Create temporary file for report
159
+ fd, bandit_report_path = tempfile.mkstemp(suffix=".json")
160
+ os.close(fd)
161
+
162
+ try:
163
+ src_dir = self.project_root / "src"
164
+ if not src_dir.exists():
165
+ logger.debug("No src/ directory found, skipping Bandit")
166
+ return None
167
+
168
+ self.runner.run(
169
+ [
170
+ "bandit",
171
+ "-r",
172
+ str(src_dir),
173
+ "-f",
174
+ "json",
175
+ "-o",
176
+ bandit_report_path,
177
+ ],
178
+ timeout=QUALITY_CHECK_LONG_TIMEOUT,
179
+ )
180
+
181
+ if Path(bandit_report_path).exists():
182
+ try:
183
+ with open(bandit_report_path) as f:
184
+ content = f.read().strip()
185
+ if content:
186
+ return json.loads(content) # type: ignore[no-any-return]
187
+ except (json.JSONDecodeError, ValueError) as e:
188
+ logger.warning(f"Failed to parse bandit report: {e}")
189
+ except OSError as e:
190
+ logger.warning(f"Failed to read bandit report: {e}")
191
+ finally:
192
+ # Clean up temporary file
193
+ try:
194
+ Path(bandit_report_path).unlink()
195
+ except OSError:
196
+ pass
197
+
198
+ except (ImportError, OSError) as e:
199
+ logger.debug(f"Bandit not available: {e}")
200
+
201
+ return None
202
+
203
+ def _run_safety(self) -> list[dict[str, Any]]:
204
+ """Run Safety dependency scanner."""
205
+ requirements_file = self.project_root / "requirements.txt"
206
+ if not requirements_file.exists():
207
+ logger.debug("No requirements.txt found, skipping Safety")
208
+ return []
209
+
210
+ result = self.runner.run(
211
+ ["safety", "check", "--file", str(requirements_file), "--json"],
212
+ timeout=QUALITY_CHECK_LONG_TIMEOUT,
213
+ )
214
+
215
+ if result.success and result.stdout:
216
+ try:
217
+ # Safety may prefix JSON with deprecation warnings, so find the first JSON marker
218
+ json_start = min(
219
+ (
220
+ pos
221
+ for pos in [result.stdout.find("{"), result.stdout.find("[")]
222
+ if pos != -1
223
+ ),
224
+ default=-1,
225
+ )
226
+ if json_start != -1:
227
+ return json.loads(result.stdout[json_start:]) # type: ignore[no-any-return]
228
+ return json.loads(result.stdout) # type: ignore[no-any-return]
229
+ except json.JSONDecodeError as e:
230
+ logger.warning(f"Failed to parse safety output: {e}")
231
+ logger.debug(f"Safety output: {result.stdout[:200]}")
232
+
233
+ return []
234
+
235
+ def _scan_javascript(self) -> dict[str, Any]:
236
+ """Run JavaScript/TypeScript security scans (npm audit)."""
237
+ results: dict[str, Any] = {"vulnerabilities": [], "by_severity": {}}
238
+
239
+ package_json = self.project_root / "package.json"
240
+ if not package_json.exists():
241
+ logger.debug("No package.json found, skipping npm audit")
242
+ return results
243
+
244
+ audit_result = self.runner.run(
245
+ ["npm", "audit", "--json"], timeout=QUALITY_CHECK_LONG_TIMEOUT
246
+ )
247
+
248
+ if audit_result.success and audit_result.stdout:
249
+ try:
250
+ audit_data = json.loads(audit_result.stdout)
251
+ results["npm_audit"] = audit_data
252
+
253
+ # Count by severity
254
+ for vuln in audit_data.get("vulnerabilities", {}).values():
255
+ severity = vuln.get("severity", "low").upper()
256
+ results["by_severity"][severity] = results["by_severity"].get(severity, 0) + 1
257
+
258
+ except json.JSONDecodeError:
259
+ logger.warning("Failed to parse npm audit output")
260
+
261
+ return results
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Specification completeness checker.
4
+
5
+ Validates that work item specification files are complete and properly formatted.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Any, Union, cast
13
+
14
+ from solokit.core.exceptions import (
15
+ FileNotFoundError as SolokitFileNotFoundError,
16
+ )
17
+ from solokit.core.exceptions import SpecValidationError
18
+ from solokit.core.logging_config import get_logger
19
+ from solokit.quality.checkers.base import CheckResult, QualityChecker
20
+ from solokit.work_items.spec_validator import validate_spec_file
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class SpecCompletenessChecker(QualityChecker):
26
+ """Specification file completeness validation."""
27
+
28
+ def __init__(
29
+ self,
30
+ config: dict[str, Any],
31
+ project_root: Path | None = None,
32
+ work_item: dict[str, Any] | None = None,
33
+ ):
34
+ """Initialize spec completeness checker.
35
+
36
+ Args:
37
+ config: Spec completeness configuration
38
+ project_root: Project root directory
39
+ work_item: Work item dictionary with 'id' and 'type' fields
40
+ """
41
+ super().__init__(config, project_root)
42
+ self.work_item = work_item or {}
43
+
44
+ def name(self) -> str:
45
+ """Return checker name."""
46
+ return "spec_completeness"
47
+
48
+ def is_enabled(self) -> bool:
49
+ """Check if spec completeness validation is enabled."""
50
+ return bool(self.config.get("enabled", True))
51
+
52
+ def run(self) -> CheckResult:
53
+ """Run spec completeness validation."""
54
+ start_time = time.time()
55
+
56
+ if not self.is_enabled():
57
+ return self._create_skipped_result()
58
+
59
+ work_item_id = self.work_item.get("id")
60
+ work_item_type = self.work_item.get("type")
61
+
62
+ if not work_item_id or not work_item_type:
63
+ execution_time = time.time() - start_time
64
+ return CheckResult(
65
+ checker_name=self.name(),
66
+ passed=False,
67
+ status="failed",
68
+ errors=[{"message": "Work item missing 'id' or 'type' field"}],
69
+ warnings=[],
70
+ info={},
71
+ execution_time=execution_time,
72
+ )
73
+
74
+ logger.info(f"Validating spec file for work item: {work_item_id}")
75
+
76
+ # Validate spec file
77
+ try:
78
+ validate_spec_file(work_item_id, work_item_type)
79
+ execution_time = time.time() - start_time
80
+ return CheckResult(
81
+ checker_name=self.name(),
82
+ passed=True,
83
+ status="passed",
84
+ errors=[],
85
+ warnings=[],
86
+ info={"message": f"Spec file for '{work_item_id}' is complete"},
87
+ execution_time=execution_time,
88
+ )
89
+ except SpecValidationError as e:
90
+ execution_time = time.time() - start_time
91
+ validation_errors = e.context.get("validation_errors", [])
92
+ errors = [{"message": error} for error in validation_errors]
93
+ return CheckResult(
94
+ checker_name=self.name(),
95
+ passed=False,
96
+ status="failed",
97
+ errors=cast(list[Union[dict[str, Any], str]], errors),
98
+ warnings=[],
99
+ info={
100
+ "message": f"Spec file for '{work_item_id}' is incomplete",
101
+ "suggestion": e.remediation
102
+ or f"Edit .session/specs/{work_item_id}.md to add missing sections",
103
+ },
104
+ execution_time=execution_time,
105
+ )
106
+ except SolokitFileNotFoundError as e:
107
+ execution_time = time.time() - start_time
108
+ return CheckResult(
109
+ checker_name=self.name(),
110
+ passed=False,
111
+ status="failed",
112
+ errors=[{"message": e.message}],
113
+ warnings=[],
114
+ info={"suggestion": e.remediation},
115
+ execution_time=execution_time,
116
+ )
117
+ except (OSError, ValueError) as e:
118
+ execution_time = time.time() - start_time
119
+ return CheckResult(
120
+ checker_name=self.name(),
121
+ passed=False,
122
+ status="failed",
123
+ errors=[{"message": f"Error validating spec file: {str(e)}"}],
124
+ warnings=[],
125
+ info={"suggestion": "Check spec file format and validator configuration"},
126
+ execution_time=execution_time,
127
+ )
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test execution and coverage checker.
4
+
5
+ Runs tests using pytest, Jest, or other test frameworks and validates coverage.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Any, Union, cast
14
+
15
+ from solokit.core.command_runner import CommandRunner
16
+ from solokit.core.constants import TEST_RUNNER_TIMEOUT
17
+ from solokit.core.logging_config import get_logger
18
+ from solokit.quality.checkers.base import CheckResult, QualityChecker
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class ExecutionChecker(QualityChecker):
24
+ """Test execution and coverage validation."""
25
+
26
+ def __init__(
27
+ self,
28
+ config: dict[str, Any],
29
+ project_root: Path | None = None,
30
+ language: str | None = None,
31
+ runner: CommandRunner | None = None,
32
+ ):
33
+ """Initialize test runner.
34
+
35
+ Args:
36
+ config: Test execution configuration
37
+ project_root: Project root directory
38
+ language: Programming language (python, javascript, typescript)
39
+ runner: Optional CommandRunner instance (for testing)
40
+ """
41
+ super().__init__(config, project_root)
42
+ self.runner = (
43
+ runner if runner is not None else CommandRunner(default_timeout=TEST_RUNNER_TIMEOUT)
44
+ )
45
+ self.language = language or self._detect_language()
46
+
47
+ def name(self) -> str:
48
+ """Return checker name."""
49
+ return "tests"
50
+
51
+ def is_enabled(self) -> bool:
52
+ """Check if test execution is enabled."""
53
+ return bool(self.config.get("enabled", True))
54
+
55
+ def _detect_language(self) -> str:
56
+ """Detect primary project language."""
57
+ if (self.project_root / "pyproject.toml").exists() or (
58
+ self.project_root / "setup.py"
59
+ ).exists():
60
+ return "python"
61
+ elif (self.project_root / "package.json").exists():
62
+ if (self.project_root / "tsconfig.json").exists():
63
+ return "typescript"
64
+ return "javascript"
65
+ return "python" # default
66
+
67
+ def run(self) -> CheckResult:
68
+ """Run test suite with coverage."""
69
+ start_time = time.time()
70
+
71
+ if not self.is_enabled():
72
+ return self._create_skipped_result()
73
+
74
+ logger.info(f"Running tests for {self.language}")
75
+
76
+ # Get test command for language
77
+ commands = self.config.get("commands", {})
78
+ command = commands.get(self.language)
79
+ if not command:
80
+ logger.warning(f"No test command configured for language: {self.language}")
81
+ return self._create_skipped_result(reason=f"no command for {self.language}")
82
+
83
+ # Run tests
84
+ result = self.runner.run(command.split(), timeout=TEST_RUNNER_TIMEOUT)
85
+
86
+ # pytest exit codes:
87
+ # 0 = all tests passed
88
+ # 1 = tests were collected and run but some failed
89
+ # 2 = test execution was interrupted
90
+ # 3 = internal error
91
+ # 4 = pytest command line usage error
92
+ # 5 = no tests were collected
93
+
94
+ if result.timed_out:
95
+ execution_time = time.time() - start_time
96
+ return CheckResult(
97
+ checker_name=self.name(),
98
+ passed=False,
99
+ status="failed",
100
+ errors=[{"message": "Test execution timed out"}],
101
+ warnings=[],
102
+ info={"reason": "timeout"},
103
+ execution_time=execution_time,
104
+ )
105
+
106
+ # Command not found (test tool not available)
107
+ if result.returncode == -1 and "not found" in result.stderr.lower():
108
+ return self._create_skipped_result(reason=f"{command.split()[0]} not available")
109
+
110
+ # Treat "no tests collected" (exit code 5) as skipped, not failed
111
+ if result.returncode == 5:
112
+ execution_time = time.time() - start_time
113
+ return CheckResult(
114
+ checker_name=self.name(),
115
+ passed=True,
116
+ status="skipped",
117
+ errors=[],
118
+ warnings=[],
119
+ info={"reason": "no tests collected", "returncode": result.returncode},
120
+ execution_time=execution_time,
121
+ )
122
+
123
+ # Parse coverage
124
+ coverage = self._parse_coverage()
125
+ passed = result.returncode == 0
126
+
127
+ # Check coverage threshold
128
+ threshold = self.config.get("coverage_threshold", 80)
129
+ if coverage is not None and coverage < threshold:
130
+ passed = False
131
+
132
+ execution_time = time.time() - start_time
133
+
134
+ errors = []
135
+ if result.returncode != 0:
136
+ errors.append(
137
+ {
138
+ "message": f"Tests failed with exit code {result.returncode}",
139
+ "output": (result.stderr[:500] if result.stderr else ""), # Limit output
140
+ }
141
+ )
142
+
143
+ if coverage is not None and coverage < threshold:
144
+ errors.append({"message": f"Coverage {coverage}% below threshold {threshold}%"})
145
+
146
+ return CheckResult(
147
+ checker_name=self.name(),
148
+ passed=passed,
149
+ status="passed" if passed else "failed",
150
+ errors=cast(list[Union[dict[str, Any], str]], errors),
151
+ warnings=[],
152
+ info={
153
+ "coverage": coverage,
154
+ "threshold": threshold,
155
+ "returncode": result.returncode,
156
+ "output": result.stdout[:1000] if result.stdout else "", # Limit output
157
+ },
158
+ execution_time=execution_time,
159
+ )
160
+
161
+ def _parse_coverage(self) -> float | None:
162
+ """Parse coverage from test results."""
163
+ try:
164
+ if self.language == "python":
165
+ coverage_file = self.project_root / "coverage.json"
166
+ if coverage_file.exists():
167
+ with open(coverage_file) as f:
168
+ data = json.load(f)
169
+ return data.get("totals", {}).get("percent_covered", 0) # type: ignore[no-any-return]
170
+
171
+ elif self.language in ["javascript", "typescript"]:
172
+ coverage_file = self.project_root / "coverage" / "coverage-summary.json"
173
+ if coverage_file.exists():
174
+ with open(coverage_file) as f:
175
+ data = json.load(f)
176
+ return data.get("total", {}).get("lines", {}).get("pct", 0) # type: ignore[no-any-return]
177
+
178
+ return None
179
+ except OSError as e:
180
+ logger.debug(f"Failed to read coverage file: {e}")
181
+ return None
182
+ except json.JSONDecodeError as e:
183
+ logger.debug(f"Failed to parse coverage file: {e}")
184
+ return None