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,309 @@
1
+ """File operations utilities
2
+
3
+ Centralized file I/O operations with consistent error handling and atomicity guarantees.
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Optional
11
+
12
+ from solokit.core.exceptions import (
13
+ ErrorCode,
14
+ FileOperationError,
15
+ SystemError,
16
+ )
17
+ from solokit.core.exceptions import (
18
+ FileNotFoundError as SolokitFileNotFoundError,
19
+ )
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class JSONFileOperations:
25
+ """Centralized JSON file I/O with atomic writes and error handling"""
26
+
27
+ @staticmethod
28
+ def load_json(
29
+ file_path: Path,
30
+ default: Optional[dict[str, Any]] = None,
31
+ validator: Optional[Callable[[dict], bool]] = None,
32
+ ) -> dict[str, Any]:
33
+ """
34
+ Load JSON file with optional default and validation
35
+
36
+ Args:
37
+ file_path: Path to JSON file
38
+ default: Default value if file doesn't exist (None raises error)
39
+ validator: Optional validation function that returns True if data is valid
40
+
41
+ Returns:
42
+ Loaded JSON data as dict
43
+
44
+ Raises:
45
+ FileOperationError: If file not found and no default provided
46
+ FileOperationError: If JSON is invalid
47
+ FileOperationError: If validation fails
48
+
49
+ Examples:
50
+ >>> # Load required file (raises if missing)
51
+ >>> data = JSONFileOperations.load_json(Path("config.json"))
52
+ >>> # Load with default
53
+ >>> data = JSONFileOperations.load_json(Path("optional.json"), default={})
54
+ >>> # Load with validation
55
+ >>> validator = lambda d: "version" in d
56
+ >>> data = JSONFileOperations.load_json(Path("config.json"), validator=validator)
57
+ """
58
+ if not file_path.exists():
59
+ if default is not None:
60
+ logger.debug(f"File not found, using default: {file_path}")
61
+ return default
62
+ raise FileOperationError(
63
+ operation="read",
64
+ file_path=str(file_path),
65
+ details="File does not exist",
66
+ )
67
+
68
+ try:
69
+ with open(file_path, encoding="utf-8") as f:
70
+ data: dict[str, Any] = json.load(f)
71
+ except json.JSONDecodeError as e:
72
+ raise FileOperationError(
73
+ operation="parse",
74
+ file_path=str(file_path),
75
+ details=f"Invalid JSON: {e}",
76
+ cause=e,
77
+ ) from e
78
+ except OSError as e:
79
+ raise FileOperationError(
80
+ operation="read",
81
+ file_path=str(file_path),
82
+ details=str(e),
83
+ cause=e,
84
+ ) from e
85
+ except Exception as e:
86
+ raise SystemError(
87
+ message=f"Unexpected error reading {file_path}",
88
+ code=ErrorCode.FILE_OPERATION_FAILED,
89
+ context={"file_path": str(file_path), "operation": "read"},
90
+ cause=e,
91
+ ) from e
92
+
93
+ if validator and not validator(data):
94
+ raise FileOperationError(
95
+ operation="validate",
96
+ file_path=str(file_path),
97
+ details="Validation function returned False",
98
+ )
99
+
100
+ logger.debug(f"Loaded JSON from {file_path}")
101
+ return data
102
+
103
+ @staticmethod
104
+ def save_json(
105
+ file_path: Path,
106
+ data: dict[str, Any],
107
+ indent: int = 2,
108
+ atomic: bool = True,
109
+ create_dirs: bool = True,
110
+ ) -> None:
111
+ """
112
+ Save data to JSON file with atomic write option
113
+
114
+ Args:
115
+ file_path: Path to JSON file
116
+ data: Data to save
117
+ indent: JSON indentation (default 2)
118
+ atomic: Use atomic write via temp file (default True)
119
+ create_dirs: Create parent directories if needed (default True)
120
+
121
+ Raises:
122
+ FileOperationError: If save fails
123
+
124
+ Examples:
125
+ >>> # Save with atomic write (default)
126
+ >>> JSONFileOperations.save_json(Path("data.json"), {"key": "value"})
127
+ >>> # Save without atomic write
128
+ >>> JSONFileOperations.save_json(Path("data.json"), data, atomic=False)
129
+ >>> # Save with custom indent
130
+ >>> JSONFileOperations.save_json(Path("data.json"), data, indent=4)
131
+ """
132
+ try:
133
+ if create_dirs:
134
+ file_path.parent.mkdir(parents=True, exist_ok=True)
135
+
136
+ if atomic:
137
+ # Atomic write via temp file
138
+ temp_path = file_path.with_suffix(file_path.suffix + ".tmp")
139
+ with open(temp_path, "w", encoding="utf-8") as f:
140
+ json.dump(data, f, indent=indent, default=str)
141
+ temp_path.replace(file_path)
142
+ else:
143
+ # Direct write
144
+ with open(file_path, "w", encoding="utf-8") as f:
145
+ json.dump(data, f, indent=indent, default=str)
146
+
147
+ logger.debug(f"Saved JSON to {file_path}")
148
+
149
+ except OSError as e:
150
+ raise FileOperationError(
151
+ operation="write",
152
+ file_path=str(file_path),
153
+ details=str(e),
154
+ cause=e,
155
+ ) from e
156
+ except Exception as e:
157
+ raise SystemError(
158
+ message=f"Unexpected error saving to {file_path}",
159
+ code=ErrorCode.FILE_OPERATION_FAILED,
160
+ context={
161
+ "file_path": str(file_path),
162
+ "operation": "write",
163
+ "atomic": atomic,
164
+ },
165
+ cause=e,
166
+ ) from e
167
+
168
+ @staticmethod
169
+ def load_json_safe(file_path: Path, default: dict[str, Any]) -> dict[str, Any]:
170
+ """
171
+ Load JSON with guaranteed return (never raises)
172
+
173
+ Logs errors but always returns a value.
174
+ Convenience method for cases where failures should be silent.
175
+
176
+ Args:
177
+ file_path: Path to JSON file
178
+ default: Default value to return if load fails
179
+
180
+ Returns:
181
+ Loaded JSON data or default value
182
+
183
+ Examples:
184
+ >>> # Always returns a dict, never raises
185
+ >>> data = JSONFileOperations.load_json_safe(Path("config.json"), {})
186
+ """
187
+ try:
188
+ return JSONFileOperations.load_json(file_path)
189
+ except FileOperationError as e:
190
+ logger.warning(f"Using default for {file_path}: {e}")
191
+ return default
192
+
193
+
194
+ # Convenience functions for backward compatibility
195
+ def load_json(file_path: Path) -> dict[str, Any]:
196
+ """Load JSON file
197
+
198
+ Backward compatibility wrapper. Raises FileOperationError if file not found.
199
+
200
+ Args:
201
+ file_path: Path to JSON file
202
+
203
+ Returns:
204
+ Loaded JSON data
205
+
206
+ Raises:
207
+ FileOperationError: If file not found or JSON is invalid
208
+ """
209
+ return JSONFileOperations.load_json(file_path)
210
+
211
+
212
+ def save_json(file_path: Path, data: dict[str, Any], indent: int = 2) -> None:
213
+ """Save data to JSON file with atomic write
214
+
215
+ Backward compatibility wrapper.
216
+
217
+ Args:
218
+ file_path: Path to JSON file
219
+ data: Data to save
220
+ indent: JSON indentation (default 2)
221
+
222
+ Raises:
223
+ FileOperationError: If save fails
224
+ """
225
+ JSONFileOperations.save_json(file_path, data, indent=indent)
226
+
227
+
228
+ def ensure_directory(path: Path) -> None:
229
+ """Ensure directory exists"""
230
+ path.mkdir(parents=True, exist_ok=True)
231
+
232
+
233
+ def backup_file(file_path: Path) -> Path:
234
+ """Create backup of a file
235
+
236
+ Args:
237
+ file_path: Path to file to backup
238
+
239
+ Returns:
240
+ Path to backup file
241
+
242
+ Raises:
243
+ SolokitFileNotFoundError: If source file doesn't exist
244
+ FileOperationError: If backup operation fails
245
+ """
246
+ if not file_path.exists():
247
+ raise SolokitFileNotFoundError(file_path=str(file_path), file_type="backup source")
248
+
249
+ try:
250
+ backup_path = file_path.with_suffix(f"{file_path.suffix}.backup")
251
+ shutil.copy2(file_path, backup_path)
252
+ return backup_path
253
+ except OSError as e:
254
+ raise FileOperationError(
255
+ operation="backup",
256
+ file_path=str(file_path),
257
+ details=str(e),
258
+ cause=e,
259
+ ) from e
260
+
261
+
262
+ def read_file(file_path: Path) -> str:
263
+ """Read file contents
264
+
265
+ Args:
266
+ file_path: Path to file to read
267
+
268
+ Returns:
269
+ File contents as string
270
+
271
+ Raises:
272
+ SolokitFileNotFoundError: If file doesn't exist
273
+ FileOperationError: If read operation fails
274
+ """
275
+ if not file_path.exists():
276
+ raise SolokitFileNotFoundError(file_path=str(file_path), file_type="text file")
277
+
278
+ try:
279
+ with open(file_path) as f:
280
+ return f.read()
281
+ except OSError as e:
282
+ raise FileOperationError(
283
+ operation="read",
284
+ file_path=str(file_path),
285
+ details=str(e),
286
+ cause=e,
287
+ ) from e
288
+
289
+
290
+ def write_file(file_path: Path, content: str) -> None:
291
+ """Write content to file
292
+
293
+ Args:
294
+ file_path: Path to file to write
295
+ content: Content to write
296
+
297
+ Raises:
298
+ FileOperationError: If write operation fails
299
+ """
300
+ try:
301
+ with open(file_path, "w") as f:
302
+ f.write(content)
303
+ except OSError as e:
304
+ raise FileOperationError(
305
+ operation="write",
306
+ file_path=str(file_path),
307
+ details=str(e),
308
+ cause=e,
309
+ ) from e
@@ -0,0 +1,166 @@
1
+ """Centralized logging configuration for Solokit."""
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ from collections.abc import Callable
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Any, Optional
10
+
11
+
12
+ class StructuredFormatter(logging.Formatter):
13
+ """Format logs as structured JSON."""
14
+
15
+ def format(self, record: logging.LogRecord) -> str:
16
+ """
17
+ Format log record as JSON.
18
+
19
+ Args:
20
+ record: Log record to format
21
+
22
+ Returns:
23
+ JSON-formatted log string
24
+ """
25
+ log_data = {
26
+ "timestamp": datetime.utcnow().isoformat(),
27
+ "level": record.levelname,
28
+ "logger": record.name,
29
+ "message": record.getMessage(),
30
+ "module": record.module,
31
+ "function": record.funcName,
32
+ "line": record.lineno,
33
+ }
34
+
35
+ # Add extra fields if present
36
+ if hasattr(record, "context"):
37
+ log_data["context"] = record.context
38
+
39
+ # Add exception info if present
40
+ if record.exc_info:
41
+ log_data["exception"] = self.formatException(record.exc_info)
42
+
43
+ return json.dumps(log_data)
44
+
45
+
46
+ class HumanReadableFormatter(logging.Formatter):
47
+ """Format logs for human reading."""
48
+
49
+ def format(self, record: logging.LogRecord) -> str:
50
+ """
51
+ Format log record for console.
52
+
53
+ Args:
54
+ record: Log record to format
55
+
56
+ Returns:
57
+ Human-readable log string
58
+ """
59
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
60
+ level = record.levelname
61
+ name = record.name
62
+ message = record.getMessage()
63
+
64
+ # Color code by level
65
+ level_colors = {
66
+ "DEBUG": "\033[36m", # Cyan
67
+ "INFO": "\033[32m", # Green
68
+ "WARNING": "\033[33m", # Yellow
69
+ "ERROR": "\033[31m", # Red
70
+ "CRITICAL": "\033[35m", # Magenta
71
+ }
72
+ color = level_colors.get(level, "")
73
+ reset = "\033[0m"
74
+
75
+ formatted = f"{timestamp} {color}{level:8}{reset} {name} - {message}"
76
+
77
+ # Add exception if present
78
+ if record.exc_info:
79
+ formatted += "\n" + self.formatException(record.exc_info)
80
+
81
+ return formatted
82
+
83
+
84
+ def setup_logging(
85
+ level: str = "INFO",
86
+ log_file: Optional[Path] = None,
87
+ structured: bool = False,
88
+ ) -> None:
89
+ """
90
+ Configure logging for the application.
91
+
92
+ Args:
93
+ level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
94
+ log_file: Optional file path for log output
95
+ structured: Use structured JSON logging (default: False)
96
+ """
97
+ root_logger = logging.getLogger("solokit")
98
+ root_logger.setLevel(getattr(logging, level.upper()))
99
+
100
+ # Remove existing handlers
101
+ root_logger.handlers.clear()
102
+
103
+ # Console handler
104
+ console_handler = logging.StreamHandler(sys.stderr)
105
+ if structured:
106
+ console_handler.setFormatter(StructuredFormatter())
107
+ else:
108
+ console_handler.setFormatter(HumanReadableFormatter())
109
+ root_logger.addHandler(console_handler)
110
+
111
+ # File handler if specified
112
+ if log_file:
113
+ log_file.parent.mkdir(parents=True, exist_ok=True)
114
+ file_handler = logging.FileHandler(log_file)
115
+ if structured:
116
+ file_handler.setFormatter(StructuredFormatter())
117
+ else:
118
+ file_handler.setFormatter(HumanReadableFormatter())
119
+ root_logger.addHandler(file_handler)
120
+
121
+
122
+ def get_logger(name: str) -> logging.Logger:
123
+ """
124
+ Get a logger for a module.
125
+
126
+ Args:
127
+ name: Logger name (usually __name__)
128
+
129
+ Returns:
130
+ Configured logger instance
131
+ """
132
+ return logging.getLogger(f"solokit.{name}")
133
+
134
+
135
+ class LogContext:
136
+ """Context manager for adding context to logs."""
137
+
138
+ def __init__(self, logger: logging.Logger, **context: Any):
139
+ """
140
+ Initialize with logger and context data.
141
+
142
+ Args:
143
+ logger: Logger instance to add context to
144
+ **context: Key-value pairs to add as context
145
+ """
146
+ self.logger = logger
147
+ self.context = context
148
+ self.old_factory: Optional[Callable[..., logging.LogRecord]] = None
149
+
150
+ def __enter__(self) -> "LogContext":
151
+ """Add context to log records."""
152
+ old_factory = logging.getLogRecordFactory()
153
+
154
+ def record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
155
+ record = old_factory(*args, **kwargs)
156
+ record.context = self.context
157
+ return record
158
+
159
+ logging.setLogRecordFactory(record_factory)
160
+ self.old_factory = old_factory
161
+ return self
162
+
163
+ def __exit__(self, *args: Any) -> None:
164
+ """Restore original factory."""
165
+ if self.old_factory is not None:
166
+ logging.setLogRecordFactory(self.old_factory)
solokit/core/output.py ADDED
@@ -0,0 +1,99 @@
1
+ """User output handler separate from diagnostic logging."""
2
+
3
+ import sys
4
+
5
+
6
+ class OutputHandler:
7
+ """Handle user-facing output separate from logs."""
8
+
9
+ def __init__(self, quiet: bool = False):
10
+ """
11
+ Initialize output handler.
12
+
13
+ Args:
14
+ quiet: Suppress all non-error output
15
+ """
16
+ self.quiet = quiet
17
+
18
+ def info(self, message: str) -> None:
19
+ """
20
+ Display info message to user.
21
+
22
+ Args:
23
+ message: Message to display
24
+ """
25
+ if not self.quiet:
26
+ print(message)
27
+
28
+ def success(self, message: str) -> None:
29
+ """
30
+ Display success message to user.
31
+
32
+ Args:
33
+ message: Success message to display
34
+ """
35
+ if not self.quiet:
36
+ print(f"✅ {message}")
37
+
38
+ def warning(self, message: str) -> None:
39
+ """
40
+ Display warning message to user.
41
+
42
+ Args:
43
+ message: Warning message to display
44
+ """
45
+ if not self.quiet:
46
+ print(f"⚠️ {message}")
47
+
48
+ def error(self, message: str) -> None:
49
+ """
50
+ Display error message to user.
51
+
52
+ Args:
53
+ message: Error message to display
54
+ """
55
+ print(f"❌ {message}", file=sys.stderr)
56
+
57
+ def progress(self, message: str) -> None:
58
+ """
59
+ Display progress message to user.
60
+
61
+ Args:
62
+ message: Progress message to display
63
+ """
64
+ if not self.quiet:
65
+ print(f"⏳ {message}")
66
+
67
+ def section(self, title: str) -> None:
68
+ """
69
+ Display section header.
70
+
71
+ Args:
72
+ title: Section title
73
+ """
74
+ if not self.quiet:
75
+ print(f"\n=== {title} ===\n")
76
+
77
+
78
+ # Global output handler
79
+ _output_handler = OutputHandler()
80
+
81
+
82
+ def get_output() -> OutputHandler:
83
+ """
84
+ Get the global output handler.
85
+
86
+ Returns:
87
+ Global OutputHandler instance
88
+ """
89
+ return _output_handler
90
+
91
+
92
+ def set_quiet(quiet: bool) -> None:
93
+ """
94
+ Set quiet mode for output handler.
95
+
96
+ Args:
97
+ quiet: True to suppress output, False to enable
98
+ """
99
+ _output_handler.quiet = quiet
@@ -0,0 +1,57 @@
1
+ """Performance monitoring and profiling"""
2
+
3
+ import time
4
+ from functools import wraps
5
+ from typing import Any, Callable, Optional, TypeVar, cast
6
+
7
+ from solokit.core.logging_config import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ F = TypeVar("F", bound=Callable[..., Any])
12
+
13
+
14
+ def measure_time(operation: Optional[str] = None) -> Callable[[F], F]:
15
+ """Decorator to measure function execution time"""
16
+
17
+ def decorator(func: F) -> F:
18
+ op_name = operation or f"{func.__module__}.{func.__name__}"
19
+
20
+ @wraps(func)
21
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
22
+ start = time.time()
23
+ try:
24
+ result = func(*args, **kwargs)
25
+ return result
26
+ finally:
27
+ duration = time.time() - start
28
+ if duration > 1.0: # Warn if > 1s
29
+ logger.warning(f"Slow operation: {op_name} took {duration:.3f}s")
30
+ elif duration > 0.1: # Log if > 100ms
31
+ logger.info(f"Performance: {op_name} took {duration:.3f}s")
32
+
33
+ return cast(F, wrapper)
34
+
35
+ return decorator
36
+
37
+
38
+ class Timer:
39
+ """Context manager for timing code blocks"""
40
+
41
+ def __init__(self, name: str):
42
+ """Initialize timer with name"""
43
+ self.name = name
44
+ self.start: Optional[float] = None
45
+ self.duration: Optional[float] = None
46
+
47
+ def __enter__(self) -> "Timer":
48
+ """Start timer"""
49
+ self.start = time.time()
50
+ return self
51
+
52
+ def __exit__(self, *args: Any) -> None:
53
+ """End timer and log"""
54
+ if self.start is not None:
55
+ self.duration = time.time() - self.start
56
+ if self.duration > 0.1:
57
+ logger.info(f"Performance: {self.name} took {self.duration:.3f}s")