codd-dev 1.35.0__tar.gz → 1.37.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.35.0 → codd_dev-1.37.0}/PKG-INFO +1 -1
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/cli.py +118 -12
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/finding.py +37 -3
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/extract_ai.py +57 -3
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/extractor.py +65 -25
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/parsing.py +390 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/git_patcher.py +15 -12
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/llm_repair_engine.py +105 -27
- codd_dev-1.37.0/codd/repair/templates/repair_strategy_meta.md +44 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/pyproject.toml +1 -1
- {codd_dev-1.35.0 → codd_dev-1.37.0}/.gitignore +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/LICENSE +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/README.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/__main__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/_git_helper.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/ask_user_question_adapter.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/assembler.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/bridge.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/clustering.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coherence_adapters.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coherence_engine.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/config.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/contracts.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coverage_auditor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coverage_metrics.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/builder.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/depends_on_consistency.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/deployment_completeness.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/edge_validity.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/environment_coverage.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/implementation_coverage.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/node_completeness.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/task_completion.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/transitive_closure.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/user_journey_coherence.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/coverage_axes.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/cli.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/cpp_embedded.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/csharp.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/elixir.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/generic.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/iot.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/java.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/kotlin.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/mobile.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/ruby.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/rust.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/scala.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/swift.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/test_frameworks.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/web.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/runner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/defaults.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/app_service.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/base.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/docker_compose.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployer.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/checks/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/deploy_targets.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/runtime_capability_inference.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/schema_providers.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/verification_templates.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/ai_command.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/llm_consideration.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/schema/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/schema/prisma.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/target/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/target/docker_compose.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/assertion_handlers.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_browser.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_engines.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_launchers.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_wire.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/curl.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/form_strategies.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/means_catalog.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/playwright.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/design_md.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/apply.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/engine.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/persistence.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/templates/diff_prompt.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/drift.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_generator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_runner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/apply.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/engine.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/base.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/interactive.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/json_fmt.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/md.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/lexicon_loader.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/persistence.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/templates/elicit_prompt_L0.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/env_refs.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixer.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/generator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/graph.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hitl_session.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/claude_settings_example.json +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/codex_hook.sh +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/git_post_commit.sh +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/git_pre_commit.sh +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/chunked_runner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/typecheck_loop.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/inheritance.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/knowledge_fetcher.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/lexicon.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/approval.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/best_practice_augmenter.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/criteria_expander.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/design_doc_extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/impl_step_deriver.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/means_catalog_loader.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/parser.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/plan_deriver.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/prompt_builder.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/strategy_validator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/best_practice_augment_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/criteria_expand_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/design_doc_extract_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/impl_step_derive_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/implementation_step_catalog.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/meta_instruction.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/plan_derive_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/verification_means_catalog.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/mcp_server.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/measure.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/planner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/policy.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/cli.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/iot.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/mobile.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/web.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/propagate.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/propagator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/registry.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/approval_repair.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/engine.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/history.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/loop.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/primary_picker.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/proof_breaks.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/repair_result.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/repairability_classifier.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/schema.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/analyze_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/propose_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/repairability_meta.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/verify_runner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair_slice.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require_plugins.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require_propagate.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/web.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts_deriver.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness_auditor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/restore.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/routes_extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/scanner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_flow_validator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_transition_extractor.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_transitions/defaults.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/synth.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/codd.yaml.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extract_ai_prompt_baseline.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/lexicon_questions.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/lexicon_schema.yaml +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/traceability.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/validator.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/__init__.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/events.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/propagation_log.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/propagation_pipeline.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/test_runner.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/watcher.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/wiring.py +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/docs/cookbook/cdp_browser/README.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/docs/requirements/README.md +0 -0
- {codd_dev-1.35.0 → codd_dev-1.37.0}/tests/integration/standalone_repair_skeleton/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.37.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
|
|
@@ -288,13 +288,9 @@ def _resolve_bootstrap_codd_dir(project_root: Path) -> Path:
|
|
|
288
288
|
return existing
|
|
289
289
|
|
|
290
290
|
hidden_dir = project_root / ".codd"
|
|
291
|
-
default_dir = project_root / "codd"
|
|
292
291
|
if hidden_dir.exists():
|
|
293
292
|
return hidden_dir
|
|
294
|
-
|
|
295
|
-
# Avoid writing config into projects whose source package is already named codd/.
|
|
296
|
-
return hidden_dir
|
|
297
|
-
return default_dir
|
|
293
|
+
return hidden_dir
|
|
298
294
|
|
|
299
295
|
|
|
300
296
|
def _format_yaml_list(items: list[str], *, indent: int = 4) -> str:
|
|
@@ -1028,6 +1024,75 @@ def elicit_apply_cmd(input_file: Path, format_name: str, project_path: str) -> N
|
|
|
1028
1024
|
click.echo(f"Updated: {file_path}")
|
|
1029
1025
|
|
|
1030
1026
|
|
|
1027
|
+
@main.command("brownfield")
|
|
1028
|
+
@click.argument("target_path", type=click.Path(file_okay=False, path_type=Path))
|
|
1029
|
+
@click.option(
|
|
1030
|
+
"--requirements",
|
|
1031
|
+
"requirements_path",
|
|
1032
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
1033
|
+
default=None,
|
|
1034
|
+
help="Requirements file (default: <target>/.codd/requirements.md; skipped if absent).",
|
|
1035
|
+
)
|
|
1036
|
+
@click.option(
|
|
1037
|
+
"--lexicon",
|
|
1038
|
+
"lexicon_path",
|
|
1039
|
+
type=click.Path(path_type=Path),
|
|
1040
|
+
default=None,
|
|
1041
|
+
help="Lexicon directory or manifest (default: <target>/.codd/lexicon; discovery mode if absent).",
|
|
1042
|
+
)
|
|
1043
|
+
@click.option(
|
|
1044
|
+
"--format",
|
|
1045
|
+
"format_name",
|
|
1046
|
+
type=click.Choice(["json", "md"]),
|
|
1047
|
+
default="md",
|
|
1048
|
+
show_default=True,
|
|
1049
|
+
help="Integrated report format.",
|
|
1050
|
+
)
|
|
1051
|
+
@click.option("--output", type=click.Path(dir_okay=False, path_type=Path), default=None, help="Output report file.")
|
|
1052
|
+
@click.option(
|
|
1053
|
+
"--ai-cmd",
|
|
1054
|
+
default=None,
|
|
1055
|
+
help="Override AI CLI command for diff and elicit engines.",
|
|
1056
|
+
)
|
|
1057
|
+
def brownfield_cmd(
|
|
1058
|
+
target_path: Path,
|
|
1059
|
+
requirements_path: Path | None,
|
|
1060
|
+
lexicon_path: Path | None,
|
|
1061
|
+
format_name: str,
|
|
1062
|
+
output: Path | None,
|
|
1063
|
+
ai_cmd: str | None,
|
|
1064
|
+
) -> None:
|
|
1065
|
+
"""Run brownfield extract, diff, elicit, and merged reporting."""
|
|
1066
|
+
from codd.brownfield.pipeline import BrownfieldPipeline, format_brownfield_result
|
|
1067
|
+
|
|
1068
|
+
project_root = target_path.expanduser().resolve()
|
|
1069
|
+
try:
|
|
1070
|
+
result = BrownfieldPipeline(ai_command=ai_cmd).run(
|
|
1071
|
+
project_root,
|
|
1072
|
+
requirements_path=requirements_path,
|
|
1073
|
+
lexicon_path=lexicon_path,
|
|
1074
|
+
)
|
|
1075
|
+
formatted = format_brownfield_result(result, format_name)
|
|
1076
|
+
output_path = _project_file(
|
|
1077
|
+
project_root,
|
|
1078
|
+
output,
|
|
1079
|
+
f".codd/brownfield_report.{format_name}",
|
|
1080
|
+
)
|
|
1081
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1082
|
+
output_path.write_text(formatted, encoding="utf-8")
|
|
1083
|
+
except (OSError, TypeError, ValueError, json.JSONDecodeError, yaml.YAMLError) as exc:
|
|
1084
|
+
click.echo(f"Error: {exc}")
|
|
1085
|
+
raise SystemExit(1)
|
|
1086
|
+
|
|
1087
|
+
click.echo(
|
|
1088
|
+
"Brownfield pipeline complete: "
|
|
1089
|
+
f"diff_findings={len(result.diff_findings)}, "
|
|
1090
|
+
f"elicit_findings={len(result.elicit_findings)}, "
|
|
1091
|
+
f"merged_findings={len(result.merged_findings)}"
|
|
1092
|
+
)
|
|
1093
|
+
click.echo(f"Output: {_display_path(output_path, project_root)}")
|
|
1094
|
+
|
|
1095
|
+
|
|
1031
1096
|
@main.group("diff", invoke_without_command=True)
|
|
1032
1097
|
@click.option("--extract-input", type=click.Path(dir_okay=False, path_type=Path), default=None)
|
|
1033
1098
|
@click.option("--requirements", "requirements_path", type=click.Path(dir_okay=False, path_type=Path), default=None)
|
|
@@ -1068,7 +1133,7 @@ def diff_cmd(
|
|
|
1068
1133
|
from codd.elicit.formatters.md import MdFormatter
|
|
1069
1134
|
|
|
1070
1135
|
project_root = Path(project_path).resolve()
|
|
1071
|
-
extract_path =
|
|
1136
|
+
extract_path = _resolve_diff_extract_input(project_root, extract_input)
|
|
1072
1137
|
req_path = _project_file(project_root, requirements_path, "docs/requirements/requirements.md")
|
|
1073
1138
|
output_path = _project_file(project_root, output, "drift_findings.md") if output is not None or format_name == "md" else None
|
|
1074
1139
|
|
|
@@ -1161,6 +1226,37 @@ def _project_file(project_root: Path, value: Path | None, default: str) -> Path:
|
|
|
1161
1226
|
return project_root / path
|
|
1162
1227
|
|
|
1163
1228
|
|
|
1229
|
+
def _resolve_diff_extract_input(project_root: Path, value: Path | None) -> Path:
|
|
1230
|
+
"""Locate the extract-input file, preferring isolated `.codd/extract/` output.
|
|
1231
|
+
|
|
1232
|
+
Order of resolution:
|
|
1233
|
+
1. Explicit ``--extract-input`` value (absolute or project-relative).
|
|
1234
|
+
2. ``.codd/extract/extracted.md`` (default Issue #17 isolation target).
|
|
1235
|
+
3. Top-level ``extracted.md`` aggregated output if present.
|
|
1236
|
+
4. First ``.codd/extract/modules/*.md`` module file (deterministic by name).
|
|
1237
|
+
5. Legacy fallback ``codd/extracted.md`` (preserved for older projects).
|
|
1238
|
+
"""
|
|
1239
|
+
if value is not None:
|
|
1240
|
+
candidate = value.expanduser()
|
|
1241
|
+
return candidate if candidate.is_absolute() else project_root / candidate
|
|
1242
|
+
|
|
1243
|
+
candidates: list[Path] = [
|
|
1244
|
+
project_root / ".codd" / "extract" / "extracted.md",
|
|
1245
|
+
project_root / "extracted.md",
|
|
1246
|
+
]
|
|
1247
|
+
modules_dir = project_root / ".codd" / "extract" / "modules"
|
|
1248
|
+
if modules_dir.is_dir():
|
|
1249
|
+
module_files = sorted(modules_dir.glob("*.md"))
|
|
1250
|
+
if module_files:
|
|
1251
|
+
candidates.append(module_files[0])
|
|
1252
|
+
candidates.append(project_root / "codd" / "extracted.md")
|
|
1253
|
+
|
|
1254
|
+
for candidate in candidates:
|
|
1255
|
+
if candidate.is_file():
|
|
1256
|
+
return candidate
|
|
1257
|
+
return candidates[0]
|
|
1258
|
+
|
|
1259
|
+
|
|
1164
1260
|
def _build_diff_engine(engine_cls: Any, ai_cmd: str | None, project_root: Path) -> Any:
|
|
1165
1261
|
attempts = (
|
|
1166
1262
|
lambda: engine_cls(llm_client=ai_cmd, project_root=project_root),
|
|
@@ -2203,7 +2299,8 @@ def e2e_generate_legacy(path: str, base_url: str, output: str | None, framework:
|
|
|
2203
2299
|
@click.option("--path", default=".", help="Project root directory")
|
|
2204
2300
|
@click.option("--language", default=None, help="Override language detection (python/typescript/javascript/go — full support; java — symbols only)")
|
|
2205
2301
|
@click.option("--source-dirs", default=None, help="Comma-separated source directories (default: auto-detect)")
|
|
2206
|
-
@click.option("--output", default=None, help="Output directory (default: <
|
|
2302
|
+
@click.option("--output", default=None, help="Output directory (default: <project-root>/.codd/extract/)")
|
|
2303
|
+
@click.option("--init", "initialize", is_flag=True, help="Add brownfield init metadata to generated YAML/Markdown")
|
|
2207
2304
|
@click.option("--ai", is_flag=True, default=False, help="Use AI-powered extraction (6-layer MECE design docs)")
|
|
2208
2305
|
@click.option(
|
|
2209
2306
|
"--ai-cmd",
|
|
@@ -2237,6 +2334,7 @@ def extract(
|
|
|
2237
2334
|
language: str | None,
|
|
2238
2335
|
source_dirs: str | None,
|
|
2239
2336
|
output: str | None,
|
|
2337
|
+
initialize: bool,
|
|
2240
2338
|
ai: bool,
|
|
2241
2339
|
ai_cmd: str | None,
|
|
2242
2340
|
prompt_file: str | None,
|
|
@@ -2249,9 +2347,8 @@ def extract(
|
|
|
2249
2347
|
Default mode: static analysis (no AI, pure structural facts).
|
|
2250
2348
|
With --ai: AI-powered 6-layer MECE extraction using claude --print.
|
|
2251
2349
|
|
|
2252
|
-
Output goes to
|
|
2253
|
-
|
|
2254
|
-
confirmed docs when ready.
|
|
2350
|
+
Output goes to `.codd/extract/` by default. Review and promote confirmed
|
|
2351
|
+
docs when ready.
|
|
2255
2352
|
"""
|
|
2256
2353
|
if ctx.invoked_subcommand is not None:
|
|
2257
2354
|
return
|
|
@@ -2259,7 +2356,13 @@ def extract(
|
|
|
2259
2356
|
project_root = Path(path).resolve()
|
|
2260
2357
|
bootstrap_codd_dir = _resolve_bootstrap_codd_dir(project_root)
|
|
2261
2358
|
dirs = [d.strip() for d in source_dirs.split(",") if d.strip()] if source_dirs else None
|
|
2262
|
-
output_path = Path(output) if output else
|
|
2359
|
+
output_path = Path(output) if output else project_root / ".codd" / "extract"
|
|
2360
|
+
if output and not output_path.is_absolute():
|
|
2361
|
+
output_path = project_root / output_path
|
|
2362
|
+
init_metadata = None
|
|
2363
|
+
if initialize:
|
|
2364
|
+
from codd.extractor import build_extract_init_metadata
|
|
2365
|
+
init_metadata = build_extract_init_metadata(project_root)
|
|
2263
2366
|
|
|
2264
2367
|
if layer == "routes":
|
|
2265
2368
|
from codd.config import load_project_config
|
|
@@ -2314,6 +2417,9 @@ def extract(
|
|
|
2314
2417
|
except Exception as exc:
|
|
2315
2418
|
click.echo(f"Error: {exc}")
|
|
2316
2419
|
raise SystemExit(1)
|
|
2420
|
+
if init_metadata is not None:
|
|
2421
|
+
from codd.extractor import add_extract_init_frontmatter
|
|
2422
|
+
add_extract_init_frontmatter(result.generated_files, init_metadata)
|
|
2317
2423
|
|
|
2318
2424
|
facts = extract_facts(project_root, language, dirs)
|
|
2319
2425
|
config_path, generated_config = _ensure_bootstrap_codd_yaml(
|
|
@@ -2340,7 +2446,7 @@ def extract(
|
|
|
2340
2446
|
from codd.extractor import run_extract
|
|
2341
2447
|
|
|
2342
2448
|
try:
|
|
2343
|
-
result = run_extract(project_root, language, dirs, str(output_path))
|
|
2449
|
+
result = run_extract(project_root, language, dirs, str(output_path), init_metadata=init_metadata)
|
|
2344
2450
|
except Exception as exc:
|
|
2345
2451
|
click.echo(f"Error: {exc}")
|
|
2346
2452
|
raise SystemExit(1)
|
|
@@ -41,13 +41,12 @@ class Finding:
|
|
|
41
41
|
|
|
42
42
|
finding_id = str(payload.get("id", "")).strip()
|
|
43
43
|
kind = str(payload.get("kind", "")).strip()
|
|
44
|
-
|
|
44
|
+
raw_severity = str(payload.get("severity", "")).strip()
|
|
45
45
|
if not finding_id:
|
|
46
46
|
raise ValueError("Finding id is required")
|
|
47
47
|
if not kind:
|
|
48
48
|
raise ValueError("Finding kind is required")
|
|
49
|
-
|
|
50
|
-
raise ValueError(f"Finding severity must be one of {sorted(_SEVERITIES)}")
|
|
49
|
+
severity = _coerce_severity(raw_severity)
|
|
51
50
|
|
|
52
51
|
source = str(payload.get("source", "greenfield")).strip() or "greenfield"
|
|
53
52
|
if source not in _SOURCES:
|
|
@@ -85,6 +84,41 @@ def _optional_text(value: Any) -> str | None:
|
|
|
85
84
|
return text if text else None
|
|
86
85
|
|
|
87
86
|
|
|
87
|
+
_SEVERITY_ALIASES = {
|
|
88
|
+
"blocker": "critical",
|
|
89
|
+
"fatal": "critical",
|
|
90
|
+
"severe": "critical",
|
|
91
|
+
"urgent": "critical",
|
|
92
|
+
"error": "high",
|
|
93
|
+
"major": "high",
|
|
94
|
+
"important": "high",
|
|
95
|
+
"warn": "medium",
|
|
96
|
+
"warning": "medium",
|
|
97
|
+
"moderate": "medium",
|
|
98
|
+
"minor": "info",
|
|
99
|
+
"low": "info",
|
|
100
|
+
"informational": "info",
|
|
101
|
+
"note": "info",
|
|
102
|
+
"trivial": "info",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _coerce_severity(raw: str) -> str:
|
|
107
|
+
"""Map a raw severity string into the canonical Literal set, with a safe fallback.
|
|
108
|
+
|
|
109
|
+
Strict membership is preserved for canonical values; common synonyms are mapped
|
|
110
|
+
deterministically; anything unknown defaults to ``info`` so downstream tools stay
|
|
111
|
+
operational rather than aborting on LLM drift. Generic mapping only — no stack /
|
|
112
|
+
framework / domain literals.
|
|
113
|
+
"""
|
|
114
|
+
cleaned = raw.lower().strip()
|
|
115
|
+
if cleaned in _SEVERITIES:
|
|
116
|
+
return cleaned
|
|
117
|
+
if cleaned in _SEVERITY_ALIASES:
|
|
118
|
+
return _SEVERITY_ALIASES[cleaned]
|
|
119
|
+
return "info"
|
|
120
|
+
|
|
121
|
+
|
|
88
122
|
@dataclass
|
|
89
123
|
class ElicitResult:
|
|
90
124
|
findings: list[Finding] = field(default_factory=list)
|
|
@@ -49,6 +49,12 @@ SCHEMA_PATTERNS = [
|
|
|
49
49
|
"prisma/schema.prisma", "db/schema.rb", "alembic/versions",
|
|
50
50
|
]
|
|
51
51
|
|
|
52
|
+
# Generic source extensions included in the AI context scan. The deterministic
|
|
53
|
+
# parser decides language-specific structure later; this layer keeps raw text.
|
|
54
|
+
SOURCE_EXTENSIONS = {
|
|
55
|
+
".go", ".java", ".js", ".jsx", ".php", ".py", ".rb", ".ts", ".tsx", ".vue",
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
# Max file size to include in prompt (bytes)
|
|
53
59
|
MAX_FILE_SIZE = 50_000
|
|
54
60
|
|
|
@@ -171,9 +177,45 @@ def _find_source_files(root: Path) -> list[Path]:
|
|
|
171
177
|
# Filter out files in skip dirs
|
|
172
178
|
found = [f for f in found if not any(s in f.parts for s in SKIP_DIRS)]
|
|
173
179
|
files.extend(found)
|
|
180
|
+
files.extend(_find_source_files_by_extension(root))
|
|
174
181
|
return sorted(set(files))
|
|
175
182
|
|
|
176
183
|
|
|
184
|
+
def _find_source_files_by_extension(root: Path) -> list[Path]:
|
|
185
|
+
"""Find source files generically by extension, preserving raw text for AI."""
|
|
186
|
+
files: list[Path] = []
|
|
187
|
+
for current, dirs, filenames in os.walk(root):
|
|
188
|
+
dirs[:] = [
|
|
189
|
+
d for d in dirs
|
|
190
|
+
if d not in SKIP_DIRS and not d.startswith(".")
|
|
191
|
+
]
|
|
192
|
+
current_path = Path(current)
|
|
193
|
+
for filename in filenames:
|
|
194
|
+
path = current_path / filename
|
|
195
|
+
if path.suffix not in SOURCE_EXTENSIONS:
|
|
196
|
+
continue
|
|
197
|
+
try:
|
|
198
|
+
rel_parts = path.relative_to(root).parts
|
|
199
|
+
except ValueError:
|
|
200
|
+
continue
|
|
201
|
+
if _is_test_path(rel_parts) or any(part in SKIP_DIRS for part in rel_parts):
|
|
202
|
+
continue
|
|
203
|
+
files.append(path)
|
|
204
|
+
return files
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _is_test_path(parts: tuple[str, ...]) -> bool:
|
|
208
|
+
if any(part in {"test", "tests", "spec", "__tests__"} for part in parts[:-1]):
|
|
209
|
+
return True
|
|
210
|
+
filename = parts[-1].lower() if parts else ""
|
|
211
|
+
return (
|
|
212
|
+
filename.startswith("test_")
|
|
213
|
+
or filename.endswith(("_test.py", "_spec.rb"))
|
|
214
|
+
or ".test." in filename
|
|
215
|
+
or ".spec." in filename
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
177
219
|
def _find_iac_files(root: Path) -> list[Path]:
|
|
178
220
|
"""Find IaC and DevOps files."""
|
|
179
221
|
iac: list[Path] = []
|
|
@@ -198,6 +240,8 @@ def _find_iac_files(root: Path) -> list[Path]:
|
|
|
198
240
|
def _find_test_files(root: Path) -> list[Path]:
|
|
199
241
|
"""Find test files (paths only, not contents)."""
|
|
200
242
|
patterns = [
|
|
243
|
+
"tests/test_*.py", "tests/**/test_*.py",
|
|
244
|
+
"test/test_*.py", "test/**/test_*.py",
|
|
201
245
|
"tests/**/*.test.*", "tests/**/*.spec.*",
|
|
202
246
|
"test/**/*.test.*", "test/**/*.spec.*",
|
|
203
247
|
"src/**/*.test.*", "src/**/*.spec.*",
|
|
@@ -206,11 +250,21 @@ def _find_test_files(root: Path) -> list[Path]:
|
|
|
206
250
|
tests: list[Path] = []
|
|
207
251
|
for pattern in patterns:
|
|
208
252
|
found = list(root.glob(pattern))
|
|
209
|
-
found = [
|
|
253
|
+
found = [
|
|
254
|
+
f for f in found
|
|
255
|
+
if not any(s in _relative_parts(root, f) for s in SKIP_DIRS)
|
|
256
|
+
]
|
|
210
257
|
tests.extend(found)
|
|
211
258
|
return sorted(set(tests))
|
|
212
259
|
|
|
213
260
|
|
|
261
|
+
def _relative_parts(root: Path, path: Path) -> tuple[str, ...]:
|
|
262
|
+
try:
|
|
263
|
+
return path.relative_to(root).parts
|
|
264
|
+
except ValueError:
|
|
265
|
+
return path.parts
|
|
266
|
+
|
|
267
|
+
|
|
214
268
|
def pre_scan(project_root: Path) -> PreScanResult:
|
|
215
269
|
"""Phase 1: Deterministic pre-scan of the project."""
|
|
216
270
|
result = PreScanResult(
|
|
@@ -456,11 +510,11 @@ def run_extract_ai(
|
|
|
456
510
|
Args:
|
|
457
511
|
project_root: Path to the project to extract from.
|
|
458
512
|
ai_command: AI CLI command (e.g. 'claude --print --model claude-opus-4-6').
|
|
459
|
-
output_dir: Output directory (default: {project_root}
|
|
513
|
+
output_dir: Output directory (default: {project_root}/.codd/extract/).
|
|
460
514
|
prompt_file: Path to a custom prompt file. Overrides the built-in baseline preset.
|
|
461
515
|
"""
|
|
462
516
|
project_root = project_root.resolve()
|
|
463
|
-
out = Path(output_dir) if output_dir else project_root / "codd" / "
|
|
517
|
+
out = Path(output_dir) if output_dir else project_root / ".codd" / "extract"
|
|
464
518
|
out.mkdir(parents=True, exist_ok=True)
|
|
465
519
|
|
|
466
520
|
# Phase 1: Pre-scan
|
|
@@ -12,6 +12,7 @@ Two-phase architecture:
|
|
|
12
12
|
import os
|
|
13
13
|
import re
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Any
|
|
17
18
|
|
|
@@ -125,6 +126,59 @@ class ExtractResult:
|
|
|
125
126
|
source_dirs: list[str]
|
|
126
127
|
|
|
127
128
|
|
|
129
|
+
def build_extract_init_metadata(project_root: Path, extracted_at: str | None = None) -> dict[str, str]:
|
|
130
|
+
"""Build generic brownfield extraction metadata for generated YAML/Markdown."""
|
|
131
|
+
timestamp = extracted_at or datetime.now().astimezone().replace(microsecond=0).isoformat()
|
|
132
|
+
return {
|
|
133
|
+
"version": "1.0",
|
|
134
|
+
"extracted_at": timestamp,
|
|
135
|
+
"source": project_root.resolve().as_posix(),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def add_extract_init_frontmatter(paths: list[Path], metadata: dict[str, str]) -> None:
|
|
140
|
+
"""Add codd init metadata to generated Markdown frontmatter or YAML payloads."""
|
|
141
|
+
for path in paths:
|
|
142
|
+
suffix = path.suffix.lower()
|
|
143
|
+
if suffix in {".md", ".markdown"}:
|
|
144
|
+
_upsert_markdown_codd_metadata(path, metadata)
|
|
145
|
+
elif suffix in {".yaml", ".yml"}:
|
|
146
|
+
_upsert_yaml_codd_metadata(path, metadata)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _merge_codd_metadata(payload: Any, metadata: dict[str, str]) -> dict[str, Any]:
|
|
150
|
+
if not isinstance(payload, dict):
|
|
151
|
+
payload = {}
|
|
152
|
+
codd = payload.get("codd")
|
|
153
|
+
if not isinstance(codd, dict):
|
|
154
|
+
codd = {}
|
|
155
|
+
codd.update(metadata)
|
|
156
|
+
payload["codd"] = codd
|
|
157
|
+
return payload
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _upsert_markdown_codd_metadata(path: Path, metadata: dict[str, str]) -> None:
|
|
161
|
+
text = path.read_text(encoding="utf-8")
|
|
162
|
+
match = re.match(r"\A---\s*\n(.*?)\n---\s*\n?", text, re.DOTALL)
|
|
163
|
+
if match:
|
|
164
|
+
frontmatter = yaml.safe_load(match.group(1)) or {}
|
|
165
|
+
body = text[match.end():]
|
|
166
|
+
else:
|
|
167
|
+
frontmatter = {}
|
|
168
|
+
body = text
|
|
169
|
+
|
|
170
|
+
payload = _merge_codd_metadata(frontmatter, metadata)
|
|
171
|
+
rendered = yaml.safe_dump(payload, sort_keys=False, allow_unicode=True)
|
|
172
|
+
separator = "" if body.startswith("\n") else "\n"
|
|
173
|
+
path.write_text(f"---\n{rendered}---\n{separator}{body}", encoding="utf-8")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _upsert_yaml_codd_metadata(path: Path, metadata: dict[str, str]) -> None:
|
|
177
|
+
payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
178
|
+
merged = _merge_codd_metadata(payload, metadata)
|
|
179
|
+
path.write_text(yaml.safe_dump(merged, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
|
180
|
+
|
|
181
|
+
|
|
128
182
|
# ═══════════════════════════════════════════════════════════
|
|
129
183
|
# Phase 1: Extract Facts (deterministic, no AI)
|
|
130
184
|
# ═══════════════════════════════════════════════════════════
|
|
@@ -695,28 +749,13 @@ def _detect_patterns(facts: ProjectFacts, project_root: Path):
|
|
|
695
749
|
|
|
696
750
|
|
|
697
751
|
def _detect_python_patterns(facts: ProjectFacts, content: str):
|
|
698
|
-
|
|
699
|
-
"fastapi": "FastAPI", "flask": "Flask", "django": "Django",
|
|
700
|
-
"starlette": "Starlette", "tornado": "Tornado", "aiohttp": "aiohttp",
|
|
701
|
-
}
|
|
702
|
-
orms = {
|
|
703
|
-
"sqlalchemy": "SQLAlchemy", "django": "Django ORM",
|
|
704
|
-
"tortoise-orm": "Tortoise ORM", "peewee": "Peewee",
|
|
705
|
-
"sqlmodel": "SQLModel", "prisma": "Prisma",
|
|
706
|
-
}
|
|
707
|
-
test_fw = {
|
|
708
|
-
"pytest": "pytest", "unittest": "unittest", "nose": "nose2",
|
|
709
|
-
}
|
|
752
|
+
"""No-op: framework/ORM/test detection is delegated to LLM (extract --ai).
|
|
710
753
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
facts.detected_orm = name
|
|
717
|
-
for key, name in test_fw.items():
|
|
718
|
-
if key in content.lower() and not facts.detected_test_framework:
|
|
719
|
-
facts.detected_test_framework = name
|
|
754
|
+
Removed in v1.36.0 to honor Generality Gate. Hard-coded framework/ORM/test
|
|
755
|
+
dictionaries violated the constraint that CoDD core must remain stack-agnostic.
|
|
756
|
+
The downstream AI extraction path (codd/extract_ai.py) infers these dynamically.
|
|
757
|
+
"""
|
|
758
|
+
return
|
|
720
759
|
|
|
721
760
|
|
|
722
761
|
def _detect_js_patterns(facts: ProjectFacts, content: str):
|
|
@@ -973,7 +1012,8 @@ def synth_architecture(facts: ProjectFacts, output_dir: Path) -> Path:
|
|
|
973
1012
|
|
|
974
1013
|
def run_extract(project_root: Path, language: str | None = None,
|
|
975
1014
|
source_dirs: list[str] | None = None,
|
|
976
|
-
output: str | None = None
|
|
1015
|
+
output: str | None = None,
|
|
1016
|
+
init_metadata: dict[str, str] | None = None) -> ExtractResult:
|
|
977
1017
|
"""Run full extract pipeline: facts → docs."""
|
|
978
1018
|
|
|
979
1019
|
# Try to load config if it exists
|
|
@@ -994,11 +1034,11 @@ def run_extract(project_root: Path, language: str | None = None,
|
|
|
994
1034
|
# Phase 2: Generate docs
|
|
995
1035
|
if output:
|
|
996
1036
|
output_dir = Path(output)
|
|
997
|
-
elif codd_dir is not None:
|
|
998
|
-
output_dir = codd_dir / "extracted"
|
|
999
1037
|
else:
|
|
1000
|
-
output_dir = project_root / "codd" / "
|
|
1038
|
+
output_dir = project_root / ".codd" / "extract"
|
|
1001
1039
|
generated = synth_docs(facts, output_dir)
|
|
1040
|
+
if init_metadata is not None:
|
|
1041
|
+
add_extract_init_frontmatter(generated, init_metadata)
|
|
1002
1042
|
|
|
1003
1043
|
return ExtractResult(
|
|
1004
1044
|
output_dir=output_dir,
|