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,428 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Learning curation orchestrator
|
|
4
|
+
|
|
5
|
+
Coordinates learning curation using specialized modules:
|
|
6
|
+
1. Categorization - Auto-categorize learnings
|
|
7
|
+
2. Similarity detection - Merge similar learnings
|
|
8
|
+
3. Archiving - Archive old learnings
|
|
9
|
+
4. Extraction - Extract learnings from various sources
|
|
10
|
+
5. Repository - Data persistence and CRUD
|
|
11
|
+
6. Reporter - Reports and statistics
|
|
12
|
+
7. Validator - Learning validation
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from solokit.core.constants import MAX_LEARNING_AGE_SESSIONS
|
|
22
|
+
from solokit.core.error_handlers import log_errors
|
|
23
|
+
from solokit.core.exceptions import FileNotFoundError as SolokitFileNotFoundError
|
|
24
|
+
from solokit.core.logging_config import get_logger
|
|
25
|
+
from solokit.core.output import get_output
|
|
26
|
+
from solokit.learning.archiver import LearningArchiver
|
|
27
|
+
from solokit.learning.categorizer import LearningCategorizer
|
|
28
|
+
from solokit.learning.extractor import LearningExtractor
|
|
29
|
+
from solokit.learning.reporter import LearningReporter
|
|
30
|
+
from solokit.learning.repository import LearningRepository
|
|
31
|
+
from solokit.learning.similarity import LearningSimilarityEngine
|
|
32
|
+
from solokit.learning.validator import LearningValidator
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
output = get_output()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LearningsCurator:
|
|
39
|
+
"""Main orchestrator for learning curation - delegates to specialized modules"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, project_root: Path | None = None):
|
|
42
|
+
"""
|
|
43
|
+
Initialize LearningsCurator with dependency injection
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
project_root: Path to project root directory
|
|
47
|
+
"""
|
|
48
|
+
if project_root is None:
|
|
49
|
+
project_root = Path.cwd()
|
|
50
|
+
|
|
51
|
+
self.project_root = project_root
|
|
52
|
+
self.session_dir = project_root / ".session"
|
|
53
|
+
|
|
54
|
+
# Initialize all components
|
|
55
|
+
self.repository = LearningRepository(self.session_dir)
|
|
56
|
+
self.similarity_engine = LearningSimilarityEngine()
|
|
57
|
+
self.categorizer = LearningCategorizer()
|
|
58
|
+
self.archiver = LearningArchiver(self.session_dir)
|
|
59
|
+
self.extractor = LearningExtractor(self.session_dir, self.project_root)
|
|
60
|
+
self.reporter = LearningReporter(self.repository)
|
|
61
|
+
self.validator = LearningValidator()
|
|
62
|
+
|
|
63
|
+
@log_errors()
|
|
64
|
+
def curate(self, dry_run: bool = False) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Curate learnings - main orchestration method
|
|
67
|
+
|
|
68
|
+
Orchestrates the entire curation workflow:
|
|
69
|
+
1. Load existing learnings
|
|
70
|
+
2. Categorize uncategorized learnings
|
|
71
|
+
3. Merge similar learnings
|
|
72
|
+
4. Archive old learnings
|
|
73
|
+
5. Update metadata
|
|
74
|
+
6. Save results (unless dry_run)
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
dry_run: If True, show changes without saving
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
FileOperationError: If reading/writing learnings file fails
|
|
81
|
+
ValidationError: If learning data is invalid
|
|
82
|
+
"""
|
|
83
|
+
logger.info("Starting learning curation (dry_run=%s)", dry_run)
|
|
84
|
+
output.section("Learning Curation")
|
|
85
|
+
|
|
86
|
+
# Load existing learnings
|
|
87
|
+
learnings = self.repository.load_learnings()
|
|
88
|
+
initial_count = self.repository.count_all_learnings(learnings)
|
|
89
|
+
output.info(f"Initial learnings: {initial_count}\n")
|
|
90
|
+
|
|
91
|
+
# Categorize uncategorized learnings
|
|
92
|
+
categorized = self._categorize_learnings(learnings)
|
|
93
|
+
output.info(f"✓ Categorized {categorized} learnings")
|
|
94
|
+
|
|
95
|
+
# Merge similar learnings
|
|
96
|
+
merged = self.similarity_engine.merge_similar_learnings(learnings)
|
|
97
|
+
output.info(f"✓ Merged {merged} duplicate learnings")
|
|
98
|
+
|
|
99
|
+
# Archive old learnings
|
|
100
|
+
archived = self.archiver.archive_old_learnings(learnings)
|
|
101
|
+
output.info(f"✓ Archived {archived} old learnings")
|
|
102
|
+
|
|
103
|
+
# Update metadata
|
|
104
|
+
learnings["last_curated"] = datetime.now().isoformat()
|
|
105
|
+
learnings["curator"] = "session_curator"
|
|
106
|
+
self.repository.update_total_learnings(learnings)
|
|
107
|
+
|
|
108
|
+
final_count = self.repository.count_all_learnings(learnings)
|
|
109
|
+
output.info(f"\nFinal learnings: {final_count}\n")
|
|
110
|
+
|
|
111
|
+
if not dry_run:
|
|
112
|
+
self.repository.save_learnings(learnings)
|
|
113
|
+
output.success("Learnings saved\n")
|
|
114
|
+
else:
|
|
115
|
+
output.info("Dry run - no changes saved\n")
|
|
116
|
+
|
|
117
|
+
def _categorize_learnings(self, learnings: dict) -> int:
|
|
118
|
+
"""
|
|
119
|
+
Categorize uncategorized learnings using extractor and categorizer
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
learnings: Learnings dictionary
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Number of learnings categorized
|
|
126
|
+
"""
|
|
127
|
+
categorized_count = 0
|
|
128
|
+
|
|
129
|
+
# Extract learnings from session summaries
|
|
130
|
+
# Use the wrapper method for test compatibility
|
|
131
|
+
new_learnings = self._extract_learnings_from_sessions()
|
|
132
|
+
|
|
133
|
+
for learning in new_learnings:
|
|
134
|
+
# Auto-categorize using categorizer
|
|
135
|
+
category = self.categorizer.categorize_learning(learning)
|
|
136
|
+
|
|
137
|
+
# Add to appropriate category
|
|
138
|
+
categories = learnings.setdefault("categories", {})
|
|
139
|
+
if category not in categories:
|
|
140
|
+
categories[category] = []
|
|
141
|
+
|
|
142
|
+
# Check if already exists using similarity engine
|
|
143
|
+
if not self.repository.learning_exists(
|
|
144
|
+
categories[category], learning, self.similarity_engine
|
|
145
|
+
):
|
|
146
|
+
categories[category].append(learning)
|
|
147
|
+
categorized_count += 1
|
|
148
|
+
|
|
149
|
+
logger.info(f"Categorized {categorized_count} learnings")
|
|
150
|
+
return categorized_count
|
|
151
|
+
|
|
152
|
+
def auto_curate_if_needed(self) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Auto-curate based on configuration and last curation time
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
True if curation was performed, False otherwise
|
|
158
|
+
"""
|
|
159
|
+
config = self.repository.get_curation_config()
|
|
160
|
+
|
|
161
|
+
if not config.auto_curate:
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
learnings = self.repository.load_learnings()
|
|
165
|
+
last_curated = learnings.get("last_curated")
|
|
166
|
+
|
|
167
|
+
if not last_curated:
|
|
168
|
+
# Never curated, do it now
|
|
169
|
+
output.info("Auto-curating (first time)...\n")
|
|
170
|
+
self.curate(dry_run=False)
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
# Check frequency
|
|
174
|
+
last_date = datetime.fromisoformat(last_curated)
|
|
175
|
+
days_since = (datetime.now() - last_date).days
|
|
176
|
+
|
|
177
|
+
frequency_days = config.frequency
|
|
178
|
+
|
|
179
|
+
if days_since >= frequency_days:
|
|
180
|
+
output.info(f"Auto-curating (last curated {days_since} days ago)...\n")
|
|
181
|
+
self.curate(dry_run=False)
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# Delegate methods to appropriate components
|
|
187
|
+
|
|
188
|
+
@log_errors()
|
|
189
|
+
def add_learning(
|
|
190
|
+
self,
|
|
191
|
+
content: str,
|
|
192
|
+
category: str,
|
|
193
|
+
session: int | None = None,
|
|
194
|
+
tags: list | None = None,
|
|
195
|
+
context: str | None = None,
|
|
196
|
+
) -> str:
|
|
197
|
+
"""Add a new learning (delegates to repository)"""
|
|
198
|
+
return self.repository.add_learning(content, category, session, tags, context)
|
|
199
|
+
|
|
200
|
+
def add_learning_if_new(self, learning_dict: dict) -> bool:
|
|
201
|
+
"""Add learning if it doesn't already exist (delegates to repository)"""
|
|
202
|
+
return self.repository.add_learning_if_new(learning_dict, self.similarity_engine)
|
|
203
|
+
|
|
204
|
+
def search_learnings(self, query: str) -> None:
|
|
205
|
+
"""Search learnings by keyword (delegates to reporter)"""
|
|
206
|
+
self.reporter.search_learnings(query)
|
|
207
|
+
|
|
208
|
+
def show_learnings(
|
|
209
|
+
self,
|
|
210
|
+
category: str | None = None,
|
|
211
|
+
tag: str | None = None,
|
|
212
|
+
session: int | None = None,
|
|
213
|
+
date_from: str | None = None,
|
|
214
|
+
date_to: str | None = None,
|
|
215
|
+
include_archived: bool = False,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Show learnings with optional filters (delegates to reporter)"""
|
|
218
|
+
self.reporter.show_learnings(category, tag, session, date_from, date_to, include_archived)
|
|
219
|
+
|
|
220
|
+
def generate_report(self) -> None:
|
|
221
|
+
"""Generate learning summary report (delegates to reporter)"""
|
|
222
|
+
self.reporter.generate_report()
|
|
223
|
+
|
|
224
|
+
def show_statistics(self) -> None:
|
|
225
|
+
"""Display learning statistics (delegates to reporter)"""
|
|
226
|
+
self.reporter.show_statistics()
|
|
227
|
+
|
|
228
|
+
def generate_statistics(self) -> dict:
|
|
229
|
+
"""Generate learning statistics (delegates to reporter)"""
|
|
230
|
+
return self.reporter.generate_statistics()
|
|
231
|
+
|
|
232
|
+
def show_timeline(self, sessions: int = 10) -> None:
|
|
233
|
+
"""Show learning timeline (delegates to reporter)"""
|
|
234
|
+
self.reporter.show_timeline(sessions)
|
|
235
|
+
|
|
236
|
+
@log_errors()
|
|
237
|
+
def extract_from_session_summary(self, session_file: Path) -> list[dict]:
|
|
238
|
+
"""Extract learnings from session summary file (delegates to extractor)"""
|
|
239
|
+
return self.extractor.extract_from_session_summary(session_file, self.validator)
|
|
240
|
+
|
|
241
|
+
@log_errors()
|
|
242
|
+
def extract_from_git_commits(
|
|
243
|
+
self, since_session: int = 0, session_id: str | None = None
|
|
244
|
+
) -> list[dict]:
|
|
245
|
+
"""Extract learnings from git commit messages (delegates to extractor)"""
|
|
246
|
+
return self.extractor.extract_from_git_commits(since_session, session_id, self.validator)
|
|
247
|
+
|
|
248
|
+
@log_errors()
|
|
249
|
+
def extract_from_code_comments(
|
|
250
|
+
self, changed_files: list[Path] | None = None, session_id: str | None = None
|
|
251
|
+
) -> list[dict]:
|
|
252
|
+
"""Extract learnings from inline code comments (delegates to extractor)"""
|
|
253
|
+
return self.extractor.extract_from_code_comments(changed_files, session_id, self.validator)
|
|
254
|
+
|
|
255
|
+
def is_valid_learning(self, content: str) -> bool:
|
|
256
|
+
"""Check if content is a valid learning (delegates to validator)"""
|
|
257
|
+
return self.validator.is_valid_learning(content)
|
|
258
|
+
|
|
259
|
+
def create_learning_entry(
|
|
260
|
+
self,
|
|
261
|
+
content: str,
|
|
262
|
+
source: str,
|
|
263
|
+
session_id: str | None = None,
|
|
264
|
+
context: str | None = None,
|
|
265
|
+
timestamp: str | None = None,
|
|
266
|
+
learning_id: str | None = None,
|
|
267
|
+
) -> dict:
|
|
268
|
+
"""Create standardized learning entry (delegates to validator)"""
|
|
269
|
+
return self.validator.create_learning_entry(
|
|
270
|
+
content, source, session_id, context, timestamp, learning_id
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def validate_learning(self, learning: dict) -> bool:
|
|
274
|
+
"""Validate learning entry against schema (delegates to validator)"""
|
|
275
|
+
return self.validator.validate_learning(learning)
|
|
276
|
+
|
|
277
|
+
def get_related_learnings(self, learning_id: str, limit: int = 5) -> list[dict]:
|
|
278
|
+
"""Get similar learnings (delegates to similarity engine)"""
|
|
279
|
+
learnings = self.repository.load_learnings()
|
|
280
|
+
return self.similarity_engine.get_related_learnings(learnings, learning_id, limit)
|
|
281
|
+
|
|
282
|
+
# ========================================================================
|
|
283
|
+
# Compatibility wrapper methods for tests
|
|
284
|
+
# These delegate to the refactored modules
|
|
285
|
+
# ========================================================================
|
|
286
|
+
|
|
287
|
+
def _count_all_learnings(self, learnings: dict) -> int:
|
|
288
|
+
"""Count all learnings (compatibility wrapper for tests)"""
|
|
289
|
+
return self.repository.count_all_learnings(learnings)
|
|
290
|
+
|
|
291
|
+
def _update_total_learnings(self, learnings: dict) -> None:
|
|
292
|
+
"""Update total learnings metadata (compatibility wrapper for tests)"""
|
|
293
|
+
self.repository.update_total_learnings(learnings)
|
|
294
|
+
|
|
295
|
+
def _keyword_score(self, text: str, keywords: list[str]) -> int:
|
|
296
|
+
"""Calculate keyword score (compatibility wrapper for tests)"""
|
|
297
|
+
return self.categorizer._keyword_score(text, keywords)
|
|
298
|
+
|
|
299
|
+
def _auto_categorize_learning(self, learning: dict) -> str:
|
|
300
|
+
"""Auto-categorize learning (compatibility wrapper for tests)"""
|
|
301
|
+
return self.categorizer.categorize_learning(learning)
|
|
302
|
+
|
|
303
|
+
def _are_similar(self, learning_a: dict, learning_b: dict) -> bool:
|
|
304
|
+
"""Check if two learnings are similar (compatibility wrapper for tests)"""
|
|
305
|
+
return self.similarity_engine.are_similar(learning_a, learning_b)
|
|
306
|
+
|
|
307
|
+
def _learning_exists(self, cat_learnings: list[dict], new_learning: dict) -> bool:
|
|
308
|
+
"""Check if learning exists in category (compatibility wrapper for tests)"""
|
|
309
|
+
return self.repository.learning_exists(cat_learnings, new_learning, self.similarity_engine)
|
|
310
|
+
|
|
311
|
+
def _merge_similar_learnings(self, learnings: dict) -> int:
|
|
312
|
+
"""Merge similar learnings (compatibility wrapper for tests)"""
|
|
313
|
+
return self.similarity_engine.merge_similar_learnings(learnings)
|
|
314
|
+
|
|
315
|
+
def _archive_old_learnings(
|
|
316
|
+
self, learnings: dict, max_age_sessions: int = MAX_LEARNING_AGE_SESSIONS
|
|
317
|
+
) -> int:
|
|
318
|
+
"""Archive old learnings (compatibility wrapper for tests)"""
|
|
319
|
+
return self.archiver.archive_old_learnings(learnings, max_age_sessions)
|
|
320
|
+
|
|
321
|
+
def _extract_session_number(self, session_id: str) -> int:
|
|
322
|
+
"""Extract session number from session ID (compatibility wrapper for tests)"""
|
|
323
|
+
return self.archiver._extract_session_number(session_id)
|
|
324
|
+
|
|
325
|
+
def _get_current_session_number(self) -> int:
|
|
326
|
+
"""Get current session number (compatibility wrapper for tests)"""
|
|
327
|
+
return self.archiver._get_current_session_number()
|
|
328
|
+
|
|
329
|
+
def _extract_learnings_from_sessions(self) -> list[dict]:
|
|
330
|
+
"""Extract learnings from session summaries (compatibility wrapper for tests)"""
|
|
331
|
+
return self.extractor.extract_from_sessions()
|
|
332
|
+
|
|
333
|
+
def _load_learnings(self) -> dict:
|
|
334
|
+
"""Load learnings from file (compatibility wrapper for tests)"""
|
|
335
|
+
return self.repository.load_learnings()
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def main() -> None:
|
|
339
|
+
"""Main entry point"""
|
|
340
|
+
parser = argparse.ArgumentParser(description="Learning curation and management")
|
|
341
|
+
|
|
342
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
343
|
+
|
|
344
|
+
# Curate command
|
|
345
|
+
curate_parser = subparsers.add_parser("curate", help="Run curation process")
|
|
346
|
+
curate_parser.add_argument("--dry-run", action="store_true", help="Show changes without saving")
|
|
347
|
+
|
|
348
|
+
# Show learnings command
|
|
349
|
+
show_parser = subparsers.add_parser("show-learnings", help="Show learnings")
|
|
350
|
+
show_parser.add_argument("--category", type=str, help="Filter by category")
|
|
351
|
+
show_parser.add_argument("--tag", type=str, help="Filter by tag")
|
|
352
|
+
show_parser.add_argument("--session", type=int, help="Filter by session number")
|
|
353
|
+
|
|
354
|
+
# Search command
|
|
355
|
+
search_parser = subparsers.add_parser("search", help="Search learnings")
|
|
356
|
+
search_parser.add_argument("query", type=str, help="Search query")
|
|
357
|
+
|
|
358
|
+
# Add learning command
|
|
359
|
+
add_parser = subparsers.add_parser("add-learning", help="Add a new learning")
|
|
360
|
+
add_parser.add_argument("--content", type=str, required=True, help="Learning content")
|
|
361
|
+
add_parser.add_argument(
|
|
362
|
+
"--category",
|
|
363
|
+
type=str,
|
|
364
|
+
required=True,
|
|
365
|
+
choices=[
|
|
366
|
+
"architecture_patterns",
|
|
367
|
+
"gotchas",
|
|
368
|
+
"best_practices",
|
|
369
|
+
"technical_debt",
|
|
370
|
+
"performance_insights",
|
|
371
|
+
"security",
|
|
372
|
+
],
|
|
373
|
+
help="Learning category",
|
|
374
|
+
)
|
|
375
|
+
add_parser.add_argument("--session", type=int, help="Session number")
|
|
376
|
+
add_parser.add_argument("--tags", type=str, help="Comma-separated tags")
|
|
377
|
+
add_parser.add_argument("--context", type=str, help="Additional context")
|
|
378
|
+
|
|
379
|
+
# Report command (legacy)
|
|
380
|
+
subparsers.add_parser("report", help="Generate summary report")
|
|
381
|
+
|
|
382
|
+
# Statistics command
|
|
383
|
+
subparsers.add_parser("statistics", help="Show learning statistics")
|
|
384
|
+
|
|
385
|
+
# Timeline command
|
|
386
|
+
timeline_parser = subparsers.add_parser("timeline", help="Show learning timeline")
|
|
387
|
+
timeline_parser.add_argument(
|
|
388
|
+
"--sessions", type=int, default=10, help="Number of recent sessions to show"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
args = parser.parse_args()
|
|
392
|
+
|
|
393
|
+
project_root = Path.cwd()
|
|
394
|
+
session_dir = project_root / ".session"
|
|
395
|
+
|
|
396
|
+
if not session_dir.exists():
|
|
397
|
+
raise SolokitFileNotFoundError(file_path=str(session_dir), file_type="session directory")
|
|
398
|
+
|
|
399
|
+
curator = LearningsCurator(project_root)
|
|
400
|
+
|
|
401
|
+
if args.command == "curate":
|
|
402
|
+
curator.curate(dry_run=args.dry_run)
|
|
403
|
+
elif args.command == "show-learnings":
|
|
404
|
+
curator.show_learnings(category=args.category, tag=args.tag, session=args.session)
|
|
405
|
+
elif args.command == "search":
|
|
406
|
+
curator.search_learnings(args.query)
|
|
407
|
+
elif args.command == "add-learning":
|
|
408
|
+
tags = args.tags.split(",") if args.tags else None
|
|
409
|
+
curator.add_learning(
|
|
410
|
+
content=args.content,
|
|
411
|
+
category=args.category,
|
|
412
|
+
session=args.session,
|
|
413
|
+
tags=tags,
|
|
414
|
+
context=args.context,
|
|
415
|
+
)
|
|
416
|
+
elif args.command == "report":
|
|
417
|
+
curator.generate_report()
|
|
418
|
+
elif args.command == "statistics":
|
|
419
|
+
curator.show_statistics()
|
|
420
|
+
elif args.command == "timeline":
|
|
421
|
+
curator.show_timeline(sessions=args.sessions)
|
|
422
|
+
else:
|
|
423
|
+
# Default to report if no command specified
|
|
424
|
+
curator.generate_report()
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
if __name__ == "__main__":
|
|
428
|
+
main()
|