codd-dev 1.22.0__tar.gz → 1.24.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.22.0 → codd_dev-1.24.0}/PKG-INFO +32 -5
- {codd_dev-1.22.0 → codd_dev-1.24.0}/README.md +30 -4
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/__init__.py +1 -1
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/cli.py +118 -18
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/coherence_engine.py +0 -1
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/coverage_metrics.py +2 -18
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/__init__.py +1 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/builder.py +310 -14
- codd_dev-1.24.0/codd/dag/checks/deployment_completeness.py +615 -0
- codd_dev-1.24.0/codd/dag/defaults/test_frameworks.yaml +7 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/defaults/web.yaml +10 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/runner.py +1 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/deployer.py +190 -118
- codd_dev-1.24.0/codd/deployment/__init__.py +67 -0
- codd_dev-1.24.0/codd/deployment/checks/__init__.py +21 -0
- codd_dev-1.24.0/codd/deployment/defaults/deploy_targets.yaml +5 -0
- codd_dev-1.24.0/codd/deployment/defaults/schema_providers.yaml +6 -0
- codd_dev-1.24.0/codd/deployment/defaults/verification_templates.yaml +7 -0
- codd_dev-1.24.0/codd/deployment/extractor.py +679 -0
- codd_dev-1.24.0/codd/deployment/providers/__init__.py +105 -0
- codd_dev-1.24.0/codd/deployment/providers/schema/__init__.py +0 -0
- codd_dev-1.24.0/codd/deployment/providers/schema/prisma.py +195 -0
- codd_dev-1.24.0/codd/deployment/providers/target/__init__.py +7 -0
- codd_dev-1.24.0/codd/deployment/providers/target/docker_compose.py +197 -0
- codd_dev-1.24.0/codd/deployment/providers/verification/__init__.py +1 -0
- codd_dev-1.24.0/codd/deployment/providers/verification/curl.py +92 -0
- codd_dev-1.24.0/codd/deployment/providers/verification/playwright.py +91 -0
- codd_dev-1.24.0/codd/hooks/recipes/claude_settings_example.json +15 -0
- codd_dev-1.24.0/codd/hooks/recipes/codex_hook.sh +31 -0
- codd_dev-1.24.0/codd/hooks/recipes/git_post_commit.sh +15 -0
- codd_dev-1.24.0/codd/hooks/recipes/git_pre_commit.sh +15 -0
- codd_dev-1.24.0/codd/watch/__init__.py +1 -0
- codd_dev-1.24.0/codd/watch/events.py +43 -0
- codd_dev-1.24.0/codd/watch/propagation_log.py +40 -0
- codd_dev-1.24.0/codd/watch/propagation_pipeline.py +233 -0
- codd_dev-1.24.0/codd/watch/test_runner.py +187 -0
- codd_dev-1.24.0/codd/watch/watcher.py +112 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/pyproject.toml +4 -1
- codd_dev-1.22.0/codd/drift_linkers/__init__.py +0 -46
- codd_dev-1.22.0/codd/drift_linkers/api.py +0 -484
- codd_dev-1.22.0/codd/drift_linkers/defaults/cli.yaml +0 -1
- codd_dev-1.22.0/codd/drift_linkers/defaults/iot.yaml +0 -1
- codd_dev-1.22.0/codd/drift_linkers/defaults/mobile.yaml +0 -2
- codd_dev-1.22.0/codd/drift_linkers/defaults/web.yaml +0 -8
- codd_dev-1.22.0/codd/drift_linkers/schema.py +0 -262
- codd_dev-1.22.0/codd/drift_linkers/screen_flow.py +0 -171
- {codd_dev-1.22.0 → codd_dev-1.24.0}/.gitignore +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/LICENSE +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/__main__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/_git_helper.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/ask_user_question_adapter.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/assembler.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/bridge.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/clustering.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/coherence_adapters.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/config.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/contracts.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/coverage_auditor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/__init__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/depends_on_consistency.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/edge_validity.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/node_completeness.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/task_completion.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/checks/transitive_closure.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/defaults/cli.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/defaults/iot.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/defaults/mobile.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/dag/extractor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/defaults.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/deploy_targets/__init__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/deploy_targets/app_service.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/deploy_targets/base.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/deploy_targets/docker_compose.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/design_md.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/drift.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/e2e_extractor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/e2e_generator.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/e2e_runner.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/env_refs.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/extract_ai.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/extractor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixer.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixup_drift.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixup_drift_strategies/__init__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/generator.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/graph.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/hitl_session.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/implementer.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/inheritance.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/knowledge_fetcher.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/lexicon.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/mcp_server.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/measure.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/parsing.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/planner.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/policy.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/preflight/__init__.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/preflight/defaults/cli.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/preflight/defaults/iot.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/preflight/defaults/mobile.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/preflight/defaults/web.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/propagate.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/propagator.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/registry.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/repair_slice.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/require.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/require_plugins.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/require_propagate.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/required_artifacts/defaults/web.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/required_artifacts_deriver.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/requirement_completeness_auditor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/restore.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/routes_extractor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/scanner.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/screen_flow_validator.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/screen_transition_extractor.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/screen_transitions/defaults.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/synth.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/codd.yaml.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/lexicon_schema.yaml +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/traceability.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/validator.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.0}/codd/wiring.py +0 -0
- {codd_dev-1.22.0 → codd_dev-1.24.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.24.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
|
|
@@ -19,6 +19,7 @@ Requires-Dist: click>=8.0
|
|
|
19
19
|
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
|
+
Requires-Dist: watchdog>=4.0.0
|
|
22
23
|
Provides-Extra: ai
|
|
23
24
|
Provides-Extra: api-parsers
|
|
24
25
|
Requires-Dist: graphql-core>=3.2.0; extra == 'api-parsers'
|
|
@@ -950,23 +951,49 @@ You: yes
|
|
|
950
951
|
|
|
951
952
|
### Hook Integration — Set It Once, Never Think Again
|
|
952
953
|
|
|
953
|
-
|
|
954
|
+
CoDD ships copyable hook recipes in `codd/hooks/recipes/` so Claude, Codex, and Git can all trigger the same change-driven propagation path:
|
|
955
|
+
`codd propagate-from --files <changed-file>`.
|
|
956
|
+
|
|
957
|
+
#### Claude PostToolUse
|
|
958
|
+
|
|
959
|
+
Use `codd/hooks/recipes/claude_settings_example.json` as the starting point for `.claude/settings.json`. It listens for Edit / Write / MultiEdit and extracts changed files from `TOOL_INPUT` before calling `propagate-from`.
|
|
954
960
|
|
|
955
961
|
```json
|
|
956
962
|
{
|
|
957
963
|
"hooks": {
|
|
958
964
|
"PostToolUse": [{
|
|
959
|
-
"matcher": "Edit|Write",
|
|
965
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
960
966
|
"hooks": [{
|
|
961
967
|
"type": "command",
|
|
962
|
-
"command": "codd
|
|
968
|
+
"command": "python -m codd propagate-from --files <changed-file> --source editor_hook --editor claude"
|
|
963
969
|
}]
|
|
964
970
|
}]
|
|
965
971
|
}
|
|
966
972
|
}
|
|
967
973
|
```
|
|
968
974
|
|
|
969
|
-
|
|
975
|
+
#### Codex Post-Edit
|
|
976
|
+
|
|
977
|
+
Use `codd/hooks/recipes/codex_hook.sh` when your Codex wrapper can pass edited files through `CODEX_EDITED_FILES`:
|
|
978
|
+
|
|
979
|
+
```bash
|
|
980
|
+
export CODEX_EDITED_FILES="src/app.ts,docs/design/api.md"
|
|
981
|
+
bash codd/hooks/recipes/codex_hook.sh
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
#### Git Hooks
|
|
985
|
+
|
|
986
|
+
Use the git recipes as a final catch for manual edits and non-editor workflows:
|
|
987
|
+
|
|
988
|
+
```bash
|
|
989
|
+
cp codd/hooks/recipes/git_pre_commit.sh .git/hooks/pre-commit
|
|
990
|
+
cp codd/hooks/recipes/git_post_commit.sh .git/hooks/post-commit
|
|
991
|
+
chmod +x .git/hooks/pre-commit .git/hooks/post-commit
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
The pre-commit hook runs `propagate-from` in `--dry-run` mode against staged files. The post-commit hook runs propagation against the committed file set.
|
|
995
|
+
|
|
996
|
+
With hooks active, your entire workflow becomes: **edit files normally, and CoDD propagates the change from the files that actually changed.** The graph maintenance is invisible.
|
|
970
997
|
|
|
971
998
|
### Available Skills
|
|
972
999
|
|
|
@@ -912,23 +912,49 @@ You: yes
|
|
|
912
912
|
|
|
913
913
|
### Hook Integration — Set It Once, Never Think Again
|
|
914
914
|
|
|
915
|
-
|
|
915
|
+
CoDD ships copyable hook recipes in `codd/hooks/recipes/` so Claude, Codex, and Git can all trigger the same change-driven propagation path:
|
|
916
|
+
`codd propagate-from --files <changed-file>`.
|
|
917
|
+
|
|
918
|
+
#### Claude PostToolUse
|
|
919
|
+
|
|
920
|
+
Use `codd/hooks/recipes/claude_settings_example.json` as the starting point for `.claude/settings.json`. It listens for Edit / Write / MultiEdit and extracts changed files from `TOOL_INPUT` before calling `propagate-from`.
|
|
916
921
|
|
|
917
922
|
```json
|
|
918
923
|
{
|
|
919
924
|
"hooks": {
|
|
920
925
|
"PostToolUse": [{
|
|
921
|
-
"matcher": "Edit|Write",
|
|
926
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
922
927
|
"hooks": [{
|
|
923
928
|
"type": "command",
|
|
924
|
-
"command": "codd
|
|
929
|
+
"command": "python -m codd propagate-from --files <changed-file> --source editor_hook --editor claude"
|
|
925
930
|
}]
|
|
926
931
|
}]
|
|
927
932
|
}
|
|
928
933
|
}
|
|
929
934
|
```
|
|
930
935
|
|
|
931
|
-
|
|
936
|
+
#### Codex Post-Edit
|
|
937
|
+
|
|
938
|
+
Use `codd/hooks/recipes/codex_hook.sh` when your Codex wrapper can pass edited files through `CODEX_EDITED_FILES`:
|
|
939
|
+
|
|
940
|
+
```bash
|
|
941
|
+
export CODEX_EDITED_FILES="src/app.ts,docs/design/api.md"
|
|
942
|
+
bash codd/hooks/recipes/codex_hook.sh
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
#### Git Hooks
|
|
946
|
+
|
|
947
|
+
Use the git recipes as a final catch for manual edits and non-editor workflows:
|
|
948
|
+
|
|
949
|
+
```bash
|
|
950
|
+
cp codd/hooks/recipes/git_pre_commit.sh .git/hooks/pre-commit
|
|
951
|
+
cp codd/hooks/recipes/git_post_commit.sh .git/hooks/post-commit
|
|
952
|
+
chmod +x .git/hooks/pre-commit .git/hooks/post-commit
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
The pre-commit hook runs `propagate-from` in `--dry-run` mode against staged files. The post-commit hook runs propagation against the committed file set.
|
|
956
|
+
|
|
957
|
+
With hooks active, your entire workflow becomes: **edit files normally, and CoDD propagates the change from the files that actually changed.** The graph maintenance is invisible.
|
|
932
958
|
|
|
933
959
|
### Available Skills
|
|
934
960
|
|
|
@@ -344,23 +344,65 @@ def scan(path: str):
|
|
|
344
344
|
run_scan(project_root, codd_dir)
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
@main.command()
|
|
348
|
-
@click.option("--diff", default="HEAD", help="Git diff target (default: HEAD, shows uncommitted changes)")
|
|
349
|
-
@click.option("--path", default=".", help="Project root directory")
|
|
350
|
-
@click.option("--output", default=None, help="Output file (default: stdout)")
|
|
351
|
-
def impact(diff: str, path: str, output: str):
|
|
347
|
+
@main.command()
|
|
348
|
+
@click.option("--diff", default="HEAD", help="Git diff target (default: HEAD, shows uncommitted changes)")
|
|
349
|
+
@click.option("--path", default=".", help="Project root directory")
|
|
350
|
+
@click.option("--output", default=None, help="Output file (default: stdout)")
|
|
351
|
+
def impact(diff: str, path: str, output: str):
|
|
352
352
|
"""Analyze change impact from git diff."""
|
|
353
353
|
from codd.propagate import run_impact
|
|
354
354
|
project_root = Path(path).resolve()
|
|
355
355
|
codd_dir = _require_codd_dir(project_root)
|
|
356
|
-
|
|
357
|
-
run_impact(project_root, codd_dir, diff, output)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
@main.command()
|
|
361
|
-
@click.option("--
|
|
362
|
-
@click.option("--
|
|
363
|
-
@click.option("--
|
|
356
|
+
|
|
357
|
+
run_impact(project_root, codd_dir, diff, output)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@main.command("watch")
|
|
361
|
+
@click.option("--project-path", "--path", default=".", show_default=True, help="Project root directory")
|
|
362
|
+
@click.option("--debounce", default=500, show_default=True, type=int, help="Debounce interval in milliseconds")
|
|
363
|
+
@click.option("--background", is_flag=True, default=False, help="Run watcher in background mode")
|
|
364
|
+
@click.option("--status", is_flag=True, default=False, help="Show watcher status")
|
|
365
|
+
def watch_cmd(project_path: str, debounce: int, background: bool, status: bool) -> None:
|
|
366
|
+
"""Watch for file changes and emit CDAP file-change events."""
|
|
367
|
+
project_root = Path(project_path).resolve()
|
|
368
|
+
pid_file = project_root / ".codd" / "watch.pid"
|
|
369
|
+
|
|
370
|
+
if status:
|
|
371
|
+
if pid_file.exists():
|
|
372
|
+
click.echo(f"Watcher running (PID: {pid_file.read_text(encoding='utf-8').strip()})")
|
|
373
|
+
else:
|
|
374
|
+
click.echo("Watcher not running")
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
from codd.watch.events import FileChangeEvent
|
|
378
|
+
from codd.watch.watcher import start_watch
|
|
379
|
+
|
|
380
|
+
if not project_root.exists():
|
|
381
|
+
click.echo(f"Error: Project path does not exist: {project_root}")
|
|
382
|
+
raise SystemExit(1)
|
|
383
|
+
if not project_root.is_dir():
|
|
384
|
+
click.echo(f"Error: Project path is not a directory: {project_root}")
|
|
385
|
+
raise SystemExit(1)
|
|
386
|
+
|
|
387
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
388
|
+
pid_file.write_text(f"{os.getpid()}\n", encoding="utf-8")
|
|
389
|
+
|
|
390
|
+
def on_change(event: FileChangeEvent) -> None:
|
|
391
|
+
preview = ", ".join(event.files[:3])
|
|
392
|
+
suffix = "" if len(event.files) <= 3 else f", ... {len(event.files) - 3} more"
|
|
393
|
+
click.echo(f"[watch] {len(event.files)} file(s) changed: {preview}{suffix}")
|
|
394
|
+
|
|
395
|
+
click.echo(f"Watching {project_root} (debounce={debounce}ms)")
|
|
396
|
+
observer = start_watch(project_root, on_change, debounce_ms=debounce, background=background)
|
|
397
|
+
if background:
|
|
398
|
+
click.echo(f"Watcher running in background mode (PID: {os.getpid()})")
|
|
399
|
+
observer.join(timeout=0)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@main.command()
|
|
403
|
+
@click.option("--wave", required=True, type=click.IntRange(min=1), help="Wave number to generate")
|
|
404
|
+
@click.option("--path", default=".", help="Project root directory")
|
|
405
|
+
@click.option("--force", is_flag=True, help="Overwrite existing files")
|
|
364
406
|
@click.option(
|
|
365
407
|
"--ai-cmd",
|
|
366
408
|
default=None,
|
|
@@ -852,11 +894,47 @@ def propagate(diff: str, path: str, update: bool, verify: bool, do_commit: bool,
|
|
|
852
894
|
click.echo(f" [{status}] {doc.path} ({doc.node_id})")
|
|
853
895
|
click.echo(f" modules: {', '.join(doc.matched_modules)}")
|
|
854
896
|
|
|
855
|
-
if not update and result.affected_docs:
|
|
856
|
-
click.echo(f"\nRun with --update to update these docs via AI.")
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
@main.command()
|
|
897
|
+
if not update and result.affected_docs:
|
|
898
|
+
click.echo(f"\nRun with --update to update these docs via AI.")
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@main.command("propagate-from")
|
|
902
|
+
@click.option("--project-path", default=".", show_default=True, help="Project root directory")
|
|
903
|
+
@click.option("--files", multiple=True, required=True, help="Changed file path. Can be repeated.")
|
|
904
|
+
@click.option(
|
|
905
|
+
"--source",
|
|
906
|
+
default="manual",
|
|
907
|
+
show_default=True,
|
|
908
|
+
type=click.Choice(["watch", "git_hook", "editor_hook", "manual"]),
|
|
909
|
+
help="Change source that triggered propagation.",
|
|
910
|
+
)
|
|
911
|
+
@click.option(
|
|
912
|
+
"--editor",
|
|
913
|
+
default=None,
|
|
914
|
+
type=click.Choice(["claude", "codex", "manual"]),
|
|
915
|
+
help="Editor that produced the change, when known.",
|
|
916
|
+
)
|
|
917
|
+
@click.option("--dry-run", is_flag=True, default=False, help="Compute impact without propagate/fix/log writes.")
|
|
918
|
+
def propagate_from(project_path: str, files: tuple[str, ...], source: str, editor: str | None, dry_run: bool):
|
|
919
|
+
"""Run the CDAP propagation pipeline from changed files."""
|
|
920
|
+
from codd.watch.events import FileChangeEvent
|
|
921
|
+
from codd.watch.propagation_pipeline import run_propagation_pipeline
|
|
922
|
+
|
|
923
|
+
project_root = Path(project_path).resolve()
|
|
924
|
+
event = FileChangeEvent(files=list(files), source=source, editor=editor)
|
|
925
|
+
result = run_propagation_pipeline(project_root, list(files), dry_run=dry_run, event=event)
|
|
926
|
+
|
|
927
|
+
click.echo(f"Impacted nodes: {len(result.impacted_nodes)}")
|
|
928
|
+
click.echo(f"Propagated: {result.propagated_count}")
|
|
929
|
+
click.echo(f"Fixed: {result.fixed_count}")
|
|
930
|
+
if result.errors:
|
|
931
|
+
click.echo(f"Errors: {result.errors}", err=True)
|
|
932
|
+
|
|
933
|
+
if not result.success:
|
|
934
|
+
raise SystemExit(1)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
@main.command()
|
|
860
938
|
@click.option("--path", default=".", help="Project root directory")
|
|
861
939
|
@click.option("--task", default=None, help="Generate only one task by task ID or title match")
|
|
862
940
|
@click.option("--clean", is_flag=True, default=False, help="Remove existing generated output before re-generating")
|
|
@@ -2183,6 +2261,28 @@ def mcp_server(project: str):
|
|
|
2183
2261
|
run_stdio(project_root)
|
|
2184
2262
|
|
|
2185
2263
|
|
|
2264
|
+
@main.command("test")
|
|
2265
|
+
@click.option("--project-path", "--path", default=".", show_default=True, help="Project root directory")
|
|
2266
|
+
@click.option("--related", multiple=True, help="Run only tests related to these files")
|
|
2267
|
+
@click.option("--dry-run", is_flag=True, default=False, help="Print the related test command without running it")
|
|
2268
|
+
def test_cmd(project_path: str, related: tuple[str, ...], dry_run: bool):
|
|
2269
|
+
"""Run tests. Use --related <file> to run only related tests."""
|
|
2270
|
+
from codd.watch.test_runner import run_related_tests
|
|
2271
|
+
|
|
2272
|
+
project_root = Path(project_path).resolve()
|
|
2273
|
+
if not related:
|
|
2274
|
+
click.echo("Use --related <file> to specify files. Full test run not supported via this command.")
|
|
2275
|
+
return
|
|
2276
|
+
|
|
2277
|
+
result = run_related_tests(project_root, list(related), dry_run=dry_run)
|
|
2278
|
+
click.echo(f"Related tests: {result['related']}")
|
|
2279
|
+
if result.get("cmd"):
|
|
2280
|
+
click.echo(f"Command: {result['cmd']}")
|
|
2281
|
+
click.echo(f"Status: {result['status']}")
|
|
2282
|
+
if result.get("exit_code") not in (None, 0):
|
|
2283
|
+
raise SystemExit(1)
|
|
2284
|
+
|
|
2285
|
+
|
|
2186
2286
|
@main.group()
|
|
2187
2287
|
def dag():
|
|
2188
2288
|
"""DAG Completeness Gate commands."""
|
|
@@ -80,7 +80,6 @@ def set_coherence_bus(bus: EventBus | None) -> None:
|
|
|
80
80
|
"""Set the opt-in coherence bus on detectors that publish DriftEvents."""
|
|
81
81
|
for module_name in (
|
|
82
82
|
"codd.drift",
|
|
83
|
-
"codd.drift_linkers.api",
|
|
84
83
|
"codd.deployer",
|
|
85
84
|
"codd.hitl_session",
|
|
86
85
|
"codd.validator",
|
|
@@ -134,7 +134,7 @@ def compute_screen_flow_coverage(
|
|
|
134
134
|
config: dict[str, Any],
|
|
135
135
|
threshold: float = 100.0,
|
|
136
136
|
) -> CoverageResult:
|
|
137
|
-
"""Measure screen-flow drift as a coverage gate metric."""
|
|
137
|
+
"""Measure screen-flow route drift as a coverage gate metric."""
|
|
138
138
|
|
|
139
139
|
try:
|
|
140
140
|
from codd.cli import CoddCLIError
|
|
@@ -153,25 +153,9 @@ def compute_screen_flow_coverage(
|
|
|
153
153
|
details=[f"error: {exc}"],
|
|
154
154
|
)
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
design_drift_count = 0
|
|
158
|
-
try:
|
|
159
|
-
from codd.drift_linkers.screen_flow import ScreenFlowGate
|
|
160
|
-
|
|
161
|
-
gate_result = ScreenFlowGate(
|
|
162
|
-
project_root=project_root,
|
|
163
|
-
settings={**config, "apply": True},
|
|
164
|
-
).run()
|
|
165
|
-
if not gate_result.skipped:
|
|
166
|
-
design_drift_count = gate_result.drift_count
|
|
167
|
-
design_drift_details = gate_result.details
|
|
168
|
-
except Exception as exc: # pragma: no cover - defensive gate behavior
|
|
169
|
-
return _exception_result("screen_flow_coverage", threshold, exc)
|
|
170
|
-
|
|
171
|
-
drift_count = len(drifts) + design_drift_count
|
|
156
|
+
drift_count = len(drifts)
|
|
172
157
|
pct = 100.0 if drift_count == 0 else max(0.0, 100.0 - drift_count * 10.0)
|
|
173
158
|
details = [f"drift_count: {drift_count}"]
|
|
174
|
-
details.extend(design_drift_details)
|
|
175
159
|
return CoverageResult(
|
|
176
160
|
metric="screen_flow_coverage",
|
|
177
161
|
total=1,
|