claude-dev-env 1.0.0
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/LICENSE +21 -0
- package/README.md +219 -0
- package/agents/agent-writer.md +157 -0
- package/agents/clasp-deployment-orchestrator.md +609 -0
- package/agents/clean-coder.md +295 -0
- package/agents/code-quality-agent.md +40 -0
- package/agents/code-standards-agent.md +93 -0
- package/agents/config-centralizer.md +686 -0
- package/agents/config-extraction-agent.md +225 -0
- package/agents/doc-orchestrator.md +47 -0
- package/agents/docs-agent.md +112 -0
- package/agents/docx-agent.md +211 -0
- package/agents/git-commit-crafter.md +100 -0
- package/agents/magic-value-eliminator-agent.md +72 -0
- package/agents/mandatory-agent-workflow-agent.md +88 -0
- package/agents/parallel-workflow-coordinator.md +779 -0
- package/agents/pdf-agent.md +302 -0
- package/agents/plan-executor.md +226 -0
- package/agents/pr-description-writer.md +87 -0
- package/agents/project-context-loader.md +238 -0
- package/agents/project-docs-analyzer.md +54 -0
- package/agents/project-structure-organizer-agent.md +72 -0
- package/agents/readability-review-agent.md +76 -0
- package/agents/refactoring-specialist.md +69 -0
- package/agents/right-sized-engineer.md +129 -0
- package/agents/session-continuity-manager.md +53 -0
- package/agents/skill-to-agent-converter.md +371 -0
- package/agents/skill-writer-agent.md +470 -0
- package/agents/stub-detector-agent.md +140 -0
- package/agents/tdd-test-writer.md +62 -0
- package/agents/test-data-builder.md +68 -0
- package/agents/tooling-builder.md +78 -0
- package/agents/user-docs-writer.md +67 -0
- package/agents/validation-expert.md +71 -0
- package/agents/workflow-visual-documenter.md +82 -0
- package/agents/xlsx-agent.md +169 -0
- package/bin/install.mjs +256 -0
- package/commands/commit.md +28 -0
- package/commands/docupdate.md +322 -0
- package/commands/implement.md +102 -0
- package/commands/initialize.md +91 -0
- package/commands/plan.md +63 -0
- package/commands/pr-comments.md +47 -0
- package/commands/readability-review.md +20 -0
- package/commands/review-plan.md +7 -0
- package/commands/right-size.md +15 -0
- package/commands/stubcheck.md +89 -0
- package/commands/sum.md +30 -0
- package/docs/CODE_RULES.md +186 -0
- package/docs/DJANGO_PATTERNS.md +80 -0
- package/docs/REACT_PATTERNS.md +185 -0
- package/docs/TEST_QUALITY.md +104 -0
- package/hooks/advisory/migration-safety-advisor.py +49 -0
- package/hooks/advisory/refactor-guard.py +205 -0
- package/hooks/blocking/block-main-commit.py +168 -0
- package/hooks/blocking/code-rules-enforcer.py +549 -0
- package/hooks/blocking/destructive-command-blocker.py +107 -0
- package/hooks/blocking/docker-settings-guard.py +44 -0
- package/hooks/blocking/hedging-language-blocker.py +130 -0
- package/hooks/blocking/parallel-task-blocker.py +69 -0
- package/hooks/blocking/pr-description-enforcer.py +87 -0
- package/hooks/blocking/pyautogui-scroll-blocker.py +74 -0
- package/hooks/blocking/sensitive-file-protector.py +70 -0
- package/hooks/blocking/tdd-enforcer.py +62 -0
- package/hooks/blocking/test-preflight-check.py +343 -0
- package/hooks/blocking/write-existing-file-blocker.py +63 -0
- package/hooks/git-hooks/post-commit.py +103 -0
- package/hooks/github-action/test_workflow.py +33 -0
- package/hooks/hooks.json +246 -0
- package/hooks/lifecycle/config-change-guard.py +84 -0
- package/hooks/lifecycle/session-end-cleanup.py +59 -0
- package/hooks/notification/attention-needed-notify.py +63 -0
- package/hooks/notification/claude-notification-handler.py +59 -0
- package/hooks/notification/notification_utils.py +206 -0
- package/hooks/rewrite-plugin-paths.py +116 -0
- package/hooks/session/bulk-edit-reminder.py +30 -0
- package/hooks/session/code-rules-reminder.py +97 -0
- package/hooks/session/compact-context-reinject.py +39 -0
- package/hooks/session/hook-structure-context.py +140 -0
- package/hooks/session/plugin-data-dir-cleanup.py +39 -0
- package/hooks/validation/code-style-validator.py +145 -0
- package/hooks/validation/e2e-test-validator.py +142 -0
- package/hooks/validation/hook-format-validator.py +66 -0
- package/hooks/validation/mypy_validator.py +180 -0
- package/hooks/validators/README.md +125 -0
- package/hooks/validators/VALIDATION_REPORT.md +287 -0
- package/hooks/validators/__init__.py +19 -0
- package/hooks/validators/abbreviation_checks.py +82 -0
- package/hooks/validators/code_quality_checks.py +133 -0
- package/hooks/validators/comment_checks.py +188 -0
- package/hooks/validators/file_structure_checks.py +182 -0
- package/hooks/validators/git_checks.py +107 -0
- package/hooks/validators/health_check.py +214 -0
- package/hooks/validators/magic_value_checks.py +81 -0
- package/hooks/validators/mypy_integration.py +52 -0
- package/hooks/validators/output_formatter.py +266 -0
- package/hooks/validators/pr_reference_checks.py +72 -0
- package/hooks/validators/python_antipattern_checks.py +110 -0
- package/hooks/validators/python_style_checks.py +364 -0
- package/hooks/validators/react_checks.py +90 -0
- package/hooks/validators/ruff_integration.py +80 -0
- package/hooks/validators/run_all_validators.py +772 -0
- package/hooks/validators/security_checks.py +135 -0
- package/hooks/validators/test_abbreviation_checks.py +76 -0
- package/hooks/validators/test_bad.tsx +7 -0
- package/hooks/validators/test_code_quality_checks.py +129 -0
- package/hooks/validators/test_file_structure_checks.py +307 -0
- package/hooks/validators/test_files/01_basic_component.tsx +10 -0
- package/hooks/validators/test_files/02_component_without_react.tsx +10 -0
- package/hooks/validators/test_files/03_pure_component.tsx +10 -0
- package/hooks/validators/test_files/04_pure_component_import.tsx +10 -0
- package/hooks/validators/test_files/05_typescript_generics.tsx +14 -0
- package/hooks/validators/test_files/06_typescript_two_generics.tsx +18 -0
- package/hooks/validators/test_files/07_multiline_declaration.tsx +11 -0
- package/hooks/validators/test_files/08_error_boundary_valid.tsx +14 -0
- package/hooks/validators/test_files/09_error_boundary_with_other_class.tsx +20 -0
- package/hooks/validators/test_files/10_inheritance_chain.tsx +16 -0
- package/hooks/validators/test_files/11_ts_file.ts +10 -0
- package/hooks/validators/test_files/12_non_react_class.tsx +14 -0
- package/hooks/validators/test_files/13_functional_component.tsx +8 -0
- package/hooks/validators/test_files/14_indented_class.tsx +13 -0
- package/hooks/validators/test_files/15_getDerivedStateFromError.tsx +14 -0
- package/hooks/validators/test_files/16_mixed_components.tsx +20 -0
- package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +175 -0
- package/hooks/validators/test_files/TEST_RESULTS_TABLE.txt +60 -0
- package/hooks/validators/test_files/VALIDATION_REPORT.md +201 -0
- package/hooks/validators/test_files/async_views.py +23 -0
- package/hooks/validators/test_files/async_with_imports.py +14 -0
- package/hooks/validators/test_files/bad_inline_imports.py +37 -0
- package/hooks/validators/test_files/management/commands/cmd_01_no_debug_check.py +10 -0
- package/hooks/validators/test_files/management/commands/cmd_02_proper_debug_check.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_03_debug_check_with_return.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_04_imported_DEBUG.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_05_debug_check_in_helper.py +16 -0
- package/hooks/validators/test_files/management/commands/cmd_06_debug_check_late.py +22 -0
- package/hooks/validators/test_files/management/commands/cmd_07_positive_debug_check.py +15 -0
- package/hooks/validators/test_files/management/commands/cmd_08_debug_with_and.py +14 -0
- package/hooks/validators/test_files/not_management_command.py +10 -0
- package/hooks/validators/test_files/skip_decorators/test_01_simple_skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_02_pytest_skipif.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_03_unittest_skipIf.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_04_skip_with_parens.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_05_xfail.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_06_custom_skip.py +11 -0
- package/hooks/validators/test_files/skip_decorators/test_07_capital_Skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_08_skipUnless.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_09_pytest_mark_skip_simple.py +7 -0
- package/hooks/validators/test_files/test_async_functions.py +45 -0
- package/hooks/validators/test_files/test_purecomponent/PureComponentExample.tsx +7 -0
- package/hooks/validators/test_files/test_purecomponent/ReactPureComponentExample.tsx +7 -0
- package/hooks/validators/test_git_checks.py +295 -0
- package/hooks/validators/test_good.tsx +5 -0
- package/hooks/validators/test_health_check.py +57 -0
- package/hooks/validators/test_magic_value_checks.py +63 -0
- package/hooks/validators/test_mypy_integration.py +27 -0
- package/hooks/validators/test_output_formatter.py +150 -0
- package/hooks/validators/test_pr_reference_checks.py +41 -0
- package/hooks/validators/test_python_antipattern_checks.py +113 -0
- package/hooks/validators/test_python_style_checks.py +439 -0
- package/hooks/validators/test_react_checks.py +213 -0
- package/hooks/validators/test_results.txt +25 -0
- package/hooks/validators/test_ruff_integration.py +27 -0
- package/hooks/validators/test_run_all_validators.py +228 -0
- package/hooks/validators/test_run_all_validators_integration.py +48 -0
- package/hooks/validators/test_safety_checks.py +243 -0
- package/hooks/validators/test_security_checks.py +105 -0
- package/hooks/validators/test_test_safety_checks.py +321 -0
- package/hooks/validators/test_todo_checks.py +39 -0
- package/hooks/validators/test_type_safety_checks.py +85 -0
- package/hooks/validators/test_useless_test_checks.py +55 -0
- package/hooks/validators/test_validator_base.py +26 -0
- package/hooks/validators/test_verify_paths.py +34 -0
- package/hooks/validators/todo_checks.py +59 -0
- package/hooks/validators/type_safety_checks.py +101 -0
- package/hooks/validators/useless_test_checks.py +92 -0
- package/hooks/validators/validator_base.py +19 -0
- package/hooks/validators/verify_paths.py +57 -0
- package/hooks/workflow/auto-formatter.py +114 -0
- package/hooks/workflow/investigation-tracker-reset.py +46 -0
- package/package.json +30 -0
- package/rules/agent-spawn-protocol.md +47 -0
- package/rules/cleanup-temp-files.md +27 -0
- package/rules/code-reviews.md +11 -0
- package/rules/code-standards.md +43 -0
- package/rules/conservative-action.md +20 -0
- package/rules/context7.md +12 -0
- package/rules/explore-thoroughly.md +27 -0
- package/rules/git-workflow.md +42 -0
- package/rules/parallel-tools.md +23 -0
- package/rules/research-mode.md +23 -0
- package/rules/right-sized-engineering.md +28 -0
- package/rules/tdd.md +7 -0
- package/rules/testing.md +12 -0
- package/skills/agent-prompt/SKILL.md +102 -0
- package/skills/anthropic-plan/SKILL.md +107 -0
- package/skills/everything-search/SKILL.md +144 -0
- package/skills/ingest/SKILL.md +40 -0
- package/skills/npm-creator/SKILL.md +183 -0
- package/skills/pr-review-responder/EXAMPLES.md +590 -0
- package/skills/pr-review-responder/PRINCIPLES.md +539 -0
- package/skills/pr-review-responder/README.md +209 -0
- package/skills/pr-review-responder/SKILL.md +202 -0
- package/skills/pr-review-responder/TESTING.md +407 -0
- package/skills/pr-review-responder/scripts/respond_to_reviews.py +376 -0
- package/skills/pr-review-responder/update_skill.py +297 -0
- package/skills/prompt-generator/REFERENCE.md +150 -0
- package/skills/prompt-generator/SKILL.md +154 -0
- package/skills/readability-review/SKILL.md +127 -0
- package/skills/recall/SKILL.md +27 -0
- package/skills/remember/SKILL.md +63 -0
- package/skills/rule-audit/SKILL.md +307 -0
- package/skills/rule-creator/SKILL.md +150 -0
- package/skills/skill-writer/REFERENCE.md +246 -0
- package/skills/skill-writer/SKILL.md +270 -0
- package/skills/tdd-team/SKILL.md +128 -0
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Code standards enforcement, safety guards, and development workflow hooks",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Write|Edit",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/write-existing-file-blocker.py",
|
|
11
|
+
"timeout": 10
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "command",
|
|
15
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/sensitive-file-protector.py",
|
|
16
|
+
"timeout": 10
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/pyautogui-scroll-blocker.py",
|
|
21
|
+
"timeout": 10
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/hook-format-validator.py",
|
|
26
|
+
"timeout": 15
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validators/run_all_validators.py",
|
|
31
|
+
"timeout": 15
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/code-rules-enforcer.py",
|
|
36
|
+
"timeout": 15
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/tdd-enforcer.py",
|
|
41
|
+
"timeout": 10
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/code-style-validator.py",
|
|
46
|
+
"timeout": 15
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/docker-settings-guard.py",
|
|
51
|
+
"timeout": 15
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"matcher": "Edit",
|
|
57
|
+
"hooks": [
|
|
58
|
+
{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/advisory/refactor-guard.py",
|
|
61
|
+
"timeout": 15
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/advisory/migration-safety-advisor.py",
|
|
66
|
+
"timeout": 15
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"matcher": "Bash",
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/destructive-command-blocker.py",
|
|
76
|
+
"timeout": 10
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "command",
|
|
80
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/block-main-commit.py",
|
|
81
|
+
"timeout": 15
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "command",
|
|
85
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/pr-description-enforcer.py",
|
|
86
|
+
"timeout": 10
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"type": "command",
|
|
90
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/test-preflight-check.py",
|
|
91
|
+
"timeout": 10
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"matcher": "Task|Agent",
|
|
97
|
+
"hooks": [
|
|
98
|
+
{
|
|
99
|
+
"type": "command",
|
|
100
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/parallel-task-blocker.py",
|
|
101
|
+
"timeout": 10
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"matcher": "AskUserQuestion",
|
|
107
|
+
"hooks": [
|
|
108
|
+
{
|
|
109
|
+
"type": "command",
|
|
110
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/attention-needed-notify.py",
|
|
111
|
+
"timeout": 15
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"UserPromptSubmit": [
|
|
117
|
+
{
|
|
118
|
+
"matcher": "",
|
|
119
|
+
"hooks": [
|
|
120
|
+
{
|
|
121
|
+
"type": "command",
|
|
122
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/hook-structure-context.py",
|
|
123
|
+
"timeout": 10
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"type": "command",
|
|
127
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/bulk-edit-reminder.py",
|
|
128
|
+
"timeout": 15
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"type": "command",
|
|
132
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/code-rules-reminder.py",
|
|
133
|
+
"timeout": 15
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
"SessionStart": [
|
|
139
|
+
{
|
|
140
|
+
"matcher": "compact",
|
|
141
|
+
"hooks": [
|
|
142
|
+
{
|
|
143
|
+
"type": "command",
|
|
144
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/compact-context-reinject.py",
|
|
145
|
+
"timeout": 10
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"matcher": "",
|
|
151
|
+
"hooks": [
|
|
152
|
+
{
|
|
153
|
+
"type": "command",
|
|
154
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/plugin-data-dir-cleanup.py",
|
|
155
|
+
"timeout": 10
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"Stop": [
|
|
161
|
+
{
|
|
162
|
+
"matcher": "",
|
|
163
|
+
"hooks": [
|
|
164
|
+
{
|
|
165
|
+
"type": "command",
|
|
166
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/attention-needed-notify.py",
|
|
167
|
+
"timeout": 15
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"type": "command",
|
|
171
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/hedging-language-blocker.py",
|
|
172
|
+
"timeout": 10
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"SessionEnd": [
|
|
178
|
+
{
|
|
179
|
+
"matcher": "",
|
|
180
|
+
"hooks": [
|
|
181
|
+
{
|
|
182
|
+
"type": "command",
|
|
183
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/lifecycle/session-end-cleanup.py",
|
|
184
|
+
"timeout": 15
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"ConfigChange": [
|
|
190
|
+
{
|
|
191
|
+
"matcher": "user_settings",
|
|
192
|
+
"hooks": [
|
|
193
|
+
{
|
|
194
|
+
"type": "command",
|
|
195
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/lifecycle/config-change-guard.py",
|
|
196
|
+
"timeout": 10
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"PostToolUse": [
|
|
202
|
+
{
|
|
203
|
+
"matcher": "Write|Edit",
|
|
204
|
+
"hooks": [
|
|
205
|
+
{
|
|
206
|
+
"type": "command",
|
|
207
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/mypy_validator.py",
|
|
208
|
+
"timeout": 30
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"type": "command",
|
|
212
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/e2e-test-validator.py",
|
|
213
|
+
"timeout": 15
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"type": "command",
|
|
217
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/auto-formatter.py",
|
|
218
|
+
"timeout": 30
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"matcher": "Agent|Task|TeamCreate",
|
|
224
|
+
"hooks": [
|
|
225
|
+
{
|
|
226
|
+
"type": "command",
|
|
227
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/investigation-tracker-reset.py",
|
|
228
|
+
"timeout": 10
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
"Notification": [
|
|
234
|
+
{
|
|
235
|
+
"matcher": "",
|
|
236
|
+
"hooks": [
|
|
237
|
+
{
|
|
238
|
+
"type": "command",
|
|
239
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/claude-notification-handler.py",
|
|
240
|
+
"timeout": 15
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
AUDIT_LOG = os.path.expanduser("~/.claude/cache/config-change-audit.log")
|
|
8
|
+
KNOWN_HOOK_COUNT_FILE = os.path.expanduser("~/.claude/cache/known-hook-count.txt")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def count_hooks_in_settings(file_path: str) -> int:
|
|
12
|
+
try:
|
|
13
|
+
with open(file_path) as settings_file:
|
|
14
|
+
settings = json.load(settings_file)
|
|
15
|
+
except (OSError, json.JSONDecodeError):
|
|
16
|
+
return 0
|
|
17
|
+
hooks_section = settings.get("hooks", {})
|
|
18
|
+
total_count = 0
|
|
19
|
+
for event_hook_groups in hooks_section.values():
|
|
20
|
+
for hook_configuration in event_hook_groups:
|
|
21
|
+
total_count += len(hook_configuration.get("hooks", []))
|
|
22
|
+
return total_count
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def write_audit_entry(source: str, file_path: str) -> None:
|
|
26
|
+
audit_entry = f"{datetime.now().isoformat()} source={source} file={file_path}\n"
|
|
27
|
+
try:
|
|
28
|
+
os.makedirs(os.path.dirname(AUDIT_LOG), exist_ok=True)
|
|
29
|
+
with open(AUDIT_LOG, "a") as audit_file:
|
|
30
|
+
audit_file.write(audit_entry)
|
|
31
|
+
except OSError:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def guard_hook_injection(file_path: str) -> None:
|
|
36
|
+
current_count = count_hooks_in_settings(file_path)
|
|
37
|
+
|
|
38
|
+
if not os.path.exists(KNOWN_HOOK_COUNT_FILE):
|
|
39
|
+
try:
|
|
40
|
+
with open(KNOWN_HOOK_COUNT_FILE, "w") as count_file:
|
|
41
|
+
count_file.write(str(current_count))
|
|
42
|
+
except OSError:
|
|
43
|
+
pass
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with open(KNOWN_HOOK_COUNT_FILE) as count_file:
|
|
48
|
+
stored_count = int(count_file.read().strip())
|
|
49
|
+
except (OSError, ValueError):
|
|
50
|
+
stored_count = current_count
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(KNOWN_HOOK_COUNT_FILE, "w") as count_file:
|
|
54
|
+
count_file.write(str(current_count))
|
|
55
|
+
except OSError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
if current_count > stored_count:
|
|
59
|
+
block_decision = {
|
|
60
|
+
"decision": "block",
|
|
61
|
+
"reason": f"Hook count changed {stored_count} -> {current_count}. Delete known-hook-count.txt to reset.",
|
|
62
|
+
}
|
|
63
|
+
print(json.dumps(block_decision))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main() -> None:
|
|
67
|
+
try:
|
|
68
|
+
hook_input = json.load(sys.stdin)
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
|
|
72
|
+
source = hook_input.get("source", "")
|
|
73
|
+
file_path = hook_input.get("file_path", "")
|
|
74
|
+
|
|
75
|
+
write_audit_entry(source, file_path)
|
|
76
|
+
|
|
77
|
+
if source == "user_settings" and file_path:
|
|
78
|
+
guard_hook_injection(file_path)
|
|
79
|
+
|
|
80
|
+
sys.exit(0)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
CACHE_DIR = os.path.expanduser("~/.claude/cache/")
|
|
9
|
+
STALE_THRESHOLD_DAYS = 7
|
|
10
|
+
BACKUP_RETENTION_DAYS = 30
|
|
11
|
+
SECONDS_PER_DAY = 86400
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def purge_old_entries(
|
|
15
|
+
directory: str,
|
|
16
|
+
max_age_days: int,
|
|
17
|
+
should_include: Callable[[os.DirEntry[str]], bool] = lambda _entry: True,
|
|
18
|
+
) -> None:
|
|
19
|
+
if not os.path.exists(directory):
|
|
20
|
+
return
|
|
21
|
+
cutoff_time = time.time() - (max_age_days * SECONDS_PER_DAY)
|
|
22
|
+
try:
|
|
23
|
+
for each_entry in os.scandir(directory):
|
|
24
|
+
if should_include(each_entry):
|
|
25
|
+
try:
|
|
26
|
+
if each_entry.stat().st_mtime < cutoff_time:
|
|
27
|
+
os.unlink(each_entry.path)
|
|
28
|
+
except OSError:
|
|
29
|
+
pass
|
|
30
|
+
except OSError:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
try:
|
|
36
|
+
json.load(sys.stdin)
|
|
37
|
+
except json.JSONDecodeError:
|
|
38
|
+
sys.exit(0)
|
|
39
|
+
|
|
40
|
+
purge_old_entries(
|
|
41
|
+
CACHE_DIR,
|
|
42
|
+
STALE_THRESHOLD_DAYS,
|
|
43
|
+
should_include=lambda each_entry: each_entry.name.startswith("claude-ctx-") and each_entry.name.endswith(".json"),
|
|
44
|
+
)
|
|
45
|
+
purge_old_entries(
|
|
46
|
+
CACHE_DIR,
|
|
47
|
+
STALE_THRESHOLD_DAYS,
|
|
48
|
+
should_include=lambda each_entry: each_entry.name.endswith(".tmp"),
|
|
49
|
+
)
|
|
50
|
+
purge_old_entries(
|
|
51
|
+
os.path.join(CACHE_DIR, "transcript-backups"),
|
|
52
|
+
BACKUP_RETENTION_DAYS,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Notification hook - cross-platform (Windows/Linux/WSL)
|
|
4
|
+
Plays chimes sound + shows desktop notification when Claude needs user input.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
13
|
+
from notification_utils import (
|
|
14
|
+
notify_ntfy,
|
|
15
|
+
is_wsl,
|
|
16
|
+
notify_windows,
|
|
17
|
+
notify_wsl,
|
|
18
|
+
notify_linux,
|
|
19
|
+
sound_windows,
|
|
20
|
+
sound_wsl,
|
|
21
|
+
sound_linux,
|
|
22
|
+
get_project_name,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
DEFAULT_MESSAGE = "Input needed"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_question_from_stdin() -> str:
|
|
29
|
+
"""Extract question text from hook input JSON."""
|
|
30
|
+
try:
|
|
31
|
+
hook_input = json.load(sys.stdin)
|
|
32
|
+
tool_input = hook_input.get("tool_input", {})
|
|
33
|
+
questions = tool_input.get("questions", [])
|
|
34
|
+
if questions:
|
|
35
|
+
return questions[0].get("question", DEFAULT_MESSAGE)
|
|
36
|
+
except (json.JSONDecodeError, KeyError, IndexError):
|
|
37
|
+
pass
|
|
38
|
+
return DEFAULT_MESSAGE
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
system = platform.system()
|
|
43
|
+
|
|
44
|
+
project_name = get_project_name()
|
|
45
|
+
question_text = get_question_from_stdin()
|
|
46
|
+
|
|
47
|
+
notify_ntfy(title=project_name, message=question_text)
|
|
48
|
+
|
|
49
|
+
if system == "Windows":
|
|
50
|
+
sound_windows()
|
|
51
|
+
notify_windows(project_name, question_text)
|
|
52
|
+
elif is_wsl():
|
|
53
|
+
sound_wsl()
|
|
54
|
+
notify_wsl(project_name, question_text)
|
|
55
|
+
elif system == "Linux":
|
|
56
|
+
sound_linux()
|
|
57
|
+
notify_linux()
|
|
58
|
+
else:
|
|
59
|
+
print("\a", end="", flush=True)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
9
|
+
from notification_utils import (
|
|
10
|
+
notify_ntfy,
|
|
11
|
+
is_wsl,
|
|
12
|
+
notify_windows,
|
|
13
|
+
notify_wsl,
|
|
14
|
+
sound_wsl,
|
|
15
|
+
sound_windows,
|
|
16
|
+
get_project_name,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def send_desktop_and_push_notification(
|
|
21
|
+
project_name: str,
|
|
22
|
+
notification_message: str,
|
|
23
|
+
ntfy_priority: str,
|
|
24
|
+
) -> None:
|
|
25
|
+
notify_ntfy(title=project_name, message=notification_message, priority=ntfy_priority)
|
|
26
|
+
system = platform.system()
|
|
27
|
+
if system == "Windows":
|
|
28
|
+
sound_windows()
|
|
29
|
+
notify_windows(project_name, notification_message)
|
|
30
|
+
elif is_wsl():
|
|
31
|
+
sound_wsl()
|
|
32
|
+
notify_wsl(project_name, notification_message)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> None:
|
|
36
|
+
try:
|
|
37
|
+
hook_input = json.load(sys.stdin)
|
|
38
|
+
except (json.JSONDecodeError, ValueError):
|
|
39
|
+
sys.exit(0)
|
|
40
|
+
|
|
41
|
+
notification_type = hook_input.get("notification_type", "")
|
|
42
|
+
notification_message = hook_input.get("message", "Claude needs attention")
|
|
43
|
+
project_name = get_project_name()
|
|
44
|
+
|
|
45
|
+
if notification_type == "idle_prompt":
|
|
46
|
+
send_desktop_and_push_notification(project_name, notification_message, ntfy_priority="default")
|
|
47
|
+
elif notification_type == "permission_prompt":
|
|
48
|
+
permission_message = f"[PERMISSION] {notification_message}"
|
|
49
|
+
send_desktop_and_push_notification(project_name, permission_message, ntfy_priority="high")
|
|
50
|
+
elif notification_type == "auth_success":
|
|
51
|
+
print(f"auth_success: {notification_message}", file=sys.stderr)
|
|
52
|
+
elif notification_type == "elicitation_dialog":
|
|
53
|
+
print(f"elicitation_dialog: {notification_message}", file=sys.stderr)
|
|
54
|
+
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
main()
|