codd-dev 1.30.0__tar.gz → 1.32.0__tar.gz
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.
- {codd_dev-1.30.0 → codd_dev-1.32.0}/PKG-INFO +1 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/__init__.py +1 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/cli.py +378 -26
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/coverage_auditor.py +2 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/builder.py +35 -1
- codd_dev-1.32.0/codd/dag/checks/environment_coverage.py +238 -0
- codd_dev-1.32.0/codd/dag/coverage_axes.py +177 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/extractor.py +4 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/runner.py +1 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/defaults.yaml +6 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployer.py +1 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/cdp_browser.py +156 -4
- codd_dev-1.32.0/codd/deployment/providers/verification/cdp_engines.py +165 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hitl_session.py +2 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/implementer/__init__.py +3 -0
- codd_dev-1.32.0/codd/implementer/typecheck_loop.py +241 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/implementer.py +55 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/knowledge_fetcher.py +2 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/best_practice_augmenter.py +2 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/criteria_expander.py +118 -4
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/impl_step_deriver.py +4 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/best_practice_augment_meta.md +12 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/criteria_expand_meta.md +9 -2
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/impl_step_derive_meta.md +8 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/planner.py +2 -1
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/verify_runner.py +13 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/codd.yaml.tmpl +14 -7
- {codd_dev-1.30.0 → codd_dev-1.32.0}/pyproject.toml +1 -1
- codd_dev-1.32.0/tests/integration/standalone_repair_skeleton/README.md +3 -0
- codd_dev-1.30.0/codd/deployment/providers/verification/cdp_engines.py +0 -40
- {codd_dev-1.30.0 → codd_dev-1.32.0}/.gitignore +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/LICENSE +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/README.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/__main__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/_git_helper.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/ask_user_question_adapter.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/assembler.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/bridge.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/clustering.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/coherence_adapters.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/coherence_engine.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/config.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/contracts.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/coverage_metrics.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/depends_on_consistency.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/deployment_completeness.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/edge_validity.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/implementation_coverage.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/node_completeness.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/task_completion.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/transitive_closure.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/checks/user_journey_coherence.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/cli.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/cpp_embedded.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/csharp.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/elixir.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/generic.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/iot.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/java.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/kotlin.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/mobile.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/ruby.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/rust.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/scala.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/swift.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/test_frameworks.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/dag/defaults/web.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deploy_targets/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deploy_targets/app_service.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deploy_targets/base.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deploy_targets/docker_compose.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/checks/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/defaults/deploy_targets.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/defaults/runtime_capability_inference.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/defaults/schema_providers.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/defaults/verification_templates.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/ai_command.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/llm_consideration.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/schema/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/schema/prisma.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/target/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/target/docker_compose.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/assertion_handlers.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/cdp_launchers.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/cdp_wire.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/curl.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/form_strategies.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/means_catalog.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/deployment/providers/verification/playwright.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/design_md.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/drift.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/e2e_extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/e2e_generator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/e2e_runner.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/env_refs.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/extract_ai.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixer.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixup_drift.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixup_drift_strategies/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/generator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/graph.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/recipes/claude_settings_example.json +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/recipes/codex_hook.sh +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/recipes/git_post_commit.sh +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/hooks/recipes/git_pre_commit.sh +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/implementer/chunked_runner.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/inheritance.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/lexicon.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/approval.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/design_doc_extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/means_catalog_loader.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/parser.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/plan_deriver.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/prompt_builder.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/strategy_validator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/design_doc_extract_meta.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/implementation_step_catalog.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/meta_instruction.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/plan_derive_meta.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/llm/templates/verification_means_catalog.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/mcp_server.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/measure.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/parsing.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/policy.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/preflight/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/preflight/defaults/cli.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/preflight/defaults/iot.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/preflight/defaults/mobile.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/preflight/defaults/web.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/propagate.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/propagator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/registry.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/approval_repair.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/engine.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/git_patcher.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/history.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/llm_repair_engine.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/loop.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/schema.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/templates/analyze_meta.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair/templates/propose_meta.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/repair_slice.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/require.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/require_plugins.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/require_propagate.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/required_artifacts/defaults/web.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/required_artifacts_deriver.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/requirement_completeness_auditor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/restore.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/routes_extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/scanner.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/screen_flow_validator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/screen_transition_extractor.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/screen_transitions/defaults.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/synth.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extract_ai_prompt_baseline.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/lexicon_questions.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/lexicon_schema.yaml +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/traceability.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/validator.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/__init__.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/events.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/propagation_log.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/propagation_pipeline.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/test_runner.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/watch/watcher.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/codd/wiring.py +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/docs/cookbook/cdp_browser/README.md +0 -0
- {codd_dev-1.30.0 → codd_dev-1.32.0}/docs/requirements/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codd-dev
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.32.0
|
|
4
4
|
Summary: CoDD: Coherence-Driven Development — cross-artifact change impact analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/yohey-w/codd-dev
|
|
6
6
|
Project-URL: Repository, https://github.com/yohey-w/codd-dev
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import asdict, dataclass, is_dataclass
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
+
import importlib.metadata
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
8
|
+
import re
|
|
7
9
|
import shutil
|
|
10
|
+
import subprocess
|
|
8
11
|
from pathlib import Path
|
|
9
12
|
from typing import Any
|
|
10
13
|
|
|
@@ -29,9 +32,148 @@ class _CliVerificationResult:
|
|
|
29
32
|
failure: Any | None = None
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class _VersionCheckResult:
|
|
37
|
+
installed_version: str
|
|
38
|
+
required_spec: str
|
|
39
|
+
satisfied: bool
|
|
40
|
+
strict: bool
|
|
41
|
+
message: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_SPECIFIER_RE = re.compile(r"^\s*(==|!=|<=|>=|<|>|~=)\s*([A-Za-z0-9.!+_-]+)\s*$")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _installed_codd_version() -> str:
|
|
48
|
+
try:
|
|
49
|
+
return importlib.metadata.version("codd-dev")
|
|
50
|
+
except importlib.metadata.PackageNotFoundError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
from codd import __version__
|
|
55
|
+
|
|
56
|
+
if __version__:
|
|
57
|
+
return str(__version__)
|
|
58
|
+
except Exception: # pragma: no cover - best-effort fallback for source trees.
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
pyproject_path = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
62
|
+
try:
|
|
63
|
+
match = re.search(r'(?m)^version\s*=\s*"([^"]+)"', pyproject_path.read_text(encoding="utf-8"))
|
|
64
|
+
except OSError:
|
|
65
|
+
match = None
|
|
66
|
+
return match.group(1) if match else "unknown"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _evaluate_version_requirement(project_root: Path, *, strict_override: bool = False) -> _VersionCheckResult | None:
|
|
70
|
+
try:
|
|
71
|
+
config = load_project_config(project_root)
|
|
72
|
+
except (FileNotFoundError, ValueError):
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
required = config.get("codd_required_version")
|
|
76
|
+
if not isinstance(required, str) or not required.strip():
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
installed = _installed_codd_version()
|
|
80
|
+
required_spec = required.strip()
|
|
81
|
+
satisfied, error = _version_satisfies(installed, required_spec)
|
|
82
|
+
strict = bool(strict_override or config.get("codd_required_version_strict", False))
|
|
83
|
+
if error:
|
|
84
|
+
return _VersionCheckResult(
|
|
85
|
+
installed_version=installed,
|
|
86
|
+
required_spec=required_spec,
|
|
87
|
+
satisfied=False,
|
|
88
|
+
strict=strict,
|
|
89
|
+
message=f"WARN: invalid codd_required_version {required_spec!r}: {error}",
|
|
90
|
+
)
|
|
91
|
+
return _VersionCheckResult(
|
|
92
|
+
installed_version=installed,
|
|
93
|
+
required_spec=required_spec,
|
|
94
|
+
satisfied=satisfied,
|
|
95
|
+
strict=strict,
|
|
96
|
+
message=f"WARN: project requires codd {required_spec}, installed {installed}",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _warn_if_project_version_mismatch(project_root: Path) -> None:
|
|
101
|
+
result = _evaluate_version_requirement(project_root)
|
|
102
|
+
if result is None or result.satisfied:
|
|
103
|
+
return
|
|
104
|
+
click.echo(result.message, err=True)
|
|
105
|
+
if result.strict:
|
|
106
|
+
raise SystemExit(1)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _version_satisfies(installed: str, specifier: str) -> tuple[bool, str | None]:
|
|
110
|
+
try:
|
|
111
|
+
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
112
|
+
from packaging.version import InvalidVersion, Version
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
return Version(installed) in SpecifierSet(specifier), None
|
|
116
|
+
except (InvalidSpecifier, InvalidVersion) as exc:
|
|
117
|
+
return False, str(exc)
|
|
118
|
+
except ImportError:
|
|
119
|
+
return _version_satisfies_fallback(installed, specifier)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _version_satisfies_fallback(installed: str, specifier: str) -> tuple[bool, str | None]:
|
|
123
|
+
installed_key = _version_key(installed)
|
|
124
|
+
if installed_key is None:
|
|
125
|
+
return False, f"unsupported installed version {installed!r}"
|
|
126
|
+
|
|
127
|
+
for raw_part in specifier.split(","):
|
|
128
|
+
part = raw_part.strip()
|
|
129
|
+
if not part:
|
|
130
|
+
continue
|
|
131
|
+
match = _SPECIFIER_RE.match(part)
|
|
132
|
+
if match is None:
|
|
133
|
+
return False, f"unsupported version specifier {part!r}; install packaging for full PEP 440 support"
|
|
134
|
+
op, expected = match.groups()
|
|
135
|
+
expected_key = _version_key(expected)
|
|
136
|
+
if expected_key is None:
|
|
137
|
+
return False, f"unsupported version {expected!r}"
|
|
138
|
+
if not _compare_version_keys(installed_key, op, expected_key):
|
|
139
|
+
return False, None
|
|
140
|
+
return True, None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _version_key(version: str) -> tuple[int, ...] | None:
|
|
144
|
+
release = version.split("+", 1)[0].split("-", 1)[0]
|
|
145
|
+
numbers = re.findall(r"\d+", release)
|
|
146
|
+
if not numbers:
|
|
147
|
+
return None
|
|
148
|
+
return tuple(int(item) for item in numbers)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _compare_version_keys(installed: tuple[int, ...], op: str, expected: tuple[int, ...]) -> bool:
|
|
152
|
+
size = max(len(installed), len(expected))
|
|
153
|
+
left = installed + (0,) * (size - len(installed))
|
|
154
|
+
right = expected + (0,) * (size - len(expected))
|
|
155
|
+
if op == "==":
|
|
156
|
+
return left == right
|
|
157
|
+
if op == "!=":
|
|
158
|
+
return left != right
|
|
159
|
+
if op == ">=":
|
|
160
|
+
return left >= right
|
|
161
|
+
if op == "<=":
|
|
162
|
+
return left <= right
|
|
163
|
+
if op == ">":
|
|
164
|
+
return left > right
|
|
165
|
+
if op == "<":
|
|
166
|
+
return left < right
|
|
167
|
+
if op == "~=":
|
|
168
|
+
upper = (expected[0] + 1, 0) if len(expected) <= 2 else (expected[0], expected[1] + 1, 0)
|
|
169
|
+
upper = upper + (0,) * (size - len(upper))
|
|
170
|
+
return left >= right and left < upper
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
32
174
|
def _run_pro_command(name: str, **kwargs):
|
|
33
175
|
"""Dispatch a Pro-only command when the bridge plugin is installed."""
|
|
34
|
-
handler = get_command_handler(name)
|
|
176
|
+
handler = get_command_handler(name)
|
|
35
177
|
if handler is None:
|
|
36
178
|
click.echo(PRO_COMMAND_INSTALL_MESSAGE)
|
|
37
179
|
raise SystemExit(1)
|
|
@@ -196,11 +338,40 @@ def _ensure_bootstrap_codd_yaml(
|
|
|
196
338
|
return config_path, True
|
|
197
339
|
|
|
198
340
|
|
|
199
|
-
@click.group()
|
|
200
|
-
@click.version_option(package_name="codd-dev")
|
|
201
|
-
|
|
341
|
+
@click.group()
|
|
342
|
+
@click.version_option(package_name="codd-dev")
|
|
343
|
+
@click.pass_context
|
|
344
|
+
def main(ctx: click.Context):
|
|
202
345
|
"""CoDD: Coherence-Driven Development."""
|
|
203
|
-
|
|
346
|
+
if ctx.resilient_parsing or ctx.invoked_subcommand in {None, "version"}:
|
|
347
|
+
return
|
|
348
|
+
_warn_if_project_version_mismatch(Path.cwd())
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@main.command("version")
|
|
352
|
+
@click.option("--check", "check_project", is_flag=True, help="Check installed CoDD against codd.yaml requirement")
|
|
353
|
+
@click.option("--strict", is_flag=True, help="Exit non-zero when the version requirement is not satisfied")
|
|
354
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
355
|
+
def version_cmd(check_project: bool, strict: bool, project_path: str) -> None:
|
|
356
|
+
"""Print the installed CoDD version."""
|
|
357
|
+
installed = _installed_codd_version()
|
|
358
|
+
click.echo(f"codd {installed}")
|
|
359
|
+
if not check_project:
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
project_root = Path(project_path).resolve()
|
|
363
|
+
result = _evaluate_version_requirement(project_root, strict_override=strict)
|
|
364
|
+
if result is None:
|
|
365
|
+
click.echo("Version check: no codd_required_version configured")
|
|
366
|
+
return
|
|
367
|
+
if result.satisfied:
|
|
368
|
+
click.echo(f"Version check: PASS (requires {result.required_spec})")
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
click.echo(result.message, err=True)
|
|
372
|
+
click.echo(f"Version check: FAIL (requires {result.required_spec})")
|
|
373
|
+
if result.strict:
|
|
374
|
+
raise SystemExit(1)
|
|
204
375
|
|
|
205
376
|
|
|
206
377
|
@main.command("preflight")
|
|
@@ -539,6 +710,13 @@ def restore(wave: int, path: str, force: bool, ai_cmd: str | None, feedback: str
|
|
|
539
710
|
default=False,
|
|
540
711
|
help="Run CoverageAuditor 3-class requirement gap analysis.",
|
|
541
712
|
)
|
|
713
|
+
@click.option(
|
|
714
|
+
"--check",
|
|
715
|
+
"check_coverage",
|
|
716
|
+
is_flag=True,
|
|
717
|
+
default=False,
|
|
718
|
+
help="Check requirement-to-implementation coverage without generating requirements.",
|
|
719
|
+
)
|
|
542
720
|
@click.option(
|
|
543
721
|
"--completeness-audit",
|
|
544
722
|
is_flag=True,
|
|
@@ -556,6 +734,7 @@ def require(
|
|
|
556
734
|
base_ref: str | None,
|
|
557
735
|
apply_mode: bool,
|
|
558
736
|
audit: bool,
|
|
737
|
+
check_coverage: bool,
|
|
559
738
|
completeness_audit: bool,
|
|
560
739
|
):
|
|
561
740
|
"""Infer requirements from extracted codebase facts (brownfield).
|
|
@@ -582,6 +761,10 @@ def require(
|
|
|
582
761
|
|
|
583
762
|
_require_codd_dir(project_root)
|
|
584
763
|
|
|
764
|
+
if check_coverage:
|
|
765
|
+
_run_require_check(project_root)
|
|
766
|
+
return
|
|
767
|
+
|
|
585
768
|
if audit:
|
|
586
769
|
from codd.coverage_auditor import CoverageAuditor
|
|
587
770
|
|
|
@@ -680,12 +863,39 @@ def require(
|
|
|
680
863
|
generated += 1
|
|
681
864
|
else:
|
|
682
865
|
skipped += 1
|
|
683
|
-
|
|
684
|
-
click.echo(f"Requirements: {generated} generated, {skipped} skipped")
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
866
|
+
|
|
867
|
+
click.echo(f"Requirements: {generated} generated, {skipped} skipped")
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _run_require_check(project_root: Path) -> None:
|
|
871
|
+
from codd.dag import runner as dag_runner
|
|
872
|
+
|
|
873
|
+
try:
|
|
874
|
+
results = dag_runner.run_all_checks(project_root, check_names=["implementation_coverage"])
|
|
875
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
876
|
+
click.echo(f"Error: {exc}")
|
|
877
|
+
raise SystemExit(1)
|
|
878
|
+
|
|
879
|
+
failed = []
|
|
880
|
+
for result in results:
|
|
881
|
+
check_name = str(getattr(result, "check_name", "implementation_coverage"))
|
|
882
|
+
message = str(getattr(result, "message", "") or "")
|
|
883
|
+
passed = bool(getattr(result, "passed", False))
|
|
884
|
+
status = "PASS" if passed else "FAIL"
|
|
885
|
+
suffix = f" - {message}" if message else ""
|
|
886
|
+
click.echo(f"{status}: {check_name}{suffix}")
|
|
887
|
+
for violation in getattr(result, "violations", []) or []:
|
|
888
|
+
click.echo(f" - {violation}")
|
|
889
|
+
if not passed:
|
|
890
|
+
failed.append(result)
|
|
891
|
+
|
|
892
|
+
if failed:
|
|
893
|
+
raise SystemExit(1)
|
|
894
|
+
click.echo("Requirement check complete: implementation_coverage PASS")
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
@main.command()
|
|
898
|
+
@click.option("--diff", default="HEAD", help="Git diff target (default: HEAD, shows uncommitted changes)")
|
|
689
899
|
@click.option("--path", default=".", help="Project root directory")
|
|
690
900
|
@click.option("--update", is_flag=True, help="Actually update affected design docs via AI")
|
|
691
901
|
@click.option("--verify", is_flag=True, help="Auto-apply green band, list amber/gray for HITL review")
|
|
@@ -1238,6 +1448,7 @@ def implement_augment_cmd(
|
|
|
1238
1448
|
show_default=True,
|
|
1239
1449
|
help="Seconds before one chunk is interrupted",
|
|
1240
1450
|
)
|
|
1451
|
+
@click.option("--enable-typecheck-loop", is_flag=True, default=False, help="Run configured typecheck repair loop after implementation")
|
|
1241
1452
|
def implement_run_cmd(
|
|
1242
1453
|
task_id: str | None,
|
|
1243
1454
|
project_path: str,
|
|
@@ -1245,12 +1456,21 @@ def implement_run_cmd(
|
|
|
1245
1456
|
use_derived_steps: str,
|
|
1246
1457
|
chunk_size: int | None,
|
|
1247
1458
|
timeout_per_chunk: int,
|
|
1459
|
+
enable_typecheck_loop: bool,
|
|
1248
1460
|
):
|
|
1249
1461
|
"""Run implementation with optional derived step injection."""
|
|
1250
|
-
from codd.implementer import implement_tasks
|
|
1462
|
+
from codd.implementer import auto_detect_task, implement_tasks
|
|
1251
1463
|
|
|
1252
1464
|
project_root = Path(project_path).resolve()
|
|
1253
1465
|
_require_codd_dir(project_root)
|
|
1466
|
+
if task_id is None:
|
|
1467
|
+
try:
|
|
1468
|
+
task_id = auto_detect_task(project_root)
|
|
1469
|
+
except ValueError as exc:
|
|
1470
|
+
click.echo(f"Error: {exc}")
|
|
1471
|
+
raise SystemExit(1)
|
|
1472
|
+
click.echo(f"Auto-detected task: {task_id}")
|
|
1473
|
+
|
|
1254
1474
|
if chunk_size is not None:
|
|
1255
1475
|
try:
|
|
1256
1476
|
result = _run_chunked_implementation(
|
|
@@ -1267,6 +1487,16 @@ def implement_run_cmd(
|
|
|
1267
1487
|
_echo_chunked_result(project_root, result)
|
|
1268
1488
|
if result.status != "SUCCESS":
|
|
1269
1489
|
raise SystemExit(1)
|
|
1490
|
+
try:
|
|
1491
|
+
_run_typecheck_loop_after_implement(
|
|
1492
|
+
project_root=project_root,
|
|
1493
|
+
modified_files=None,
|
|
1494
|
+
ai_cmd=ai_cmd,
|
|
1495
|
+
force_enabled=enable_typecheck_loop,
|
|
1496
|
+
)
|
|
1497
|
+
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
1498
|
+
click.echo(f"Error: {exc}")
|
|
1499
|
+
raise SystemExit(1)
|
|
1270
1500
|
return
|
|
1271
1501
|
|
|
1272
1502
|
try:
|
|
@@ -1286,6 +1516,16 @@ def implement_run_cmd(
|
|
|
1286
1516
|
click.echo(f"{sum(len(result.generated_files) for result in results)} files generated across {len(results) - len(failed)} task(s)")
|
|
1287
1517
|
if failed:
|
|
1288
1518
|
raise SystemExit(1)
|
|
1519
|
+
try:
|
|
1520
|
+
_run_typecheck_loop_after_implement(
|
|
1521
|
+
project_root=project_root,
|
|
1522
|
+
modified_files=[generated_file for result in results for generated_file in result.generated_files],
|
|
1523
|
+
ai_cmd=ai_cmd,
|
|
1524
|
+
force_enabled=enable_typecheck_loop,
|
|
1525
|
+
)
|
|
1526
|
+
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
1527
|
+
click.echo(f"Error: {exc}")
|
|
1528
|
+
raise SystemExit(1)
|
|
1289
1529
|
|
|
1290
1530
|
|
|
1291
1531
|
@implement.command("resume")
|
|
@@ -1391,9 +1631,71 @@ def _echo_chunked_result(project_root: Path, result) -> None:
|
|
|
1391
1631
|
f"Chunked implementation {result.status}: "
|
|
1392
1632
|
f"{len(result.completed_chunks)}/{result.total_chunks} chunks; history={history}"
|
|
1393
1633
|
)
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
def _run_typecheck_loop_after_implement(
|
|
1637
|
+
*,
|
|
1638
|
+
project_root: Path,
|
|
1639
|
+
modified_files: list[Path] | None,
|
|
1640
|
+
ai_cmd: str | None,
|
|
1641
|
+
force_enabled: bool,
|
|
1642
|
+
):
|
|
1643
|
+
config = _load_optional_project_config(project_root)
|
|
1644
|
+
if not force_enabled and not _typecheck_config_enabled(config):
|
|
1645
|
+
return None
|
|
1646
|
+
from codd.implementer import TypecheckRepairLoop
|
|
1647
|
+
|
|
1648
|
+
loop = TypecheckRepairLoop.from_config(config, force_enabled=force_enabled)
|
|
1649
|
+
if not loop.enabled:
|
|
1650
|
+
return None
|
|
1651
|
+
|
|
1652
|
+
result = loop.run_after_implement(
|
|
1653
|
+
project_root,
|
|
1654
|
+
modified_files if modified_files is not None else _git_modified_files(project_root),
|
|
1655
|
+
ai_cmd or _configured_ai_command(config),
|
|
1656
|
+
)
|
|
1657
|
+
click.echo(f"Typecheck loop {result.status}")
|
|
1658
|
+
if result.status == "REPAIR_EXHAUSTED":
|
|
1659
|
+
raise CoddCLIError("typecheck repair loop exhausted")
|
|
1660
|
+
return result
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
def _configured_ai_command(config: dict[str, Any]) -> str:
|
|
1664
|
+
command = config.get("ai_command")
|
|
1665
|
+
return command if isinstance(command, str) else ""
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
def _typecheck_config_enabled(config: dict[str, Any]) -> bool:
|
|
1669
|
+
typecheck = config.get("typecheck")
|
|
1670
|
+
return bool(typecheck.get("enabled")) if isinstance(typecheck, dict) else False
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
def _git_modified_files(project_root: Path) -> list[Path]:
|
|
1674
|
+
try:
|
|
1675
|
+
completed = subprocess.run(
|
|
1676
|
+
["git", "-C", str(project_root), "status", "--porcelain=v1", "--untracked-files=all"],
|
|
1677
|
+
capture_output=True,
|
|
1678
|
+
text=True,
|
|
1679
|
+
encoding="utf-8",
|
|
1680
|
+
check=False,
|
|
1681
|
+
)
|
|
1682
|
+
except (OSError, ValueError):
|
|
1683
|
+
return []
|
|
1684
|
+
if completed.returncode != 0:
|
|
1685
|
+
return []
|
|
1686
|
+
paths: list[Path] = []
|
|
1687
|
+
for line in completed.stdout.splitlines():
|
|
1688
|
+
if len(line) < 4:
|
|
1689
|
+
continue
|
|
1690
|
+
raw_path = line[3:]
|
|
1691
|
+
if " -> " in raw_path:
|
|
1692
|
+
raw_path = raw_path.rsplit(" -> ", 1)[1]
|
|
1693
|
+
if raw_path:
|
|
1694
|
+
paths.append(project_root / raw_path)
|
|
1695
|
+
return paths
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
@main.command()
|
|
1397
1699
|
@click.option("--path", default=".", help="Project root directory")
|
|
1398
1700
|
@click.option("--output-dir", default=None, help="Output directory for assembled project (default: src/)")
|
|
1399
1701
|
@click.option(
|
|
@@ -1457,7 +1759,7 @@ def verify(
|
|
|
1457
1759
|
return
|
|
1458
1760
|
|
|
1459
1761
|
project_root = Path(path).resolve()
|
|
1460
|
-
result = _run_verify_once(path=path, sprint=sprint)
|
|
1762
|
+
result = _run_verify_once(path=path, sprint=sprint, prefer_standalone=True)
|
|
1461
1763
|
if result.passed:
|
|
1462
1764
|
return
|
|
1463
1765
|
|
|
@@ -1471,7 +1773,7 @@ def verify(
|
|
|
1471
1773
|
repair_config=repair_config,
|
|
1472
1774
|
max_attempts=max_attempts,
|
|
1473
1775
|
engine_name=engine_name,
|
|
1474
|
-
verify_callable=lambda: _run_verify_once(path=path, sprint=sprint),
|
|
1776
|
+
verify_callable=lambda: _run_verify_once(path=path, sprint=sprint, prefer_standalone=True),
|
|
1475
1777
|
)
|
|
1476
1778
|
click.echo(f"Repair outcome: {outcome.status}")
|
|
1477
1779
|
click.echo(f"Repair history: {_display_path(outcome.history_session_dir, project_root)}")
|
|
@@ -3236,12 +3538,14 @@ def _load_optional_project_config(project_root: Path) -> dict[str, Any]:
|
|
|
3236
3538
|
return {}
|
|
3237
3539
|
|
|
3238
3540
|
|
|
3239
|
-
def _run_verify_once(
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3541
|
+
def _run_verify_once(
|
|
3542
|
+
path: str,
|
|
3543
|
+
sprint: int | None = None,
|
|
3544
|
+
*,
|
|
3545
|
+
prefer_standalone: bool = False,
|
|
3546
|
+
) -> _CliVerificationResult:
|
|
3547
|
+
if prefer_standalone or get_command_handler("verify") is None:
|
|
3548
|
+
return _run_standalone_verify_once(path)
|
|
3245
3549
|
|
|
3246
3550
|
try:
|
|
3247
3551
|
_run_pro_command("verify", path=path, sprint=sprint)
|
|
@@ -3260,6 +3564,23 @@ def _run_verify_once(path: str, sprint: int | None = None) -> _CliVerificationRe
|
|
|
3260
3564
|
return _CliVerificationResult(passed=True, exit_code=0, failure=None)
|
|
3261
3565
|
|
|
3262
3566
|
|
|
3567
|
+
def _run_standalone_verify_once(path: str) -> _CliVerificationResult:
|
|
3568
|
+
from codd.repair.verify_runner import run_standalone_verify
|
|
3569
|
+
|
|
3570
|
+
result = run_standalone_verify(Path(path).resolve())
|
|
3571
|
+
_echo_verification_warnings(result)
|
|
3572
|
+
return _cli_result_from_standalone_verify(result)
|
|
3573
|
+
|
|
3574
|
+
|
|
3575
|
+
def _echo_verification_warnings(result: Any) -> None:
|
|
3576
|
+
for warning in getattr(result, "warnings", []) or []:
|
|
3577
|
+
text = str(warning)
|
|
3578
|
+
if not text:
|
|
3579
|
+
continue
|
|
3580
|
+
prefix = "" if text.upper().startswith("WARNING:") else "WARNING: "
|
|
3581
|
+
click.echo(f"{prefix}{text}")
|
|
3582
|
+
|
|
3583
|
+
|
|
3263
3584
|
def _cli_result_from_standalone_verify(result: Any) -> _CliVerificationResult:
|
|
3264
3585
|
return _CliVerificationResult(
|
|
3265
3586
|
passed=bool(result.passed),
|
|
@@ -3769,19 +4090,32 @@ def dag_journeys(project_path: str, output_format: str):
|
|
|
3769
4090
|
@dag.command("run-journey")
|
|
3770
4091
|
@click.argument("journey_name")
|
|
3771
4092
|
@click.option("--project-path", "--path", default=".", show_default=True, help="Project root directory")
|
|
4093
|
+
@click.option(
|
|
4094
|
+
"--axis",
|
|
4095
|
+
"axis_overrides",
|
|
4096
|
+
multiple=True,
|
|
4097
|
+
metavar="TYPE=VARIANT",
|
|
4098
|
+
help="Runtime axis override. Repeat for multiple axes.",
|
|
4099
|
+
)
|
|
3772
4100
|
@click.option(
|
|
3773
4101
|
"--config-section",
|
|
3774
4102
|
default="cdp_browser",
|
|
3775
4103
|
show_default=True,
|
|
3776
4104
|
help="verification.templates section used for browser config",
|
|
3777
4105
|
)
|
|
3778
|
-
def dag_run_journey(
|
|
4106
|
+
def dag_run_journey(
|
|
4107
|
+
journey_name: str,
|
|
4108
|
+
project_path: str,
|
|
4109
|
+
axis_overrides: tuple[str, ...],
|
|
4110
|
+
config_section: str,
|
|
4111
|
+
):
|
|
3779
4112
|
"""Run one declared user_journey with the CDP browser template."""
|
|
3780
4113
|
from codd.dag.builder import build_dag
|
|
3781
4114
|
from codd.deployment.providers.verification.cdp_browser import CdpBrowser
|
|
3782
4115
|
|
|
3783
4116
|
project_root = Path(project_path).resolve()
|
|
3784
4117
|
try:
|
|
4118
|
+
parsed_axes = _parse_axis_overrides(axis_overrides)
|
|
3785
4119
|
config = load_project_config(project_root)
|
|
3786
4120
|
template_config = _journey_template_config(config, config_section)
|
|
3787
4121
|
built_dag = build_dag(project_root)
|
|
@@ -3795,7 +4129,7 @@ def dag_run_journey(journey_name: str, project_path: str, config_section: str):
|
|
|
3795
4129
|
raise SystemExit(2)
|
|
3796
4130
|
|
|
3797
4131
|
command = json.dumps(
|
|
3798
|
-
_journey_execution_plan(project_root, journey_record, template_config),
|
|
4132
|
+
_journey_execution_plan(project_root, journey_record, template_config, parsed_axes),
|
|
3799
4133
|
sort_keys=True,
|
|
3800
4134
|
)
|
|
3801
4135
|
result = CdpBrowser(config=template_config).execute(command)
|
|
@@ -3816,6 +4150,20 @@ def _journey_template_config(config: dict[str, Any], config_section: str) -> dic
|
|
|
3816
4150
|
return dict(section)
|
|
3817
4151
|
|
|
3818
4152
|
|
|
4153
|
+
def _parse_axis_overrides(axis_overrides: tuple[str, ...]) -> dict[str, str]:
|
|
4154
|
+
parsed: dict[str, str] = {}
|
|
4155
|
+
for raw in axis_overrides:
|
|
4156
|
+
if "=" not in raw:
|
|
4157
|
+
raise ValueError("--axis must use TYPE=VARIANT")
|
|
4158
|
+
axis_type, variant_id = raw.split("=", 1)
|
|
4159
|
+
axis = axis_type.strip()
|
|
4160
|
+
variant = variant_id.strip()
|
|
4161
|
+
if not axis or not variant:
|
|
4162
|
+
raise ValueError("--axis must use non-empty TYPE=VARIANT")
|
|
4163
|
+
parsed[axis] = variant
|
|
4164
|
+
return parsed
|
|
4165
|
+
|
|
4166
|
+
|
|
3819
4167
|
def _find_dag_journey(
|
|
3820
4168
|
dag: Any,
|
|
3821
4169
|
journey_name: str,
|
|
@@ -3838,11 +4186,12 @@ def _journey_execution_plan(
|
|
|
3838
4186
|
project_root: Path,
|
|
3839
4187
|
journey_record: dict[str, Any],
|
|
3840
4188
|
template_config: dict[str, Any],
|
|
4189
|
+
axis_overrides: dict[str, str] | None = None,
|
|
3841
4190
|
) -> dict[str, Any]:
|
|
3842
4191
|
journey = dict(journey_record["journey"])
|
|
3843
4192
|
journey_name = str(journey.get("name") or "")
|
|
3844
4193
|
steps = journey.get("steps")
|
|
3845
|
-
|
|
4194
|
+
plan = {
|
|
3846
4195
|
"template": "cdp_browser",
|
|
3847
4196
|
"test_kind": "e2e",
|
|
3848
4197
|
"target": _journey_target(journey),
|
|
@@ -3853,6 +4202,9 @@ def _journey_execution_plan(
|
|
|
3853
4202
|
"design_doc": journey_record["design_doc"],
|
|
3854
4203
|
"config": template_config,
|
|
3855
4204
|
}
|
|
4205
|
+
if axis_overrides:
|
|
4206
|
+
plan["axis_overrides"] = dict(axis_overrides)
|
|
4207
|
+
return plan
|
|
3856
4208
|
|
|
3857
4209
|
|
|
3858
4210
|
def _journey_target(journey: dict[str, Any]) -> str:
|
|
@@ -16,6 +16,7 @@ import yaml
|
|
|
16
16
|
|
|
17
17
|
from codd.config import load_project_config
|
|
18
18
|
from codd.dag import DAG, Edge, Node
|
|
19
|
+
from codd.dag.coverage_axes import CoverageAxis, extract_coverage_axes_from_design_doc, extract_coverage_axes_from_lexicon
|
|
19
20
|
from codd.dag.extractor import extract_design_doc_metadata, extract_imports, scan_capability_evidence
|
|
20
21
|
from codd.llm.design_doc_extractor import (
|
|
21
22
|
ExpectedExtraction,
|
|
@@ -88,6 +89,7 @@ def build_dag(project_root: Path, settings: dict[str, Any] | None = None) -> DAG
|
|
|
88
89
|
_add_design_doc_expected_outcome_edges(dag, design_docs)
|
|
89
90
|
_add_plan_tasks(dag, root, dag_settings)
|
|
90
91
|
_add_deployment_graph(dag, root, design_docs, impl_nodes)
|
|
92
|
+
_attach_coverage_axes(dag, root, dag_settings)
|
|
91
93
|
|
|
92
94
|
write_dag_json(dag, root, default_dag_json_path(root))
|
|
93
95
|
return dag
|
|
@@ -151,7 +153,7 @@ def default_dag_mermaid_path(project_root: Path) -> Path:
|
|
|
151
153
|
def dag_to_dict(dag: DAG, project_root: Path) -> dict[str, Any]:
|
|
152
154
|
"""Serialize a DAG using the stable `.codd/dag.json` schema."""
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
payload = {
|
|
155
157
|
"version": "1",
|
|
156
158
|
"built_at": datetime.now(timezone.utc).isoformat(),
|
|
157
159
|
"project_root": str(Path(project_root).resolve()),
|
|
@@ -167,6 +169,12 @@ def dag_to_dict(dag: DAG, project_root: Path) -> dict[str, Any]:
|
|
|
167
169
|
"edges": [_edge_to_dict(edge) for edge in sorted(dag.edges, key=lambda item: (item.from_id, item.to_id, item.kind))],
|
|
168
170
|
"cycles": dag.detect_cycles(),
|
|
169
171
|
}
|
|
172
|
+
coverage_axes = getattr(dag, "coverage_axes", [])
|
|
173
|
+
if coverage_axes:
|
|
174
|
+
payload["coverage_axes"] = [
|
|
175
|
+
axis.to_dict() if isinstance(axis, CoverageAxis) else axis for axis in coverage_axes
|
|
176
|
+
]
|
|
177
|
+
return payload
|
|
170
178
|
|
|
171
179
|
|
|
172
180
|
def _edge_to_dict(edge: Edge) -> dict[str, Any]:
|
|
@@ -656,6 +664,32 @@ def _add_deployment_graph(
|
|
|
656
664
|
existing_edges.add(edge_key)
|
|
657
665
|
|
|
658
666
|
|
|
667
|
+
def _attach_coverage_axes(dag: DAG, project_root: Path, settings: dict[str, Any]) -> None:
|
|
668
|
+
lexicon_path = _project_path(project_root, str(settings.get("lexicon_file", "project_lexicon.yaml")))
|
|
669
|
+
axes = extract_coverage_axes_from_lexicon(lexicon_path)
|
|
670
|
+
for node in sorted(dag.nodes.values(), key=lambda item: item.id):
|
|
671
|
+
if node.kind == "design_doc":
|
|
672
|
+
axes.extend(extract_coverage_axes_from_design_doc(node))
|
|
673
|
+
dag.coverage_axes = _dedupe_coverage_axes(axes)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _dedupe_coverage_axes(axes: list[CoverageAxis]) -> list[CoverageAxis]:
|
|
677
|
+
deduped: list[CoverageAxis] = []
|
|
678
|
+
seen: set[tuple[str, tuple[str, ...], str, str]] = set()
|
|
679
|
+
for axis in axes:
|
|
680
|
+
key = (
|
|
681
|
+
axis.axis_type,
|
|
682
|
+
tuple(variant.id for variant in axis.variants),
|
|
683
|
+
axis.source,
|
|
684
|
+
axis.owner_section,
|
|
685
|
+
)
|
|
686
|
+
if key in seen:
|
|
687
|
+
continue
|
|
688
|
+
seen.add(key)
|
|
689
|
+
deduped.append(axis)
|
|
690
|
+
return deduped
|
|
691
|
+
|
|
692
|
+
|
|
659
693
|
def _extract_outputs(section: str) -> list[str]:
|
|
660
694
|
match = OUTPUTS_RE.search(section)
|
|
661
695
|
if not match:
|