codd-dev 1.5.0__tar.gz → 1.6.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.5.0 → codd_dev-1.6.0}/PKG-INFO +5 -1
- {codd_dev-1.5.0 → codd_dev-1.6.0}/README.md +3 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/__init__.py +1 -1
- codd_dev-1.6.0/codd/bridge.py +83 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/cli.py +140 -137
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/extractor.py +11 -3
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/mcp_server.py +17 -26
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/measure.py +18 -13
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/policy.py +14 -1
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/require_plugins.py +10 -60
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/codd.yaml.tmpl +2 -2
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/validator.py +46 -2
- {codd_dev-1.5.0 → codd_dev-1.6.0}/pyproject.toml +10 -9
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/build-1.4.2.dist-info/licenses/LICENSE +0 -20
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/certifi-2026.2.25.dist-info/licenses/LICENSE +0 -20
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/cffi-2.0.0.dist-info/licenses/LICENSE +0 -23
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/charset_normalizer-3.4.7.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/cryptography-46.0.6.dist-info/licenses/LICENSE +0 -3
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/id-1.6.1.dist-info/licenses/LICENSE +0 -202
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/iniconfig-2.3.0.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/jaraco.classes-3.4.0.dist-info/LICENSE +0 -17
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/jaraco_context-6.1.2.dist-info/licenses/LICENSE +0 -18
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/jaraco_functools-4.4.0.dist-info/licenses/LICENSE +0 -18
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/jeepney-0.9.0.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/keyring-25.7.0.dist-info/licenses/LICENSE +0 -18
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/markdown_it_py-4.0.0.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/mdurl-0.1.2.dist-info/LICENSE +0 -46
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/more_itertools-11.0.1.dist-info/licenses/LICENSE +0 -19
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/nh3-0.3.4.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/packaging-26.0.dist-info/licenses/LICENSE +0 -3
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/certifi/LICENSE +0 -20
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/distro/LICENSE +0 -202
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/packaging/LICENSE +0 -3
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/pkg_resources/LICENSE +0 -17
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/platformdirs/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/pygments/LICENSE +0 -25
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/requests/LICENSE +0 -175
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/resolvelib/LICENSE +0 -13
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/rich/LICENSE +0 -19
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/tomli/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/tomli_w/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip/_vendor/truststore/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +0 -20
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/distro/LICENSE +0 -202
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +0 -3
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -17
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +0 -25
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/requests/LICENSE +0 -175
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -13
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/rich/LICENSE +0 -19
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pip-26.0.1.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pycparser-3.0.dist-info/licenses/LICENSE +0 -27
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pygments-2.20.0.dist-info/licenses/LICENSE +0 -25
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pyproject_hooks-1.2.0.dist-info/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pytest-9.0.2.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/pyyaml-6.0.3.dist-info/licenses/LICENSE +0 -20
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/readme_renderer-44.0.dist-info/LICENSE +0 -174
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/requests-2.33.1.dist-info/licenses/LICENSE +0 -175
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/requests_toolbelt-1.0.0.dist-info/LICENSE +0 -13
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/rfc3986-2.0.0.dist-info/LICENSE +0 -13
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/rich-14.3.3.dist-info/licenses/LICENSE +0 -19
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/secretstorage-3.5.0.dist-info/licenses/LICENSE +0 -25
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/tree_sitter-0.25.2.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/tree_sitter_java-0.23.5.dist-info/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/tree_sitter_python-0.25.0.dist-info/licenses/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/tree_sitter_typescript-0.23.2.dist-info/LICENSE +0 -21
- codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/twine-6.2.0.dist-info/licenses/LICENSE +0 -174
- codd_dev-1.5.0/LICENSE +0 -21
- codd_dev-1.5.0/codd/audit.py +0 -354
- codd_dev-1.5.0/codd/reviewer.py +0 -342
- codd_dev-1.5.0/codd/risk.py +0 -100
- codd_dev-1.5.0/codd/verifier.py +0 -679
- {codd_dev-1.5.0 → codd_dev-1.6.0}/.gitignore +0 -0
- {codd_dev-1.5.0/.venv-scanonly-209d2/lib/python3.12/site-packages/codd_dev-0.2.0a5.dist-info/licenses → codd_dev-1.6.0}/LICENSE +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/assembler.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/clustering.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/config.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/contracts.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/defaults.yaml +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/env_refs.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/extract_ai.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/generator.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/graph.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/hooks/__init__.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/hooks/pre-commit +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/implementer.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/inheritance.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/parsing.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/planner.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/propagate.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/propagator.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/require.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/restore.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/scanner.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/schema_refs.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/synth.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/conventions.yaml.tmpl +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/doc_links.yaml.tmpl +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/extracted/system-context.md.j2 +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/gitignore.tmpl +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/templates/overrides.yaml.tmpl +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/traceability.py +0 -0
- {codd_dev-1.5.0 → codd_dev-1.6.0}/codd/wiring.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codd-dev
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
+
Provides-Extra: ai
|
|
22
23
|
Provides-Extra: api-parsers
|
|
23
24
|
Requires-Dist: graphql-core>=3.2.0; extra == 'api-parsers'
|
|
24
25
|
Provides-Extra: infra
|
|
@@ -262,6 +263,8 @@ All other commands (`scan`, `impact`, `generate`, etc.) automatically discover w
|
|
|
262
263
|
|
|
263
264
|
Already have a codebase? CoDD provides a full brownfield workflow — from code extraction to design doc reconstruction.
|
|
264
265
|
|
|
266
|
+
Full walkthrough: [Harness as Code — A Guide to CoDD #2 Brownfield](https://zenn.dev/shio_shoppaize/articles/shogun-codd-brownfield?locale=en)
|
|
267
|
+
|
|
265
268
|
### AI-Powered Extraction (--ai)
|
|
266
269
|
|
|
267
270
|
> **Note on presets**: `codd extract --ai` ships with a **baseline** extraction prompt. The extraction quality in published benchmarks (F1 0.953+) was achieved with a tuned preset and internal evaluation dataset — not the public baseline. The baseline uses the same workflow and output format, but results will vary depending on your codebase and prompt. Use `--prompt-file` to supply your own tuned prompt.
|
|
@@ -652,6 +655,7 @@ If CoDD can't manage itself, it shouldn't manage your project.
|
|
|
652
655
|
- [dev.to: Harness as Code — Treating AI Workflows Like Infrastructure](https://dev.to/yohey-w/harness-as-code-treating-ai-workflows-like-infrastructure-27ni)
|
|
653
656
|
- [dev.to: What Happens After "Spec First"](https://dev.to/yohey-w/codd-coherence-driven-development-what-happens-after-spec-first-514f)
|
|
654
657
|
- [Zenn: Harness as Code — A Guide to CoDD #1 spec → design → code](https://zenn.dev/shio_shoppaize/articles/codd-greenfield-guide?locale=en)
|
|
658
|
+
- [Zenn: Harness as Code — A Guide to CoDD #2 Brownfield](https://zenn.dev/shio_shoppaize/articles/shogun-codd-brownfield?locale=en)
|
|
655
659
|
- [Zenn: CoDD deep-dive](https://zenn.dev/shio_shoppaize/articles/shogun-codd-coherence?locale=en)
|
|
656
660
|
|
|
657
661
|
## License
|
|
@@ -225,6 +225,8 @@ All other commands (`scan`, `impact`, `generate`, etc.) automatically discover w
|
|
|
225
225
|
|
|
226
226
|
Already have a codebase? CoDD provides a full brownfield workflow — from code extraction to design doc reconstruction.
|
|
227
227
|
|
|
228
|
+
Full walkthrough: [Harness as Code — A Guide to CoDD #2 Brownfield](https://zenn.dev/shio_shoppaize/articles/shogun-codd-brownfield?locale=en)
|
|
229
|
+
|
|
228
230
|
### AI-Powered Extraction (--ai)
|
|
229
231
|
|
|
230
232
|
> **Note on presets**: `codd extract --ai` ships with a **baseline** extraction prompt. The extraction quality in published benchmarks (F1 0.953+) was achieved with a tuned preset and internal evaluation dataset — not the public baseline. The baseline uses the same workflow and output format, but results will vary depending on your codebase and prompt. Use `--prompt-file` to supply your own tuned prompt.
|
|
@@ -615,6 +617,7 @@ If CoDD can't manage itself, it shouldn't manage your project.
|
|
|
615
617
|
- [dev.to: Harness as Code — Treating AI Workflows Like Infrastructure](https://dev.to/yohey-w/harness-as-code-treating-ai-workflows-like-infrastructure-27ni)
|
|
616
618
|
- [dev.to: What Happens After "Spec First"](https://dev.to/yohey-w/codd-coherence-driven-development-what-happens-after-spec-first-514f)
|
|
617
619
|
- [Zenn: Harness as Code — A Guide to CoDD #1 spec → design → code](https://zenn.dev/shio_shoppaize/articles/codd-greenfield-guide?locale=en)
|
|
620
|
+
- [Zenn: Harness as Code — A Guide to CoDD #2 Brownfield](https://zenn.dev/shio_shoppaize/articles/shogun-codd-brownfield?locale=en)
|
|
618
621
|
- [Zenn: CoDD deep-dive](https://zenn.dev/shio_shoppaize/articles/shogun-codd-coherence?locale=en)
|
|
619
622
|
|
|
620
623
|
## License
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Bridge helpers for optional codd-pro extensions and plugin registration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from importlib.metadata import entry_points
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PLUGIN_GROUP = "codd.plugins"
|
|
11
|
+
PRO_COMMAND_INSTALL_MESSAGE = (
|
|
12
|
+
"このコマンドは codd-pro に移動しました。"
|
|
13
|
+
"pip install codd-pro でインストールできます。"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class BridgeRegistry:
|
|
19
|
+
"""Mutable registry populated by entry-point plugins."""
|
|
20
|
+
|
|
21
|
+
require_plugin: Any | None = None
|
|
22
|
+
validator_handler: Callable[..., Any] | None = None
|
|
23
|
+
policy_handler: Callable[..., Any] | None = None
|
|
24
|
+
risk_builder: Callable[..., Any] | None = None
|
|
25
|
+
command_handlers: dict[str, Callable[..., Any]] = field(default_factory=dict)
|
|
26
|
+
mcp_tools: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
27
|
+
mcp_handlers: dict[str, Callable[..., Any]] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def register_require_plugin(self, plugin: Any) -> None:
|
|
30
|
+
self.require_plugin = plugin
|
|
31
|
+
|
|
32
|
+
def register_validator(self, handler: Callable[..., Any]) -> None:
|
|
33
|
+
self.validator_handler = handler
|
|
34
|
+
|
|
35
|
+
def register_policy(self, handler: Callable[..., Any]) -> None:
|
|
36
|
+
self.policy_handler = handler
|
|
37
|
+
|
|
38
|
+
def register_risk_builder(self, handler: Callable[..., Any]) -> None:
|
|
39
|
+
self.risk_builder = handler
|
|
40
|
+
|
|
41
|
+
def register_command(self, name: str, handler: Callable[..., Any]) -> None:
|
|
42
|
+
self.command_handlers[name] = handler
|
|
43
|
+
|
|
44
|
+
def register_mcp_tool(self, tool: dict[str, Any], handler: Callable[..., Any]) -> None:
|
|
45
|
+
name = str(tool.get("name") or "")
|
|
46
|
+
if not name:
|
|
47
|
+
raise ValueError("MCP tool registration requires a non-empty tool name")
|
|
48
|
+
self.mcp_tools[name] = tool
|
|
49
|
+
self.mcp_handlers[name] = handler
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _iter_plugin_entry_points():
|
|
53
|
+
try:
|
|
54
|
+
return tuple(entry_points(group=PLUGIN_GROUP))
|
|
55
|
+
except TypeError:
|
|
56
|
+
return tuple(entry_points().select(group=PLUGIN_GROUP))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_bridge_registry() -> BridgeRegistry:
|
|
60
|
+
"""Load all registered bridge plugins, ignoring broken extensions."""
|
|
61
|
+
registry = BridgeRegistry()
|
|
62
|
+
|
|
63
|
+
for plugin_entry in _iter_plugin_entry_points():
|
|
64
|
+
try:
|
|
65
|
+
plugin = plugin_entry.load()
|
|
66
|
+
except Exception:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
register = getattr(plugin, "register", plugin)
|
|
70
|
+
if not callable(register):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
register(registry)
|
|
75
|
+
except Exception:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
return registry
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_command_handler(name: str) -> Callable[..., Any] | None:
|
|
82
|
+
"""Return the registered handler for a Pro-only CLI command."""
|
|
83
|
+
return load_bridge_registry().command_handlers.get(name)
|
|
@@ -4,11 +4,24 @@ import click
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import shutil
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from codd.
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from codd.bridge import PRO_COMMAND_INSTALL_MESSAGE, get_command_handler
|
|
10
|
+
from codd.config import find_codd_dir
|
|
11
|
+
|
|
12
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _run_pro_command(name: str, **kwargs):
|
|
16
|
+
"""Dispatch a Pro-only command when the bridge plugin is installed."""
|
|
17
|
+
handler = get_command_handler(name)
|
|
18
|
+
if handler is None:
|
|
19
|
+
click.echo(PRO_COMMAND_INSTALL_MESSAGE)
|
|
20
|
+
raise SystemExit(1)
|
|
21
|
+
|
|
22
|
+
result = handler(**kwargs)
|
|
23
|
+
if type(result) is int:
|
|
24
|
+
raise SystemExit(result)
|
|
12
25
|
|
|
13
26
|
|
|
14
27
|
def _require_codd_dir(project_root: Path) -> Path:
|
|
@@ -20,6 +33,64 @@ def _require_codd_dir(project_root: Path) -> Path:
|
|
|
20
33
|
return codd_dir
|
|
21
34
|
|
|
22
35
|
|
|
36
|
+
def _resolve_bootstrap_codd_dir(project_root: Path) -> Path:
|
|
37
|
+
"""Choose a config dir for brownfield bootstrap without clobbering source code."""
|
|
38
|
+
existing = find_codd_dir(project_root)
|
|
39
|
+
if existing is not None:
|
|
40
|
+
return existing
|
|
41
|
+
|
|
42
|
+
hidden_dir = project_root / ".codd"
|
|
43
|
+
default_dir = project_root / "codd"
|
|
44
|
+
if hidden_dir.exists():
|
|
45
|
+
return hidden_dir
|
|
46
|
+
if default_dir.exists():
|
|
47
|
+
# Avoid writing config into projects whose source package is already named codd/.
|
|
48
|
+
return hidden_dir
|
|
49
|
+
return default_dir
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _format_yaml_list(items: list[str], *, indent: int = 4) -> str:
|
|
53
|
+
"""Render a YAML list block for the simple template engine."""
|
|
54
|
+
if not items:
|
|
55
|
+
return " " * indent + "[]"
|
|
56
|
+
return "\n".join(f'{" " * indent}- "{item}"' for item in items)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _display_path(path: Path, project_root: Path) -> str:
|
|
60
|
+
try:
|
|
61
|
+
return path.relative_to(project_root).as_posix()
|
|
62
|
+
except ValueError:
|
|
63
|
+
return path.as_posix()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _ensure_bootstrap_codd_yaml(
|
|
67
|
+
project_root: Path,
|
|
68
|
+
*,
|
|
69
|
+
codd_dir: Path | None = None,
|
|
70
|
+
language: str,
|
|
71
|
+
source_dirs: list[str],
|
|
72
|
+
) -> tuple[Path, bool]:
|
|
73
|
+
"""Create a minimal codd.yaml after brownfield extract when none exists."""
|
|
74
|
+
codd_dir = codd_dir or _resolve_bootstrap_codd_dir(project_root)
|
|
75
|
+
config_path = codd_dir / "codd.yaml"
|
|
76
|
+
if config_path.exists():
|
|
77
|
+
return config_path, False
|
|
78
|
+
|
|
79
|
+
codd_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
codd_dir_name = _display_path(codd_dir, project_root)
|
|
81
|
+
_render_template(
|
|
82
|
+
"codd.yaml.tmpl",
|
|
83
|
+
config_path,
|
|
84
|
+
{
|
|
85
|
+
"project_name": project_root.name,
|
|
86
|
+
"language": language,
|
|
87
|
+
"source_dirs": _format_yaml_list(source_dirs),
|
|
88
|
+
"graph_path": f"{codd_dir_name}/scan",
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
return config_path, True
|
|
92
|
+
|
|
93
|
+
|
|
23
94
|
@click.group()
|
|
24
95
|
@click.version_option(package_name="codd-dev")
|
|
25
96
|
def main():
|
|
@@ -66,6 +137,8 @@ def init(project_name: str, language: str, dest: str, requirements: str | None,
|
|
|
66
137
|
_render_template("codd.yaml.tmpl", codd_dir / "codd.yaml", {
|
|
67
138
|
"project_name": project_name,
|
|
68
139
|
"language": language,
|
|
140
|
+
"source_dirs": _format_yaml_list(["src/"]),
|
|
141
|
+
"graph_path": f"{config_dir}/scan",
|
|
69
142
|
})
|
|
70
143
|
_render_template("gitignore.tmpl", codd_dir / ".gitignore", {})
|
|
71
144
|
|
|
@@ -380,54 +453,16 @@ def assemble(path: str, output_dir: str | None, ai_cmd: str | None):
|
|
|
380
453
|
@main.command()
|
|
381
454
|
@click.option("--path", default=".", help="Project root directory")
|
|
382
455
|
@click.option("--sprint", default=None, type=click.IntRange(min=1), help="Sprint number to verify")
|
|
383
|
-
def verify(path: str, sprint: int | None) -> None:
|
|
384
|
-
"""Run build + test verification and trace failures to design documents."""
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
project_root = Path(path).resolve()
|
|
388
|
-
codd_dir = _require_codd_dir(project_root)
|
|
389
|
-
|
|
390
|
-
try:
|
|
391
|
-
result = run_verify(project_root, sprint=sprint)
|
|
392
|
-
except VerifyPreflightError as exc:
|
|
393
|
-
click.echo(f"Preflight check failed: {exc}")
|
|
394
|
-
raise SystemExit(1)
|
|
395
|
-
except (FileNotFoundError, ValueError) as exc:
|
|
396
|
-
click.echo(f"Error: {exc}")
|
|
397
|
-
raise SystemExit(1)
|
|
398
|
-
|
|
399
|
-
if result.typecheck.success:
|
|
400
|
-
click.echo("Typecheck: PASS")
|
|
401
|
-
else:
|
|
402
|
-
click.echo(f"Typecheck: FAIL ({result.typecheck.error_count} errors)")
|
|
403
|
-
|
|
404
|
-
if result.tests.success:
|
|
405
|
-
click.echo(f"Tests: PASS ({result.tests.passed}/{result.tests.total})")
|
|
406
|
-
else:
|
|
407
|
-
click.echo(f"Tests: FAIL ({result.tests.failed} failed, {result.tests.passed} passed)")
|
|
408
|
-
|
|
409
|
-
if result.design_refs:
|
|
410
|
-
click.echo("\nDesign documents to review:")
|
|
411
|
-
for ref in result.design_refs:
|
|
412
|
-
click.echo(f" {ref.node_id} -> {ref.doc_path} (from {ref.source_file})")
|
|
413
|
-
propagate_targets = tuple(dict.fromkeys(ref.node_id for ref in result.design_refs))
|
|
414
|
-
if propagate_targets:
|
|
415
|
-
click.echo("\nSuggested propagate targets:")
|
|
416
|
-
for target in propagate_targets:
|
|
417
|
-
click.echo(f" {target}")
|
|
418
|
-
|
|
419
|
-
for warning in result.warnings:
|
|
420
|
-
click.echo(f"Warning: {warning}")
|
|
421
|
-
|
|
422
|
-
click.echo(f"\nReport: {result.report_path}")
|
|
423
|
-
raise SystemExit(0 if result.success else 1)
|
|
456
|
+
def verify(path: str, sprint: int | None) -> None:
|
|
457
|
+
"""Run build + test verification and trace failures to design documents."""
|
|
458
|
+
_run_pro_command("verify", path=path, sprint=sprint)
|
|
424
459
|
|
|
425
460
|
|
|
426
461
|
@main.command()
|
|
427
462
|
@click.option("--path", default=".", help="Project root directory")
|
|
428
463
|
@click.option("--language", default=None, help="Override language detection (python/typescript/javascript/go — full support; java — symbols only)")
|
|
429
464
|
@click.option("--source-dirs", default=None, help="Comma-separated source directories (default: auto-detect)")
|
|
430
|
-
@click.option("--output", default=None, help="Output directory (default:
|
|
465
|
+
@click.option("--output", default=None, help="Output directory (default: <config-dir>/extracted/)")
|
|
431
466
|
@click.option("--ai", is_flag=True, default=False, help="Use AI-powered extraction (6-layer MECE design docs)")
|
|
432
467
|
@click.option(
|
|
433
468
|
"--ai-cmd",
|
|
@@ -446,14 +481,19 @@ def extract(path: str, language: str | None, source_dirs: str | None, output: st
|
|
|
446
481
|
Default mode: static analysis (no AI, pure structural facts).
|
|
447
482
|
With --ai: AI-powered 6-layer MECE extraction using claude --print.
|
|
448
483
|
|
|
449
|
-
Output goes to
|
|
450
|
-
|
|
484
|
+
Output goes to the discovered CoDD config dir as draft documents
|
|
485
|
+
(`codd/extracted/` or `.codd/extracted/`). Review and promote
|
|
486
|
+
confirmed docs when ready.
|
|
451
487
|
"""
|
|
452
488
|
project_root = Path(path).resolve()
|
|
489
|
+
bootstrap_codd_dir = _resolve_bootstrap_codd_dir(project_root)
|
|
490
|
+
dirs = [d.strip() for d in source_dirs.split(",") if d.strip()] if source_dirs else None
|
|
491
|
+
output_path = Path(output) if output else bootstrap_codd_dir / "extracted"
|
|
453
492
|
|
|
454
493
|
if ai:
|
|
455
494
|
from codd.extract_ai import run_extract_ai
|
|
456
|
-
from codd.
|
|
495
|
+
from codd.extractor import extract_facts
|
|
496
|
+
from codd.config import load_project_config
|
|
457
497
|
|
|
458
498
|
# Resolve AI command
|
|
459
499
|
if ai_cmd is None:
|
|
@@ -471,39 +511,60 @@ def extract(path: str, language: str | None, source_dirs: str | None, output: st
|
|
|
471
511
|
click.echo(f"Scanning project...")
|
|
472
512
|
|
|
473
513
|
try:
|
|
474
|
-
result = run_extract_ai(project_root, ai_cmd,
|
|
514
|
+
result = run_extract_ai(project_root, ai_cmd, str(output_path), prompt_file=prompt_file)
|
|
475
515
|
except Exception as exc:
|
|
476
516
|
click.echo(f"Error: {exc}")
|
|
477
517
|
raise SystemExit(1)
|
|
478
518
|
|
|
519
|
+
facts = extract_facts(project_root, language, dirs)
|
|
520
|
+
config_path, generated_config = _ensure_bootstrap_codd_yaml(
|
|
521
|
+
project_root,
|
|
522
|
+
codd_dir=bootstrap_codd_dir,
|
|
523
|
+
language=facts.language,
|
|
524
|
+
source_dirs=facts.source_dirs,
|
|
525
|
+
)
|
|
526
|
+
output_display = _display_path(result.output_dir, project_root)
|
|
527
|
+
config_display = _display_path(config_path, project_root)
|
|
528
|
+
|
|
479
529
|
click.echo(f"\nExtracted: {result.module_count} source files analyzed")
|
|
480
|
-
click.echo(f"Output: {
|
|
530
|
+
click.echo(f"Output: {output_display}/")
|
|
481
531
|
for f in result.generated_files:
|
|
482
532
|
click.echo(f" {f.name}")
|
|
533
|
+
if generated_config:
|
|
534
|
+
click.echo(f"Generated: {config_display} (minimal brownfield config)")
|
|
483
535
|
|
|
484
536
|
click.echo(f"\nNext steps:")
|
|
485
|
-
click.echo(f" 1. Review generated docs in {
|
|
486
|
-
click.echo(f" 2. Promote confirmed docs: mv
|
|
537
|
+
click.echo(f" 1. Review generated docs in {output_display}/")
|
|
538
|
+
click.echo(f" 2. Promote confirmed docs: mv {output_display}/*.md docs/design/")
|
|
487
539
|
click.echo(f" 3. Run: codd scan (to build the dependency graph)")
|
|
488
540
|
else:
|
|
489
541
|
from codd.extractor import run_extract
|
|
490
542
|
|
|
491
|
-
dirs = [d.strip() for d in source_dirs.split(",")] if source_dirs else None
|
|
492
|
-
|
|
493
543
|
try:
|
|
494
|
-
result = run_extract(project_root, language, dirs,
|
|
544
|
+
result = run_extract(project_root, language, dirs, str(output_path))
|
|
495
545
|
except Exception as exc:
|
|
496
546
|
click.echo(f"Error: {exc}")
|
|
497
547
|
raise SystemExit(1)
|
|
498
548
|
|
|
549
|
+
config_path, generated_config = _ensure_bootstrap_codd_yaml(
|
|
550
|
+
project_root,
|
|
551
|
+
codd_dir=bootstrap_codd_dir,
|
|
552
|
+
language=result.language,
|
|
553
|
+
source_dirs=result.source_dirs,
|
|
554
|
+
)
|
|
555
|
+
output_display = _display_path(result.output_dir, project_root)
|
|
556
|
+
config_display = _display_path(config_path, project_root)
|
|
557
|
+
|
|
499
558
|
click.echo(f"Extracted: {result.module_count} modules from {result.total_files} files ({result.total_lines:,} lines)")
|
|
500
|
-
click.echo(f"Output: {
|
|
559
|
+
click.echo(f"Output: {output_display}/")
|
|
501
560
|
for f in result.generated_files:
|
|
502
561
|
click.echo(f" {f.relative_to(result.output_dir)}")
|
|
562
|
+
if generated_config:
|
|
563
|
+
click.echo(f"Generated: {config_display} (minimal brownfield config)")
|
|
503
564
|
|
|
504
565
|
click.echo(f"\nNext steps:")
|
|
505
|
-
click.echo(f" 1. Review generated docs in {
|
|
506
|
-
click.echo(f" 2. Promote confirmed docs: mv
|
|
566
|
+
click.echo(f" 1. Review generated docs in {output_display}/")
|
|
567
|
+
click.echo(f" 2. Promote confirmed docs: mv {output_display}/*.md docs/design/")
|
|
507
568
|
click.echo(f" 3. Run: codd scan (to build the dependency graph)")
|
|
508
569
|
|
|
509
570
|
|
|
@@ -516,7 +577,7 @@ def extract(path: str, language: str | None, source_dirs: str | None, output: st
|
|
|
516
577
|
default=None,
|
|
517
578
|
help="Override AI CLI command (defaults to codd.yaml ai_command)",
|
|
518
579
|
)
|
|
519
|
-
def review(path: str, scope: str | None, as_json: bool, ai_cmd: str | None):
|
|
580
|
+
def review(path: str, scope: str | None, as_json: bool, ai_cmd: str | None):
|
|
520
581
|
"""Review design documents for content quality using AI.
|
|
521
582
|
|
|
522
583
|
Evaluates artifacts against type-specific criteria (architecture soundness,
|
|
@@ -526,56 +587,7 @@ def review(path: str, scope: str | None, as_json: bool, ai_cmd: str | None):
|
|
|
526
587
|
Without --scope: reviews all documents.
|
|
527
588
|
With --scope: reviews a single document by node_id.
|
|
528
589
|
"""
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
project_root = Path(path).resolve()
|
|
532
|
-
_require_codd_dir(project_root)
|
|
533
|
-
|
|
534
|
-
try:
|
|
535
|
-
summary = run_review(project_root, scope=scope, ai_command=ai_cmd)
|
|
536
|
-
except (FileNotFoundError, ValueError) as exc:
|
|
537
|
-
click.echo(f"Error: {exc}")
|
|
538
|
-
raise SystemExit(1)
|
|
539
|
-
|
|
540
|
-
if not summary.results:
|
|
541
|
-
click.echo("No documents found to review.")
|
|
542
|
-
return
|
|
543
|
-
|
|
544
|
-
if as_json:
|
|
545
|
-
output = {
|
|
546
|
-
"pass_count": summary.pass_count,
|
|
547
|
-
"fail_count": summary.fail_count,
|
|
548
|
-
"avg_score": round(summary.avg_score, 1),
|
|
549
|
-
"results": [
|
|
550
|
-
{
|
|
551
|
-
"node_id": r.node_id,
|
|
552
|
-
"path": r.path,
|
|
553
|
-
"verdict": r.verdict,
|
|
554
|
-
"score": r.score,
|
|
555
|
-
"issues": [{"severity": i.severity, "message": i.message} for i in r.issues],
|
|
556
|
-
"feedback": r.feedback,
|
|
557
|
-
}
|
|
558
|
-
for r in summary.results
|
|
559
|
-
],
|
|
560
|
-
}
|
|
561
|
-
click.echo(json.dumps(output, ensure_ascii=False, indent=2))
|
|
562
|
-
else:
|
|
563
|
-
for r in summary.results:
|
|
564
|
-
icon = "PASS" if r.verdict == "PASS" else "FAIL"
|
|
565
|
-
click.echo(f" [{icon}] {r.path} ({r.node_id}) — score: {r.score}")
|
|
566
|
-
for issue in r.issues:
|
|
567
|
-
click.echo(f" [{issue.severity}] {issue.message}")
|
|
568
|
-
if r.feedback:
|
|
569
|
-
# Show first 200 chars of feedback in summary mode
|
|
570
|
-
preview = r.feedback[:200].replace("\n", " ")
|
|
571
|
-
if len(r.feedback) > 200:
|
|
572
|
-
preview += "..."
|
|
573
|
-
click.echo(f" Feedback: {preview}")
|
|
574
|
-
|
|
575
|
-
click.echo(f"\nSummary: {summary.pass_count} passed, {summary.fail_count} failed, avg score: {summary.avg_score:.0f}")
|
|
576
|
-
|
|
577
|
-
exit_code = 0 if summary.fail_count == 0 else 1
|
|
578
|
-
raise SystemExit(exit_code)
|
|
590
|
+
_run_pro_command("review", path=path, scope=scope, as_json=as_json, ai_cmd=ai_cmd)
|
|
579
591
|
|
|
580
592
|
|
|
581
593
|
@main.command()
|
|
@@ -597,7 +609,7 @@ def validate(path: str):
|
|
|
597
609
|
@click.option("--skip-review", is_flag=True, help="Skip AI review (faster, no AI cost)")
|
|
598
610
|
@click.option("--output", default=None, help="Output file (default: stdout)")
|
|
599
611
|
@click.option("--ai-cmd", default=None, help="Override AI command for review phase")
|
|
600
|
-
def audit(diff: str, path: str, as_json: bool, skip_review: bool, output: str | None, ai_cmd: str | None):
|
|
612
|
+
def audit(diff: str, path: str, as_json: bool, skip_review: bool, output: str | None, ai_cmd: str | None):
|
|
601
613
|
"""Change review pack — validate + impact + policy + review in one report.
|
|
602
614
|
|
|
603
615
|
Produces a consolidated audit report for PM/QA to make merge/release
|
|
@@ -607,31 +619,22 @@ def audit(diff: str, path: str, as_json: bool, skip_review: bool, output: str |
|
|
|
607
619
|
|
|
608
620
|
Exit code: 0 = APPROVE, 1 = CONDITIONAL or REJECT.
|
|
609
621
|
"""
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
text = format_audit_json(result) if as_json else format_audit_text(result)
|
|
627
|
-
|
|
628
|
-
if output:
|
|
629
|
-
Path(output).write_text(text, encoding="utf-8")
|
|
630
|
-
click.echo(f"Audit report written to {output}")
|
|
631
|
-
else:
|
|
632
|
-
click.echo(text)
|
|
633
|
-
|
|
634
|
-
raise SystemExit(0 if result.verdict == "APPROVE" else 1)
|
|
622
|
+
_run_pro_command(
|
|
623
|
+
"audit",
|
|
624
|
+
diff=diff,
|
|
625
|
+
path=path,
|
|
626
|
+
as_json=as_json,
|
|
627
|
+
skip_review=skip_review,
|
|
628
|
+
output=output,
|
|
629
|
+
ai_cmd=ai_cmd,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
@main.command()
|
|
634
|
+
@click.option("--path", default=".", help="Project root directory")
|
|
635
|
+
def risk(path: str):
|
|
636
|
+
"""Analyze change risk using the codd-pro extension pack."""
|
|
637
|
+
_run_pro_command("risk", path=path)
|
|
635
638
|
|
|
636
639
|
|
|
637
640
|
@main.command()
|
|
@@ -17,6 +17,7 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
import yaml
|
|
19
19
|
|
|
20
|
+
from codd.bridge import load_bridge_registry
|
|
20
21
|
from codd.parsing import (
|
|
21
22
|
BuildDepsExtractor,
|
|
22
23
|
BuildDepsInfo,
|
|
@@ -120,6 +121,8 @@ class ExtractResult:
|
|
|
120
121
|
module_count: int
|
|
121
122
|
total_files: int
|
|
122
123
|
total_lines: int
|
|
124
|
+
language: str
|
|
125
|
+
source_dirs: list[str]
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
# ═══════════════════════════════════════════════════════════
|
|
@@ -202,9 +205,12 @@ def extract_facts(project_root: Path, language: str | None = None,
|
|
|
202
205
|
from codd.wiring import build_runtime_wires
|
|
203
206
|
build_runtime_wires(facts, project_root)
|
|
204
207
|
|
|
205
|
-
# R5.4: Change risk scoring
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
# R5.4: Change risk scoring is provided by codd-pro when installed.
|
|
209
|
+
risk_builder = load_bridge_registry().risk_builder
|
|
210
|
+
if risk_builder is not None:
|
|
211
|
+
risk_builder(facts)
|
|
212
|
+
else:
|
|
213
|
+
facts.change_risks = []
|
|
208
214
|
|
|
209
215
|
# R8: Environment & config dependency detection
|
|
210
216
|
from codd.env_refs import build_env_refs
|
|
@@ -981,6 +987,8 @@ def run_extract(project_root: Path, language: str | None = None,
|
|
|
981
987
|
module_count=len(facts.modules),
|
|
982
988
|
total_files=facts.total_files,
|
|
983
989
|
total_lines=facts.total_lines,
|
|
990
|
+
language=facts.language,
|
|
991
|
+
source_dirs=facts.source_dirs,
|
|
984
992
|
)
|
|
985
993
|
|
|
986
994
|
|
|
@@ -25,6 +25,8 @@ import json
|
|
|
25
25
|
import sys
|
|
26
26
|
from pathlib import Path
|
|
27
27
|
|
|
28
|
+
from codd.bridge import load_bridge_registry
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
# ── JSON-RPC helpers ──────────────────────────────────────────────
|
|
30
32
|
|
|
@@ -71,21 +73,6 @@ TOOLS = [
|
|
|
71
73
|
"required": [],
|
|
72
74
|
},
|
|
73
75
|
},
|
|
74
|
-
{
|
|
75
|
-
"name": "codd_audit",
|
|
76
|
-
"description": "Run consolidated change review: validate + impact + policy. Returns APPROVE/CONDITIONAL/REJECT verdict with full details. Use this for PR review decisions.",
|
|
77
|
-
"inputSchema": {
|
|
78
|
-
"type": "object",
|
|
79
|
-
"properties": {
|
|
80
|
-
"diff_target": {
|
|
81
|
-
"type": "string",
|
|
82
|
-
"description": "Git ref to diff against (default: HEAD)",
|
|
83
|
-
"default": "HEAD",
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
"required": [],
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
76
|
{
|
|
90
77
|
"name": "codd_scan",
|
|
91
78
|
"description": "Build or rebuild the dependency graph from frontmatter in design documents.",
|
|
@@ -172,13 +159,6 @@ def _handle_policy(project_root: Path, _args: dict) -> dict:
|
|
|
172
159
|
return {"content": [{"type": "text", "text": format_policy_text(result)}]}
|
|
173
160
|
|
|
174
161
|
|
|
175
|
-
def _handle_audit(project_root: Path, args: dict) -> dict:
|
|
176
|
-
from codd.audit import run_audit, format_audit_json
|
|
177
|
-
diff_target = args.get("diff_target", "HEAD")
|
|
178
|
-
result = run_audit(project_root, diff_target=diff_target, skip_review=True)
|
|
179
|
-
return {"content": [{"type": "text", "text": format_audit_json(result)}]}
|
|
180
|
-
|
|
181
|
-
|
|
182
162
|
def _handle_scan(project_root: Path, _args: dict) -> dict:
|
|
183
163
|
from codd.config import find_codd_dir, load_project_config
|
|
184
164
|
from codd.scanner import scan_project
|
|
@@ -204,12 +184,23 @@ HANDLERS = {
|
|
|
204
184
|
"codd_validate": _handle_validate,
|
|
205
185
|
"codd_impact": _handle_impact,
|
|
206
186
|
"codd_policy": _handle_policy,
|
|
207
|
-
"codd_audit": _handle_audit,
|
|
208
187
|
"codd_scan": _handle_scan,
|
|
209
188
|
"codd_measure": _handle_measure,
|
|
210
189
|
}
|
|
211
190
|
|
|
212
191
|
|
|
192
|
+
def _registered_tools() -> list[dict]:
|
|
193
|
+
tools = list(TOOLS)
|
|
194
|
+
tools.extend(load_bridge_registry().mcp_tools.values())
|
|
195
|
+
return tools
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _registered_handlers() -> dict[str, object]:
|
|
199
|
+
handlers = dict(HANDLERS)
|
|
200
|
+
handlers.update(load_bridge_registry().mcp_handlers)
|
|
201
|
+
return handlers
|
|
202
|
+
|
|
203
|
+
|
|
213
204
|
# ── MCP Protocol Handler ─────────────────────────────────────────
|
|
214
205
|
|
|
215
206
|
def handle_request(request: dict, project_root: Path) -> dict | None:
|
|
@@ -226,7 +217,7 @@ def handle_request(request: dict, project_root: Path) -> dict | None:
|
|
|
226
217
|
},
|
|
227
218
|
"serverInfo": {
|
|
228
219
|
"name": "codd",
|
|
229
|
-
"version": "1.
|
|
220
|
+
"version": "1.6.0",
|
|
230
221
|
},
|
|
231
222
|
})
|
|
232
223
|
|
|
@@ -234,13 +225,13 @@ def handle_request(request: dict, project_root: Path) -> dict | None:
|
|
|
234
225
|
return None # No response for notifications
|
|
235
226
|
|
|
236
227
|
if method == "tools/list":
|
|
237
|
-
return _jsonrpc_response(req_id, {"tools":
|
|
228
|
+
return _jsonrpc_response(req_id, {"tools": _registered_tools()})
|
|
238
229
|
|
|
239
230
|
if method == "tools/call":
|
|
240
231
|
tool_name = params.get("name", "")
|
|
241
232
|
arguments = params.get("arguments", {})
|
|
242
233
|
|
|
243
|
-
handler =
|
|
234
|
+
handler = _registered_handlers().get(tool_name)
|
|
244
235
|
if handler is None:
|
|
245
236
|
return _jsonrpc_error(req_id, -32601, f"Unknown tool: {tool_name}")
|
|
246
237
|
|