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,424 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API contract validation for integration tests.
|
|
4
|
+
|
|
5
|
+
Supports:
|
|
6
|
+
- OpenAPI/Swagger specification validation
|
|
7
|
+
- Breaking change detection
|
|
8
|
+
- Contract testing
|
|
9
|
+
- Version compatibility checking
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from solokit.core.error_handlers import convert_file_errors, log_errors
|
|
20
|
+
from solokit.core.exceptions import (
|
|
21
|
+
BreakingChangeError,
|
|
22
|
+
FileOperationError,
|
|
23
|
+
InvalidOpenAPISpecError,
|
|
24
|
+
SchemaValidationError,
|
|
25
|
+
WorkItemNotFoundError,
|
|
26
|
+
)
|
|
27
|
+
from solokit.core.exceptions import (
|
|
28
|
+
FileNotFoundError as SolokitFileNotFoundError,
|
|
29
|
+
)
|
|
30
|
+
from solokit.core.file_ops import load_json
|
|
31
|
+
from solokit.core.output import get_output
|
|
32
|
+
|
|
33
|
+
output = get_output()
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class APIContractValidator:
|
|
38
|
+
"""Validate API contracts for integration tests."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, work_item: dict):
|
|
41
|
+
"""
|
|
42
|
+
Initialize API contract validator.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
work_item: Integration test work item with contract specifications
|
|
46
|
+
"""
|
|
47
|
+
self.work_item = work_item
|
|
48
|
+
self.contracts = work_item.get("api_contracts", [])
|
|
49
|
+
self.results: dict[str, Any] = {
|
|
50
|
+
"contracts_validated": 0,
|
|
51
|
+
"breaking_changes": [],
|
|
52
|
+
"warnings": [],
|
|
53
|
+
"passed": False,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@log_errors()
|
|
57
|
+
def validate_contracts(self) -> tuple[bool, dict[str, Any]]:
|
|
58
|
+
"""
|
|
59
|
+
Validate all API contracts.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
(passed: bool, results: dict)
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
SchemaValidationError: If contract file validation fails
|
|
66
|
+
InvalidOpenAPISpecError: If OpenAPI/Swagger spec is invalid
|
|
67
|
+
BreakingChangeError: If breaking changes detected and not allowed
|
|
68
|
+
FileNotFoundError: If contract file not found
|
|
69
|
+
"""
|
|
70
|
+
logger.info(f"Validating {len(self.contracts)} API contracts...")
|
|
71
|
+
|
|
72
|
+
all_passed = True
|
|
73
|
+
|
|
74
|
+
for contract in self.contracts:
|
|
75
|
+
contract_file = contract.get("contract_file")
|
|
76
|
+
if not contract_file:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Validate contract file exists and is valid
|
|
80
|
+
try:
|
|
81
|
+
self._validate_contract_file(contract_file)
|
|
82
|
+
except (
|
|
83
|
+
SchemaValidationError,
|
|
84
|
+
InvalidOpenAPISpecError,
|
|
85
|
+
SolokitFileNotFoundError,
|
|
86
|
+
) as e:
|
|
87
|
+
logger.error(f"Contract validation failed for {contract_file}: {e.message}")
|
|
88
|
+
all_passed = False
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Check for breaking changes if previous version exists
|
|
92
|
+
previous_version = contract.get("previous_version")
|
|
93
|
+
if previous_version:
|
|
94
|
+
try:
|
|
95
|
+
breaking_changes = self._detect_breaking_changes(
|
|
96
|
+
contract_file, previous_version
|
|
97
|
+
)
|
|
98
|
+
if breaking_changes:
|
|
99
|
+
self.results["breaking_changes"].extend(breaking_changes)
|
|
100
|
+
if not contract.get("allow_breaking_changes", False):
|
|
101
|
+
all_passed = False
|
|
102
|
+
except BreakingChangeError as e:
|
|
103
|
+
logger.error(f"Breaking change detection failed: {e.message}")
|
|
104
|
+
all_passed = False
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
self.results["contracts_validated"] += 1
|
|
108
|
+
|
|
109
|
+
self.results["passed"] = all_passed
|
|
110
|
+
return all_passed, self.results
|
|
111
|
+
|
|
112
|
+
@log_errors()
|
|
113
|
+
def _validate_contract_file(self, contract_file: str) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Validate OpenAPI/Swagger contract file.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
contract_file: Path to contract file
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
FileNotFoundError: If contract file not found
|
|
122
|
+
SchemaValidationError: If contract parsing fails
|
|
123
|
+
InvalidOpenAPISpecError: If not a valid OpenAPI/Swagger spec
|
|
124
|
+
"""
|
|
125
|
+
contract_path = Path(contract_file)
|
|
126
|
+
|
|
127
|
+
if not contract_path.exists():
|
|
128
|
+
raise SolokitFileNotFoundError(file_path=contract_file, file_type="API contract")
|
|
129
|
+
|
|
130
|
+
# Load contract
|
|
131
|
+
try:
|
|
132
|
+
if contract_file.endswith(".yaml") or contract_file.endswith(".yml"):
|
|
133
|
+
with open(contract_path) as f:
|
|
134
|
+
spec = yaml.safe_load(f)
|
|
135
|
+
else:
|
|
136
|
+
with open(contract_path) as f:
|
|
137
|
+
spec = json.load(f)
|
|
138
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
139
|
+
raise SchemaValidationError(
|
|
140
|
+
contract_file=contract_file,
|
|
141
|
+
details=f"Failed to parse contract file: {e}",
|
|
142
|
+
) from e
|
|
143
|
+
except OSError as e:
|
|
144
|
+
raise FileOperationError(
|
|
145
|
+
operation="read", file_path=contract_file, details=str(e), cause=e
|
|
146
|
+
) from e
|
|
147
|
+
|
|
148
|
+
# Validate OpenAPI structure
|
|
149
|
+
if "openapi" not in spec and "swagger" not in spec:
|
|
150
|
+
raise InvalidOpenAPISpecError(
|
|
151
|
+
contract_file=contract_file,
|
|
152
|
+
details="Missing 'openapi' or 'swagger' field",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Validate required fields
|
|
156
|
+
if "paths" not in spec:
|
|
157
|
+
raise SchemaValidationError(
|
|
158
|
+
contract_file=contract_file, details="Missing 'paths' field"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
logger.info(f"Contract valid: {contract_file}")
|
|
162
|
+
|
|
163
|
+
@log_errors()
|
|
164
|
+
def _detect_breaking_changes(self, current_file: str, previous_file: str) -> list[dict]:
|
|
165
|
+
"""
|
|
166
|
+
Detect breaking changes between contract versions.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
current_file: Path to current contract
|
|
170
|
+
previous_file: Path to previous contract version
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of breaking changes
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
FileNotFoundError: If contract file not found
|
|
177
|
+
SchemaValidationError: If contract parsing fails
|
|
178
|
+
BreakingChangeError: If breaking changes detected and not allowed
|
|
179
|
+
"""
|
|
180
|
+
breaking_changes = []
|
|
181
|
+
|
|
182
|
+
# Load both versions
|
|
183
|
+
try:
|
|
184
|
+
current_spec = self._load_spec(current_file)
|
|
185
|
+
previous_spec = self._load_spec(previous_file)
|
|
186
|
+
except (SolokitFileNotFoundError, SchemaValidationError, FileOperationError) as e:
|
|
187
|
+
logger.error(f"Failed to load contract specs: {e.message}")
|
|
188
|
+
return [{"type": "load_error", "message": str(e)}]
|
|
189
|
+
|
|
190
|
+
# Check for removed endpoints
|
|
191
|
+
previous_paths = set(previous_spec.get("paths", {}).keys())
|
|
192
|
+
current_paths = set(current_spec.get("paths", {}).keys())
|
|
193
|
+
|
|
194
|
+
removed_paths = previous_paths - current_paths
|
|
195
|
+
for path in removed_paths:
|
|
196
|
+
breaking_changes.append(
|
|
197
|
+
{
|
|
198
|
+
"type": "removed_endpoint",
|
|
199
|
+
"path": path,
|
|
200
|
+
"severity": "high",
|
|
201
|
+
"message": f"Endpoint removed: {path}",
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Check for modified endpoints
|
|
206
|
+
for path in previous_paths & current_paths:
|
|
207
|
+
endpoint_changes = self._check_endpoint_changes(
|
|
208
|
+
path, previous_spec["paths"][path], current_spec["paths"][path]
|
|
209
|
+
)
|
|
210
|
+
breaking_changes.extend(endpoint_changes)
|
|
211
|
+
|
|
212
|
+
if breaking_changes:
|
|
213
|
+
logger.warning(f"{len(breaking_changes)} breaking changes detected")
|
|
214
|
+
for change in breaking_changes:
|
|
215
|
+
logger.warning(f" - {change['type']}: {change['message']}")
|
|
216
|
+
else:
|
|
217
|
+
logger.info("No breaking changes detected")
|
|
218
|
+
|
|
219
|
+
return breaking_changes
|
|
220
|
+
|
|
221
|
+
@convert_file_errors
|
|
222
|
+
def _load_spec(self, file_path: str) -> dict[str, Any]:
|
|
223
|
+
"""
|
|
224
|
+
Load OpenAPI/Swagger spec from file.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
file_path: Path to spec file
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Parsed spec dictionary
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
FileNotFoundError: If file not found
|
|
234
|
+
SchemaValidationError: If parsing fails
|
|
235
|
+
"""
|
|
236
|
+
path = Path(file_path)
|
|
237
|
+
|
|
238
|
+
if not path.exists():
|
|
239
|
+
raise SolokitFileNotFoundError(file_path=file_path, file_type="API contract")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
if file_path.endswith(".yaml") or file_path.endswith(".yml"):
|
|
243
|
+
with open(path) as f:
|
|
244
|
+
return yaml.safe_load(f) # type: ignore[no-any-return]
|
|
245
|
+
else:
|
|
246
|
+
with open(path) as f:
|
|
247
|
+
return json.load(f) # type: ignore[no-any-return]
|
|
248
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
249
|
+
raise SchemaValidationError(
|
|
250
|
+
contract_file=file_path, details=f"Failed to parse contract file: {e}"
|
|
251
|
+
) from e
|
|
252
|
+
|
|
253
|
+
def _check_endpoint_changes(self, path: str, previous: dict, current: dict) -> list[dict]:
|
|
254
|
+
"""
|
|
255
|
+
Check for breaking changes in a specific endpoint.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
path: Endpoint path
|
|
259
|
+
previous: Previous endpoint definition
|
|
260
|
+
current: Current endpoint definition
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of breaking changes
|
|
264
|
+
"""
|
|
265
|
+
changes = []
|
|
266
|
+
|
|
267
|
+
# Check HTTP methods
|
|
268
|
+
previous_methods = set(previous.keys())
|
|
269
|
+
current_methods = set(current.keys())
|
|
270
|
+
|
|
271
|
+
removed_methods = previous_methods - current_methods
|
|
272
|
+
for method in removed_methods:
|
|
273
|
+
changes.append(
|
|
274
|
+
{
|
|
275
|
+
"type": "removed_method",
|
|
276
|
+
"path": path,
|
|
277
|
+
"method": method.upper(),
|
|
278
|
+
"severity": "high",
|
|
279
|
+
"message": f"HTTP method removed: {method.upper()} {path}",
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Check parameters for common methods
|
|
284
|
+
for method in previous_methods & current_methods:
|
|
285
|
+
if method in ["get", "post", "put", "patch", "delete"]:
|
|
286
|
+
param_changes = self._check_parameter_changes(
|
|
287
|
+
path, method, previous.get(method, {}), current.get(method, {})
|
|
288
|
+
)
|
|
289
|
+
changes.extend(param_changes)
|
|
290
|
+
|
|
291
|
+
return changes
|
|
292
|
+
|
|
293
|
+
def _check_parameter_changes(
|
|
294
|
+
self, path: str, method: str, previous: dict, current: dict
|
|
295
|
+
) -> list[dict]:
|
|
296
|
+
"""
|
|
297
|
+
Check for breaking changes in endpoint parameters.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
path: Endpoint path
|
|
301
|
+
method: HTTP method
|
|
302
|
+
previous: Previous endpoint definition
|
|
303
|
+
current: Current endpoint definition
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of breaking changes
|
|
307
|
+
"""
|
|
308
|
+
changes = []
|
|
309
|
+
|
|
310
|
+
previous_params = {p["name"]: p for p in previous.get("parameters", [])}
|
|
311
|
+
current_params = {p["name"]: p for p in current.get("parameters", [])}
|
|
312
|
+
|
|
313
|
+
# Check for removed required parameters
|
|
314
|
+
for param_name, param in previous_params.items():
|
|
315
|
+
if param.get("required", False):
|
|
316
|
+
if param_name not in current_params:
|
|
317
|
+
changes.append(
|
|
318
|
+
{
|
|
319
|
+
"type": "removed_required_parameter",
|
|
320
|
+
"path": path,
|
|
321
|
+
"method": method.upper(),
|
|
322
|
+
"parameter": param_name,
|
|
323
|
+
"severity": "high",
|
|
324
|
+
"message": f"Required parameter removed: {param_name} from {method.upper()} {path}",
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Check for newly required parameters (breaking change)
|
|
329
|
+
for param_name, param in current_params.items():
|
|
330
|
+
if param.get("required", False):
|
|
331
|
+
if param_name not in previous_params:
|
|
332
|
+
changes.append(
|
|
333
|
+
{
|
|
334
|
+
"type": "added_required_parameter",
|
|
335
|
+
"path": path,
|
|
336
|
+
"method": method.upper(),
|
|
337
|
+
"parameter": param_name,
|
|
338
|
+
"severity": "high",
|
|
339
|
+
"message": f"New required parameter: {param_name} in {method.upper()} {path}",
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
elif not previous_params[param_name].get("required", False):
|
|
343
|
+
changes.append(
|
|
344
|
+
{
|
|
345
|
+
"type": "parameter_now_required",
|
|
346
|
+
"path": path,
|
|
347
|
+
"method": method.upper(),
|
|
348
|
+
"parameter": param_name,
|
|
349
|
+
"severity": "high",
|
|
350
|
+
"message": f"Parameter became required: {param_name} in {method.upper()} {path}",
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return changes
|
|
355
|
+
|
|
356
|
+
def generate_report(self) -> str:
|
|
357
|
+
"""
|
|
358
|
+
Generate API contract validation report.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Formatted report string
|
|
362
|
+
"""
|
|
363
|
+
report = f"""
|
|
364
|
+
API Contract Validation Report
|
|
365
|
+
{"=" * 80}
|
|
366
|
+
|
|
367
|
+
Contracts Validated: {self.results["contracts_validated"]}
|
|
368
|
+
|
|
369
|
+
Breaking Changes: {len(self.results["breaking_changes"])}
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
if self.results["breaking_changes"]:
|
|
373
|
+
report += "\nBreaking Changes Detected:\n"
|
|
374
|
+
for change in self.results["breaking_changes"]:
|
|
375
|
+
report += f" • [{change['severity'].upper()}] {change['message']}\n"
|
|
376
|
+
|
|
377
|
+
if self.results["warnings"]:
|
|
378
|
+
report += "\nWarnings:\n"
|
|
379
|
+
for warning in self.results["warnings"]:
|
|
380
|
+
report += f" • {warning}\n"
|
|
381
|
+
|
|
382
|
+
report += f"\nStatus: {'PASSED' if self.results['passed'] else 'FAILED'}\n"
|
|
383
|
+
|
|
384
|
+
return report
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@log_errors()
|
|
388
|
+
def main() -> None:
|
|
389
|
+
"""
|
|
390
|
+
CLI entry point.
|
|
391
|
+
|
|
392
|
+
Raises:
|
|
393
|
+
WorkItemNotFoundError: If work item not found
|
|
394
|
+
Various validation errors from APIContractValidator
|
|
395
|
+
"""
|
|
396
|
+
import sys
|
|
397
|
+
|
|
398
|
+
if len(sys.argv) < 2:
|
|
399
|
+
logger.error("Usage: python api_contract_validator.py <work_item_id>")
|
|
400
|
+
sys.exit(1)
|
|
401
|
+
|
|
402
|
+
work_item_id = sys.argv[1]
|
|
403
|
+
|
|
404
|
+
# Load work item
|
|
405
|
+
work_items_file = Path(".session/tracking/work_items.json")
|
|
406
|
+
data = load_json(work_items_file)
|
|
407
|
+
work_item = data["work_items"].get(work_item_id)
|
|
408
|
+
|
|
409
|
+
if not work_item:
|
|
410
|
+
raise WorkItemNotFoundError(work_item_id)
|
|
411
|
+
|
|
412
|
+
# Validate contracts
|
|
413
|
+
validator = APIContractValidator(work_item)
|
|
414
|
+
try:
|
|
415
|
+
passed, results = validator.validate_contracts()
|
|
416
|
+
output.info(validator.generate_report())
|
|
417
|
+
sys.exit(0 if passed else 1)
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logger.error(f"Validation failed: {e}")
|
|
420
|
+
raise
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if __name__ == "__main__":
|
|
424
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Quality checker modules."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from solokit.quality.checkers.base import CheckResult, QualityChecker
|
|
7
|
+
from solokit.quality.checkers.custom import CustomValidationChecker
|
|
8
|
+
from solokit.quality.checkers.documentation import DocumentationChecker
|
|
9
|
+
from solokit.quality.checkers.formatting import FormattingChecker
|
|
10
|
+
from solokit.quality.checkers.linting import LintingChecker
|
|
11
|
+
from solokit.quality.checkers.security import SecurityChecker
|
|
12
|
+
from solokit.quality.checkers.spec_completeness import SpecCompletenessChecker
|
|
13
|
+
from solokit.quality.checkers.tests import ExecutionChecker
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CheckResult",
|
|
17
|
+
"QualityChecker",
|
|
18
|
+
"CustomValidationChecker",
|
|
19
|
+
"DocumentationChecker",
|
|
20
|
+
"FormattingChecker",
|
|
21
|
+
"LintingChecker",
|
|
22
|
+
"SecurityChecker",
|
|
23
|
+
"SpecCompletenessChecker",
|
|
24
|
+
"ExecutionChecker",
|
|
25
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Base classes for quality checkers.
|
|
4
|
+
|
|
5
|
+
Defines the abstract interface for all quality checkers and the result structure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Union
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CheckResult:
|
|
18
|
+
"""Result from a quality check execution.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
checker_name: Name of the checker that produced this result
|
|
22
|
+
passed: Whether the check passed overall
|
|
23
|
+
status: Status string (e.g., "passed", "failed", "skipped")
|
|
24
|
+
errors: List of error messages or error dictionaries
|
|
25
|
+
warnings: List of warning messages or warning dictionaries
|
|
26
|
+
info: Additional information about the check execution
|
|
27
|
+
execution_time: Time taken to run the check in seconds
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
checker_name: str
|
|
31
|
+
passed: bool
|
|
32
|
+
status: str
|
|
33
|
+
errors: list[Union[dict[str, Any], str]] = field(default_factory=list) # noqa: UP007
|
|
34
|
+
warnings: list[Union[dict[str, Any], str]] = field(default_factory=list) # noqa: UP007
|
|
35
|
+
info: dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
execution_time: float = 0.0
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def details(self) -> dict[str, Any]:
|
|
40
|
+
"""Return check result as details dict for backward compatibility.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dict containing status, passed, and info merged together
|
|
44
|
+
"""
|
|
45
|
+
result_dict: dict[str, Any] = {"status": self.status, "passed": self.passed}
|
|
46
|
+
result_dict.update(self.info)
|
|
47
|
+
if self.errors:
|
|
48
|
+
result_dict["errors"] = self.errors
|
|
49
|
+
if self.warnings:
|
|
50
|
+
result_dict["warnings"] = self.warnings
|
|
51
|
+
return result_dict
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class QualityChecker(ABC):
|
|
55
|
+
"""Abstract base class for quality checkers.
|
|
56
|
+
|
|
57
|
+
All quality checkers must inherit from this class and implement
|
|
58
|
+
the abstract methods. This provides a consistent interface for
|
|
59
|
+
the orchestrator to execute different types of quality checks.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: dict[str, Any], project_root: Path | None = None):
|
|
63
|
+
"""Initialize the quality checker.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
config: Configuration dictionary for this checker
|
|
67
|
+
project_root: Root directory of the project (defaults to current directory)
|
|
68
|
+
"""
|
|
69
|
+
self.config = config
|
|
70
|
+
self.project_root = project_root or Path.cwd()
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def name(self) -> str:
|
|
74
|
+
"""Return the name of this checker.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A unique identifier for this checker (e.g., "bandit", "tests")
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def is_enabled(self) -> bool:
|
|
83
|
+
"""Check if this checker is enabled in the configuration.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if the checker should run, False to skip
|
|
87
|
+
"""
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def run(self) -> CheckResult:
|
|
92
|
+
"""Execute the quality check.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
CheckResult containing the outcome of the check
|
|
96
|
+
"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
def _create_skipped_result(self, reason: str = "disabled") -> CheckResult:
|
|
100
|
+
"""Helper to create a skipped result.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
reason: Reason why the check was skipped
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
CheckResult marked as skipped
|
|
107
|
+
"""
|
|
108
|
+
return CheckResult(
|
|
109
|
+
checker_name=self.name(),
|
|
110
|
+
passed=True,
|
|
111
|
+
status="skipped",
|
|
112
|
+
info={"reason": reason},
|
|
113
|
+
execution_time=0.0,
|
|
114
|
+
)
|