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,1567 @@
1
+ """
2
+ Comprehensive exception hierarchy for Solokit.
3
+
4
+ Provides structured exceptions with error codes, categories, and context.
5
+ All business logic should raise these exceptions rather than returning error tuples.
6
+
7
+ Usage:
8
+ from solokit.core.exceptions import WorkItemNotFoundError
9
+
10
+ def get_work_item(item_id: str) -> WorkItem:
11
+ if item_id not in work_items:
12
+ raise WorkItemNotFoundError(item_id)
13
+ return work_items[item_id]
14
+
15
+ # CLI layer catches and formats
16
+ try:
17
+ item = get_work_item("invalid")
18
+ except WorkItemNotFoundError as e:
19
+ output.info(f"Error: {e}")
20
+ output.info(f"Remediation: {e.remediation}")
21
+ sys.exit(e.exit_code)
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from enum import Enum
27
+ from typing import Any
28
+
29
+ from solokit.core.logging_config import get_logger
30
+ from solokit.core.output import get_output
31
+
32
+ logger = get_logger(__name__)
33
+ output = get_output()
34
+
35
+
36
+ class ErrorCategory(Enum):
37
+ """Error categories for classification and handling"""
38
+
39
+ VALIDATION = "validation"
40
+ NOT_FOUND = "not_found"
41
+ ALREADY_EXISTS = "already_exists"
42
+ CONFIGURATION = "configuration"
43
+ SYSTEM = "system"
44
+ GIT = "git"
45
+ DEPENDENCY = "dependency"
46
+ SECURITY = "security"
47
+ TIMEOUT = "timeout"
48
+ PERMISSION = "permission"
49
+
50
+
51
+ class ErrorCode(Enum):
52
+ """Specific error codes for programmatic handling"""
53
+
54
+ # Validation errors (1000-1999)
55
+ INVALID_WORK_ITEM_ID = 1001
56
+ INVALID_WORK_ITEM_TYPE = 1002
57
+ MISSING_REQUIRED_FIELD = 1003
58
+ INVALID_JSON = 1004
59
+ SPEC_VALIDATION_FAILED = 1005
60
+ INVALID_STATUS = 1006
61
+ INVALID_PRIORITY = 1007
62
+ INVALID_COMMAND = 1008
63
+ PROJECT_NOT_BLANK = 1009
64
+ INVALID_CONFIGURATION = 1010
65
+
66
+ # Not found errors (2000-2999)
67
+ WORK_ITEM_NOT_FOUND = 2001
68
+ FILE_NOT_FOUND = 2002
69
+ SESSION_NOT_FOUND = 2003
70
+ LEARNING_NOT_FOUND = 2004
71
+ CONFIG_NOT_FOUND = 2005
72
+
73
+ # Already exists errors (3000-3999)
74
+ WORK_ITEM_ALREADY_EXISTS = 3001
75
+ FILE_ALREADY_EXISTS = 3002
76
+ SESSION_ALREADY_ACTIVE = 3003
77
+
78
+ # Configuration errors (4000-4999)
79
+ CONFIG_FILE_MISSING = 4001
80
+ CONFIG_VALIDATION_FAILED = 4002
81
+ SCHEMA_MISSING = 4003
82
+ INVALID_CONFIG_VALUE = 4004
83
+
84
+ # System errors (5000-5999)
85
+ FILE_OPERATION_FAILED = 5001
86
+ SUBPROCESS_FAILED = 5002
87
+ IMPORT_FAILED = 5003
88
+ COMMAND_FAILED = 5004
89
+ MODULE_NOT_FOUND = 5005
90
+ FUNCTION_NOT_FOUND = 5006
91
+
92
+ # Git errors (6000-6999)
93
+ NOT_A_GIT_REPO = 6001
94
+ GIT_NOT_FOUND = 6002
95
+ WORKING_DIR_NOT_CLEAN = 6003
96
+ GIT_COMMAND_FAILED = 6004
97
+ BRANCH_NOT_FOUND = 6005
98
+ BRANCH_ALREADY_EXISTS = 6006
99
+
100
+ # Dependency errors (7000-7999)
101
+ CIRCULAR_DEPENDENCY = 7001
102
+ UNMET_DEPENDENCY = 7002
103
+
104
+ # Security errors (8000-8999)
105
+ SECURITY_SCAN_FAILED = 8001
106
+ VULNERABILITY_FOUND = 8002
107
+
108
+ # Timeout errors (9000-9999)
109
+ OPERATION_TIMEOUT = 9001
110
+ SUBPROCESS_TIMEOUT = 9002
111
+
112
+ # Quality gate errors (10000-10999)
113
+ TEST_FAILED = 10001
114
+ LINT_FAILED = 10002
115
+ COVERAGE_BELOW_THRESHOLD = 10003
116
+ QUALITY_GATE_FAILED = 10004
117
+
118
+ # Deployment errors (11000-11999)
119
+ DEPLOYMENT_FAILED = 11001
120
+ PRE_DEPLOYMENT_CHECK_FAILED = 11002
121
+ SMOKE_TEST_FAILED = 11003
122
+ ROLLBACK_FAILED = 11004
123
+ DEPLOYMENT_STEP_FAILED = 11005
124
+
125
+ # API validation errors (12000-12999)
126
+ API_VALIDATION_FAILED = 12001
127
+ SCHEMA_VALIDATION_FAILED = 12002
128
+ CONTRACT_VIOLATION = 12003
129
+ BREAKING_CHANGE_DETECTED = 12004
130
+ INVALID_OPENAPI_SPEC = 12005
131
+
132
+ # Performance testing errors (13000-13999)
133
+ PERFORMANCE_TEST_FAILED = 13001
134
+ BENCHMARK_FAILED = 13002
135
+ PERFORMANCE_REGRESSION = 13003
136
+ LOAD_TEST_FAILED = 13004
137
+
138
+
139
+ class SolokitError(Exception):
140
+ """
141
+ Base exception for all Solokit errors.
142
+
143
+ Attributes:
144
+ message: Human-readable error message
145
+ code: ErrorCode enum for programmatic handling
146
+ category: ErrorCategory enum for classification
147
+ context: Additional context data (file paths, IDs, etc.)
148
+ remediation: Suggested fix for the user
149
+ cause: Original exception if wrapping another error
150
+ exit_code: Suggested exit code for CLI
151
+
152
+ Example:
153
+ >>> error = SolokitError(
154
+ ... message="Something went wrong",
155
+ ... code=ErrorCode.FILE_OPERATION_FAILED,
156
+ ... category=ErrorCategory.SYSTEM,
157
+ ... context={"file": "/path/to/file"},
158
+ ... remediation="Check file permissions"
159
+ ... )
160
+ >>> print(error.to_dict())
161
+ """
162
+
163
+ def __init__(
164
+ self,
165
+ message: str,
166
+ code: ErrorCode,
167
+ category: ErrorCategory,
168
+ context: dict[str, Any] | None = None,
169
+ remediation: str | None = None,
170
+ cause: Exception | None = None,
171
+ ):
172
+ super().__init__(message)
173
+ self.message = message
174
+ self.code = code
175
+ self.category = category
176
+ self.context = context or {}
177
+ self.remediation = remediation
178
+ self.cause = cause
179
+
180
+ @property
181
+ def exit_code(self) -> int:
182
+ """Get CLI exit code based on error category"""
183
+ exit_codes = {
184
+ ErrorCategory.VALIDATION: 2,
185
+ ErrorCategory.NOT_FOUND: 3,
186
+ ErrorCategory.CONFIGURATION: 4,
187
+ ErrorCategory.SYSTEM: 5,
188
+ ErrorCategory.GIT: 6,
189
+ ErrorCategory.DEPENDENCY: 7,
190
+ ErrorCategory.SECURITY: 8,
191
+ ErrorCategory.TIMEOUT: 9,
192
+ ErrorCategory.ALREADY_EXISTS: 10,
193
+ ErrorCategory.PERMISSION: 11,
194
+ }
195
+ return exit_codes.get(self.category, 1)
196
+
197
+ def __str__(self) -> str:
198
+ """Format error for display"""
199
+ parts = [self.message]
200
+ if self.remediation:
201
+ parts.append(f"Remediation: {self.remediation}")
202
+ return "\n".join(parts)
203
+
204
+ def to_dict(self) -> dict[str, Any]:
205
+ """Convert to dictionary for structured logging"""
206
+ return {
207
+ "message": self.message,
208
+ "code": self.code.value,
209
+ "code_name": self.code.name,
210
+ "category": self.category.value,
211
+ "context": self.context,
212
+ "remediation": self.remediation,
213
+ "cause": str(self.cause) if self.cause else None,
214
+ "exit_code": self.exit_code,
215
+ }
216
+
217
+
218
+ # ============================================================================
219
+ # Validation Errors
220
+ # ============================================================================
221
+
222
+
223
+ class ValidationError(SolokitError):
224
+ """
225
+ Raised when validation fails.
226
+
227
+ Example:
228
+ >>> raise ValidationError(
229
+ ... message="Invalid work item ID format",
230
+ ... code=ErrorCode.INVALID_WORK_ITEM_ID,
231
+ ... context={"work_item_id": "bad-id!"},
232
+ ... remediation="Use alphanumeric characters and underscores only"
233
+ ... )
234
+ """
235
+
236
+ def __init__(
237
+ self,
238
+ message: str,
239
+ code: ErrorCode = ErrorCode.MISSING_REQUIRED_FIELD,
240
+ context: dict[str, Any] | None = None,
241
+ remediation: str | None = None,
242
+ cause: Exception | None = None,
243
+ ):
244
+ super().__init__(
245
+ message=message,
246
+ code=code,
247
+ category=ErrorCategory.VALIDATION,
248
+ context=context,
249
+ remediation=remediation,
250
+ cause=cause,
251
+ )
252
+
253
+
254
+ class SpecValidationError(ValidationError):
255
+ """
256
+ Raised when spec file validation fails.
257
+
258
+ Example:
259
+ >>> raise SpecValidationError(
260
+ ... work_item_id="my_feature",
261
+ ... errors=["Missing Overview section", "Missing acceptance criteria"]
262
+ ... )
263
+ """
264
+
265
+ def __init__(self, work_item_id: str, errors: list[str], remediation: str | None = None):
266
+ message = f"Spec validation failed for '{work_item_id}'"
267
+ context = {
268
+ "work_item_id": work_item_id,
269
+ "validation_errors": errors,
270
+ "error_count": len(errors),
271
+ }
272
+ super().__init__(
273
+ message=message,
274
+ code=ErrorCode.SPEC_VALIDATION_FAILED,
275
+ context=context,
276
+ remediation=remediation
277
+ or f"Edit .session/specs/{work_item_id}.md to fix validation errors",
278
+ )
279
+
280
+
281
+ # ============================================================================
282
+ # Not Found Errors
283
+ # ============================================================================
284
+
285
+
286
+ class NotFoundError(SolokitError):
287
+ """
288
+ Raised when a resource is not found.
289
+
290
+ Example:
291
+ >>> raise NotFoundError(
292
+ ... message="Configuration file not found",
293
+ ... code=ErrorCode.CONFIG_NOT_FOUND,
294
+ ... context={"path": ".session/config.json"}
295
+ ... )
296
+ """
297
+
298
+ def __init__(
299
+ self,
300
+ message: str,
301
+ code: ErrorCode,
302
+ context: dict[str, Any] | None = None,
303
+ remediation: str | None = None,
304
+ cause: Exception | None = None,
305
+ ):
306
+ super().__init__(
307
+ message=message,
308
+ code=code,
309
+ category=ErrorCategory.NOT_FOUND,
310
+ context=context,
311
+ remediation=remediation,
312
+ cause=cause,
313
+ )
314
+
315
+
316
+ class WorkItemNotFoundError(NotFoundError):
317
+ """
318
+ Raised when work item doesn't exist.
319
+
320
+ Example:
321
+ >>> raise WorkItemNotFoundError("nonexistent_feature")
322
+ """
323
+
324
+ def __init__(self, work_item_id: str):
325
+ super().__init__(
326
+ message=f"Work item '{work_item_id}' not found",
327
+ code=ErrorCode.WORK_ITEM_NOT_FOUND,
328
+ context={"work_item_id": work_item_id},
329
+ remediation="Use 'sk work-list' to see available work items",
330
+ )
331
+
332
+
333
+ class FileNotFoundError(NotFoundError):
334
+ """
335
+ Raised when a required file doesn't exist.
336
+
337
+ Note: This shadows the built-in FileNotFoundError, but provides
338
+ more structured error information.
339
+
340
+ Example:
341
+ >>> raise FileNotFoundError(
342
+ ... file_path=".session/specs/my_feature.md",
343
+ ... file_type="spec"
344
+ ... )
345
+ """
346
+
347
+ def __init__(self, file_path: str, file_type: str | None = None):
348
+ context = {"file_path": file_path}
349
+ if file_type:
350
+ context["file_type"] = file_type
351
+
352
+ remediation_msg = None
353
+ if file_type:
354
+ remediation_msg = f"Create the missing {file_type} file: {file_path}"
355
+
356
+ super().__init__(
357
+ message=f"File not found: {file_path}",
358
+ code=ErrorCode.FILE_NOT_FOUND,
359
+ context=context,
360
+ remediation=remediation_msg,
361
+ )
362
+
363
+
364
+ class SessionNotFoundError(NotFoundError):
365
+ """
366
+ Raised when no active session exists.
367
+
368
+ Example:
369
+ >>> raise SessionNotFoundError()
370
+ """
371
+
372
+ def __init__(self) -> None:
373
+ super().__init__(
374
+ message="No active session found",
375
+ code=ErrorCode.SESSION_NOT_FOUND,
376
+ remediation="Start a session with 'sk start' or 'sk start <work_item_id>'",
377
+ )
378
+
379
+
380
+ # ============================================================================
381
+ # Configuration Errors
382
+ # ============================================================================
383
+
384
+
385
+ class ConfigurationError(SolokitError):
386
+ """
387
+ Raised when configuration is invalid.
388
+
389
+ Example:
390
+ >>> raise ConfigurationError(
391
+ ... message="Invalid configuration value",
392
+ ... code=ErrorCode.INVALID_CONFIG_VALUE,
393
+ ... context={"key": "test_command", "value": None}
394
+ ... )
395
+ """
396
+
397
+ def __init__(
398
+ self,
399
+ message: str,
400
+ code: ErrorCode = ErrorCode.CONFIG_VALIDATION_FAILED,
401
+ context: dict[str, Any] | None = None,
402
+ remediation: str | None = None,
403
+ cause: Exception | None = None,
404
+ ):
405
+ super().__init__(
406
+ message=message,
407
+ code=code,
408
+ category=ErrorCategory.CONFIGURATION,
409
+ context=context,
410
+ remediation=remediation,
411
+ cause=cause,
412
+ )
413
+
414
+
415
+ class ConfigValidationError(ConfigurationError):
416
+ """
417
+ Raised when config fails schema validation.
418
+
419
+ Example:
420
+ >>> raise ConfigValidationError(
421
+ ... config_path=".session/config.json",
422
+ ... errors=["Missing 'project_name' field"]
423
+ ... )
424
+ """
425
+
426
+ def __init__(self, config_path: str, errors: list[str]):
427
+ message = f"Configuration validation failed: {config_path}"
428
+ context = {
429
+ "config_path": config_path,
430
+ "validation_errors": errors,
431
+ "error_count": len(errors),
432
+ }
433
+ super().__init__(
434
+ message=message,
435
+ code=ErrorCode.CONFIG_VALIDATION_FAILED,
436
+ context=context,
437
+ remediation="Check docs/guides/configuration.md for valid configuration options",
438
+ )
439
+
440
+
441
+ # ============================================================================
442
+ # Git Errors
443
+ # ============================================================================
444
+
445
+
446
+ class GitError(SolokitError):
447
+ """
448
+ Raised for git-related errors.
449
+
450
+ Example:
451
+ >>> raise GitError(
452
+ ... message="Git command failed",
453
+ ... code=ErrorCode.GIT_COMMAND_FAILED,
454
+ ... context={"command": "git status"}
455
+ ... )
456
+ """
457
+
458
+ def __init__(
459
+ self,
460
+ message: str,
461
+ code: ErrorCode,
462
+ context: dict[str, Any] | None = None,
463
+ remediation: str | None = None,
464
+ cause: Exception | None = None,
465
+ ):
466
+ super().__init__(
467
+ message=message,
468
+ code=code,
469
+ category=ErrorCategory.GIT,
470
+ context=context,
471
+ remediation=remediation,
472
+ cause=cause,
473
+ )
474
+
475
+
476
+ class NotAGitRepoError(GitError):
477
+ """
478
+ Raised when operation requires git repo but not in one.
479
+
480
+ Example:
481
+ >>> raise NotAGitRepoError()
482
+ """
483
+
484
+ def __init__(self, path: str | None = None):
485
+ context = {"path": path} if path else {}
486
+ super().__init__(
487
+ message="Not a git repository",
488
+ code=ErrorCode.NOT_A_GIT_REPO,
489
+ context=context,
490
+ remediation="Run 'git init' to initialize a repository",
491
+ )
492
+
493
+
494
+ class WorkingDirNotCleanError(GitError):
495
+ """
496
+ Raised when working directory has uncommitted changes.
497
+
498
+ Example:
499
+ >>> raise WorkingDirNotCleanError(
500
+ ... changes=["M src/file.py", "?? new_file.py"]
501
+ ... )
502
+ """
503
+
504
+ def __init__(self, changes: list[str] | None = None):
505
+ context = {"uncommitted_changes": changes} if changes else {}
506
+ super().__init__(
507
+ message="Working directory not clean (uncommitted changes)",
508
+ code=ErrorCode.WORKING_DIR_NOT_CLEAN,
509
+ context=context,
510
+ remediation="Commit or stash changes before proceeding",
511
+ )
512
+
513
+
514
+ class BranchNotFoundError(GitError):
515
+ """
516
+ Raised when git branch doesn't exist.
517
+
518
+ Example:
519
+ >>> raise BranchNotFoundError("feature-branch")
520
+ """
521
+
522
+ def __init__(self, branch_name: str):
523
+ super().__init__(
524
+ message=f"Branch '{branch_name}' not found",
525
+ code=ErrorCode.BRANCH_NOT_FOUND,
526
+ context={"branch_name": branch_name},
527
+ remediation="Check branch name or create it with 'git checkout -b <branch>'",
528
+ )
529
+
530
+
531
+ # ============================================================================
532
+ # System Errors
533
+ # ============================================================================
534
+
535
+
536
+ class SystemError(SolokitError):
537
+ """
538
+ Raised for system-level errors.
539
+
540
+ Example:
541
+ >>> raise SystemError(
542
+ ... message="Failed to write file",
543
+ ... code=ErrorCode.FILE_OPERATION_FAILED,
544
+ ... context={"path": "/tmp/file.txt"}
545
+ ... )
546
+ """
547
+
548
+ def __init__(
549
+ self,
550
+ message: str,
551
+ code: ErrorCode,
552
+ context: dict[str, Any] | None = None,
553
+ remediation: str | None = None,
554
+ cause: Exception | None = None,
555
+ ):
556
+ super().__init__(
557
+ message=message,
558
+ code=code,
559
+ category=ErrorCategory.SYSTEM,
560
+ context=context,
561
+ remediation=remediation,
562
+ cause=cause,
563
+ )
564
+
565
+
566
+ class SubprocessError(SystemError):
567
+ """
568
+ Raised when subprocess command fails.
569
+
570
+ Example:
571
+ >>> raise SubprocessError(
572
+ ... command="pytest tests/",
573
+ ... returncode=1,
574
+ ... stderr="FAILED tests/test_foo.py"
575
+ ... )
576
+ """
577
+
578
+ def __init__(
579
+ self, command: str, returncode: int, stderr: str | None = None, stdout: str | None = None
580
+ ):
581
+ context = {"command": command, "returncode": returncode, "stderr": stderr, "stdout": stdout}
582
+ super().__init__(
583
+ message=f"Command failed with exit code {returncode}: {command}",
584
+ code=ErrorCode.SUBPROCESS_FAILED,
585
+ context=context,
586
+ )
587
+
588
+
589
+ class TimeoutError(SystemError):
590
+ """
591
+ Raised when operation times out.
592
+
593
+ Example:
594
+ >>> raise TimeoutError(
595
+ ... operation="git fetch",
596
+ ... timeout_seconds=30
597
+ ... )
598
+ """
599
+
600
+ def __init__(self, operation: str, timeout_seconds: int, context: dict[str, Any] | None = None):
601
+ ctx = context or {}
602
+ ctx.update({"operation": operation, "timeout_seconds": timeout_seconds})
603
+ super().__init__(
604
+ message=f"Operation timed out after {timeout_seconds}s: {operation}",
605
+ code=ErrorCode.OPERATION_TIMEOUT,
606
+ context=ctx,
607
+ )
608
+
609
+
610
+ class CommandExecutionError(SystemError):
611
+ """
612
+ Raised when a command execution fails.
613
+
614
+ This wraps the CommandExecutionError from command_runner for consistency.
615
+
616
+ Example:
617
+ >>> raise CommandExecutionError(
618
+ ... command="npm test",
619
+ ... returncode=1,
620
+ ... stderr="Test failed"
621
+ ... )
622
+ """
623
+
624
+ def __init__(
625
+ self,
626
+ command: str,
627
+ returncode: int | None = None,
628
+ stderr: str | None = None,
629
+ stdout: str | None = None,
630
+ exit_code: int | None = None,
631
+ context: dict[str, Any] | None = None,
632
+ ):
633
+ # Support both returncode and exit_code for backwards compatibility
634
+ actual_code = exit_code if exit_code is not None else returncode
635
+
636
+ # Merge provided context with command details
637
+ ctx = context or {}
638
+ ctx.update(
639
+ {
640
+ "command": command,
641
+ "returncode": actual_code,
642
+ "stderr": stderr,
643
+ "stdout": stdout,
644
+ }
645
+ )
646
+
647
+ super().__init__(
648
+ message=f"Command execution failed: {command}",
649
+ code=ErrorCode.COMMAND_FAILED,
650
+ context=ctx,
651
+ )
652
+ # Store returncode for easy access (can't override exit_code which is a property)
653
+ self.returncode = actual_code
654
+ # Store stderr and stdout for easy access
655
+ self.stderr = stderr
656
+ self.stdout = stdout
657
+
658
+
659
+ # ============================================================================
660
+ # Dependency Errors
661
+ # ============================================================================
662
+
663
+
664
+ class DependencyError(SolokitError):
665
+ """
666
+ Raised for dependency-related errors.
667
+
668
+ Example:
669
+ >>> raise DependencyError(
670
+ ... message="Dependency not met",
671
+ ... code=ErrorCode.UNMET_DEPENDENCY,
672
+ ... context={"dependency": "feature_a"}
673
+ ... )
674
+ """
675
+
676
+ def __init__(
677
+ self,
678
+ message: str,
679
+ code: ErrorCode,
680
+ context: dict[str, Any] | None = None,
681
+ remediation: str | None = None,
682
+ cause: Exception | None = None,
683
+ ):
684
+ super().__init__(
685
+ message=message,
686
+ code=code,
687
+ category=ErrorCategory.DEPENDENCY,
688
+ context=context,
689
+ remediation=remediation,
690
+ cause=cause,
691
+ )
692
+
693
+
694
+ class CircularDependencyError(DependencyError):
695
+ """
696
+ Raised when circular dependency detected.
697
+
698
+ Example:
699
+ >>> raise CircularDependencyError(["feature_a", "feature_b", "feature_a"])
700
+ """
701
+
702
+ def __init__(self, cycle: list[str]):
703
+ cycle_str = " -> ".join(cycle)
704
+ super().__init__(
705
+ message=f"Circular dependency detected: {cycle_str}",
706
+ code=ErrorCode.CIRCULAR_DEPENDENCY,
707
+ context={"cycle": cycle},
708
+ remediation="Break the dependency cycle by reordering work items",
709
+ )
710
+
711
+
712
+ class UnmetDependencyError(DependencyError):
713
+ """
714
+ Raised when dependency not met.
715
+
716
+ Example:
717
+ >>> raise UnmetDependencyError("feature_b", "feature_a")
718
+ """
719
+
720
+ def __init__(self, work_item_id: str, dependency_id: str):
721
+ super().__init__(
722
+ message=f"Cannot start '{work_item_id}': dependency '{dependency_id}' not completed",
723
+ code=ErrorCode.UNMET_DEPENDENCY,
724
+ context={"work_item_id": work_item_id, "dependency_id": dependency_id},
725
+ remediation=f"Complete '{dependency_id}' before starting '{work_item_id}'",
726
+ )
727
+
728
+
729
+ # ============================================================================
730
+ # Already Exists Errors
731
+ # ============================================================================
732
+
733
+
734
+ class AlreadyExistsError(SolokitError):
735
+ """
736
+ Raised when resource already exists.
737
+
738
+ Example:
739
+ >>> raise AlreadyExistsError(
740
+ ... message="Work item already exists",
741
+ ... code=ErrorCode.WORK_ITEM_ALREADY_EXISTS,
742
+ ... context={"work_item_id": "my_feature"}
743
+ ... )
744
+ """
745
+
746
+ def __init__(
747
+ self,
748
+ message: str,
749
+ code: ErrorCode,
750
+ context: dict[str, Any] | None = None,
751
+ remediation: str | None = None,
752
+ cause: Exception | None = None,
753
+ ):
754
+ super().__init__(
755
+ message=message,
756
+ code=code,
757
+ category=ErrorCategory.ALREADY_EXISTS,
758
+ context=context,
759
+ remediation=remediation,
760
+ cause=cause,
761
+ )
762
+
763
+
764
+ class SessionAlreadyActiveError(AlreadyExistsError):
765
+ """
766
+ Raised when trying to start session while one is active.
767
+
768
+ Example:
769
+ >>> raise SessionAlreadyActiveError("current_feature")
770
+ """
771
+
772
+ def __init__(self, current_work_item_id: str):
773
+ super().__init__(
774
+ message=f"Session already active for '{current_work_item_id}'",
775
+ code=ErrorCode.SESSION_ALREADY_ACTIVE,
776
+ context={"current_work_item_id": current_work_item_id},
777
+ remediation="Complete current session with 'sk end' before starting a new one",
778
+ )
779
+
780
+
781
+ class WorkItemAlreadyExistsError(AlreadyExistsError):
782
+ """
783
+ Raised when trying to create work item that already exists.
784
+
785
+ Example:
786
+ >>> raise WorkItemAlreadyExistsError("my_feature")
787
+ """
788
+
789
+ def __init__(self, work_item_id: str):
790
+ super().__init__(
791
+ message=f"Work item '{work_item_id}' already exists",
792
+ code=ErrorCode.WORK_ITEM_ALREADY_EXISTS,
793
+ context={"work_item_id": work_item_id},
794
+ remediation=f"Use 'sk work-show {work_item_id}' to view existing work item",
795
+ )
796
+
797
+
798
+ # ============================================================================
799
+ # Quality Gate Errors
800
+ # ============================================================================
801
+
802
+
803
+ class QualityGateError(SolokitError):
804
+ """
805
+ Raised when quality gate fails.
806
+
807
+ Example:
808
+ >>> raise QualityGateError(
809
+ ... message="Tests failed",
810
+ ... code=ErrorCode.TEST_FAILED,
811
+ ... context={"failed_tests": ["test_foo", "test_bar"]}
812
+ ... )
813
+ """
814
+
815
+ def __init__(
816
+ self,
817
+ message: str,
818
+ code: ErrorCode,
819
+ context: dict[str, Any] | None = None,
820
+ remediation: str | None = None,
821
+ cause: Exception | None = None,
822
+ ):
823
+ super().__init__(
824
+ message=message,
825
+ code=code,
826
+ category=ErrorCategory.VALIDATION,
827
+ context=context,
828
+ remediation=remediation,
829
+ cause=cause,
830
+ )
831
+
832
+
833
+ class QualityTestFailedError(QualityGateError):
834
+ """
835
+ Raised when quality tests fail.
836
+
837
+ Example:
838
+ >>> raise QualityTestFailedError(
839
+ ... failed_count=2,
840
+ ... total_count=10,
841
+ ... details=["test_foo failed", "test_bar failed"]
842
+ ... )
843
+ """
844
+
845
+ def __init__(self, failed_count: int, total_count: int, details: list[str] | None = None):
846
+ message = f"{failed_count} of {total_count} tests failed"
847
+ context = {"failed_count": failed_count, "total_count": total_count, "details": details}
848
+ super().__init__(
849
+ message=message,
850
+ code=ErrorCode.TEST_FAILED,
851
+ context=context,
852
+ remediation="Fix failing tests before completing session",
853
+ )
854
+
855
+
856
+ # ============================================================================
857
+ # File Operation Errors
858
+ # ============================================================================
859
+
860
+
861
+ class FileOperationError(SystemError):
862
+ """
863
+ Raised when file operations fail (read, write, parse, etc.).
864
+
865
+ Example:
866
+ >>> raise FileOperationError(
867
+ ... operation="write",
868
+ ... file_path="/path/to/file.json",
869
+ ... details="Permission denied"
870
+ ... )
871
+ """
872
+
873
+ def __init__(
874
+ self,
875
+ operation: str,
876
+ file_path: str,
877
+ details: str | None = None,
878
+ cause: Exception | None = None,
879
+ ):
880
+ message = f"File {operation} operation failed: {file_path}"
881
+ if details:
882
+ message = f"{message} - {details}"
883
+
884
+ context = {"operation": operation, "file_path": file_path, "details": details}
885
+ super().__init__(
886
+ message=message, code=ErrorCode.FILE_OPERATION_FAILED, context=context, cause=cause
887
+ )
888
+ # Store as instance attributes for easy access
889
+ self.operation = operation
890
+ self.file_path = file_path
891
+ self.details = details
892
+
893
+
894
+ # ============================================================================
895
+ # Learning Errors
896
+ # ============================================================================
897
+
898
+
899
+ class LearningError(ValidationError):
900
+ """
901
+ Raised when learning operations fail (validation, storage, curation).
902
+
903
+ Example:
904
+ >>> raise LearningError(
905
+ ... message="Learning content cannot be empty",
906
+ ... context={"learning_id": "abc123"}
907
+ ... )
908
+ """
909
+
910
+ def __init__(
911
+ self,
912
+ message: str,
913
+ context: dict[str, Any] | None = None,
914
+ remediation: str | None = None,
915
+ cause: Exception | None = None,
916
+ ):
917
+ super().__init__(
918
+ message=message,
919
+ code=ErrorCode.MISSING_REQUIRED_FIELD,
920
+ context=context,
921
+ remediation=remediation or "Check learning content and structure",
922
+ cause=cause,
923
+ )
924
+
925
+
926
+ class LearningNotFoundError(NotFoundError):
927
+ """
928
+ Raised when a learning doesn't exist.
929
+
930
+ Example:
931
+ >>> raise LearningNotFoundError("abc123")
932
+ """
933
+
934
+ def __init__(self, learning_id: str):
935
+ super().__init__(
936
+ message=f"Learning '{learning_id}' not found",
937
+ code=ErrorCode.LEARNING_NOT_FOUND,
938
+ context={"learning_id": learning_id},
939
+ remediation="Use search or list commands to find available learnings",
940
+ )
941
+
942
+
943
+ # ============================================================================
944
+ # Deployment Errors
945
+ # ============================================================================
946
+
947
+
948
+ class DeploymentError(SolokitError):
949
+ """
950
+ Raised when deployment operations fail.
951
+
952
+ Example:
953
+ >>> raise DeploymentError(
954
+ ... message="Deployment failed",
955
+ ... code=ErrorCode.DEPLOYMENT_FAILED,
956
+ ... context={"work_item_id": "deploy-001"}
957
+ ... )
958
+ """
959
+
960
+ def __init__(
961
+ self,
962
+ message: str,
963
+ code: ErrorCode = ErrorCode.DEPLOYMENT_FAILED,
964
+ context: dict[str, Any] | None = None,
965
+ remediation: str | None = None,
966
+ cause: Exception | None = None,
967
+ ):
968
+ super().__init__(
969
+ message=message,
970
+ code=code,
971
+ category=ErrorCategory.SYSTEM,
972
+ context=context,
973
+ remediation=remediation,
974
+ cause=cause,
975
+ )
976
+
977
+
978
+ class PreDeploymentCheckError(DeploymentError):
979
+ """
980
+ Raised when pre-deployment validation checks fail.
981
+
982
+ Example:
983
+ >>> raise PreDeploymentCheckError(
984
+ ... check_name="integration_tests",
985
+ ... details="3 tests failed"
986
+ ... )
987
+ """
988
+
989
+ def __init__(
990
+ self, check_name: str, details: str | None = None, context: dict[str, Any] | None = None
991
+ ):
992
+ message = f"Pre-deployment check '{check_name}' failed"
993
+ if details:
994
+ message = f"{message}: {details}"
995
+
996
+ ctx = context or {}
997
+ ctx.update({"check_name": check_name, "details": details})
998
+
999
+ super().__init__(
1000
+ message=message,
1001
+ code=ErrorCode.PRE_DEPLOYMENT_CHECK_FAILED,
1002
+ context=ctx,
1003
+ remediation=f"Fix {check_name} issues before proceeding with deployment",
1004
+ )
1005
+
1006
+
1007
+ class SmokeTestError(DeploymentError):
1008
+ """
1009
+ Raised when smoke tests fail after deployment.
1010
+
1011
+ Example:
1012
+ >>> raise SmokeTestError(
1013
+ ... test_name="health_check",
1014
+ ... details="Endpoint returned 500"
1015
+ ... )
1016
+ """
1017
+
1018
+ def __init__(
1019
+ self, test_name: str, details: str | None = None, context: dict[str, Any] | None = None
1020
+ ):
1021
+ message = f"Smoke test '{test_name}' failed"
1022
+ if details:
1023
+ message = f"{message}: {details}"
1024
+
1025
+ ctx = context or {}
1026
+ ctx.update({"test_name": test_name, "details": details})
1027
+
1028
+ super().__init__(
1029
+ message=message,
1030
+ code=ErrorCode.SMOKE_TEST_FAILED,
1031
+ context=ctx,
1032
+ remediation="Check deployment logs and verify service health",
1033
+ )
1034
+
1035
+
1036
+ class RollbackError(DeploymentError):
1037
+ """
1038
+ Raised when rollback operation fails.
1039
+
1040
+ Example:
1041
+ >>> raise RollbackError(
1042
+ ... step="restore_database",
1043
+ ... details="Backup file not found"
1044
+ ... )
1045
+ """
1046
+
1047
+ def __init__(
1048
+ self,
1049
+ step: str | None = None,
1050
+ details: str | None = None,
1051
+ context: dict[str, Any] | None = None,
1052
+ ):
1053
+ message = "Rollback failed"
1054
+ if step:
1055
+ message = f"Rollback failed at step '{step}'"
1056
+ if details:
1057
+ message = f"{message}: {details}"
1058
+
1059
+ ctx = context or {}
1060
+ if step:
1061
+ ctx["failed_step"] = step
1062
+ if details:
1063
+ ctx["details"] = details
1064
+
1065
+ super().__init__(
1066
+ message=message,
1067
+ code=ErrorCode.ROLLBACK_FAILED,
1068
+ context=ctx,
1069
+ remediation="Manual intervention may be required to restore system state",
1070
+ )
1071
+
1072
+
1073
+ class DeploymentStepError(DeploymentError):
1074
+ """
1075
+ Raised when a deployment step fails.
1076
+
1077
+ Example:
1078
+ >>> raise DeploymentStepError(
1079
+ ... step_number=2,
1080
+ ... step_description="Build application",
1081
+ ... details="Compilation failed"
1082
+ ... )
1083
+ """
1084
+
1085
+ def __init__(
1086
+ self,
1087
+ step_number: int,
1088
+ step_description: str,
1089
+ details: str | None = None,
1090
+ context: dict[str, Any] | None = None,
1091
+ ):
1092
+ message = f"Deployment step {step_number} failed: {step_description}"
1093
+ if details:
1094
+ message = f"{message} - {details}"
1095
+
1096
+ ctx = context or {}
1097
+ ctx.update(
1098
+ {"step_number": step_number, "step_description": step_description, "details": details}
1099
+ )
1100
+
1101
+ super().__init__(
1102
+ message=message,
1103
+ code=ErrorCode.DEPLOYMENT_STEP_FAILED,
1104
+ context=ctx,
1105
+ remediation="Review deployment logs and fix the failing step",
1106
+ )
1107
+
1108
+
1109
+ # ============================================================================
1110
+ # Integration Test Errors
1111
+ # ============================================================================
1112
+
1113
+
1114
+ class IntegrationTestError(SolokitError):
1115
+ """
1116
+ Base exception for integration test failures.
1117
+
1118
+ Example:
1119
+ >>> raise IntegrationTestError(
1120
+ ... message="Integration test failed",
1121
+ ... context={"test_name": "order_processing"}
1122
+ ... )
1123
+ """
1124
+
1125
+ def __init__(
1126
+ self,
1127
+ message: str,
1128
+ code: ErrorCode = ErrorCode.TEST_FAILED,
1129
+ context: dict[str, Any] | None = None,
1130
+ remediation: str | None = None,
1131
+ cause: Exception | None = None,
1132
+ ):
1133
+ super().__init__(
1134
+ message=message,
1135
+ code=code,
1136
+ category=ErrorCategory.SYSTEM,
1137
+ context=context,
1138
+ remediation=remediation,
1139
+ cause=cause,
1140
+ )
1141
+
1142
+
1143
+ class EnvironmentSetupError(IntegrationTestError):
1144
+ """
1145
+ Raised when integration test environment setup fails.
1146
+
1147
+ Example:
1148
+ >>> raise EnvironmentSetupError(
1149
+ ... component="docker-compose",
1150
+ ... details="Failed to start PostgreSQL service"
1151
+ ... )
1152
+ """
1153
+
1154
+ def __init__(
1155
+ self, component: str, details: str | None = None, context: dict[str, Any] | None = None
1156
+ ):
1157
+ message = f"Environment setup failed: {component}"
1158
+ if details:
1159
+ message = f"{message} - {details}"
1160
+
1161
+ ctx = context or {}
1162
+ ctx.update({"component": component, "details": details})
1163
+
1164
+ super().__init__(
1165
+ message=message,
1166
+ code=ErrorCode.COMMAND_FAILED,
1167
+ context=ctx,
1168
+ remediation="Check Docker/docker-compose installation and service configurations",
1169
+ )
1170
+
1171
+
1172
+ class IntegrationExecutionError(IntegrationTestError):
1173
+ """
1174
+ Raised when test execution fails.
1175
+
1176
+ Example:
1177
+ >>> raise IntegrationExecutionError(
1178
+ ... test_framework="pytest",
1179
+ ... details="3 tests failed"
1180
+ ... )
1181
+ """
1182
+
1183
+ def __init__(
1184
+ self, test_framework: str, details: str | None = None, context: dict[str, Any] | None = None
1185
+ ):
1186
+ message = f"Test execution failed: {test_framework}"
1187
+ if details:
1188
+ message = f"{message} - {details}"
1189
+
1190
+ ctx = context or {}
1191
+ ctx.update({"test_framework": test_framework, "details": details})
1192
+
1193
+ super().__init__(
1194
+ message=message,
1195
+ code=ErrorCode.TEST_FAILED,
1196
+ context=ctx,
1197
+ remediation="Review test output and fix failing tests",
1198
+ )
1199
+
1200
+
1201
+ # ============================================================================
1202
+ # API Validation Errors
1203
+ # ============================================================================
1204
+
1205
+
1206
+ class APIValidationError(ValidationError):
1207
+ """
1208
+ Base exception for API validation errors.
1209
+
1210
+ Example:
1211
+ >>> raise APIValidationError(
1212
+ ... message="API validation failed",
1213
+ ... context={"endpoint": "/api/users"}
1214
+ ... )
1215
+ """
1216
+
1217
+ def __init__(
1218
+ self,
1219
+ message: str,
1220
+ code: ErrorCode = ErrorCode.API_VALIDATION_FAILED,
1221
+ context: dict[str, Any] | None = None,
1222
+ remediation: str | None = None,
1223
+ cause: Exception | None = None,
1224
+ ):
1225
+ super().__init__(
1226
+ message=message, code=code, context=context, remediation=remediation, cause=cause
1227
+ )
1228
+
1229
+
1230
+ class SchemaValidationError(APIValidationError):
1231
+ """
1232
+ Raised when OpenAPI/Swagger schema validation fails.
1233
+
1234
+ Example:
1235
+ >>> raise SchemaValidationError(
1236
+ ... contract_file="api/openapi.yaml",
1237
+ ... details="Missing 'paths' field"
1238
+ ... )
1239
+ """
1240
+
1241
+ def __init__(self, contract_file: str, details: str, context: dict[str, Any] | None = None):
1242
+ message = f"Schema validation failed for '{contract_file}': {details}"
1243
+ ctx = context or {}
1244
+ ctx.update({"contract_file": contract_file, "details": details})
1245
+
1246
+ super().__init__(
1247
+ message=message,
1248
+ code=ErrorCode.SCHEMA_VALIDATION_FAILED,
1249
+ context=ctx,
1250
+ remediation=f"Fix schema validation errors in {contract_file}",
1251
+ )
1252
+
1253
+
1254
+ class ContractViolationError(APIValidationError):
1255
+ """
1256
+ Raised when API contract is violated.
1257
+
1258
+ Example:
1259
+ >>> raise ContractViolationError(
1260
+ ... path="/api/users",
1261
+ ... method="POST",
1262
+ ... violation_type="removed_required_parameter",
1263
+ ... details="Parameter 'email' is required"
1264
+ ... )
1265
+ """
1266
+
1267
+ def __init__(
1268
+ self,
1269
+ path: str,
1270
+ method: str,
1271
+ violation_type: str,
1272
+ details: str,
1273
+ severity: str = "high",
1274
+ context: dict[str, Any] | None = None,
1275
+ ):
1276
+ message = f"Contract violation in {method} {path}: {details}"
1277
+ ctx = context or {}
1278
+ ctx.update(
1279
+ {
1280
+ "path": path,
1281
+ "method": method,
1282
+ "violation_type": violation_type,
1283
+ "details": details,
1284
+ "severity": severity,
1285
+ }
1286
+ )
1287
+
1288
+ super().__init__(
1289
+ message=message,
1290
+ code=ErrorCode.CONTRACT_VIOLATION,
1291
+ context=ctx,
1292
+ remediation="Review API contract changes and update implementation",
1293
+ )
1294
+
1295
+
1296
+ class BreakingChangeError(APIValidationError):
1297
+ """
1298
+ Raised when breaking changes are detected in API contracts.
1299
+
1300
+ Example:
1301
+ >>> raise BreakingChangeError(
1302
+ ... breaking_changes=[
1303
+ ... {"type": "removed_endpoint", "path": "/api/old"},
1304
+ ... {"type": "removed_method", "path": "/api/users", "method": "DELETE"}
1305
+ ... ],
1306
+ ... allow_breaking_changes=False
1307
+ ... )
1308
+ """
1309
+
1310
+ def __init__(
1311
+ self,
1312
+ breaking_changes: list[dict],
1313
+ allow_breaking_changes: bool = False,
1314
+ context: dict[str, Any] | None = None,
1315
+ ):
1316
+ change_count = len(breaking_changes)
1317
+ message = f"{change_count} breaking change{'s' if change_count != 1 else ''} detected"
1318
+ if not allow_breaking_changes:
1319
+ message = f"{message} (not allowed)"
1320
+
1321
+ ctx = context or {}
1322
+ ctx.update(
1323
+ {
1324
+ "breaking_changes": breaking_changes,
1325
+ "breaking_change_count": change_count,
1326
+ "allow_breaking_changes": allow_breaking_changes,
1327
+ }
1328
+ )
1329
+
1330
+ super().__init__(
1331
+ message=message,
1332
+ code=ErrorCode.BREAKING_CHANGE_DETECTED,
1333
+ context=ctx,
1334
+ remediation=(
1335
+ "Review breaking changes and either: "
1336
+ "1) Fix them to maintain backward compatibility, or "
1337
+ "2) Set 'allow_breaking_changes: true' if intentional"
1338
+ ),
1339
+ )
1340
+
1341
+
1342
+ class InvalidOpenAPISpecError(APIValidationError):
1343
+ """
1344
+ Raised when OpenAPI/Swagger specification is invalid.
1345
+
1346
+ Example:
1347
+ >>> raise InvalidOpenAPISpecError(
1348
+ ... contract_file="api/openapi.yaml",
1349
+ ... details="Not a valid OpenAPI/Swagger specification"
1350
+ ... )
1351
+ """
1352
+
1353
+ def __init__(self, contract_file: str, details: str, context: dict[str, Any] | None = None):
1354
+ message = f"Invalid OpenAPI/Swagger spec: {contract_file}"
1355
+ ctx = context or {}
1356
+ ctx.update({"contract_file": contract_file, "details": details})
1357
+
1358
+ super().__init__(
1359
+ message=message,
1360
+ code=ErrorCode.INVALID_OPENAPI_SPEC,
1361
+ context=ctx,
1362
+ remediation=f"Ensure {contract_file} is a valid OpenAPI/Swagger specification with 'openapi' or 'swagger' field",
1363
+ )
1364
+
1365
+
1366
+ # ============================================================================
1367
+ # Performance Testing Errors
1368
+ # ============================================================================
1369
+
1370
+
1371
+ class PerformanceTestError(SolokitError):
1372
+ """
1373
+ Base class for performance testing errors.
1374
+
1375
+ Example:
1376
+ >>> raise PerformanceTestError(
1377
+ ... message="Performance test failed",
1378
+ ... code=ErrorCode.PERFORMANCE_TEST_FAILED,
1379
+ ... context={"work_item_id": "perf-001"}
1380
+ ... )
1381
+ """
1382
+
1383
+ def __init__(
1384
+ self,
1385
+ message: str,
1386
+ code: ErrorCode = ErrorCode.PERFORMANCE_TEST_FAILED,
1387
+ context: dict[str, Any] | None = None,
1388
+ remediation: str | None = None,
1389
+ cause: Exception | None = None,
1390
+ ):
1391
+ super().__init__(
1392
+ message=message,
1393
+ code=code,
1394
+ category=ErrorCategory.VALIDATION,
1395
+ context=context,
1396
+ remediation=remediation,
1397
+ cause=cause,
1398
+ )
1399
+
1400
+
1401
+ class BenchmarkFailedError(PerformanceTestError):
1402
+ """
1403
+ Raised when a performance benchmark fails to meet requirements.
1404
+
1405
+ Example:
1406
+ >>> raise BenchmarkFailedError(
1407
+ ... metric="p95_latency",
1408
+ ... actual=150.5,
1409
+ ... expected=100.0,
1410
+ ... unit="ms"
1411
+ ... )
1412
+ """
1413
+
1414
+ def __init__(self, metric: str, actual: float, expected: float, unit: str = "ms"):
1415
+ message = f"Benchmark failed: {metric} {actual}{unit} exceeds requirement {expected}{unit}"
1416
+ context = {
1417
+ "metric": metric,
1418
+ "actual_value": actual,
1419
+ "expected_value": expected,
1420
+ "unit": unit,
1421
+ "delta": actual - expected,
1422
+ "percentage_over": ((actual / expected - 1) * 100) if expected > 0 else 0,
1423
+ }
1424
+ super().__init__(
1425
+ message=message,
1426
+ code=ErrorCode.BENCHMARK_FAILED,
1427
+ context=context,
1428
+ remediation=f"Optimize performance to meet {metric} requirement of {expected}{unit}",
1429
+ )
1430
+
1431
+
1432
+ class PerformanceRegressionError(PerformanceTestError):
1433
+ """
1434
+ Raised when performance regression is detected against baseline.
1435
+
1436
+ Example:
1437
+ >>> raise PerformanceRegressionError(
1438
+ ... metric="p50_latency",
1439
+ ... current=55.0,
1440
+ ... baseline=50.0,
1441
+ ... threshold_percent=10.0
1442
+ ... )
1443
+ """
1444
+
1445
+ def __init__(
1446
+ self, metric: str, current: float, baseline: float, threshold_percent: float = 10.0
1447
+ ):
1448
+ regression_percent = ((current / baseline - 1) * 100) if baseline > 0 else 0
1449
+ message = (
1450
+ f"Performance regression detected: {metric} increased from "
1451
+ f"{baseline}ms to {current}ms ({regression_percent:.1f}% slower, "
1452
+ f"threshold: {threshold_percent}%)"
1453
+ )
1454
+ context = {
1455
+ "metric": metric,
1456
+ "current_value": current,
1457
+ "baseline_value": baseline,
1458
+ "regression_percent": regression_percent,
1459
+ "threshold_percent": threshold_percent,
1460
+ }
1461
+ super().__init__(
1462
+ message=message,
1463
+ code=ErrorCode.PERFORMANCE_REGRESSION,
1464
+ context=context,
1465
+ remediation="Investigate recent changes that may have caused performance degradation",
1466
+ )
1467
+
1468
+
1469
+ class LoadTestFailedError(PerformanceTestError):
1470
+ """
1471
+ Raised when load test execution fails.
1472
+
1473
+ Example:
1474
+ >>> raise LoadTestFailedError(
1475
+ ... endpoint="http://localhost:8000/api",
1476
+ ... details="Connection refused"
1477
+ ... )
1478
+ """
1479
+
1480
+ def __init__(
1481
+ self, endpoint: str, details: str | None = None, context: dict[str, Any] | None = None
1482
+ ):
1483
+ message = f"Load test failed for endpoint: {endpoint}"
1484
+ if details:
1485
+ message = f"{message} - {details}"
1486
+
1487
+ ctx = context or {}
1488
+ ctx.update({"endpoint": endpoint, "details": details})
1489
+
1490
+ super().__init__(
1491
+ message=message,
1492
+ code=ErrorCode.LOAD_TEST_FAILED,
1493
+ context=ctx,
1494
+ remediation="Verify the endpoint is accessible and the service is running",
1495
+ )
1496
+
1497
+
1498
+ # ============================================================================
1499
+ # Project Initialization Errors
1500
+ # ============================================================================
1501
+
1502
+
1503
+ class ProjectInitializationError(SolokitError):
1504
+ """
1505
+ Base exception for project initialization errors.
1506
+
1507
+ Example:
1508
+ >>> raise ProjectInitializationError(
1509
+ ... message="Project initialization failed",
1510
+ ... context={"reason": "Missing template files"}
1511
+ ... )
1512
+ """
1513
+
1514
+ def __init__(
1515
+ self,
1516
+ message: str,
1517
+ code: ErrorCode = ErrorCode.FILE_OPERATION_FAILED,
1518
+ context: dict[str, Any] | None = None,
1519
+ remediation: str | None = None,
1520
+ cause: Exception | None = None,
1521
+ ):
1522
+ super().__init__(
1523
+ message=message,
1524
+ code=code,
1525
+ category=ErrorCategory.SYSTEM,
1526
+ context=context,
1527
+ remediation=remediation,
1528
+ cause=cause,
1529
+ )
1530
+
1531
+
1532
+ class DirectoryNotEmptyError(AlreadyExistsError):
1533
+ """
1534
+ Raised when attempting to initialize in a directory that already has Solokit structure.
1535
+
1536
+ Example:
1537
+ >>> raise DirectoryNotEmptyError(".session")
1538
+ """
1539
+
1540
+ def __init__(self, directory: str):
1541
+ super().__init__(
1542
+ message=f"Directory '{directory}' already exists",
1543
+ code=ErrorCode.FILE_ALREADY_EXISTS,
1544
+ context={"directory": directory},
1545
+ remediation=f"Remove '{directory}' directory or run initialization in a different location",
1546
+ )
1547
+
1548
+
1549
+ class TemplateNotFoundError(FileNotFoundError):
1550
+ """
1551
+ Raised when a required template file is not found.
1552
+
1553
+ Example:
1554
+ >>> raise TemplateNotFoundError(
1555
+ ... template_name="package.json.template",
1556
+ ... template_path="/path/to/templates"
1557
+ ... )
1558
+ """
1559
+
1560
+ def __init__(self, template_name: str, template_path: str):
1561
+ super().__init__(file_path=f"{template_path}/{template_name}", file_type="template")
1562
+ self.context["template_name"] = template_name
1563
+ self.template_name = template_name
1564
+ self.template_path = template_path
1565
+ self.remediation = (
1566
+ f"Ensure Solokit is properly installed and template file exists: {template_name}"
1567
+ )