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,282 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Git context and status checking.
4
+ Part of the briefing module decomposition.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any, Optional
10
+
11
+ from solokit.core.command_runner import CommandRunner
12
+ from solokit.core.constants import GIT_QUICK_TIMEOUT
13
+ from solokit.core.error_handlers import log_errors
14
+ from solokit.core.exceptions import ErrorCode, GitError, SystemError
15
+ from solokit.core.logging_config import get_logger
16
+ from solokit.core.output import get_output
17
+ from solokit.core.types import GitStatus, WorkItemStatus
18
+
19
+ logger = get_logger(__name__)
20
+ output = get_output()
21
+
22
+
23
+ class GitContext:
24
+ """Handle git status and branch information."""
25
+
26
+ def __init__(self) -> None:
27
+ """Initialize git context handler."""
28
+ self.runner = CommandRunner(default_timeout=GIT_QUICK_TIMEOUT)
29
+
30
+ @log_errors()
31
+ def check_git_status(self) -> dict[str, Any]:
32
+ """Check git status for session start.
33
+
34
+ Returns:
35
+ Dict with keys: clean (bool), status (str), branch (str or None)
36
+
37
+ Raises:
38
+ GitError: If git command fails
39
+ NotAGitRepoError: If not in a git repository
40
+ SystemError: If git workflow import or execution fails
41
+ """
42
+ try:
43
+ # Import git workflow from new location
44
+ from solokit.git.integration import GitWorkflow
45
+
46
+ workflow = GitWorkflow()
47
+ is_clean: bool
48
+ status_msg: str
49
+ is_clean, status_msg = workflow.check_git_status() # type: ignore[misc]
50
+ current_branch = workflow.get_current_branch()
51
+
52
+ return {"clean": is_clean, "status": status_msg, "branch": current_branch}
53
+ except ImportError as e:
54
+ raise SystemError(
55
+ message="Failed to import GitWorkflow",
56
+ code=ErrorCode.IMPORT_FAILED,
57
+ context={"module": "solokit.git.integration", "error": str(e)},
58
+ remediation="Ensure git integration module is properly installed",
59
+ cause=e,
60
+ ) from e
61
+ except GitError:
62
+ # Re-raise GitError as-is (already standardized)
63
+ raise
64
+ except Exception as e:
65
+ # Wrap unexpected errors
66
+ raise GitError(
67
+ message=f"Failed to check git status: {e}",
68
+ code=ErrorCode.GIT_COMMAND_FAILED,
69
+ context={"error": str(e)},
70
+ remediation="Check git installation and repository state",
71
+ cause=e,
72
+ ) from e
73
+
74
+ @log_errors()
75
+ def determine_git_branch_final_status(self, branch_name: str, git_info: dict) -> str:
76
+ """Determine the final status of a git branch by inspecting actual git state.
77
+
78
+ Args:
79
+ branch_name: Name of the git branch
80
+ git_info: Git info dict from work item
81
+
82
+ Returns:
83
+ One of: "merged", "pr_created", "pr_closed", "ready_for_pr", "deleted"
84
+
85
+ Raises:
86
+ GitError: If git commands fail critically
87
+ SystemError: If JSON parsing or other operations fail
88
+ """
89
+ parent_branch = git_info.get("parent_branch", "main")
90
+
91
+ try:
92
+ # Check 1: Is branch merged?
93
+ result = self.runner.run(["git", "branch", "--merged", parent_branch])
94
+ if result.success and branch_name in result.stdout:
95
+ logger.debug(f"Branch {branch_name} is merged to {parent_branch}")
96
+ return GitStatus.MERGED.value
97
+
98
+ # Check 2: Does PR exist? (requires gh CLI)
99
+ result = self.runner.run(
100
+ [
101
+ "gh",
102
+ "pr",
103
+ "list",
104
+ "--head",
105
+ branch_name,
106
+ "--state",
107
+ "all",
108
+ "--json",
109
+ "number,state",
110
+ ]
111
+ )
112
+ if result.success and result.stdout.strip():
113
+ try:
114
+ prs = json.loads(result.stdout)
115
+ if prs:
116
+ pr = prs[0] # Get first/most recent PR
117
+ pr_state = pr.get("state", "").upper()
118
+
119
+ if pr_state == "MERGED":
120
+ logger.debug(f"Branch {branch_name} has merged PR")
121
+ return GitStatus.MERGED.value
122
+ elif pr_state == "CLOSED":
123
+ logger.debug(f"Branch {branch_name} has closed (unmerged) PR")
124
+ return GitStatus.PR_CLOSED.value
125
+ elif pr_state == "OPEN":
126
+ logger.debug(f"Branch {branch_name} has open PR")
127
+ return GitStatus.PR_CREATED.value
128
+ except json.JSONDecodeError as e:
129
+ # Log but don't fail - gh CLI output may be malformed
130
+ logger.debug(f"Error parsing PR JSON: {e}")
131
+ # Continue to check branch existence
132
+ else:
133
+ logger.debug("gh CLI not available or no PRs found")
134
+
135
+ # Check 3: Does branch still exist locally?
136
+ result = self.runner.run(["git", "show-ref", "--verify", f"refs/heads/{branch_name}"])
137
+ if result.success:
138
+ logger.debug(f"Branch {branch_name} exists locally")
139
+ # Branch exists locally, no PR found
140
+ return GitStatus.READY_FOR_PR.value
141
+
142
+ # Check 4: Does branch exist remotely?
143
+ result = self.runner.run(["git", "ls-remote", "--heads", "origin", branch_name])
144
+ if result.success and result.stdout.strip():
145
+ logger.debug(f"Branch {branch_name} exists remotely")
146
+ # Branch exists remotely, no PR found
147
+ return GitStatus.READY_FOR_PR.value
148
+
149
+ # Branch doesn't exist and no PR found
150
+ logger.debug(f"Branch {branch_name} not found locally or remotely")
151
+ return GitStatus.DELETED.value
152
+
153
+ except GitError:
154
+ # Re-raise GitError from CommandRunner
155
+ raise
156
+ except Exception as e:
157
+ # Wrap unexpected errors
158
+ raise GitError(
159
+ message=f"Failed to determine git branch status for '{branch_name}': {e}",
160
+ code=ErrorCode.GIT_COMMAND_FAILED,
161
+ context={
162
+ "branch_name": branch_name,
163
+ "parent_branch": parent_branch,
164
+ "error": str(e),
165
+ },
166
+ remediation="Check git installation and repository state",
167
+ cause=e,
168
+ ) from e
169
+
170
+ @log_errors()
171
+ def finalize_previous_work_item_git_status(
172
+ self, work_items_data: dict, current_work_item_id: str
173
+ ) -> Optional[str]:
174
+ """Finalize git status for previous completed work item when starting a new one.
175
+
176
+ This handles the case where:
177
+ - Previous work item was completed
178
+ - User performed git operations externally (pushed, created PR, merged)
179
+ - Starting a new work item (not resuming the previous one)
180
+
181
+ Args:
182
+ work_items_data: Loaded work items data
183
+ current_work_item_id: ID of work item being started
184
+
185
+ Returns:
186
+ Previous work item ID if finalized, None otherwise
187
+
188
+ Raises:
189
+ GitError: If git operations fail
190
+ SystemError: If file operations fail
191
+ """
192
+ work_items = work_items_data.get("work_items", {})
193
+
194
+ # Find previously active work item
195
+ previous_work_item = None
196
+ previous_work_item_id = None
197
+
198
+ for wid, wi in work_items.items():
199
+ # Skip current work item
200
+ if wid == current_work_item_id:
201
+ continue
202
+
203
+ # Find work item with git branch in "in_progress" status
204
+ git_info = wi.get("git", {})
205
+ if git_info.get("status") == GitStatus.IN_PROGRESS.value:
206
+ # Only finalize if work item itself is completed
207
+ if wi.get("status") == WorkItemStatus.COMPLETED.value:
208
+ previous_work_item = wi
209
+ previous_work_item_id = wid
210
+ break
211
+
212
+ if not previous_work_item:
213
+ # No previous work item to finalize
214
+ logger.debug("No previous work item with stale git status found")
215
+ return None
216
+
217
+ git_info = previous_work_item.get("git", {})
218
+ branch_name = git_info.get("branch")
219
+
220
+ if not branch_name:
221
+ logger.debug(f"Previous work item {previous_work_item_id} has no git branch")
222
+ return None
223
+
224
+ logger.info(f"Finalizing git status for completed work item: {previous_work_item_id}")
225
+
226
+ try:
227
+ # Inspect actual git state
228
+ final_status = self.determine_git_branch_final_status(branch_name, git_info)
229
+
230
+ # Update git status
231
+ work_items[previous_work_item_id]["git"]["status"] = final_status
232
+
233
+ # Save updated work items
234
+ work_items_file = Path(".session/tracking/work_items.json")
235
+ try:
236
+ with open(work_items_file, "w") as f:
237
+ json.dump(work_items_data, f, indent=2)
238
+ except OSError as e:
239
+ raise SystemError(
240
+ message=f"Failed to save work items file: {work_items_file}",
241
+ code=ErrorCode.FILE_OPERATION_FAILED,
242
+ context={
243
+ "file_path": str(work_items_file),
244
+ "work_item_id": previous_work_item_id,
245
+ "error": str(e),
246
+ },
247
+ remediation="Check file permissions and disk space",
248
+ cause=e,
249
+ ) from e
250
+
251
+ logger.info(
252
+ "Updated git status for %s: in_progress → %s",
253
+ previous_work_item_id,
254
+ final_status,
255
+ )
256
+ output.success(
257
+ f"Finalized git status for previous work item: "
258
+ f"{previous_work_item_id} → {final_status}"
259
+ )
260
+
261
+ return previous_work_item_id
262
+
263
+ except GitError:
264
+ # Re-raise GitError from determine_git_branch_final_status
265
+ raise
266
+ except SystemError:
267
+ # Re-raise SystemError from file operations
268
+ raise
269
+ except Exception as e:
270
+ # Wrap unexpected errors
271
+ raise SystemError(
272
+ message=f"Failed to finalize git status for work item '{previous_work_item_id}': {e}",
273
+ code=ErrorCode.FILE_OPERATION_FAILED,
274
+ context={
275
+ "work_item_id": previous_work_item_id,
276
+ "current_work_item_id": current_work_item_id,
277
+ "branch_name": branch_name,
278
+ "error": str(e),
279
+ },
280
+ remediation="Check work items data integrity and git repository state",
281
+ cause=e,
282
+ ) from e
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Learning loading and relevance scoring.
4
+ Part of the briefing module decomposition.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import re
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from solokit.core.constants import MAX_SPEC_KEYWORDS
16
+ from solokit.core.exceptions import FileOperationError
17
+ from solokit.core.logging_config import get_logger
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class LearningLoader:
23
+ """Load and score learnings based on relevance."""
24
+
25
+ def __init__(self, session_dir: Path | None = None):
26
+ """Initialize learning loader.
27
+
28
+ Args:
29
+ session_dir: Path to .session directory (defaults to .session)
30
+ """
31
+ self.session_dir = session_dir or Path(".session")
32
+ self.learnings_file = self.session_dir / "tracking" / "learnings.json"
33
+
34
+ def load_learnings(self) -> dict[str, Any]:
35
+ """Load learnings from tracking file.
36
+
37
+ Returns:
38
+ Learnings data structure
39
+
40
+ Raises:
41
+ FileOperationError: If file read or JSON parsing fails
42
+ """
43
+ if not self.learnings_file.exists():
44
+ return {"learnings": []}
45
+
46
+ try:
47
+ with open(self.learnings_file) as f:
48
+ return json.load(f) # type: ignore[no-any-return]
49
+ except json.JSONDecodeError as e:
50
+ raise FileOperationError(
51
+ operation="parse",
52
+ file_path=str(self.learnings_file),
53
+ details=f"Invalid JSON format: {e.msg} at line {e.lineno}, column {e.colno}",
54
+ cause=e,
55
+ )
56
+ except OSError as e:
57
+ raise FileOperationError(
58
+ operation="read",
59
+ file_path=str(self.learnings_file),
60
+ details=str(e),
61
+ cause=e,
62
+ )
63
+
64
+ def get_relevant_learnings(
65
+ self, learnings_data: dict, work_item: dict, spec_content: str = ""
66
+ ) -> list[dict]:
67
+ """Get learnings relevant to this work item using multi-factor scoring.
68
+
69
+ Enhancement #11 Phase 4: Uses intelligent scoring algorithm instead of
70
+ simple tag matching. Considers keyword matching, type-based relevance,
71
+ recency weighting, and category bonuses.
72
+
73
+ Args:
74
+ learnings_data: Full learnings.json data structure
75
+ work_item: Work item dictionary with title, type, tags
76
+ spec_content: Optional spec content for keyword extraction
77
+
78
+ Returns:
79
+ Top 10 scored learnings
80
+ """
81
+ # Flatten all learnings from categories structure
82
+ all_learnings = []
83
+ categories = learnings_data.get("categories", {})
84
+
85
+ # Handle both old format (learnings list) and new format (categories dict)
86
+ if not categories and "learnings" in learnings_data:
87
+ # Old format compatibility
88
+ all_learnings = learnings_data.get("learnings", [])
89
+ for learning in all_learnings:
90
+ if "category" not in learning:
91
+ learning["category"] = "general"
92
+ else:
93
+ # New format with categories
94
+ for category, learnings in categories.items():
95
+ for learning in learnings:
96
+ learning_copy = learning.copy()
97
+ learning_copy["category"] = category
98
+ all_learnings.append(learning_copy)
99
+
100
+ if not all_learnings:
101
+ return []
102
+
103
+ # Extract keywords from work item
104
+ title_keywords = self._extract_keywords(work_item.get("title", ""))
105
+ spec_keywords = self._extract_keywords(spec_content[:MAX_SPEC_KEYWORDS])
106
+ work_type = work_item.get("type", "")
107
+ work_tags = set(work_item.get("tags", []))
108
+
109
+ scored = []
110
+ for learning in all_learnings:
111
+ score: float = 0
112
+ content_lower = learning.get("content", "").lower()
113
+ context_lower = learning.get("context", "").lower()
114
+ learning_tags = set(learning.get("tags", []))
115
+ category = learning.get("category", "general")
116
+
117
+ # 1. Keyword matching (title and spec)
118
+ content_keywords = self._extract_keywords(content_lower)
119
+ title_matches = len(title_keywords & content_keywords)
120
+ spec_matches = len(spec_keywords & content_keywords)
121
+ score += title_matches * 3 # Title match is worth more
122
+ score += spec_matches * 1.5
123
+
124
+ # 2. Type-based matching
125
+ if work_type in content_lower or work_type in context_lower:
126
+ score += 5
127
+
128
+ # 3. Tag matching (legacy support)
129
+ tag_overlap = len(work_tags & learning_tags)
130
+ score += tag_overlap * 2
131
+
132
+ # 4. Category bonuses
133
+ category_bonuses = {
134
+ "best_practices": 3,
135
+ "patterns": 2,
136
+ "gotchas": 2,
137
+ "architecture": 2,
138
+ }
139
+ score += category_bonuses.get(category, 0)
140
+
141
+ # 5. Recency weighting (decay over time)
142
+ created_at = learning.get("created_at", "")
143
+ if created_at:
144
+ days_ago = self._calculate_days_ago(created_at)
145
+ if days_ago < 7:
146
+ score += 3 # Very recent
147
+ elif days_ago < 30:
148
+ score += 2 # Recent
149
+ elif days_ago < 90:
150
+ score += 1 # Moderately recent
151
+
152
+ # Only include if score > 0
153
+ if score > 0:
154
+ scored.append((score, learning))
155
+
156
+ # Sort by score (descending) and return top 10
157
+ scored.sort(key=lambda x: x[0], reverse=True)
158
+ return [learning for score, learning in scored[:10]]
159
+
160
+ def _extract_keywords(self, text: str) -> set[str]:
161
+ """Extract meaningful keywords from text (lowercase, >3 chars).
162
+
163
+ Args:
164
+ text: Text to extract keywords from
165
+
166
+ Returns:
167
+ Set of lowercase keywords longer than 3 characters
168
+ """
169
+ words = re.findall(r"\b\w+\b", text.lower())
170
+ # Filter stop words and short words
171
+ stop_words = {
172
+ "the",
173
+ "this",
174
+ "that",
175
+ "with",
176
+ "from",
177
+ "have",
178
+ "will",
179
+ "for",
180
+ "and",
181
+ "or",
182
+ "not",
183
+ "but",
184
+ "was",
185
+ "are",
186
+ "been",
187
+ }
188
+ return {w for w in words if len(w) > 3 and w not in stop_words}
189
+
190
+ def _calculate_days_ago(self, timestamp: str) -> int:
191
+ """Calculate days since timestamp.
192
+
193
+ Args:
194
+ timestamp: ISO format timestamp string
195
+
196
+ Returns:
197
+ Number of days ago (defaults to 365 if parsing fails)
198
+
199
+ Note:
200
+ Returns 365 (considered "old") if timestamp is empty or invalid.
201
+ This is intentional to avoid breaking the scoring algorithm.
202
+ """
203
+ if not timestamp:
204
+ return 365 # Empty timestamp considered old
205
+
206
+ try:
207
+ ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
208
+ delta = datetime.now() - ts
209
+ return delta.days
210
+ except (ValueError, TypeError) as e:
211
+ logger.debug(f"Failed to parse timestamp '{timestamp}': {e}. Treating as old learning.")
212
+ return 365 # Default to old if parsing fails
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Milestone context building.
4
+ Part of the briefing module decomposition.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+
12
+ from solokit.core.logging_config import get_logger
13
+ from solokit.core.types import WorkItemStatus
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class MilestoneBuilder:
19
+ """Build milestone context for briefings."""
20
+
21
+ def __init__(self, session_dir: Path | None = None):
22
+ """Initialize milestone builder.
23
+
24
+ Args:
25
+ session_dir: Path to .session directory (defaults to .session)
26
+ """
27
+ self.session_dir = session_dir or Path(".session")
28
+ self.work_items_file = self.session_dir / "tracking" / "work_items.json"
29
+
30
+ def load_milestone_context(self, work_item: dict) -> dict | None:
31
+ """Load milestone context for briefing.
32
+
33
+ Args:
34
+ work_item: Work item dictionary
35
+
36
+ Returns:
37
+ Milestone context dict or None if not in a milestone
38
+ """
39
+ milestone_name = work_item.get("milestone")
40
+ if not milestone_name:
41
+ return None
42
+
43
+ if not self.work_items_file.exists():
44
+ return None
45
+
46
+ with open(self.work_items_file) as f:
47
+ data = json.load(f)
48
+
49
+ milestones = data.get("milestones", {})
50
+ milestone = milestones.get(milestone_name)
51
+
52
+ if not milestone:
53
+ return None
54
+
55
+ # Calculate progress
56
+ items = data.get("work_items", {})
57
+ milestone_items = [
58
+ {**item, "id": item_id}
59
+ for item_id, item in items.items()
60
+ if item.get("milestone") == milestone_name
61
+ ]
62
+
63
+ total = len(milestone_items)
64
+ completed = sum(
65
+ 1 for item in milestone_items if item["status"] == WorkItemStatus.COMPLETED.value
66
+ )
67
+ percent = int((completed / total) * 100) if total > 0 else 0
68
+
69
+ return {
70
+ "name": milestone_name,
71
+ "title": milestone["title"],
72
+ "description": milestone["description"],
73
+ "target_date": milestone.get("target_date", ""),
74
+ "progress": percent,
75
+ "total_items": total,
76
+ "completed_items": completed,
77
+ "milestone_items": milestone_items,
78
+ }