greymatter-plugin 1.4.0__tar.gz
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.
- greymatter_plugin-1.4.0/.gitignore +56 -0
- greymatter_plugin-1.4.0/PKG-INFO +9 -0
- greymatter_plugin-1.4.0/commands/gm-sync.md +27 -0
- greymatter_plugin-1.4.0/hooks/agent_post_hook.py +53 -0
- greymatter_plugin-1.4.0/hooks/agent_pre_hook.py +63 -0
- greymatter_plugin-1.4.0/hooks/hooks.json +90 -0
- greymatter_plugin-1.4.0/hooks/observation_hooks.py +119 -0
- greymatter_plugin-1.4.0/hooks/pattern-capture.md +18 -0
- greymatter_plugin-1.4.0/hooks/post_tool_retrieval.py +120 -0
- greymatter_plugin-1.4.0/hooks/pre_compact.py +342 -0
- greymatter_plugin-1.4.0/hooks/quality-gate.md +22 -0
- greymatter_plugin-1.4.0/hooks/run_extraction.py +103 -0
- greymatter_plugin-1.4.0/hooks/session-context.md +14 -0
- greymatter_plugin-1.4.0/hooks/session_context.py +276 -0
- greymatter_plugin-1.4.0/hooks/user_prompt_submit.py +359 -0
- greymatter_plugin-1.4.0/plugin.json +14 -0
- greymatter_plugin-1.4.0/pyproject.toml +18 -0
- greymatter_plugin-1.4.0/run-server.sh +5 -0
- greymatter_plugin-1.4.0/skills/create-agent.md +199 -0
- greymatter_plugin-1.4.0/skills/gm-dashboard.md +35 -0
- greymatter_plugin-1.4.0/skills/gm-pattern.md +23 -0
- greymatter_plugin-1.4.0/skills/gm-review.md +22 -0
- greymatter_plugin-1.4.0/skills/gm-search.md +66 -0
- greymatter_plugin-1.4.0/skills/gm-soul.md +16 -0
- greymatter_plugin-1.4.0/skills/gm-status.md +14 -0
- greymatter_plugin-1.4.0/skills/gm-work.md +24 -0
- greymatter_plugin-1.4.0/skills/node-bootstrap.md +194 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/__init__.py +1 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/__main__.py +4 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/budget.py +242 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/harness.py +242 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/http_server.py +88 -0
- greymatter_plugin-1.4.0/src/greymatter_plugin/server.py +1914 -0
- greymatter_plugin-1.4.0/tests/__init__.py +0 -0
- greymatter_plugin-1.4.0/tests/test_budget.py +164 -0
- greymatter_plugin-1.4.0/tests/test_budget_extended.py +160 -0
- greymatter_plugin-1.4.0/tests/test_extraction_trigger.py +94 -0
- greymatter_plugin-1.4.0/tests/test_gm_code.py +183 -0
- greymatter_plugin-1.4.0/tests/test_harness.py +219 -0
- greymatter_plugin-1.4.0/tests/test_post_tool_retrieval.py +58 -0
- greymatter_plugin-1.4.0/tests/test_pre_compact.py +375 -0
- greymatter_plugin-1.4.0/tests/test_run_extraction.py +48 -0
- greymatter_plugin-1.4.0/tests/test_savings_metadata.py +41 -0
- greymatter_plugin-1.4.0/tests/test_server.py +787 -0
- greymatter_plugin-1.4.0/tests/test_session_context.py +222 -0
- greymatter_plugin-1.4.0/tests/test_user_prompt_submit.py +209 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
|
|
12
|
+
# Testing
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
.tox/
|
|
17
|
+
.nox/
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
*~
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# Local configuration
|
|
31
|
+
*.local.md
|
|
32
|
+
|
|
33
|
+
# Environment files (NEVER commit secrets)
|
|
34
|
+
.env
|
|
35
|
+
.env.local
|
|
36
|
+
.env.*.local
|
|
37
|
+
!.env.schema
|
|
38
|
+
!.env.example
|
|
39
|
+
|
|
40
|
+
# Certificates and keys (never commit secrets)
|
|
41
|
+
certs/
|
|
42
|
+
*.key
|
|
43
|
+
*.crt
|
|
44
|
+
*.pem
|
|
45
|
+
|
|
46
|
+
# Claude working memory (session-specific)
|
|
47
|
+
.claude/working-memory.md
|
|
48
|
+
|
|
49
|
+
# Virtual environments
|
|
50
|
+
.venv/
|
|
51
|
+
|
|
52
|
+
# Lock files (generated)
|
|
53
|
+
uv.lock
|
|
54
|
+
|
|
55
|
+
# Worktrees
|
|
56
|
+
.worktrees/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: greymatter-plugin
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: GreyMatter MCP plugin for Claude Code
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: fastmcp>=3.0
|
|
7
|
+
Requires-Dist: greymatter-code
|
|
8
|
+
Requires-Dist: greymatter-core
|
|
9
|
+
Requires-Dist: greymatter-salesos
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gm-sync
|
|
3
|
+
description: Sync SalesOS meeting data from Mac Mini into local GreyMatter SQLite
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Sync meeting data from the Mac Mini into local GreyMatter:
|
|
7
|
+
|
|
8
|
+
1. Export meetings from Mac Mini as JSON:
|
|
9
|
+
```bash
|
|
10
|
+
ssh agenticai@100.81.247.14 'sqlite3 ~/.greymatter/greymatter.db "SELECT json_group_array(json_object(\"id\",id,\"title\",title,\"date\",date,\"summary\",summary,\"transcript\",transcript,\"account\",account,\"tags\",tags)) FROM salesos_meetings"' > /tmp/meetings.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Import into local GreyMatter:
|
|
14
|
+
```bash
|
|
15
|
+
cd /Users/keithcotterman/Development/greymatter/core
|
|
16
|
+
python3 scripts/import_crdb_data.py --meetings /tmp/meetings.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Report sync results from the script output.
|
|
20
|
+
|
|
21
|
+
The script:
|
|
22
|
+
- Reads meeting JSON exported from Mac Mini SQLite
|
|
23
|
+
- Imports meetings with upsert (new records added, existing updated)
|
|
24
|
+
|
|
25
|
+
**Requirements:** SSH access to `agenticai@100.81.247.14` (Mac Mini via Tailscale).
|
|
26
|
+
|
|
27
|
+
If SSH fails, suggest: `tailscale status` to check connectivity.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/Users/keithcotterman/Development/greymatter/.venv/bin/python
|
|
2
|
+
"""PostToolUse hook: output tracking instructions after Agent tool completes.
|
|
3
|
+
|
|
4
|
+
Instructs Claude to create work items and observations via MCP tools.
|
|
5
|
+
Falls through gracefully on any error.
|
|
6
|
+
|
|
7
|
+
Input (stdin JSON): {"toolName": "Agent", "toolInput": {...}, "toolResult": "..."}
|
|
8
|
+
Output (stdout JSON): {} (with optional stderr message for Claude)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# Add greymatter core + plugin to path
|
|
16
|
+
GREYMATTER_ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "core", "src")
|
|
17
|
+
PLUGIN_ROOT = os.path.join(os.path.dirname(__file__), "..", "src")
|
|
18
|
+
sys.path.insert(0, os.path.abspath(GREYMATTER_ROOT))
|
|
19
|
+
sys.path.insert(0, os.path.abspath(PLUGIN_ROOT))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> None:
|
|
23
|
+
try:
|
|
24
|
+
input_data = json.load(sys.stdin)
|
|
25
|
+
except (json.JSONDecodeError, EOFError):
|
|
26
|
+
print("{}")
|
|
27
|
+
sys.exit(0)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
tool_input = input_data.get("toolInput", {})
|
|
31
|
+
agent_type = tool_input.get("subagent_type", "general-purpose")
|
|
32
|
+
agent_name = tool_input.get("name", "")
|
|
33
|
+
|
|
34
|
+
from greymatter_plugin.harness import HarnessEnricher
|
|
35
|
+
|
|
36
|
+
enricher = HarnessEnricher()
|
|
37
|
+
name = agent_name or "unnamed"
|
|
38
|
+
work_item_title = f"Agent: {agent_type}/{name}"
|
|
39
|
+
instructions = enricher.build_capture_instructions(work_item_title, agent_type)
|
|
40
|
+
|
|
41
|
+
# Output instructions via stderr so Claude sees them
|
|
42
|
+
sys.stderr.write(f"[GreyMatter Harness] {instructions}\n")
|
|
43
|
+
print("{}")
|
|
44
|
+
|
|
45
|
+
except Exception as e:
|
|
46
|
+
sys.stderr.write(f"[GreyMatter Harness] PostToolUse skipped: {e}\n")
|
|
47
|
+
print("{}")
|
|
48
|
+
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/Users/keithcotterman/Development/greymatter/.venv/bin/python
|
|
2
|
+
"""PreToolUse hook: enrich Agent tool prompts with GreyMatter quality context.
|
|
3
|
+
|
|
4
|
+
Reads DB in read-only mode. Modifies the agent prompt via updatedInput.
|
|
5
|
+
Falls through gracefully on any error — never blocks agent dispatch.
|
|
6
|
+
|
|
7
|
+
Input (stdin JSON): {"toolName": "Agent", "toolInput": {"prompt": "...", "subagent_type": "...", ...}}
|
|
8
|
+
Output (stdout JSON): {"decision": "approve", "updatedInput": {...}} or {"decision": "approve"}
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# Add greymatter core + plugin to path
|
|
16
|
+
GREYMATTER_ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "core", "src")
|
|
17
|
+
PLUGIN_ROOT = os.path.join(os.path.dirname(__file__), "..", "src")
|
|
18
|
+
sys.path.insert(0, os.path.abspath(GREYMATTER_ROOT))
|
|
19
|
+
sys.path.insert(0, os.path.abspath(PLUGIN_ROOT))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> None:
|
|
23
|
+
try:
|
|
24
|
+
input_data = json.load(sys.stdin)
|
|
25
|
+
except (json.JSONDecodeError, EOFError):
|
|
26
|
+
print(json.dumps({"decision": "approve"}))
|
|
27
|
+
sys.exit(0)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
tool_input = input_data.get("toolInput", {})
|
|
31
|
+
agent_prompt = tool_input.get("prompt", "")
|
|
32
|
+
agent_type = tool_input.get("subagent_type", "general-purpose")
|
|
33
|
+
agent_name = tool_input.get("name", "")
|
|
34
|
+
|
|
35
|
+
if not agent_prompt:
|
|
36
|
+
print(json.dumps({"decision": "approve"}))
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
|
|
39
|
+
from greymatter_plugin.harness import HarnessEnricher
|
|
40
|
+
|
|
41
|
+
enricher = HarnessEnricher()
|
|
42
|
+
result = enricher.enrich(agent_prompt, agent_type, agent_name)
|
|
43
|
+
|
|
44
|
+
# Build updated tool input with enriched prompt
|
|
45
|
+
updated_input = dict(tool_input)
|
|
46
|
+
updated_input["prompt"] = result["enriched_prompt"]
|
|
47
|
+
|
|
48
|
+
output = {
|
|
49
|
+
"decision": "approve",
|
|
50
|
+
"updatedInput": updated_input,
|
|
51
|
+
}
|
|
52
|
+
print(json.dumps(output))
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# Graceful degradation: approve without enrichment
|
|
56
|
+
sys.stderr.write(f"[GreyMatter Harness] PreToolUse skipped: {e}\n")
|
|
57
|
+
print(json.dumps({"decision": "approve"}))
|
|
58
|
+
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "GreyMatter plugin hooks — session context, checkpoint, observation capture, agent harness",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session_context.py",
|
|
10
|
+
"timeout": 10
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PreCompact": [
|
|
16
|
+
{
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre_compact.py",
|
|
21
|
+
"timeout": 30
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"UserPromptSubmit": [
|
|
27
|
+
{
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/user_prompt_submit.py",
|
|
32
|
+
"timeout": 5
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"PreToolUse": [
|
|
38
|
+
{
|
|
39
|
+
"matcher": {
|
|
40
|
+
"toolName": "Agent"
|
|
41
|
+
},
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/agent_pre_hook.py",
|
|
46
|
+
"timeout": 5
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"PostToolUse": [
|
|
52
|
+
{
|
|
53
|
+
"matcher": {
|
|
54
|
+
"toolName": "gm_pattern|gm_knowledge|gm_work|gm_docs|gm_salesos|gm_observe"
|
|
55
|
+
},
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/observation_hooks.py",
|
|
60
|
+
"timeout": 5
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"matcher": {
|
|
66
|
+
"toolName": "Read|Grep|Glob"
|
|
67
|
+
},
|
|
68
|
+
"hooks": [
|
|
69
|
+
{
|
|
70
|
+
"type": "command",
|
|
71
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post_tool_retrieval.py",
|
|
72
|
+
"timeout": 5
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"matcher": {
|
|
78
|
+
"toolName": "Agent"
|
|
79
|
+
},
|
|
80
|
+
"hooks": [
|
|
81
|
+
{
|
|
82
|
+
"type": "command",
|
|
83
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/agent_post_hook.py",
|
|
84
|
+
"timeout": 5
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/Users/keithcotterman/Development/greymatter/.venv/bin/python
|
|
2
|
+
"""PostToolUse hook: evaluate tool results for observational memory.
|
|
3
|
+
|
|
4
|
+
Monitors MCP tool calls and queues observation-worthy results
|
|
5
|
+
for batch extraction at the next token threshold.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
# Add greymatter core and cognitive to path
|
|
13
|
+
GREYMATTER_ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "core", "src")
|
|
14
|
+
sys.path.insert(0, os.path.abspath(GREYMATTER_ROOT))
|
|
15
|
+
COGNITIVE_ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "cognitive", "src")
|
|
16
|
+
sys.path.insert(0, os.path.abspath(COGNITIVE_ROOT))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Tools that are never observation-worthy
|
|
20
|
+
SKIP_TOOLS = {"gm_status", "gm_context", "gm_standards", "gm_benchmark", "gm_checkpoint"}
|
|
21
|
+
|
|
22
|
+
# Tools whose create/mutation actions should be captured
|
|
23
|
+
MUTATION_ACTIONS = {"create", "confirm"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main() -> None:
|
|
27
|
+
try:
|
|
28
|
+
input_data = json.load(sys.stdin)
|
|
29
|
+
except (json.JSONDecodeError, EOFError):
|
|
30
|
+
print("{}")
|
|
31
|
+
sys.exit(0)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
tool_name = input_data.get("toolName", "")
|
|
35
|
+
tool_input = input_data.get("toolInput", {})
|
|
36
|
+
tool_result = input_data.get("toolResult", "")
|
|
37
|
+
|
|
38
|
+
# Skip non-observation-worthy tools
|
|
39
|
+
if tool_name in SKIP_TOOLS:
|
|
40
|
+
print("{}")
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
# Only capture mutations and errors
|
|
44
|
+
action = ""
|
|
45
|
+
if isinstance(tool_input, dict):
|
|
46
|
+
action = tool_input.get("action", "")
|
|
47
|
+
|
|
48
|
+
is_mutation = action in MUTATION_ACTIONS
|
|
49
|
+
is_error = False
|
|
50
|
+
if isinstance(tool_result, str):
|
|
51
|
+
try:
|
|
52
|
+
parsed = json.loads(tool_result)
|
|
53
|
+
is_error = "error" in parsed
|
|
54
|
+
except (json.JSONDecodeError, TypeError):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
if not is_mutation and not is_error:
|
|
58
|
+
print("{}")
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
# Record the tool event for batch extraction
|
|
62
|
+
from greymatter_core.config import Config
|
|
63
|
+
from greymatter_core.db import SQLiteDatabase
|
|
64
|
+
from greymatter_core.sessions import SessionManager
|
|
65
|
+
from greymatter_core.cognitive_bridge import get_memory
|
|
66
|
+
|
|
67
|
+
config = Config()
|
|
68
|
+
db = SQLiteDatabase(config.db_path)
|
|
69
|
+
sessions = SessionManager(db)
|
|
70
|
+
memory = get_memory()
|
|
71
|
+
|
|
72
|
+
current = sessions.get_current()
|
|
73
|
+
if not current:
|
|
74
|
+
print("{}")
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
session_id = current["id"]
|
|
78
|
+
|
|
79
|
+
# For mutations, create an observation from the tool result
|
|
80
|
+
if is_mutation and isinstance(tool_result, str):
|
|
81
|
+
content = f"Tool {tool_name} action={action}: {tool_result[:500]}"
|
|
82
|
+
title = f"[tool] {tool_name} {action}"
|
|
83
|
+
|
|
84
|
+
# Check for duplicate
|
|
85
|
+
existing = memory.db.execute_one(
|
|
86
|
+
"SELECT id FROM observations WHERE title = ? AND pruned = 0", (title,)
|
|
87
|
+
)
|
|
88
|
+
if not existing:
|
|
89
|
+
memory.observe(
|
|
90
|
+
content=content,
|
|
91
|
+
category="insight",
|
|
92
|
+
title=title,
|
|
93
|
+
session_id=session_id,
|
|
94
|
+
source="post_tool_use",
|
|
95
|
+
confidence=0.3,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# For errors, capture the error pattern
|
|
99
|
+
if is_error and isinstance(tool_result, str):
|
|
100
|
+
memory.observe(
|
|
101
|
+
content=tool_result[:500],
|
|
102
|
+
category="correction",
|
|
103
|
+
title=f"[error] {tool_name} {action} failed",
|
|
104
|
+
session_id=session_id,
|
|
105
|
+
source="post_tool_use",
|
|
106
|
+
confidence=0.4,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
print("{}")
|
|
110
|
+
|
|
111
|
+
except Exception:
|
|
112
|
+
# Fail silently — don't block tool execution
|
|
113
|
+
print("{}")
|
|
114
|
+
|
|
115
|
+
sys.exit(0)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pattern-capture
|
|
3
|
+
description: Capture patterns and learnings when session ends
|
|
4
|
+
event: Stop
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Before this session ends, briefly reflect on what was accomplished and capture any reusable learnings:
|
|
8
|
+
|
|
9
|
+
1. Identify any patterns, corrections, or gotchas discovered during this session
|
|
10
|
+
2. For each new pattern, call `gm_pattern(action="create", type="<type>", domain="<domain>", title="<title>", observation="<observation>")`
|
|
11
|
+
- Types: convention, anti-pattern, architecture, workflow, debugging
|
|
12
|
+
- Be specific and actionable in the observation
|
|
13
|
+
3. For any knowledge worth preserving, call `gm_knowledge(action="create", entry_type="<type>", title="<title>", content="<content>")`
|
|
14
|
+
- Entry types: expertise, correction, gotcha, reference
|
|
15
|
+
|
|
16
|
+
Only capture genuinely new and useful patterns. Do not create trivial or obvious entries. If nothing novel was learned, skip silently.
|
|
17
|
+
|
|
18
|
+
Do this quickly — spend no more than 10 seconds deciding what to capture.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/Users/keithcotterman/Development/greymatter/.venv/bin/python
|
|
2
|
+
"""PostToolUse retrieval hook: surface relevant memories after read-type tools.
|
|
3
|
+
|
|
4
|
+
Fires after Read, Grep, Glob tool executions. Extracts context cues
|
|
5
|
+
from the tool result and queries the priming cache for relevant memories.
|
|
6
|
+
|
|
7
|
+
Budget: max 1000 tokens per injection.
|
|
8
|
+
Only fires for read-type tools (never for writes).
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
# Add greymatter lib to path
|
|
15
|
+
GREYMATTER_LIB = os.path.join(os.path.dirname(__file__), "..", "..", "lib")
|
|
16
|
+
sys.path.insert(0, os.path.abspath(GREYMATTER_LIB))
|
|
17
|
+
|
|
18
|
+
MAX_TOKENS = 1000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def extract_context_cues(tool_name: str, tool_input: dict) -> list:
|
|
22
|
+
"""Extract context keywords from tool input.
|
|
23
|
+
|
|
24
|
+
Read: file path → language, framework keywords
|
|
25
|
+
Grep: search pattern → topic keywords
|
|
26
|
+
Glob: file pattern → language keywords
|
|
27
|
+
"""
|
|
28
|
+
from priming import extract_keywords
|
|
29
|
+
|
|
30
|
+
cues = []
|
|
31
|
+
|
|
32
|
+
if tool_name == "Read":
|
|
33
|
+
file_path = tool_input.get("file_path", "")
|
|
34
|
+
if file_path:
|
|
35
|
+
# Extract meaningful parts from the path
|
|
36
|
+
cues.extend(extract_keywords(file_path.replace("/", " ").replace(".", " ")))
|
|
37
|
+
|
|
38
|
+
elif tool_name == "Grep":
|
|
39
|
+
pattern = tool_input.get("pattern", "")
|
|
40
|
+
if pattern:
|
|
41
|
+
cues.extend(extract_keywords(pattern))
|
|
42
|
+
path = tool_input.get("path", "")
|
|
43
|
+
if path:
|
|
44
|
+
cues.extend(extract_keywords(path.replace("/", " ")))
|
|
45
|
+
|
|
46
|
+
elif tool_name == "Glob":
|
|
47
|
+
pattern = tool_input.get("pattern", "")
|
|
48
|
+
if pattern:
|
|
49
|
+
cues.extend(extract_keywords(pattern.replace("*", " ").replace("/", " ")))
|
|
50
|
+
|
|
51
|
+
# Deduplicate while preserving order
|
|
52
|
+
seen = set()
|
|
53
|
+
unique = []
|
|
54
|
+
for c in cues:
|
|
55
|
+
if c not in seen:
|
|
56
|
+
seen.add(c)
|
|
57
|
+
unique.append(c)
|
|
58
|
+
|
|
59
|
+
return unique[:10]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main() -> None:
|
|
63
|
+
try:
|
|
64
|
+
input_data = json.load(sys.stdin)
|
|
65
|
+
except (json.JSONDecodeError, EOFError):
|
|
66
|
+
print("{}")
|
|
67
|
+
sys.exit(0)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
tool_name = input_data.get("tool_name", input_data.get("toolName", ""))
|
|
71
|
+
tool_input = input_data.get("tool_input", input_data.get("toolInput", {}))
|
|
72
|
+
|
|
73
|
+
if not isinstance(tool_input, dict):
|
|
74
|
+
print("{}")
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
# Extract context cues
|
|
78
|
+
cues = extract_context_cues(tool_name, tool_input)
|
|
79
|
+
if not cues:
|
|
80
|
+
print("{}")
|
|
81
|
+
sys.exit(0)
|
|
82
|
+
|
|
83
|
+
# Tier 0 lookup from priming cache
|
|
84
|
+
from priming import lookup_by_keywords
|
|
85
|
+
entries = lookup_by_keywords(cues, max_results=3)
|
|
86
|
+
|
|
87
|
+
if not entries:
|
|
88
|
+
print("{}")
|
|
89
|
+
sys.exit(0)
|
|
90
|
+
|
|
91
|
+
# Format as additional context
|
|
92
|
+
lines = []
|
|
93
|
+
tokens_used = 0
|
|
94
|
+
for entry in entries:
|
|
95
|
+
title = entry.get("title", "")
|
|
96
|
+
preview = entry.get("preview", "")[:100]
|
|
97
|
+
line = f"- {title}"
|
|
98
|
+
if preview and preview != title:
|
|
99
|
+
line += f": {preview}"
|
|
100
|
+
|
|
101
|
+
est_tokens = len(line) // 4
|
|
102
|
+
if tokens_used + est_tokens > MAX_TOKENS:
|
|
103
|
+
break
|
|
104
|
+
lines.append(line)
|
|
105
|
+
tokens_used += est_tokens
|
|
106
|
+
|
|
107
|
+
if lines:
|
|
108
|
+
context = "Related from memory:\n" + "\n".join(lines)
|
|
109
|
+
print(context)
|
|
110
|
+
else:
|
|
111
|
+
print("{}")
|
|
112
|
+
|
|
113
|
+
except Exception:
|
|
114
|
+
print("{}")
|
|
115
|
+
|
|
116
|
+
sys.exit(0)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|