codd-dev 1.24.0__tar.gz → 1.26.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.24.0 → codd_dev-1.26.0}/PKG-INFO +2 -1
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/cli.py +300 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/__init__.py +1 -1
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/builder.py +365 -20
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/__init__.py +35 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/deployment_completeness.py +3 -2
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/node_completeness.py +2 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/task_completion.py +3 -1
- codd_dev-1.26.0/codd/dag/checks/user_journey_coherence.py +728 -0
- codd_dev-1.26.0/codd/dag/defaults/cpp_embedded.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/csharp.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/elixir.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/generic.yaml +5 -0
- codd_dev-1.26.0/codd/dag/defaults/java.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/kotlin.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/ruby.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/rust.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/scala.yaml +2 -0
- codd_dev-1.26.0/codd/dag/defaults/swift.yaml +2 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/defaults/web.yaml +2 -0
- codd_dev-1.26.0/codd/dag/extractor.py +264 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/runner.py +1 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/defaults.yaml +2 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployer.py +146 -1
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/__init__.py +1 -0
- codd_dev-1.26.0/codd/deployment/defaults/runtime_capability_inference.yaml +12 -0
- codd_dev-1.26.0/codd/deployment/defaults/verification_means_catalog.yaml +6 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/extractor.py +228 -5
- codd_dev-1.26.0/codd/deployment/providers/ai_command.py +186 -0
- codd_dev-1.26.0/codd/deployment/providers/llm_consideration.py +338 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/__init__.py +21 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/assertion_handlers.py +244 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/cdp_browser.py +350 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/cdp_engines.py +40 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/cdp_launchers.py +44 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/cdp_wire.py +130 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/form_strategies.py +44 -0
- codd_dev-1.26.0/codd/deployment/providers/verification/means_catalog.py +102 -0
- codd_dev-1.26.0/codd/llm/approval.py +324 -0
- codd_dev-1.26.0/codd/llm/means_catalog_loader.py +122 -0
- codd_dev-1.26.0/codd/llm/parser.py +119 -0
- codd_dev-1.26.0/codd/llm/prompt_builder.py +57 -0
- codd_dev-1.26.0/codd/llm/strategy_validator.py +37 -0
- codd_dev-1.26.0/codd/llm/templates/meta_instruction.md +37 -0
- codd_dev-1.26.0/codd/llm/templates/verification_means_catalog.yaml +6 -0
- codd_dev-1.26.0/codd/repair/__init__.py +5 -0
- codd_dev-1.26.0/codd/repair/engine.py +90 -0
- codd_dev-1.26.0/codd/repair/git_patcher.py +120 -0
- codd_dev-1.26.0/codd/repair/history.py +124 -0
- codd_dev-1.26.0/codd/repair/llm_repair_engine.py +305 -0
- codd_dev-1.26.0/codd/repair/schema.py +85 -0
- codd_dev-1.26.0/codd/repair/templates/analyze_meta.md +28 -0
- codd_dev-1.26.0/codd/repair/templates/propose_meta.md +34 -0
- codd_dev-1.26.0/codd/templates/extract_ai_prompt_baseline.md +418 -0
- codd_dev-1.26.0/codd/templates/lexicon_questions.md +151 -0
- codd_dev-1.26.0/docs/cookbook/cdp_browser/README.md +88 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/pyproject.toml +3 -1
- codd_dev-1.24.0/codd/dag/extractor.py +0 -76
- {codd_dev-1.24.0 → codd_dev-1.26.0}/.gitignore +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/LICENSE +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/README.md +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/__main__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/_git_helper.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/ask_user_question_adapter.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/assembler.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/bridge.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/clustering.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/coherence_adapters.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/coherence_engine.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/config.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/contracts.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/coverage_auditor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/coverage_metrics.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/depends_on_consistency.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/edge_validity.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/checks/transitive_closure.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/defaults/cli.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/defaults/iot.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/defaults/mobile.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/dag/defaults/test_frameworks.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deploy_targets/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deploy_targets/app_service.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deploy_targets/base.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deploy_targets/docker_compose.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/checks/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/defaults/deploy_targets.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/defaults/schema_providers.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/defaults/verification_templates.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/schema/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/schema/prisma.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/target/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/target/docker_compose.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/verification/curl.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/deployment/providers/verification/playwright.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/design_md.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/drift.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/e2e_extractor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/e2e_generator.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/e2e_runner.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/env_refs.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/extract_ai.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/extractor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixer.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixup_drift.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixup_drift_strategies/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/generator.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/graph.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hitl_session.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/recipes/claude_settings_example.json +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/recipes/codex_hook.sh +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/recipes/git_post_commit.sh +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/hooks/recipes/git_pre_commit.sh +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/implementer.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/inheritance.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/knowledge_fetcher.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/lexicon.py +0 -0
- {codd_dev-1.24.0/codd/deployment/providers/verification → codd_dev-1.26.0/codd/llm}/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/mcp_server.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/measure.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/parsing.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/planner.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/policy.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/preflight/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/preflight/defaults/cli.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/preflight/defaults/iot.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/preflight/defaults/mobile.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/preflight/defaults/web.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/propagate.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/propagator.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/registry.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/repair_slice.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/require.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/require_plugins.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/require_propagate.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/required_artifacts/defaults/web.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/required_artifacts_deriver.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/requirement_completeness_auditor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/restore.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/routes_extractor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/scanner.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/screen_flow_validator.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/screen_transition_extractor.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/screen_transitions/defaults.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/synth.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/codd.yaml.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/lexicon_schema.yaml +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/traceability.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/validator.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/__init__.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/events.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/propagation_log.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/propagation_pipeline.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/test_runner.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/watch/watcher.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.0}/codd/wiring.py +0 -0
- {codd_dev-1.24.0 → codd_dev-1.26.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.26.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
|
|
@@ -20,6 +20,7 @@ Requires-Dist: jinja2>=3.1.0
|
|
|
20
20
|
Requires-Dist: pyyaml>=6.0
|
|
21
21
|
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
|
|
22
22
|
Requires-Dist: watchdog>=4.0.0
|
|
23
|
+
Requires-Dist: websocket-client>=1.8.0
|
|
23
24
|
Provides-Extra: ai
|
|
24
25
|
Provides-Extra: api-parsers
|
|
25
26
|
Requires-Dist: graphql-core>=3.2.0; extra == 'api-parsers'
|
|
@@ -2283,6 +2283,119 @@ def test_cmd(project_path: str, related: tuple[str, ...], dry_run: bool):
|
|
|
2283
2283
|
raise SystemExit(1)
|
|
2284
2284
|
|
|
2285
2285
|
|
|
2286
|
+
@main.group()
|
|
2287
|
+
def llm():
|
|
2288
|
+
"""Manage LLM-derived considerations."""
|
|
2289
|
+
pass
|
|
2290
|
+
|
|
2291
|
+
|
|
2292
|
+
@llm.command("derive")
|
|
2293
|
+
@click.argument("design_doc", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
2294
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
2295
|
+
@click.option("--ai-cmd", default=None, help="Override AI CLI command")
|
|
2296
|
+
@click.option("--model", default=None, help="Override AI model")
|
|
2297
|
+
@click.option("--force", is_flag=True, help="Bypass cached derived considerations")
|
|
2298
|
+
def llm_derive(design_doc: Path, project_path: str, ai_cmd: str | None, model: str | None, force: bool):
|
|
2299
|
+
"""Derive considerations for a design document."""
|
|
2300
|
+
from codd.deployment.providers.ai_command import SubprocessAiCommand
|
|
2301
|
+
from codd.deployment.providers.llm_consideration import LlmConsiderationProvider
|
|
2302
|
+
from codd.llm.approval import notify_pending_considerations
|
|
2303
|
+
|
|
2304
|
+
project_root = Path(project_path).resolve()
|
|
2305
|
+
config = _load_optional_project_config(project_root)
|
|
2306
|
+
provider = LlmConsiderationProvider(
|
|
2307
|
+
SubprocessAiCommand(command=ai_cmd, project_root=project_root, config=config),
|
|
2308
|
+
project_root=project_root,
|
|
2309
|
+
cache_dir=project_root / ".codd" / "consideration_cache",
|
|
2310
|
+
model=model,
|
|
2311
|
+
use_cache=not force,
|
|
2312
|
+
)
|
|
2313
|
+
result = provider.provide(design_doc.read_text(encoding="utf-8"), {"model": model} if model else {})
|
|
2314
|
+
notify_pending_considerations(result.considerations, config)
|
|
2315
|
+
click.echo(f"Derived considerations: {len(result.considerations)}")
|
|
2316
|
+
|
|
2317
|
+
|
|
2318
|
+
@llm.command("approve")
|
|
2319
|
+
@click.argument("consideration_id", required=False)
|
|
2320
|
+
@click.option("--all", "approve_all", is_flag=True, help="Approve all pending considerations")
|
|
2321
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
2322
|
+
def llm_approve(consideration_id: str | None, approve_all: bool, project_path: str):
|
|
2323
|
+
"""Approve one or all pending considerations."""
|
|
2324
|
+
from codd.llm.approval import ApprovalCache, consideration_status, load_cached_considerations
|
|
2325
|
+
|
|
2326
|
+
project_root = Path(project_path).resolve()
|
|
2327
|
+
considerations = load_cached_considerations(project_root)
|
|
2328
|
+
by_id = {consideration.id: consideration for consideration in considerations}
|
|
2329
|
+
|
|
2330
|
+
if approve_all:
|
|
2331
|
+
targets = [
|
|
2332
|
+
consideration
|
|
2333
|
+
for consideration in considerations
|
|
2334
|
+
if consideration_status(consideration, project_root) == "pending"
|
|
2335
|
+
]
|
|
2336
|
+
else:
|
|
2337
|
+
if not consideration_id:
|
|
2338
|
+
click.echo("Error: consideration_id or --all is required")
|
|
2339
|
+
raise SystemExit(2)
|
|
2340
|
+
if consideration_id not in by_id:
|
|
2341
|
+
click.echo(f"Error: consideration not found: {consideration_id}")
|
|
2342
|
+
raise SystemExit(1)
|
|
2343
|
+
targets = [by_id[consideration_id]]
|
|
2344
|
+
|
|
2345
|
+
for consideration in targets:
|
|
2346
|
+
ApprovalCache.save(consideration.id, "approved", project_root)
|
|
2347
|
+
click.echo(f"Approved considerations: {len(targets)}")
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
@llm.command("skip")
|
|
2351
|
+
@click.argument("consideration_id")
|
|
2352
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
2353
|
+
def llm_skip(consideration_id: str, project_path: str):
|
|
2354
|
+
"""Skip one consideration."""
|
|
2355
|
+
from codd.llm.approval import ApprovalCache, load_cached_considerations
|
|
2356
|
+
|
|
2357
|
+
project_root = Path(project_path).resolve()
|
|
2358
|
+
known = {consideration.id for consideration in load_cached_considerations(project_root)}
|
|
2359
|
+
if consideration_id not in known:
|
|
2360
|
+
click.echo(f"Error: consideration not found: {consideration_id}")
|
|
2361
|
+
raise SystemExit(1)
|
|
2362
|
+
ApprovalCache.save(consideration_id, "skipped", project_root)
|
|
2363
|
+
click.echo(f"Skipped consideration: {consideration_id}")
|
|
2364
|
+
|
|
2365
|
+
|
|
2366
|
+
@llm.command("list")
|
|
2367
|
+
@click.option("--path", "project_path", default=".", help="Project root directory")
|
|
2368
|
+
@click.option("--format", "output_format", default="text", type=click.Choice(["text", "json"]))
|
|
2369
|
+
def llm_list(project_path: str, output_format: str):
|
|
2370
|
+
"""List generated considerations with approval status."""
|
|
2371
|
+
from codd.llm.approval import consideration_status, consideration_to_dict, load_cached_considerations
|
|
2372
|
+
|
|
2373
|
+
project_root = Path(project_path).resolve()
|
|
2374
|
+
rows = []
|
|
2375
|
+
for consideration in sorted(load_cached_considerations(project_root), key=lambda item: item.id):
|
|
2376
|
+
row = consideration_to_dict(consideration)
|
|
2377
|
+
row["status"] = consideration_status(consideration, project_root)
|
|
2378
|
+
rows.append(row)
|
|
2379
|
+
|
|
2380
|
+
if output_format == "json":
|
|
2381
|
+
click.echo(json.dumps(rows, ensure_ascii=False, indent=2))
|
|
2382
|
+
return
|
|
2383
|
+
|
|
2384
|
+
if not rows:
|
|
2385
|
+
click.echo("No considerations found")
|
|
2386
|
+
return
|
|
2387
|
+
for row in rows:
|
|
2388
|
+
description = str(row.get("description") or "")
|
|
2389
|
+
click.echo(f"{row['id']}\t{row['status']}\t{description}")
|
|
2390
|
+
|
|
2391
|
+
|
|
2392
|
+
def _load_optional_project_config(project_root: Path) -> dict[str, Any]:
|
|
2393
|
+
try:
|
|
2394
|
+
return load_project_config(project_root)
|
|
2395
|
+
except (FileNotFoundError, ValueError):
|
|
2396
|
+
return {}
|
|
2397
|
+
|
|
2398
|
+
|
|
2286
2399
|
@main.group()
|
|
2287
2400
|
def dag():
|
|
2288
2401
|
"""DAG Completeness Gate commands."""
|
|
@@ -2409,6 +2522,192 @@ def dag_visualize(project_path: str):
|
|
|
2409
2522
|
click.echo(render_mermaid(built_dag), nl=False)
|
|
2410
2523
|
|
|
2411
2524
|
|
|
2525
|
+
@dag.command("journeys")
|
|
2526
|
+
@click.option("--project-path", "--path", default=".", show_default=True, help="Project root directory")
|
|
2527
|
+
@click.option(
|
|
2528
|
+
"--format",
|
|
2529
|
+
"output_format",
|
|
2530
|
+
default="text",
|
|
2531
|
+
type=click.Choice(["text", "json"]),
|
|
2532
|
+
help="Output format",
|
|
2533
|
+
)
|
|
2534
|
+
def dag_journeys(project_path: str, output_format: str):
|
|
2535
|
+
"""List user_journeys declared on design_doc DAG nodes."""
|
|
2536
|
+
from codd.dag.builder import build_dag
|
|
2537
|
+
|
|
2538
|
+
project_root = Path(project_path).resolve()
|
|
2539
|
+
try:
|
|
2540
|
+
built_dag = build_dag(project_root)
|
|
2541
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
2542
|
+
click.echo(f"Error: {exc}")
|
|
2543
|
+
raise SystemExit(1)
|
|
2544
|
+
|
|
2545
|
+
journeys = _collect_dag_journeys(built_dag, project_root, _load_optional_project_config(project_root))
|
|
2546
|
+
if output_format == "json":
|
|
2547
|
+
click.echo(json.dumps(journeys, ensure_ascii=False, indent=2))
|
|
2548
|
+
return
|
|
2549
|
+
|
|
2550
|
+
current_doc: str | None = None
|
|
2551
|
+
for journey in journeys:
|
|
2552
|
+
design_doc = journey["design_doc"]
|
|
2553
|
+
if design_doc != current_doc:
|
|
2554
|
+
if current_doc is not None:
|
|
2555
|
+
click.echo()
|
|
2556
|
+
click.echo(design_doc)
|
|
2557
|
+
current_doc = design_doc
|
|
2558
|
+
required = journey["required_capabilities"]
|
|
2559
|
+
requires = ", ".join(required) if required else "-"
|
|
2560
|
+
click.echo(f" {journey['name']} [{journey['criticality']}] requires: {requires}")
|
|
2561
|
+
|
|
2562
|
+
|
|
2563
|
+
@dag.command("run-journey")
|
|
2564
|
+
@click.argument("journey_name")
|
|
2565
|
+
@click.option("--project-path", "--path", default=".", show_default=True, help="Project root directory")
|
|
2566
|
+
@click.option(
|
|
2567
|
+
"--config-section",
|
|
2568
|
+
default="cdp_browser",
|
|
2569
|
+
show_default=True,
|
|
2570
|
+
help="verification.templates section used for browser config",
|
|
2571
|
+
)
|
|
2572
|
+
def dag_run_journey(journey_name: str, project_path: str, config_section: str):
|
|
2573
|
+
"""Run one declared user_journey with the CDP browser template."""
|
|
2574
|
+
from codd.dag.builder import build_dag
|
|
2575
|
+
from codd.deployment.providers.verification.cdp_browser import CdpBrowser
|
|
2576
|
+
|
|
2577
|
+
project_root = Path(project_path).resolve()
|
|
2578
|
+
try:
|
|
2579
|
+
config = load_project_config(project_root)
|
|
2580
|
+
template_config = _journey_template_config(config, config_section)
|
|
2581
|
+
built_dag = build_dag(project_root)
|
|
2582
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
2583
|
+
click.echo(f"Error: {exc}")
|
|
2584
|
+
raise SystemExit(2)
|
|
2585
|
+
|
|
2586
|
+
journey_record = _find_dag_journey(built_dag, journey_name, project_root, config)
|
|
2587
|
+
if journey_record is None:
|
|
2588
|
+
click.echo(f"Error: user_journey not found: {journey_name}")
|
|
2589
|
+
raise SystemExit(2)
|
|
2590
|
+
|
|
2591
|
+
command = json.dumps(
|
|
2592
|
+
_journey_execution_plan(project_root, journey_record, template_config),
|
|
2593
|
+
sort_keys=True,
|
|
2594
|
+
)
|
|
2595
|
+
result = CdpBrowser(config=template_config).execute(command)
|
|
2596
|
+
if result.output:
|
|
2597
|
+
click.echo(result.output)
|
|
2598
|
+
raise SystemExit(0 if result.passed else 1)
|
|
2599
|
+
|
|
2600
|
+
|
|
2601
|
+
def _journey_template_config(config: dict[str, Any], config_section: str) -> dict[str, Any]:
|
|
2602
|
+
verification = config.get("verification")
|
|
2603
|
+
templates = verification.get("templates") if isinstance(verification, dict) else None
|
|
2604
|
+
if not isinstance(templates, dict) or config_section not in templates:
|
|
2605
|
+
raise ValueError(f"verification.templates.{config_section} config not found")
|
|
2606
|
+
|
|
2607
|
+
section = templates[config_section]
|
|
2608
|
+
if not isinstance(section, dict):
|
|
2609
|
+
raise ValueError(f"verification.templates.{config_section} must be a mapping")
|
|
2610
|
+
return dict(section)
|
|
2611
|
+
|
|
2612
|
+
|
|
2613
|
+
def _find_dag_journey(
|
|
2614
|
+
dag: Any,
|
|
2615
|
+
journey_name: str,
|
|
2616
|
+
project_root: Path | None = None,
|
|
2617
|
+
settings: dict[str, Any] | None = None,
|
|
2618
|
+
) -> dict[str, Any] | None:
|
|
2619
|
+
for node in sorted(dag.nodes.values(), key=lambda item: item.id):
|
|
2620
|
+
if node.kind != "design_doc":
|
|
2621
|
+
continue
|
|
2622
|
+
for journey in _node_user_journeys(node, project_root, settings):
|
|
2623
|
+
if str(journey.get("name") or "") == journey_name:
|
|
2624
|
+
return {
|
|
2625
|
+
"design_doc": node.path or node.id,
|
|
2626
|
+
"journey": dict(journey),
|
|
2627
|
+
}
|
|
2628
|
+
return None
|
|
2629
|
+
|
|
2630
|
+
|
|
2631
|
+
def _journey_execution_plan(
|
|
2632
|
+
project_root: Path,
|
|
2633
|
+
journey_record: dict[str, Any],
|
|
2634
|
+
template_config: dict[str, Any],
|
|
2635
|
+
) -> dict[str, Any]:
|
|
2636
|
+
journey = dict(journey_record["journey"])
|
|
2637
|
+
journey_name = str(journey.get("name") or "")
|
|
2638
|
+
steps = journey.get("steps")
|
|
2639
|
+
return {
|
|
2640
|
+
"template": "cdp_browser",
|
|
2641
|
+
"test_kind": "e2e",
|
|
2642
|
+
"target": _journey_target(journey),
|
|
2643
|
+
"identifier": f"journey:{journey_name}",
|
|
2644
|
+
"journey": journey_name,
|
|
2645
|
+
"steps": steps if isinstance(steps, list) else [],
|
|
2646
|
+
"project_root": str(project_root),
|
|
2647
|
+
"design_doc": journey_record["design_doc"],
|
|
2648
|
+
"config": template_config,
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
|
|
2652
|
+
def _journey_target(journey: dict[str, Any]) -> str:
|
|
2653
|
+
steps = journey.get("steps")
|
|
2654
|
+
if isinstance(steps, list):
|
|
2655
|
+
for step in steps:
|
|
2656
|
+
if not isinstance(step, dict):
|
|
2657
|
+
continue
|
|
2658
|
+
if step.get("action") == "navigate":
|
|
2659
|
+
target = step.get("target") or step.get("url")
|
|
2660
|
+
if target:
|
|
2661
|
+
return str(target)
|
|
2662
|
+
|
|
2663
|
+
target = journey.get("target") or journey.get("url")
|
|
2664
|
+
return str(target or "")
|
|
2665
|
+
|
|
2666
|
+
|
|
2667
|
+
def _collect_dag_journeys(
|
|
2668
|
+
dag: Any,
|
|
2669
|
+
project_root: Path | None = None,
|
|
2670
|
+
settings: dict[str, Any] | None = None,
|
|
2671
|
+
) -> list[dict[str, Any]]:
|
|
2672
|
+
journeys: list[dict[str, Any]] = []
|
|
2673
|
+
for node in sorted(dag.nodes.values(), key=lambda item: item.id):
|
|
2674
|
+
if node.kind != "design_doc":
|
|
2675
|
+
continue
|
|
2676
|
+
for journey in _node_user_journeys(node, project_root, settings):
|
|
2677
|
+
journeys.append(
|
|
2678
|
+
{
|
|
2679
|
+
"design_doc": node.path or node.id,
|
|
2680
|
+
"name": str(journey.get("name") or ""),
|
|
2681
|
+
"criticality": str(journey.get("criticality") or ""),
|
|
2682
|
+
"required_capabilities": _string_list(journey.get("required_capabilities")),
|
|
2683
|
+
}
|
|
2684
|
+
)
|
|
2685
|
+
return journeys
|
|
2686
|
+
|
|
2687
|
+
|
|
2688
|
+
def _node_user_journeys(
|
|
2689
|
+
node: Any,
|
|
2690
|
+
project_root: Path | None = None,
|
|
2691
|
+
settings: dict[str, Any] | None = None,
|
|
2692
|
+
) -> list[dict[str, Any]]:
|
|
2693
|
+
if project_root is not None:
|
|
2694
|
+
from codd.dag.checks.user_journey_coherence import UserJourneyCoherenceCheck
|
|
2695
|
+
|
|
2696
|
+
return UserJourneyCoherenceCheck(project_root=project_root, settings=settings or {})._journey_entries(node)
|
|
2697
|
+
|
|
2698
|
+
attributes = getattr(node, "attributes", {}) or {}
|
|
2699
|
+
value = attributes.get("user_journeys")
|
|
2700
|
+
if not isinstance(value, list):
|
|
2701
|
+
return []
|
|
2702
|
+
return [journey for journey in value if isinstance(journey, dict)]
|
|
2703
|
+
|
|
2704
|
+
|
|
2705
|
+
def _string_list(value: Any) -> list[str]:
|
|
2706
|
+
if not isinstance(value, list):
|
|
2707
|
+
return []
|
|
2708
|
+
return [str(item) for item in value if isinstance(item, str) and item]
|
|
2709
|
+
|
|
2710
|
+
|
|
2412
2711
|
def _dag_result_to_dict(result: Any) -> dict[str, Any]:
|
|
2413
2712
|
if is_dataclass(result):
|
|
2414
2713
|
return asdict(result)
|
|
@@ -2447,6 +2746,7 @@ def _dag_result_has_findings(result: Any) -> bool:
|
|
|
2447
2746
|
def _dag_result_details(result: Any) -> list[str]:
|
|
2448
2747
|
details: list[str] = []
|
|
2449
2748
|
for key in (
|
|
2749
|
+
"message",
|
|
2450
2750
|
"missing_impl_files",
|
|
2451
2751
|
"orphan_edges",
|
|
2452
2752
|
"dangling_refs",
|