ima-claude 2.9.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 +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Standalone debug logger for ima-claude hooks.
|
|
4
|
+
|
|
5
|
+
Only active when the environment variable CLAUDE_HOOK_DEBUG=1 is set.
|
|
6
|
+
When the variable is absent or set to anything else, log_hook() is a no-op —
|
|
7
|
+
zero overhead in normal operation.
|
|
8
|
+
|
|
9
|
+
Usage
|
|
10
|
+
-----
|
|
11
|
+
Add to any hook script to gain a persistent audit trail:
|
|
12
|
+
|
|
13
|
+
import sys, os
|
|
14
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
15
|
+
from hook_logger import log_hook
|
|
16
|
+
|
|
17
|
+
# When the hook fires:
|
|
18
|
+
log_hook("my_hook_name", triggered=True, reason="why it fired")
|
|
19
|
+
|
|
20
|
+
# On skip paths:
|
|
21
|
+
log_hook("my_hook_name", triggered=False, reason="why it was skipped")
|
|
22
|
+
|
|
23
|
+
Enable logging before launching Claude Code:
|
|
24
|
+
|
|
25
|
+
export CLAUDE_HOOK_DEBUG=1
|
|
26
|
+
claude
|
|
27
|
+
|
|
28
|
+
Watch the log in real time:
|
|
29
|
+
|
|
30
|
+
tail -f ~/.claude/hook-activity.log
|
|
31
|
+
|
|
32
|
+
Log format:
|
|
33
|
+
|
|
34
|
+
2026-02-27 14:23:01 | enforce_rg_over_grep | TRIGGERED | grep found in command
|
|
35
|
+
2026-02-27 14:23:05 | serena_over_read | SKIPPED | non-code file extension
|
|
36
|
+
|
|
37
|
+
This file does NOT modify any existing hook scripts. It is imported on demand only.
|
|
38
|
+
"""
|
|
39
|
+
import datetime
|
|
40
|
+
import os
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
LOG_FILE = os.path.expanduser("~/.claude/hook-activity.log")
|
|
44
|
+
_DEBUG_ENABLED = os.environ.get("CLAUDE_HOOK_DEBUG", "").strip() == "1"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def log_hook(hook_name: str, triggered: bool, reason: str = "") -> None:
|
|
48
|
+
"""Write a timestamped entry to the hook activity log.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
hook_name: Short name identifying the hook (e.g. "enforce_rg_over_grep").
|
|
52
|
+
triggered: True if the hook emitted a warning/action; False if it skipped.
|
|
53
|
+
reason: Human-readable explanation for the outcome.
|
|
54
|
+
"""
|
|
55
|
+
if not _DEBUG_ENABLED:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
59
|
+
status = "TRIGGERED" if triggered else "SKIPPED"
|
|
60
|
+
entry = f"{timestamp} | {hook_name:<24} | {status:<9} | {reason}\n"
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
log_dir = os.path.dirname(LOG_FILE)
|
|
64
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
65
|
+
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
|
66
|
+
f.write(entry)
|
|
67
|
+
except OSError:
|
|
68
|
+
# Never let logging errors propagate into a hook and alter its exit code.
|
|
69
|
+
pass
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/bootstrap.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Bash",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/block_sed_edits.py" },
|
|
19
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/enforce_rg_over_grep.py" },
|
|
20
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "Read",
|
|
25
|
+
"hooks": [
|
|
26
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" },
|
|
27
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_over_read.py" }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"matcher": "Edit",
|
|
32
|
+
"hooks": [
|
|
33
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"matcher": "Write",
|
|
38
|
+
"hooks": [
|
|
39
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"matcher": "Glob",
|
|
44
|
+
"hooks": [
|
|
45
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"matcher": "Grep",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_bootstrap.py" },
|
|
52
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_over_grep.py" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"matcher": "mcp__tavily__tavily-extract",
|
|
57
|
+
"hooks": [
|
|
58
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/tavily_extract_advanced.py" },
|
|
59
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/vestige_before_external.py" }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"matcher": "mcp__tavily__tavily_search",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/vestige_before_external.py" }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"matcher": "mcp__tavily__tavily_research",
|
|
70
|
+
"hooks": [
|
|
71
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/vestige_before_external.py" }
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"matcher": "mcp__context7__resolve-library-id",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/vestige_before_external.py" }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"matcher": "mcp__context7__query-docs",
|
|
82
|
+
"hooks": [
|
|
83
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/vestige_before_external.py" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"matcher": "WebFetch",
|
|
88
|
+
"hooks": [
|
|
89
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/webfetch_to_tavily.py" }
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"matcher": "WebSearch",
|
|
94
|
+
"hooks": [
|
|
95
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/websearch_to_tavily.py" }
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"matcher": "mcp__serena__jet_brains_find_symbol",
|
|
100
|
+
"hooks": [
|
|
101
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_project_check.py" }
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"matcher": "mcp__serena__jet_brains_find_referencing_symbols",
|
|
106
|
+
"hooks": [
|
|
107
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_project_check.py" }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"matcher": "mcp__serena__jet_brains_get_symbols_overview",
|
|
112
|
+
"hooks": [
|
|
113
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_project_check.py" }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"matcher": "mcp__serena__jet_brains_type_hierarchy",
|
|
118
|
+
"hooks": [
|
|
119
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/serena_project_check.py" }
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"matcher": "mcp__claude_ai_Atlassian__getJiraIssue",
|
|
124
|
+
"hooks": [
|
|
125
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"matcher": "mcp__claude_ai_Atlassian__editJiraIssue",
|
|
130
|
+
"hooks": [
|
|
131
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"matcher": "mcp__claude_ai_Atlassian__createJiraIssue",
|
|
136
|
+
"hooks": [
|
|
137
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"matcher": "mcp__claude_ai_Atlassian__searchJiraIssuesUsingJql",
|
|
142
|
+
"hooks": [
|
|
143
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"matcher": "mcp__claude_ai_Atlassian__transitionJiraIssue",
|
|
148
|
+
"hooks": [
|
|
149
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"matcher": "mcp__claude_ai_Atlassian__addCommentToJiraIssue",
|
|
154
|
+
"hooks": [
|
|
155
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"matcher": "mcp__claude_ai_Atlassian__getConfluencePage",
|
|
160
|
+
"hooks": [
|
|
161
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"matcher": "mcp__claude_ai_Atlassian__createConfluencePage",
|
|
166
|
+
"hooks": [
|
|
167
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"matcher": "mcp__claude_ai_Atlassian__updateConfluencePage",
|
|
172
|
+
"hooks": [
|
|
173
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"matcher": "mcp__claude_ai_Atlassian__searchConfluenceUsingCql",
|
|
178
|
+
"hooks": [
|
|
179
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"matcher": "mcp__claude_ai_Atlassian__getAccessibleAtlassianResources",
|
|
184
|
+
"hooks": [
|
|
185
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"matcher": "mcp__claude_ai_Atlassian__getTransitionsForJiraIssue",
|
|
190
|
+
"hooks": [
|
|
191
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/atlassian_prereqs.py" }
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
"PostToolUse": [
|
|
196
|
+
{
|
|
197
|
+
"matcher": "Edit",
|
|
198
|
+
"hooks": [
|
|
199
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_store_reminder.py" },
|
|
200
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/wp_security_check.py" },
|
|
201
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/sql_injection_check.py" },
|
|
202
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/fp_utility_check.py" },
|
|
203
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/jquery_in_wordpress.py" },
|
|
204
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/bootstrap_utility_check.py" },
|
|
205
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/composer_autoload_check.py" }
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"matcher": "Write",
|
|
210
|
+
"hooks": [
|
|
211
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/memory_store_reminder.py" },
|
|
212
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/wp_security_check.py" },
|
|
213
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/sql_injection_check.py" },
|
|
214
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/fp_utility_check.py" },
|
|
215
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/jquery_in_wordpress.py" },
|
|
216
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/bootstrap_utility_check.py" },
|
|
217
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/composer_autoload_check.py" },
|
|
218
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/docs_organization.py" }
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"matcher": "ExitPlanMode",
|
|
223
|
+
"hooks": [
|
|
224
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/task_master_after_plan.py" }
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"UserPromptSubmit": [
|
|
229
|
+
{
|
|
230
|
+
"hooks": [
|
|
231
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/prompt_coach.py" },
|
|
232
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/jira_issue_fetch.py" },
|
|
233
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/task_master_before_impl.py" },
|
|
234
|
+
{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/sequential_thinking_check.py" }
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UserPromptSubmit hook: Remind Claude to fetch Jira issue context when a key is detected.
|
|
4
|
+
|
|
5
|
+
M6 — Auto-fetch Jira issue when key detected in prompt.
|
|
6
|
+
|
|
7
|
+
Scans the user prompt for Jira issue key patterns (e.g., FNR-123, IMA-456).
|
|
8
|
+
Prints a reminder to fetch the issue via Atlassian MCP for the first new key seen.
|
|
9
|
+
Tracks seen keys in ~/.claude/.jira_keys_fetched to avoid repeat reminders.
|
|
10
|
+
Exit code 0 = soft warning via stderr.
|
|
11
|
+
"""
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
STATE_FILE = os.path.expanduser("~/.claude/.jira_keys_fetched")
|
|
19
|
+
STALENESS_SECONDS = 3600 # 1 hour — reset seen keys after this gap
|
|
20
|
+
|
|
21
|
+
JIRA_KEY_PATTERN = re.compile(r"\b[A-Z]{2,10}-\d+\b")
|
|
22
|
+
|
|
23
|
+
CLOUD_ID = "<cloudId>"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_seen_keys() -> set[str]:
|
|
27
|
+
"""Load previously seen Jira keys from state file, if still fresh."""
|
|
28
|
+
if not os.path.exists(STATE_FILE):
|
|
29
|
+
return set()
|
|
30
|
+
try:
|
|
31
|
+
mtime = os.path.getmtime(STATE_FILE)
|
|
32
|
+
if (time.time() - mtime) >= STALENESS_SECONDS:
|
|
33
|
+
return set()
|
|
34
|
+
with open(STATE_FILE, "r") as f:
|
|
35
|
+
return set(line.strip() for line in f if line.strip())
|
|
36
|
+
except OSError:
|
|
37
|
+
return set()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_seen_keys(keys: set[str]) -> None:
|
|
41
|
+
"""Persist the set of seen Jira keys to the state file."""
|
|
42
|
+
state_dir = os.path.dirname(STATE_FILE)
|
|
43
|
+
os.makedirs(state_dir, exist_ok=True)
|
|
44
|
+
with open(STATE_FILE, "w") as f:
|
|
45
|
+
f.write("\n".join(sorted(keys)))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
input_data = json.load(sys.stdin)
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
prompt = input_data.get("user_prompt", "")
|
|
54
|
+
|
|
55
|
+
if not prompt:
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
|
|
58
|
+
matches = JIRA_KEY_PATTERN.findall(prompt)
|
|
59
|
+
|
|
60
|
+
if not matches:
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
first_key = matches[0]
|
|
64
|
+
|
|
65
|
+
seen_keys = load_seen_keys()
|
|
66
|
+
|
|
67
|
+
if first_key in seen_keys:
|
|
68
|
+
sys.exit(0)
|
|
69
|
+
|
|
70
|
+
seen_keys.add(first_key)
|
|
71
|
+
save_seen_keys(seen_keys)
|
|
72
|
+
|
|
73
|
+
print(
|
|
74
|
+
f'Jira issue key {first_key} detected — consider fetching context:\n'
|
|
75
|
+
f' mcp__claude_ai_Atlassian__getJiraIssue issueIdOrKey: "{first_key}" cloudId: "{CLOUD_ID}"',
|
|
76
|
+
file=sys.stderr,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
sys.exit(0)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PostToolUse hook: Warn about vanilla DOM JS in WordPress context.
|
|
4
|
+
|
|
5
|
+
Checks (soft warning only, exit 0):
|
|
6
|
+
M2 — querySelector/addEventListener/etc. when jQuery is already loaded
|
|
7
|
+
|
|
8
|
+
WordPress context is detected via file path (wp-content/plugins, wp-content/themes)
|
|
9
|
+
or file content (jQuery signals).
|
|
10
|
+
|
|
11
|
+
Applies to: Edit, Write on .js files only.
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
WP_PATH_SIGNALS = ("wp-content/plugins/", "wp-content/themes/")
|
|
18
|
+
|
|
19
|
+
WP_CONTENT_SIGNALS = re.compile(
|
|
20
|
+
r"jQuery\s*[\(\.]|"
|
|
21
|
+
r"\(function\s*\(\$\)|"
|
|
22
|
+
r"\$\s*\(document\)|"
|
|
23
|
+
r"\bwp\."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
VANILLA_DOM_PATTERNS = re.compile(
|
|
27
|
+
r"document\.querySelectorAll\s*\(|"
|
|
28
|
+
r"document\.querySelector\s*\(|"
|
|
29
|
+
r"document\.getElementById\s*\(|"
|
|
30
|
+
r"document\.getElementsByClassName\s*\(|"
|
|
31
|
+
r"\.addEventListener\s*\(|"
|
|
32
|
+
r"document\.createElement\s*\("
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
WARNING = (
|
|
36
|
+
"⚠️ Vanilla DOM JS in WordPress context — jQuery is already loaded (0 extra bytes).\n"
|
|
37
|
+
" document.querySelector('.x') → $('.x')\n"
|
|
38
|
+
" el.addEventListener('click') → $(el).on('click')\n"
|
|
39
|
+
" See /jquery skill for FP-aligned patterns."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_wordpress_path(file_path: str) -> bool:
|
|
44
|
+
return any(signal in file_path for signal in WP_PATH_SIGNALS)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_wordpress_content(content: str) -> bool:
|
|
48
|
+
return bool(WP_CONTENT_SIGNALS.search(content))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def has_vanilla_dom(content: str) -> bool:
|
|
52
|
+
return bool(VANILLA_DOM_PATTERNS.search(content))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def read_file(file_path: str) -> str:
|
|
56
|
+
try:
|
|
57
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
58
|
+
return f.read()
|
|
59
|
+
except OSError:
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
input_data = json.load(sys.stdin)
|
|
65
|
+
except json.JSONDecodeError:
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
tool_name = input_data.get("tool_name", "")
|
|
69
|
+
tool_input = input_data.get("tool_input", {})
|
|
70
|
+
file_path = tool_input.get("file_path", "")
|
|
71
|
+
|
|
72
|
+
if tool_name not in ("Edit", "Write"):
|
|
73
|
+
sys.exit(0)
|
|
74
|
+
|
|
75
|
+
if not file_path.endswith(".js"):
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
|
|
78
|
+
if tool_name == "Write":
|
|
79
|
+
written_content = tool_input.get("content", "")
|
|
80
|
+
wp_context = is_wordpress_path(file_path) or is_wordpress_content(written_content)
|
|
81
|
+
vanilla_found = has_vanilla_dom(written_content)
|
|
82
|
+
else:
|
|
83
|
+
# Edit: check path for WP context; read disk file for content signals
|
|
84
|
+
new_string = tool_input.get("new_string", "")
|
|
85
|
+
disk_content = read_file(file_path)
|
|
86
|
+
wp_context = is_wordpress_path(file_path) or is_wordpress_content(disk_content)
|
|
87
|
+
vanilla_found = has_vanilla_dom(new_string)
|
|
88
|
+
|
|
89
|
+
if wp_context and vanilla_found:
|
|
90
|
+
print(WARNING, file=sys.stderr)
|
|
91
|
+
|
|
92
|
+
sys.exit(0)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PreToolUse hook: Remind Claude to search Vestige + Qdrant before starting work.
|
|
4
|
+
|
|
5
|
+
On the first non-memory tool use per session, prints a reminder to stderr.
|
|
6
|
+
Uses a timestamp-based state file to avoid repeating within the same session.
|
|
7
|
+
Exit code 0 = allow tool to proceed (soft warning only).
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
STATE_FILE = os.path.expanduser("~/.claude/.memory_bootstrapped")
|
|
15
|
+
STALENESS_SECONDS = 3600 # 1 hour — new session after this gap
|
|
16
|
+
|
|
17
|
+
MEMORY_TOOLS = {
|
|
18
|
+
"mcp__vestige__search",
|
|
19
|
+
"mcp__vestige__smart_ingest",
|
|
20
|
+
"mcp__vestige__ingest",
|
|
21
|
+
"mcp__vestige__memory",
|
|
22
|
+
"mcp__vestige__intention",
|
|
23
|
+
"mcp__vestige__codebase",
|
|
24
|
+
"mcp__vestige__promote_memory",
|
|
25
|
+
"mcp__vestige__demote_memory",
|
|
26
|
+
"mcp__vestige__session_checkpoint",
|
|
27
|
+
"mcp__qdrant-memory__qdrant-find",
|
|
28
|
+
"mcp__qdrant-memory__qdrant-store",
|
|
29
|
+
"mcp__serena__read_memory",
|
|
30
|
+
"mcp__serena__list_memories",
|
|
31
|
+
"mcp__serena__write_memory",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
REMINDER = """Memory bootstrap: Search Vestige and Qdrant before starting work.
|
|
35
|
+
mcp__vestige__search query: "{project}" limit: 5
|
|
36
|
+
mcp__qdrant-memory__qdrant-find query: "{project}"
|
|
37
|
+
mcp__vestige__intention action: "check"
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_bootstrapped():
|
|
42
|
+
"""Check if memory bootstrap already happened this session."""
|
|
43
|
+
if not os.path.exists(STATE_FILE):
|
|
44
|
+
return False
|
|
45
|
+
try:
|
|
46
|
+
mtime = os.path.getmtime(STATE_FILE)
|
|
47
|
+
return (time.time() - mtime) < STALENESS_SECONDS
|
|
48
|
+
except OSError:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def mark_bootstrapped():
|
|
53
|
+
"""Mark that bootstrap reminder has fired."""
|
|
54
|
+
state_dir = os.path.dirname(STATE_FILE)
|
|
55
|
+
os.makedirs(state_dir, exist_ok=True)
|
|
56
|
+
with open(STATE_FILE, "w") as f:
|
|
57
|
+
f.write(str(time.time()))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
input_data = json.load(sys.stdin)
|
|
62
|
+
except json.JSONDecodeError:
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
65
|
+
tool_name = input_data.get("tool_name", "")
|
|
66
|
+
|
|
67
|
+
# If this IS a memory tool, mark as bootstrapped and exit silently
|
|
68
|
+
if tool_name in MEMORY_TOOLS:
|
|
69
|
+
mark_bootstrapped()
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
|
|
72
|
+
# If already bootstrapped this session, exit silently
|
|
73
|
+
if is_bootstrapped():
|
|
74
|
+
sys.exit(0)
|
|
75
|
+
|
|
76
|
+
# First non-memory tool use — print reminder and mark bootstrapped
|
|
77
|
+
print(REMINDER, file=sys.stderr)
|
|
78
|
+
mark_bootstrapped()
|
|
79
|
+
sys.exit(0)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PostToolUse hook: Nudge Claude to store decisions/patterns after several edits.
|
|
4
|
+
|
|
5
|
+
After every 5th Edit/Write without a Vestige or Qdrant store, prints a gentle reminder.
|
|
6
|
+
Counter resets when a memory store is detected or after the reminder fires.
|
|
7
|
+
Exit code 0 = allow tool to proceed (soft warning only).
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
STATE_FILE = os.path.expanduser("~/.claude/.memory_edit_count")
|
|
14
|
+
EDIT_THRESHOLD = 5
|
|
15
|
+
|
|
16
|
+
MEMORY_STORE_TOOLS = {
|
|
17
|
+
"mcp__vestige__smart_ingest",
|
|
18
|
+
"mcp__vestige__ingest",
|
|
19
|
+
"mcp__vestige__codebase",
|
|
20
|
+
"mcp__vestige__session_checkpoint",
|
|
21
|
+
"mcp__qdrant-memory__qdrant-store",
|
|
22
|
+
"mcp__serena__write_memory",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
REMINDER = """You've made several changes this session. Any decisions or patterns worth storing?
|
|
26
|
+
→ Vestige smart_ingest for decisions/patterns (neural, fades if unused)
|
|
27
|
+
→ Qdrant qdrant-store for reference material (permanent library)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_edit_count():
|
|
32
|
+
"""Read current edit count from state file."""
|
|
33
|
+
if not os.path.exists(STATE_FILE):
|
|
34
|
+
return 0
|
|
35
|
+
try:
|
|
36
|
+
with open(STATE_FILE, "r") as f:
|
|
37
|
+
return int(f.read().strip())
|
|
38
|
+
except (ValueError, OSError):
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def set_edit_count(count):
|
|
43
|
+
"""Write edit count to state file."""
|
|
44
|
+
state_dir = os.path.dirname(STATE_FILE)
|
|
45
|
+
os.makedirs(state_dir, exist_ok=True)
|
|
46
|
+
with open(STATE_FILE, "w") as f:
|
|
47
|
+
f.write(str(count))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
input_data = json.load(sys.stdin)
|
|
52
|
+
except json.JSONDecodeError:
|
|
53
|
+
sys.exit(0)
|
|
54
|
+
|
|
55
|
+
tool_name = input_data.get("tool_name", "")
|
|
56
|
+
|
|
57
|
+
# If a memory store just happened, reset counter
|
|
58
|
+
if tool_name in MEMORY_STORE_TOOLS:
|
|
59
|
+
set_edit_count(0)
|
|
60
|
+
sys.exit(0)
|
|
61
|
+
|
|
62
|
+
# Only count Edit and Write tools
|
|
63
|
+
if tool_name not in ("Edit", "Write"):
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
# Increment edit count
|
|
67
|
+
count = get_edit_count() + 1
|
|
68
|
+
|
|
69
|
+
if count >= EDIT_THRESHOLD:
|
|
70
|
+
print(REMINDER, file=sys.stderr)
|
|
71
|
+
set_edit_count(0) # Reset after reminding
|
|
72
|
+
else:
|
|
73
|
+
set_edit_count(count)
|
|
74
|
+
|
|
75
|
+
sys.exit(0)
|