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,267 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Work Item Repository - Data access and persistence layer.
|
|
4
|
+
|
|
5
|
+
Handles CRUD operations for work items and milestones in work_items.json.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from solokit.core.cache import FileCache
|
|
15
|
+
from solokit.core.file_ops import load_json, save_json
|
|
16
|
+
from solokit.core.logging_config import get_logger
|
|
17
|
+
from solokit.core.performance import measure_time
|
|
18
|
+
from solokit.core.types import WorkItemStatus
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WorkItemRepository:
|
|
24
|
+
"""Repository for work item data access and persistence with caching"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, session_dir: Path):
|
|
27
|
+
"""Initialize repository with session directory
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
session_dir: Path to .session directory
|
|
31
|
+
"""
|
|
32
|
+
self.session_dir = session_dir
|
|
33
|
+
self.work_items_file = session_dir / "tracking" / "work_items.json"
|
|
34
|
+
self._file_cache = FileCache()
|
|
35
|
+
|
|
36
|
+
@measure_time("load_work_items")
|
|
37
|
+
def load_all(self) -> dict[str, Any]:
|
|
38
|
+
"""Load all work items and milestones from work_items.json with caching
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
dict: Complete work items data including work_items and milestones
|
|
42
|
+
"""
|
|
43
|
+
if not self.work_items_file.exists():
|
|
44
|
+
return {"work_items": {}, "milestones": {}}
|
|
45
|
+
|
|
46
|
+
return cast(dict[str, Any], self._file_cache.load_json(self.work_items_file, load_json))
|
|
47
|
+
|
|
48
|
+
def save_all(self, data: dict[str, Any]) -> None:
|
|
49
|
+
"""Save all work items and milestones to work_items.json
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
data: Complete work items data to save
|
|
53
|
+
"""
|
|
54
|
+
# Update metadata counters before saving
|
|
55
|
+
self._update_metadata(data)
|
|
56
|
+
save_json(self.work_items_file, data)
|
|
57
|
+
# Invalidate cache after write
|
|
58
|
+
self._file_cache.invalidate(self.work_items_file)
|
|
59
|
+
|
|
60
|
+
def get_work_item(self, work_id: str) -> dict[str, Any] | None:
|
|
61
|
+
"""Get a single work item by ID
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
work_id: Work item ID
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
dict: Work item data, or None if not found
|
|
68
|
+
"""
|
|
69
|
+
data = self.load_all()
|
|
70
|
+
work_items = data.get("work_items", {})
|
|
71
|
+
result = work_items.get(work_id)
|
|
72
|
+
return result if result is None else dict(result)
|
|
73
|
+
|
|
74
|
+
def get_all_work_items(self) -> dict[str, Any]:
|
|
75
|
+
"""Get all work items
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
dict: All work items keyed by ID
|
|
79
|
+
"""
|
|
80
|
+
data = self.load_all()
|
|
81
|
+
return dict(data.get("work_items", {}))
|
|
82
|
+
|
|
83
|
+
def work_item_exists(self, work_id: str) -> bool:
|
|
84
|
+
"""Check if work item exists
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
work_id: Work item ID
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
bool: True if work item exists
|
|
91
|
+
"""
|
|
92
|
+
return self.get_work_item(work_id) is not None
|
|
93
|
+
|
|
94
|
+
def add_work_item(
|
|
95
|
+
self,
|
|
96
|
+
work_id: str,
|
|
97
|
+
work_type: str,
|
|
98
|
+
title: str,
|
|
99
|
+
priority: str,
|
|
100
|
+
dependencies: list[str],
|
|
101
|
+
spec_file: str = "",
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Add a new work item to tracking
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
work_id: Unique work item ID
|
|
107
|
+
work_type: Type of work item
|
|
108
|
+
title: Work item title
|
|
109
|
+
priority: Priority level
|
|
110
|
+
dependencies: List of dependency IDs
|
|
111
|
+
spec_file: Relative path to spec file
|
|
112
|
+
"""
|
|
113
|
+
data = self.load_all()
|
|
114
|
+
|
|
115
|
+
work_item = {
|
|
116
|
+
"id": work_id,
|
|
117
|
+
"type": work_type,
|
|
118
|
+
"title": title,
|
|
119
|
+
"status": WorkItemStatus.NOT_STARTED.value,
|
|
120
|
+
"priority": priority,
|
|
121
|
+
"dependencies": dependencies,
|
|
122
|
+
"milestone": "",
|
|
123
|
+
"spec_file": spec_file,
|
|
124
|
+
"created_at": datetime.now().isoformat(),
|
|
125
|
+
"sessions": [],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
data.setdefault("work_items", {})[work_id] = work_item
|
|
129
|
+
self.save_all(data)
|
|
130
|
+
logger.info("Added work item: %s", work_id)
|
|
131
|
+
|
|
132
|
+
def update_work_item(self, work_id: str, updates: dict[str, Any]) -> None:
|
|
133
|
+
"""Update a work item with new field values
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
work_id: Work item ID
|
|
137
|
+
updates: Dictionary of field updates
|
|
138
|
+
"""
|
|
139
|
+
data = self.load_all()
|
|
140
|
+
items = data.get("work_items", {})
|
|
141
|
+
|
|
142
|
+
if work_id not in items:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
item = items[work_id]
|
|
146
|
+
|
|
147
|
+
# Apply updates
|
|
148
|
+
for field, value in updates.items():
|
|
149
|
+
if field == "add_dependency":
|
|
150
|
+
deps = item.get("dependencies", [])
|
|
151
|
+
if value not in deps:
|
|
152
|
+
deps.append(value)
|
|
153
|
+
item["dependencies"] = deps
|
|
154
|
+
elif field == "remove_dependency":
|
|
155
|
+
deps = item.get("dependencies", [])
|
|
156
|
+
if value in deps:
|
|
157
|
+
deps.remove(value)
|
|
158
|
+
item["dependencies"] = deps
|
|
159
|
+
else:
|
|
160
|
+
item[field] = value
|
|
161
|
+
|
|
162
|
+
data["work_items"][work_id] = item
|
|
163
|
+
self.save_all(data)
|
|
164
|
+
logger.debug("Updated work item: %s", work_id)
|
|
165
|
+
|
|
166
|
+
def delete_work_item(self, work_id: str) -> bool:
|
|
167
|
+
"""Delete a work item
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
work_id: Work item ID
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
bool: True if deleted, False if not found
|
|
174
|
+
"""
|
|
175
|
+
data = self.load_all()
|
|
176
|
+
items = data.get("work_items", {})
|
|
177
|
+
|
|
178
|
+
if work_id not in items:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
del items[work_id]
|
|
182
|
+
data["work_items"] = items
|
|
183
|
+
self.save_all(data)
|
|
184
|
+
logger.info("Deleted work item: %s", work_id)
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
def get_milestone(self, name: str) -> dict[str, Any] | None:
|
|
188
|
+
"""Get a milestone by name
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
name: Milestone name
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
dict: Milestone data, or None if not found
|
|
195
|
+
"""
|
|
196
|
+
data = self.load_all()
|
|
197
|
+
milestones = data.get("milestones", {})
|
|
198
|
+
result = milestones.get(name)
|
|
199
|
+
return result if result is None else dict(result)
|
|
200
|
+
|
|
201
|
+
def get_all_milestones(self) -> dict[str, Any]:
|
|
202
|
+
"""Get all milestones
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
dict: All milestones keyed by name
|
|
206
|
+
"""
|
|
207
|
+
data = self.load_all()
|
|
208
|
+
return dict(data.get("milestones", {}))
|
|
209
|
+
|
|
210
|
+
def milestone_exists(self, name: str) -> bool:
|
|
211
|
+
"""Check if milestone exists
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
name: Milestone name
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
bool: True if milestone exists
|
|
218
|
+
"""
|
|
219
|
+
return self.get_milestone(name) is not None
|
|
220
|
+
|
|
221
|
+
def add_milestone(
|
|
222
|
+
self, name: str, title: str, description: str, target_date: str | None = None
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Add a new milestone
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
name: Unique milestone name
|
|
228
|
+
title: Milestone title
|
|
229
|
+
description: Milestone description
|
|
230
|
+
target_date: Optional target completion date
|
|
231
|
+
"""
|
|
232
|
+
data = self.load_all()
|
|
233
|
+
|
|
234
|
+
milestone = {
|
|
235
|
+
"name": name,
|
|
236
|
+
"title": title,
|
|
237
|
+
"description": description,
|
|
238
|
+
"target_date": target_date or "",
|
|
239
|
+
"status": "not_started",
|
|
240
|
+
"created_at": datetime.now().isoformat(),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
data.setdefault("milestones", {})[name] = milestone
|
|
244
|
+
self.save_all(data)
|
|
245
|
+
logger.info("Added milestone: %s", name)
|
|
246
|
+
|
|
247
|
+
def _update_metadata(self, data: dict[str, Any]) -> None:
|
|
248
|
+
"""Update metadata counters
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
data: Work items data to update metadata for
|
|
252
|
+
"""
|
|
253
|
+
if "metadata" not in data:
|
|
254
|
+
data["metadata"] = {}
|
|
255
|
+
|
|
256
|
+
work_items = data.get("work_items", {})
|
|
257
|
+
data["metadata"]["total_items"] = len(work_items)
|
|
258
|
+
data["metadata"]["completed"] = sum(
|
|
259
|
+
1 for item in work_items.values() if item["status"] == WorkItemStatus.COMPLETED.value
|
|
260
|
+
)
|
|
261
|
+
data["metadata"]["in_progress"] = sum(
|
|
262
|
+
1 for item in work_items.values() if item["status"] == WorkItemStatus.IN_PROGRESS.value
|
|
263
|
+
)
|
|
264
|
+
data["metadata"]["blocked"] = sum(
|
|
265
|
+
1 for item in work_items.values() if item["status"] == WorkItemStatus.BLOCKED.value
|
|
266
|
+
)
|
|
267
|
+
data["metadata"]["last_updated"] = datetime.now().isoformat()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Work Item Scheduler - Work queue and next item selection.
|
|
4
|
+
|
|
5
|
+
Handles finding the next work item to start based on dependencies and priority.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from solokit.core.logging_config import get_logger
|
|
13
|
+
from solokit.core.types import Priority, WorkItemStatus
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .repository import WorkItemRepository
|
|
17
|
+
from solokit.core.output import get_output
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
output = get_output()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WorkItemScheduler:
|
|
24
|
+
"""Handles work item scheduling and queue management"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, repository: WorkItemRepository):
|
|
27
|
+
"""Initialize scheduler with repository
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
repository: WorkItemRepository instance for data access
|
|
31
|
+
"""
|
|
32
|
+
self.repository = repository
|
|
33
|
+
|
|
34
|
+
def get_next(self) -> dict[str, Any] | None:
|
|
35
|
+
"""Find next work item to start based on dependencies and priority
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
dict: Next work item to start, or None if none available
|
|
39
|
+
"""
|
|
40
|
+
items = self.repository.get_all_work_items()
|
|
41
|
+
|
|
42
|
+
if not items:
|
|
43
|
+
output.info("No work items found.")
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
# Filter to not_started items
|
|
47
|
+
not_started = {
|
|
48
|
+
wid: item
|
|
49
|
+
for wid, item in items.items()
|
|
50
|
+
if item["status"] == WorkItemStatus.NOT_STARTED.value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if not not_started:
|
|
54
|
+
output.info("No work items available to start.")
|
|
55
|
+
output.info("All items are either in progress or completed.")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# Check dependencies and categorize
|
|
59
|
+
ready_items = []
|
|
60
|
+
blocked_items = []
|
|
61
|
+
|
|
62
|
+
for work_id, item in not_started.items():
|
|
63
|
+
is_blocked = self._is_blocked(item, items)
|
|
64
|
+
if is_blocked:
|
|
65
|
+
# Find what's blocking
|
|
66
|
+
blocking = [
|
|
67
|
+
dep_id
|
|
68
|
+
for dep_id in item.get("dependencies", [])
|
|
69
|
+
if items.get(dep_id, {}).get("status") != WorkItemStatus.COMPLETED.value
|
|
70
|
+
]
|
|
71
|
+
blocked_items.append((work_id, item, blocking))
|
|
72
|
+
else:
|
|
73
|
+
ready_items.append((work_id, item))
|
|
74
|
+
|
|
75
|
+
if not ready_items:
|
|
76
|
+
output.info("No work items ready to start. All have unmet dependencies.\n")
|
|
77
|
+
output.info("Blocked items:")
|
|
78
|
+
for work_id, item, blocking in blocked_items:
|
|
79
|
+
output.info(f" 🔴 {work_id} - Blocked by: {', '.join(blocking)}")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# Sort ready items by priority
|
|
83
|
+
priority_order = {
|
|
84
|
+
Priority.CRITICAL.value: 0,
|
|
85
|
+
Priority.HIGH.value: 1,
|
|
86
|
+
Priority.MEDIUM.value: 2,
|
|
87
|
+
Priority.LOW.value: 3,
|
|
88
|
+
}
|
|
89
|
+
ready_items.sort(key=lambda x: priority_order.get(x[1]["priority"], 99))
|
|
90
|
+
|
|
91
|
+
# Get top item
|
|
92
|
+
next_id, next_item = ready_items[0]
|
|
93
|
+
|
|
94
|
+
# Display
|
|
95
|
+
self._display_next_item(next_id, next_item, ready_items, blocked_items, items)
|
|
96
|
+
|
|
97
|
+
return next_item # type: ignore[no-any-return]
|
|
98
|
+
|
|
99
|
+
def _is_blocked(self, item: dict, all_items: dict) -> bool:
|
|
100
|
+
"""Check if work item is blocked by dependencies
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
item: Work item to check
|
|
104
|
+
all_items: All work items
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
bool: True if blocked
|
|
108
|
+
"""
|
|
109
|
+
dependencies = item.get("dependencies", [])
|
|
110
|
+
if not dependencies:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
for dep_id in dependencies:
|
|
114
|
+
if dep_id not in all_items:
|
|
115
|
+
continue
|
|
116
|
+
if all_items[dep_id]["status"] != WorkItemStatus.COMPLETED.value:
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def _display_next_item(
|
|
122
|
+
self,
|
|
123
|
+
next_id: str,
|
|
124
|
+
next_item: dict,
|
|
125
|
+
ready_items: list,
|
|
126
|
+
blocked_items: list,
|
|
127
|
+
all_items: dict,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Display the next recommended work item
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
next_id: ID of next item
|
|
133
|
+
next_item: Next item data
|
|
134
|
+
ready_items: List of ready items
|
|
135
|
+
blocked_items: List of blocked items
|
|
136
|
+
all_items: All work items
|
|
137
|
+
"""
|
|
138
|
+
output.info("\nNext Recommended Work Item:")
|
|
139
|
+
output.info("=" * 80)
|
|
140
|
+
output.info("")
|
|
141
|
+
|
|
142
|
+
priority_emoji = {
|
|
143
|
+
Priority.CRITICAL.value: "🔴",
|
|
144
|
+
Priority.HIGH.value: "🟠",
|
|
145
|
+
Priority.MEDIUM.value: "🟡",
|
|
146
|
+
Priority.LOW.value: "🟢",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
emoji = priority_emoji.get(next_item["priority"], "")
|
|
150
|
+
output.info(f"{emoji} {next_item['priority'].upper()}: {next_item['title']}")
|
|
151
|
+
output.info(f"ID: {next_id}")
|
|
152
|
+
output.info(f"Type: {next_item['type']}")
|
|
153
|
+
output.info(f"Priority: {next_item['priority']}")
|
|
154
|
+
output.info("Ready to start: Yes ✓")
|
|
155
|
+
output.info("")
|
|
156
|
+
|
|
157
|
+
# Dependencies
|
|
158
|
+
deps = next_item.get("dependencies", [])
|
|
159
|
+
if deps:
|
|
160
|
+
output.info("Dependencies: All satisfied")
|
|
161
|
+
for dep_id in deps:
|
|
162
|
+
output.info(f" ✓ {dep_id} (completed)")
|
|
163
|
+
else:
|
|
164
|
+
output.info("Dependencies: None")
|
|
165
|
+
output.info("")
|
|
166
|
+
|
|
167
|
+
# Estimated effort
|
|
168
|
+
estimated = next_item.get("estimated_effort", "Unknown")
|
|
169
|
+
output.info(f"Estimated effort: {estimated}")
|
|
170
|
+
output.info("")
|
|
171
|
+
|
|
172
|
+
output.info("To start: /start")
|
|
173
|
+
output.info("")
|
|
174
|
+
|
|
175
|
+
# Show other items
|
|
176
|
+
if len(ready_items) > 1 or blocked_items:
|
|
177
|
+
output.info("Other items waiting:")
|
|
178
|
+
for work_id, item in ready_items[1:3]: # Show next 2 ready items
|
|
179
|
+
emoji = priority_emoji.get(item["priority"], "")
|
|
180
|
+
output.info(f" {emoji} {work_id} - Ready ({item['priority']} priority)")
|
|
181
|
+
|
|
182
|
+
for work_id, item, blocking in blocked_items[:2]: # Show 2 blocked items
|
|
183
|
+
output.info(f" 🔴 {work_id} - Blocked by: {', '.join(blocking[:2])}")
|
|
184
|
+
output.info("")
|