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,424 @@
1
+ """
2
+ Template Installer Module
3
+
4
+ Handles template file installation with tier-based structure.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import re
12
+ import shutil
13
+ from pathlib import Path
14
+ from typing import Any, cast
15
+
16
+ from solokit.core.exceptions import FileOperationError, TemplateNotFoundError
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def load_template_registry() -> dict[str, Any]:
22
+ """
23
+ Load template registry from templates/template-registry.json.
24
+
25
+ Returns:
26
+ Template registry dictionary
27
+
28
+ Raises:
29
+ TemplateNotFoundError: If registry file not found
30
+ FileOperationError: If registry file is invalid JSON
31
+ """
32
+ registry_path = Path(__file__).parent.parent / "templates" / "template-registry.json"
33
+
34
+ if not registry_path.exists():
35
+ raise TemplateNotFoundError(
36
+ template_name="template-registry.json",
37
+ template_path=str(registry_path.parent),
38
+ )
39
+
40
+ try:
41
+ with open(registry_path) as f:
42
+ return cast(dict[str, Any], json.load(f))
43
+ except json.JSONDecodeError as e:
44
+ raise FileOperationError(
45
+ operation="parse",
46
+ file_path=str(registry_path),
47
+ details=f"Invalid JSON in template registry: {str(e)}",
48
+ cause=e,
49
+ )
50
+
51
+
52
+ def get_template_info(template_id: str) -> dict[str, Any]:
53
+ """
54
+ Get template information from registry.
55
+
56
+ Args:
57
+ template_id: Template identifier (e.g., "saas_t3")
58
+
59
+ Returns:
60
+ Template metadata dictionary
61
+
62
+ Raises:
63
+ TemplateNotFoundError: If template not found in registry
64
+ """
65
+ registry = load_template_registry()
66
+
67
+ if template_id not in registry["templates"]:
68
+ available = ", ".join(registry["templates"].keys())
69
+ raise TemplateNotFoundError(
70
+ template_name=template_id,
71
+ template_path=f"Available templates: {available}",
72
+ )
73
+
74
+ return cast(dict[str, Any], registry["templates"][template_id])
75
+
76
+
77
+ def get_template_directory(template_id: str) -> Path:
78
+ """
79
+ Get path to template directory.
80
+
81
+ Args:
82
+ template_id: Template identifier (e.g., "saas_t3")
83
+
84
+ Returns:
85
+ Path to template directory
86
+
87
+ Raises:
88
+ TemplateNotFoundError: If template directory doesn't exist
89
+ """
90
+ templates_root = Path(__file__).parent.parent / "templates"
91
+ template_dir = templates_root / template_id
92
+
93
+ if not template_dir.exists():
94
+ raise TemplateNotFoundError(template_name=template_id, template_path=str(templates_root))
95
+
96
+ return template_dir
97
+
98
+
99
+ def copy_directory_tree(src: Path, dst: Path, skip_patterns: list[str] | None = None) -> int:
100
+ """
101
+ Recursively copy directory tree from src to dst.
102
+
103
+ Args:
104
+ src: Source directory
105
+ dst: Destination directory
106
+ skip_patterns: List of filename patterns to skip (e.g., [".template", "__pycache__"])
107
+
108
+ Returns:
109
+ Number of files copied
110
+
111
+ Raises:
112
+ FileOperationError: If copy operation fails
113
+ """
114
+ if skip_patterns is None:
115
+ skip_patterns = []
116
+
117
+ files_copied = 0
118
+
119
+ try:
120
+ # Create destination directory
121
+ dst.mkdir(parents=True, exist_ok=True)
122
+
123
+ # Copy all files and subdirectories
124
+ for item in src.iterdir():
125
+ # Skip patterns
126
+ if any(pattern in item.name for pattern in skip_patterns):
127
+ continue
128
+
129
+ src_item = src / item.name
130
+ dst_item = dst / item.name
131
+
132
+ if src_item.is_dir():
133
+ files_copied += copy_directory_tree(src_item, dst_item, skip_patterns)
134
+ else:
135
+ shutil.copy2(src_item, dst_item)
136
+ files_copied += 1
137
+ logger.debug(f"Copied {src_item} -> {dst_item}")
138
+
139
+ except Exception as e:
140
+ raise FileOperationError(
141
+ operation="copy",
142
+ file_path=str(src),
143
+ details=f"Failed to copy directory tree: {str(e)}",
144
+ cause=e,
145
+ )
146
+
147
+ return files_copied
148
+
149
+
150
+ def replace_placeholders(content: str, replacements: dict[str, str]) -> str:
151
+ """
152
+ Replace placeholders in template content.
153
+
154
+ Args:
155
+ content: Template content with placeholders like {project_name}
156
+ replacements: Dictionary of placeholder -> value
157
+
158
+ Returns:
159
+ Content with placeholders replaced
160
+ """
161
+ result = content
162
+ for placeholder, value in replacements.items():
163
+ result = result.replace(f"{{{placeholder}}}", value)
164
+ return result
165
+
166
+
167
+ def install_base_template(
168
+ template_id: str, project_root: Path, replacements: dict[str, str]
169
+ ) -> int:
170
+ """
171
+ Install base template files.
172
+
173
+ Args:
174
+ template_id: Template identifier
175
+ project_root: Project root directory
176
+ replacements: Placeholder replacements (e.g., {"project_name": "my-app"})
177
+
178
+ Returns:
179
+ Number of files copied
180
+
181
+ Raises:
182
+ TemplateNotFoundError: If template not found
183
+ FileOperationError: If installation fails
184
+ """
185
+ template_dir = get_template_directory(template_id)
186
+ base_dir = template_dir / "base"
187
+
188
+ if not base_dir.exists():
189
+ raise TemplateNotFoundError(
190
+ template_name=f"{template_id}/base", template_path=str(template_dir)
191
+ )
192
+
193
+ logger.info(f"Installing base template files from {template_id}...")
194
+
195
+ # Copy base files
196
+ files_copied = copy_directory_tree(
197
+ base_dir, project_root, skip_patterns=[".template", "__pycache__", ".pyc"]
198
+ )
199
+
200
+ # Process template files (files ending in .template)
201
+ for template_file in base_dir.rglob("*.template"):
202
+ relative_path = template_file.relative_to(base_dir)
203
+ # Remove .template extension for output file
204
+ output_path = project_root / relative_path.parent / relative_path.stem
205
+
206
+ try:
207
+ content = template_file.read_text()
208
+ processed_content = replace_placeholders(content, replacements)
209
+ output_path.parent.mkdir(parents=True, exist_ok=True)
210
+ output_path.write_text(processed_content)
211
+ logger.debug(f"Processed template: {template_file.name} -> {output_path.name}")
212
+ files_copied += 1
213
+ except Exception as e:
214
+ raise FileOperationError(
215
+ operation="process",
216
+ file_path=str(template_file),
217
+ details=f"Failed to process template file: {str(e)}",
218
+ cause=e,
219
+ )
220
+
221
+ logger.info(f"Installed {files_copied} base files")
222
+ return files_copied
223
+
224
+
225
+ def install_tier_files(
226
+ template_id: str, tier: str, project_root: Path, replacements: dict[str, str]
227
+ ) -> int:
228
+ """
229
+ Install tier-specific files.
230
+
231
+ Args:
232
+ template_id: Template identifier
233
+ tier: Tier name (e.g., "tier-1-essential")
234
+ project_root: Project root directory
235
+ replacements: Placeholder replacements
236
+
237
+ Returns:
238
+ Number of files copied/processed
239
+
240
+ Raises:
241
+ TemplateNotFoundError: If tier directory not found
242
+ FileOperationError: If installation fails
243
+ """
244
+ template_dir = get_template_directory(template_id)
245
+ tier_dir = template_dir / tier
246
+
247
+ if not tier_dir.exists():
248
+ logger.warning(f"Tier directory not found: {tier_dir}")
249
+ return 0
250
+
251
+ logger.info(f"Installing {tier} files...")
252
+
253
+ # Copy tier files
254
+ files_copied = copy_directory_tree(
255
+ tier_dir, project_root, skip_patterns=[".template", "__pycache__", ".pyc"]
256
+ )
257
+
258
+ # Process template files
259
+ for template_file in tier_dir.rglob("*.template"):
260
+ relative_path = template_file.relative_to(tier_dir)
261
+
262
+ # Strip tier suffix from filename if present (e.g., package.json.tier4.template -> package.json)
263
+ # This allows tier files to overwrite the base file instead of creating separate tier files
264
+ filename_without_template = relative_path.stem # Removes .template
265
+
266
+ # Remove tier suffixes: .tier1, .tier2, .tier3, .tier4
267
+ filename_without_tier = re.sub(r"\.tier[1-4]$", "", filename_without_template)
268
+
269
+ output_path = project_root / relative_path.parent / filename_without_tier
270
+
271
+ try:
272
+ content = template_file.read_text()
273
+ processed_content = replace_placeholders(content, replacements)
274
+ output_path.parent.mkdir(parents=True, exist_ok=True)
275
+ output_path.write_text(processed_content)
276
+ logger.debug(f"Processed template: {template_file.name} -> {output_path.name}")
277
+ files_copied += 1
278
+ except Exception as e:
279
+ raise FileOperationError(
280
+ operation="process",
281
+ file_path=str(template_file),
282
+ details=f"Failed to process template file: {str(e)}",
283
+ cause=e,
284
+ )
285
+
286
+ logger.info(f"Installed {files_copied} files from {tier}")
287
+ return files_copied
288
+
289
+
290
+ def install_additional_option(
291
+ template_id: str, option: str, project_root: Path, replacements: dict[str, str]
292
+ ) -> int:
293
+ """
294
+ Install additional option files (CI/CD, Docker, etc.).
295
+
296
+ Args:
297
+ template_id: Template identifier
298
+ option: Option name (e.g., "ci-cd", "docker")
299
+ project_root: Project root directory
300
+ replacements: Placeholder replacements
301
+
302
+ Returns:
303
+ Number of files copied
304
+
305
+ Raises:
306
+ FileOperationError: If installation fails
307
+ """
308
+ template_dir = get_template_directory(template_id)
309
+ option_dir = template_dir / option
310
+
311
+ if not option_dir.exists():
312
+ logger.warning(f"Option directory not found: {option_dir}")
313
+ return 0
314
+
315
+ logger.info(f"Installing {option} files...")
316
+
317
+ # Copy option files
318
+ files_copied = copy_directory_tree(
319
+ option_dir, project_root, skip_patterns=[".template", "__pycache__", ".pyc"]
320
+ )
321
+
322
+ # Process template files
323
+ for template_file in option_dir.rglob("*.template"):
324
+ relative_path = template_file.relative_to(option_dir)
325
+ output_path = project_root / relative_path.parent / relative_path.stem
326
+
327
+ try:
328
+ content = template_file.read_text()
329
+ processed_content = replace_placeholders(content, replacements)
330
+ output_path.parent.mkdir(parents=True, exist_ok=True)
331
+ output_path.write_text(processed_content)
332
+ logger.debug(f"Processed template: {template_file.name} -> {output_path.name}")
333
+ files_copied += 1
334
+ except Exception as e:
335
+ raise FileOperationError(
336
+ operation="process",
337
+ file_path=str(template_file),
338
+ details=f"Failed to process template file: {str(e)}",
339
+ cause=e,
340
+ )
341
+
342
+ logger.info(f"Installed {files_copied} files from {option}")
343
+ return files_copied
344
+
345
+
346
+ def install_template(
347
+ template_id: str,
348
+ tier: str,
349
+ additional_options: list[str],
350
+ project_root: Path | None = None,
351
+ ) -> dict[str, Any]:
352
+ """
353
+ Install complete template with base + tier + options.
354
+
355
+ Args:
356
+ template_id: Template identifier (e.g., "saas_t3")
357
+ tier: Quality tier (e.g., "tier-2-standard")
358
+ additional_options: List of option names (e.g., ["ci-cd", "docker"])
359
+ project_root: Project root directory (defaults to current directory)
360
+
361
+ Returns:
362
+ Installation summary dictionary with file counts and paths
363
+
364
+ Raises:
365
+ TemplateNotFoundError: If template not found
366
+ FileOperationError: If installation fails
367
+ """
368
+ if project_root is None:
369
+ project_root = Path.cwd()
370
+
371
+ # Get template info
372
+ template_info = get_template_info(template_id)
373
+
374
+ # Prepare placeholder replacements
375
+ project_name = project_root.name
376
+ replacements = {
377
+ "project_name": project_name,
378
+ "project_description": f"A {template_info['display_name']} project",
379
+ "template_id": template_id,
380
+ "template_name": template_info["display_name"],
381
+ }
382
+
383
+ total_files = 0
384
+
385
+ # Install base template
386
+ total_files += install_base_template(template_id, project_root, replacements)
387
+
388
+ # Install tier files (cumulative - install all tiers up to selected)
389
+ tier_order = [
390
+ "tier-1-essential",
391
+ "tier-2-standard",
392
+ "tier-3-comprehensive",
393
+ "tier-4-production",
394
+ ]
395
+ selected_tier_index = tier_order.index(tier)
396
+
397
+ for i in range(selected_tier_index + 1):
398
+ tier_to_install = tier_order[i]
399
+ total_files += install_tier_files(template_id, tier_to_install, project_root, replacements)
400
+
401
+ # Install additional options
402
+ for option in additional_options:
403
+ # Map option keys to directory names
404
+ option_dir_map = {
405
+ "ci_cd": "ci-cd",
406
+ "docker": "docker",
407
+ "pre_commit": "pre-commit",
408
+ "env_templates": "env-templates",
409
+ }
410
+ option_dir = option_dir_map.get(option, option)
411
+ total_files += install_additional_option(
412
+ template_id, option_dir, project_root, replacements
413
+ )
414
+
415
+ logger.info(f"Template installation complete: {total_files} files installed")
416
+
417
+ return {
418
+ "template_id": template_id,
419
+ "template_name": template_info["display_name"],
420
+ "tier": tier,
421
+ "additional_options": additional_options,
422
+ "files_installed": total_files,
423
+ "project_root": str(project_root),
424
+ }
@@ -0,0 +1 @@
1
+ """Learning system for capturing and curating development insights."""
@@ -0,0 +1,115 @@
1
+ """Learning archiving module"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from solokit.core.constants import MAX_LEARNING_AGE_SESSIONS
11
+ from solokit.core.file_ops import load_json
12
+ from solokit.core.logging_config import get_logger
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class LearningArchiver:
18
+ """Handles archiving of old, unreferenced learnings"""
19
+
20
+ def __init__(self, session_dir: Path, max_age_sessions: int = MAX_LEARNING_AGE_SESSIONS):
21
+ """
22
+ Initialize archiver
23
+
24
+ Args:
25
+ session_dir: Path to .session directory
26
+ max_age_sessions: Maximum age in sessions before archiving (default: 50)
27
+ """
28
+ self.session_dir = session_dir
29
+ self.max_age_sessions = max_age_sessions
30
+
31
+ def archive_old_learnings(
32
+ self, learnings: dict[str, Any], max_age_sessions: int | None = None
33
+ ) -> int:
34
+ """
35
+ Archive old, unreferenced learnings
36
+
37
+ Args:
38
+ learnings: Learnings dict with 'categories' key
39
+ max_age_sessions: Override default max age (optional)
40
+
41
+ Returns:
42
+ Number of learnings archived
43
+ """
44
+ max_age = max_age_sessions if max_age_sessions is not None else self.max_age_sessions
45
+ archived_count = 0
46
+ categories = learnings.get("categories", {})
47
+
48
+ # Get current session number from tracking
49
+ current_session = self._get_current_session_number()
50
+
51
+ for category_name, category_learnings in categories.items():
52
+ to_archive = []
53
+
54
+ for i, learning in enumerate(category_learnings):
55
+ # Extract session number from learned_in field
56
+ session_num = self._extract_session_number(learning.get("learned_in", ""))
57
+
58
+ # Archive if too old
59
+ if session_num and current_session > 0 and current_session - session_num > max_age:
60
+ to_archive.append(i)
61
+
62
+ # Move to archive
63
+ archived = learnings.setdefault("archived", [])
64
+ for idx in sorted(to_archive, reverse=True):
65
+ learning = category_learnings.pop(idx)
66
+ learning["archived_from"] = category_name
67
+ learning["archived_at"] = datetime.now().isoformat()
68
+ archived.append(learning)
69
+ archived_count += 1
70
+
71
+ logger.info(f"Archived {archived_count} old learnings")
72
+ return archived_count
73
+
74
+ def _get_current_session_number(self) -> int:
75
+ """
76
+ Get the current session number from work items
77
+
78
+ Returns:
79
+ Maximum session number found, or 0 if none found
80
+ """
81
+ try:
82
+ work_items_path = self.session_dir / "tracking" / "work_items.json"
83
+ if work_items_path.exists():
84
+ data = load_json(work_items_path)
85
+ # Find max session number across all work items
86
+ max_session = 0
87
+ for item in data.get("work_items", {}).values():
88
+ sessions = item.get("sessions", [])
89
+ if sessions and isinstance(sessions, list):
90
+ max_session = max(max_session, max(sessions))
91
+ return max_session
92
+ except (ValueError, KeyError, TypeError) as e:
93
+ logger.warning(f"Failed to get current session number: {e}")
94
+ return 0
95
+ return 0
96
+
97
+ def _extract_session_number(self, session_id: str) -> int:
98
+ """
99
+ Extract numeric session number from session ID
100
+
101
+ Args:
102
+ session_id: Session ID string (e.g., "session_001", "001", "1")
103
+
104
+ Returns:
105
+ Extracted session number or 0 if extraction fails
106
+ """
107
+ try:
108
+ # Handle formats like "session_001", "001", "1", etc.
109
+ match = re.search(r"\d+", session_id)
110
+ if match:
111
+ return int(match.group())
112
+ except (ValueError, AttributeError) as e:
113
+ logger.debug(f"Failed to extract session number from '{session_id}': {e}")
114
+ return 0
115
+ return 0
@@ -0,0 +1,126 @@
1
+ """Learning categorization module"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from solokit.core.logging_config import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class LearningCategorizer:
13
+ """Handles automatic categorization of learnings based on content analysis"""
14
+
15
+ # Keyword sets for each category
16
+ ARCHITECTURE_KEYWORDS = [
17
+ "architecture",
18
+ "design",
19
+ "pattern",
20
+ "structure",
21
+ "component",
22
+ "module",
23
+ "layer",
24
+ "service",
25
+ ]
26
+
27
+ GOTCHA_KEYWORDS = [
28
+ "gotcha",
29
+ "trap",
30
+ "pitfall",
31
+ "mistake",
32
+ "error",
33
+ "bug",
34
+ "issue",
35
+ "problem",
36
+ "challenge",
37
+ "warning",
38
+ ]
39
+
40
+ PRACTICE_KEYWORDS = [
41
+ "best practice",
42
+ "convention",
43
+ "standard",
44
+ "guideline",
45
+ "recommendation",
46
+ "should",
47
+ "always",
48
+ "never",
49
+ ]
50
+
51
+ DEBT_KEYWORDS = [
52
+ "technical debt",
53
+ "refactor",
54
+ "cleanup",
55
+ "legacy",
56
+ "deprecated",
57
+ "workaround",
58
+ "hack",
59
+ "todo",
60
+ ]
61
+
62
+ PERFORMANCE_KEYWORDS = [
63
+ "performance",
64
+ "optimization",
65
+ "speed",
66
+ "slow",
67
+ "fast",
68
+ "efficient",
69
+ "memory",
70
+ "cpu",
71
+ "benchmark",
72
+ ]
73
+
74
+ def categorize_learning(self, learning: dict[str, Any]) -> str:
75
+ """
76
+ Automatically categorize a single learning based on content analysis
77
+
78
+ Args:
79
+ learning: Learning dict with 'content' field
80
+
81
+ Returns:
82
+ Category name string
83
+ """
84
+ content = learning.get("content", "").lower()
85
+
86
+ # Check for suggested type first
87
+ if "suggested_type" in learning:
88
+ suggested = learning["suggested_type"]
89
+ if suggested in [
90
+ "architecture_pattern",
91
+ "gotcha",
92
+ "best_practice",
93
+ "technical_debt",
94
+ "performance_insight",
95
+ ]:
96
+ return f"{suggested}s" # Pluralize
97
+
98
+ # Score each category based on keywords
99
+ scores = {
100
+ "architecture_patterns": self._keyword_score(content, self.ARCHITECTURE_KEYWORDS),
101
+ "gotchas": self._keyword_score(content, self.GOTCHA_KEYWORDS),
102
+ "best_practices": self._keyword_score(content, self.PRACTICE_KEYWORDS),
103
+ "technical_debt": self._keyword_score(content, self.DEBT_KEYWORDS),
104
+ "performance_insights": self._keyword_score(content, self.PERFORMANCE_KEYWORDS),
105
+ }
106
+
107
+ # Return category with highest score, default to best_practices
108
+ max_category = max(scores.items(), key=lambda x: x[1])
109
+ return max_category[0] if max_category[1] > 0 else "best_practices"
110
+
111
+ def _keyword_score(self, text: str, keywords: list[str]) -> int:
112
+ """
113
+ Calculate keyword match score for text
114
+
115
+ Args:
116
+ text: Text to analyze (should be lowercased)
117
+ keywords: List of keywords to search for
118
+
119
+ Returns:
120
+ Number of keyword matches found
121
+ """
122
+ score = 0
123
+ for keyword in keywords:
124
+ if keyword in text:
125
+ score += 1
126
+ return score