universal-agent-context 0.2.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.
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_inject_context.py +108 -0
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_monitor_context.py +217 -0
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_precompact.py +84 -0
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_store.py +277 -0
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_store_realtime.py +146 -0
- universal_agent_context-0.2.0/.claude-plugin/hooks/uacs_tag_prompt.py +260 -0
- universal_agent_context-0.2.0/.claude-plugin/install.sh +142 -0
- universal_agent_context-0.2.0/.claude-plugin/plugin-enhanced.json +122 -0
- universal_agent_context-0.2.0/.claude-plugin/plugin-proactive.json +160 -0
- universal_agent_context-0.2.0/.claude-plugin/plugin.json +63 -0
- universal_agent_context-0.2.0/.claude-plugin/skills/uacs_context.md +105 -0
- universal_agent_context-0.2.0/.claude-plugin/test_hook.py +131 -0
- universal_agent_context-0.2.0/.editorconfig +29 -0
- universal_agent_context-0.2.0/.github/CLAUDE_CODE_HOOKS_ENHANCED.md +409 -0
- universal_agent_context-0.2.0/.github/CLAUDE_CODE_INTEGRATION_TODO.md +549 -0
- universal_agent_context-0.2.0/.github/CLAUDE_CODE_PLUGIN_READY.md +349 -0
- universal_agent_context-0.2.0/.github/COMPACTION_PREVENTION_STRATEGY.md +644 -0
- universal_agent_context-0.2.0/.github/HONEST_LAUNCH_PLAN.md +490 -0
- universal_agent_context-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +54 -0
- universal_agent_context-0.2.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- universal_agent_context-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- universal_agent_context-0.2.0/.github/ISSUE_TEMPLATE/question.md +26 -0
- universal_agent_context-0.2.0/.github/PLUGIN_EVOLUTION.md +519 -0
- universal_agent_context-0.2.0/.github/PRE_RELEASE_AUDIT.md +625 -0
- universal_agent_context-0.2.0/.github/RELEASE_NOTES_v0.1.0.md +171 -0
- universal_agent_context-0.2.0/.github/RELEASE_STRATEGY.md +929 -0
- universal_agent_context-0.2.0/.github/REPOSITORY_SETTINGS.md +59 -0
- universal_agent_context-0.2.0/.github/SESSION_SUMMARY.md +245 -0
- universal_agent_context-0.2.0/.github/SKILL_SUGGESTION_SYSTEM.md +792 -0
- universal_agent_context-0.2.0/.github/TESTING_PLAN.md +405 -0
- universal_agent_context-0.2.0/.github/TEST_RESULTS.md +430 -0
- universal_agent_context-0.2.0/.github/TRACE_VISUALIZATION_DESIGN.md +563 -0
- universal_agent_context-0.2.0/.github/TRACE_VIZ_IMPLEMENTATION_STATUS.md +241 -0
- universal_agent_context-0.2.0/.github/V0.1.0_LAUNCH_GUIDE.md +273 -0
- universal_agent_context-0.2.0/.github/VISUALIZATION_FEATURE_SUMMARY.md +382 -0
- universal_agent_context-0.2.0/.github/WORKFLOWS.md +424 -0
- universal_agent_context-0.2.0/.github/internal/DEVELOPMENT_ROADMAP.md +1684 -0
- universal_agent_context-0.2.0/.github/internal/LAUNCH_STRATEGY.md +575 -0
- universal_agent_context-0.2.0/.github/internal/PERFORMANCE_BENCHMARKS.md +494 -0
- universal_agent_context-0.2.0/.github/internal/SECURITY_IMPLEMENTATION.md +581 -0
- universal_agent_context-0.2.0/.github/internal/proposals/SURREALDB_MEMORY_DESIGN.md +162 -0
- universal_agent_context-0.2.0/.gitignore +63 -0
- universal_agent_context-0.2.0/.pre-commit-config.yaml +58 -0
- universal_agent_context-0.2.0/CHANGELOG.md +265 -0
- universal_agent_context-0.2.0/CLAUDE.md +167 -0
- universal_agent_context-0.2.0/CONTRIBUTING.md +350 -0
- universal_agent_context-0.2.0/Dockerfile +26 -0
- universal_agent_context-0.2.0/LICENSE +21 -0
- universal_agent_context-0.2.0/Makefile +66 -0
- universal_agent_context-0.2.0/PKG-INFO +873 -0
- universal_agent_context-0.2.0/QUICKSTART.md +425 -0
- universal_agent_context-0.2.0/README.md +824 -0
- universal_agent_context-0.2.0/bin/docker-quickstart +145 -0
- universal_agent_context-0.2.0/bin/install +117 -0
- universal_agent_context-0.2.0/docs/ARCHITECTURE.md +644 -0
- universal_agent_context-0.2.0/docs/CLI_REFERENCE.md +219 -0
- universal_agent_context-0.2.0/docs/LIBRARY_GUIDE.md +125 -0
- universal_agent_context-0.2.0/docs/MARKETPLACE.md +38 -0
- universal_agent_context-0.2.0/docs/archive/INSPIRATION_ANALYSIS.md +1627 -0
- universal_agent_context-0.2.0/docs/archive/MARKETPLACE_AGGREGATION_STRATEGY.md +819 -0
- universal_agent_context-0.2.0/docs/archive/SKILLS_MD_FORMAT.md +527 -0
- universal_agent_context-0.2.0/docs/archive/audits/PRE_RELEASE_AUDIT_2026-01-07.md +315 -0
- universal_agent_context-0.2.0/docs/archive/builds/VISUALIZATION_BUILD_COMPLETE_2026-01-31.md +454 -0
- universal_agent_context-0.2.0/docs/archive/phase5/COMPLETION_REPORT.md +485 -0
- universal_agent_context-0.2.0/docs/archive/phase5/INTEGRATION_PROMPT.md +294 -0
- universal_agent_context-0.2.0/docs/archive/testing/TESTING_STATE_2026-01-31.md +281 -0
- universal_agent_context-0.2.0/docs/archive/uacs_code_review_report.txt +51 -0
- universal_agent_context-0.2.0/docs/archive/uacs_security_review_report.txt +38 -0
- universal_agent_context-0.2.0/docs/features/ADAPTERS.md +107 -0
- universal_agent_context-0.2.0/docs/features/CONTEXT.md +550 -0
- universal_agent_context-0.2.0/docs/features/INTEGRATIONS.md +692 -0
- universal_agent_context-0.2.0/docs/features/PACKAGES.md +286 -0
- universal_agent_context-0.2.0/docs/features/VISUALIZATION.md +563 -0
- universal_agent_context-0.2.0/docs/guides/DEVELOPMENT.md +447 -0
- universal_agent_context-0.2.0/docs/guides/MCP_SERVER_BINARY.md +76 -0
- universal_agent_context-0.2.0/docs/guides/MCP_SERVER_DOCKER.md +62 -0
- universal_agent_context-0.2.0/docs/guides/MCP_SERVER_SETUP.md +141 -0
- universal_agent_context-0.2.0/docs/integrations/CLAUDE_DESKTOP.md +717 -0
- universal_agent_context-0.2.0/docs/integrations/CURSOR.md +772 -0
- universal_agent_context-0.2.0/docs/integrations/WINDSURF.md +861 -0
- universal_agent_context-0.2.0/examples/DEMOS.md +508 -0
- universal_agent_context-0.2.0/examples/README.md +338 -0
- universal_agent_context-0.2.0/examples/VISUALIZATION_QUICKSTART.md +218 -0
- universal_agent_context-0.2.0/examples/demo_comprehensive.py +409 -0
- universal_agent_context-0.2.0/examples/quickstart/basic_context.py +61 -0
- universal_agent_context-0.2.0/examples/quickstart/compression_example.py +247 -0
- universal_agent_context-0.2.0/examples/quickstart/custom_adapter.py +85 -0
- universal_agent_context-0.2.0/examples/quickstart/mcp_tool_usage.py +46 -0
- universal_agent_context-0.2.0/examples/quickstart/memory_usage.py +76 -0
- universal_agent_context-0.2.0/examples/quickstart/multi_format_translation.py +237 -0
- universal_agent_context-0.2.0/examples/quickstart/package_install.py +232 -0
- universal_agent_context-0.2.0/examples/quickstart/visualization_demo.py +180 -0
- universal_agent_context-0.2.0/examples/tutorials/01_basic_setup/README.md +189 -0
- universal_agent_context-0.2.0/examples/tutorials/01_basic_setup/demo.py +200 -0
- universal_agent_context-0.2.0/examples/tutorials/01_basic_setup/output.txt +105 -0
- universal_agent_context-0.2.0/examples/tutorials/02_context_compression/README.md +231 -0
- universal_agent_context-0.2.0/examples/tutorials/02_context_compression/comparison.md +327 -0
- universal_agent_context-0.2.0/examples/tutorials/02_context_compression/demo.py +423 -0
- universal_agent_context-0.2.0/examples/tutorials/03_multi_agent_context/README.md +298 -0
- universal_agent_context-0.2.0/examples/tutorials/03_multi_agent_context/architecture.md +672 -0
- universal_agent_context-0.2.0/examples/tutorials/03_multi_agent_context/demo.py +380 -0
- universal_agent_context-0.2.0/examples/tutorials/04_topic_based_retrieval/README.md +299 -0
- universal_agent_context-0.2.0/examples/tutorials/04_topic_based_retrieval/demo.py +341 -0
- universal_agent_context-0.2.0/examples/tutorials/04_topic_based_retrieval/use_cases.md +549 -0
- universal_agent_context-0.2.0/examples/tutorials/05_claude_code_integration/DESIGN.md +810 -0
- universal_agent_context-0.2.0/examples/tutorials/05_claude_code_integration/README.md +334 -0
- universal_agent_context-0.2.0/examples/tutorials/05_claude_code_integration/demo.py +417 -0
- universal_agent_context-0.2.0/pyproject.toml +211 -0
- universal_agent_context-0.2.0/src/uacs/__init__.py +12 -0
- universal_agent_context-0.2.0/src/uacs/adapters/__init__.py +19 -0
- universal_agent_context-0.2.0/src/uacs/adapters/agent_skill_adapter.py +202 -0
- universal_agent_context-0.2.0/src/uacs/adapters/agents_md_adapter.py +330 -0
- universal_agent_context-0.2.0/src/uacs/adapters/base.py +261 -0
- universal_agent_context-0.2.0/src/uacs/adapters/clinerules_adapter.py +39 -0
- universal_agent_context-0.2.0/src/uacs/adapters/cursorrules_adapter.py +39 -0
- universal_agent_context-0.2.0/src/uacs/api.py +262 -0
- universal_agent_context-0.2.0/src/uacs/cli/__init__.py +6 -0
- universal_agent_context-0.2.0/src/uacs/cli/context.py +349 -0
- universal_agent_context-0.2.0/src/uacs/cli/main.py +195 -0
- universal_agent_context-0.2.0/src/uacs/cli/mcp.py +115 -0
- universal_agent_context-0.2.0/src/uacs/cli/memory.py +142 -0
- universal_agent_context-0.2.0/src/uacs/cli/packages.py +309 -0
- universal_agent_context-0.2.0/src/uacs/cli/skills.py +144 -0
- universal_agent_context-0.2.0/src/uacs/cli/utils.py +24 -0
- universal_agent_context-0.2.0/src/uacs/config/repositories.yaml +26 -0
- universal_agent_context-0.2.0/src/uacs/context/__init__.py +0 -0
- universal_agent_context-0.2.0/src/uacs/context/agent_context.py +406 -0
- universal_agent_context-0.2.0/src/uacs/context/shared_context.py +661 -0
- universal_agent_context-0.2.0/src/uacs/context/unified_context.py +332 -0
- universal_agent_context-0.2.0/src/uacs/mcp_server_entry.py +80 -0
- universal_agent_context-0.2.0/src/uacs/memory/__init__.py +5 -0
- universal_agent_context-0.2.0/src/uacs/memory/simple_memory.py +255 -0
- universal_agent_context-0.2.0/src/uacs/packages/__init__.py +26 -0
- universal_agent_context-0.2.0/src/uacs/packages/manager.py +413 -0
- universal_agent_context-0.2.0/src/uacs/packages/models.py +60 -0
- universal_agent_context-0.2.0/src/uacs/packages/sources.py +270 -0
- universal_agent_context-0.2.0/src/uacs/protocols/__init__.py +5 -0
- universal_agent_context-0.2.0/src/uacs/protocols/mcp/__init__.py +8 -0
- universal_agent_context-0.2.0/src/uacs/protocols/mcp/manager.py +77 -0
- universal_agent_context-0.2.0/src/uacs/protocols/mcp/skills_server.py +700 -0
- universal_agent_context-0.2.0/src/uacs/skills_validator.py +367 -0
- universal_agent_context-0.2.0/src/uacs/utils/__init__.py +5 -0
- universal_agent_context-0.2.0/src/uacs/utils/paths.py +24 -0
- universal_agent_context-0.2.0/src/uacs/visualization/README.md +132 -0
- universal_agent_context-0.2.0/src/uacs/visualization/__init__.py +36 -0
- universal_agent_context-0.2.0/src/uacs/visualization/models.py +195 -0
- universal_agent_context-0.2.0/src/uacs/visualization/static/index.html +857 -0
- universal_agent_context-0.2.0/src/uacs/visualization/storage.py +402 -0
- universal_agent_context-0.2.0/src/uacs/visualization/visualization.py +328 -0
- universal_agent_context-0.2.0/src/uacs/visualization/web_server.py +364 -0
- universal_agent_context-0.2.0/tests/conftest.py +64 -0
- universal_agent_context-0.2.0/tests/integration/test_mcp_server_binary.py +260 -0
- universal_agent_context-0.2.0/tests/integration/test_mcp_server_docker.py +374 -0
- universal_agent_context-0.2.0/tests/scripts/test_visualization.sh +138 -0
- universal_agent_context-0.2.0/tests/test_adapters.py +105 -0
- universal_agent_context-0.2.0/tests/test_agent_skill_precedence.py +207 -0
- universal_agent_context-0.2.0/tests/test_api.py +140 -0
- universal_agent_context-0.2.0/tests/test_context.py +180 -0
- universal_agent_context-0.2.0/tests/test_focused_context.py +205 -0
- universal_agent_context-0.2.0/tests/test_multi_skills.py +267 -0
- universal_agent_context-0.2.0/tests/test_package_sources.py +448 -0
- universal_agent_context-0.2.0/tests/test_packages.py +485 -0
- universal_agent_context-0.2.0/tests/test_readme_example.py +30 -0
- universal_agent_context-0.2.0/tests/test_simple_memory.py +210 -0
- universal_agent_context-0.2.0/tests/test_skills_validator.py +419 -0
- universal_agent_context-0.2.0/tests/test_visualization_server.py +267 -0
- universal_agent_context-0.2.0/tools/build_mcp_server.py +116 -0
- universal_agent_context-0.2.0/uv.lock +1890 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UACS SessionStart Hook - Context Injection
|
|
4
|
+
|
|
5
|
+
When resuming a session, automatically inject relevant past context
|
|
6
|
+
from UACS into Claude's initial context.
|
|
7
|
+
|
|
8
|
+
Hook Type: SessionStart
|
|
9
|
+
Fires: When session starts (especially on resume)
|
|
10
|
+
Matcher: "resume" - only fires when resuming, not new sessions
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def inject_context_on_resume(hook_input: dict) -> dict:
|
|
18
|
+
"""Inject UACS context when resuming a session."""
|
|
19
|
+
try:
|
|
20
|
+
from uacs import UACS
|
|
21
|
+
|
|
22
|
+
# Get hook inputs
|
|
23
|
+
source = hook_input.get("source", "")
|
|
24
|
+
project_dir = hook_input.get("cwd", ".")
|
|
25
|
+
|
|
26
|
+
# Only inject on resume (not new sessions)
|
|
27
|
+
if source != "resume":
|
|
28
|
+
return {
|
|
29
|
+
"hookSpecificOutput": {
|
|
30
|
+
"hookEventName": "SessionStart",
|
|
31
|
+
"message": "New session - no context injection needed"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Initialize UACS
|
|
36
|
+
uacs = UACS(project_path=project_dir)
|
|
37
|
+
|
|
38
|
+
# Get recent context (last 5 sessions or 2000 tokens)
|
|
39
|
+
recent_context = uacs.shared_context.get_compressed_context(
|
|
40
|
+
max_tokens=2000, min_quality=0.7
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if not recent_context or len(recent_context.strip()) == 0:
|
|
44
|
+
return {
|
|
45
|
+
"hookSpecificOutput": {
|
|
46
|
+
"hookEventName": "SessionStart",
|
|
47
|
+
"message": "No previous context found"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Get topics from recent conversations
|
|
52
|
+
topics = set()
|
|
53
|
+
for entry in list(uacs.shared_context.entries.values())[-10:]:
|
|
54
|
+
if entry.topics:
|
|
55
|
+
topics.update(entry.topics)
|
|
56
|
+
|
|
57
|
+
# Format context for injection
|
|
58
|
+
context_summary = f"""
|
|
59
|
+
## Previous Session Context (from UACS)
|
|
60
|
+
|
|
61
|
+
You have access to context from previous sessions in this project:
|
|
62
|
+
|
|
63
|
+
**Recent Topics:** {', '.join(sorted(topics)) if topics else 'None'}
|
|
64
|
+
|
|
65
|
+
**Recent Conversations:**
|
|
66
|
+
{recent_context[:1000]}...
|
|
67
|
+
|
|
68
|
+
Use this context to maintain continuity. The user may reference previous discussions.
|
|
69
|
+
""".strip()
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"hookSpecificOutput": {
|
|
73
|
+
"hookEventName": "SessionStart",
|
|
74
|
+
"additionalContext": context_summary,
|
|
75
|
+
"message": f"Injected {len(topics)} topics from previous sessions"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return {
|
|
81
|
+
"hookSpecificOutput": {
|
|
82
|
+
"hookEventName": "SessionStart",
|
|
83
|
+
"error": str(e),
|
|
84
|
+
"message": f"Context injection failed: {type(e).__name__}"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
"""Main entry point for SessionStart hook."""
|
|
91
|
+
try:
|
|
92
|
+
input_data = json.load(sys.stdin)
|
|
93
|
+
result = inject_context_on_resume(input_data)
|
|
94
|
+
print(json.dumps(result))
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
error_result = {
|
|
98
|
+
"hookSpecificOutput": {
|
|
99
|
+
"hookEventName": "SessionStart",
|
|
100
|
+
"error": str(e)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
print(json.dumps(error_result))
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UACS UserPromptSubmit Hook - Context Monitoring and Early Compression
|
|
4
|
+
|
|
5
|
+
Fires on every user prompt to check context window usage.
|
|
6
|
+
If usage exceeds 50%, proactively compress old context to UACS
|
|
7
|
+
to prevent Claude from hitting the 75% auto-compaction threshold.
|
|
8
|
+
|
|
9
|
+
Hook Type: UserPromptSubmit
|
|
10
|
+
Fires: On every user prompt
|
|
11
|
+
Matcher: None (fires for all prompts)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def monitor_and_compress_context(hook_input: dict) -> dict:
|
|
21
|
+
"""Monitor context size and trigger early compression at 50%."""
|
|
22
|
+
try:
|
|
23
|
+
from uacs import UACS
|
|
24
|
+
|
|
25
|
+
# Get context stats from hook input
|
|
26
|
+
session_id = hook_input.get("session_id", "unknown")
|
|
27
|
+
project_dir = hook_input.get("cwd", ".")
|
|
28
|
+
transcript_path = hook_input.get("transcript_path")
|
|
29
|
+
|
|
30
|
+
# Note: Claude Code doesn't expose current token count in hook input yet
|
|
31
|
+
# This is a limitation - we'll estimate based on transcript length
|
|
32
|
+
# In future Claude Code versions, this may be available as:
|
|
33
|
+
# current_tokens = hook_input.get("context_tokens", 0)
|
|
34
|
+
# max_tokens = hook_input.get("max_context_tokens", 200000)
|
|
35
|
+
|
|
36
|
+
# For now, we'll use a heuristic: check transcript size
|
|
37
|
+
if not transcript_path or not Path(transcript_path).exists():
|
|
38
|
+
return {"continue": True, "message": "UACS: No transcript available"}
|
|
39
|
+
|
|
40
|
+
# Estimate token usage based on transcript
|
|
41
|
+
transcript_size = Path(transcript_path).stat().st_size
|
|
42
|
+
estimated_tokens = estimate_tokens_from_size(transcript_size)
|
|
43
|
+
max_tokens = 200000 # Standard Sonnet 4.5 window
|
|
44
|
+
|
|
45
|
+
usage_percent = (estimated_tokens / max_tokens) * 100
|
|
46
|
+
|
|
47
|
+
# Trigger early compression at 50% usage
|
|
48
|
+
COMPRESSION_THRESHOLD = 50.0
|
|
49
|
+
|
|
50
|
+
if usage_percent < COMPRESSION_THRESHOLD:
|
|
51
|
+
# Context is fine, no action needed
|
|
52
|
+
return {"continue": True}
|
|
53
|
+
|
|
54
|
+
# Context is above threshold - trigger UACS compression
|
|
55
|
+
uacs = UACS(project_path=Path(project_dir))
|
|
56
|
+
|
|
57
|
+
# Read transcript and identify old context (first 40% of conversation)
|
|
58
|
+
transcript_lines = read_transcript(Path(transcript_path))
|
|
59
|
+
|
|
60
|
+
if len(transcript_lines) < 5:
|
|
61
|
+
# Too short to compress
|
|
62
|
+
return {"continue": True, "message": "UACS: Session too short to compress"}
|
|
63
|
+
|
|
64
|
+
# Get oldest 40% of conversation
|
|
65
|
+
compression_portion = 0.4
|
|
66
|
+
split_point = int(len(transcript_lines) * compression_portion)
|
|
67
|
+
old_context = transcript_lines[:split_point]
|
|
68
|
+
|
|
69
|
+
# Format for storage
|
|
70
|
+
old_context_text = format_conversation(old_context)
|
|
71
|
+
|
|
72
|
+
# Extract topics (simple heuristics for now)
|
|
73
|
+
# TODO: Use local LLM for better topic extraction
|
|
74
|
+
topics = extract_topics_heuristic(old_context_text)
|
|
75
|
+
|
|
76
|
+
# Store in UACS
|
|
77
|
+
timestamp = datetime.now().isoformat()
|
|
78
|
+
uacs.add_to_context(
|
|
79
|
+
key=f"early_compress_{session_id}_{timestamp}",
|
|
80
|
+
content=old_context_text,
|
|
81
|
+
topics=topics,
|
|
82
|
+
metadata={
|
|
83
|
+
"session_id": session_id,
|
|
84
|
+
"stored_at": timestamp,
|
|
85
|
+
"source": "early-compression",
|
|
86
|
+
"trigger_usage": f"{usage_percent:.1f}%",
|
|
87
|
+
"tokens_archived": len(old_context_text.split()),
|
|
88
|
+
"prevented_compaction": True,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Inform Claude that old context is safely stored
|
|
93
|
+
return {
|
|
94
|
+
"hookSpecificOutput": {
|
|
95
|
+
"hookEventName": "UserPromptSubmit",
|
|
96
|
+
"additionalContext": f"""
|
|
97
|
+
⚙️ UACS Context Management
|
|
98
|
+
|
|
99
|
+
Context window usage reached {usage_percent:.1f}% - triggered early compression.
|
|
100
|
+
|
|
101
|
+
Archived {split_point}/{len(transcript_lines)} conversation turns to UACS storage.
|
|
102
|
+
All history preserved with perfect fidelity.
|
|
103
|
+
|
|
104
|
+
You can continue working without hitting compaction threshold (75%).
|
|
105
|
+
""".strip(),
|
|
106
|
+
"message": f"UACS: Compressed at {usage_percent:.1f}% usage",
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
# Non-blocking - don't interrupt user's prompt
|
|
112
|
+
return {
|
|
113
|
+
"continue": True,
|
|
114
|
+
"error": str(e),
|
|
115
|
+
"message": f"UACS: Context monitoring failed (non-critical): {type(e).__name__}",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def estimate_tokens_from_size(file_size_bytes: int) -> int:
|
|
120
|
+
"""Estimate token count from file size.
|
|
121
|
+
|
|
122
|
+
Rough heuristic: 1 token ≈ 4 characters ≈ 4 bytes (for JSON)
|
|
123
|
+
This is conservative - actual may be lower.
|
|
124
|
+
"""
|
|
125
|
+
return file_size_bytes // 4
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def read_transcript(transcript_path: Path) -> list[dict]:
|
|
129
|
+
"""Read JSONL transcript and parse each line."""
|
|
130
|
+
transcript = []
|
|
131
|
+
with open(transcript_path, "r", encoding="utf-8") as f:
|
|
132
|
+
for line in f:
|
|
133
|
+
if line.strip():
|
|
134
|
+
try:
|
|
135
|
+
transcript.append(json.loads(line))
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
continue
|
|
138
|
+
return transcript
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def format_conversation(transcript: list[dict]) -> str:
|
|
142
|
+
"""Format transcript turns into readable conversation."""
|
|
143
|
+
lines = []
|
|
144
|
+
for turn in transcript:
|
|
145
|
+
role = turn.get("role", "unknown")
|
|
146
|
+
content = turn.get("content", "")
|
|
147
|
+
|
|
148
|
+
if isinstance(content, list):
|
|
149
|
+
# Handle multi-part content (tool uses, etc.)
|
|
150
|
+
for item in content:
|
|
151
|
+
if isinstance(item, dict):
|
|
152
|
+
if item.get("type") == "text":
|
|
153
|
+
lines.append(f"{role}: {item.get('text', '')}")
|
|
154
|
+
elif item.get("type") == "tool_use":
|
|
155
|
+
tool_name = item.get("name", "unknown")
|
|
156
|
+
lines.append(f"{role}: [Tool: {tool_name}]")
|
|
157
|
+
else:
|
|
158
|
+
lines.append(f"{role}: {item}")
|
|
159
|
+
else:
|
|
160
|
+
lines.append(f"{role}: {content}")
|
|
161
|
+
|
|
162
|
+
return "\n\n".join(lines)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def extract_topics_heuristic(content: str) -> list[str]:
|
|
166
|
+
"""Extract topics using simple heuristics.
|
|
167
|
+
|
|
168
|
+
TODO: Replace with local LLM (Ollama) for better quality.
|
|
169
|
+
"""
|
|
170
|
+
topics = set()
|
|
171
|
+
content_lower = content.lower()
|
|
172
|
+
|
|
173
|
+
# Topic keywords
|
|
174
|
+
topic_keywords = {
|
|
175
|
+
"testing": ["test", "pytest", "unittest", "jest", "spec"],
|
|
176
|
+
"security": ["security", "auth", "password", "encryption", "vulnerability", "sql injection", "xss"],
|
|
177
|
+
"performance": ["performance", "optimize", "slow", "cache", "benchmark"],
|
|
178
|
+
"bug": ["bug", "error", "exception", "traceback", "fix"],
|
|
179
|
+
"feature": ["feature", "implement", "add", "create new"],
|
|
180
|
+
"documentation": ["document", "readme", "docs", "comment"],
|
|
181
|
+
"deployment": ["deploy", "docker", "kubernetes", "production"],
|
|
182
|
+
"database": ["database", "sql", "query", "migration", "schema"],
|
|
183
|
+
"api": ["api", "endpoint", "rest", "graphql", "request"],
|
|
184
|
+
"frontend": ["react", "vue", "angular", "component", "ui"],
|
|
185
|
+
"backend": ["server", "backend", "service", "microservice"],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for topic, keywords in topic_keywords.items():
|
|
189
|
+
if any(keyword in content_lower for keyword in keywords):
|
|
190
|
+
topics.add(topic)
|
|
191
|
+
|
|
192
|
+
# Default if no topics found
|
|
193
|
+
if not topics:
|
|
194
|
+
topics.add("general")
|
|
195
|
+
|
|
196
|
+
return list(topics)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def main():
|
|
200
|
+
"""Main entry point for UserPromptSubmit hook."""
|
|
201
|
+
try:
|
|
202
|
+
input_data = json.load(sys.stdin)
|
|
203
|
+
result = monitor_and_compress_context(input_data)
|
|
204
|
+
print(json.dumps(result))
|
|
205
|
+
sys.exit(0)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
error_result = {
|
|
208
|
+
"continue": True,
|
|
209
|
+
"error": str(e),
|
|
210
|
+
"message": "UACS: Monitoring hook failed (non-blocking)",
|
|
211
|
+
}
|
|
212
|
+
print(json.dumps(error_result))
|
|
213
|
+
sys.exit(0)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
main()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UACS PreCompact Hook - Compression Trigger
|
|
4
|
+
|
|
5
|
+
Fires before Claude compacts its context window. This is our chance to:
|
|
6
|
+
1. Store current context in UACS before it's lost
|
|
7
|
+
2. Compress UACS storage to save space
|
|
8
|
+
3. Return additional context to help Claude with compaction
|
|
9
|
+
|
|
10
|
+
Hook Type: PreCompact
|
|
11
|
+
Fires: Before Claude compresses its context window (running low on tokens)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def handle_precompact(hook_input: dict) -> dict:
|
|
19
|
+
"""Handle PreCompact event - store and compress."""
|
|
20
|
+
try:
|
|
21
|
+
from uacs import UACS
|
|
22
|
+
|
|
23
|
+
project_dir = hook_input.get("project_dir", ".")
|
|
24
|
+
trigger = hook_input.get("trigger", "unknown")
|
|
25
|
+
|
|
26
|
+
# Initialize UACS
|
|
27
|
+
uacs = UACS(project_path=project_dir)
|
|
28
|
+
|
|
29
|
+
# Get current stats
|
|
30
|
+
stats_before = uacs.shared_context.get_stats()
|
|
31
|
+
|
|
32
|
+
# Trigger UACS compression/optimization
|
|
33
|
+
uacs.optimize_context()
|
|
34
|
+
|
|
35
|
+
# Get stats after
|
|
36
|
+
stats_after = uacs.shared_context.get_stats()
|
|
37
|
+
|
|
38
|
+
# Calculate savings
|
|
39
|
+
tokens_saved = stats_before.get("total_tokens", 0) - stats_after.get(
|
|
40
|
+
"total_tokens", 0
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"hookSpecificOutput": {
|
|
45
|
+
"hookEventName": "PreCompact",
|
|
46
|
+
"additionalContext": f"""
|
|
47
|
+
UACS has compressed its storage before Claude's compaction:
|
|
48
|
+
- Tokens before: {stats_before.get('total_tokens', 0)}
|
|
49
|
+
- Tokens after: {stats_after.get('total_tokens', 0)}
|
|
50
|
+
- Saved: {tokens_saved} tokens
|
|
51
|
+
|
|
52
|
+
All previous context is safely stored in UACS with perfect fidelity.
|
|
53
|
+
""".strip(),
|
|
54
|
+
"message": f"UACS compression: saved {tokens_saved} tokens",
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return {
|
|
60
|
+
"hookSpecificOutput": {
|
|
61
|
+
"hookEventName": "PreCompact",
|
|
62
|
+
"error": str(e),
|
|
63
|
+
"message": f"PreCompact failed: {type(e).__name__}",
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
"""Main entry point for PreCompact hook."""
|
|
70
|
+
try:
|
|
71
|
+
input_data = json.load(sys.stdin)
|
|
72
|
+
result = handle_precompact(input_data)
|
|
73
|
+
print(json.dumps(result))
|
|
74
|
+
sys.exit(0)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
error_result = {
|
|
77
|
+
"hookSpecificOutput": {"hookEventName": "PreCompact", "error": str(e)}
|
|
78
|
+
}
|
|
79
|
+
print(json.dumps(error_result))
|
|
80
|
+
sys.exit(0)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UACS Hook for Claude Code - Automatic Context Storage
|
|
4
|
+
|
|
5
|
+
This hook fires on SessionEnd and stores the full conversation
|
|
6
|
+
in UACS with perfect fidelity (100% exact storage, zero loss).
|
|
7
|
+
|
|
8
|
+
Hook Lifecycle:
|
|
9
|
+
1. Claude Code session ends
|
|
10
|
+
2. Hook receives JSON via stdin with transcript_path
|
|
11
|
+
3. Read transcript (JSONL format)
|
|
12
|
+
4. Extract topics using heuristics
|
|
13
|
+
5. Store in UACS via Python SDK
|
|
14
|
+
6. Return success/failure JSON
|
|
15
|
+
|
|
16
|
+
Error Handling:
|
|
17
|
+
- Graceful degradation (never block Claude Code)
|
|
18
|
+
- Log errors for debugging
|
|
19
|
+
- Continue on failure (non-critical)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def store_session_to_uacs(hook_input: dict) -> dict:
|
|
29
|
+
"""Store Claude Code session in UACS.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
hook_input: JSON from Claude Code hook containing:
|
|
33
|
+
- transcript_path: Path to session transcript (JSONL)
|
|
34
|
+
- session_id: Unique session identifier
|
|
35
|
+
- project_dir: Current project directory
|
|
36
|
+
- timestamp: Session end timestamp
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
JSON with continue: true/false and optional message/error
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
# Lazy import to avoid startup cost
|
|
43
|
+
from uacs import UACS
|
|
44
|
+
|
|
45
|
+
# Get hook inputs
|
|
46
|
+
transcript_path = hook_input.get("transcript_path")
|
|
47
|
+
session_id = hook_input.get("session_id", "unknown")
|
|
48
|
+
project_dir = hook_input.get("project_dir", ".")
|
|
49
|
+
|
|
50
|
+
if not transcript_path:
|
|
51
|
+
return {
|
|
52
|
+
"continue": True,
|
|
53
|
+
"error": "No transcript_path provided",
|
|
54
|
+
"message": "UACS: Skipped (no transcript)",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Read transcript
|
|
58
|
+
transcript = read_transcript(Path(transcript_path))
|
|
59
|
+
|
|
60
|
+
if not transcript or len(transcript) == 0:
|
|
61
|
+
return {
|
|
62
|
+
"continue": True,
|
|
63
|
+
"error": "Empty transcript",
|
|
64
|
+
"message": "UACS: Skipped (empty session)",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Initialize UACS for this project
|
|
68
|
+
uacs = UACS(project_path=Path(project_dir))
|
|
69
|
+
|
|
70
|
+
# Format conversation with full fidelity
|
|
71
|
+
full_conversation = format_conversation(transcript)
|
|
72
|
+
|
|
73
|
+
# Extract topics (simple heuristics, can be improved with LLM)
|
|
74
|
+
topics = extract_topics(full_conversation)
|
|
75
|
+
|
|
76
|
+
# Store in UACS with metadata
|
|
77
|
+
uacs.add_to_context(
|
|
78
|
+
key=f"claude_code_session_{session_id}",
|
|
79
|
+
content=full_conversation,
|
|
80
|
+
topics=topics,
|
|
81
|
+
metadata={
|
|
82
|
+
"session_id": session_id,
|
|
83
|
+
"stored_at": datetime.now().isoformat(),
|
|
84
|
+
"turn_count": len(transcript),
|
|
85
|
+
"source": "claude-code-hook",
|
|
86
|
+
"hook_version": "0.1.0",
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Success
|
|
91
|
+
return {
|
|
92
|
+
"continue": True,
|
|
93
|
+
"message": f"UACS: Stored session {session_id[:8]}... ({len(topics)} topics, {len(transcript)} turns)",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
except ImportError as e:
|
|
97
|
+
# UACS not installed
|
|
98
|
+
return {
|
|
99
|
+
"continue": True,
|
|
100
|
+
"error": f"UACS not installed: {e}",
|
|
101
|
+
"message": "UACS: Install with 'pip install universal-agent-context'",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
# Graceful degradation - don't break Claude Code
|
|
106
|
+
return {
|
|
107
|
+
"continue": True,
|
|
108
|
+
"error": str(e),
|
|
109
|
+
"message": f"UACS: Storage failed (non-critical): {type(e).__name__}",
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def read_transcript(path: Path) -> list[dict]:
|
|
114
|
+
"""Read JSONL transcript file from Claude Code.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
path: Path to transcript file
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of transcript entries (each is a dict with role, content, etc.)
|
|
121
|
+
"""
|
|
122
|
+
if not path.exists():
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
transcript = []
|
|
126
|
+
try:
|
|
127
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
128
|
+
for line in f:
|
|
129
|
+
line = line.strip()
|
|
130
|
+
if line:
|
|
131
|
+
try:
|
|
132
|
+
transcript.append(json.loads(line))
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
# Skip malformed lines
|
|
135
|
+
continue
|
|
136
|
+
except Exception:
|
|
137
|
+
# Return what we have so far
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
return transcript
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_conversation(transcript: list[dict]) -> str:
|
|
144
|
+
"""Format transcript into readable conversation with full fidelity.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
transcript: List of turn dictionaries
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Formatted conversation string (100% fidelity, no summarization)
|
|
151
|
+
"""
|
|
152
|
+
parts = []
|
|
153
|
+
|
|
154
|
+
for turn in transcript:
|
|
155
|
+
role = turn.get("role", "unknown")
|
|
156
|
+
content = turn.get("content", "")
|
|
157
|
+
|
|
158
|
+
# Handle structured content (text + tool uses)
|
|
159
|
+
if isinstance(content, list):
|
|
160
|
+
text_parts = []
|
|
161
|
+
for item in content:
|
|
162
|
+
if isinstance(item, dict):
|
|
163
|
+
if item.get("type") == "text":
|
|
164
|
+
text_parts.append(item.get("text", ""))
|
|
165
|
+
elif item.get("type") == "tool_use":
|
|
166
|
+
# Include tool usage info
|
|
167
|
+
tool_name = item.get("name", "unknown_tool")
|
|
168
|
+
text_parts.append(f"[Used tool: {tool_name}]")
|
|
169
|
+
else:
|
|
170
|
+
text_parts.append(str(item))
|
|
171
|
+
content = " ".join(text_parts)
|
|
172
|
+
|
|
173
|
+
# Format turn
|
|
174
|
+
if content:
|
|
175
|
+
parts.append(f"[{role}] {content}")
|
|
176
|
+
|
|
177
|
+
return "\n\n".join(parts)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def extract_topics(content: str) -> list[str]:
|
|
181
|
+
"""Extract topics from conversation using heuristics.
|
|
182
|
+
|
|
183
|
+
This is a simple keyword-based approach. In the future, could use
|
|
184
|
+
Claude API for better extraction or transformers.js for local NLP.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
content: Full conversation text
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of topic tags
|
|
191
|
+
"""
|
|
192
|
+
topics = set()
|
|
193
|
+
|
|
194
|
+
# Technical keywords → topics mapping
|
|
195
|
+
keyword_map = {
|
|
196
|
+
"security": [
|
|
197
|
+
"security",
|
|
198
|
+
"vulnerability",
|
|
199
|
+
"attack",
|
|
200
|
+
"injection",
|
|
201
|
+
"xss",
|
|
202
|
+
"csrf",
|
|
203
|
+
"auth",
|
|
204
|
+
"password",
|
|
205
|
+
"token",
|
|
206
|
+
"encryption",
|
|
207
|
+
],
|
|
208
|
+
"performance": [
|
|
209
|
+
"performance",
|
|
210
|
+
"slow",
|
|
211
|
+
"optimize",
|
|
212
|
+
"speed",
|
|
213
|
+
"n+1",
|
|
214
|
+
"query",
|
|
215
|
+
"cache",
|
|
216
|
+
"latency",
|
|
217
|
+
"memory",
|
|
218
|
+
],
|
|
219
|
+
"testing": ["test", "pytest", "unittest", "coverage", "mock", "fixture"],
|
|
220
|
+
"refactor": ["refactor", "clean", "technical debt", "restructure"],
|
|
221
|
+
"bug": ["bug", "error", "crash", "fail", "broken", "fix"],
|
|
222
|
+
"feature": ["feature", "implement", "add", "new"],
|
|
223
|
+
"documentation": ["document", "readme", "comment", "docs"],
|
|
224
|
+
"database": ["database", "sql", "postgres", "mysql", "query", "migration"],
|
|
225
|
+
"api": ["api", "endpoint", "rest", "graphql", "request", "response"],
|
|
226
|
+
"ui": ["ui", "interface", "component", "design", "style", "css"],
|
|
227
|
+
"deployment": ["deploy", "production", "docker", "kubernetes", "ci/cd"],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
content_lower = content.lower()
|
|
231
|
+
|
|
232
|
+
# Check for keyword matches
|
|
233
|
+
for topic, keywords in keyword_map.items():
|
|
234
|
+
if any(keyword in content_lower for keyword in keywords):
|
|
235
|
+
topics.add(topic)
|
|
236
|
+
|
|
237
|
+
# Add programming language topics
|
|
238
|
+
languages = ["python", "javascript", "typescript", "rust", "go", "java"]
|
|
239
|
+
for lang in languages:
|
|
240
|
+
if lang in content_lower:
|
|
241
|
+
topics.add(lang)
|
|
242
|
+
|
|
243
|
+
# Default topic if nothing found
|
|
244
|
+
if not topics:
|
|
245
|
+
topics.add("general")
|
|
246
|
+
|
|
247
|
+
return sorted(list(topics))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def main():
|
|
251
|
+
"""Main entry point for Claude Code hook."""
|
|
252
|
+
try:
|
|
253
|
+
# Claude Code hooks receive JSON via stdin
|
|
254
|
+
input_data = json.load(sys.stdin)
|
|
255
|
+
|
|
256
|
+
# Store session
|
|
257
|
+
result = store_session_to_uacs(input_data)
|
|
258
|
+
|
|
259
|
+
# Return result to Claude Code
|
|
260
|
+
print(json.dumps(result))
|
|
261
|
+
|
|
262
|
+
# Exit 0 = success (continue), exit 2 = block action
|
|
263
|
+
sys.exit(0)
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
# Critical failure - still don't block Claude Code
|
|
267
|
+
error_result = {
|
|
268
|
+
"continue": True,
|
|
269
|
+
"error": f"Hook crashed: {e}",
|
|
270
|
+
"message": "UACS: Critical error (non-blocking)",
|
|
271
|
+
}
|
|
272
|
+
print(json.dumps(error_result))
|
|
273
|
+
sys.exit(0)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
if __name__ == "__main__":
|
|
277
|
+
main()
|