feat(contract-validator): add gate contract versioning
- design-gate.md and data-gate.md declare gate_contract: v1 - domain-consultation.md Gate Command Reference includes Contract column - validate_workflow_integration now checks contract version compatibility - Tests added for match, mismatch, and missing contract scenarios Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,3 +15,11 @@
|
|||||||
2026-02-02T13:46:21 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-status.md | docs/COMMANDS-CHEATSHEET.md README.md
|
2026-02-02T13:46:21 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-status.md | docs/COMMANDS-CHEATSHEET.md README.md
|
||||||
2026-02-02T13:46:38 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/planner.md | README.md CLAUDE.md
|
2026-02-02T13:46:38 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/planner.md | README.md CLAUDE.md
|
||||||
2026-02-02T13:46:57 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/code-reviewer.md | README.md CLAUDE.md
|
2026-02-02T13:46:57 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/code-reviewer.md | README.md CLAUDE.md
|
||||||
|
2026-02-02T13:49:13 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/design-gate.md | docs/COMMANDS-CHEATSHEET.md README.md
|
||||||
|
2026-02-02T13:49:24 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-gate.md | docs/COMMANDS-CHEATSHEET.md README.md
|
||||||
|
2026-02-02T13:49:35 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/domain-consultation.md | README.md
|
||||||
|
2026-02-02T13:50:04 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/mcp_server/validation_tools.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
|
||||||
|
2026-02-02T13:50:59 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/mcp_server/server.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
|
||||||
|
2026-02-02T13:51:32 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/tests/test_validation_tools.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
|
||||||
|
2026-02-02T13:51:49 | skills | /home/lmiranda/claude-plugins-work/plugins/contract-validator/skills/validation-rules.md | README.md
|
||||||
|
2026-02-02T13:52:07 | skills | /home/lmiranda/claude-plugins-work/plugins/contract-validator/skills/mcp-tools-reference.md | README.md
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class ContractValidatorMCPServer:
|
|||||||
),
|
),
|
||||||
Tool(
|
Tool(
|
||||||
name="validate_workflow_integration",
|
name="validate_workflow_integration",
|
||||||
description="Validate that a domain plugin exposes the required advisory interfaces (gate command, review command, advisory agent) expected by projman's domain-consultation skill",
|
description="Validate that a domain plugin exposes the required advisory interfaces (gate command, review command, advisory agent) expected by projman's domain-consultation skill. Also checks gate contract version compatibility.",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -144,6 +144,10 @@ class ContractValidatorMCPServer:
|
|||||||
"domain_label": {
|
"domain_label": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The Domain/* label it claims to handle, e.g. Domain/Viz"
|
"description": "The Domain/* label it claims to handle, e.g. Domain/Viz"
|
||||||
|
},
|
||||||
|
"expected_contract": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Expected contract version (e.g., 'v1'). If provided, validates the gate command's contract matches."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["plugin_path", "domain_label"]
|
"required": ["plugin_path", "domain_label"]
|
||||||
@@ -261,9 +265,16 @@ class ContractValidatorMCPServer:
|
|||||||
"""Validate agent data flow"""
|
"""Validate agent data flow"""
|
||||||
return await self.validation_tools.validate_data_flow(agent_name, claude_md_path)
|
return await self.validation_tools.validate_data_flow(agent_name, claude_md_path)
|
||||||
|
|
||||||
async def _validate_workflow_integration(self, plugin_path: str, domain_label: str) -> dict:
|
async def _validate_workflow_integration(
|
||||||
|
self,
|
||||||
|
plugin_path: str,
|
||||||
|
domain_label: str,
|
||||||
|
expected_contract: str = None
|
||||||
|
) -> dict:
|
||||||
"""Validate domain plugin exposes required advisory interfaces"""
|
"""Validate domain plugin exposes required advisory interfaces"""
|
||||||
return await self.validation_tools.validate_workflow_integration(plugin_path, domain_label)
|
return await self.validation_tools.validate_workflow_integration(
|
||||||
|
plugin_path, domain_label, expected_contract
|
||||||
|
)
|
||||||
|
|
||||||
# Report tool implementations (Issue #188)
|
# Report tool implementations (Issue #188)
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class WorkflowIntegrationResult(BaseModel):
|
|||||||
domain_label: str
|
domain_label: str
|
||||||
valid: bool
|
valid: bool
|
||||||
gate_command_found: bool
|
gate_command_found: bool
|
||||||
|
gate_contract: Optional[str] = None # Contract version declared by gate command
|
||||||
review_command_found: bool
|
review_command_found: bool
|
||||||
advisory_agent_found: bool
|
advisory_agent_found: bool
|
||||||
issues: list[ValidationIssue] = []
|
issues: list[ValidationIssue] = []
|
||||||
@@ -349,22 +350,32 @@ class ValidationTools:
|
|||||||
|
|
||||||
return result.model_dump()
|
return result.model_dump()
|
||||||
|
|
||||||
async def validate_workflow_integration(self, plugin_path: str, domain_label: str) -> dict:
|
async def validate_workflow_integration(
|
||||||
|
self,
|
||||||
|
plugin_path: str,
|
||||||
|
domain_label: str,
|
||||||
|
expected_contract: Optional[str] = None
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Validate that a domain plugin exposes required advisory interfaces.
|
Validate that a domain plugin exposes required advisory interfaces.
|
||||||
|
|
||||||
Checks for:
|
Checks for:
|
||||||
- Gate command (e.g., /design-gate, /data-gate) - REQUIRED
|
- Gate command (e.g., /design-gate, /data-gate) - REQUIRED
|
||||||
|
- Gate contract version (gate_contract in frontmatter) - INFO if missing
|
||||||
- Review command (e.g., /design-review, /data-review) - recommended
|
- Review command (e.g., /design-review, /data-review) - recommended
|
||||||
- Advisory agent referencing the domain label - recommended
|
- Advisory agent referencing the domain label - recommended
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_path: Path to the domain plugin directory
|
plugin_path: Path to the domain plugin directory
|
||||||
domain_label: The Domain/* label it claims to handle (e.g., Domain/Viz)
|
domain_label: The Domain/* label it claims to handle (e.g., Domain/Viz)
|
||||||
|
expected_contract: Expected contract version (e.g., 'v1'). If provided,
|
||||||
|
validates the gate command's contract matches.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Validation result with found interfaces and issues
|
Validation result with found interfaces and issues
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
plugin_path_obj = Path(plugin_path)
|
plugin_path_obj = Path(plugin_path)
|
||||||
issues = []
|
issues = []
|
||||||
|
|
||||||
@@ -383,6 +394,7 @@ class ValidationTools:
|
|||||||
# Check for gate command
|
# Check for gate command
|
||||||
commands_dir = plugin_path_obj / "commands"
|
commands_dir = plugin_path_obj / "commands"
|
||||||
gate_command_found = False
|
gate_command_found = False
|
||||||
|
gate_contract = None
|
||||||
gate_patterns = ["pass", "fail", "PASS", "FAIL", "Binary pass/fail", "gate"]
|
gate_patterns = ["pass", "fail", "PASS", "FAIL", "Binary pass/fail", "gate"]
|
||||||
|
|
||||||
if commands_dir.exists():
|
if commands_dir.exists():
|
||||||
@@ -392,6 +404,13 @@ class ValidationTools:
|
|||||||
content = cmd_file.read_text()
|
content = cmd_file.read_text()
|
||||||
if any(pattern in content for pattern in gate_patterns):
|
if any(pattern in content for pattern in gate_patterns):
|
||||||
gate_command_found = True
|
gate_command_found = True
|
||||||
|
# Parse frontmatter for gate_contract
|
||||||
|
frontmatter_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||||
|
if frontmatter_match:
|
||||||
|
frontmatter = frontmatter_match.group(1)
|
||||||
|
contract_match = re.search(r'gate_contract:\s*(\S+)', frontmatter)
|
||||||
|
if contract_match:
|
||||||
|
gate_contract = contract_match.group(1)
|
||||||
break
|
break
|
||||||
|
|
||||||
if not gate_command_found:
|
if not gate_command_found:
|
||||||
@@ -441,11 +460,31 @@ class ValidationTools:
|
|||||||
suggestion=f"Create agents/{domain_short}-advisor.md referencing '{domain_label}'"
|
suggestion=f"Create agents/{domain_short}-advisor.md referencing '{domain_label}'"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check gate contract version
|
||||||
|
if gate_command_found:
|
||||||
|
if not gate_contract:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.INFO,
|
||||||
|
issue_type=IssueType.MISSING_INTEGRATION,
|
||||||
|
message=f"Gate command does not declare a contract version",
|
||||||
|
location=str(commands_dir),
|
||||||
|
suggestion="Consider adding `gate_contract: v1` to frontmatter for version tracking"
|
||||||
|
))
|
||||||
|
elif expected_contract and gate_contract != expected_contract:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
issue_type=IssueType.INTERFACE_MISMATCH,
|
||||||
|
message=f"Contract version mismatch: gate declares {gate_contract}, projman expects {expected_contract}",
|
||||||
|
location=str(commands_dir),
|
||||||
|
suggestion=f"Update domain-consultation.md Gate Command Reference table to {gate_contract}, or update gate command to {expected_contract}"
|
||||||
|
))
|
||||||
|
|
||||||
result = WorkflowIntegrationResult(
|
result = WorkflowIntegrationResult(
|
||||||
plugin_name=plugin_name,
|
plugin_name=plugin_name,
|
||||||
domain_label=domain_label,
|
domain_label=domain_label,
|
||||||
valid=gate_command_found, # Only gate is required for validity
|
valid=gate_command_found, # Only gate is required for validity
|
||||||
gate_command_found=gate_command_found,
|
gate_command_found=gate_command_found,
|
||||||
|
gate_contract=gate_contract,
|
||||||
review_command_found=review_command_found,
|
review_command_found=review_command_found,
|
||||||
advisory_agent_found=advisory_agent_found,
|
advisory_agent_found=advisory_agent_found,
|
||||||
issues=issues
|
issues=issues
|
||||||
|
|||||||
@@ -404,3 +404,108 @@ async def test_validate_workflow_integration_nonexistent_plugin(validation_tools
|
|||||||
|
|
||||||
assert "error" in result
|
assert "error" in result
|
||||||
assert "not found" in result["error"].lower()
|
assert "not found" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Gate Contract Version Tests ---
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def domain_plugin_with_contract(tmp_path):
|
||||||
|
"""Create domain plugin with gate_contract: v1 in frontmatter"""
|
||||||
|
plugin_dir = tmp_path / "viz-platform-versioned"
|
||||||
|
plugin_dir.mkdir()
|
||||||
|
(plugin_dir / ".claude-plugin").mkdir()
|
||||||
|
(plugin_dir / "commands").mkdir()
|
||||||
|
(plugin_dir / "agents").mkdir()
|
||||||
|
|
||||||
|
# Gate command with gate_contract in frontmatter
|
||||||
|
gate_cmd = plugin_dir / "commands" / "design-gate.md"
|
||||||
|
gate_cmd.write_text("""---
|
||||||
|
description: Design system compliance gate (pass/fail)
|
||||||
|
gate_contract: v1
|
||||||
|
---
|
||||||
|
|
||||||
|
# /design-gate
|
||||||
|
|
||||||
|
Binary pass/fail validation gate for design system compliance.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **PASS**: All design system checks passed
|
||||||
|
- **FAIL**: Design system violations detected
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Review command
|
||||||
|
review_cmd = plugin_dir / "commands" / "design-review.md"
|
||||||
|
review_cmd.write_text("""# /design-review
|
||||||
|
|
||||||
|
Comprehensive design system audit.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Advisory agent
|
||||||
|
agent = plugin_dir / "agents" / "design-reviewer.md"
|
||||||
|
agent.write_text("""# design-reviewer
|
||||||
|
|
||||||
|
Design system compliance auditor for Domain/Viz issues.
|
||||||
|
""")
|
||||||
|
|
||||||
|
return str(plugin_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_workflow_contract_match(validation_tools, domain_plugin_with_contract):
|
||||||
|
"""Test that matching expected_contract produces no warning"""
|
||||||
|
result = await validation_tools.validate_workflow_integration(
|
||||||
|
domain_plugin_with_contract,
|
||||||
|
"Domain/Viz",
|
||||||
|
expected_contract="v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is True
|
||||||
|
assert result["gate_contract"] == "v1"
|
||||||
|
|
||||||
|
# Should have no warnings about contract mismatch
|
||||||
|
warning_issues = [i for i in result["issues"] if i["severity"].value == "warning"]
|
||||||
|
contract_warnings = [i for i in warning_issues if "contract" in i["message"].lower()]
|
||||||
|
assert len(contract_warnings) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_workflow_contract_mismatch(validation_tools, domain_plugin_with_contract):
|
||||||
|
"""Test that mismatched expected_contract produces WARNING"""
|
||||||
|
result = await validation_tools.validate_workflow_integration(
|
||||||
|
domain_plugin_with_contract,
|
||||||
|
"Domain/Viz",
|
||||||
|
expected_contract="v2" # Gate has v1
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is True # Contract mismatch doesn't affect validity
|
||||||
|
assert result["gate_contract"] == "v1"
|
||||||
|
|
||||||
|
# Should have warning about contract mismatch
|
||||||
|
warning_issues = [i for i in result["issues"] if i["severity"].value == "warning"]
|
||||||
|
contract_warnings = [i for i in warning_issues if "contract" in i["message"].lower()]
|
||||||
|
assert len(contract_warnings) == 1
|
||||||
|
assert "mismatch" in contract_warnings[0]["message"].lower()
|
||||||
|
assert "v1" in contract_warnings[0]["message"]
|
||||||
|
assert "v2" in contract_warnings[0]["message"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_workflow_no_contract(validation_tools, domain_plugin_complete):
|
||||||
|
"""Test that missing gate_contract produces INFO suggestion"""
|
||||||
|
result = await validation_tools.validate_workflow_integration(
|
||||||
|
domain_plugin_complete,
|
||||||
|
"Domain/Viz"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is True
|
||||||
|
assert result["gate_contract"] is None
|
||||||
|
|
||||||
|
# Should have info issue about missing contract
|
||||||
|
info_issues = [i for i in result["issues"] if i["severity"].value == "info"]
|
||||||
|
contract_info = [i for i in info_issues if "contract" in i["message"].lower()]
|
||||||
|
assert len(contract_info) == 1
|
||||||
|
assert "does not declare" in contract_info[0]["message"].lower()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Available MCP tools for contract-validator operations.
|
|||||||
| `validate_compatibility` | Check two plugins for conflicts |
|
| `validate_compatibility` | Check two plugins for conflicts |
|
||||||
| `validate_agent_refs` | Check agent tool references exist |
|
| `validate_agent_refs` | Check agent tool references exist |
|
||||||
| `validate_data_flow` | Verify data flow through agent sequence |
|
| `validate_data_flow` | Verify data flow through agent sequence |
|
||||||
| `validate_workflow_integration` | Check domain plugin exposes required advisory interfaces |
|
| `validate_workflow_integration` | Check domain plugin exposes required advisory interfaces and gate contract version |
|
||||||
|
|
||||||
### Report Tools
|
### Report Tools
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
@@ -57,8 +57,9 @@ Available MCP tools for contract-validator operations.
|
|||||||
### Workflow Integration Check
|
### Workflow Integration Check
|
||||||
```
|
```
|
||||||
1. validate_workflow_integration(plugin_path, domain_label) # Check single domain plugin
|
1. validate_workflow_integration(plugin_path, domain_label) # Check single domain plugin
|
||||||
2. For each domain in domain-consultation.md detection rules:
|
2. validate_workflow_integration(plugin_path, domain_label, expected_contract="v1") # With contract version check
|
||||||
validate_workflow_integration(domain_plugin_path, domain_label)
|
3. For each domain in domain-consultation.md detection rules:
|
||||||
|
validate_workflow_integration(domain_plugin_path, domain_label, expected_contract)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ Rules for validating plugin compatibility and agent definitions.
|
|||||||
2. Gate command produces binary PASS/FAIL output
|
2. Gate command produces binary PASS/FAIL output
|
||||||
3. Review command exists (WARNING if missing, not ERROR)
|
3. Review command exists (WARNING if missing, not ERROR)
|
||||||
4. Advisory agent exists referencing the domain label
|
4. Advisory agent exists referencing the domain label
|
||||||
- Severity: ERROR for missing gate, WARNING for missing review/agent
|
5. Gate command declares `gate_contract` version in frontmatter
|
||||||
|
6. If expected version provided, gate contract version matches expected
|
||||||
|
- Severity: ERROR for missing gate, WARNING for missing review/agent or contract mismatch, INFO for missing contract
|
||||||
|
|
||||||
## Severity Levels
|
## Severity Levels
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: Data integrity compliance gate (pass/fail) for sprint execution
|
description: Data integrity compliance gate (pass/fail) for sprint execution
|
||||||
|
gate_contract: v1
|
||||||
arguments:
|
arguments:
|
||||||
- name: path
|
- name: path
|
||||||
description: File or directory to validate
|
description: File or directory to validate
|
||||||
|
|||||||
@@ -154,10 +154,12 @@ This pattern ensures domain expertise stays in domain plugins while projman orch
|
|||||||
|
|
||||||
## Gate Command Reference
|
## Gate Command Reference
|
||||||
|
|
||||||
| Domain | Gate Command | Review Command | Advisory Agent |
|
| Domain | Gate Command | Contract | Review Command | Advisory Agent |
|
||||||
|--------|--------------|----------------|----------------|
|
|--------|--------------|----------|----------------|----------------|
|
||||||
| Viz | `/design-gate <path>` | `/design-review <path>` | `design-reviewer` |
|
| Viz | `/design-gate <path>` | v1 | `/design-review <path>` | `design-reviewer` |
|
||||||
| Data | `/data-gate <path>` | `/data-review <path>` | `data-advisor` |
|
| Data | `/data-gate <path>` | v1 | `/data-review <path>` | `data-advisor` |
|
||||||
|
|
||||||
Gate commands return binary PASS/FAIL for automation.
|
Gate commands return binary PASS/FAIL for automation.
|
||||||
Review commands return detailed reports for human review.
|
Review commands return detailed reports for human review.
|
||||||
|
|
||||||
|
**Contract Version:** Gate commands declare `gate_contract: vN` in their frontmatter. The version in this table is what projman expects. If a gate command bumps its contract version, this table must be updated to match. The `contract-validator` plugin checks this automatically via `validate_workflow_integration`.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: Design system compliance gate (pass/fail) for sprint execution
|
description: Design system compliance gate (pass/fail) for sprint execution
|
||||||
|
gate_contract: v1
|
||||||
arguments:
|
arguments:
|
||||||
- name: path
|
- name: path
|
||||||
description: File or directory to validate
|
description: File or directory to validate
|
||||||
|
|||||||
Reference in New Issue
Block a user