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/quality/gates.py
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quality gate validation for session completion.
|
|
4
|
+
|
|
5
|
+
Refactored to use pluggable checker architecture for better maintainability.
|
|
6
|
+
|
|
7
|
+
Provides comprehensive validation including:
|
|
8
|
+
- Test execution with coverage
|
|
9
|
+
- Linting and formatting
|
|
10
|
+
- Security scanning
|
|
11
|
+
- Documentation validation
|
|
12
|
+
- Custom validation rules
|
|
13
|
+
|
|
14
|
+
Updated to use modular checker architecture while maintaining backward compatibility.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from solokit.core.command_runner import CommandRunner
|
|
24
|
+
from solokit.core.config import get_config_manager
|
|
25
|
+
from solokit.core.constants import QUALITY_CHECK_VERY_LONG_TIMEOUT
|
|
26
|
+
from solokit.core.error_handlers import log_errors
|
|
27
|
+
from solokit.core.exceptions import (
|
|
28
|
+
FileOperationError,
|
|
29
|
+
)
|
|
30
|
+
from solokit.core.logging_config import get_logger
|
|
31
|
+
from solokit.quality.checkers import (
|
|
32
|
+
CustomValidationChecker,
|
|
33
|
+
DocumentationChecker,
|
|
34
|
+
ExecutionChecker,
|
|
35
|
+
FormattingChecker,
|
|
36
|
+
LintingChecker,
|
|
37
|
+
SecurityChecker,
|
|
38
|
+
SpecCompletenessChecker,
|
|
39
|
+
)
|
|
40
|
+
from solokit.quality.reporters import ConsoleReporter
|
|
41
|
+
from solokit.quality.results import ResultAggregator
|
|
42
|
+
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
# Import spec validator for spec completeness quality gate
|
|
46
|
+
try:
|
|
47
|
+
from solokit.core.exceptions import SpecValidationError
|
|
48
|
+
from solokit.work_items.spec_validator import validate_spec_file
|
|
49
|
+
except ImportError:
|
|
50
|
+
validate_spec_file = None # type: ignore[assignment]
|
|
51
|
+
SpecValidationError = None # type: ignore[assignment, misc]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class QualityGates:
|
|
55
|
+
"""Quality gate validation using modular checker architecture.
|
|
56
|
+
|
|
57
|
+
This class maintains backward compatibility with the original QualityGates interface
|
|
58
|
+
while delegating to specialized checker classes internally.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, config_path: Path | None = None):
|
|
62
|
+
"""Initialize quality gates with configuration."""
|
|
63
|
+
if config_path is None:
|
|
64
|
+
config_path = Path(".session/config.json")
|
|
65
|
+
self._config_path = config_path
|
|
66
|
+
|
|
67
|
+
# Use ConfigManager for centralized config management
|
|
68
|
+
config_manager = get_config_manager()
|
|
69
|
+
config_manager.load_config(config_path)
|
|
70
|
+
self.config = config_manager.quality_gates # QualityGatesConfig dataclass
|
|
71
|
+
|
|
72
|
+
# Initialize command runner (for backward compatibility)
|
|
73
|
+
self.runner = CommandRunner(default_timeout=QUALITY_CHECK_VERY_LONG_TIMEOUT)
|
|
74
|
+
|
|
75
|
+
# Project root
|
|
76
|
+
self.project_root = Path.cwd()
|
|
77
|
+
|
|
78
|
+
# Result aggregator and reporter
|
|
79
|
+
self.aggregator = ResultAggregator()
|
|
80
|
+
self.reporter = ConsoleReporter()
|
|
81
|
+
|
|
82
|
+
@log_errors()
|
|
83
|
+
def _load_full_config(self) -> dict[str, Any]:
|
|
84
|
+
"""Load full configuration file for optional sections (context7, custom_validations, etc.)."""
|
|
85
|
+
if self._config_path.exists():
|
|
86
|
+
try:
|
|
87
|
+
with open(self._config_path) as f:
|
|
88
|
+
return json.load(f) # type: ignore[no-any-return]
|
|
89
|
+
except OSError as e:
|
|
90
|
+
raise FileOperationError(
|
|
91
|
+
operation="read",
|
|
92
|
+
file_path=str(self._config_path),
|
|
93
|
+
details="Failed to read configuration file",
|
|
94
|
+
cause=e,
|
|
95
|
+
) from e
|
|
96
|
+
except json.JSONDecodeError as e:
|
|
97
|
+
raise FileOperationError(
|
|
98
|
+
operation="parse",
|
|
99
|
+
file_path=str(self._config_path),
|
|
100
|
+
details="Invalid JSON in configuration file",
|
|
101
|
+
cause=e,
|
|
102
|
+
) from e
|
|
103
|
+
return {}
|
|
104
|
+
|
|
105
|
+
def _detect_language(self) -> str:
|
|
106
|
+
"""Detect primary project language."""
|
|
107
|
+
# Check for common files
|
|
108
|
+
if Path("pyproject.toml").exists() or Path("setup.py").exists():
|
|
109
|
+
return "python"
|
|
110
|
+
elif Path("package.json").exists():
|
|
111
|
+
# Check if TypeScript
|
|
112
|
+
if Path("tsconfig.json").exists():
|
|
113
|
+
return "typescript"
|
|
114
|
+
return "javascript"
|
|
115
|
+
|
|
116
|
+
return "python" # default
|
|
117
|
+
|
|
118
|
+
def run_tests(self, language: str | None = None) -> tuple[bool, dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Run test suite with coverage.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
language: Programming language (optional, will be detected if not provided)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
(passed: bool, results: dict)
|
|
127
|
+
"""
|
|
128
|
+
logger.info("Running test quality gate")
|
|
129
|
+
|
|
130
|
+
# Convert config dataclass to dict for checker
|
|
131
|
+
test_config = {
|
|
132
|
+
"enabled": self.config.test_execution.enabled,
|
|
133
|
+
"commands": self.config.test_execution.commands,
|
|
134
|
+
"coverage_threshold": self.config.test_execution.coverage_threshold,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Create and run test checker (pass runner for test compatibility)
|
|
138
|
+
checker = ExecutionChecker(
|
|
139
|
+
test_config, self.project_root, language=language, runner=self.runner
|
|
140
|
+
)
|
|
141
|
+
result = checker.run()
|
|
142
|
+
|
|
143
|
+
# Convert CheckResult to legacy format
|
|
144
|
+
# Extract reason from errors if present (for coverage failures)
|
|
145
|
+
reason = result.info.get("reason")
|
|
146
|
+
if not reason and result.errors:
|
|
147
|
+
# Check if any error is about coverage
|
|
148
|
+
for error in result.errors:
|
|
149
|
+
if isinstance(error, dict):
|
|
150
|
+
msg = error.get("message", "")
|
|
151
|
+
if "coverage" in msg.lower() and "threshold" in msg.lower():
|
|
152
|
+
reason = msg
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
return result.passed, {
|
|
156
|
+
"status": result.status,
|
|
157
|
+
"coverage": result.info.get("coverage"),
|
|
158
|
+
"returncode": result.info.get("returncode", 0),
|
|
159
|
+
"output": result.info.get("output", ""),
|
|
160
|
+
"errors": "\n".join(str(e) for e in result.errors) if result.errors else "",
|
|
161
|
+
"reason": reason,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def run_security_scan(self, language: str | None = None) -> tuple[bool, dict[str, Any]]:
|
|
165
|
+
"""
|
|
166
|
+
Run security vulnerability scanning.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
language: Programming language (optional, will be detected if not provided)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
(passed: bool, results: dict)
|
|
173
|
+
"""
|
|
174
|
+
logger.info("Running security scan quality gate")
|
|
175
|
+
|
|
176
|
+
# Convert config dataclass to dict for checker
|
|
177
|
+
security_config = {
|
|
178
|
+
"enabled": self.config.security.enabled,
|
|
179
|
+
"fail_on": self.config.security.fail_on,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Create and run security checker (pass runner for test compatibility)
|
|
183
|
+
checker = SecurityChecker(
|
|
184
|
+
security_config, self.project_root, language=language, runner=self.runner
|
|
185
|
+
)
|
|
186
|
+
result = checker.run()
|
|
187
|
+
|
|
188
|
+
# Convert CheckResult to legacy format
|
|
189
|
+
return result.passed, {
|
|
190
|
+
"status": result.status,
|
|
191
|
+
"vulnerabilities": result.errors,
|
|
192
|
+
"by_severity": result.info.get("by_severity", {}),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def run_linting(
|
|
196
|
+
self, language: str | None = None, auto_fix: bool | None = None
|
|
197
|
+
) -> tuple[bool, dict[str, Any]]:
|
|
198
|
+
"""Run linting with optional auto-fix.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
language: Programming language (optional, will be detected if not provided)
|
|
202
|
+
auto_fix: Whether to automatically fix issues (overrides config)
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
(passed: bool, results: dict)
|
|
206
|
+
"""
|
|
207
|
+
logger.info("Running linting quality gate")
|
|
208
|
+
|
|
209
|
+
# Convert config dataclass to dict for checker
|
|
210
|
+
linting_config = {
|
|
211
|
+
"enabled": self.config.linting.enabled,
|
|
212
|
+
"commands": self.config.linting.commands,
|
|
213
|
+
"auto_fix": self.config.linting.auto_fix,
|
|
214
|
+
"required": self.config.linting.required,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Create and run linting checker (pass runner for test compatibility)
|
|
218
|
+
checker = LintingChecker(
|
|
219
|
+
linting_config,
|
|
220
|
+
self.project_root,
|
|
221
|
+
language=language,
|
|
222
|
+
auto_fix=auto_fix,
|
|
223
|
+
runner=self.runner,
|
|
224
|
+
)
|
|
225
|
+
result = checker.run()
|
|
226
|
+
|
|
227
|
+
# Convert CheckResult to legacy format
|
|
228
|
+
return result.passed, {
|
|
229
|
+
"status": result.status,
|
|
230
|
+
"issues_found": result.info.get("issues_found", 0),
|
|
231
|
+
"output": result.info.get("output", ""),
|
|
232
|
+
"fixed": result.info.get("auto_fixed", False),
|
|
233
|
+
"reason": result.info.get("reason"),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def run_formatting(
|
|
237
|
+
self, language: str | None = None, auto_fix: bool | None = None
|
|
238
|
+
) -> tuple[bool, dict[str, Any]]:
|
|
239
|
+
"""Run code formatting.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
language: Programming language (optional, will be detected if not provided)
|
|
243
|
+
auto_fix: Whether to automatically format (overrides config)
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
(passed: bool, results: dict)
|
|
247
|
+
"""
|
|
248
|
+
logger.info("Running formatting quality gate")
|
|
249
|
+
|
|
250
|
+
# Convert config dataclass to dict for checker
|
|
251
|
+
formatting_config = {
|
|
252
|
+
"enabled": self.config.formatting.enabled,
|
|
253
|
+
"commands": self.config.formatting.commands,
|
|
254
|
+
"auto_fix": self.config.formatting.auto_fix,
|
|
255
|
+
"required": self.config.formatting.required,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Create and run formatting checker (pass runner for test compatibility)
|
|
259
|
+
checker = FormattingChecker(
|
|
260
|
+
formatting_config,
|
|
261
|
+
self.project_root,
|
|
262
|
+
language=language,
|
|
263
|
+
auto_fix=auto_fix,
|
|
264
|
+
runner=self.runner,
|
|
265
|
+
)
|
|
266
|
+
result = checker.run()
|
|
267
|
+
|
|
268
|
+
# Convert CheckResult to legacy format
|
|
269
|
+
return result.passed, {
|
|
270
|
+
"status": result.status,
|
|
271
|
+
"formatted": result.info.get("formatted", False),
|
|
272
|
+
"output": result.info.get("output", ""),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
def validate_documentation(self, work_item: dict | None = None) -> tuple[bool, dict[str, Any]]:
|
|
276
|
+
"""Validate documentation requirements.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
work_item: Work item dictionary (optional)
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
(passed: bool, results: dict)
|
|
283
|
+
"""
|
|
284
|
+
logger.info("Running documentation validation quality gate")
|
|
285
|
+
|
|
286
|
+
# Convert config dataclass to dict for checker
|
|
287
|
+
doc_config = {
|
|
288
|
+
"enabled": self.config.documentation.enabled,
|
|
289
|
+
"check_changelog": self.config.documentation.check_changelog,
|
|
290
|
+
"check_docstrings": self.config.documentation.check_docstrings,
|
|
291
|
+
"check_readme": self.config.documentation.check_readme,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# Create and run documentation checker (pass runner for test compatibility)
|
|
295
|
+
checker = DocumentationChecker(
|
|
296
|
+
doc_config, self.project_root, work_item=work_item, runner=self.runner
|
|
297
|
+
)
|
|
298
|
+
result = checker.run()
|
|
299
|
+
|
|
300
|
+
# Convert CheckResult to legacy format
|
|
301
|
+
return result.passed, {
|
|
302
|
+
"status": result.status,
|
|
303
|
+
"checks": result.info.get("checks", []),
|
|
304
|
+
"passed": result.passed,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
def validate_spec_completeness(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
308
|
+
"""
|
|
309
|
+
Validate that the work item specification file is complete.
|
|
310
|
+
|
|
311
|
+
Part of Phase 5.7.5: Spec File Validation System
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
work_item: Work item dictionary with 'id' and 'type' fields
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Tuple of (passed, results)
|
|
318
|
+
"""
|
|
319
|
+
logger.info(f"Running spec completeness validation for work item: {work_item.get('id')}")
|
|
320
|
+
|
|
321
|
+
# Convert config dataclass to dict for checker
|
|
322
|
+
spec_config = {
|
|
323
|
+
"enabled": self.config.spec_completeness.enabled,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Create and run spec completeness checker
|
|
327
|
+
checker = SpecCompletenessChecker(spec_config, self.project_root, work_item=work_item)
|
|
328
|
+
result = checker.run()
|
|
329
|
+
|
|
330
|
+
# Convert CheckResult to legacy format
|
|
331
|
+
if result.status == "skipped":
|
|
332
|
+
return True, {"status": "skipped", "reason": result.info.get("reason", "")}
|
|
333
|
+
|
|
334
|
+
if result.passed:
|
|
335
|
+
return True, {
|
|
336
|
+
"status": "passed",
|
|
337
|
+
"message": result.info.get("message", ""),
|
|
338
|
+
}
|
|
339
|
+
else:
|
|
340
|
+
return False, {
|
|
341
|
+
"status": "failed",
|
|
342
|
+
"errors": [
|
|
343
|
+
e.get("message", str(e)) if isinstance(e, dict) else str(e)
|
|
344
|
+
for e in result.errors
|
|
345
|
+
],
|
|
346
|
+
"message": result.info.get("message", ""),
|
|
347
|
+
"suggestion": result.info.get("suggestion", ""),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
def run_custom_validations(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
351
|
+
"""Run custom validation rules for work item.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
work_item: Work item dictionary
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
(passed: bool, results: dict)
|
|
358
|
+
"""
|
|
359
|
+
logger.info("Running custom validations quality gate")
|
|
360
|
+
|
|
361
|
+
# Get custom rules from full config
|
|
362
|
+
full_config = self._load_full_config()
|
|
363
|
+
custom_config = full_config.get("custom_validations", {})
|
|
364
|
+
|
|
365
|
+
# Create and run custom validation checker (pass runner for test compatibility)
|
|
366
|
+
checker = CustomValidationChecker(
|
|
367
|
+
custom_config, self.project_root, work_item=work_item, runner=self.runner
|
|
368
|
+
)
|
|
369
|
+
result = checker.run()
|
|
370
|
+
|
|
371
|
+
# Convert CheckResult to legacy format
|
|
372
|
+
return result.passed, {
|
|
373
|
+
"status": result.status,
|
|
374
|
+
"validations": result.info.get("validations", []),
|
|
375
|
+
"passed": result.passed,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
def check_required_gates(self) -> tuple[bool, list[str]]:
|
|
379
|
+
"""
|
|
380
|
+
Check if all required gates are configured.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
(all_required_met: bool, missing_gates: List[str])
|
|
384
|
+
"""
|
|
385
|
+
missing = []
|
|
386
|
+
|
|
387
|
+
# Check each quality gate
|
|
388
|
+
gates = {
|
|
389
|
+
"test_execution": self.config.test_execution,
|
|
390
|
+
"linting": self.config.linting,
|
|
391
|
+
"formatting": self.config.formatting,
|
|
392
|
+
"security": self.config.security,
|
|
393
|
+
"documentation": self.config.documentation,
|
|
394
|
+
"spec_completeness": self.config.spec_completeness,
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for gate_name, gate_config in gates.items():
|
|
398
|
+
if gate_config.required and not gate_config.enabled: # type: ignore[attr-defined]
|
|
399
|
+
missing.append(gate_name)
|
|
400
|
+
|
|
401
|
+
return len(missing) == 0, missing
|
|
402
|
+
|
|
403
|
+
# ========================================================================
|
|
404
|
+
# Integration Test Gates
|
|
405
|
+
# ========================================================================
|
|
406
|
+
|
|
407
|
+
def run_integration_tests(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
408
|
+
"""Run integration tests for integration test work items."""
|
|
409
|
+
from solokit.quality.checkers.integration import IntegrationChecker
|
|
410
|
+
|
|
411
|
+
checker = IntegrationChecker(
|
|
412
|
+
work_item=work_item,
|
|
413
|
+
config=self.config.integration.__dict__,
|
|
414
|
+
runner=self.runner,
|
|
415
|
+
config_path=self._config_path,
|
|
416
|
+
)
|
|
417
|
+
result = checker.run()
|
|
418
|
+
return result.passed, result.details
|
|
419
|
+
|
|
420
|
+
def validate_integration_environment(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
421
|
+
"""Validate integration test environment requirements."""
|
|
422
|
+
from solokit.quality.checkers.integration import IntegrationChecker
|
|
423
|
+
|
|
424
|
+
checker = IntegrationChecker(
|
|
425
|
+
work_item=work_item,
|
|
426
|
+
config=self.config.integration.__dict__,
|
|
427
|
+
runner=self.runner,
|
|
428
|
+
config_path=self._config_path,
|
|
429
|
+
)
|
|
430
|
+
result = checker.validate_environment()
|
|
431
|
+
return result.passed, result.details
|
|
432
|
+
|
|
433
|
+
def validate_integration_documentation(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
434
|
+
"""Validate integration test documentation requirements."""
|
|
435
|
+
from solokit.quality.checkers.integration import IntegrationChecker
|
|
436
|
+
|
|
437
|
+
checker = IntegrationChecker(
|
|
438
|
+
work_item=work_item,
|
|
439
|
+
config=self.config.integration.__dict__,
|
|
440
|
+
runner=self.runner,
|
|
441
|
+
config_path=self._config_path,
|
|
442
|
+
)
|
|
443
|
+
result = checker.validate_documentation()
|
|
444
|
+
return result.passed, result.details
|
|
445
|
+
|
|
446
|
+
def verify_context7_libraries(self) -> tuple[bool, dict[str, Any]]:
|
|
447
|
+
"""Verify important libraries via Context7 MCP."""
|
|
448
|
+
from solokit.quality.checkers.context7 import Context7Checker
|
|
449
|
+
|
|
450
|
+
checker = Context7Checker(
|
|
451
|
+
config=self.config.context7.__dict__,
|
|
452
|
+
project_root=self.project_root,
|
|
453
|
+
runner=self.runner,
|
|
454
|
+
)
|
|
455
|
+
result = checker.run()
|
|
456
|
+
return result.passed, result.details
|
|
457
|
+
|
|
458
|
+
def run_deployment_gates(self, work_item: dict) -> tuple[bool, dict[str, Any]]:
|
|
459
|
+
"""Run deployment-specific quality gates."""
|
|
460
|
+
from solokit.quality.checkers.deployment import DeploymentChecker
|
|
461
|
+
|
|
462
|
+
checker = DeploymentChecker(
|
|
463
|
+
work_item=work_item,
|
|
464
|
+
config=self.config.deployment.__dict__,
|
|
465
|
+
project_root=self.project_root,
|
|
466
|
+
runner=self.runner,
|
|
467
|
+
)
|
|
468
|
+
result = checker.run()
|
|
469
|
+
return result.passed, result.details
|
|
470
|
+
|
|
471
|
+
# ========================================================================
|
|
472
|
+
# Report Generation
|
|
473
|
+
# ========================================================================
|
|
474
|
+
|
|
475
|
+
def generate_report(self, all_results: dict) -> str:
|
|
476
|
+
"""Generate comprehensive quality gate report.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
all_results: Dictionary of results from all quality gates
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Formatted report string
|
|
483
|
+
"""
|
|
484
|
+
report = []
|
|
485
|
+
report.append("=" * 60)
|
|
486
|
+
report.append("QUALITY GATE RESULTS")
|
|
487
|
+
report.append("=" * 60)
|
|
488
|
+
|
|
489
|
+
# Test results
|
|
490
|
+
if "tests" in all_results:
|
|
491
|
+
test_results = all_results["tests"]
|
|
492
|
+
status = "✓ PASSED" if test_results.get("status") == "passed" else "✗ FAILED"
|
|
493
|
+
report.append(f"\nTests: {status}")
|
|
494
|
+
if test_results.get("coverage"):
|
|
495
|
+
report.append(f" Coverage: {test_results['coverage']}%")
|
|
496
|
+
|
|
497
|
+
# Security results
|
|
498
|
+
if "security" in all_results:
|
|
499
|
+
sec_results = all_results["security"]
|
|
500
|
+
status = "✓ PASSED" if sec_results.get("status") == "passed" else "✗ FAILED"
|
|
501
|
+
report.append(f"\nSecurity: {status}")
|
|
502
|
+
if sec_results.get("by_severity"):
|
|
503
|
+
for severity, count in sec_results["by_severity"].items():
|
|
504
|
+
report.append(f" {severity}: {count}")
|
|
505
|
+
|
|
506
|
+
# Linting results
|
|
507
|
+
if "linting" in all_results:
|
|
508
|
+
lint_results = all_results["linting"]
|
|
509
|
+
if lint_results.get("status") == "passed":
|
|
510
|
+
status = "✓ PASSED"
|
|
511
|
+
elif lint_results.get("status") == "skipped":
|
|
512
|
+
status = "⊘ SKIPPED"
|
|
513
|
+
else:
|
|
514
|
+
status = "✗ FAILED"
|
|
515
|
+
report.append(f"\nLinting: {status}")
|
|
516
|
+
if lint_results.get("fixed"):
|
|
517
|
+
report.append(" Auto-fix applied")
|
|
518
|
+
|
|
519
|
+
# Formatting results
|
|
520
|
+
if "formatting" in all_results:
|
|
521
|
+
fmt_results = all_results["formatting"]
|
|
522
|
+
if fmt_results.get("status") == "passed":
|
|
523
|
+
status = "✓ PASSED"
|
|
524
|
+
elif fmt_results.get("status") == "skipped":
|
|
525
|
+
status = "⊘ SKIPPED"
|
|
526
|
+
else:
|
|
527
|
+
status = "✗ FAILED"
|
|
528
|
+
report.append(f"\nFormatting: {status}")
|
|
529
|
+
if fmt_results.get("formatted"):
|
|
530
|
+
report.append(" Auto-format applied")
|
|
531
|
+
|
|
532
|
+
# Documentation results
|
|
533
|
+
if "documentation" in all_results:
|
|
534
|
+
doc_results = all_results["documentation"]
|
|
535
|
+
status = "✓ PASSED" if doc_results.get("status") == "passed" else "✗ FAILED"
|
|
536
|
+
report.append(f"\nDocumentation: {status}")
|
|
537
|
+
for check in doc_results.get("checks", []):
|
|
538
|
+
check_status = "✓" if check["passed"] else "✗"
|
|
539
|
+
report.append(f" {check_status} {check['name']}")
|
|
540
|
+
|
|
541
|
+
# Context7 results
|
|
542
|
+
if "context7" in all_results:
|
|
543
|
+
ctx_results = all_results["context7"]
|
|
544
|
+
if ctx_results.get("status") != "skipped":
|
|
545
|
+
status = "✓ PASSED" if ctx_results.get("status") == "passed" else "✗ FAILED"
|
|
546
|
+
report.append(f"\nContext7: {status}")
|
|
547
|
+
report.append(f" Verified: {ctx_results.get('verified', 0)}")
|
|
548
|
+
report.append(f" Failed: {ctx_results.get('failed', 0)}")
|
|
549
|
+
|
|
550
|
+
# Custom validations results
|
|
551
|
+
if "custom" in all_results:
|
|
552
|
+
custom_results = all_results["custom"]
|
|
553
|
+
if custom_results.get("status") == "passed":
|
|
554
|
+
status = "✓ PASSED"
|
|
555
|
+
elif custom_results.get("status") == "skipped":
|
|
556
|
+
status = "⊘ SKIPPED"
|
|
557
|
+
else:
|
|
558
|
+
status = "✗ FAILED"
|
|
559
|
+
report.append(f"\nCustom Validations: {status}")
|
|
560
|
+
for validation in custom_results.get("validations", []):
|
|
561
|
+
val_status = "✓" if validation["passed"] else "✗"
|
|
562
|
+
required_mark = " (required)" if validation["required"] else ""
|
|
563
|
+
report.append(f" {val_status} {validation['name']}{required_mark}")
|
|
564
|
+
|
|
565
|
+
report.append("\n" + "=" * 60)
|
|
566
|
+
|
|
567
|
+
return "\n".join(report)
|
|
568
|
+
|
|
569
|
+
def get_remediation_guidance(self, failed_gates: list[str]) -> str:
|
|
570
|
+
"""Get remediation guidance for failed gates.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
failed_gates: List of failed gate names
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
Formatted remediation guidance string
|
|
577
|
+
"""
|
|
578
|
+
guidance = []
|
|
579
|
+
guidance.append("\nREMEDIATION GUIDANCE:")
|
|
580
|
+
guidance.append("-" * 60)
|
|
581
|
+
|
|
582
|
+
for gate in failed_gates:
|
|
583
|
+
if gate == "tests":
|
|
584
|
+
guidance.append("\n• Tests Failed:")
|
|
585
|
+
guidance.append(" - Review test output above")
|
|
586
|
+
guidance.append(" - Fix failing tests")
|
|
587
|
+
guidance.append(" - Improve coverage if below threshold")
|
|
588
|
+
|
|
589
|
+
elif gate == "security":
|
|
590
|
+
guidance.append("\n• Security Issues Found:")
|
|
591
|
+
guidance.append(" - Review vulnerability details above")
|
|
592
|
+
guidance.append(" - Update vulnerable dependencies")
|
|
593
|
+
guidance.append(" - Fix high/critical issues immediately")
|
|
594
|
+
|
|
595
|
+
elif gate == "linting":
|
|
596
|
+
guidance.append("\n• Linting Issues:")
|
|
597
|
+
guidance.append(" - Run with --auto-fix to fix automatically")
|
|
598
|
+
guidance.append(" - Review remaining issues manually")
|
|
599
|
+
|
|
600
|
+
elif gate == "formatting":
|
|
601
|
+
guidance.append("\n• Formatting Issues:")
|
|
602
|
+
guidance.append(" - Run with --auto-format to fix automatically")
|
|
603
|
+
guidance.append(" - Ensure consistent code style")
|
|
604
|
+
|
|
605
|
+
elif gate == "documentation":
|
|
606
|
+
guidance.append("\n• Documentation Issues:")
|
|
607
|
+
guidance.append(" Update CHANGELOG.md with your changes:")
|
|
608
|
+
guidance.append("")
|
|
609
|
+
guidance.append(" ## [Unreleased]")
|
|
610
|
+
guidance.append(" ### Added")
|
|
611
|
+
guidance.append(" - Feature: User authentication with JWT")
|
|
612
|
+
guidance.append(" - Tests: Comprehensive auth endpoint tests")
|
|
613
|
+
guidance.append("")
|
|
614
|
+
guidance.append(" Then commit:")
|
|
615
|
+
guidance.append(" git add CHANGELOG.md")
|
|
616
|
+
guidance.append(" git commit -m 'docs: Update CHANGELOG'")
|
|
617
|
+
guidance.append("")
|
|
618
|
+
guidance.append(" - Add docstrings to new functions")
|
|
619
|
+
guidance.append(" - Update README if needed")
|
|
620
|
+
|
|
621
|
+
elif gate == "context7":
|
|
622
|
+
guidance.append("\n• Context7 Library Verification Failed:")
|
|
623
|
+
guidance.append(" - Review failed library versions")
|
|
624
|
+
guidance.append(" - Update outdated libraries")
|
|
625
|
+
guidance.append(" - Check for security updates")
|
|
626
|
+
|
|
627
|
+
elif gate == "custom":
|
|
628
|
+
guidance.append("\n• Custom Validation Failed:")
|
|
629
|
+
guidance.append(" - Review failed validation rules")
|
|
630
|
+
guidance.append(" - Address required validations")
|
|
631
|
+
guidance.append(" - Check work item requirements")
|
|
632
|
+
|
|
633
|
+
return "\n".join(guidance)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def main() -> None:
|
|
637
|
+
"""CLI entry point."""
|
|
638
|
+
gates = QualityGates()
|
|
639
|
+
|
|
640
|
+
logger.info("Running quality gates...")
|
|
641
|
+
|
|
642
|
+
# Run tests
|
|
643
|
+
passed, results = gates.run_tests()
|
|
644
|
+
logger.info(f"\nTest Execution: {'✓ PASSED' if passed else '✗ FAILED'}")
|
|
645
|
+
if results.get("coverage"):
|
|
646
|
+
logger.info(f" Coverage: {results['coverage']}%")
|
|
647
|
+
|
|
648
|
+
# Check required gates
|
|
649
|
+
all_met, missing = gates.check_required_gates()
|
|
650
|
+
if not all_met:
|
|
651
|
+
logger.error(f"\n✗ Missing required gates: {', '.join(missing)}")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
if __name__ == "__main__":
|
|
655
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Quality gate reporters."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from solokit.quality.reporters.base import Reporter
|
|
7
|
+
from solokit.quality.reporters.console import ConsoleReporter
|
|
8
|
+
from solokit.quality.reporters.json_reporter import JSONReporter
|
|
9
|
+
|
|
10
|
+
__all__ = ["Reporter", "ConsoleReporter", "JSONReporter"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Base reporter interface for quality gate results.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Reporter(ABC):
|
|
13
|
+
"""Abstract base class for result reporters."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def generate(self, aggregated_results: dict[str, Any]) -> str:
|
|
17
|
+
"""Generate a report from aggregated results.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
aggregated_results: Aggregated results from ResultAggregator
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Formatted report string
|
|
24
|
+
"""
|
|
25
|
+
pass
|