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.
Files changed (47) hide show
  1. package/README.md +149 -0
  2. package/dist/cli.js +199 -0
  3. package/package.json +42 -0
  4. package/template/.claude/CLAUDE.md +168 -0
  5. package/template/.claude/README.md +208 -0
  6. package/template/.claude/agents/analyzer.md +139 -0
  7. package/template/.claude/agents/commit-manager.md +231 -0
  8. package/template/.claude/agents/documenter.md +160 -0
  9. package/template/.claude/agents/domain-updater.md +200 -0
  10. package/template/.claude/agents/final-validator.md +182 -0
  11. package/template/.claude/agents/orchestrator.md +136 -0
  12. package/template/.claude/agents/quality-checker.md +264 -0
  13. package/template/.claude/agents/research.md +262 -0
  14. package/template/.claude/agents/security-auditor.md +199 -0
  15. package/template/.claude/agents/tester.md +572 -0
  16. package/template/.claude/agents/ui-ux-reviewer.md +180 -0
  17. package/template/.claude/commands/feature.md +102 -0
  18. package/template/.claude/commands/fix.md +80 -0
  19. package/template/.claude/commands/research.md +107 -0
  20. package/template/.claude/commands/validate.md +72 -0
  21. package/template/.claude/config/README.md +30 -0
  22. package/template/.claude/config/domain-mapping.json +26 -0
  23. package/template/.claude/config/project-config.json +53 -0
  24. package/template/.claude/config/quality-gates.json +46 -0
  25. package/template/.claude/config/security-rules.json +45 -0
  26. package/template/.claude/config/testing-config.json +168 -0
  27. package/template/.claude/hooks/SETUP.md +181 -0
  28. package/template/.claude/hooks/post-tool-use.py +155 -0
  29. package/template/.claude/hooks/pre-tool-use.py +159 -0
  30. package/template/.claude/hooks/security-check.js +202 -0
  31. package/template/.claude/hooks/stop-validation.py +155 -0
  32. package/template/.claude/hooks/user-prompt-submit.py +277 -0
  33. package/template/.claude/hooks/validate-commit.py +200 -0
  34. package/template/.claude/hooks/workflow-manager.py +350 -0
  35. package/template/.claude/settings.json +269 -0
  36. package/template/.claude/skills/codebase-knowledge/SKILL.md +145 -0
  37. package/template/.claude/skills/codebase-knowledge/TEMPLATE.md +35 -0
  38. package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +321 -0
  39. package/template/.claude/skills/docs-tracker/SKILL.md +239 -0
  40. package/template/.claude/skills/final-check/SKILL.md +284 -0
  41. package/template/.claude/skills/quality-gate/SKILL.md +278 -0
  42. package/template/.claude/skills/research-cache/SKILL.md +207 -0
  43. package/template/.claude/skills/security-scan/SKILL.md +206 -0
  44. package/template/.claude/skills/test-coverage/SKILL.md +441 -0
  45. package/template/.claude/skills/ui-ux-audit/SKILL.md +254 -0
  46. package/template/.claude/workflow-state.schema.json +200 -0
  47. package/template/CLAUDE.md +96 -0
@@ -0,0 +1,45 @@
1
+ {
2
+ "$comment": "Security rules. Used by security-auditor agent.",
3
+
4
+ "authentication": {
5
+ "framework": "session-based",
6
+ "userIdSource": "ctx.user._id",
7
+ "protectedProcedure": "protectedProcedure",
8
+ "sessionStore": "mongodb"
9
+ },
10
+
11
+ "validation": {
12
+ "library": "zod",
13
+ "requireOnAllRoutes": true
14
+ },
15
+
16
+ "sensitivePatterns": {
17
+ "forbidden": [
18
+ "input.userId",
19
+ "input.user_id",
20
+ "req.body.userId",
21
+ "passwordHash",
22
+ "password:"
23
+ ],
24
+ "files": ["auth/", "api/", "server/", "routers/"]
25
+ },
26
+
27
+ "cryptography": {
28
+ "passwordHashing": "bcrypt",
29
+ "minSaltRounds": 10,
30
+ "tokenGeneration": "crypto.randomBytes"
31
+ },
32
+
33
+ "cookies": {
34
+ "httpOnly": true,
35
+ "secure": true,
36
+ "sameSite": "strict"
37
+ },
38
+
39
+ "owaspChecks": {
40
+ "a01_brokenAccessControl": true,
41
+ "a02_cryptographicFailures": true,
42
+ "a03_injection": true,
43
+ "a07_authenticationFailures": true
44
+ }
45
+ }
@@ -0,0 +1,168 @@
1
+ {
2
+ "$comment": "Testing configuration. Used by tester agent.",
3
+
4
+ "framework": {
5
+ "unit": "vitest",
6
+ "e2e": "playwright",
7
+ "version": "1.40+"
8
+ },
9
+
10
+ "paths": {
11
+ "unitTests": "tests/unit/*.test.ts",
12
+ "e2eTests": "tests/e2e/**/*.spec.ts",
13
+ "fixtures": "tests/e2e/fixtures/",
14
+ "pages": "tests/e2e/pages/",
15
+ "flows": "tests/e2e/flows/",
16
+ "api": "tests/e2e/api/",
17
+ "authState": "tests/e2e/.auth/"
18
+ },
19
+
20
+ "viewports": {
21
+ "desktop": {
22
+ "device": "Desktop Chrome",
23
+ "width": 1280,
24
+ "height": 800,
25
+ "required": true
26
+ },
27
+ "tablet": {
28
+ "device": "iPad",
29
+ "width": 768,
30
+ "height": 1024,
31
+ "required": true
32
+ },
33
+ "mobile": {
34
+ "device": "iPhone SE",
35
+ "width": 375,
36
+ "height": 667,
37
+ "required": true
38
+ },
39
+ "mobileLarge": {
40
+ "device": "iPhone 14",
41
+ "width": 390,
42
+ "height": 844,
43
+ "required": false
44
+ }
45
+ },
46
+
47
+ "auth": {
48
+ "storageStatePath": "tests/e2e/.auth/user.json",
49
+ "loginPage": "/auth/login",
50
+ "registerPage": "/auth/register",
51
+ "protectedPrefix": "/app",
52
+ "setupProject": "setup",
53
+ "reuseSession": true
54
+ },
55
+
56
+ "database": {
57
+ "type": "mongodb",
58
+ "testConnectionEnv": "MONGODB_URI",
59
+ "cleanupStrategy": "fixture-tracking",
60
+ "verifyAfterActions": true
61
+ },
62
+
63
+ "cleanup": {
64
+ "strategy": "fixture-based",
65
+ "trackCreatedIds": true,
66
+ "deleteOnlyTracked": true,
67
+ "cleanupOnFailure": true,
68
+ "collections": ["users", "items", "sessions"]
69
+ },
70
+
71
+ "dataTestIds": {
72
+ "form": {
73
+ "nameInput": "name-input",
74
+ "emailInput": "email-input",
75
+ "passwordInput": "password-input",
76
+ "confirmPasswordInput": "confirm-password-input",
77
+ "submitButton": "submit-button"
78
+ },
79
+ "feedback": {
80
+ "errorMessage": "error-message",
81
+ "successMessage": "success-message",
82
+ "loadingSpinner": "loading-spinner"
83
+ },
84
+ "navigation": {
85
+ "sidebar": "sidebar",
86
+ "hamburgerMenu": "hamburger-menu",
87
+ "mobileNav": "mobile-nav",
88
+ "logoutButton": "logout-button"
89
+ },
90
+ "actions": {
91
+ "deleteButton": "delete-button",
92
+ "editButton": "edit-button",
93
+ "confirmDelete": "confirm-delete",
94
+ "cancelButton": "cancel-button"
95
+ }
96
+ },
97
+
98
+ "api": {
99
+ "rest": {
100
+ "baseUrl": "/api",
101
+ "authEndpoint": "/api/auth/login",
102
+ "validateInput": true,
103
+ "requireAuth": true
104
+ },
105
+ "trpc": {
106
+ "baseUrl": "/api/trpc",
107
+ "batchEnabled": true,
108
+ "validateInput": true
109
+ }
110
+ },
111
+
112
+ "security": {
113
+ "testForbiddenRequests": true,
114
+ "testRateLimiting": true,
115
+ "testCrossUserAccess": true,
116
+ "testUnauthenticated": true,
117
+ "expectedForbiddenStatus": 403,
118
+ "expectedUnauthorizedStatus": 401,
119
+ "expectedRateLimitStatus": 429
120
+ },
121
+
122
+ "flows": {
123
+ "required": [
124
+ "registration",
125
+ "login",
126
+ "logout",
127
+ "crud-create",
128
+ "crud-read",
129
+ "crud-update",
130
+ "crud-delete",
131
+ "permissions"
132
+ ],
133
+ "optional": [
134
+ "password-reset",
135
+ "email-verification",
136
+ "profile-update"
137
+ ]
138
+ },
139
+
140
+ "commands": {
141
+ "install": "bun add -D @playwright/test && bunx playwright install",
142
+ "run": "bunx playwright test",
143
+ "runUi": "bunx playwright test --ui",
144
+ "runHeaded": "bunx playwright test --headed",
145
+ "runMobile": "bunx playwright test --project='iPhone SE'",
146
+ "debug": "bunx playwright test --debug",
147
+ "report": "bunx playwright show-report",
148
+ "codegen": "bunx playwright codegen"
149
+ },
150
+
151
+ "rules": {
152
+ "noSkip": true,
153
+ "noMockAuth": true,
154
+ "requireCleanup": true,
155
+ "requireDbValidation": true,
156
+ "requireViewportTests": true,
157
+ "requireDataTestId": true,
158
+ "uniqueTestData": true,
159
+ "timestampEmails": true
160
+ },
161
+
162
+ "reporting": {
163
+ "trace": "on-first-retry",
164
+ "screenshot": "only-on-failure",
165
+ "video": "retain-on-failure",
166
+ "outputFolder": "test-results"
167
+ }
168
+ }
@@ -0,0 +1,181 @@
1
+ # Workflow Enforcement Hooks - Setup Guide
2
+
3
+ ## Overview
4
+
5
+ This system enforces the agent workflow by:
6
+
7
+ 1. **Blocking file modifications** until workflow is started and files are approved
8
+ 2. **Tracking all changes** automatically
9
+ 3. **Preventing incomplete workflow** from stopping
10
+ 4. **Blocking commits** that don't follow the workflow
11
+
12
+ ## Requirements
13
+
14
+ - Python 3.8+
15
+ - Git
16
+ - Husky (for pre-commit hooks)
17
+
18
+ ## Files
19
+
20
+ | File | Purpose |
21
+ | --------------------- | ------------------------------------------------ |
22
+ | `pre-tool-use.py` | Blocks Edit/Write unless file is approved |
23
+ | `post-tool-use.py` | Auto-tracks modifications to workflow-state.json |
24
+ | `stop-validation.py` | Blocks stopping if workflow incomplete |
25
+ | `validate-commit.py` | Husky pre-commit validation |
26
+ | `workflow-manager.py` | CLI for agents to update workflow state |
27
+
28
+ ## Husky Integration
29
+
30
+ ### 1. Install Husky
31
+
32
+ ```bash
33
+ bun add husky --dev
34
+ bunx husky init
35
+ ```
36
+
37
+ ### 2. Create Pre-Commit Hook
38
+
39
+ ```bash
40
+ # Create .husky/pre-commit
41
+ echo '#!/usr/bin/env sh
42
+ . "$(dirname -- "$0")/_/husky.sh"
43
+
44
+ python .claude/hooks/validate-commit.py
45
+ ' > .husky/pre-commit
46
+
47
+ chmod +x .husky/pre-commit
48
+ ```
49
+
50
+ ### 3. Add to package.json
51
+
52
+ ```json
53
+ {
54
+ "scripts": {
55
+ "prepare": "husky"
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Claude Code Configuration
61
+
62
+ The hooks are configured in `.claude/settings.json`:
63
+
64
+ ```json
65
+ {
66
+ "hooks": {
67
+ "PreToolUse": [
68
+ {
69
+ "matcher": "Edit|Write|NotebookEdit",
70
+ "hooks": [
71
+ {
72
+ "type": "command",
73
+ "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-tool-use.py\"",
74
+ "timeout": 30
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ "PostToolUse": [
80
+ {
81
+ "matcher": "Edit|Write|NotebookEdit",
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use.py\"",
86
+ "timeout": 30
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ "Stop": [
92
+ {
93
+ "matcher": "",
94
+ "hooks": [
95
+ {
96
+ "type": "command",
97
+ "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop-validation.py\"",
98
+ "timeout": 30
99
+ }
100
+ ]
101
+ }
102
+ ]
103
+ }
104
+ }
105
+ ```
106
+
107
+ **IMPORTANT:** Use `$CLAUDE_PROJECT_DIR` to ensure hooks work from any directory.
108
+
109
+ ## Workflow Manager CLI
110
+
111
+ ```bash
112
+ # Start a task (REQUIRED FIRST)
113
+ python .claude/hooks/workflow-manager.py start-task --type feature --description "Add user auth"
114
+
115
+ # Approve files for modification
116
+ python .claude/hooks/workflow-manager.py approve-files --files "src/auth.ts" "app/login/*"
117
+
118
+ # Mark agent as executed
119
+ python .claude/hooks/workflow-manager.py agent-executed --agent analyzer --result approved
120
+
121
+ # Record quality gate result
122
+ python .claude/hooks/workflow-manager.py quality-gate --gate typecheck --passed true
123
+
124
+ # Record security audit
125
+ python .claude/hooks/workflow-manager.py security-audit --result approved
126
+
127
+ # Final validation
128
+ python .claude/hooks/workflow-manager.py final-validation --result approved --ready-to-commit true
129
+
130
+ # Complete task after commit
131
+ python .claude/hooks/workflow-manager.py complete-task --commit-hash abc123def
132
+
133
+ # Check current status
134
+ python .claude/hooks/workflow-manager.py status
135
+ ```
136
+
137
+ ## Error Messages
138
+
139
+ ### "BLOCKED: No active task"
140
+
141
+ Run `workflow-manager.py start-task` first.
142
+
143
+ ### "BLOCKED: Analyzer agent has not executed"
144
+
145
+ Run the analyzer agent and call `workflow-manager.py agent-executed --agent analyzer --result approved`.
146
+
147
+ ### "BLOCKED: File not in approved list"
148
+
149
+ Add the file to approved list via `workflow-manager.py approve-files --files "path/to/file"`.
150
+
151
+ ### "WORKFLOW INCOMPLETE - Cannot stop yet"
152
+
153
+ Execute all required agents before stopping the session.
154
+
155
+ ### "COMMIT BLOCKED - Workflow validation failed"
156
+
157
+ Ensure all agents executed, tests created, and final validation approved.
158
+
159
+ ## Environment Variables
160
+
161
+ | Variable | Default | Description |
162
+ | -------------------- | ------------- | ---------------------- |
163
+ | `CLAUDE_PROJECT_DIR` | `os.getcwd()` | Project root directory |
164
+
165
+ ## Troubleshooting
166
+
167
+ ### Hooks not executing
168
+
169
+ 1. Verify Python is in PATH
170
+ 2. Check `.claude/settings.json` hooks configuration
171
+ 3. Ensure hook files have execute permissions
172
+
173
+ ### Workflow state corrupted
174
+
175
+ Delete `.claude/workflow-state.json` and start fresh.
176
+
177
+ ### Husky not running
178
+
179
+ 1. Run `bun run prepare`
180
+ 2. Check `.husky/pre-commit` has execute permissions
181
+ 3. Verify Git hooks are enabled: `git config core.hooksPath`
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse Hook - Track File Modifications
4
+
5
+ This hook records all file modifications to workflow-state.json
6
+ for later validation by Husky pre-commit hook.
7
+
8
+ Exit codes:
9
+ - 0: Always (tracking is non-blocking)
10
+ """
11
+
12
+ import json
13
+ import sys
14
+ import os
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ # Get project directory
19
+ PROJECT_DIR = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
20
+ WORKFLOW_STATE_PATH = Path(PROJECT_DIR) / '.claude' / 'workflow-state.json'
21
+
22
+ # Tools that modify files
23
+ FILE_MODIFY_TOOLS = {'Edit', 'Write', 'NotebookEdit'}
24
+ FILE_DELETE_TOOLS = {'Bash'} # rm commands
25
+
26
+ # Test file patterns
27
+ TEST_PATTERNS = ['test', 'spec', '.test.', '.spec.', '__tests__']
28
+
29
+ # Doc file patterns
30
+ DOC_PATTERNS = ['docs/', 'README', 'CHANGELOG', '.md', 'domains/']
31
+
32
+
33
+ def load_workflow_state():
34
+ """Load current workflow state"""
35
+ if not WORKFLOW_STATE_PATH.exists():
36
+ return {"version": "1.0.0", "currentTask": None, "sessions": []}
37
+ try:
38
+ with open(WORKFLOW_STATE_PATH) as f:
39
+ return json.load(f)
40
+ except (json.JSONDecodeError, IOError):
41
+ return {"version": "1.0.0", "currentTask": None, "sessions": []}
42
+
43
+
44
+ def save_workflow_state(state: dict):
45
+ """Save workflow state"""
46
+ WORKFLOW_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
47
+ with open(WORKFLOW_STATE_PATH, 'w') as f:
48
+ json.dump(state, f, indent=2)
49
+
50
+
51
+ def is_test_file(path: str) -> bool:
52
+ """Check if file is a test file"""
53
+ return any(p in path.lower() for p in TEST_PATTERNS)
54
+
55
+
56
+ def is_doc_file(path: str) -> bool:
57
+ """Check if file is a documentation file"""
58
+ return any(p in path.lower() for p in DOC_PATTERNS)
59
+
60
+
61
+ def main():
62
+ # Read hook input from stdin
63
+ try:
64
+ hook_input = json.load(sys.stdin)
65
+ except json.JSONDecodeError:
66
+ sys.exit(0)
67
+
68
+ tool_name = hook_input.get('tool_name', '')
69
+ tool_input = hook_input.get('tool_input', {})
70
+ tool_result = hook_input.get('tool_result', {})
71
+
72
+ # Only track file modification tools
73
+ if tool_name not in FILE_MODIFY_TOOLS:
74
+ sys.exit(0)
75
+
76
+ # Get file path
77
+ file_path = tool_input.get('file_path', tool_input.get('notebook_path', ''))
78
+
79
+ if not file_path:
80
+ sys.exit(0)
81
+
82
+ # Skip workflow state file itself
83
+ if 'workflow-state.json' in file_path:
84
+ sys.exit(0)
85
+
86
+ # Load state
87
+ state = load_workflow_state()
88
+
89
+ # If no current task, just exit (pre-tool-use should have blocked)
90
+ if not state.get('currentTask'):
91
+ sys.exit(0)
92
+
93
+ task = state['currentTask']
94
+
95
+ # Determine action type
96
+ # For Write tool, check if file existed
97
+ action = 'modified'
98
+ if tool_name == 'Write':
99
+ # Assume created if not in modified list
100
+ existing = [m['path'] for m in task.get('modifiedFiles', [])]
101
+ if file_path not in existing:
102
+ action = 'created'
103
+
104
+ # Normalize path
105
+ file_path = file_path.replace('\\', '/')
106
+
107
+ # Check if file was approved
108
+ approved_files = task.get('approvedFiles', [])
109
+ was_approved = any(
110
+ file_path.endswith(af) or file_path == af or
111
+ (af.endswith('*') and file_path.startswith(af[:-1]))
112
+ for af in approved_files
113
+ )
114
+
115
+ # Record modification
116
+ modification = {
117
+ 'path': file_path,
118
+ 'action': action,
119
+ 'timestamp': datetime.now().isoformat(),
120
+ 'approved': was_approved
121
+ }
122
+
123
+ if 'modifiedFiles' not in task:
124
+ task['modifiedFiles'] = []
125
+
126
+ # Update or add
127
+ existing_paths = [m['path'] for m in task['modifiedFiles']]
128
+ if file_path in existing_paths:
129
+ idx = existing_paths.index(file_path)
130
+ task['modifiedFiles'][idx] = modification
131
+ else:
132
+ task['modifiedFiles'].append(modification)
133
+
134
+ # Track test files
135
+ if is_test_file(file_path):
136
+ if 'testsCreated' not in task:
137
+ task['testsCreated'] = []
138
+ if file_path not in task['testsCreated']:
139
+ task['testsCreated'].append(file_path)
140
+
141
+ # Track doc files
142
+ if is_doc_file(file_path):
143
+ if 'docsUpdated' not in task:
144
+ task['docsUpdated'] = []
145
+ if file_path not in task['docsUpdated']:
146
+ task['docsUpdated'].append(file_path)
147
+
148
+ # Save state
149
+ save_workflow_state(state)
150
+
151
+ sys.exit(0)
152
+
153
+
154
+ if __name__ == '__main__':
155
+ main()
@@ -0,0 +1,159 @@
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()