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.
- solokit/__init__.py +10 -0
- solokit/__version__.py +3 -0
- solokit/cli.py +374 -0
- solokit/core/__init__.py +1 -0
- solokit/core/cache.py +102 -0
- solokit/core/command_runner.py +278 -0
- solokit/core/config.py +453 -0
- solokit/core/config_validator.py +204 -0
- solokit/core/constants.py +291 -0
- solokit/core/error_formatter.py +279 -0
- solokit/core/error_handlers.py +346 -0
- solokit/core/exceptions.py +1567 -0
- solokit/core/file_ops.py +309 -0
- solokit/core/logging_config.py +166 -0
- solokit/core/output.py +99 -0
- solokit/core/performance.py +57 -0
- solokit/core/protocols.py +141 -0
- solokit/core/types.py +312 -0
- solokit/deployment/__init__.py +1 -0
- solokit/deployment/executor.py +411 -0
- solokit/git/__init__.py +1 -0
- solokit/git/integration.py +619 -0
- solokit/init/__init__.py +41 -0
- solokit/init/claude_commands_installer.py +87 -0
- solokit/init/dependency_installer.py +313 -0
- solokit/init/docs_structure.py +90 -0
- solokit/init/env_generator.py +160 -0
- solokit/init/environment_validator.py +334 -0
- solokit/init/git_hooks_installer.py +71 -0
- solokit/init/git_setup.py +188 -0
- solokit/init/gitignore_updater.py +195 -0
- solokit/init/initial_commit.py +145 -0
- solokit/init/initial_scans.py +109 -0
- solokit/init/orchestrator.py +246 -0
- solokit/init/readme_generator.py +207 -0
- solokit/init/session_structure.py +239 -0
- solokit/init/template_installer.py +424 -0
- solokit/learning/__init__.py +1 -0
- solokit/learning/archiver.py +115 -0
- solokit/learning/categorizer.py +126 -0
- solokit/learning/curator.py +428 -0
- solokit/learning/extractor.py +352 -0
- solokit/learning/reporter.py +351 -0
- solokit/learning/repository.py +254 -0
- solokit/learning/similarity.py +342 -0
- solokit/learning/validator.py +144 -0
- solokit/project/__init__.py +1 -0
- solokit/project/init.py +1162 -0
- solokit/project/stack.py +436 -0
- solokit/project/sync_plugin.py +438 -0
- solokit/project/tree.py +375 -0
- solokit/quality/__init__.py +1 -0
- solokit/quality/api_validator.py +424 -0
- solokit/quality/checkers/__init__.py +25 -0
- solokit/quality/checkers/base.py +114 -0
- solokit/quality/checkers/context7.py +221 -0
- solokit/quality/checkers/custom.py +162 -0
- solokit/quality/checkers/deployment.py +323 -0
- solokit/quality/checkers/documentation.py +179 -0
- solokit/quality/checkers/formatting.py +161 -0
- solokit/quality/checkers/integration.py +394 -0
- solokit/quality/checkers/linting.py +159 -0
- solokit/quality/checkers/security.py +261 -0
- solokit/quality/checkers/spec_completeness.py +127 -0
- solokit/quality/checkers/tests.py +184 -0
- solokit/quality/env_validator.py +306 -0
- solokit/quality/gates.py +655 -0
- solokit/quality/reporters/__init__.py +10 -0
- solokit/quality/reporters/base.py +25 -0
- solokit/quality/reporters/console.py +98 -0
- solokit/quality/reporters/json_reporter.py +34 -0
- solokit/quality/results.py +98 -0
- solokit/session/__init__.py +1 -0
- solokit/session/briefing/__init__.py +245 -0
- solokit/session/briefing/documentation_loader.py +53 -0
- solokit/session/briefing/formatter.py +476 -0
- solokit/session/briefing/git_context.py +282 -0
- solokit/session/briefing/learning_loader.py +212 -0
- solokit/session/briefing/milestone_builder.py +78 -0
- solokit/session/briefing/orchestrator.py +137 -0
- solokit/session/briefing/stack_detector.py +51 -0
- solokit/session/briefing/tree_generator.py +52 -0
- solokit/session/briefing/work_item_loader.py +209 -0
- solokit/session/briefing.py +353 -0
- solokit/session/complete.py +1188 -0
- solokit/session/status.py +246 -0
- solokit/session/validate.py +452 -0
- solokit/templates/.claude/commands/end.md +109 -0
- solokit/templates/.claude/commands/init.md +159 -0
- solokit/templates/.claude/commands/learn-curate.md +88 -0
- solokit/templates/.claude/commands/learn-search.md +62 -0
- solokit/templates/.claude/commands/learn-show.md +69 -0
- solokit/templates/.claude/commands/learn.md +136 -0
- solokit/templates/.claude/commands/start.md +114 -0
- solokit/templates/.claude/commands/status.md +22 -0
- solokit/templates/.claude/commands/validate.md +27 -0
- solokit/templates/.claude/commands/work-delete.md +119 -0
- solokit/templates/.claude/commands/work-graph.md +139 -0
- solokit/templates/.claude/commands/work-list.md +26 -0
- solokit/templates/.claude/commands/work-new.md +114 -0
- solokit/templates/.claude/commands/work-next.md +25 -0
- solokit/templates/.claude/commands/work-show.md +24 -0
- solokit/templates/.claude/commands/work-update.md +141 -0
- solokit/templates/CHANGELOG.md +17 -0
- solokit/templates/WORK_ITEM_TYPES.md +141 -0
- solokit/templates/__init__.py +1 -0
- solokit/templates/bug_spec.md +217 -0
- solokit/templates/config.schema.json +150 -0
- solokit/templates/dashboard_refine/base/.gitignore +36 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/layout.tsx +22 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/page.tsx +68 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/users/page.tsx +77 -0
- solokit/templates/dashboard_refine/base/app/globals.css +60 -0
- solokit/templates/dashboard_refine/base/app/layout.tsx +23 -0
- solokit/templates/dashboard_refine/base/app/page.tsx +9 -0
- solokit/templates/dashboard_refine/base/components/client-refine-wrapper.tsx +21 -0
- solokit/templates/dashboard_refine/base/components/layout/header.tsx +44 -0
- solokit/templates/dashboard_refine/base/components/layout/sidebar.tsx +82 -0
- solokit/templates/dashboard_refine/base/components/ui/button.tsx +53 -0
- solokit/templates/dashboard_refine/base/components/ui/card.tsx +78 -0
- solokit/templates/dashboard_refine/base/components/ui/table.tsx +116 -0
- solokit/templates/dashboard_refine/base/components.json +16 -0
- solokit/templates/dashboard_refine/base/lib/refine.tsx +65 -0
- solokit/templates/dashboard_refine/base/lib/utils.ts +13 -0
- solokit/templates/dashboard_refine/base/next.config.ts +10 -0
- solokit/templates/dashboard_refine/base/package.json.template +40 -0
- solokit/templates/dashboard_refine/base/postcss.config.mjs +8 -0
- solokit/templates/dashboard_refine/base/providers/refine-provider.tsx +26 -0
- solokit/templates/dashboard_refine/base/tailwind.config.ts +57 -0
- solokit/templates/dashboard_refine/base/tsconfig.json +27 -0
- solokit/templates/dashboard_refine/docker/Dockerfile +57 -0
- solokit/templates/dashboard_refine/docker/docker-compose.prod.yml +31 -0
- solokit/templates/dashboard_refine/docker/docker-compose.yml +21 -0
- solokit/templates/dashboard_refine/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/dashboard_refine/tier-1-essential/package.json.tier1.template +57 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/setup.ts +26 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/unit/example.test.tsx +73 -0
- solokit/templates/dashboard_refine/tier-2-standard/package.json.tier2.template +62 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/eslint.config.mjs +22 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/package.json.tier3.template +79 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/stryker.conf.json +38 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/dashboard.spec.ts +88 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/user-management.spec.ts +102 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/integration/dashboard.test.tsx +90 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/type-coverage.json +16 -0
- solokit/templates/dashboard_refine/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/dashboard_refine/tier-4-production/k6/dashboard-load-test.js +70 -0
- solokit/templates/dashboard_refine/tier-4-production/next.config.ts +46 -0
- solokit/templates/dashboard_refine/tier-4-production/package.json.tier4.template +89 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/deployment_spec.md +500 -0
- solokit/templates/feature_spec.md +248 -0
- solokit/templates/fullstack_nextjs/base/.gitignore +36 -0
- solokit/templates/fullstack_nextjs/base/app/api/example/route.ts +65 -0
- solokit/templates/fullstack_nextjs/base/app/globals.css +27 -0
- solokit/templates/fullstack_nextjs/base/app/layout.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/app/page.tsx +32 -0
- solokit/templates/fullstack_nextjs/base/components/example-component.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/lib/prisma.ts +17 -0
- solokit/templates/fullstack_nextjs/base/lib/utils.ts +13 -0
- solokit/templates/fullstack_nextjs/base/lib/validations.ts +20 -0
- solokit/templates/fullstack_nextjs/base/next.config.ts +7 -0
- solokit/templates/fullstack_nextjs/base/package.json.template +32 -0
- solokit/templates/fullstack_nextjs/base/postcss.config.mjs +8 -0
- solokit/templates/fullstack_nextjs/base/prisma/schema.prisma +21 -0
- solokit/templates/fullstack_nextjs/base/tailwind.config.ts +19 -0
- solokit/templates/fullstack_nextjs/base/tsconfig.json +27 -0
- solokit/templates/fullstack_nextjs/docker/Dockerfile +60 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.prod.yml +57 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.yml +47 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/package.json.tier1.template +48 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/api/example.test.ts +88 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/unit/example.test.tsx +22 -0
- solokit/templates/fullstack_nextjs/tier-2-standard/package.json.tier2.template +52 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/package.json.tier3.template +68 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/stryker.conf.json +33 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/e2e/flow.spec.ts +59 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/integration/api.test.ts +165 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/fullstack_nextjs/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/fullstack_nextjs/tier-4-production/k6/load-test.js +45 -0
- solokit/templates/fullstack_nextjs/tier-4-production/next.config.ts +46 -0
- solokit/templates/fullstack_nextjs/tier-4-production/package.json.tier4.template +77 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/git-hooks/prepare-commit-msg +24 -0
- solokit/templates/integration_test_spec.md +363 -0
- solokit/templates/learnings.json +15 -0
- solokit/templates/ml_ai_fastapi/base/.gitignore +104 -0
- solokit/templates/ml_ai_fastapi/base/alembic/env.py +96 -0
- solokit/templates/ml_ai_fastapi/base/alembic.ini +114 -0
- solokit/templates/ml_ai_fastapi/base/pyproject.toml.template +91 -0
- solokit/templates/ml_ai_fastapi/base/requirements.txt.template +28 -0
- solokit/templates/ml_ai_fastapi/base/src/__init__.py +5 -0
- solokit/templates/ml_ai_fastapi/base/src/api/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/dependencies.py +20 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/example.py +134 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/health.py +66 -0
- solokit/templates/ml_ai_fastapi/base/src/core/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/core/config.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/core/database.py +50 -0
- solokit/templates/ml_ai_fastapi/base/src/main.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/models/__init__.py +7 -0
- solokit/templates/ml_ai_fastapi/base/src/models/example.py +61 -0
- solokit/templates/ml_ai_fastapi/base/src/services/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/services/example.py +115 -0
- solokit/templates/ml_ai_fastapi/docker/Dockerfile +59 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.prod.yml +112 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.yml +77 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyproject.toml.tier1.template +112 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyrightconfig.json +41 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pytest.ini +69 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/requirements-dev.txt +17 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/ruff.toml +81 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/conftest.py +72 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/test_main.py +49 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/test_example.py +113 -0
- solokit/templates/ml_ai_fastapi/tier-2-standard/pyproject.toml.tier2.template +130 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/locustfile.py +99 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/mutmut_config.py +53 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/pyproject.toml.tier3.template +150 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/conftest.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/test_api.py +131 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/pyproject.toml.tier4.template +162 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/requirements-prod.txt +25 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/api/routes/metrics.py +19 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/logging.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/monitoring.py +68 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/sentry.py +66 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/logging.py +79 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/tracing.py +60 -0
- solokit/templates/refactor_spec.md +287 -0
- solokit/templates/saas_t3/base/.gitignore +36 -0
- solokit/templates/saas_t3/base/app/api/trpc/[trpc]/route.ts +33 -0
- solokit/templates/saas_t3/base/app/globals.css +27 -0
- solokit/templates/saas_t3/base/app/layout.tsx +23 -0
- solokit/templates/saas_t3/base/app/page.tsx +31 -0
- solokit/templates/saas_t3/base/lib/api.tsx +77 -0
- solokit/templates/saas_t3/base/lib/utils.ts +13 -0
- solokit/templates/saas_t3/base/next.config.ts +7 -0
- solokit/templates/saas_t3/base/package.json.template +38 -0
- solokit/templates/saas_t3/base/postcss.config.mjs +8 -0
- solokit/templates/saas_t3/base/prisma/schema.prisma +20 -0
- solokit/templates/saas_t3/base/server/api/root.ts +19 -0
- solokit/templates/saas_t3/base/server/api/routers/example.ts +28 -0
- solokit/templates/saas_t3/base/server/api/trpc.ts +52 -0
- solokit/templates/saas_t3/base/server/db.ts +17 -0
- solokit/templates/saas_t3/base/tailwind.config.ts +19 -0
- solokit/templates/saas_t3/base/tsconfig.json +27 -0
- solokit/templates/saas_t3/docker/Dockerfile +60 -0
- solokit/templates/saas_t3/docker/docker-compose.prod.yml +59 -0
- solokit/templates/saas_t3/docker/docker-compose.yml +49 -0
- solokit/templates/saas_t3/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/saas_t3/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/saas_t3/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/saas_t3/tier-1-essential/package.json.tier1.template +54 -0
- solokit/templates/saas_t3/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/saas_t3/tier-1-essential/tests/unit/example.test.tsx +24 -0
- solokit/templates/saas_t3/tier-2-standard/package.json.tier2.template +58 -0
- solokit/templates/saas_t3/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/saas_t3/tier-3-comprehensive/package.json.tier3.template +74 -0
- solokit/templates/saas_t3/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/saas_t3/tier-3-comprehensive/stryker.conf.json +34 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/e2e/home.spec.ts +41 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/integration/api.test.ts +44 -0
- solokit/templates/saas_t3/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/saas_t3/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/saas_t3/tier-4-production/k6/load-test.js +51 -0
- solokit/templates/saas_t3/tier-4-production/next.config.ts +46 -0
- solokit/templates/saas_t3/tier-4-production/package.json.tier4.template +83 -0
- solokit/templates/saas_t3/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/saas_t3/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/vercel.json +37 -0
- solokit/templates/security_spec.md +287 -0
- solokit/templates/stack-versions.yaml +617 -0
- solokit/templates/status_update.json +6 -0
- solokit/templates/template-registry.json +257 -0
- solokit/templates/work_items.json +11 -0
- solokit/testing/__init__.py +1 -0
- solokit/testing/integration_runner.py +550 -0
- solokit/testing/performance.py +637 -0
- solokit/visualization/__init__.py +1 -0
- solokit/visualization/dependency_graph.py +788 -0
- solokit/work_items/__init__.py +1 -0
- solokit/work_items/creator.py +217 -0
- solokit/work_items/delete.py +264 -0
- solokit/work_items/get_dependencies.py +185 -0
- solokit/work_items/get_dependents.py +113 -0
- solokit/work_items/get_metadata.py +121 -0
- solokit/work_items/get_next_recommendations.py +133 -0
- solokit/work_items/manager.py +235 -0
- solokit/work_items/milestones.py +137 -0
- solokit/work_items/query.py +376 -0
- solokit/work_items/repository.py +267 -0
- solokit/work_items/scheduler.py +184 -0
- solokit/work_items/spec_parser.py +838 -0
- solokit/work_items/spec_validator.py +493 -0
- solokit/work_items/updater.py +157 -0
- solokit/work_items/validator.py +205 -0
- solokit-0.1.1.dist-info/METADATA +640 -0
- solokit-0.1.1.dist-info/RECORD +323 -0
- solokit-0.1.1.dist-info/WHEEL +5 -0
- solokit-0.1.1.dist-info/entry_points.txt +2 -0
- solokit-0.1.1.dist-info/licenses/LICENSE +21 -0
- 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 "[ ]"
|