mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-10-13 04:59:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcab3ebef4 | ||
|
|
a1d4459329 | ||
|
|
5a81f37779 | ||
|
|
11fd1969de | ||
|
|
cd00f74e2c |
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/lukaszraczylo/claude-mnemonic/pkg/hooks"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/pkg/sanitize"
|
||||
)
|
||||
|
||||
// Input is the hook input from Claude Code.
|
||||
@@ -148,14 +149,14 @@ func handleUserPrompt(ctx *hooks.HookContext, input *Input) (string, error) {
|
||||
obsText += "Key facts:\n"
|
||||
for _, fact := range facts {
|
||||
if factStr, ok := fact.(string); ok {
|
||||
obsText += fmt.Sprintf("- %s\n", factStr)
|
||||
obsText += fmt.Sprintf("- %s\n", sanitize.StripSystemXML(factStr))
|
||||
}
|
||||
}
|
||||
obsText += "\n"
|
||||
}
|
||||
|
||||
if narrative, ok := obsMap["narrative"].(string); ok && narrative != "" {
|
||||
obsText += narrative + "\n\n"
|
||||
obsText += sanitize.StripSystemXML(narrative) + "\n\n"
|
||||
}
|
||||
|
||||
obsTokens := estimateTokens(obsText)
|
||||
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/sugarme/tokenizer v0.3.0
|
||||
github.com/yalue/onnxruntime_go v1.25.0
|
||||
github.com/yalue/onnxruntime_go v1.27.0
|
||||
golang.org/x/sync v0.19.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
|
||||
@@ -54,6 +54,8 @@ github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c h1:pwb4kNSHb4K89
|
||||
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c/go.mod h1:2gwkXLWbDGUQWeL3RtpCmcY4mzCtU13kb9UsAg9xMaw=
|
||||
github.com/yalue/onnxruntime_go v1.25.0 h1:nlhVau1BpLZ/BYr+WpPZCJRD/WES0qo6dK7aKyyAs3g=
|
||||
github.com/yalue/onnxruntime_go v1.25.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
|
||||
github.com/yalue/onnxruntime_go v1.27.0 h1:c1YSgDNtpf0WGtxj3YeRIb8VC5LmM1J+Ve3uHdteC1U=
|
||||
github.com/yalue/onnxruntime_go v1.27.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.23.2
|
||||
1.24.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.23.2
|
||||
1.24.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.23.2
|
||||
1.24.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.23.2
|
||||
1.24.3
|
||||
|
||||
@@ -49,6 +49,10 @@ func BuildObservationPrompt(exec ToolExecution) string {
|
||||
inputJSON, _ := json.MarshalIndent(toolInput, " ", " ")
|
||||
outputJSON, _ := json.MarshalIndent(toolOutput, " ", " ")
|
||||
|
||||
// Strip system XML artifacts from tool data before building prompt
|
||||
cleanInput := StripSystemXML(string(inputJSON))
|
||||
cleanOutput := StripSystemXML(string(outputJSON))
|
||||
|
||||
timestamp := time.UnixMilli(exec.CreatedAtEpoch).Format(time.RFC3339)
|
||||
|
||||
var sb strings.Builder
|
||||
@@ -58,8 +62,8 @@ func BuildObservationPrompt(exec ToolExecution) string {
|
||||
if exec.CWD != "" {
|
||||
sb.WriteString(fmt.Sprintf(" <working_directory>%s</working_directory>\n", exec.CWD))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" <parameters>%s</parameters>\n", truncate(string(inputJSON), 3000)))
|
||||
sb.WriteString(fmt.Sprintf(" <outcome>%s</outcome>\n", truncate(string(outputJSON), 5000)))
|
||||
sb.WriteString(fmt.Sprintf(" <parameters>%s</parameters>\n", truncate(cleanInput, 3000)))
|
||||
sb.WriteString(fmt.Sprintf(" <outcome>%s</outcome>\n", truncate(cleanOutput, 5000)))
|
||||
sb.WriteString("</observed_from_primary_session>")
|
||||
|
||||
return sb.String()
|
||||
@@ -84,8 +88,10 @@ func BuildSummaryPrompt(req SummaryRequest) string {
|
||||
sb.WriteString("Write progress notes of what was done, what was learned, and what's next. This is a checkpoint to capture progress so far. The session is ongoing - you may receive more requests and tool executions after this summary. Write \"next_steps\" as the current trajectory of work (what's actively being worked on or coming up next), not as post-session future work. Always write at least a minimal summary explaining current progress, even if work is still in early stages, so that users see a summary output tied to each request.\n\n")
|
||||
|
||||
if req.LastAssistantMessage != "" {
|
||||
// Strip system XML artifacts from captured transcript content
|
||||
cleanMsg := StripSystemXML(req.LastAssistantMessage)
|
||||
sb.WriteString("Claude's Full Response to User:\n")
|
||||
sb.WriteString(truncate(req.LastAssistantMessage, 4000))
|
||||
sb.WriteString(truncate(cleanMsg, 4000))
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Package sdk provides SDK agent integration for claude-mnemonic.
|
||||
package sdk
|
||||
|
||||
import "github.com/lukaszraczylo/claude-mnemonic/pkg/sanitize"
|
||||
|
||||
// StripSystemXML removes known Claude Code internal XML blocks from text.
|
||||
// Delegates to the shared sanitize package.
|
||||
var StripSystemXML = sanitize.StripSystemXML
|
||||
+10
-10
@@ -13,7 +13,7 @@
|
||||
{
|
||||
"name": "claude-mnemonic",
|
||||
"description": "Persistent memory system for Claude Code - Go implementation with SQLite and ChromaDB vector search",
|
||||
"version": "0.11.37",
|
||||
"version": "0.11.47",
|
||||
"author": {
|
||||
"name": "lukaszraczylo",
|
||||
"url": "https://github.com/lukaszraczylo"
|
||||
@@ -23,25 +23,25 @@
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/lukaszraczylo/claude-mnemonic",
|
||||
"releases": {
|
||||
"latest": "0.11.37",
|
||||
"latest": "0.11.47",
|
||||
"versions": {
|
||||
"0.11.37": {
|
||||
"releaseDate": "2026-02-11",
|
||||
"0.11.47": {
|
||||
"releaseDate": "2026-03-06",
|
||||
"downloads": {
|
||||
"darwin-arm64": {
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.37/claude-mnemonic_0.11.37_darwin_arm64.tar.gz",
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.47/claude-mnemonic_0.11.47_darwin_arm64.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"sha256": "4f0fa6e2034a56baea3e506fc0697b43aba9393cf03fa560ab40cb9520ff804f"
|
||||
"sha256": "486ce84e6c5d0dc21f3516cd0f69dae510b74c100aba921eef232dd2b0040bd8"
|
||||
},
|
||||
"linux-amd64": {
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.37/claude-mnemonic_0.11.37_linux_amd64.tar.gz",
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.47/claude-mnemonic_0.11.47_linux_amd64.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"sha256": "f1138fba85ccfb49541f1c9b0401abd9028f216729cee6784ec0ad1c8f9dde83"
|
||||
"sha256": "fe1692b789977f5466995ebdd8f2ee1ef3eed01c1121f6f17d8350baaf247dac"
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.37/claude-mnemonic_0.11.37_windows_amd64.zip",
|
||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.47/claude-mnemonic_0.11.47_windows_amd64.zip",
|
||||
"format": "zip",
|
||||
"sha256": "e05227844ed0123b874285cdec5a365cd17f1a094537fb0309c00694e9c30289"
|
||||
"sha256": "507de7f2dd07394e06676ff0e569ce2c359d9faa7c5a222335be0b66c7e68ec7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package sanitize provides content cleaning utilities for stripping
|
||||
// Claude Code internal XML artifacts from captured text.
|
||||
package sanitize
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// systemXMLTags lists Claude Code internal XML tags that should be stripped
|
||||
// from captured content before processing. These are system-level artifacts
|
||||
// that pollute observations and summaries when stored.
|
||||
var systemXMLTags = []string{
|
||||
// Claude Code task/agent system
|
||||
"task-notification",
|
||||
// System reminders injected by Claude Code
|
||||
"system-reminder",
|
||||
// Claude-mnemonic's own context injection
|
||||
"relevant-memory",
|
||||
// Hook output wrappers
|
||||
"user-prompt-submit-hook",
|
||||
// Large output persistence
|
||||
"persisted-output",
|
||||
// Tool loading system
|
||||
"available-deferred-tools",
|
||||
// Fast mode info
|
||||
"fast_mode_info",
|
||||
// Anthropic internal
|
||||
"antml_thinking",
|
||||
"antml_function_calls",
|
||||
}
|
||||
|
||||
// systemXMLRegexps are compiled regexps for each tag, built once at init.
|
||||
var systemXMLRegexps []*regexp.Regexp
|
||||
|
||||
func init() {
|
||||
systemXMLRegexps = make([]*regexp.Regexp, len(systemXMLTags))
|
||||
for i, tag := range systemXMLTags {
|
||||
// Match opening tag (with optional attributes), content (including newlines), and closing tag
|
||||
systemXMLRegexps[i] = regexp.MustCompile(`(?s)<` + regexp.QuoteMeta(tag) + `[^>]*>.*?</` + regexp.QuoteMeta(tag) + `>`)
|
||||
}
|
||||
}
|
||||
|
||||
// StripSystemXML removes known Claude Code internal XML blocks from text.
|
||||
// This prevents system artifacts like <task-notification>, <system-reminder>,
|
||||
// and <relevant-memory> from being stored in observations and summaries.
|
||||
func StripSystemXML(s string) string {
|
||||
// Quick check: if no angle brackets, nothing to strip
|
||||
if !strings.Contains(s, "<") {
|
||||
return s
|
||||
}
|
||||
|
||||
for _, re := range systemXMLRegexps {
|
||||
s = re.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
// Clean up resulting double-blank-lines from removed blocks
|
||||
for strings.Contains(s, "\n\n\n") {
|
||||
s = strings.ReplaceAll(s, "\n\n\n", "\n\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package sanitize
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStripSystemXML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no XML tags",
|
||||
input: "Hello, this is plain text with no XML.",
|
||||
expected: "Hello, this is plain text with no XML.",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "task-notification block",
|
||||
input: "Before\n<task-notification>\n<task-id>abc123</task-id>\n<status>completed</status>\n<summary>Agent completed</summary>\n<result>Some result text</result>\n</task-notification>\nAfter",
|
||||
expected: "Before\n\nAfter",
|
||||
},
|
||||
{
|
||||
name: "system-reminder block",
|
||||
input: "Content before\n<system-reminder>\nSome system reminder text\n</system-reminder>\nContent after",
|
||||
expected: "Content before\n\nContent after",
|
||||
},
|
||||
{
|
||||
name: "relevant-memory block",
|
||||
input: "<relevant-memory>\n# Relevant Knowledge\n## 1. Something\n</relevant-memory>\nActual content",
|
||||
expected: "Actual content",
|
||||
},
|
||||
{
|
||||
name: "system-reminder with attributes",
|
||||
input: "Before <system-reminder type=\"hook\">reminder text</system-reminder> After",
|
||||
expected: "Before After",
|
||||
},
|
||||
{
|
||||
name: "multiple different tags",
|
||||
input: "Start\n<task-notification><status>done</status></task-notification>\nMiddle\n<system-reminder>reminder</system-reminder>\nEnd",
|
||||
expected: "Start\n\nMiddle\n\nEnd",
|
||||
},
|
||||
{
|
||||
name: "persisted-output block",
|
||||
input: "Result: <persisted-output>\nLarge output stored at: /tmp/foo\n</persisted-output> done",
|
||||
expected: "Result: done",
|
||||
},
|
||||
{
|
||||
name: "available-deferred-tools block",
|
||||
input: "<available-deferred-tools>\nTool1\nTool2\n</available-deferred-tools>\nUser message",
|
||||
expected: "User message",
|
||||
},
|
||||
{
|
||||
name: "fast_mode_info block",
|
||||
input: "Text <fast_mode_info>\nFast mode uses same model\n</fast_mode_info> more text",
|
||||
expected: "Text more text",
|
||||
},
|
||||
{
|
||||
name: "preserves non-system XML",
|
||||
input: "<observation><type>bugfix</type><title>Fixed thing</title></observation>",
|
||||
expected: "<observation><type>bugfix</type><title>Fixed thing</title></observation>",
|
||||
},
|
||||
{
|
||||
name: "no angle brackets fast path",
|
||||
input: "Just plain text without any special characters",
|
||||
expected: "Just plain text without any special characters",
|
||||
},
|
||||
{
|
||||
name: "collapses triple newlines",
|
||||
input: "Before\n<system-reminder>x</system-reminder>\n\n\nAfter",
|
||||
expected: "Before\n\nAfter",
|
||||
},
|
||||
{
|
||||
name: "real-world task notification",
|
||||
input: "Here is the analysis:\n<task-notification>\n<task-id>a077fd04aed547ce1</task-id>\n<tool-use-id>toolu_01Kw2GwsArQQR1F9aV3VfzhP</tool-use-id>\n<status>completed</status>\n<summary>Agent \"Analyze agency-agents repo structure\" completed</summary>\n<result>Now I have all the information I need. Let me compile a comprehensive report.\n\n## Agency-Agents Directory Exploration Report\n\n### 1. Agent File Count and Organization\n\n**Total Agent Files (excluding strategy docs):** 61 agents across 9 categories</result>\n</task-notification>\nThe repo has 61 agents.",
|
||||
expected: "Here is the analysis:\n\nThe repo has 61 agents.",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := StripSystemXML(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStripSystemXML(b *testing.B) {
|
||||
// Text with no XML (fast path)
|
||||
b.Run("no_xml", func(b *testing.B) {
|
||||
text := "This is plain text without any XML tags at all."
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
StripSystemXML(text)
|
||||
}
|
||||
})
|
||||
|
||||
// Text with system XML to strip
|
||||
b.Run("with_xml", func(b *testing.B) {
|
||||
text := "Before\n<task-notification>\n<task-id>abc</task-id>\n<status>done</status>\n</task-notification>\n<system-reminder>reminder text here</system-reminder>\nAfter"
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
StripSystemXML(text)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
ONNX_VERSION="1.23.2"
|
||||
ONNX_VERSION="1.24.3"
|
||||
ASSETS_DIR="internal/embedding/assets/lib"
|
||||
PLATFORM="${1:-all}"
|
||||
FORCE_DOWNLOAD=false
|
||||
|
||||
+19
-16
@@ -194,29 +194,32 @@ download_release() {
|
||||
fi
|
||||
|
||||
# Verify download integrity via checksum
|
||||
local checksum_url="${download_url}.sha256"
|
||||
local checksums_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/checksums.txt"
|
||||
info "Verifying download integrity..."
|
||||
if curl -sSL -o "$tmp_dir/checksum.sha256" "$checksum_url" 2>/dev/null; then
|
||||
if curl -fsSL -o "$tmp_dir/checksums.txt" "$checksums_url" 2>/dev/null; then
|
||||
local expected_hash actual_hash
|
||||
expected_hash=$(awk '{print $1}' "$tmp_dir/checksum.sha256")
|
||||
if command -v shasum &> /dev/null; then
|
||||
actual_hash=$(shasum -a 256 "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
|
||||
elif command -v sha256sum &> /dev/null; then
|
||||
actual_hash=$(sha256sum "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
|
||||
expected_hash=$(grep "${archive_name}" "$tmp_dir/checksums.txt" | awk '{print $1}')
|
||||
if [[ -z "$expected_hash" ]]; then
|
||||
warn "Archive ${archive_name} not found in checksums.txt, skipping verification"
|
||||
else
|
||||
warn "No SHA256 tool found (shasum or sha256sum), skipping checksum verification"
|
||||
actual_hash=""
|
||||
fi
|
||||
if [[ -n "$actual_hash" ]]; then
|
||||
if [[ "$expected_hash" != "$actual_hash" ]]; then
|
||||
error "Checksum verification failed! Expected: $expected_hash Got: $actual_hash"
|
||||
if command -v shasum &> /dev/null; then
|
||||
actual_hash=$(shasum -a 256 "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
|
||||
elif command -v sha256sum &> /dev/null; then
|
||||
actual_hash=$(sha256sum "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
|
||||
else
|
||||
warn "No SHA256 tool found (shasum or sha256sum), skipping checksum verification"
|
||||
actual_hash=""
|
||||
fi
|
||||
if [[ -n "$actual_hash" ]]; then
|
||||
if [[ "$expected_hash" != "$actual_hash" ]]; then
|
||||
error "Checksum verification failed! Expected: $expected_hash Got: $actual_hash"
|
||||
fi
|
||||
success "Checksum verified"
|
||||
fi
|
||||
success "Checksum verified"
|
||||
fi
|
||||
else
|
||||
warn "No checksum file available at $checksum_url, skipping verification"
|
||||
warn "No checksum file available, skipping verification"
|
||||
fi
|
||||
|
||||
info "Extracting archive..."
|
||||
if [[ "$archive_ext" == "zip" ]]; then
|
||||
if ! command -v unzip &> /dev/null; then
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-mnemonic-dashboard",
|
||||
"version": "v0.10.5-15-g385d05a-dirty",
|
||||
"version": "v0.11.43-2-g11fd196-dirty",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-mnemonic-dashboard",
|
||||
"version": "v0.10.5-15-g385d05a-dirty",
|
||||
"version": "v0.11.43-2-g11fd196-dirty",
|
||||
"dependencies": {
|
||||
"vis-data": "^7.1.9",
|
||||
"vis-network": "^9.1.9",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mnemonic-dashboard",
|
||||
"version": "v0.10.5-15-g385d05a-dirty",
|
||||
"version": "v0.11.43-2-g11fd196-dirty",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user