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,137 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Milestone Manager - Milestone creation and progress tracking.
4
+
5
+ Handles milestone CRUD operations and progress calculation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from solokit.core.error_handlers import log_errors
13
+ from solokit.core.exceptions import ErrorCode, ValidationError
14
+ from solokit.core.logging_config import get_logger
15
+ from solokit.core.types import WorkItemStatus
16
+
17
+ if TYPE_CHECKING:
18
+ from .repository import WorkItemRepository
19
+ from solokit.core.output import get_output
20
+
21
+ logger = get_logger(__name__)
22
+ output = get_output()
23
+
24
+
25
+ class MilestoneManager:
26
+ """Handles milestone management operations"""
27
+
28
+ def __init__(self, repository: WorkItemRepository):
29
+ """Initialize milestone manager with repository
30
+
31
+ Args:
32
+ repository: WorkItemRepository instance for data access
33
+ """
34
+ self.repository = repository
35
+
36
+ @log_errors()
37
+ def create(
38
+ self, name: str, title: str, description: str, target_date: str | None = None
39
+ ) -> None:
40
+ """Create a new milestone
41
+
42
+ Args:
43
+ name: Milestone name (unique identifier)
44
+ title: Milestone title
45
+ description: Milestone description
46
+ target_date: Optional target completion date
47
+
48
+ Raises:
49
+ ValidationError: If milestone with this name already exists
50
+ """
51
+ if self.repository.milestone_exists(name):
52
+ logger.error("Milestone '%s' already exists", name)
53
+ raise ValidationError(
54
+ message=f"Milestone '{name}' already exists",
55
+ code=ErrorCode.WORK_ITEM_ALREADY_EXISTS,
56
+ context={"milestone_name": name},
57
+ remediation="Choose a different milestone name",
58
+ )
59
+
60
+ self.repository.add_milestone(name, title, description, target_date)
61
+ logger.info("Created milestone: %s", name)
62
+ output.info(f"✓ Created milestone: {name}")
63
+
64
+ def get_progress(self, milestone_name: str) -> dict:
65
+ """Calculate milestone progress
66
+
67
+ Args:
68
+ milestone_name: Name of the milestone
69
+
70
+ Returns:
71
+ dict: Progress statistics including total, completed, in_progress, not_started, percent
72
+ """
73
+ items = self.repository.get_all_work_items()
74
+
75
+ # Filter items in this milestone
76
+ milestone_items = [
77
+ item for item in items.values() if item.get("milestone") == milestone_name
78
+ ]
79
+
80
+ if not milestone_items:
81
+ return {
82
+ "total": 0,
83
+ "completed": 0,
84
+ "in_progress": 0,
85
+ "not_started": 0,
86
+ "percent": 0,
87
+ }
88
+
89
+ total = len(milestone_items)
90
+ completed = sum(
91
+ 1 for item in milestone_items if item["status"] == WorkItemStatus.COMPLETED.value
92
+ )
93
+ in_progress = sum(
94
+ 1 for item in milestone_items if item["status"] == WorkItemStatus.IN_PROGRESS.value
95
+ )
96
+ not_started = sum(
97
+ 1 for item in milestone_items if item["status"] == WorkItemStatus.NOT_STARTED.value
98
+ )
99
+ percent = int((completed / total) * 100) if total > 0 else 0
100
+
101
+ return {
102
+ "total": total,
103
+ "completed": completed,
104
+ "in_progress": in_progress,
105
+ "not_started": not_started,
106
+ "percent": percent,
107
+ }
108
+
109
+ def list_all(self) -> None:
110
+ """List all milestones with progress"""
111
+ milestones = self.repository.get_all_milestones()
112
+
113
+ if not milestones:
114
+ output.info("No milestones found.")
115
+ return
116
+
117
+ output.info("\nMilestones:\n")
118
+
119
+ for name, milestone in milestones.items():
120
+ progress = self.get_progress(name)
121
+ percent = progress["percent"]
122
+
123
+ # Progress bar
124
+ bar_length = 20
125
+ filled = int(bar_length * percent / 100)
126
+ bar = "â–ˆ" * filled + "â–‘" * (bar_length - filled)
127
+
128
+ output.info(f"{milestone['title']}")
129
+ output.info(f" [{bar}] {percent}%")
130
+ output.info(
131
+ f" {progress['completed']}/{progress['total']} complete, "
132
+ f"{progress['in_progress']} in progress"
133
+ )
134
+
135
+ if milestone.get("target_date"):
136
+ output.info(f" Target: {milestone['target_date']}")
137
+ output.info("")
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Work Item Query - Listing, filtering, and displaying work items.
4
+
5
+ Handles work item queries, sorting, and formatted display.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from solokit.core.error_handlers import log_errors
14
+ from solokit.core.exceptions import FileOperationError, WorkItemNotFoundError
15
+ from solokit.core.logging_config import get_logger
16
+ from solokit.core.types import Priority, WorkItemStatus
17
+
18
+ if TYPE_CHECKING:
19
+ from .repository import WorkItemRepository
20
+ from solokit.core.output import get_output
21
+
22
+ logger = get_logger(__name__)
23
+ output = get_output()
24
+
25
+
26
+ class WorkItemQuery:
27
+ """Handles work item queries, filtering, and display"""
28
+
29
+ def __init__(self, repository: WorkItemRepository):
30
+ """Initialize query engine with repository
31
+
32
+ Args:
33
+ repository: WorkItemRepository instance for data access
34
+ """
35
+ self.repository = repository
36
+
37
+ def list_items(
38
+ self,
39
+ status_filter: str | None = None,
40
+ type_filter: str | None = None,
41
+ milestone_filter: str | None = None,
42
+ ) -> dict:
43
+ """List work items with optional filtering
44
+
45
+ Args:
46
+ status_filter: Optional status filter
47
+ type_filter: Optional type filter
48
+ milestone_filter: Optional milestone filter
49
+
50
+ Returns:
51
+ dict: Dictionary with 'items' list and 'count'
52
+ """
53
+ items = self.repository.get_all_work_items()
54
+
55
+ if not items:
56
+ output.info("No work items found. Create one with /work-item create")
57
+ return {"items": [], "count": 0}
58
+
59
+ # Apply filters
60
+ filtered_items = {}
61
+ for work_id, item in items.items():
62
+ # Status filter
63
+ if status_filter and item["status"] != status_filter:
64
+ continue
65
+
66
+ # Type filter
67
+ if type_filter and item["type"] != type_filter:
68
+ continue
69
+
70
+ # Milestone filter
71
+ if milestone_filter and item.get("milestone") != milestone_filter:
72
+ continue
73
+
74
+ filtered_items[work_id] = item
75
+
76
+ # Check dependency status for each item
77
+ for work_id, item in filtered_items.items():
78
+ item["_blocked"] = self._is_blocked(item, items)
79
+ item["_ready"] = (
80
+ not item["_blocked"] and item["status"] == WorkItemStatus.NOT_STARTED.value
81
+ )
82
+
83
+ # Sort items
84
+ sorted_items = self._sort_items(filtered_items)
85
+
86
+ # Display
87
+ self._display_items(sorted_items)
88
+
89
+ return {"items": sorted_items, "count": len(sorted_items)}
90
+
91
+ @log_errors()
92
+ def show_item(self, work_id: str) -> dict[str, Any]:
93
+ """Display detailed information about a work item
94
+
95
+ Args:
96
+ work_id: ID of the work item to display
97
+
98
+ Returns:
99
+ dict: The work item data
100
+
101
+ Raises:
102
+ FileOperationError: If work_items.json doesn't exist
103
+ WorkItemNotFoundError: If work item doesn't exist
104
+ """
105
+ items = self.repository.get_all_work_items()
106
+
107
+ if not items:
108
+ raise FileOperationError(
109
+ operation="read",
110
+ file_path=str(self.repository.work_items_file),
111
+ details="No work items found",
112
+ )
113
+
114
+ if work_id not in items:
115
+ # Log available work items for context
116
+ available = list(items.keys())[:5]
117
+ logger.error(f"Work item '{work_id}' not found. Available: {', '.join(available)}")
118
+ raise WorkItemNotFoundError(work_id)
119
+
120
+ item = items[work_id]
121
+
122
+ # Display header
123
+ output.info("=" * 80)
124
+ output.info(f"Work Item: {work_id}")
125
+ output.info("=" * 80)
126
+ output.info("")
127
+
128
+ # Basic info
129
+ output.info(f"Type: {item['type']}")
130
+ output.info(f"Status: {item['status']}")
131
+ output.info(f"Priority: {item['priority']}")
132
+ output.info(f"Created: {item.get('created_at', 'Unknown')[:10]}")
133
+ output.info("")
134
+
135
+ # Dependencies
136
+ if item.get("dependencies"):
137
+ output.info("Dependencies:")
138
+ for dep_id in item["dependencies"]:
139
+ if dep_id in items:
140
+ dep_status = items[dep_id]["status"]
141
+ icon = "✓" if dep_status == WorkItemStatus.COMPLETED.value else "✗"
142
+ output.info(f" {icon} {dep_id} ({dep_status})")
143
+ else:
144
+ output.info(f" ? {dep_id} (not found)")
145
+ output.info("")
146
+
147
+ # Sessions
148
+ sessions = item.get("sessions", [])
149
+ if sessions:
150
+ output.info(f"Sessions: {len(sessions)}")
151
+ for i, session in enumerate(sessions[-5:], 1): # Last 5 sessions
152
+ session_num = session.get("session_number", i)
153
+ date = session.get("date", "Unknown")
154
+ duration = session.get("duration", "Unknown")
155
+ notes = session.get("notes", "")
156
+ output.info(f" {session_num}. {date} ({duration}) - {notes[:50]}")
157
+ output.info("")
158
+
159
+ # Git info
160
+ git_info = item.get("git", {})
161
+ if git_info:
162
+ output.info(f"Git Branch: {git_info.get('branch', 'N/A')}")
163
+ commits = git_info.get("commits", [])
164
+ output.info(f"Commits: {len(commits)}")
165
+ output.info("")
166
+
167
+ # Specification - use spec_file from work item config
168
+ spec_file_path = item.get("spec_file", f".session/specs/{work_id}.md")
169
+ spec_path = Path(spec_file_path)
170
+ if spec_path.exists():
171
+ output.info("Specification:")
172
+ output.info("-" * 80)
173
+ spec_content = spec_path.read_text()
174
+ # Show first 50 lines (increased to include Acceptance Criteria section)
175
+ lines = spec_content.split("\n")[:50]
176
+ output.info("\n".join(lines))
177
+ if len(spec_content.split("\n")) > 50:
178
+ output.info(f"\n[... see full specification in {spec_file_path}]")
179
+ output.info("")
180
+
181
+ # Next steps
182
+ output.info("Next Steps:")
183
+ if item["status"] == WorkItemStatus.NOT_STARTED.value:
184
+ # Check dependencies
185
+ blocked = any(
186
+ items.get(dep_id, {}).get("status") != WorkItemStatus.COMPLETED.value
187
+ for dep_id in item.get("dependencies", [])
188
+ )
189
+ if blocked:
190
+ output.info("- Waiting on dependencies to complete")
191
+ else:
192
+ output.info("- Start working: /start")
193
+ elif item["status"] == WorkItemStatus.IN_PROGRESS.value:
194
+ output.info("- Continue working: /start")
195
+ elif item["status"] == WorkItemStatus.COMPLETED.value:
196
+ output.info("- Work item is complete")
197
+
198
+ output.info(f"- Update fields: /work-update {work_id}")
199
+ if item.get("milestone"):
200
+ output.info(f"- View related items: /work-list --milestone {item['milestone']}")
201
+ output.info("")
202
+
203
+ return item # type: ignore[no-any-return]
204
+
205
+ def _is_blocked(self, item: dict, all_items: dict) -> bool:
206
+ """Check if work item is blocked by dependencies
207
+
208
+ Args:
209
+ item: Work item to check
210
+ all_items: All work items
211
+
212
+ Returns:
213
+ bool: True if blocked
214
+ """
215
+ if item["status"] != WorkItemStatus.NOT_STARTED.value:
216
+ return False
217
+
218
+ dependencies = item.get("dependencies", [])
219
+ if not dependencies:
220
+ return False
221
+
222
+ for dep_id in dependencies:
223
+ if dep_id not in all_items:
224
+ continue
225
+ if all_items[dep_id]["status"] != WorkItemStatus.COMPLETED.value:
226
+ return True
227
+
228
+ return False
229
+
230
+ def _sort_items(self, items: dict) -> list[dict]:
231
+ """Sort items by priority, dependency status, and date
232
+
233
+ Args:
234
+ items: Items to sort
235
+
236
+ Returns:
237
+ list: Sorted list of items
238
+ """
239
+ priority_order = {
240
+ Priority.CRITICAL.value: 0,
241
+ Priority.HIGH.value: 1,
242
+ Priority.MEDIUM.value: 2,
243
+ Priority.LOW.value: 3,
244
+ }
245
+
246
+ items_list = list(items.values())
247
+
248
+ # Sort by:
249
+ # 1. Priority (critical first)
250
+ # 2. Blocked status (ready items first)
251
+ # 3. Status (in_progress first)
252
+ # 4. Creation date (oldest first)
253
+ items_list.sort(
254
+ key=lambda x: (
255
+ priority_order.get(x["priority"], 99),
256
+ x.get("_blocked", False),
257
+ 0 if x["status"] == WorkItemStatus.IN_PROGRESS.value else 1,
258
+ x.get("created_at", ""),
259
+ )
260
+ )
261
+
262
+ return items_list
263
+
264
+ def _display_items(self, items: list[dict]) -> None:
265
+ """Display items with color coding and indicators
266
+
267
+ Args:
268
+ items: List of items to display
269
+ """
270
+ if not items:
271
+ output.info("No work items found matching filters.")
272
+ return
273
+
274
+ # Count by status
275
+ status_counts = {
276
+ WorkItemStatus.NOT_STARTED.value: 0,
277
+ WorkItemStatus.IN_PROGRESS.value: 0,
278
+ WorkItemStatus.BLOCKED.value: 0,
279
+ WorkItemStatus.COMPLETED.value: 0,
280
+ }
281
+
282
+ for item in items:
283
+ if item.get("_blocked"):
284
+ status_counts[WorkItemStatus.BLOCKED.value] += 1
285
+ else:
286
+ status_counts[item["status"]] += 1
287
+
288
+ # Header
289
+ total = len(items)
290
+ output.info(
291
+ f"\nWork Items ({total} total, "
292
+ f"{status_counts[WorkItemStatus.IN_PROGRESS.value]} in progress, "
293
+ f"{status_counts[WorkItemStatus.NOT_STARTED.value]} not started, "
294
+ f"{status_counts[WorkItemStatus.COMPLETED.value]} completed)\n"
295
+ )
296
+
297
+ # Group by priority
298
+ priority_groups: dict[str, list[Any]] = {
299
+ Priority.CRITICAL.value: [],
300
+ Priority.HIGH.value: [],
301
+ Priority.MEDIUM.value: [],
302
+ Priority.LOW.value: [],
303
+ }
304
+
305
+ for item in items:
306
+ priority = item.get("priority", Priority.MEDIUM.value)
307
+ priority_groups[priority].append(item)
308
+
309
+ # Display each priority group
310
+ priority_emoji = {
311
+ Priority.CRITICAL.value: "🔴",
312
+ Priority.HIGH.value: "🟠",
313
+ Priority.MEDIUM.value: "🟡",
314
+ Priority.LOW.value: "🟢",
315
+ }
316
+
317
+ for priority in [
318
+ Priority.CRITICAL.value,
319
+ Priority.HIGH.value,
320
+ Priority.MEDIUM.value,
321
+ Priority.LOW.value,
322
+ ]:
323
+ group_items = priority_groups[priority]
324
+ if not group_items:
325
+ continue
326
+
327
+ output.info(f"{priority_emoji[priority]} {priority.upper()}")
328
+
329
+ for item in group_items:
330
+ status_icon = self._get_status_icon(item)
331
+ work_id = item["id"]
332
+
333
+ # Build status string
334
+ if item.get("_blocked"):
335
+ # Show blocking dependencies
336
+ deps = item.get("dependencies", [])[:2]
337
+ status_str = f"(blocked - waiting on: {', '.join(deps)}) 🚫"
338
+ elif item["status"] == WorkItemStatus.IN_PROGRESS.value:
339
+ sessions = len(item.get("sessions", []))
340
+ status_str = f"(in progress, session {sessions})"
341
+ elif item["status"] == WorkItemStatus.COMPLETED.value:
342
+ sessions = len(item.get("sessions", []))
343
+ status_str = f"(completed, {sessions} session{'s' if sessions != 1 else ''})"
344
+ elif item.get("_ready"):
345
+ status_str = "(ready to start) ✓"
346
+ else:
347
+ status_str = ""
348
+
349
+ output.info(f" {status_icon} {work_id} {status_str}")
350
+
351
+ output.info("")
352
+
353
+ # Legend
354
+ output.info("Legend:")
355
+ output.info(" [ ] Not started")
356
+ output.info(" [>>] In progress")
357
+ output.info(" [✓] Completed")
358
+ output.info(" 🚫 Blocked by dependencies")
359
+ output.info(" ✓ Ready to start")
360
+ output.info("")
361
+
362
+ def _get_status_icon(self, item: dict) -> str:
363
+ """Get status icon for work item
364
+
365
+ Args:
366
+ item: Work item
367
+
368
+ Returns:
369
+ str: Status icon
370
+ """
371
+ if item["status"] == WorkItemStatus.COMPLETED.value:
372
+ return "[✓]"
373
+ elif item["status"] == WorkItemStatus.IN_PROGRESS.value:
374
+ return "[>>]"
375
+ else:
376
+ return "[ ]"