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.
Files changed (46) hide show
  1. greymatter_plugin-1.4.0/.gitignore +56 -0
  2. greymatter_plugin-1.4.0/PKG-INFO +9 -0
  3. greymatter_plugin-1.4.0/commands/gm-sync.md +27 -0
  4. greymatter_plugin-1.4.0/hooks/agent_post_hook.py +53 -0
  5. greymatter_plugin-1.4.0/hooks/agent_pre_hook.py +63 -0
  6. greymatter_plugin-1.4.0/hooks/hooks.json +90 -0
  7. greymatter_plugin-1.4.0/hooks/observation_hooks.py +119 -0
  8. greymatter_plugin-1.4.0/hooks/pattern-capture.md +18 -0
  9. greymatter_plugin-1.4.0/hooks/post_tool_retrieval.py +120 -0
  10. greymatter_plugin-1.4.0/hooks/pre_compact.py +342 -0
  11. greymatter_plugin-1.4.0/hooks/quality-gate.md +22 -0
  12. greymatter_plugin-1.4.0/hooks/run_extraction.py +103 -0
  13. greymatter_plugin-1.4.0/hooks/session-context.md +14 -0
  14. greymatter_plugin-1.4.0/hooks/session_context.py +276 -0
  15. greymatter_plugin-1.4.0/hooks/user_prompt_submit.py +359 -0
  16. greymatter_plugin-1.4.0/plugin.json +14 -0
  17. greymatter_plugin-1.4.0/pyproject.toml +18 -0
  18. greymatter_plugin-1.4.0/run-server.sh +5 -0
  19. greymatter_plugin-1.4.0/skills/create-agent.md +199 -0
  20. greymatter_plugin-1.4.0/skills/gm-dashboard.md +35 -0
  21. greymatter_plugin-1.4.0/skills/gm-pattern.md +23 -0
  22. greymatter_plugin-1.4.0/skills/gm-review.md +22 -0
  23. greymatter_plugin-1.4.0/skills/gm-search.md +66 -0
  24. greymatter_plugin-1.4.0/skills/gm-soul.md +16 -0
  25. greymatter_plugin-1.4.0/skills/gm-status.md +14 -0
  26. greymatter_plugin-1.4.0/skills/gm-work.md +24 -0
  27. greymatter_plugin-1.4.0/skills/node-bootstrap.md +194 -0
  28. greymatter_plugin-1.4.0/src/greymatter_plugin/__init__.py +1 -0
  29. greymatter_plugin-1.4.0/src/greymatter_plugin/__main__.py +4 -0
  30. greymatter_plugin-1.4.0/src/greymatter_plugin/budget.py +242 -0
  31. greymatter_plugin-1.4.0/src/greymatter_plugin/harness.py +242 -0
  32. greymatter_plugin-1.4.0/src/greymatter_plugin/http_server.py +88 -0
  33. greymatter_plugin-1.4.0/src/greymatter_plugin/server.py +1914 -0
  34. greymatter_plugin-1.4.0/tests/__init__.py +0 -0
  35. greymatter_plugin-1.4.0/tests/test_budget.py +164 -0
  36. greymatter_plugin-1.4.0/tests/test_budget_extended.py +160 -0
  37. greymatter_plugin-1.4.0/tests/test_extraction_trigger.py +94 -0
  38. greymatter_plugin-1.4.0/tests/test_gm_code.py +183 -0
  39. greymatter_plugin-1.4.0/tests/test_harness.py +219 -0
  40. greymatter_plugin-1.4.0/tests/test_post_tool_retrieval.py +58 -0
  41. greymatter_plugin-1.4.0/tests/test_pre_compact.py +375 -0
  42. greymatter_plugin-1.4.0/tests/test_run_extraction.py +48 -0
  43. greymatter_plugin-1.4.0/tests/test_savings_metadata.py +41 -0
  44. greymatter_plugin-1.4.0/tests/test_server.py +787 -0
  45. greymatter_plugin-1.4.0/tests/test_session_context.py +222 -0
  46. 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()