start-vibing 1.1.2 → 1.1.4
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.
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +129 -168
- package/template/.claude/agents/analyzer.md +0 -14
- package/template/.claude/agents/commit-manager.md +0 -19
- package/template/.claude/agents/documenter.md +0 -10
- package/template/.claude/agents/domain-updater.md +194 -200
- package/template/.claude/agents/final-validator.md +0 -18
- package/template/.claude/agents/orchestrator.md +36 -34
- package/template/.claude/agents/quality-checker.md +0 -24
- package/template/.claude/agents/research.md +299 -262
- package/template/.claude/agents/security-auditor.md +1 -14
- package/template/.claude/agents/tester.md +0 -8
- package/template/.claude/agents/ui-ux-reviewer.md +80 -18
- package/template/.claude/commands/feature.md +48 -102
- package/template/.claude/config/README.md +30 -30
- package/template/.claude/config/project-config.json +53 -53
- package/template/.claude/config/quality-gates.json +46 -46
- package/template/.claude/config/security-rules.json +45 -45
- package/template/.claude/config/testing-config.json +168 -168
- package/template/.claude/hooks/SETUP.md +52 -181
- package/template/.claude/hooks/user-prompt-submit.py +184 -46
- package/template/.claude/settings.json +0 -39
- package/template/.claude/skills/codebase-knowledge/SKILL.md +145 -145
- package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +260 -321
- package/template/.claude/skills/docs-tracker/SKILL.md +239 -239
- package/template/.claude/skills/final-check/SKILL.md +284 -284
- package/template/.claude/skills/quality-gate/SKILL.md +278 -278
- package/template/.claude/skills/research-cache/SKILL.md +207 -207
- package/template/.claude/skills/security-scan/SKILL.md +206 -206
- package/template/.claude/skills/test-coverage/SKILL.md +441 -441
- package/template/.claude/skills/ui-ux-audit/SKILL.md +254 -254
- package/template/.claude/config/domain-mapping.json +0 -26
- package/template/.claude/hooks/post-tool-use.py +0 -155
- package/template/.claude/hooks/pre-tool-use.py +0 -159
- package/template/.claude/hooks/stop-validation.py +0 -155
- package/template/.claude/hooks/validate-commit.py +0 -200
- package/template/.claude/hooks/workflow-manager.py +0 -350
- package/template/.claude/workflow-state.schema.json +0 -200
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
PreToolUse Hook - File Modification Enforcement
|
|
4
|
-
|
|
5
|
-
This hook blocks file modifications unless:
|
|
6
|
-
1. The analyzer agent has approved the file
|
|
7
|
-
2. The file is in the approvedFiles list in workflow-state.json
|
|
8
|
-
|
|
9
|
-
Exit codes:
|
|
10
|
-
- 0: Allow the operation
|
|
11
|
-
- 2: Block the operation (shows stderr to Claude)
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import json
|
|
15
|
-
import sys
|
|
16
|
-
import os
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
# Get project directory
|
|
21
|
-
PROJECT_DIR = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
|
|
22
|
-
WORKFLOW_STATE_PATH = Path(PROJECT_DIR) / '.claude' / 'workflow-state.json'
|
|
23
|
-
|
|
24
|
-
# Tools that modify files
|
|
25
|
-
FILE_MODIFY_TOOLS = {'Edit', 'Write', 'NotebookEdit'}
|
|
26
|
-
|
|
27
|
-
# Files always allowed (config, etc)
|
|
28
|
-
ALWAYS_ALLOWED = {
|
|
29
|
-
'.claude/workflow-state.json',
|
|
30
|
-
'.claude/hooks/',
|
|
31
|
-
'package-lock.json',
|
|
32
|
-
'node_modules/',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
# Files always blocked
|
|
36
|
-
ALWAYS_BLOCKED = {
|
|
37
|
-
'.env',
|
|
38
|
-
'.env.local',
|
|
39
|
-
'.env.production',
|
|
40
|
-
'credentials.json',
|
|
41
|
-
'secrets/',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def load_workflow_state():
|
|
46
|
-
"""Load current workflow state"""
|
|
47
|
-
if not WORKFLOW_STATE_PATH.exists():
|
|
48
|
-
return None
|
|
49
|
-
try:
|
|
50
|
-
with open(WORKFLOW_STATE_PATH) as f:
|
|
51
|
-
return json.load(f)
|
|
52
|
-
except (json.JSONDecodeError, IOError):
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def is_file_approved(file_path: str, state: dict) -> tuple[bool, str]:
|
|
57
|
-
"""Check if file is approved for modification"""
|
|
58
|
-
|
|
59
|
-
# Normalize path
|
|
60
|
-
file_path = file_path.replace('\\', '/')
|
|
61
|
-
|
|
62
|
-
# Always blocked files
|
|
63
|
-
for blocked in ALWAYS_BLOCKED:
|
|
64
|
-
if blocked in file_path:
|
|
65
|
-
return False, f"BLOCKED: {file_path} is in the always-blocked list (sensitive file)"
|
|
66
|
-
|
|
67
|
-
# Always allowed files
|
|
68
|
-
for allowed in ALWAYS_ALLOWED:
|
|
69
|
-
if allowed in file_path:
|
|
70
|
-
return True, "Allowed (system file)"
|
|
71
|
-
|
|
72
|
-
# No active task - block all modifications
|
|
73
|
-
if not state or not state.get('currentTask'):
|
|
74
|
-
return False, f"""BLOCKED: No active task in workflow-state.json
|
|
75
|
-
|
|
76
|
-
To modify files, you must first:
|
|
77
|
-
1. Start a task via orchestrator agent
|
|
78
|
-
2. Run analyzer agent to approve files for modification
|
|
79
|
-
3. The file '{file_path}' must be in the approvedFiles list
|
|
80
|
-
|
|
81
|
-
Current state: No task active. Start with orchestrator first."""
|
|
82
|
-
|
|
83
|
-
task = state['currentTask']
|
|
84
|
-
approved_files = task.get('approvedFiles', [])
|
|
85
|
-
|
|
86
|
-
# Check if analyzer has run
|
|
87
|
-
agents = task.get('agents', {})
|
|
88
|
-
analyzer = agents.get('analyzer', {})
|
|
89
|
-
|
|
90
|
-
if not analyzer.get('executed'):
|
|
91
|
-
return False, f"""BLOCKED: Analyzer agent has not executed yet
|
|
92
|
-
|
|
93
|
-
To modify '{file_path}', you must:
|
|
94
|
-
1. Run the analyzer agent first
|
|
95
|
-
2. Analyzer will determine which files can be modified
|
|
96
|
-
3. Add approved files to workflow-state.json
|
|
97
|
-
|
|
98
|
-
Current task: {task.get('description', 'Unknown')}
|
|
99
|
-
Analyzer status: Not executed"""
|
|
100
|
-
|
|
101
|
-
# Check if file is in approved list
|
|
102
|
-
for approved in approved_files:
|
|
103
|
-
# Support glob patterns
|
|
104
|
-
if approved.endswith('*'):
|
|
105
|
-
if file_path.startswith(approved[:-1]):
|
|
106
|
-
return True, f"Approved (matches pattern: {approved})"
|
|
107
|
-
elif approved == file_path or file_path.endswith(approved):
|
|
108
|
-
return True, f"Approved by analyzer"
|
|
109
|
-
|
|
110
|
-
return False, f"""BLOCKED: File not in approved list
|
|
111
|
-
|
|
112
|
-
File: {file_path}
|
|
113
|
-
Task: {task.get('description', 'Unknown')}
|
|
114
|
-
|
|
115
|
-
Approved files for this task:
|
|
116
|
-
{chr(10).join(f' - {f}' for f in approved_files) if approved_files else ' (none)'}
|
|
117
|
-
|
|
118
|
-
To modify this file, run analyzer again and add it to approvedFiles."""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def main():
|
|
122
|
-
# Read hook input from stdin
|
|
123
|
-
try:
|
|
124
|
-
hook_input = json.load(sys.stdin)
|
|
125
|
-
except json.JSONDecodeError:
|
|
126
|
-
# Can't parse input, allow by default
|
|
127
|
-
sys.exit(0)
|
|
128
|
-
|
|
129
|
-
tool_name = hook_input.get('tool_name', '')
|
|
130
|
-
tool_input = hook_input.get('tool_input', {})
|
|
131
|
-
|
|
132
|
-
# Only check file modification tools
|
|
133
|
-
if tool_name not in FILE_MODIFY_TOOLS:
|
|
134
|
-
sys.exit(0)
|
|
135
|
-
|
|
136
|
-
# Get file path from tool input
|
|
137
|
-
file_path = tool_input.get('file_path', tool_input.get('notebook_path', ''))
|
|
138
|
-
|
|
139
|
-
if not file_path:
|
|
140
|
-
sys.exit(0)
|
|
141
|
-
|
|
142
|
-
# Load workflow state
|
|
143
|
-
state = load_workflow_state()
|
|
144
|
-
|
|
145
|
-
# Check if file is approved
|
|
146
|
-
approved, message = is_file_approved(file_path, state)
|
|
147
|
-
|
|
148
|
-
if approved:
|
|
149
|
-
# Log the approval (optional verbose output)
|
|
150
|
-
# print(f"✓ {message}", file=sys.stderr)
|
|
151
|
-
sys.exit(0)
|
|
152
|
-
else:
|
|
153
|
-
# Block with error message
|
|
154
|
-
print(message, file=sys.stderr)
|
|
155
|
-
sys.exit(2)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if __name__ == '__main__':
|
|
159
|
-
main()
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Stop Hook - Workflow Validation Before Stopping
|
|
4
|
-
|
|
5
|
-
This hook validates that the workflow was properly executed before
|
|
6
|
-
allowing Claude to stop. If validation fails, Claude is forced to continue.
|
|
7
|
-
|
|
8
|
-
Exit codes:
|
|
9
|
-
- 0: Allow stop (or output JSON to block)
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import json
|
|
13
|
-
import sys
|
|
14
|
-
import os
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
PROJECT_DIR = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
|
|
18
|
-
WORKFLOW_STATE_PATH = Path(PROJECT_DIR) / '.claude' / 'workflow-state.json'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def load_workflow_state():
|
|
22
|
-
"""Load current workflow state"""
|
|
23
|
-
if not WORKFLOW_STATE_PATH.exists():
|
|
24
|
-
return None
|
|
25
|
-
try:
|
|
26
|
-
with open(WORKFLOW_STATE_PATH) as f:
|
|
27
|
-
return json.load(f)
|
|
28
|
-
except (json.JSONDecodeError, IOError):
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def validate_workflow(state: dict) -> tuple[bool, list[str]]:
|
|
33
|
-
"""Validate workflow completion"""
|
|
34
|
-
issues = []
|
|
35
|
-
|
|
36
|
-
if not state or not state.get('currentTask'):
|
|
37
|
-
# No task, allow stop
|
|
38
|
-
return True, []
|
|
39
|
-
|
|
40
|
-
task = state['currentTask']
|
|
41
|
-
task_type = task.get('type', 'feature')
|
|
42
|
-
agents = task.get('agents', {})
|
|
43
|
-
modified_files = task.get('modifiedFiles', [])
|
|
44
|
-
|
|
45
|
-
# Skip validation for research/config tasks
|
|
46
|
-
if task_type in ['research', 'config']:
|
|
47
|
-
return True, []
|
|
48
|
-
|
|
49
|
-
# Check required agents executed
|
|
50
|
-
required_agents = {
|
|
51
|
-
'feature': ['orchestrator', 'analyzer', 'research', 'tester', 'securityAuditor', 'qualityChecker', 'finalValidator', 'domainUpdater'],
|
|
52
|
-
'fix': ['analyzer', 'research', 'tester', 'securityAuditor', 'qualityChecker', 'finalValidator', 'domainUpdater'],
|
|
53
|
-
'refactor': ['analyzer', 'tester', 'qualityChecker', 'finalValidator', 'domainUpdater'],
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for agent in required_agents.get(task_type, []):
|
|
57
|
-
agent_status = agents.get(agent, {})
|
|
58
|
-
if not agent_status.get('executed'):
|
|
59
|
-
issues.append(f"Agent '{agent}' has not executed")
|
|
60
|
-
elif agent_status.get('result') == 'vetoed':
|
|
61
|
-
issues.append(f"Agent '{agent}' VETOED - must resolve before stopping")
|
|
62
|
-
|
|
63
|
-
# Check if files were modified without tests
|
|
64
|
-
if modified_files and task_type in ['feature', 'fix']:
|
|
65
|
-
tests_created = task.get('testsCreated', [])
|
|
66
|
-
# Exclude: tests, specs, docs, markdown, and .claude config files
|
|
67
|
-
source_files = [m for m in modified_files if not any(
|
|
68
|
-
p in m['path'].lower() for p in ['test', 'spec', '.md', 'docs/', '.claude/']
|
|
69
|
-
)]
|
|
70
|
-
|
|
71
|
-
if source_files and not tests_created:
|
|
72
|
-
issues.append(f"Modified {len(source_files)} source file(s) but no tests created")
|
|
73
|
-
|
|
74
|
-
# Check documentation (skip for .claude config files)
|
|
75
|
-
if modified_files and task_type == 'feature':
|
|
76
|
-
non_config_files = [m for m in modified_files if '.claude/' not in m['path']]
|
|
77
|
-
if non_config_files:
|
|
78
|
-
docs_updated = task.get('docsUpdated', [])
|
|
79
|
-
if not docs_updated:
|
|
80
|
-
issues.append("No documentation updated for feature")
|
|
81
|
-
|
|
82
|
-
# Check quality gates
|
|
83
|
-
quality = task.get('qualityGates', {})
|
|
84
|
-
for gate in ['typecheck', 'lint', 'build']:
|
|
85
|
-
gate_result = quality.get(gate, {})
|
|
86
|
-
if gate_result and not gate_result.get('passed'):
|
|
87
|
-
issues.append(f"Quality gate '{gate}' failed")
|
|
88
|
-
|
|
89
|
-
# Check security audit
|
|
90
|
-
security = task.get('securityAudit', {})
|
|
91
|
-
if security.get('result') == 'vetoed':
|
|
92
|
-
issues.append("Security audit VETOED - must resolve vulnerabilities")
|
|
93
|
-
|
|
94
|
-
# Check final validation
|
|
95
|
-
final = task.get('finalValidation', {})
|
|
96
|
-
if final.get('executed') and not final.get('readyToCommit'):
|
|
97
|
-
issues.append("Final validation not approved for commit")
|
|
98
|
-
|
|
99
|
-
# Check domain-updater (critical for knowledge retention)
|
|
100
|
-
domain_updater = agents.get('domainUpdater', {})
|
|
101
|
-
if modified_files and not domain_updater.get('executed'):
|
|
102
|
-
issues.append("Domain updater has not run - domains must be updated with session learnings")
|
|
103
|
-
|
|
104
|
-
# Check commit-manager (critical for persisting changes - FINAL step)
|
|
105
|
-
commit_manager = agents.get('commitManager', {})
|
|
106
|
-
if domain_updater.get('executed') and not commit_manager.get('executed'):
|
|
107
|
-
issues.append("Commit-manager has not run - changes must be committed before session ends")
|
|
108
|
-
|
|
109
|
-
return len(issues) == 0, issues
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def main():
|
|
113
|
-
# Read hook input
|
|
114
|
-
try:
|
|
115
|
-
hook_input = json.load(sys.stdin)
|
|
116
|
-
except json.JSONDecodeError:
|
|
117
|
-
sys.exit(0)
|
|
118
|
-
|
|
119
|
-
# Load state
|
|
120
|
-
state = load_workflow_state()
|
|
121
|
-
|
|
122
|
-
# Validate
|
|
123
|
-
is_valid, issues = validate_workflow(state)
|
|
124
|
-
|
|
125
|
-
if is_valid:
|
|
126
|
-
# Allow stop
|
|
127
|
-
sys.exit(0)
|
|
128
|
-
else:
|
|
129
|
-
# Block stop and force continuation
|
|
130
|
-
output = {
|
|
131
|
-
"decision": "block",
|
|
132
|
-
"reason": f"""WORKFLOW INCOMPLETE - Cannot stop yet.
|
|
133
|
-
|
|
134
|
-
Issues found:
|
|
135
|
-
{chr(10).join(f' ❌ {issue}' for issue in issues)}
|
|
136
|
-
|
|
137
|
-
You must complete these steps before stopping:
|
|
138
|
-
1. Execute all required agents
|
|
139
|
-
2. Resolve any VETOs
|
|
140
|
-
3. Create tests for modified files
|
|
141
|
-
4. Update documentation
|
|
142
|
-
5. Pass all quality gates
|
|
143
|
-
6. Get final validation approval
|
|
144
|
-
7. Run domain-updater to update domains with session learnings
|
|
145
|
-
8. Run commit-manager to commit changes (FINAL step)
|
|
146
|
-
|
|
147
|
-
Resume the workflow to address these issues."""
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
print(json.dumps(output))
|
|
151
|
-
sys.exit(0)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if __name__ == '__main__':
|
|
155
|
-
main()
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Husky Pre-Commit Hook - Workflow Validation
|
|
4
|
-
|
|
5
|
-
This script is called by Husky before allowing a commit.
|
|
6
|
-
It validates that:
|
|
7
|
-
1. The workflow was properly executed
|
|
8
|
-
2. All modified files were approved
|
|
9
|
-
3. Tests were created for source files
|
|
10
|
-
4. Documentation was updated
|
|
11
|
-
5. Quality gates passed
|
|
12
|
-
6. Final validation approved
|
|
13
|
-
|
|
14
|
-
Exit codes:
|
|
15
|
-
- 0: Allow commit
|
|
16
|
-
- 1: Block commit with error message
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import json
|
|
20
|
-
import sys
|
|
21
|
-
import os
|
|
22
|
-
import subprocess
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
|
|
25
|
-
PROJECT_DIR = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
|
|
26
|
-
WORKFLOW_STATE_PATH = Path(PROJECT_DIR) / '.claude' / 'workflow-state.json'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_staged_files():
|
|
30
|
-
"""Get list of staged files"""
|
|
31
|
-
try:
|
|
32
|
-
result = subprocess.run(
|
|
33
|
-
['git', 'diff', '--cached', '--name-only'],
|
|
34
|
-
capture_output=True,
|
|
35
|
-
text=True,
|
|
36
|
-
check=True
|
|
37
|
-
)
|
|
38
|
-
return [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
|
|
39
|
-
except subprocess.CalledProcessError:
|
|
40
|
-
return []
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def load_workflow_state():
|
|
44
|
-
"""Load current workflow state"""
|
|
45
|
-
if not WORKFLOW_STATE_PATH.exists():
|
|
46
|
-
return None
|
|
47
|
-
try:
|
|
48
|
-
with open(WORKFLOW_STATE_PATH) as f:
|
|
49
|
-
return json.load(f)
|
|
50
|
-
except (json.JSONDecodeError, IOError):
|
|
51
|
-
return None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def validate_commit(state: dict, staged_files: list) -> tuple[bool, list[str]]:
|
|
55
|
-
"""Validate that commit is allowed"""
|
|
56
|
-
errors = []
|
|
57
|
-
|
|
58
|
-
# Skip validation if no staged files
|
|
59
|
-
if not staged_files:
|
|
60
|
-
return True, []
|
|
61
|
-
|
|
62
|
-
# Skip if only workflow state files
|
|
63
|
-
non_workflow_files = [f for f in staged_files if '.claude/' not in f]
|
|
64
|
-
if not non_workflow_files:
|
|
65
|
-
return True, []
|
|
66
|
-
|
|
67
|
-
# Check workflow state exists
|
|
68
|
-
if not state:
|
|
69
|
-
errors.append("No workflow-state.json found - did you use the agent workflow?")
|
|
70
|
-
errors.append("Run the orchestrator agent to start a task before committing.")
|
|
71
|
-
return False, errors
|
|
72
|
-
|
|
73
|
-
task = state.get('currentTask')
|
|
74
|
-
if not task:
|
|
75
|
-
errors.append("No active task in workflow state")
|
|
76
|
-
errors.append("The agent workflow was not properly started")
|
|
77
|
-
return False, errors
|
|
78
|
-
|
|
79
|
-
# Check all staged files were in workflow
|
|
80
|
-
modified_in_workflow = [m['path'] for m in task.get('modifiedFiles', [])]
|
|
81
|
-
approved_files = task.get('approvedFiles', [])
|
|
82
|
-
|
|
83
|
-
for staged in staged_files:
|
|
84
|
-
# Skip workflow files
|
|
85
|
-
if '.claude/' in staged or staged.endswith('.md'):
|
|
86
|
-
continue
|
|
87
|
-
|
|
88
|
-
# Check if file was tracked
|
|
89
|
-
if staged not in modified_in_workflow:
|
|
90
|
-
# Check if matches approved pattern
|
|
91
|
-
matched = False
|
|
92
|
-
for approved in approved_files:
|
|
93
|
-
if approved.endswith('*') and staged.startswith(approved[:-1]):
|
|
94
|
-
matched = True
|
|
95
|
-
break
|
|
96
|
-
if staged.endswith(approved) or staged == approved:
|
|
97
|
-
matched = True
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
if not matched:
|
|
101
|
-
errors.append(f"File '{staged}' was not tracked in workflow")
|
|
102
|
-
errors.append(" This file was modified outside the agent workflow")
|
|
103
|
-
|
|
104
|
-
# Check agents executed
|
|
105
|
-
agents = task.get('agents', {})
|
|
106
|
-
task_type = task.get('type', 'feature')
|
|
107
|
-
|
|
108
|
-
# Required agents by task type
|
|
109
|
-
required = {
|
|
110
|
-
'feature': ['analyzer', 'tester', 'securityAuditor', 'qualityChecker', 'finalValidator'],
|
|
111
|
-
'fix': ['analyzer', 'tester', 'qualityChecker', 'finalValidator'],
|
|
112
|
-
'refactor': ['analyzer', 'qualityChecker', 'finalValidator'],
|
|
113
|
-
'config': ['qualityChecker'],
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for agent in required.get(task_type, []):
|
|
117
|
-
agent_status = agents.get(agent, {})
|
|
118
|
-
if not agent_status.get('executed'):
|
|
119
|
-
errors.append(f"Required agent '{agent}' was not executed")
|
|
120
|
-
elif agent_status.get('result') == 'vetoed':
|
|
121
|
-
errors.append(f"Agent '{agent}' VETOED this task - resolve before committing")
|
|
122
|
-
|
|
123
|
-
# Check tests
|
|
124
|
-
if task_type in ['feature', 'fix']:
|
|
125
|
-
source_files = [f for f in staged_files if not any(
|
|
126
|
-
p in f.lower() for p in ['test', 'spec', '.md', 'docs/', '.json']
|
|
127
|
-
)]
|
|
128
|
-
tests_created = task.get('testsCreated', [])
|
|
129
|
-
|
|
130
|
-
if source_files and not tests_created:
|
|
131
|
-
errors.append(f"Modified {len(source_files)} source file(s) but no tests were created")
|
|
132
|
-
errors.append(" Run the tester agent to create tests")
|
|
133
|
-
|
|
134
|
-
# Check quality gates
|
|
135
|
-
quality = task.get('qualityGates', {})
|
|
136
|
-
for gate in ['typecheck', 'lint']:
|
|
137
|
-
gate_result = quality.get(gate, {})
|
|
138
|
-
if gate_result.get('passed') is False:
|
|
139
|
-
errors.append(f"Quality gate '{gate}' failed")
|
|
140
|
-
|
|
141
|
-
# Check security audit
|
|
142
|
-
security = task.get('securityAudit', {})
|
|
143
|
-
if security.get('result') == 'vetoed':
|
|
144
|
-
errors.append("Security audit VETOED - vulnerabilities must be fixed")
|
|
145
|
-
|
|
146
|
-
# Check final validation
|
|
147
|
-
final = task.get('finalValidation', {})
|
|
148
|
-
if not final.get('readyToCommit'):
|
|
149
|
-
if not final.get('executed'):
|
|
150
|
-
errors.append("Final validation was not executed")
|
|
151
|
-
errors.append(" Run the final-validator agent before committing")
|
|
152
|
-
else:
|
|
153
|
-
errors.append("Final validation did not approve commit")
|
|
154
|
-
violations = final.get('violations', [])
|
|
155
|
-
for v in violations[:5]: # Show first 5
|
|
156
|
-
errors.append(f" - {v}")
|
|
157
|
-
|
|
158
|
-
return len(errors) == 0, errors
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def main():
|
|
162
|
-
print("🔍 Validating workflow compliance...\n")
|
|
163
|
-
|
|
164
|
-
# Get staged files
|
|
165
|
-
staged_files = get_staged_files()
|
|
166
|
-
|
|
167
|
-
if not staged_files:
|
|
168
|
-
print("✅ No files staged for commit")
|
|
169
|
-
sys.exit(0)
|
|
170
|
-
|
|
171
|
-
# Load state
|
|
172
|
-
state = load_workflow_state()
|
|
173
|
-
|
|
174
|
-
# Validate
|
|
175
|
-
is_valid, errors = validate_commit(state, staged_files)
|
|
176
|
-
|
|
177
|
-
if is_valid:
|
|
178
|
-
print("✅ Workflow validation passed")
|
|
179
|
-
print(f" {len(staged_files)} file(s) ready to commit")
|
|
180
|
-
sys.exit(0)
|
|
181
|
-
else:
|
|
182
|
-
print("❌ COMMIT BLOCKED - Workflow validation failed\n")
|
|
183
|
-
print("Errors found:")
|
|
184
|
-
for error in errors:
|
|
185
|
-
print(f" {error}")
|
|
186
|
-
|
|
187
|
-
print("\n" + "=" * 50)
|
|
188
|
-
print("How to fix:")
|
|
189
|
-
print("1. Run the complete agent workflow (orchestrator → ... → final-validator)")
|
|
190
|
-
print("2. Ensure all agents executed and approved")
|
|
191
|
-
print("3. Create tests for new/modified code")
|
|
192
|
-
print("4. Pass all quality gates")
|
|
193
|
-
print("5. Get final validation approval")
|
|
194
|
-
print("=" * 50)
|
|
195
|
-
|
|
196
|
-
sys.exit(1)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if __name__ == '__main__':
|
|
200
|
-
main()
|