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
solokit/project/stack.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate and update technology stack documentation.
|
|
4
|
+
|
|
5
|
+
Detects:
|
|
6
|
+
- Languages (from file extensions)
|
|
7
|
+
- Frameworks (from imports and config files)
|
|
8
|
+
- Libraries (from requirements.txt, package.json, etc.)
|
|
9
|
+
- Databases (from connection strings, imports)
|
|
10
|
+
- MCP Servers (from context7 usage, etc.)
|
|
11
|
+
- External APIs (from code inspection)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from solokit.core.error_handlers import log_errors
|
|
23
|
+
from solokit.core.exceptions import FileOperationError
|
|
24
|
+
from solokit.core.output import get_output
|
|
25
|
+
|
|
26
|
+
output = get_output()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StackGenerator:
|
|
30
|
+
"""Generate technology stack documentation."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, project_root: Path | None = None):
|
|
33
|
+
"""Initialize StackGenerator with project root path."""
|
|
34
|
+
self.project_root = project_root or Path.cwd()
|
|
35
|
+
self.stack_file = self.project_root / ".session" / "tracking" / "stack.txt"
|
|
36
|
+
self.updates_file = self.project_root / ".session" / "tracking" / "stack_updates.json"
|
|
37
|
+
|
|
38
|
+
def detect_languages(self) -> dict[str, str]:
|
|
39
|
+
"""Detect programming languages from file extensions."""
|
|
40
|
+
languages = {}
|
|
41
|
+
|
|
42
|
+
# Count files by extension
|
|
43
|
+
extensions = {
|
|
44
|
+
".py": ("Python", "python"),
|
|
45
|
+
".js": ("JavaScript", "javascript"),
|
|
46
|
+
".ts": ("TypeScript", "typescript"),
|
|
47
|
+
".rs": ("Rust", "rust"),
|
|
48
|
+
".go": ("Go", "go"),
|
|
49
|
+
".java": ("Java", "java"),
|
|
50
|
+
".cpp": ("C++", "cpp"),
|
|
51
|
+
".c": ("C", "c"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for ext, (name, key) in extensions.items():
|
|
55
|
+
files = list(self.project_root.rglob(f"*{ext}"))
|
|
56
|
+
# Exclude common non-source directories
|
|
57
|
+
files = [
|
|
58
|
+
f
|
|
59
|
+
for f in files
|
|
60
|
+
if not any(
|
|
61
|
+
part in f.parts
|
|
62
|
+
for part in [
|
|
63
|
+
"node_modules",
|
|
64
|
+
"venv",
|
|
65
|
+
".venv",
|
|
66
|
+
"build",
|
|
67
|
+
"dist",
|
|
68
|
+
"__pycache__",
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
if files:
|
|
74
|
+
# Try to detect version
|
|
75
|
+
version = self._detect_language_version(key)
|
|
76
|
+
languages[name] = version or "detected"
|
|
77
|
+
|
|
78
|
+
return languages
|
|
79
|
+
|
|
80
|
+
def _detect_language_version(self, language: str) -> str:
|
|
81
|
+
"""Detect language version from environment."""
|
|
82
|
+
from solokit.core.command_runner import CommandRunner
|
|
83
|
+
from solokit.core.constants import STACK_DETECTION_TIMEOUT
|
|
84
|
+
from solokit.core.error_handlers import safe_execute
|
|
85
|
+
|
|
86
|
+
version_commands = {
|
|
87
|
+
"python": ["python", "--version"],
|
|
88
|
+
"node": ["node", "--version"],
|
|
89
|
+
"rust": ["rustc", "--version"],
|
|
90
|
+
"go": ["go", "version"],
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if language in version_commands:
|
|
94
|
+
|
|
95
|
+
def detect_version() -> str:
|
|
96
|
+
runner = CommandRunner(default_timeout=STACK_DETECTION_TIMEOUT)
|
|
97
|
+
result = runner.run(version_commands[language])
|
|
98
|
+
if result.success:
|
|
99
|
+
# Extract version number
|
|
100
|
+
version_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", result.stdout)
|
|
101
|
+
if version_match:
|
|
102
|
+
return version_match.group(1)
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
result = safe_execute(detect_version, default="", log_errors=False)
|
|
106
|
+
return result or ""
|
|
107
|
+
|
|
108
|
+
return ""
|
|
109
|
+
|
|
110
|
+
def detect_frameworks(self) -> dict[str, list[str]]:
|
|
111
|
+
"""Detect frameworks from imports and config files."""
|
|
112
|
+
from solokit.core.error_handlers import safe_execute
|
|
113
|
+
|
|
114
|
+
frameworks: dict[str, list[str]] = {
|
|
115
|
+
"backend": [],
|
|
116
|
+
"frontend": [],
|
|
117
|
+
"testing": [],
|
|
118
|
+
"database": [],
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Check Python imports
|
|
122
|
+
if (self.project_root / "requirements.txt").exists():
|
|
123
|
+
try:
|
|
124
|
+
requirements = (self.project_root / "requirements.txt").read_text()
|
|
125
|
+
if "fastapi" in requirements.lower():
|
|
126
|
+
version = self._extract_version(requirements, "fastapi")
|
|
127
|
+
frameworks["backend"].append(f"FastAPI {version}")
|
|
128
|
+
if "django" in requirements.lower():
|
|
129
|
+
version = self._extract_version(requirements, "django")
|
|
130
|
+
frameworks["backend"].append(f"Django {version}")
|
|
131
|
+
if "flask" in requirements.lower():
|
|
132
|
+
version = self._extract_version(requirements, "flask")
|
|
133
|
+
frameworks["backend"].append(f"Flask {version}")
|
|
134
|
+
if "sqlalchemy" in requirements.lower():
|
|
135
|
+
version = self._extract_version(requirements, "sqlalchemy")
|
|
136
|
+
frameworks["database"].append(f"SQLAlchemy {version}")
|
|
137
|
+
if "pytest" in requirements.lower():
|
|
138
|
+
frameworks["testing"].append("pytest")
|
|
139
|
+
except OSError as e:
|
|
140
|
+
raise FileOperationError(
|
|
141
|
+
operation="read",
|
|
142
|
+
file_path=str(self.project_root / "requirements.txt"),
|
|
143
|
+
details=str(e),
|
|
144
|
+
cause=e,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Check JavaScript/TypeScript frameworks
|
|
148
|
+
if (self.project_root / "package.json").exists():
|
|
149
|
+
|
|
150
|
+
def parse_package_json() -> None:
|
|
151
|
+
try:
|
|
152
|
+
content = (self.project_root / "package.json").read_text()
|
|
153
|
+
package = json.loads(content)
|
|
154
|
+
deps = {
|
|
155
|
+
**package.get("dependencies", {}),
|
|
156
|
+
**package.get("devDependencies", {}),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if "react" in deps:
|
|
160
|
+
frameworks["frontend"].append(f"React {deps['react']}")
|
|
161
|
+
if "vue" in deps:
|
|
162
|
+
frameworks["frontend"].append(f"Vue {deps['vue']}")
|
|
163
|
+
if "next" in deps:
|
|
164
|
+
frameworks["frontend"].append(f"Next.js {deps['next']}")
|
|
165
|
+
if "jest" in deps:
|
|
166
|
+
frameworks["testing"].append("Jest")
|
|
167
|
+
except json.JSONDecodeError as e:
|
|
168
|
+
raise FileOperationError(
|
|
169
|
+
operation="parse",
|
|
170
|
+
file_path=str(self.project_root / "package.json"),
|
|
171
|
+
details=f"Invalid JSON: {e}",
|
|
172
|
+
cause=e,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
safe_execute(parse_package_json, default=None, log_errors=False)
|
|
176
|
+
|
|
177
|
+
return {k: v for k, v in frameworks.items() if v}
|
|
178
|
+
|
|
179
|
+
def _extract_version(self, requirements: str, package: str) -> str:
|
|
180
|
+
"""Extract version from requirements file."""
|
|
181
|
+
pattern = rf"{package}\s*[>=<~]+\s*([0-9.]+)"
|
|
182
|
+
match = re.search(pattern, requirements, re.IGNORECASE)
|
|
183
|
+
if match:
|
|
184
|
+
return match.group(1)
|
|
185
|
+
return ""
|
|
186
|
+
|
|
187
|
+
def detect_libraries(self) -> list[str]:
|
|
188
|
+
"""Detect libraries from dependency files."""
|
|
189
|
+
libraries = []
|
|
190
|
+
|
|
191
|
+
# Python libraries
|
|
192
|
+
if (self.project_root / "requirements.txt").exists():
|
|
193
|
+
try:
|
|
194
|
+
requirements = (self.project_root / "requirements.txt").read_text()
|
|
195
|
+
for line in requirements.split("\n"):
|
|
196
|
+
line = line.strip()
|
|
197
|
+
if line and not line.startswith("#"):
|
|
198
|
+
# Extract package name and version
|
|
199
|
+
match = re.match(r"([a-zA-Z0-9_-]+)([>=<~]+.*)?", line)
|
|
200
|
+
if match:
|
|
201
|
+
libraries.append(line)
|
|
202
|
+
except OSError as e:
|
|
203
|
+
raise FileOperationError(
|
|
204
|
+
operation="read",
|
|
205
|
+
file_path=str(self.project_root / "requirements.txt"),
|
|
206
|
+
details=str(e),
|
|
207
|
+
cause=e,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return libraries[:20] # Limit to top 20
|
|
211
|
+
|
|
212
|
+
def detect_mcp_servers(self) -> list[str]:
|
|
213
|
+
"""Detect MCP servers in use."""
|
|
214
|
+
from solokit.core.error_handlers import safe_execute
|
|
215
|
+
|
|
216
|
+
mcp_servers = []
|
|
217
|
+
|
|
218
|
+
# Check for context7 usage in code
|
|
219
|
+
for py_file in self.project_root.rglob("*.py"):
|
|
220
|
+
|
|
221
|
+
def read_py_file() -> bool:
|
|
222
|
+
content = py_file.read_text()
|
|
223
|
+
if "context7" in content.lower() or "mcp__context7" in content:
|
|
224
|
+
if "Context7 (library documentation)" not in mcp_servers:
|
|
225
|
+
mcp_servers.append("Context7 (library documentation)")
|
|
226
|
+
return True
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
if safe_execute(read_py_file, default=False, log_errors=False):
|
|
230
|
+
break # Found it, no need to continue
|
|
231
|
+
|
|
232
|
+
return mcp_servers
|
|
233
|
+
|
|
234
|
+
def generate_stack_txt(self) -> str:
|
|
235
|
+
"""Generate stack.txt content."""
|
|
236
|
+
languages = self.detect_languages()
|
|
237
|
+
frameworks = self.detect_frameworks()
|
|
238
|
+
libraries = self.detect_libraries()
|
|
239
|
+
mcp_servers = self.detect_mcp_servers()
|
|
240
|
+
|
|
241
|
+
lines = ["# Technology Stack\n"]
|
|
242
|
+
|
|
243
|
+
if languages:
|
|
244
|
+
lines.append("## Languages")
|
|
245
|
+
for name, version in languages.items():
|
|
246
|
+
lines.append(f"- {name} {version}")
|
|
247
|
+
lines.append("")
|
|
248
|
+
|
|
249
|
+
if frameworks.get("backend"):
|
|
250
|
+
lines.append("## Backend Framework")
|
|
251
|
+
for fw in frameworks["backend"]:
|
|
252
|
+
lines.append(f"- {fw}")
|
|
253
|
+
lines.append("")
|
|
254
|
+
|
|
255
|
+
if frameworks.get("frontend"):
|
|
256
|
+
lines.append("## Frontend Framework")
|
|
257
|
+
for fw in frameworks["frontend"]:
|
|
258
|
+
lines.append(f"- {fw}")
|
|
259
|
+
lines.append("")
|
|
260
|
+
|
|
261
|
+
if frameworks.get("database"):
|
|
262
|
+
lines.append("## Database")
|
|
263
|
+
for db in frameworks["database"]:
|
|
264
|
+
lines.append(f"- {db}")
|
|
265
|
+
lines.append("")
|
|
266
|
+
|
|
267
|
+
if mcp_servers:
|
|
268
|
+
lines.append("## MCP Servers")
|
|
269
|
+
for mcp in mcp_servers:
|
|
270
|
+
lines.append(f"- {mcp}")
|
|
271
|
+
lines.append("")
|
|
272
|
+
|
|
273
|
+
if frameworks.get("testing"):
|
|
274
|
+
lines.append("## Testing")
|
|
275
|
+
for test in frameworks["testing"]:
|
|
276
|
+
lines.append(f"- {test}")
|
|
277
|
+
lines.append("")
|
|
278
|
+
|
|
279
|
+
if libraries:
|
|
280
|
+
lines.append("## Key Libraries")
|
|
281
|
+
for lib in libraries:
|
|
282
|
+
lines.append(f"- {lib}")
|
|
283
|
+
lines.append("")
|
|
284
|
+
|
|
285
|
+
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
def detect_changes(self, old_content: str, new_content: str) -> list[dict]:
|
|
290
|
+
"""Detect changes between old and new stack."""
|
|
291
|
+
old_lines = set(old_content.split("\n"))
|
|
292
|
+
new_lines = set(new_content.split("\n"))
|
|
293
|
+
|
|
294
|
+
added = new_lines - old_lines
|
|
295
|
+
removed = old_lines - new_lines
|
|
296
|
+
|
|
297
|
+
changes = []
|
|
298
|
+
for line in added:
|
|
299
|
+
if line.strip() and not line.startswith("#") and not line.startswith("Generated:"):
|
|
300
|
+
changes.append({"type": "addition", "content": line.strip()})
|
|
301
|
+
|
|
302
|
+
for line in removed:
|
|
303
|
+
if line.strip() and not line.startswith("#") and not line.startswith("Generated:"):
|
|
304
|
+
changes.append({"type": "removal", "content": line.strip()})
|
|
305
|
+
|
|
306
|
+
return changes
|
|
307
|
+
|
|
308
|
+
@log_errors()
|
|
309
|
+
def update_stack(
|
|
310
|
+
self, session_num: int | None = None, non_interactive: bool = False
|
|
311
|
+
) -> list[dict[str, str]]:
|
|
312
|
+
"""Generate/update stack.txt and detect changes.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
session_num: Current session number
|
|
316
|
+
non_interactive: If True, skip interactive reasoning prompts
|
|
317
|
+
"""
|
|
318
|
+
# Generate new stack content
|
|
319
|
+
new_content = self.generate_stack_txt()
|
|
320
|
+
|
|
321
|
+
# Load old content if exists
|
|
322
|
+
old_content = ""
|
|
323
|
+
if self.stack_file.exists():
|
|
324
|
+
try:
|
|
325
|
+
old_content = self.stack_file.read_text()
|
|
326
|
+
except OSError as e:
|
|
327
|
+
raise FileOperationError(
|
|
328
|
+
operation="read", file_path=str(self.stack_file), details=str(e), cause=e
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Detect changes
|
|
332
|
+
changes = self.detect_changes(old_content, new_content)
|
|
333
|
+
|
|
334
|
+
# Save new stack
|
|
335
|
+
try:
|
|
336
|
+
self.stack_file.parent.mkdir(parents=True, exist_ok=True)
|
|
337
|
+
self.stack_file.write_text(new_content)
|
|
338
|
+
except OSError as e:
|
|
339
|
+
raise FileOperationError(
|
|
340
|
+
operation="write", file_path=str(self.stack_file), details=str(e), cause=e
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# If changes detected, prompt for reasoning (unless non-interactive)
|
|
344
|
+
if changes and session_num:
|
|
345
|
+
output.info(f"\n{'=' * 50}")
|
|
346
|
+
output.info("Stack Changes Detected")
|
|
347
|
+
output.info("=" * 50)
|
|
348
|
+
|
|
349
|
+
for change in changes:
|
|
350
|
+
output.info(f" {change['type'].upper()}: {change['content']}")
|
|
351
|
+
|
|
352
|
+
if non_interactive:
|
|
353
|
+
reasoning = "Automated update during session completion"
|
|
354
|
+
output.info("\n(Non-interactive mode: recording changes without manual reasoning)")
|
|
355
|
+
else:
|
|
356
|
+
output.info("\nPlease provide reasoning for these changes:")
|
|
357
|
+
reasoning = input("> ")
|
|
358
|
+
|
|
359
|
+
# Update stack_updates.json
|
|
360
|
+
self._record_stack_update(session_num, changes, reasoning)
|
|
361
|
+
|
|
362
|
+
return changes
|
|
363
|
+
|
|
364
|
+
def _record_stack_update(
|
|
365
|
+
self, session_num: int, changes: list[dict[str, Any]], reasoning: str
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Record stack update in stack_updates.json."""
|
|
368
|
+
from solokit.core.error_handlers import safe_execute
|
|
369
|
+
|
|
370
|
+
updates: dict[str, Any] = {"updates": []}
|
|
371
|
+
|
|
372
|
+
if self.updates_file.exists():
|
|
373
|
+
|
|
374
|
+
def load_updates() -> dict[str, Any]:
|
|
375
|
+
try:
|
|
376
|
+
content = self.updates_file.read_text()
|
|
377
|
+
return json.loads(content) # type: ignore[no-any-return]
|
|
378
|
+
except json.JSONDecodeError as e:
|
|
379
|
+
raise FileOperationError(
|
|
380
|
+
operation="parse",
|
|
381
|
+
file_path=str(self.updates_file),
|
|
382
|
+
details=f"Invalid JSON: {e}",
|
|
383
|
+
cause=e,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
loaded_updates = safe_execute(load_updates, default=None, log_errors=False)
|
|
387
|
+
if loaded_updates:
|
|
388
|
+
updates = loaded_updates
|
|
389
|
+
|
|
390
|
+
update_entry = {
|
|
391
|
+
"timestamp": datetime.now().isoformat(),
|
|
392
|
+
"session": session_num,
|
|
393
|
+
"changes": changes,
|
|
394
|
+
"reasoning": reasoning,
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
updates["updates"].append(update_entry)
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
self.updates_file.write_text(json.dumps(updates, indent=2))
|
|
401
|
+
except OSError as e:
|
|
402
|
+
raise FileOperationError(
|
|
403
|
+
operation="write", file_path=str(self.updates_file), details=str(e), cause=e
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def main() -> None:
|
|
408
|
+
"""CLI entry point."""
|
|
409
|
+
import argparse
|
|
410
|
+
|
|
411
|
+
from solokit.core.output import get_output
|
|
412
|
+
|
|
413
|
+
output = get_output()
|
|
414
|
+
|
|
415
|
+
parser = argparse.ArgumentParser(description="Generate technology stack documentation")
|
|
416
|
+
parser.add_argument("--session", type=int, help="Current session number")
|
|
417
|
+
parser.add_argument(
|
|
418
|
+
"--non-interactive",
|
|
419
|
+
action="store_true",
|
|
420
|
+
help="Skip interactive prompts (use automated reasoning)",
|
|
421
|
+
)
|
|
422
|
+
args = parser.parse_args()
|
|
423
|
+
|
|
424
|
+
generator = StackGenerator()
|
|
425
|
+
changes = generator.update_stack(session_num=args.session, non_interactive=args.non_interactive)
|
|
426
|
+
|
|
427
|
+
if changes:
|
|
428
|
+
output.info(f"\n✓ Stack updated with {len(changes)} changes")
|
|
429
|
+
else:
|
|
430
|
+
output.info("\n✓ Stack generated (no changes)")
|
|
431
|
+
|
|
432
|
+
output.info(f"✓ Saved to: {generator.stack_file}")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
if __name__ == "__main__":
|
|
436
|
+
main()
|