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,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Deployment quality gates checker.
|
|
4
|
+
|
|
5
|
+
Validates deployment readiness including environment setup, documentation,
|
|
6
|
+
rollback procedures, and orchestrates integration tests and security scans.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Union, cast
|
|
15
|
+
|
|
16
|
+
from solokit.core.command_runner import CommandRunner
|
|
17
|
+
from solokit.core.constants import QUALITY_CHECK_LONG_TIMEOUT
|
|
18
|
+
from solokit.core.logging_config import get_logger
|
|
19
|
+
from solokit.quality.checkers.base import CheckResult, QualityChecker
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeploymentChecker(QualityChecker):
|
|
25
|
+
"""Deployment readiness validation and orchestration."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
work_item: dict[str, Any],
|
|
30
|
+
config: dict[str, Any],
|
|
31
|
+
project_root: Path | None = None,
|
|
32
|
+
runner: CommandRunner | None = None,
|
|
33
|
+
):
|
|
34
|
+
"""Initialize deployment checker.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
work_item: Work item dictionary (must be deployment type)
|
|
38
|
+
config: Deployment quality gate configuration
|
|
39
|
+
project_root: Project root directory
|
|
40
|
+
runner: Optional CommandRunner instance (for testing)
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(config, project_root)
|
|
43
|
+
self.work_item = work_item
|
|
44
|
+
self.runner = (
|
|
45
|
+
runner
|
|
46
|
+
if runner is not None
|
|
47
|
+
else CommandRunner(default_timeout=QUALITY_CHECK_LONG_TIMEOUT)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def name(self) -> str:
|
|
51
|
+
"""Return checker name."""
|
|
52
|
+
return "deployment"
|
|
53
|
+
|
|
54
|
+
def is_enabled(self) -> bool:
|
|
55
|
+
"""Check if deployment quality gates are enabled."""
|
|
56
|
+
return bool(self.config.get("enabled", True))
|
|
57
|
+
|
|
58
|
+
def run(self) -> CheckResult:
|
|
59
|
+
"""Execute deployment quality gates orchestration.
|
|
60
|
+
|
|
61
|
+
Orchestrates:
|
|
62
|
+
1. Integration tests (via IntegrationChecker)
|
|
63
|
+
2. Security scans (via SecurityChecker)
|
|
64
|
+
3. Environment validation
|
|
65
|
+
4. Deployment documentation validation
|
|
66
|
+
5. Rollback procedure testing
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
CheckResult with orchestrated deployment validation outcome
|
|
70
|
+
"""
|
|
71
|
+
start_time = time.time()
|
|
72
|
+
|
|
73
|
+
if not self.is_enabled():
|
|
74
|
+
return self._create_skipped_result("disabled")
|
|
75
|
+
|
|
76
|
+
logger.info("Running deployment quality gates...")
|
|
77
|
+
|
|
78
|
+
gates: list[dict[str, Any]] = []
|
|
79
|
+
errors: list[dict[str, Any]] = []
|
|
80
|
+
warnings: list[dict[str, Any]] = []
|
|
81
|
+
overall_passed = True
|
|
82
|
+
|
|
83
|
+
# Gate 1: All integration tests must pass
|
|
84
|
+
try:
|
|
85
|
+
from solokit.quality.checkers.integration import IntegrationChecker
|
|
86
|
+
|
|
87
|
+
integration_config = self.config.get("integration_tests", {"enabled": True})
|
|
88
|
+
integration_checker = IntegrationChecker(
|
|
89
|
+
self.work_item, integration_config, runner=self.runner
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Check if integration checker is applicable
|
|
93
|
+
if integration_checker.is_enabled():
|
|
94
|
+
logger.info("Running integration tests gate...")
|
|
95
|
+
integration_result = integration_checker.run()
|
|
96
|
+
|
|
97
|
+
gates.append(
|
|
98
|
+
{
|
|
99
|
+
"name": "Integration Tests",
|
|
100
|
+
"required": True,
|
|
101
|
+
"passed": integration_result.passed,
|
|
102
|
+
"details": integration_result.info,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if not integration_result.passed:
|
|
107
|
+
overall_passed = False
|
|
108
|
+
for error in integration_result.errors:
|
|
109
|
+
errors.append({"gate": "Integration Tests", "message": error})
|
|
110
|
+
else:
|
|
111
|
+
gates.append(
|
|
112
|
+
{
|
|
113
|
+
"name": "Integration Tests",
|
|
114
|
+
"required": True,
|
|
115
|
+
"passed": True,
|
|
116
|
+
"details": {"skipped": "not applicable"},
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
except ImportError as e:
|
|
120
|
+
logger.warning(f"Integration checker not available: {e}")
|
|
121
|
+
gates.append(
|
|
122
|
+
{
|
|
123
|
+
"name": "Integration Tests",
|
|
124
|
+
"required": True,
|
|
125
|
+
"passed": True,
|
|
126
|
+
"details": {"skipped": "checker not available"},
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Gate 2: Security scans must pass
|
|
131
|
+
try:
|
|
132
|
+
from solokit.quality.checkers.security import SecurityChecker
|
|
133
|
+
|
|
134
|
+
security_config = self.config.get("security_scans", {"enabled": True})
|
|
135
|
+
security_checker = SecurityChecker(
|
|
136
|
+
security_config, project_root=self.project_root, runner=self.runner
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if security_checker.is_enabled():
|
|
140
|
+
logger.info("Running security scans gate...")
|
|
141
|
+
security_result = security_checker.run()
|
|
142
|
+
|
|
143
|
+
gates.append(
|
|
144
|
+
{
|
|
145
|
+
"name": "Security Scans",
|
|
146
|
+
"required": True,
|
|
147
|
+
"passed": security_result.passed,
|
|
148
|
+
"details": security_result.info,
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not security_result.passed:
|
|
153
|
+
overall_passed = False
|
|
154
|
+
for error in security_result.errors:
|
|
155
|
+
errors.append({"gate": "Security Scans", "message": error})
|
|
156
|
+
else:
|
|
157
|
+
gates.append(
|
|
158
|
+
{
|
|
159
|
+
"name": "Security Scans",
|
|
160
|
+
"required": True,
|
|
161
|
+
"passed": True,
|
|
162
|
+
"details": {"skipped": "disabled"},
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
except ImportError as e:
|
|
166
|
+
logger.warning(f"Security checker not available: {e}")
|
|
167
|
+
gates.append(
|
|
168
|
+
{
|
|
169
|
+
"name": "Security Scans",
|
|
170
|
+
"required": True,
|
|
171
|
+
"passed": True,
|
|
172
|
+
"details": {"skipped": "checker not available"},
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Gate 3: Environment must be validated
|
|
177
|
+
logger.info("Validating deployment environment...")
|
|
178
|
+
env_passed = self._validate_deployment_environment()
|
|
179
|
+
gates.append(
|
|
180
|
+
{
|
|
181
|
+
"name": "Environment Validation",
|
|
182
|
+
"required": True,
|
|
183
|
+
"passed": env_passed,
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
if not env_passed:
|
|
187
|
+
overall_passed = False
|
|
188
|
+
errors.append(
|
|
189
|
+
{
|
|
190
|
+
"gate": "Environment Validation",
|
|
191
|
+
"message": "Deployment environment validation failed",
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Gate 4: Deployment documentation complete
|
|
196
|
+
logger.info("Validating deployment documentation...")
|
|
197
|
+
docs_passed = self._validate_deployment_documentation()
|
|
198
|
+
gates.append(
|
|
199
|
+
{
|
|
200
|
+
"name": "Deployment Documentation",
|
|
201
|
+
"required": True,
|
|
202
|
+
"passed": docs_passed,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
if not docs_passed:
|
|
206
|
+
overall_passed = False
|
|
207
|
+
errors.append(
|
|
208
|
+
{
|
|
209
|
+
"gate": "Deployment Documentation",
|
|
210
|
+
"message": "Deployment documentation incomplete",
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Gate 5: Rollback procedure tested
|
|
215
|
+
logger.info("Checking rollback testing...")
|
|
216
|
+
rollback_tested = self._check_rollback_tested()
|
|
217
|
+
gates.append({"name": "Rollback Tested", "required": True, "passed": rollback_tested})
|
|
218
|
+
if not rollback_tested:
|
|
219
|
+
overall_passed = False
|
|
220
|
+
errors.append(
|
|
221
|
+
{
|
|
222
|
+
"gate": "Rollback Tested",
|
|
223
|
+
"message": "Rollback procedure not tested",
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
execution_time = time.time() - start_time
|
|
228
|
+
|
|
229
|
+
return CheckResult(
|
|
230
|
+
checker_name=self.name(),
|
|
231
|
+
passed=overall_passed,
|
|
232
|
+
status="passed" if overall_passed else "failed",
|
|
233
|
+
errors=cast(list[Union[dict[str, Any], str]], errors),
|
|
234
|
+
warnings=cast(list[Union[dict[str, Any], str]], warnings),
|
|
235
|
+
info={"gates": gates},
|
|
236
|
+
execution_time=execution_time,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def _validate_deployment_environment(self) -> bool:
|
|
240
|
+
"""Validate deployment environment is ready.
|
|
241
|
+
|
|
242
|
+
Checks:
|
|
243
|
+
- Target environment extracted from work item specification
|
|
244
|
+
- Environment validator available and passes validation
|
|
245
|
+
- Falls back to True if validator not available
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
True if environment validation passes, False otherwise
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
from solokit.quality.env_validator import EnvironmentValidator
|
|
252
|
+
|
|
253
|
+
# Parse target environment from work item
|
|
254
|
+
# Try to extract from spec, fallback to "staging"
|
|
255
|
+
spec = self.work_item.get("specification", "")
|
|
256
|
+
environment = "staging" # Default fallback
|
|
257
|
+
|
|
258
|
+
# Simple pattern matching for common environment declarations
|
|
259
|
+
env_match = re.search(r"environment[:\s]+(\w+)", spec.lower())
|
|
260
|
+
if env_match:
|
|
261
|
+
environment = env_match.group(1)
|
|
262
|
+
|
|
263
|
+
logger.info(f"Validating deployment environment: {environment}")
|
|
264
|
+
validator = EnvironmentValidator(environment)
|
|
265
|
+
passed, _ = validator.validate_all()
|
|
266
|
+
|
|
267
|
+
return passed
|
|
268
|
+
except ImportError:
|
|
269
|
+
# If environment_validator not available, return True
|
|
270
|
+
logger.debug("Environment validator module not available, skipping validation")
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
def _validate_deployment_documentation(self) -> bool:
|
|
274
|
+
"""Validate deployment documentation is complete.
|
|
275
|
+
|
|
276
|
+
Required sections in work item specification:
|
|
277
|
+
- deployment procedure
|
|
278
|
+
- rollback procedure
|
|
279
|
+
- smoke tests
|
|
280
|
+
- monitoring & alerting
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
True if all required sections present, False otherwise
|
|
284
|
+
"""
|
|
285
|
+
spec = self.work_item.get("specification", "")
|
|
286
|
+
|
|
287
|
+
required_sections = [
|
|
288
|
+
"deployment procedure",
|
|
289
|
+
"rollback procedure",
|
|
290
|
+
"smoke tests",
|
|
291
|
+
"monitoring & alerting",
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
missing_sections = []
|
|
295
|
+
for section in required_sections:
|
|
296
|
+
if section.lower() not in spec.lower():
|
|
297
|
+
missing_sections.append(section)
|
|
298
|
+
logger.warning(f"Missing required documentation section: {section}")
|
|
299
|
+
|
|
300
|
+
if missing_sections:
|
|
301
|
+
logger.error(
|
|
302
|
+
f"Deployment documentation incomplete. Missing: {', '.join(missing_sections)}"
|
|
303
|
+
)
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
def _check_rollback_tested(self) -> bool:
|
|
309
|
+
"""Check if rollback procedure has been tested.
|
|
310
|
+
|
|
311
|
+
NOTE: Framework stub - Check deployment history for rollback test
|
|
312
|
+
When implemented, this should:
|
|
313
|
+
1. Query deployment history/logs
|
|
314
|
+
2. Check if rollback has been tested in staging/test environment
|
|
315
|
+
3. Verify rollback completed successfully
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
True by default to allow framework operation
|
|
319
|
+
"""
|
|
320
|
+
# NOTE: Framework stub - Returns True to allow operation
|
|
321
|
+
# In production, implement actual rollback testing verification
|
|
322
|
+
logger.debug("Rollback testing check: Framework stub, returning True")
|
|
323
|
+
return True
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Documentation validation checker.
|
|
4
|
+
|
|
5
|
+
Validates CHANGELOG updates, docstrings, and README currency.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Union, cast
|
|
14
|
+
|
|
15
|
+
from solokit.core.command_runner import CommandRunner
|
|
16
|
+
from solokit.core.constants import (
|
|
17
|
+
GIT_STANDARD_TIMEOUT,
|
|
18
|
+
QUALITY_CHECK_STANDARD_TIMEOUT,
|
|
19
|
+
)
|
|
20
|
+
from solokit.core.logging_config import get_logger
|
|
21
|
+
from solokit.quality.checkers.base import CheckResult, QualityChecker
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DocumentationChecker(QualityChecker):
|
|
27
|
+
"""Documentation requirements validation."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
config: dict[str, Any],
|
|
32
|
+
project_root: Path | None = None,
|
|
33
|
+
work_item: dict[str, Any] | None = None,
|
|
34
|
+
runner: CommandRunner | None = None,
|
|
35
|
+
):
|
|
36
|
+
"""Initialize documentation checker.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config: Documentation configuration
|
|
40
|
+
project_root: Project root directory
|
|
41
|
+
work_item: Work item dictionary (optional, for README check)
|
|
42
|
+
runner: Optional CommandRunner instance (for testing)
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(config, project_root)
|
|
45
|
+
self.runner = (
|
|
46
|
+
runner
|
|
47
|
+
if runner is not None
|
|
48
|
+
else CommandRunner(default_timeout=QUALITY_CHECK_STANDARD_TIMEOUT)
|
|
49
|
+
)
|
|
50
|
+
self.work_item = work_item or {}
|
|
51
|
+
|
|
52
|
+
def name(self) -> str:
|
|
53
|
+
"""Return checker name."""
|
|
54
|
+
return "documentation"
|
|
55
|
+
|
|
56
|
+
def is_enabled(self) -> bool:
|
|
57
|
+
"""Check if documentation validation is enabled."""
|
|
58
|
+
return bool(self.config.get("enabled", True))
|
|
59
|
+
|
|
60
|
+
def run(self) -> CheckResult:
|
|
61
|
+
"""Run documentation validation."""
|
|
62
|
+
start_time = time.time()
|
|
63
|
+
|
|
64
|
+
if not self.is_enabled():
|
|
65
|
+
return self._create_skipped_result()
|
|
66
|
+
|
|
67
|
+
logger.info("Running documentation validation")
|
|
68
|
+
|
|
69
|
+
checks = []
|
|
70
|
+
passed = True
|
|
71
|
+
|
|
72
|
+
# Check CHANGELOG updated
|
|
73
|
+
if self.config.get("check_changelog", False):
|
|
74
|
+
changelog_passed = self._check_changelog_updated()
|
|
75
|
+
checks.append({"name": "CHANGELOG updated", "passed": changelog_passed})
|
|
76
|
+
if not changelog_passed:
|
|
77
|
+
passed = False
|
|
78
|
+
|
|
79
|
+
# Check docstrings for Python
|
|
80
|
+
if self.config.get("check_docstrings", False):
|
|
81
|
+
docstrings_passed = self._check_python_docstrings()
|
|
82
|
+
checks.append({"name": "Docstrings present", "passed": docstrings_passed})
|
|
83
|
+
if not docstrings_passed:
|
|
84
|
+
passed = False
|
|
85
|
+
|
|
86
|
+
# Check README current
|
|
87
|
+
if self.config.get("check_readme", False):
|
|
88
|
+
readme_passed = self._check_readme_current()
|
|
89
|
+
checks.append({"name": "README current", "passed": readme_passed})
|
|
90
|
+
if not readme_passed:
|
|
91
|
+
passed = False
|
|
92
|
+
|
|
93
|
+
execution_time = time.time() - start_time
|
|
94
|
+
|
|
95
|
+
errors = []
|
|
96
|
+
for check in checks:
|
|
97
|
+
if not check["passed"]:
|
|
98
|
+
errors.append({"message": f"{check['name']} check failed"})
|
|
99
|
+
|
|
100
|
+
return CheckResult(
|
|
101
|
+
checker_name=self.name(),
|
|
102
|
+
passed=passed,
|
|
103
|
+
status="passed" if passed else "failed",
|
|
104
|
+
errors=cast(list[Union[dict[str, Any], str]], errors),
|
|
105
|
+
warnings=[],
|
|
106
|
+
info={"checks": checks},
|
|
107
|
+
execution_time=execution_time,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _check_changelog_updated(self) -> bool:
|
|
111
|
+
"""Check if CHANGELOG was updated in the current branch."""
|
|
112
|
+
# Get the current branch name
|
|
113
|
+
result = self.runner.run(
|
|
114
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"], timeout=GIT_STANDARD_TIMEOUT
|
|
115
|
+
)
|
|
116
|
+
if not result.success:
|
|
117
|
+
logger.debug("Could not check CHANGELOG: git not available")
|
|
118
|
+
return True # Skip check if git not available
|
|
119
|
+
|
|
120
|
+
current_branch = result.stdout.strip()
|
|
121
|
+
|
|
122
|
+
# Don't check if we're on main/master
|
|
123
|
+
if current_branch in ["main", "master"]:
|
|
124
|
+
logger.debug("On main/master branch, skipping CHANGELOG check")
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
# Check if CHANGELOG.md was modified in any commit on this branch
|
|
128
|
+
result = self.runner.run(
|
|
129
|
+
["git", "log", "--name-only", "--pretty=format:", "main..HEAD"],
|
|
130
|
+
timeout=GIT_STANDARD_TIMEOUT,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if result.success and "CHANGELOG.md" in result.stdout:
|
|
134
|
+
logger.debug("CHANGELOG updated in branch")
|
|
135
|
+
return True
|
|
136
|
+
else:
|
|
137
|
+
logger.debug("CHANGELOG not updated in branch")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def _check_python_docstrings(self) -> bool:
|
|
141
|
+
"""Check if Python functions have docstrings."""
|
|
142
|
+
# Only check if this is a Python project
|
|
143
|
+
if (
|
|
144
|
+
not (self.project_root / "pyproject.toml").exists()
|
|
145
|
+
and not (self.project_root / "setup.py").exists()
|
|
146
|
+
):
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
result = self.runner.run(
|
|
150
|
+
[sys.executable, "-m", "pydocstyle", "--count"], timeout=QUALITY_CHECK_STANDARD_TIMEOUT
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# If pydocstyle not available or timeout, skip check
|
|
154
|
+
if result.timed_out or result.returncode == -1:
|
|
155
|
+
logger.debug("pydocstyle not available, skipping docstring check")
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
# If no issues found, return True
|
|
159
|
+
return result.returncode == 0
|
|
160
|
+
|
|
161
|
+
def _check_readme_current(self) -> bool:
|
|
162
|
+
"""Check if README was updated (optional check)."""
|
|
163
|
+
result = self.runner.run(
|
|
164
|
+
["git", "diff", "--name-only", "HEAD~1..HEAD"], timeout=GIT_STANDARD_TIMEOUT
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if not result.success:
|
|
168
|
+
logger.debug("Could not check README: git not available")
|
|
169
|
+
return True # Skip check if git not available
|
|
170
|
+
|
|
171
|
+
changed_files = result.stdout.strip().split("\n")
|
|
172
|
+
readme_updated = any("README" in f.upper() for f in changed_files)
|
|
173
|
+
|
|
174
|
+
if readme_updated:
|
|
175
|
+
logger.debug("README updated")
|
|
176
|
+
else:
|
|
177
|
+
logger.debug("README not updated")
|
|
178
|
+
|
|
179
|
+
return readme_updated
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Code formatting checker.
|
|
4
|
+
|
|
5
|
+
Runs formatters like black, prettier, etc.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Union, cast
|
|
13
|
+
|
|
14
|
+
from solokit.core.command_runner import CommandRunner
|
|
15
|
+
from solokit.core.constants import QUALITY_CHECK_VERY_LONG_TIMEOUT
|
|
16
|
+
from solokit.core.logging_config import get_logger
|
|
17
|
+
from solokit.quality.checkers.base import CheckResult, QualityChecker
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FormattingChecker(QualityChecker):
|
|
23
|
+
"""Code formatting validation."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: dict[str, Any],
|
|
28
|
+
project_root: Path | None = None,
|
|
29
|
+
language: str | None = None,
|
|
30
|
+
auto_fix: bool | None = None,
|
|
31
|
+
runner: CommandRunner | None = None,
|
|
32
|
+
):
|
|
33
|
+
"""Initialize formatting checker.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
config: Formatting configuration
|
|
37
|
+
project_root: Project root directory
|
|
38
|
+
language: Programming language (python, javascript, typescript)
|
|
39
|
+
auto_fix: Whether to automatically format (overrides config)
|
|
40
|
+
runner: Optional CommandRunner instance (for testing)
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(config, project_root)
|
|
43
|
+
self.runner = (
|
|
44
|
+
runner
|
|
45
|
+
if runner is not None
|
|
46
|
+
else CommandRunner(default_timeout=QUALITY_CHECK_VERY_LONG_TIMEOUT)
|
|
47
|
+
)
|
|
48
|
+
self.language = language or self._detect_language()
|
|
49
|
+
self.auto_fix = auto_fix if auto_fix is not None else self.config.get("auto_fix", False)
|
|
50
|
+
|
|
51
|
+
def name(self) -> str:
|
|
52
|
+
"""Return checker name."""
|
|
53
|
+
return "formatting"
|
|
54
|
+
|
|
55
|
+
def is_enabled(self) -> bool:
|
|
56
|
+
"""Check if formatting is enabled."""
|
|
57
|
+
return bool(self.config.get("enabled", True))
|
|
58
|
+
|
|
59
|
+
def _detect_language(self) -> str:
|
|
60
|
+
"""Detect primary project language."""
|
|
61
|
+
if (self.project_root / "pyproject.toml").exists() or (
|
|
62
|
+
self.project_root / "setup.py"
|
|
63
|
+
).exists():
|
|
64
|
+
return "python"
|
|
65
|
+
elif (self.project_root / "package.json").exists():
|
|
66
|
+
if (self.project_root / "tsconfig.json").exists():
|
|
67
|
+
return "typescript"
|
|
68
|
+
return "javascript"
|
|
69
|
+
return "python" # default
|
|
70
|
+
|
|
71
|
+
def run(self) -> CheckResult:
|
|
72
|
+
"""Run formatting checks."""
|
|
73
|
+
start_time = time.time()
|
|
74
|
+
|
|
75
|
+
if not self.is_enabled():
|
|
76
|
+
return self._create_skipped_result()
|
|
77
|
+
|
|
78
|
+
logger.info(f"Running formatting for {self.language}")
|
|
79
|
+
|
|
80
|
+
# Get formatting command for language
|
|
81
|
+
commands = self.config.get("commands", {})
|
|
82
|
+
command = commands.get(self.language)
|
|
83
|
+
if not command:
|
|
84
|
+
logger.warning(f"No formatting command configured for language: {self.language}")
|
|
85
|
+
return self._create_skipped_result(reason=f"no command for {self.language}")
|
|
86
|
+
|
|
87
|
+
# Add appropriate flags based on auto_fix and language
|
|
88
|
+
if self.language == "python":
|
|
89
|
+
if not self.auto_fix:
|
|
90
|
+
command += " --check"
|
|
91
|
+
elif self.language in ["javascript", "typescript"]:
|
|
92
|
+
if self.auto_fix:
|
|
93
|
+
command += " --write"
|
|
94
|
+
else:
|
|
95
|
+
command += " --check"
|
|
96
|
+
|
|
97
|
+
# Run formatter
|
|
98
|
+
result = self.runner.run(command.split(), timeout=QUALITY_CHECK_VERY_LONG_TIMEOUT)
|
|
99
|
+
|
|
100
|
+
execution_time = time.time() - start_time
|
|
101
|
+
|
|
102
|
+
if result.timed_out:
|
|
103
|
+
required = self.config.get("required", False)
|
|
104
|
+
if required:
|
|
105
|
+
return CheckResult(
|
|
106
|
+
checker_name=self.name(),
|
|
107
|
+
passed=False,
|
|
108
|
+
status="failed",
|
|
109
|
+
errors=[{"message": "Formatting timed out"}],
|
|
110
|
+
warnings=[],
|
|
111
|
+
info={"reason": "timeout"},
|
|
112
|
+
execution_time=execution_time,
|
|
113
|
+
)
|
|
114
|
+
return CheckResult(
|
|
115
|
+
checker_name=self.name(),
|
|
116
|
+
passed=True,
|
|
117
|
+
status="skipped",
|
|
118
|
+
errors=[],
|
|
119
|
+
warnings=[],
|
|
120
|
+
info={"reason": "timeout"},
|
|
121
|
+
execution_time=execution_time,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Tool not found
|
|
125
|
+
if result.returncode == -1:
|
|
126
|
+
required = self.config.get("required", False)
|
|
127
|
+
if required:
|
|
128
|
+
return CheckResult(
|
|
129
|
+
checker_name=self.name(),
|
|
130
|
+
passed=False,
|
|
131
|
+
status="failed",
|
|
132
|
+
errors=[{"message": "Required formatting tool not found"}],
|
|
133
|
+
warnings=[],
|
|
134
|
+
info={"reason": "tool not found"},
|
|
135
|
+
execution_time=execution_time,
|
|
136
|
+
)
|
|
137
|
+
return self._create_skipped_result(reason="formatting tool not available")
|
|
138
|
+
|
|
139
|
+
passed = result.returncode == 0
|
|
140
|
+
|
|
141
|
+
errors = []
|
|
142
|
+
if not passed:
|
|
143
|
+
errors.append(
|
|
144
|
+
{
|
|
145
|
+
"message": "Code formatting issues found",
|
|
146
|
+
"output": result.stdout[:500] if result.stdout else "",
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return CheckResult(
|
|
151
|
+
checker_name=self.name(),
|
|
152
|
+
passed=passed,
|
|
153
|
+
status="passed" if passed else "failed",
|
|
154
|
+
errors=cast(list[Union[dict[str, Any], str]], errors),
|
|
155
|
+
warnings=[],
|
|
156
|
+
info={
|
|
157
|
+
"formatted": self.auto_fix,
|
|
158
|
+
"output": result.stdout[:1000] if result.stdout else "",
|
|
159
|
+
},
|
|
160
|
+
execution_time=execution_time,
|
|
161
|
+
)
|