codd-dev 1.28.0__tar.gz → 1.30.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.28.0 → codd_dev-1.30.0}/PKG-INFO +1 -1
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/cli.py +460 -14
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/config.py +5 -2
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/builder.py +28 -1
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/implementation_coverage.py +180 -10
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/extractor.py +57 -18
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/defaults.yaml +6 -0
- codd_dev-1.30.0/codd/implementer/__init__.py +31 -0
- codd_dev-1.30.0/codd/implementer/chunked_runner.py +498 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/implementer.py +190 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/approval.py +58 -0
- codd_dev-1.30.0/codd/llm/best_practice_augmenter.py +158 -0
- codd_dev-1.30.0/codd/llm/impl_step_deriver.py +611 -0
- codd_dev-1.30.0/codd/llm/templates/best_practice_augment_meta.md +52 -0
- codd_dev-1.30.0/codd/llm/templates/impl_step_derive_meta.md +50 -0
- codd_dev-1.30.0/codd/llm/templates/implementation_step_catalog.yaml +35 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/verify_runner.py +13 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/pyproject.toml +1 -1
- {codd_dev-1.28.0 → codd_dev-1.30.0}/.gitignore +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/LICENSE +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/README.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/__main__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/_git_helper.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/ask_user_question_adapter.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/assembler.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/bridge.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/clustering.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/coherence_adapters.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/coherence_engine.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/contracts.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/coverage_auditor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/coverage_metrics.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/depends_on_consistency.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/deployment_completeness.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/edge_validity.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/node_completeness.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/task_completion.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/transitive_closure.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/checks/user_journey_coherence.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/cli.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/cpp_embedded.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/csharp.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/elixir.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/generic.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/iot.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/java.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/kotlin.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/mobile.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/ruby.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/rust.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/scala.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/swift.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/test_frameworks.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/defaults/web.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/dag/runner.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deploy_targets/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deploy_targets/app_service.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deploy_targets/base.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deploy_targets/docker_compose.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployer.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/checks/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/defaults/deploy_targets.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/defaults/runtime_capability_inference.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/defaults/schema_providers.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/defaults/verification_templates.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/ai_command.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/llm_consideration.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/schema/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/schema/prisma.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/target/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/target/docker_compose.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/assertion_handlers.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/cdp_browser.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/cdp_engines.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/cdp_launchers.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/cdp_wire.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/curl.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/form_strategies.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/means_catalog.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/deployment/providers/verification/playwright.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/design_md.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/drift.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/e2e_extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/e2e_generator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/e2e_runner.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/env_refs.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/extract_ai.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixer.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixup_drift.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixup_drift_strategies/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/generator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/graph.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hitl_session.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/recipes/claude_settings_example.json +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/recipes/codex_hook.sh +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/recipes/git_post_commit.sh +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/hooks/recipes/git_pre_commit.sh +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/inheritance.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/knowledge_fetcher.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/lexicon.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/criteria_expander.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/design_doc_extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/means_catalog_loader.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/parser.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/plan_deriver.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/prompt_builder.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/strategy_validator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/templates/criteria_expand_meta.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/templates/design_doc_extract_meta.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/templates/meta_instruction.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/templates/plan_derive_meta.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/llm/templates/verification_means_catalog.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/mcp_server.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/measure.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/parsing.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/planner.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/policy.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/preflight/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/preflight/defaults/cli.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/preflight/defaults/iot.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/preflight/defaults/mobile.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/preflight/defaults/web.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/propagate.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/propagator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/registry.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/approval_repair.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/engine.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/git_patcher.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/history.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/llm_repair_engine.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/loop.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/schema.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/templates/analyze_meta.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair/templates/propose_meta.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/repair_slice.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/require.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/require_plugins.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/require_propagate.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/required_artifacts/defaults/web.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/required_artifacts_deriver.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/requirement_completeness_auditor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/restore.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/routes_extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/scanner.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/screen_flow_validator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/screen_transition_extractor.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/screen_transitions/defaults.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/synth.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/codd.yaml.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extract_ai_prompt_baseline.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/lexicon_questions.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/lexicon_schema.yaml +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/traceability.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/validator.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/__init__.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/events.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/propagation_log.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/propagation_pipeline.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/test_runner.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/watch/watcher.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/codd/wiring.py +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.0}/docs/cookbook/cdp_browser/README.md +0 -0
- {codd_dev-1.28.0 → codd_dev-1.30.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.30.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
|
|
@@ -942,7 +942,7 @@ def propagate_from(project_path: str, files: tuple[str, ...], source: str, edito
|
|
|
942
942
|
raise SystemExit(1)
|
|
943
943
|
|
|
944
944
|
|
|
945
|
-
@main.
|
|
945
|
+
@main.group(invoke_without_command=True)
|
|
946
946
|
@click.option("--path", default=".", help="Project root directory")
|
|
947
947
|
@click.option("--task", default=None, help="Generate only one task by task ID or title match")
|
|
948
948
|
@click.option("--clean", is_flag=True, default=False, help="Remove existing generated output before re-generating")
|
|
@@ -964,16 +964,23 @@ def propagate_from(project_path: str, files: tuple[str, ...], source: str, edito
|
|
|
964
964
|
default=None,
|
|
965
965
|
help="Override AI CLI command (defaults to codd.yaml ai_command or merged CoDD defaults)",
|
|
966
966
|
)
|
|
967
|
+
@click.option("--use-derived-steps", default=None, help="Inject derived implementation steps: true or false")
|
|
968
|
+
@click.pass_context
|
|
967
969
|
def implement(
|
|
970
|
+
ctx,
|
|
968
971
|
path: str,
|
|
969
972
|
task: str | None,
|
|
970
973
|
clean: bool,
|
|
971
974
|
max_tasks: int,
|
|
972
975
|
wave: int | None,
|
|
973
976
|
ai_cmd: str | None,
|
|
977
|
+
use_derived_steps: str | None,
|
|
974
978
|
):
|
|
975
|
-
"""Generate implementation code from the implementation plan."""
|
|
976
|
-
|
|
979
|
+
"""Generate implementation code from the implementation plan."""
|
|
980
|
+
if ctx.invoked_subcommand is not None:
|
|
981
|
+
return
|
|
982
|
+
|
|
983
|
+
from codd.implementer import implement_tasks
|
|
977
984
|
|
|
978
985
|
project_root = Path(path).resolve()
|
|
979
986
|
codd_dir = _require_codd_dir(project_root)
|
|
@@ -982,14 +989,17 @@ def implement(
|
|
|
982
989
|
click.echo("Cleaning src/generated/ ...")
|
|
983
990
|
|
|
984
991
|
try:
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
)
|
|
992
|
+
implement_kwargs = {
|
|
993
|
+
"task": task,
|
|
994
|
+
"ai_command": ai_cmd,
|
|
995
|
+
"clean": clean,
|
|
996
|
+
"max_tasks": max_tasks,
|
|
997
|
+
"wave": wave,
|
|
998
|
+
}
|
|
999
|
+
parsed_use_derived_steps = _optional_bool(use_derived_steps)
|
|
1000
|
+
if parsed_use_derived_steps is not None:
|
|
1001
|
+
implement_kwargs["use_derived_steps"] = parsed_use_derived_steps
|
|
1002
|
+
results = implement_tasks(project_root, **implement_kwargs)
|
|
993
1003
|
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
994
1004
|
click.echo(f"Error: {exc}")
|
|
995
1005
|
raise SystemExit(1)
|
|
@@ -1013,9 +1023,374 @@ def implement(
|
|
|
1013
1023
|
f"\nFAILED: {len(failed_tasks)} task(s) produced no files:",
|
|
1014
1024
|
fg="red", bold=True,
|
|
1015
1025
|
))
|
|
1016
|
-
for ft in failed_tasks:
|
|
1017
|
-
click.echo(click.style(f" ✗ {ft.task_id} ({ft.task_title}): {ft.error}", fg="red"))
|
|
1018
|
-
raise SystemExit(1)
|
|
1026
|
+
for ft in failed_tasks:
|
|
1027
|
+
click.echo(click.style(f" ✗ {ft.task_id} ({ft.task_title}): {ft.error}", fg="red"))
|
|
1028
|
+
raise SystemExit(1)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
@implement.command("plan")
|
|
1032
|
+
@click.option("--task", "task_id", required=True, help="Implementation task id or title match")
|
|
1033
|
+
@click.option("--design-doc", "design_docs", multiple=True, help="Design document path. May be repeated.")
|
|
1034
|
+
@click.option("--force", is_flag=True, help="Bypass cached implementation steps")
|
|
1035
|
+
@click.option("--dry-run", is_flag=True, help="Print derived steps without writing cache")
|
|
1036
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
1037
|
+
@click.option("--provider", default=None, help="Implementation step deriver provider name")
|
|
1038
|
+
@click.option("--ai-cmd", default=None, help="Override AI command")
|
|
1039
|
+
def implement_plan_cmd(
|
|
1040
|
+
task_id: str,
|
|
1041
|
+
design_docs: tuple[str, ...],
|
|
1042
|
+
force: bool,
|
|
1043
|
+
dry_run: bool,
|
|
1044
|
+
project_path: str,
|
|
1045
|
+
provider: str | None,
|
|
1046
|
+
ai_cmd: str | None,
|
|
1047
|
+
):
|
|
1048
|
+
"""Derive implementation steps for one task."""
|
|
1049
|
+
from codd.deployment.providers.ai_command import SubprocessAiCommand
|
|
1050
|
+
from codd.llm.impl_step_deriver import IMPL_STEP_DERIVERS
|
|
1051
|
+
|
|
1052
|
+
project_root = Path(project_path).resolve()
|
|
1053
|
+
_require_codd_dir(project_root)
|
|
1054
|
+
config = _load_optional_project_config(project_root)
|
|
1055
|
+
provider_name = provider or _impl_step_provider(config)
|
|
1056
|
+
deriver_cls = IMPL_STEP_DERIVERS.get(provider_name)
|
|
1057
|
+
if deriver_cls is None:
|
|
1058
|
+
click.echo(f"Error: implementation step deriver provider not found: {provider_name}")
|
|
1059
|
+
raise SystemExit(1)
|
|
1060
|
+
|
|
1061
|
+
try:
|
|
1062
|
+
task_item = _implement_task_for_cli(project_root, config, task_id)
|
|
1063
|
+
nodes = _plan_design_doc_nodes(project_root, design_docs)
|
|
1064
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
1065
|
+
click.echo(f"Error: {exc}")
|
|
1066
|
+
raise SystemExit(1)
|
|
1067
|
+
|
|
1068
|
+
command = ai_cmd or _impl_step_command(config)
|
|
1069
|
+
deriver = deriver_cls(SubprocessAiCommand(command=command, project_root=project_root, config=config))
|
|
1070
|
+
steps = deriver.derive_steps(
|
|
1071
|
+
task_item,
|
|
1072
|
+
nodes,
|
|
1073
|
+
{
|
|
1074
|
+
"project_root": project_root,
|
|
1075
|
+
"force": force,
|
|
1076
|
+
"dry_run": dry_run,
|
|
1077
|
+
"write_cache": not dry_run,
|
|
1078
|
+
"config": config,
|
|
1079
|
+
"project_context": {"project": config.get("project", {})},
|
|
1080
|
+
},
|
|
1081
|
+
)
|
|
1082
|
+
if dry_run:
|
|
1083
|
+
click.echo(yaml.safe_dump([step.to_dict() for step in steps], sort_keys=False, allow_unicode=True), nl=False)
|
|
1084
|
+
return
|
|
1085
|
+
click.echo(f"Derived implementation steps: {len(steps)}")
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
@implement.command("steps")
|
|
1089
|
+
@click.option("--task", "task_id", required=True, help="Implementation task id")
|
|
1090
|
+
@click.option("--approve", is_flag=True, help="Approve one or more derived steps")
|
|
1091
|
+
@click.option("--step", "step_id", default=None, help="Step id for --approve")
|
|
1092
|
+
@click.option("--all", "approve_all", is_flag=True, help="Approve all pending steps")
|
|
1093
|
+
@click.option("--show-only", is_flag=True, help="Only show cached steps")
|
|
1094
|
+
@click.option("--show-layer-breakdown", is_flag=True, help="Show explicit and inferred step groups")
|
|
1095
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
1096
|
+
def implement_steps_cmd(
|
|
1097
|
+
task_id: str,
|
|
1098
|
+
approve: bool,
|
|
1099
|
+
step_id: str | None,
|
|
1100
|
+
approve_all: bool,
|
|
1101
|
+
show_only: bool,
|
|
1102
|
+
show_layer_breakdown: bool,
|
|
1103
|
+
project_path: str,
|
|
1104
|
+
):
|
|
1105
|
+
"""Show or approve derived implementation steps."""
|
|
1106
|
+
from codd.llm.impl_step_deriver import approve_cached_impl_steps, impl_step_cache_path, read_impl_step_cache
|
|
1107
|
+
|
|
1108
|
+
project_root = Path(project_path).resolve()
|
|
1109
|
+
cache_path = impl_step_cache_path(task_id, {"project_root": project_root})
|
|
1110
|
+
if approve:
|
|
1111
|
+
if not approve_all and not step_id:
|
|
1112
|
+
click.echo("Error: --approve requires --step or --all")
|
|
1113
|
+
raise SystemExit(2)
|
|
1114
|
+
try:
|
|
1115
|
+
changed = approve_cached_impl_steps(cache_path, step_id=step_id, approve_all=approve_all)
|
|
1116
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
1117
|
+
click.echo(f"Error: {exc}")
|
|
1118
|
+
raise SystemExit(1)
|
|
1119
|
+
click.echo(f"Approved implementation steps: {changed}")
|
|
1120
|
+
if not show_only:
|
|
1121
|
+
return
|
|
1122
|
+
|
|
1123
|
+
record = read_impl_step_cache(cache_path)
|
|
1124
|
+
if record is None:
|
|
1125
|
+
click.echo("No derived implementation steps found")
|
|
1126
|
+
return
|
|
1127
|
+
if show_layer_breakdown:
|
|
1128
|
+
_echo_impl_step_layer_breakdown(record)
|
|
1129
|
+
return
|
|
1130
|
+
for step in record.steps:
|
|
1131
|
+
layer = "layer2" if step.inferred else "layer1"
|
|
1132
|
+
status = "approved" if step.approved else "pending"
|
|
1133
|
+
click.echo(f"{step.id}\t{status}\t{layer}\t{step.kind}\t{step.source_design_section}")
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def _echo_impl_step_layer_breakdown(record: Any) -> None:
|
|
1137
|
+
layer_1 = [step for step in record.steps if not step.inferred]
|
|
1138
|
+
layer_2 = [step for step in record.steps if step.inferred]
|
|
1139
|
+
click.echo(f"[Layer 1 - Explicit, from design] (count={len(layer_1)})")
|
|
1140
|
+
for step in layer_1:
|
|
1141
|
+
click.echo(f" - {step.kind}: {step.id} (rationale: {step.rationale})")
|
|
1142
|
+
|
|
1143
|
+
avg_confidence = sum(float(step.confidence) for step in layer_2) / len(layer_2) if layer_2 else 0.0
|
|
1144
|
+
click.echo("")
|
|
1145
|
+
click.echo(f"[Layer 2 - Best Practice Augment] (count={len(layer_2)}, avg_confidence={avg_confidence:.2f})")
|
|
1146
|
+
for step in layer_2:
|
|
1147
|
+
category = step.best_practice_category or "uncategorized"
|
|
1148
|
+
click.echo(
|
|
1149
|
+
f" - {step.kind}: {step.id} "
|
|
1150
|
+
f"(confidence={step.confidence:.2f}, category={category}, rationale: {step.rationale})"
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
@implement.command("augment")
|
|
1155
|
+
@click.option("--task", "task_id", required=True, help="Implementation task id or title match")
|
|
1156
|
+
@click.option("--design-doc", "design_docs", multiple=True, help="Design document path. May be repeated.")
|
|
1157
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
1158
|
+
@click.option("--provider", default=None, help="Best practice augmenter provider name")
|
|
1159
|
+
@click.option("--ai-cmd", default=None, help="Override AI command")
|
|
1160
|
+
def implement_augment_cmd(
|
|
1161
|
+
task_id: str,
|
|
1162
|
+
design_docs: tuple[str, ...],
|
|
1163
|
+
project_path: str,
|
|
1164
|
+
provider: str | None,
|
|
1165
|
+
ai_cmd: str | None,
|
|
1166
|
+
):
|
|
1167
|
+
"""Suggest inferred implementation steps and merge them into the task cache."""
|
|
1168
|
+
from codd.deployment.providers.ai_command import SubprocessAiCommand
|
|
1169
|
+
from codd.llm.best_practice_augmenter import BEST_PRACTICE_AUGMENTERS
|
|
1170
|
+
from codd.llm.impl_step_deriver import (
|
|
1171
|
+
ImplStepCacheRecord,
|
|
1172
|
+
impl_step_cache_path,
|
|
1173
|
+
merge_impl_steps,
|
|
1174
|
+
read_impl_step_cache,
|
|
1175
|
+
utc_timestamp,
|
|
1176
|
+
write_impl_step_cache,
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
project_root = Path(project_path).resolve()
|
|
1180
|
+
_require_codd_dir(project_root)
|
|
1181
|
+
config = _load_optional_project_config(project_root)
|
|
1182
|
+
cache_path = impl_step_cache_path(task_id, {"project_root": project_root})
|
|
1183
|
+
record = read_impl_step_cache(cache_path)
|
|
1184
|
+
if record is None:
|
|
1185
|
+
click.echo("Error: derive Layer 1 steps before augmenting")
|
|
1186
|
+
raise SystemExit(1)
|
|
1187
|
+
|
|
1188
|
+
provider_name = provider or _best_practice_provider(config)
|
|
1189
|
+
augmenter_cls = BEST_PRACTICE_AUGMENTERS.get(provider_name)
|
|
1190
|
+
if augmenter_cls is None:
|
|
1191
|
+
click.echo(f"Error: best practice augmenter provider not found: {provider_name}")
|
|
1192
|
+
raise SystemExit(1)
|
|
1193
|
+
|
|
1194
|
+
try:
|
|
1195
|
+
task_item = _implement_task_for_cli(project_root, config, task_id)
|
|
1196
|
+
docs = design_docs or tuple(record.design_docs)
|
|
1197
|
+
nodes = _plan_design_doc_nodes(project_root, docs)
|
|
1198
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
1199
|
+
click.echo(f"Error: {exc}")
|
|
1200
|
+
raise SystemExit(1)
|
|
1201
|
+
|
|
1202
|
+
command = ai_cmd or _best_practice_command(config)
|
|
1203
|
+
augmenter = augmenter_cls(SubprocessAiCommand(command=command, project_root=project_root, config=config))
|
|
1204
|
+
explicit = [step for step in record.steps if not step.inferred]
|
|
1205
|
+
implicit = augmenter.suggest_implicit_steps(
|
|
1206
|
+
task_item,
|
|
1207
|
+
nodes,
|
|
1208
|
+
explicit,
|
|
1209
|
+
{"project_root": project_root, "config": config, "project_context": {"project": config.get("project", {})}},
|
|
1210
|
+
)
|
|
1211
|
+
merged = merge_impl_steps(explicit, implicit)
|
|
1212
|
+
write_impl_step_cache(
|
|
1213
|
+
cache_path,
|
|
1214
|
+
ImplStepCacheRecord(
|
|
1215
|
+
provider_id=record.provider_id,
|
|
1216
|
+
cache_key=f"{record.cache_key}:augmented",
|
|
1217
|
+
task_id=record.task_id,
|
|
1218
|
+
design_doc_sha=record.design_doc_sha,
|
|
1219
|
+
prompt_template_sha=record.prompt_template_sha,
|
|
1220
|
+
generated_at=utc_timestamp(),
|
|
1221
|
+
design_docs=record.design_docs,
|
|
1222
|
+
steps=merged,
|
|
1223
|
+
),
|
|
1224
|
+
)
|
|
1225
|
+
click.echo(f"Augmented implementation steps: {len(implicit)}")
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
@implement.command("run")
|
|
1229
|
+
@click.option("--task", "task_id", default=None, help="Generate only one task by task ID or title match")
|
|
1230
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
1231
|
+
@click.option("--ai-cmd", default=None, help="Override AI CLI command")
|
|
1232
|
+
@click.option("--use-derived-steps", default="true", help="Inject derived implementation steps: true or false")
|
|
1233
|
+
@click.option("--chunk-size", default=None, type=click.IntRange(min=1), help="Run derived steps in chunks of this size")
|
|
1234
|
+
@click.option(
|
|
1235
|
+
"--timeout-per-chunk",
|
|
1236
|
+
default=600,
|
|
1237
|
+
type=click.IntRange(min=1),
|
|
1238
|
+
show_default=True,
|
|
1239
|
+
help="Seconds before one chunk is interrupted",
|
|
1240
|
+
)
|
|
1241
|
+
def implement_run_cmd(
|
|
1242
|
+
task_id: str | None,
|
|
1243
|
+
project_path: str,
|
|
1244
|
+
ai_cmd: str | None,
|
|
1245
|
+
use_derived_steps: str,
|
|
1246
|
+
chunk_size: int | None,
|
|
1247
|
+
timeout_per_chunk: int,
|
|
1248
|
+
):
|
|
1249
|
+
"""Run implementation with optional derived step injection."""
|
|
1250
|
+
from codd.implementer import implement_tasks
|
|
1251
|
+
|
|
1252
|
+
project_root = Path(project_path).resolve()
|
|
1253
|
+
_require_codd_dir(project_root)
|
|
1254
|
+
if chunk_size is not None:
|
|
1255
|
+
try:
|
|
1256
|
+
result = _run_chunked_implementation(
|
|
1257
|
+
project_root=project_root,
|
|
1258
|
+
task_id=task_id,
|
|
1259
|
+
ai_cmd=ai_cmd,
|
|
1260
|
+
chunk_size=chunk_size,
|
|
1261
|
+
timeout_per_chunk=timeout_per_chunk,
|
|
1262
|
+
history=None,
|
|
1263
|
+
)
|
|
1264
|
+
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
1265
|
+
click.echo(f"Error: {exc}")
|
|
1266
|
+
raise SystemExit(1)
|
|
1267
|
+
_echo_chunked_result(project_root, result)
|
|
1268
|
+
if result.status != "SUCCESS":
|
|
1269
|
+
raise SystemExit(1)
|
|
1270
|
+
return
|
|
1271
|
+
|
|
1272
|
+
try:
|
|
1273
|
+
results = implement_tasks(
|
|
1274
|
+
project_root,
|
|
1275
|
+
task=task_id,
|
|
1276
|
+
ai_command=ai_cmd,
|
|
1277
|
+
use_derived_steps=_optional_bool(use_derived_steps),
|
|
1278
|
+
)
|
|
1279
|
+
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
1280
|
+
click.echo(f"Error: {exc}")
|
|
1281
|
+
raise SystemExit(1)
|
|
1282
|
+
failed = [result for result in results if result.error]
|
|
1283
|
+
for result in results:
|
|
1284
|
+
for generated_file in result.generated_files:
|
|
1285
|
+
click.echo(f"Generated: {generated_file.relative_to(project_root)} ({result.task_id})")
|
|
1286
|
+
click.echo(f"{sum(len(result.generated_files) for result in results)} files generated across {len(results) - len(failed)} task(s)")
|
|
1287
|
+
if failed:
|
|
1288
|
+
raise SystemExit(1)
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
@implement.command("resume")
|
|
1292
|
+
@click.option("--task", "task_id", required=True, help="Implementation task id or title match")
|
|
1293
|
+
@click.option("--history", required=True, help="History id or path from a previous chunked run")
|
|
1294
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
1295
|
+
@click.option("--ai-cmd", default=None, help="Override AI CLI command")
|
|
1296
|
+
@click.option("--chunk-size", default=5, type=click.IntRange(min=1), show_default=True, help="Chunk size")
|
|
1297
|
+
@click.option(
|
|
1298
|
+
"--timeout-per-chunk",
|
|
1299
|
+
default=600,
|
|
1300
|
+
type=click.IntRange(min=1),
|
|
1301
|
+
show_default=True,
|
|
1302
|
+
help="Seconds before one chunk is interrupted",
|
|
1303
|
+
)
|
|
1304
|
+
def implement_resume_cmd(
|
|
1305
|
+
task_id: str,
|
|
1306
|
+
history: str,
|
|
1307
|
+
project_path: str,
|
|
1308
|
+
ai_cmd: str | None,
|
|
1309
|
+
chunk_size: int,
|
|
1310
|
+
timeout_per_chunk: int,
|
|
1311
|
+
):
|
|
1312
|
+
"""Resume a chunked implementation run."""
|
|
1313
|
+
project_root = Path(project_path).resolve()
|
|
1314
|
+
_require_codd_dir(project_root)
|
|
1315
|
+
try:
|
|
1316
|
+
result = _run_chunked_implementation(
|
|
1317
|
+
project_root=project_root,
|
|
1318
|
+
task_id=task_id,
|
|
1319
|
+
ai_cmd=ai_cmd,
|
|
1320
|
+
chunk_size=chunk_size,
|
|
1321
|
+
timeout_per_chunk=timeout_per_chunk,
|
|
1322
|
+
history=history,
|
|
1323
|
+
)
|
|
1324
|
+
except (FileNotFoundError, ValueError, CoddCLIError) as exc:
|
|
1325
|
+
click.echo(f"Error: {exc}")
|
|
1326
|
+
raise SystemExit(1)
|
|
1327
|
+
_echo_chunked_result(project_root, result)
|
|
1328
|
+
if result.status != "SUCCESS":
|
|
1329
|
+
raise SystemExit(1)
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
def _run_chunked_implementation(
|
|
1333
|
+
*,
|
|
1334
|
+
project_root: Path,
|
|
1335
|
+
task_id: str | None,
|
|
1336
|
+
ai_cmd: str | None,
|
|
1337
|
+
chunk_size: int,
|
|
1338
|
+
timeout_per_chunk: int,
|
|
1339
|
+
history: str | None,
|
|
1340
|
+
):
|
|
1341
|
+
if not task_id:
|
|
1342
|
+
raise ValueError("--task is required when chunked execution is enabled")
|
|
1343
|
+
|
|
1344
|
+
import codd.generator as generator_module
|
|
1345
|
+
from codd.implementer.chunked_runner import ChunkedRunner
|
|
1346
|
+
|
|
1347
|
+
config = _load_optional_project_config(project_root)
|
|
1348
|
+
task_item, steps = _chunked_task_and_steps(project_root, config, task_id)
|
|
1349
|
+
resolved_ai_command = generator_module._resolve_ai_command(config, ai_cmd, command_name="implement")
|
|
1350
|
+
|
|
1351
|
+
def progress(current: int, total: int) -> None:
|
|
1352
|
+
click.echo(f"Chunk {current}/{total} complete")
|
|
1353
|
+
|
|
1354
|
+
runner = ChunkedRunner(
|
|
1355
|
+
chunk_size=chunk_size,
|
|
1356
|
+
timeout_per_chunk=timeout_per_chunk,
|
|
1357
|
+
progress_callback=progress,
|
|
1358
|
+
)
|
|
1359
|
+
if history is None:
|
|
1360
|
+
return runner.run_steps(task_item, steps, resolved_ai_command, project_root)
|
|
1361
|
+
return runner.resume_steps(task_item, steps, resolved_ai_command, project_root, history)
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def _chunked_task_and_steps(project_root: Path, config: dict[str, Any], task_id: str):
|
|
1365
|
+
from codd.implementer import _filter_layer1_impl_steps, _filter_layer2_impl_steps
|
|
1366
|
+
from codd.llm.impl_step_deriver import impl_step_cache_path, read_impl_step_cache
|
|
1367
|
+
|
|
1368
|
+
task_item = _implement_task_for_cli(project_root, config, task_id)
|
|
1369
|
+
context = {"project_root": project_root}
|
|
1370
|
+
cache_path = impl_step_cache_path(task_item, context)
|
|
1371
|
+
record = read_impl_step_cache(cache_path)
|
|
1372
|
+
if record is None:
|
|
1373
|
+
record = read_impl_step_cache(impl_step_cache_path(task_id, context))
|
|
1374
|
+
if record is None or not record.steps:
|
|
1375
|
+
raise ValueError("no derived implementation steps found; run 'codd implement plan' first")
|
|
1376
|
+
|
|
1377
|
+
explicit = _filter_layer1_impl_steps([step for step in record.steps if not step.inferred], config)
|
|
1378
|
+
implicit = _filter_layer2_impl_steps([step for step in record.steps if step.inferred], config)
|
|
1379
|
+
steps = [*explicit, *implicit]
|
|
1380
|
+
if not steps:
|
|
1381
|
+
raise ValueError("no approved implementation steps found for chunked execution")
|
|
1382
|
+
return task_item, steps
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
def _echo_chunked_result(project_root: Path, result) -> None:
|
|
1386
|
+
try:
|
|
1387
|
+
history = result.history_path.relative_to(project_root)
|
|
1388
|
+
except ValueError:
|
|
1389
|
+
history = result.history_path
|
|
1390
|
+
click.echo(
|
|
1391
|
+
f"Chunked implementation {result.status}: "
|
|
1392
|
+
f"{len(result.completed_chunks)}/{result.total_chunks} chunks; history={history}"
|
|
1393
|
+
)
|
|
1019
1394
|
|
|
1020
1395
|
|
|
1021
1396
|
@main.command()
|
|
@@ -2425,6 +2800,63 @@ def _plan_derive_provider(config: dict[str, Any]) -> str:
|
|
|
2425
2800
|
return value if isinstance(value, str) and value else "subprocess_ai_command"
|
|
2426
2801
|
|
|
2427
2802
|
|
|
2803
|
+
def _impl_step_command(config: dict[str, Any]) -> str | None:
|
|
2804
|
+
value = _nested_config_value(config, ("ai_commands", "impl_step_derive"))
|
|
2805
|
+
if isinstance(value, str):
|
|
2806
|
+
return value
|
|
2807
|
+
if isinstance(value, dict):
|
|
2808
|
+
command = value.get("command")
|
|
2809
|
+
return command if isinstance(command, str) else None
|
|
2810
|
+
return None
|
|
2811
|
+
|
|
2812
|
+
|
|
2813
|
+
def _impl_step_provider(config: dict[str, Any]) -> str:
|
|
2814
|
+
value = _nested_config_value(config, ("ai_commands", "impl_step_derive"))
|
|
2815
|
+
if isinstance(value, dict) and isinstance(value.get("provider"), str):
|
|
2816
|
+
return value["provider"]
|
|
2817
|
+
value = _nested_config_value(config, ("ai_commands", "impl_step_deriver_provider"))
|
|
2818
|
+
return value if isinstance(value, str) and value else "subprocess_ai_command"
|
|
2819
|
+
|
|
2820
|
+
|
|
2821
|
+
def _best_practice_command(config: dict[str, Any]) -> str | None:
|
|
2822
|
+
value = _nested_config_value(config, ("ai_commands", "best_practice_augment"))
|
|
2823
|
+
if isinstance(value, str):
|
|
2824
|
+
return value
|
|
2825
|
+
if isinstance(value, dict):
|
|
2826
|
+
command = value.get("command")
|
|
2827
|
+
return command if isinstance(command, str) else None
|
|
2828
|
+
return None
|
|
2829
|
+
|
|
2830
|
+
|
|
2831
|
+
def _best_practice_provider(config: dict[str, Any]) -> str:
|
|
2832
|
+
value = _nested_config_value(config, ("ai_commands", "best_practice_augment"))
|
|
2833
|
+
if isinstance(value, dict) and isinstance(value.get("provider"), str):
|
|
2834
|
+
return value["provider"]
|
|
2835
|
+
value = _nested_config_value(config, ("ai_commands", "best_practice_augmenter_provider"))
|
|
2836
|
+
return value if isinstance(value, str) and value else "subprocess_ai_command"
|
|
2837
|
+
|
|
2838
|
+
|
|
2839
|
+
def _optional_bool(value: str | bool | None) -> bool | None:
|
|
2840
|
+
if value is None or isinstance(value, bool):
|
|
2841
|
+
return value
|
|
2842
|
+
text = str(value).strip().casefold()
|
|
2843
|
+
if text in {"1", "true", "yes", "on"}:
|
|
2844
|
+
return True
|
|
2845
|
+
if text in {"0", "false", "no", "off"}:
|
|
2846
|
+
return False
|
|
2847
|
+
raise click.BadParameter("expected true or false")
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
def _implement_task_for_cli(project_root: Path, config: dict[str, Any], task_id: str):
|
|
2851
|
+
from codd.implementer import _extract_all_tasks, _filter_tasks, _load_implementation_plan
|
|
2852
|
+
|
|
2853
|
+
plan = _load_implementation_plan(project_root, config)
|
|
2854
|
+
matches = _filter_tasks(_extract_all_tasks(plan), task_id)
|
|
2855
|
+
if not matches:
|
|
2856
|
+
raise ValueError(f"no implementation task matched {task_id!r}")
|
|
2857
|
+
return matches[0]
|
|
2858
|
+
|
|
2859
|
+
|
|
2428
2860
|
def _nested_config_value(config: dict[str, Any], path: tuple[str, ...]) -> Any:
|
|
2429
2861
|
value: Any = config
|
|
2430
2862
|
for key in path:
|
|
@@ -2805,6 +3237,12 @@ def _load_optional_project_config(project_root: Path) -> dict[str, Any]:
|
|
|
2805
3237
|
|
|
2806
3238
|
|
|
2807
3239
|
def _run_verify_once(path: str, sprint: int | None = None) -> _CliVerificationResult:
|
|
3240
|
+
if get_command_handler("verify") is None:
|
|
3241
|
+
from codd.repair.verify_runner import run_standalone_verify
|
|
3242
|
+
|
|
3243
|
+
result = run_standalone_verify(Path(path).resolve())
|
|
3244
|
+
return _cli_result_from_standalone_verify(result)
|
|
3245
|
+
|
|
2808
3246
|
try:
|
|
2809
3247
|
_run_pro_command("verify", path=path, sprint=sprint)
|
|
2810
3248
|
except SystemExit as exc:
|
|
@@ -2822,6 +3260,14 @@ def _run_verify_once(path: str, sprint: int | None = None) -> _CliVerificationRe
|
|
|
2822
3260
|
return _CliVerificationResult(passed=True, exit_code=0, failure=None)
|
|
2823
3261
|
|
|
2824
3262
|
|
|
3263
|
+
def _cli_result_from_standalone_verify(result: Any) -> _CliVerificationResult:
|
|
3264
|
+
return _CliVerificationResult(
|
|
3265
|
+
passed=bool(result.passed),
|
|
3266
|
+
exit_code=0 if result.passed else 1,
|
|
3267
|
+
failure=getattr(result, "failure", None),
|
|
3268
|
+
)
|
|
3269
|
+
|
|
3270
|
+
|
|
2825
3271
|
def _system_exit_code(exc: SystemExit) -> int:
|
|
2826
3272
|
code = exc.code
|
|
2827
3273
|
if code is None:
|
|
@@ -57,16 +57,19 @@ def _read_yaml_mapping(path: Path) -> dict[str, Any]:
|
|
|
57
57
|
return payload
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def _deep_merge(defaults: Any, project: Any) -> Any:
|
|
60
|
+
def _deep_merge(defaults: Any, project: Any, path: tuple[str, ...] = ()) -> Any:
|
|
61
61
|
if isinstance(defaults, dict) and isinstance(project, dict):
|
|
62
62
|
merged = deepcopy(defaults)
|
|
63
63
|
for key, value in project.items():
|
|
64
64
|
if key in merged:
|
|
65
|
-
merged[key] = _deep_merge(merged[key], value)
|
|
65
|
+
merged[key] = _deep_merge(merged[key], value, (*path, str(key)))
|
|
66
66
|
else:
|
|
67
67
|
merged[key] = deepcopy(value)
|
|
68
68
|
return merged
|
|
69
69
|
|
|
70
|
+
if path == ("coherence", "path_prefix_tolerant"):
|
|
71
|
+
return deepcopy(project)
|
|
72
|
+
|
|
70
73
|
if isinstance(defaults, list) and isinstance(project, list):
|
|
71
74
|
return _merge_lists(defaults, project)
|
|
72
75
|
|
|
@@ -124,6 +124,7 @@ def load_dag_settings(project_root: Path, settings: dict[str, Any] | None = None
|
|
|
124
124
|
_apply_scan_patterns(merged, project_config)
|
|
125
125
|
_apply_scan_patterns(merged, requested_settings)
|
|
126
126
|
merged["coherence"] = _coherence_settings(project_config, requested_settings)
|
|
127
|
+
merged["extraction"] = _extraction_settings(project_config, requested_settings)
|
|
127
128
|
merged.setdefault("design_doc_patterns", [])
|
|
128
129
|
merged.setdefault("impl_file_patterns", [])
|
|
129
130
|
merged.setdefault("test_file_patterns", [])
|
|
@@ -210,11 +211,12 @@ def render_mermaid(dag: DAG) -> str:
|
|
|
210
211
|
def _add_design_docs(dag: DAG, project_root: Path, settings: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
211
212
|
design_docs: dict[str, dict[str, Any]] = {}
|
|
212
213
|
aliases: dict[str, str] = {}
|
|
214
|
+
frontmatter_alias = _frontmatter_alias_settings(settings)
|
|
213
215
|
for md_path in _glob_project_paths(project_root, settings.get("design_doc_patterns", [])):
|
|
214
216
|
if not md_path.is_file():
|
|
215
217
|
continue
|
|
216
218
|
node_id = _relative_id(md_path, project_root)
|
|
217
|
-
metadata = extract_design_doc_metadata(md_path)
|
|
219
|
+
metadata = extract_design_doc_metadata(md_path, frontmatter_alias=frontmatter_alias)
|
|
218
220
|
attributes = metadata.get("attributes") or {}
|
|
219
221
|
_validate_design_doc_journey_attributes(node_id, attributes)
|
|
220
222
|
_add_node_once(
|
|
@@ -1180,6 +1182,31 @@ def _coherence_settings(*configs: dict[str, Any]) -> dict[str, Any]:
|
|
|
1180
1182
|
return coherence
|
|
1181
1183
|
|
|
1182
1184
|
|
|
1185
|
+
def _extraction_settings(*configs: dict[str, Any]) -> dict[str, Any]:
|
|
1186
|
+
extraction: dict[str, Any] = {"frontmatter_alias": {}}
|
|
1187
|
+
for config in configs:
|
|
1188
|
+
section = config.get("extraction", {})
|
|
1189
|
+
if isinstance(section, dict):
|
|
1190
|
+
extraction = _deep_merge(extraction, section)
|
|
1191
|
+
if not isinstance(extraction.get("frontmatter_alias"), dict):
|
|
1192
|
+
extraction["frontmatter_alias"] = {}
|
|
1193
|
+
return extraction
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
def _frontmatter_alias_settings(settings: dict[str, Any]) -> dict[str, str]:
|
|
1197
|
+
extraction = settings.get("extraction", {})
|
|
1198
|
+
if not isinstance(extraction, dict):
|
|
1199
|
+
return {}
|
|
1200
|
+
aliases = extraction.get("frontmatter_alias", {})
|
|
1201
|
+
if not isinstance(aliases, dict):
|
|
1202
|
+
return {}
|
|
1203
|
+
return {
|
|
1204
|
+
str(alias_key).strip(): str(canonical_key).strip()
|
|
1205
|
+
for alias_key, canonical_key in aliases.items()
|
|
1206
|
+
if str(alias_key).strip() and str(canonical_key).strip()
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
|
|
1183
1210
|
def _capability_patterns(settings: dict[str, Any]) -> dict[str, Any]:
|
|
1184
1211
|
coherence = settings.get("coherence", {})
|
|
1185
1212
|
if not isinstance(coherence, dict):
|