start-vibing 1.1.1
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/README.md +149 -0
- package/dist/cli.js +199 -0
- package/package.json +42 -0
- package/template/.claude/CLAUDE.md +168 -0
- package/template/.claude/README.md +208 -0
- package/template/.claude/agents/analyzer.md +139 -0
- package/template/.claude/agents/commit-manager.md +231 -0
- package/template/.claude/agents/documenter.md +160 -0
- package/template/.claude/agents/domain-updater.md +200 -0
- package/template/.claude/agents/final-validator.md +182 -0
- package/template/.claude/agents/orchestrator.md +136 -0
- package/template/.claude/agents/quality-checker.md +264 -0
- package/template/.claude/agents/research.md +262 -0
- package/template/.claude/agents/security-auditor.md +199 -0
- package/template/.claude/agents/tester.md +572 -0
- package/template/.claude/agents/ui-ux-reviewer.md +180 -0
- package/template/.claude/commands/feature.md +102 -0
- package/template/.claude/commands/fix.md +80 -0
- package/template/.claude/commands/research.md +107 -0
- package/template/.claude/commands/validate.md +72 -0
- package/template/.claude/config/README.md +30 -0
- package/template/.claude/config/domain-mapping.json +26 -0
- package/template/.claude/config/project-config.json +53 -0
- package/template/.claude/config/quality-gates.json +46 -0
- package/template/.claude/config/security-rules.json +45 -0
- package/template/.claude/config/testing-config.json +168 -0
- package/template/.claude/hooks/SETUP.md +181 -0
- package/template/.claude/hooks/post-tool-use.py +155 -0
- package/template/.claude/hooks/pre-tool-use.py +159 -0
- package/template/.claude/hooks/security-check.js +202 -0
- package/template/.claude/hooks/stop-validation.py +155 -0
- package/template/.claude/hooks/user-prompt-submit.py +277 -0
- package/template/.claude/hooks/validate-commit.py +200 -0
- package/template/.claude/hooks/workflow-manager.py +350 -0
- package/template/.claude/settings.json +269 -0
- package/template/.claude/skills/codebase-knowledge/SKILL.md +145 -0
- package/template/.claude/skills/codebase-knowledge/TEMPLATE.md +35 -0
- package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +321 -0
- package/template/.claude/skills/docs-tracker/SKILL.md +239 -0
- package/template/.claude/skills/final-check/SKILL.md +284 -0
- package/template/.claude/skills/quality-gate/SKILL.md +278 -0
- package/template/.claude/skills/research-cache/SKILL.md +207 -0
- package/template/.claude/skills/security-scan/SKILL.md +206 -0
- package/template/.claude/skills/test-coverage/SKILL.md +441 -0
- package/template/.claude/skills/ui-ux-audit/SKILL.md +254 -0
- package/template/.claude/workflow-state.schema.json +200 -0
- package/template/CLAUDE.md +96 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook de Seguranca Pre-Tool
|
|
3
|
+
*
|
|
4
|
+
* Este hook e executado ANTES de qualquer ferramenta ser chamada.
|
|
5
|
+
* Sua funcao e bloquear acoes potencialmente perigosas.
|
|
6
|
+
*
|
|
7
|
+
* Baseado em: OpenSSF Security Guide for AI Code Assistants
|
|
8
|
+
* https://best.openssf.org/Security-Focused-Guide-for-AI-Code-Assistant-Instructions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Padroes perigosos que devem ser bloqueados
|
|
12
|
+
const DANGEROUS_PATTERNS = {
|
|
13
|
+
// Comandos destrutivos
|
|
14
|
+
commands: [
|
|
15
|
+
/rm\s+-rf\s+[\/~]/i, // rm -rf com path perigoso
|
|
16
|
+
/rm\s+-rf\s+\*/i, // rm -rf *
|
|
17
|
+
/sudo\s+rm/i, // sudo rm
|
|
18
|
+
/mkfs/i, // formatar disco
|
|
19
|
+
/dd\s+if=/i, // dd (pode destruir dados)
|
|
20
|
+
/>\s*\/dev\//i, // escrever em devices
|
|
21
|
+
/chmod\s+777/i, // permissoes muito abertas
|
|
22
|
+
/curl.*\|\s*(ba)?sh/i, // curl pipe to shell
|
|
23
|
+
/wget.*\|\s*(ba)?sh/i, // wget pipe to shell
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
// Padroes de codigo inseguro
|
|
27
|
+
code: [
|
|
28
|
+
/eval\s*\(/i, // eval()
|
|
29
|
+
/new\s+Function\s*\(/i, // new Function()
|
|
30
|
+
/innerHTML\s*=/i, // innerHTML assignment (XSS)
|
|
31
|
+
/document\.write\s*\(/i, // document.write (XSS)
|
|
32
|
+
/dangerouslySetInnerHTML/i, // React dangerous prop
|
|
33
|
+
/\$\{.*\}\s*\)/i, // Template injection em queries
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Exposicao de dados sensiveis
|
|
37
|
+
sensitive: [
|
|
38
|
+
/password\s*[:=]/i, // Senha hardcoded
|
|
39
|
+
/api[_-]?key\s*[:=]/i, // API key hardcoded
|
|
40
|
+
/secret\s*[:=]/i, // Secret hardcoded
|
|
41
|
+
/private[_-]?key/i, // Private key
|
|
42
|
+
/BEGIN\s+(RSA|DSA|EC)\s+PRIVATE/i, // Chave privada PEM
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
// Patterns especificos do projeto
|
|
46
|
+
project: [
|
|
47
|
+
/userId.*req\.body/i, // userId do request body
|
|
48
|
+
/userId.*input\./i, // userId do input tRPC
|
|
49
|
+
/findById\(.*input/i, // Query sem validacao de owner
|
|
50
|
+
/z\.any\(\)/i, // Zod any (sem validacao)
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Arquivos que nao devem ser modificados
|
|
55
|
+
const PROTECTED_FILES = ['.env', '.env.local', '.env.production', '.env.development', 'bun.lockb'];
|
|
56
|
+
|
|
57
|
+
// Diretorios que nao devem ser acessados
|
|
58
|
+
const PROTECTED_DIRS = ['/etc', '/var', '/usr', '/root', '/home', 'node_modules', '.git/objects'];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Verifica se um comando/codigo contem padroes perigosos
|
|
62
|
+
* @param {string} content - Conteudo a verificar
|
|
63
|
+
* @param {string} category - Categoria de padroes
|
|
64
|
+
* @returns {Object} - { blocked: boolean, reason: string }
|
|
65
|
+
*/
|
|
66
|
+
function checkDangerousPatterns(content, category) {
|
|
67
|
+
const patterns = DANGEROUS_PATTERNS[category] || [];
|
|
68
|
+
|
|
69
|
+
for (const pattern of patterns) {
|
|
70
|
+
if (pattern.test(content)) {
|
|
71
|
+
return {
|
|
72
|
+
blocked: true,
|
|
73
|
+
reason: `Padrao perigoso detectado: ${pattern.toString()}`,
|
|
74
|
+
category,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { blocked: false };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Verifica se um arquivo e protegido
|
|
84
|
+
* @param {string} filePath - Caminho do arquivo
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
function isProtectedFile(filePath) {
|
|
88
|
+
return PROTECTED_FILES.some(
|
|
89
|
+
(protected) => filePath.endsWith(protected) || filePath.includes(protected)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Verifica se um diretorio e protegido
|
|
95
|
+
* @param {string} dirPath - Caminho do diretorio
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
*/
|
|
98
|
+
function isProtectedDir(dirPath) {
|
|
99
|
+
return PROTECTED_DIRS.some(
|
|
100
|
+
(protected) => dirPath.startsWith(protected) || dirPath.includes(protected)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hook principal - executado antes de cada tool call
|
|
106
|
+
* @param {Object} toolCall - Dados da chamada de ferramenta
|
|
107
|
+
* @returns {Object} - { allowed: boolean, reason?: string }
|
|
108
|
+
*/
|
|
109
|
+
function preToolHook(toolCall) {
|
|
110
|
+
const { name, args } = toolCall;
|
|
111
|
+
|
|
112
|
+
// Verificar comandos bash
|
|
113
|
+
if (name === 'bash' && args.command) {
|
|
114
|
+
const result = checkDangerousPatterns(args.command, 'commands');
|
|
115
|
+
if (result.blocked) {
|
|
116
|
+
return {
|
|
117
|
+
allowed: false,
|
|
118
|
+
reason: `Comando bloqueado: ${result.reason}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Verificar escrita de arquivos
|
|
124
|
+
if (['file_write', 'file_edit'].includes(name)) {
|
|
125
|
+
// Verificar arquivo protegido
|
|
126
|
+
if (args.path && isProtectedFile(args.path)) {
|
|
127
|
+
return {
|
|
128
|
+
allowed: false,
|
|
129
|
+
reason: `Arquivo protegido: ${args.path}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Verificar conteudo perigoso
|
|
134
|
+
if (args.content) {
|
|
135
|
+
const codeResult = checkDangerousPatterns(args.content, 'code');
|
|
136
|
+
if (codeResult.blocked) {
|
|
137
|
+
return {
|
|
138
|
+
allowed: false,
|
|
139
|
+
reason: `Codigo inseguro: ${codeResult.reason}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const sensitiveResult = checkDangerousPatterns(args.content, 'sensitive');
|
|
144
|
+
if (sensitiveResult.blocked) {
|
|
145
|
+
return {
|
|
146
|
+
allowed: false,
|
|
147
|
+
reason: `Dados sensiveis detectados: ${sensitiveResult.reason}`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const projectResult = checkDangerousPatterns(args.content, 'project');
|
|
152
|
+
if (projectResult.blocked) {
|
|
153
|
+
return {
|
|
154
|
+
allowed: false,
|
|
155
|
+
reason: `Violacao de regra do projeto: ${projectResult.reason}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Verificar leitura de diretorios protegidos
|
|
162
|
+
if (name === 'file_read' && args.path) {
|
|
163
|
+
if (isProtectedDir(args.path)) {
|
|
164
|
+
return {
|
|
165
|
+
allowed: false,
|
|
166
|
+
reason: `Diretorio protegido: ${args.path}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Permitir por padrao
|
|
172
|
+
return { allowed: true };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Log de seguranca para auditoria
|
|
177
|
+
* @param {string} action - Acao tomada
|
|
178
|
+
* @param {Object} details - Detalhes
|
|
179
|
+
*/
|
|
180
|
+
function logSecurityEvent(action, details) {
|
|
181
|
+
const timestamp = new Date().toISOString();
|
|
182
|
+
const logEntry = {
|
|
183
|
+
timestamp,
|
|
184
|
+
action,
|
|
185
|
+
...details,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Em producao, enviar para sistema de logging
|
|
189
|
+
console.log('[SECURITY]', JSON.stringify(logEntry));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Exportar para uso pelo SDK
|
|
193
|
+
module.exports = {
|
|
194
|
+
preToolHook,
|
|
195
|
+
checkDangerousPatterns,
|
|
196
|
+
isProtectedFile,
|
|
197
|
+
isProtectedDir,
|
|
198
|
+
logSecurityEvent,
|
|
199
|
+
DANGEROUS_PATTERNS,
|
|
200
|
+
PROTECTED_FILES,
|
|
201
|
+
PROTECTED_DIRS,
|
|
202
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
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()
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UserPromptSubmit Hook - Agent Selection Enforcement
|
|
4
|
+
|
|
5
|
+
This hook runs BEFORE Claude processes any user prompt and:
|
|
6
|
+
1. Lists all available agents
|
|
7
|
+
2. Analyzes the prompt to suggest the best agent
|
|
8
|
+
3. Reminds Claude to follow the workflow strictly
|
|
9
|
+
4. Prevents over-engineering and redundant file creation
|
|
10
|
+
|
|
11
|
+
Output is sent to Claude as context before processing the user's request.
|
|
12
|
+
|
|
13
|
+
Based on official Claude Code documentation:
|
|
14
|
+
https://code.claude.com/docs/en/hooks.md
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
PROJECT_DIR = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
|
|
24
|
+
WORKFLOW_STATE_PATH = Path(PROJECT_DIR) / '.claude' / 'workflow-state.json'
|
|
25
|
+
SETTINGS_PATH = Path(PROJECT_DIR) / '.claude' / 'settings.json'
|
|
26
|
+
|
|
27
|
+
# Agent definitions with triggers
|
|
28
|
+
AGENTS = {
|
|
29
|
+
'orchestrator': {
|
|
30
|
+
'description': 'Coordinates entire development flow',
|
|
31
|
+
'triggers': ['implement feature', 'build', 'create', 'fix and test', 'full workflow', 'multi-step'],
|
|
32
|
+
'priority': 1,
|
|
33
|
+
'when': 'Task requires >2 agents or touches >3 files'
|
|
34
|
+
},
|
|
35
|
+
'analyzer': {
|
|
36
|
+
'description': 'Analyzes impact before code modification',
|
|
37
|
+
'triggers': ['implement', 'add feature', 'fix bug', 'refactor', 'change', 'modify', 'update code'],
|
|
38
|
+
'priority': 2,
|
|
39
|
+
'when': 'BEFORE any Edit/Write to source files'
|
|
40
|
+
},
|
|
41
|
+
'research': {
|
|
42
|
+
'description': 'Researches best practices and recent solutions (2024-2025)',
|
|
43
|
+
'triggers': ['research', 'investigate', 'study', 'best practice', 'how to', 'pattern'],
|
|
44
|
+
'priority': 3,
|
|
45
|
+
'when': 'AFTER analyzer - finds best practices before implementation'
|
|
46
|
+
},
|
|
47
|
+
'ui-ux-reviewer': {
|
|
48
|
+
'description': 'Reviews UI/UX and researches competitors',
|
|
49
|
+
'triggers': ['ui', 'component', 'page', 'design', 'layout', 'style', 'responsive', 'frontend'],
|
|
50
|
+
'priority': 3,
|
|
51
|
+
'when': 'Files in components/, app/, or .tsx with JSX'
|
|
52
|
+
},
|
|
53
|
+
'documenter': {
|
|
54
|
+
'description': 'Creates and maintains documentation',
|
|
55
|
+
'triggers': ['document', 'readme', 'docs', 'explain'],
|
|
56
|
+
'priority': 4,
|
|
57
|
+
'when': 'After code implementation completes'
|
|
58
|
+
},
|
|
59
|
+
'tester': {
|
|
60
|
+
'description': 'Creates unit tests (Vitest) and E2E tests (Playwright)',
|
|
61
|
+
'triggers': ['test', 'coverage', 'spec', 'e2e'],
|
|
62
|
+
'priority': 5,
|
|
63
|
+
'when': 'After any code implementation'
|
|
64
|
+
},
|
|
65
|
+
'security-auditor': {
|
|
66
|
+
'description': 'Audits security (CAN VETO)',
|
|
67
|
+
'triggers': ['auth', 'session', 'password', 'token', 'api', 'database', 'user data', 'security'],
|
|
68
|
+
'priority': 6,
|
|
69
|
+
'when': 'Code touches auth/, api/, server/, or sensitive data',
|
|
70
|
+
'can_veto': True
|
|
71
|
+
},
|
|
72
|
+
'quality-checker': {
|
|
73
|
+
'description': 'Runs typecheck, lint, test, build',
|
|
74
|
+
'triggers': ['check', 'verify', 'validate', 'run tests', 'quality'],
|
|
75
|
+
'priority': 7,
|
|
76
|
+
'when': 'After implementation and tests complete'
|
|
77
|
+
},
|
|
78
|
+
'final-validator': {
|
|
79
|
+
'description': 'Final validation before commit (CAN VETO)',
|
|
80
|
+
'triggers': ['final', 'commit ready', 'approve'],
|
|
81
|
+
'priority': 8,
|
|
82
|
+
'when': 'Before any commit operation',
|
|
83
|
+
'can_veto': True
|
|
84
|
+
},
|
|
85
|
+
'commit-manager': {
|
|
86
|
+
'description': 'Creates conventional commits and completes workflow',
|
|
87
|
+
'triggers': ['commit', 'save changes', 'push', 'finalize', 'git'],
|
|
88
|
+
'priority': 10,
|
|
89
|
+
'when': 'FINAL step - after domain-updater, runs complete-task'
|
|
90
|
+
},
|
|
91
|
+
'domain-updater': {
|
|
92
|
+
'description': 'Updates domain knowledge with session learnings',
|
|
93
|
+
'triggers': ['update domain', 'document learnings', 'session end'],
|
|
94
|
+
'priority': 9,
|
|
95
|
+
'when': 'BEFORE commit-manager - updates domains so git stays clean'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Workflow sequences (research after analyzer, domain-updater BEFORE commit-manager for clean git)
|
|
100
|
+
WORKFLOWS = {
|
|
101
|
+
'feature': ['analyzer', 'research', 'ui-ux-reviewer', 'documenter', 'tester', 'security-auditor', 'quality-checker', 'final-validator', 'domain-updater', 'commit-manager'],
|
|
102
|
+
'fix': ['analyzer', 'research', 'tester', 'security-auditor', 'quality-checker', 'final-validator', 'domain-updater', 'commit-manager'],
|
|
103
|
+
'refactor': ['analyzer', 'tester', 'quality-checker', 'final-validator', 'domain-updater', 'commit-manager'],
|
|
104
|
+
'config': ['quality-checker', 'domain-updater', 'commit-manager']
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def load_workflow_state():
|
|
109
|
+
"""Load current workflow state"""
|
|
110
|
+
if not WORKFLOW_STATE_PATH.exists():
|
|
111
|
+
return None
|
|
112
|
+
try:
|
|
113
|
+
with open(WORKFLOW_STATE_PATH) as f:
|
|
114
|
+
return json.load(f)
|
|
115
|
+
except (json.JSONDecodeError, IOError):
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def detect_task_type(prompt: str) -> str:
|
|
120
|
+
"""Detect task type from prompt"""
|
|
121
|
+
prompt_lower = prompt.lower()
|
|
122
|
+
|
|
123
|
+
if any(word in prompt_lower for word in ['bug', 'fix', 'error', 'broken', 'not working', 'issue']):
|
|
124
|
+
return 'fix'
|
|
125
|
+
elif any(word in prompt_lower for word in ['refactor', 'restructure', 'reorganize', 'clean up']):
|
|
126
|
+
return 'refactor'
|
|
127
|
+
elif any(word in prompt_lower for word in ['config', 'setting', 'env', 'package.json', 'tsconfig']):
|
|
128
|
+
return 'config'
|
|
129
|
+
else:
|
|
130
|
+
return 'feature'
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def detect_best_agent(prompt: str) -> tuple[str, str]:
|
|
134
|
+
"""Detect best agent for the task"""
|
|
135
|
+
prompt_lower = prompt.lower()
|
|
136
|
+
|
|
137
|
+
# Check for multi-step tasks first
|
|
138
|
+
multi_step_indicators = ['and then', 'after that', 'also', 'implement', 'build', 'create feature']
|
|
139
|
+
if any(indicator in prompt_lower for indicator in multi_step_indicators):
|
|
140
|
+
return 'orchestrator', 'Multi-step task detected - orchestrator will coordinate all agents'
|
|
141
|
+
|
|
142
|
+
# Check each agent's triggers
|
|
143
|
+
matches = []
|
|
144
|
+
for agent_name, agent_info in AGENTS.items():
|
|
145
|
+
for trigger in agent_info['triggers']:
|
|
146
|
+
if trigger in prompt_lower:
|
|
147
|
+
matches.append((agent_name, agent_info['priority'], trigger))
|
|
148
|
+
|
|
149
|
+
if matches:
|
|
150
|
+
# Sort by priority (lower is better)
|
|
151
|
+
matches.sort(key=lambda x: x[1])
|
|
152
|
+
best = matches[0]
|
|
153
|
+
return best[0], f"Matched trigger '{best[2]}'"
|
|
154
|
+
|
|
155
|
+
# Default to orchestrator for unknown tasks
|
|
156
|
+
return 'orchestrator', 'No specific trigger matched - orchestrator will analyze'
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def format_agent_list() -> str:
|
|
160
|
+
"""Format agent list for display"""
|
|
161
|
+
lines = []
|
|
162
|
+
for name, info in sorted(AGENTS.items(), key=lambda x: x[1]['priority']):
|
|
163
|
+
veto = ' [CAN VETO]' if info.get('can_veto') else ''
|
|
164
|
+
lines.append(f" - {name}: {info['description']}{veto}")
|
|
165
|
+
return '\n'.join(lines)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def format_workflow_status(state: dict) -> str:
|
|
169
|
+
"""Format current workflow status"""
|
|
170
|
+
if not state or not state.get('currentTask'):
|
|
171
|
+
return "STATUS: No active task. Start with orchestrator or workflow-manager.py start-task"
|
|
172
|
+
|
|
173
|
+
task = state['currentTask']
|
|
174
|
+
agents = task.get('agents', {})
|
|
175
|
+
|
|
176
|
+
executed = [name for name, status in agents.items() if status.get('executed')]
|
|
177
|
+
pending = [name for name, status in agents.items() if not status.get('executed')]
|
|
178
|
+
|
|
179
|
+
return f"""STATUS: Task active
|
|
180
|
+
ID: {task.get('id', 'unknown')}
|
|
181
|
+
Type: {task.get('type', 'unknown')}
|
|
182
|
+
Description: {task.get('description', 'unknown')}
|
|
183
|
+
Executed: {', '.join(executed) if executed else 'none'}
|
|
184
|
+
Pending: {', '.join(pending) if pending else 'none'}
|
|
185
|
+
Approved files: {len(task.get('approvedFiles', []))}"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
# Read hook input from stdin
|
|
190
|
+
try:
|
|
191
|
+
hook_input = json.load(sys.stdin)
|
|
192
|
+
except json.JSONDecodeError:
|
|
193
|
+
hook_input = {}
|
|
194
|
+
|
|
195
|
+
# Get user prompt (may not be available in all hook contexts)
|
|
196
|
+
prompt = hook_input.get('user_prompt', hook_input.get('prompt', ''))
|
|
197
|
+
|
|
198
|
+
# Load current state
|
|
199
|
+
state = load_workflow_state()
|
|
200
|
+
|
|
201
|
+
# Detect task type and best agent
|
|
202
|
+
task_type = detect_task_type(prompt) if prompt else 'unknown'
|
|
203
|
+
best_agent, reason = detect_best_agent(prompt) if prompt else ('orchestrator', 'Default')
|
|
204
|
+
|
|
205
|
+
# Build output message
|
|
206
|
+
output = f"""
|
|
207
|
+
================================================================================
|
|
208
|
+
WORKFLOW ENFORCEMENT - UserPromptSubmit Hook
|
|
209
|
+
================================================================================
|
|
210
|
+
|
|
211
|
+
AVAILABLE AGENTS:
|
|
212
|
+
{format_agent_list()}
|
|
213
|
+
|
|
214
|
+
TASK ANALYSIS:
|
|
215
|
+
Detected type: {task_type}
|
|
216
|
+
Recommended agent: {best_agent}
|
|
217
|
+
Reason: {reason}
|
|
218
|
+
Workflow sequence: {' -> '.join(WORKFLOWS.get(task_type, WORKFLOWS['feature']))}
|
|
219
|
+
|
|
220
|
+
{format_workflow_status(state)}
|
|
221
|
+
|
|
222
|
+
================================================================================
|
|
223
|
+
CRITICAL: INVOKE AGENTS VIA TASK TOOL - NOT MANUALLY
|
|
224
|
+
================================================================================
|
|
225
|
+
|
|
226
|
+
You MUST use the Task tool with subagent_type to invoke agents.
|
|
227
|
+
DO NOT execute agent logic manually - INVOKE the agent properly.
|
|
228
|
+
|
|
229
|
+
AGENTS THAT CAN BE INVOKED VIA TASK TOOL:
|
|
230
|
+
- commit-manager: Use Task(subagent_type="commit-manager", prompt="...")
|
|
231
|
+
- analyzer: Use Task(subagent_type="analyzer", prompt="...")
|
|
232
|
+
- tester: Use Task(subagent_type="tester", prompt="...")
|
|
233
|
+
- security-auditor: Use Task(subagent_type="security-auditor", prompt="...")
|
|
234
|
+
- quality-checker: Use Task(subagent_type="quality-checker", prompt="...")
|
|
235
|
+
- final-validator: Use Task(subagent_type="final-validator", prompt="...")
|
|
236
|
+
- documenter: Use Task(subagent_type="documenter", prompt="...")
|
|
237
|
+
- ui-ux-reviewer: Use Task(subagent_type="ui-ux-reviewer", prompt="...")
|
|
238
|
+
- orchestrator: Use Task(subagent_type="orchestrator", prompt="...")
|
|
239
|
+
|
|
240
|
+
WRONG (manual execution):
|
|
241
|
+
"Let me create the commit..." then git commands directly
|
|
242
|
+
|
|
243
|
+
CORRECT (agent invocation):
|
|
244
|
+
Use Task tool with subagent_type="commit-manager"
|
|
245
|
+
|
|
246
|
+
================================================================================
|
|
247
|
+
MANDATORY RULES:
|
|
248
|
+
================================================================================
|
|
249
|
+
|
|
250
|
+
1. I WILL use Task tool to invoke agent: {best_agent}
|
|
251
|
+
2. I WILL NOT execute agent logic manually - ALWAYS invoke via Task tool
|
|
252
|
+
3. I WILL NOT create redundant files
|
|
253
|
+
4. I WILL NOT over-engineer - keep solutions simple and focused
|
|
254
|
+
5. I WILL follow the workflow sequence strictly
|
|
255
|
+
6. I MUST run analyzer BEFORE any Edit/Write to source files
|
|
256
|
+
7. I MUST run commit-manager via Task tool for ALL commits
|
|
257
|
+
8. After commit, I MUST update domains and run complete-task
|
|
258
|
+
|
|
259
|
+
WORKFLOW COMMANDS (for tracking, not replacement for agents):
|
|
260
|
+
Start task: python .claude/hooks/workflow-manager.py start-task --type {task_type} --description "..."
|
|
261
|
+
Approve files: python .claude/hooks/workflow-manager.py approve-files --files "path/to/file"
|
|
262
|
+
|
|
263
|
+
================================================================================
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
# Return as JSON with decision to continue
|
|
267
|
+
result = {
|
|
268
|
+
"continue": True,
|
|
269
|
+
"systemMessage": output
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
print(json.dumps(result))
|
|
273
|
+
sys.exit(0)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
if __name__ == '__main__':
|
|
277
|
+
main()
|