mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-10-12 04:55:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c0083c24c | ||
|
|
a0fecab186 | ||
|
|
96e64a5796 | ||
|
|
ef92d710b8 | ||
|
|
f8702cb0c2 | ||
|
|
c52bab1e95 | ||
|
|
1eec436746 | ||
|
|
3ccb20df63 | ||
|
|
5fbba6bace | ||
|
|
de422c0de0 | ||
|
|
db2a552eb8 | ||
|
|
195caffc59 | ||
|
|
7b979a3f95 | ||
|
|
49e7efd27d | ||
|
|
fcab3ebef4 | ||
|
|
a1d4459329 | ||
|
|
5a81f37779 |
@@ -14,7 +14,7 @@ GOARCH ?= $(shell go env GOARCH)
|
|||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
BUILD_TAGS := -tags "fts5"
|
BUILD_TAGS := -tags "fts5"
|
||||||
|
|
||||||
.PHONY: all build clean test install lint hooks worker mcp stop-worker start-worker restart-worker dashboard website dev-website setup-libs
|
.PHONY: all build clean test install lint hooks worker mcp stop-worker start-worker restart-worker dashboard website dev-website setup-libs update-version
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -22,8 +22,20 @@ all: build
|
|||||||
setup-libs:
|
setup-libs:
|
||||||
@./scripts/download-onnx-libs.sh all
|
@./scripts/download-onnx-libs.sh all
|
||||||
|
|
||||||
|
# Update root plugin metadata with current version
|
||||||
|
update-version:
|
||||||
|
@mkdir -p .claude-plugin
|
||||||
|
@sed 's/{{ .Version }}/$(VERSION)/g; s/{{.Version}}/$(VERSION)/g' $(PLUGIN_DIR)/.claude-plugin/plugin.json.tpl > .claude-plugin/plugin.json
|
||||||
|
@echo "Updated .claude-plugin/plugin.json to version $(VERSION)"
|
||||||
|
@# marketplace.json contains release-specific data (URLs, SHA256 hashes) that requires manual update per release.
|
||||||
|
@# Only the top-level version field is updated here.
|
||||||
|
@if [ -f marketplace.json ]; then \
|
||||||
|
sed 's/"version": "[^"]*"/"version": "$(VERSION)"/' marketplace.json > marketplace.json.tmp && mv marketplace.json.tmp marketplace.json; \
|
||||||
|
echo "Updated marketplace.json version fields to $(VERSION)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Build all binaries
|
# Build all binaries
|
||||||
build: setup-libs dashboard worker hooks mcp
|
build: setup-libs update-version dashboard worker hooks mcp
|
||||||
|
|
||||||
# Build Vue dashboard
|
# Build Vue dashboard
|
||||||
dashboard:
|
dashboard:
|
||||||
@@ -235,6 +247,7 @@ help:
|
|||||||
@echo " make fmt - Format code"
|
@echo " make fmt - Format code"
|
||||||
@echo " make clean - Clean build artifacts"
|
@echo " make clean - Clean build artifacts"
|
||||||
@echo " make dev - Run worker in development mode"
|
@echo " make dev - Run worker in development mode"
|
||||||
|
@echo " make update-version - Update version in plugin metadata files"
|
||||||
@echo " make deps - Download dependencies"
|
@echo " make deps - Download dependencies"
|
||||||
@echo " make website - Build website for production"
|
@echo " make website - Build website for production"
|
||||||
@echo " make dev-website - Run website dev server"
|
@echo " make dev-website - Run website dev server"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lukaszraczylo/claude-mnemonic/pkg/hooks"
|
"github.com/lukaszraczylo/claude-mnemonic/pkg/hooks"
|
||||||
|
"github.com/lukaszraczylo/claude-mnemonic/pkg/sanitize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Input is the hook input from Claude Code.
|
// 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"
|
obsText += "Key facts:\n"
|
||||||
for _, fact := range facts {
|
for _, fact := range facts {
|
||||||
if factStr, ok := fact.(string); ok {
|
if factStr, ok := fact.(string); ok {
|
||||||
obsText += fmt.Sprintf("- %s\n", factStr)
|
obsText += fmt.Sprintf("- %s\n", sanitize.StripSystemXML(factStr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obsText += "\n"
|
obsText += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if narrative, ok := obsMap["narrative"].(string); ok && narrative != "" {
|
if narrative, ok := obsMap["narrative"].(string); ok && narrative != "" {
|
||||||
obsText += narrative + "\n\n"
|
obsText += sanitize.StripSystemXML(narrative) + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
obsTokens := estimateTokens(obsText)
|
obsTokens := estimateTokens(obsText)
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/go-chi/chi/v5 v5.2.5
|
github.com/go-chi/chi/v5 v5.2.5
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5
|
github.com/go-gormigrate/gormigrate/v2 v2.1.5
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.6
|
||||||
github.com/mattn/go-sqlite3 v1.14.34
|
github.com/mattn/go-sqlite3 v1.14.38
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.35.0
|
||||||
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
|
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/sugarme/tokenizer v0.3.0
|
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
|
golang.org/x/sync v0.20.0
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
@@ -34,7 +34,7 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/schollz/progressbar/v2 v2.15.0 // indirect
|
github.com/schollz/progressbar/v2 v2.15.0 // indirect
|
||||||
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c // indirect
|
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ github.com/asg017/sqlite-vec-go-bindings v0.1.6 h1:Nx0jAzyS38XpkKznJ9xQjFXz2X9tI
|
|||||||
github.com/asg017/sqlite-vec-go-bindings v0.1.6/go.mod h1:A8+cTt/nKFsYCQF6OgzSNpKZrzNo5gQsXBTfsXHXY0Q=
|
github.com/asg017/sqlite-vec-go-bindings v0.1.6/go.mod h1:A8+cTt/nKFsYCQF6OgzSNpKZrzNo5gQsXBTfsXHXY0Q=
|
||||||
github.com/clems4ever/tokenizer v0.0.0-20250926133620-9ddc80533c43 h1:j8YQypEqa5OjqbGciCNb9hOcYbo1oTVuEjd/iu9U2SY=
|
github.com/clems4ever/tokenizer v0.0.0-20250926133620-9ddc80533c43 h1:j8YQypEqa5OjqbGciCNb9hOcYbo1oTVuEjd/iu9U2SY=
|
||||||
github.com/clems4ever/tokenizer v0.0.0-20250926133620-9ddc80533c43/go.mod h1:VJ+DLK5ZEZwzvODOWwY0cw+B1dabTd3nCB5HuFCItCc=
|
github.com/clems4ever/tokenizer v0.0.0-20250926133620-9ddc80533c43/go.mod h1:VJ+DLK5ZEZwzvODOWwY0cw+B1dabTd3nCB5HuFCItCc=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -14,34 +13,28 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
|||||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
|
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
|
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4=
|
||||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
|
||||||
github.com/schollz/progressbar/v2 v2.15.0 h1:dVzHQ8fHRmtPjD3K10jT3Qgn/+H+92jhPrhmxIJfDz8=
|
github.com/schollz/progressbar/v2 v2.15.0 h1:dVzHQ8fHRmtPjD3K10jT3Qgn/+H+92jhPrhmxIJfDz8=
|
||||||
github.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=
|
github.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=
|
||||||
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
|
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
|
||||||
@@ -52,17 +45,15 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c h1:pwb4kNSHb4K89ymCaN+5lPH/MwnfSVg4rzGDh4d+iy4=
|
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c h1:pwb4kNSHb4K89ymCaN+5lPH/MwnfSVg4rzGDh4d+iy4=
|
||||||
github.com/sugarme/regexpset v0.0.0-20200920021344-4d4ec8eaf93c/go.mod h1:2gwkXLWbDGUQWeL3RtpCmcY4mzCtU13kb9UsAg9xMaw=
|
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.27.0 h1:c1YSgDNtpf0WGtxj3YeRIb8VC5LmM1J+Ve3uHdteC1U=
|
||||||
github.com/yalue/onnxruntime_go v1.25.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
|
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -264,6 +264,28 @@ func (s *PromptStore) FindRecentPromptByText(ctx context.Context, claudeSessionI
|
|||||||
return prompt.ID, prompt.PromptNumber, true
|
return prompt.ID, prompt.PromptNumber, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindRecentPromptByTextGlobal finds a recent prompt by exact text match within a time window
|
||||||
|
// across ALL sessions (not scoped to a single claude session ID).
|
||||||
|
// This catches duplicates when the same prompt arrives from different session IDs,
|
||||||
|
// e.g. when the SDK processor's callClaudeCLI subprocess fires with a different session ID.
|
||||||
|
// Returns (promptID, promptNumber, found).
|
||||||
|
func (s *PromptStore) FindRecentPromptByTextGlobal(ctx context.Context, promptText string, withinSeconds int) (int64, int, bool) {
|
||||||
|
cutoffEpoch := time.Now().Add(-time.Duration(withinSeconds) * time.Second).UnixMilli()
|
||||||
|
|
||||||
|
var prompt UserPrompt
|
||||||
|
err := s.db.WithContext(ctx).
|
||||||
|
Where("prompt_text = ? AND created_at_epoch >= ?",
|
||||||
|
promptText, cutoffEpoch).
|
||||||
|
Order("created_at_epoch DESC").
|
||||||
|
First(&prompt).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt.ID, prompt.PromptNumber, true
|
||||||
|
}
|
||||||
|
|
||||||
// GetRecentUserPromptsByProject retrieves recent user prompts for a specific project.
|
// GetRecentUserPromptsByProject retrieves recent user prompts for a specific project.
|
||||||
func (s *PromptStore) GetRecentUserPromptsByProject(ctx context.Context, project string, limit int) ([]*models.UserPromptWithSession, error) {
|
func (s *PromptStore) GetRecentUserPromptsByProject(ctx context.Context, project string, limit int) ([]*models.UserPromptWithSession, error) {
|
||||||
var results []struct {
|
var results []struct {
|
||||||
|
|||||||
@@ -333,6 +333,39 @@ func TestPromptStore_FindRecentPromptByText(t *testing.T) {
|
|||||||
assert.False(t, notFound, "Should not find prompt outside time window")
|
assert.False(t, notFound, "Should not find prompt outside time window")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPromptStore_FindRecentPromptByTextGlobal(t *testing.T) {
|
||||||
|
promptStore, _, cleanup := testPromptStore(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Save a prompt under session "claude-1"
|
||||||
|
id, err := promptStore.SaveUserPromptWithMatches(ctx, "claude-1", 1, "What is the architecture?", 3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Global search should find it without specifying session ID
|
||||||
|
foundID, foundNumber, found := promptStore.FindRecentPromptByTextGlobal(ctx, "What is the architecture?", 60)
|
||||||
|
assert.True(t, found, "Should find the prompt globally")
|
||||||
|
assert.Equal(t, id, foundID)
|
||||||
|
assert.Equal(t, 1, foundNumber)
|
||||||
|
|
||||||
|
// Global search should find it even when a different session ID would have been used
|
||||||
|
// (This is the core cross-session dedup scenario)
|
||||||
|
foundID2, foundNumber2, found2 := promptStore.FindRecentPromptByTextGlobal(ctx, "What is the architecture?", 60)
|
||||||
|
assert.True(t, found2, "Should find the prompt from any session")
|
||||||
|
assert.Equal(t, id, foundID2)
|
||||||
|
assert.Equal(t, 1, foundNumber2)
|
||||||
|
|
||||||
|
// Different text should not match
|
||||||
|
_, _, notFound := promptStore.FindRecentPromptByTextGlobal(ctx, "Different text", 60)
|
||||||
|
assert.False(t, notFound, "Should not find a different prompt")
|
||||||
|
|
||||||
|
// Should not find prompt outside time window
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
_, _, notFound = promptStore.FindRecentPromptByTextGlobal(ctx, "What is the architecture?", 0)
|
||||||
|
assert.False(t, notFound, "Should not find prompt outside time window")
|
||||||
|
}
|
||||||
|
|
||||||
func TestPromptStore_GetRecentUserPromptsByProject(t *testing.T) {
|
func TestPromptStore_GetRecentUserPromptsByProject(t *testing.T) {
|
||||||
promptStore, store, cleanup := testPromptStore(t)
|
promptStore, store, cleanup := testPromptStore(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ type PromptReader interface {
|
|||||||
GetAllPrompts(ctx context.Context) ([]*models.UserPromptWithSession, error)
|
GetAllPrompts(ctx context.Context) ([]*models.UserPromptWithSession, error)
|
||||||
GetRecentUserPromptsByProject(ctx context.Context, project string, limit int) ([]*models.UserPromptWithSession, error)
|
GetRecentUserPromptsByProject(ctx context.Context, project string, limit int) ([]*models.UserPromptWithSession, error)
|
||||||
FindRecentPromptByText(ctx context.Context, claudeSessionID, promptText string, withinSeconds int) (int64, int, bool)
|
FindRecentPromptByText(ctx context.Context, claudeSessionID, promptText string, withinSeconds int) (int64, int, bool)
|
||||||
|
FindRecentPromptByTextGlobal(ctx context.Context, promptText string, withinSeconds int) (int64, int, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PromptWriter defines write operations for prompts.
|
// PromptWriter defines write operations for prompts.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@@ -44,7 +45,30 @@ func (s *Service) handleSessionInit(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Reject requests from internal CLI calls (callClaudeCLI runs from /tmp with no session ID).
|
||||||
|
// These are the memory extraction agent's own prompts, not real user prompts.
|
||||||
|
if req.ClaudeSessionID == "" {
|
||||||
|
log.Debug().Str("project", req.Project).Msg("Rejecting session init with empty claude session ID (internal call)")
|
||||||
|
writeJSON(w, SessionInitResponse{
|
||||||
|
Skipped: true,
|
||||||
|
Reason: "internal",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject prompts that contain the internal system prompt (from SDK processor).
|
||||||
|
// The SDK processor sets CLAUDE_MNEMONIC_INTERNAL=1 on the subprocess env,
|
||||||
|
// but Claude Code does NOT propagate custom env vars to hook subprocesses,
|
||||||
|
// so that guard fails and the system prompt leaks into the database as a
|
||||||
|
// regular user prompt. This server-side check catches those cases reliably.
|
||||||
|
if strings.Contains(req.Prompt, "You are a memory extraction agent for Claude Code sessions") {
|
||||||
|
log.Debug().Str("project", req.Project).Msg("Rejecting session init with internal system prompt")
|
||||||
|
writeJSON(w, SessionInitResponse{
|
||||||
|
Skipped: true,
|
||||||
|
Reason: "internal",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
// Privacy check
|
// Privacy check
|
||||||
if privacy.IsEntirelyPrivate(req.Prompt) {
|
if privacy.IsEntirelyPrivate(req.Prompt) {
|
||||||
// Create session but skip processing
|
// Create session but skip processing
|
||||||
@@ -84,6 +108,24 @@ func (s *Service) handleSessionInit(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CROSS-SESSION DUPLICATE DETECTION: Same prompt text from ANY session within the window.
|
||||||
|
// This catches duplicates when the SDK processor's callClaudeCLI subprocess fires
|
||||||
|
// UserPromptSubmit with a different Claude session ID than the original hook.
|
||||||
|
if _, existingNum, found := s.promptStore.FindRecentPromptByTextGlobal(r.Context(), cleanedPrompt, DuplicatePromptWindowSeconds); found {
|
||||||
|
sessionID, _ := s.sessionStore.CreateSDKSession(r.Context(), req.ClaudeSessionID, req.Project, cleanedPrompt)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Int64("sessionId", sessionID).
|
||||||
|
Int("promptNumber", existingNum).
|
||||||
|
Msg("Cross-session duplicate prompt detected")
|
||||||
|
|
||||||
|
writeJSON(w, SessionInitResponse{
|
||||||
|
SessionDBID: sessionID,
|
||||||
|
PromptNumber: existingNum,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create session (idempotent)
|
// Create session (idempotent)
|
||||||
sessionID, err := s.sessionStore.CreateSDKSession(r.Context(), req.ClaudeSessionID, req.Project, cleanedPrompt)
|
sessionID, err := s.sessionStore.CreateSDKSession(r.Context(), req.ClaudeSessionID, req.Project, cleanedPrompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,6 +252,12 @@ func (s *Service) handleObservation(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Reject requests from internal CLI calls (no session ID = internal callClaudeCLI)
|
||||||
|
if req.ClaudeSessionID == "" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Find session
|
// Find session
|
||||||
sess, err := s.sessionStore.FindAnySDKSession(r.Context(), req.ClaudeSessionID)
|
sess, err := s.sessionStore.FindAnySDKSession(r.Context(), req.ClaudeSessionID)
|
||||||
|
|||||||
@@ -696,7 +696,12 @@ func (p *Processor) callClaudeCLI(ctx context.Context, prompt string) (string, e
|
|||||||
// (hooks are triggered based on working directory)
|
// (hooks are triggered based on working directory)
|
||||||
cmd.Dir = "/tmp"
|
cmd.Dir = "/tmp"
|
||||||
|
|
||||||
// Disable any plugin hooks by setting an env var that our hooks can check
|
// Disable any plugin hooks by setting an env var that our hooks can check.
|
||||||
|
// NOTE: This env var is NOT reliably propagated to hook subprocesses because
|
||||||
|
// Claude Code constructs its own environment for hooks. The server-side check
|
||||||
|
// in handleSessionInit (handlers_sessions.go) provides the reliable guard by
|
||||||
|
// detecting the system prompt signature in the prompt text. This env var is
|
||||||
|
// kept as a best-effort defense-in-depth measure for cases where it does work.
|
||||||
cmd.Env = append(os.Environ(), "CLAUDE_MNEMONIC_INTERNAL=1")
|
cmd.Env = append(os.Environ(), "CLAUDE_MNEMONIC_INTERNAL=1")
|
||||||
|
|
||||||
// Capture output with size limits to prevent unbounded memory usage
|
// Capture output with size limits to prevent unbounded memory usage
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ func BuildObservationPrompt(exec ToolExecution) string {
|
|||||||
inputJSON, _ := json.MarshalIndent(toolInput, " ", " ")
|
inputJSON, _ := json.MarshalIndent(toolInput, " ", " ")
|
||||||
outputJSON, _ := json.MarshalIndent(toolOutput, " ", " ")
|
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)
|
timestamp := time.UnixMilli(exec.CreatedAtEpoch).Format(time.RFC3339)
|
||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
@@ -58,8 +62,8 @@ func BuildObservationPrompt(exec ToolExecution) string {
|
|||||||
if exec.CWD != "" {
|
if exec.CWD != "" {
|
||||||
sb.WriteString(fmt.Sprintf(" <working_directory>%s</working_directory>\n", 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(" <parameters>%s</parameters>\n", truncate(cleanInput, 3000)))
|
||||||
sb.WriteString(fmt.Sprintf(" <outcome>%s</outcome>\n", truncate(string(outputJSON), 5000)))
|
sb.WriteString(fmt.Sprintf(" <outcome>%s</outcome>\n", truncate(cleanOutput, 5000)))
|
||||||
sb.WriteString("</observed_from_primary_session>")
|
sb.WriteString("</observed_from_primary_session>")
|
||||||
|
|
||||||
return sb.String()
|
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")
|
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 != "" {
|
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("Claude's Full Response to User:\n")
|
||||||
sb.WriteString(truncate(req.LastAssistantMessage, 4000))
|
sb.WriteString(truncate(cleanMsg, 4000))
|
||||||
sb.WriteString("\n\n")
|
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",
|
"name": "claude-mnemonic",
|
||||||
"description": "Persistent memory system for Claude Code - Go implementation with SQLite and ChromaDB vector search",
|
"description": "Persistent memory system for Claude Code - Go implementation with SQLite and ChromaDB vector search",
|
||||||
"version": "0.11.43",
|
"version": "0.11.77",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "lukaszraczylo",
|
"name": "lukaszraczylo",
|
||||||
"url": "https://github.com/lukaszraczylo"
|
"url": "https://github.com/lukaszraczylo"
|
||||||
@@ -23,25 +23,25 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/lukaszraczylo/claude-mnemonic",
|
"homepage": "https://github.com/lukaszraczylo/claude-mnemonic",
|
||||||
"releases": {
|
"releases": {
|
||||||
"latest": "0.11.43",
|
"latest": "0.11.77",
|
||||||
"versions": {
|
"versions": {
|
||||||
"0.11.43": {
|
"0.11.77": {
|
||||||
"releaseDate": "2026-03-06",
|
"releaseDate": "2026-03-28",
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.43/claude-mnemonic_0.11.43_darwin_arm64.tar.gz",
|
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.77/claude-mnemonic_0.11.77_darwin_arm64.tar.gz",
|
||||||
"format": "tar.gz",
|
"format": "tar.gz",
|
||||||
"sha256": "b27df5f25e2fc57e34e09e42670ba782f240efa6b92865632a5f190e68b24f4d"
|
"sha256": "9965e9985c738521092f13ed01c4fcc563eed749acbd54f61efff65083b100df"
|
||||||
},
|
},
|
||||||
"linux-amd64": {
|
"linux-amd64": {
|
||||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.43/claude-mnemonic_0.11.43_linux_amd64.tar.gz",
|
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.77/claude-mnemonic_0.11.77_linux_amd64.tar.gz",
|
||||||
"format": "tar.gz",
|
"format": "tar.gz",
|
||||||
"sha256": "d4477529e0a99bcb496fdeeceaf75bcedb8f123b6e00172245586349a36a3e52"
|
"sha256": "92e7a28b8923b52f0ec2c050074c1cdfb18ef592b1ab8ed79bdcd65ff5a20278"
|
||||||
},
|
},
|
||||||
"windows-amd64": {
|
"windows-amd64": {
|
||||||
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.43/claude-mnemonic_0.11.43_windows_amd64.zip",
|
"url": "https://github.com/lukaszraczylo/claude-mnemonic/releases/download/v0.11.77/claude-mnemonic_0.11.77_windows_amd64.zip",
|
||||||
"format": "zip",
|
"format": "zip",
|
||||||
"sha256": "495b95dc2cc701f106463d9fa859f1815859234aa994398230f20584f403e8a5"
|
"sha256": "1d9b465fd24f1569e5647341c79064db1767379c8da7c6368b372ee1344313d8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -410,8 +410,6 @@ func findWorkerBinary() string {
|
|||||||
locations := []string{
|
locations := []string{
|
||||||
"./worker",
|
"./worker",
|
||||||
"./bin/worker",
|
"./bin/worker",
|
||||||
filepath.Join(home, ".claude/plugins/cache/claude-mnemonic/claude-mnemonic/1.0.0/worker"),
|
|
||||||
filepath.Join(home, ".claude/plugins/marketplaces/claude-mnemonic/worker"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, loc := range locations {
|
for _, loc := range locations {
|
||||||
@@ -420,6 +418,19 @@ func findWorkerBinary() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try cache directory with any version (glob returns lexically sorted matches)
|
||||||
|
matches, _ := filepath.Glob(filepath.Join(home, ".claude/plugins/cache/claude-mnemonic/claude-mnemonic/*/worker"))
|
||||||
|
if len(matches) > 0 {
|
||||||
|
// Use the last match (latest version due to lexical sorting)
|
||||||
|
return matches[len(matches)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try marketplaces directory
|
||||||
|
marketplacePath := filepath.Join(home, ".claude/plugins/marketplaces/claude-mnemonic/worker")
|
||||||
|
if _, err := os.Stat(marketplacePath); err == nil {
|
||||||
|
return marketplacePath
|
||||||
|
}
|
||||||
|
|
||||||
// Try PATH
|
// Try PATH
|
||||||
if path, err := exec.LookPath("claude-mnemonic-worker"); err == nil {
|
if path, err := exec.LookPath("claude-mnemonic-worker"); err == nil {
|
||||||
return path
|
return path
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,5 +5,14 @@
|
|||||||
"author": {
|
"author": {
|
||||||
"name": "lukaszraczylo",
|
"name": "lukaszraczylo",
|
||||||
"email": "[email protected]"
|
"email": "[email protected]"
|
||||||
}
|
},
|
||||||
|
"hooks": "./hooks/hooks.json",
|
||||||
|
"mcpServers": {
|
||||||
|
"claude-mnemonic": {
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-server",
|
||||||
|
"args": ["--project", "${CLAUDE_PROJECT}"],
|
||||||
|
"env": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": ["./commands/restart.md"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ONNX_VERSION="1.23.2"
|
ONNX_VERSION="1.24.3"
|
||||||
ASSETS_DIR="internal/embedding/assets/lib"
|
ASSETS_DIR="internal/embedding/assets/lib"
|
||||||
PLATFORM="${1:-all}"
|
PLATFORM="${1:-all}"
|
||||||
FORCE_DOWNLOAD=false
|
FORCE_DOWNLOAD=false
|
||||||
|
|||||||
+110
-51
@@ -69,7 +69,101 @@ if [ ! -f "$MARKETPLACES_FILE" ]; then
|
|||||||
echo '{}' > "$MARKETPLACES_FILE"
|
echo '{}' > "$MARKETPLACES_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if jq is available
|
# Validate marketplace path exists and contains expected files
|
||||||
|
if [ ! -d "$MARKETPLACE_PATH" ]; then
|
||||||
|
echo "Warning: Marketplace directory not found at $MARKETPLACE_PATH"
|
||||||
|
echo "Plugin files may not be copied to cache correctly."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure cache directory exists and copy plugin files
|
||||||
|
mkdir -p "$CACHE_PATH/.claude-plugin"
|
||||||
|
mkdir -p "$CACHE_PATH/hooks"
|
||||||
|
mkdir -p "$CACHE_PATH/commands"
|
||||||
|
|
||||||
|
# Copy files from marketplace to cache
|
||||||
|
if ! cp -r "$MARKETPLACE_PATH/"* "$CACHE_PATH/" 2>/dev/null; then
|
||||||
|
echo "ERROR: Failed to copy plugin files to cache directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify critical files exist in cache
|
||||||
|
for f in worker mcp-server hooks/hooks.json .claude-plugin/plugin.json; do
|
||||||
|
if [ ! -f "$CACHE_PATH/$f" ]; then
|
||||||
|
echo "WARNING: Expected file $f not found in cache after copy"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- JSON registration ---
|
||||||
|
# Uses jq if available, falls back to python3 for systems without jq.
|
||||||
|
|
||||||
|
STATUSLINE_CMD="\${CLAUDE_PLUGIN_ROOT}/hooks/statusline"
|
||||||
|
|
||||||
|
# Helper: register plugin using python3 (jq-free fallback)
|
||||||
|
register_with_python() {
|
||||||
|
python3 - "$PLUGINS_FILE" "$SETTINGS_FILE" "$MARKETPLACES_FILE" \
|
||||||
|
"$PLUGIN_KEY" "$CACHE_PATH" "$VERSION" "$TIMESTAMP" \
|
||||||
|
"$STATUSLINE_CMD" "$MARKETPLACE_NAME" "$MARKETPLACE_PATH" <<'PYEOF'
|
||||||
|
import json, sys, os
|
||||||
|
|
||||||
|
plugins_file, settings_file, marketplaces_file = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
plugin_key, cache_path, version, timestamp = sys.argv[4], sys.argv[5], sys.argv[6], sys.argv[7]
|
||||||
|
statusline_cmd, marketplace_name, marketplace_path = sys.argv[8], sys.argv[9], sys.argv[10]
|
||||||
|
|
||||||
|
def load_json(path):
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_json(path, data):
|
||||||
|
tmp = path + ".tmp"
|
||||||
|
with open(tmp, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
os.replace(tmp, path)
|
||||||
|
|
||||||
|
# 1. installed_plugins.json
|
||||||
|
plugins = load_json(plugins_file)
|
||||||
|
plugins.setdefault("version", 2)
|
||||||
|
plugins.setdefault("plugins", {})
|
||||||
|
plugins["plugins"][plugin_key] = [{
|
||||||
|
"scope": "user",
|
||||||
|
"installPath": cache_path,
|
||||||
|
"version": version,
|
||||||
|
"installedAt": timestamp,
|
||||||
|
"lastUpdated": timestamp,
|
||||||
|
"isLocal": True
|
||||||
|
}]
|
||||||
|
save_json(plugins_file, plugins)
|
||||||
|
print("Plugin registered in installed_plugins.json")
|
||||||
|
|
||||||
|
# 2. settings.json
|
||||||
|
settings = load_json(settings_file)
|
||||||
|
settings.setdefault("enabledPlugins", {})
|
||||||
|
settings["enabledPlugins"][plugin_key] = True
|
||||||
|
settings["statusLine"] = {
|
||||||
|
"type": "command",
|
||||||
|
"command": statusline_cmd,
|
||||||
|
"padding": 0
|
||||||
|
}
|
||||||
|
save_json(settings_file, settings)
|
||||||
|
print("Plugin enabled in settings.json")
|
||||||
|
print("Statusline configured in settings.json")
|
||||||
|
|
||||||
|
# 3. known_marketplaces.json
|
||||||
|
marketplaces = load_json(marketplaces_file)
|
||||||
|
marketplaces[marketplace_name] = {
|
||||||
|
"source": {"source": "directory", "path": marketplace_path},
|
||||||
|
"installLocation": marketplace_path,
|
||||||
|
"lastUpdated": timestamp
|
||||||
|
}
|
||||||
|
save_json(marketplaces_file, marketplaces)
|
||||||
|
print("Marketplace registered in known_marketplaces.json")
|
||||||
|
PYEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect JSON tool and register
|
||||||
if command -v jq &> /dev/null; then
|
if command -v jq &> /dev/null; then
|
||||||
# Validate jq version (1.6+ required for //= operator)
|
# Validate jq version (1.6+ required for //= operator)
|
||||||
JQ_VERSION=$(jq --version 2>/dev/null | sed 's/jq-//')
|
JQ_VERSION=$(jq --version 2>/dev/null | sed 's/jq-//')
|
||||||
@@ -77,26 +171,18 @@ if command -v jq &> /dev/null; then
|
|||||||
JQ_MINOR=$(echo "$JQ_VERSION" | cut -d. -f2)
|
JQ_MINOR=$(echo "$JQ_VERSION" | cut -d. -f2)
|
||||||
if [ -n "$JQ_MAJOR" ] && [ -n "$JQ_MINOR" ]; then
|
if [ -n "$JQ_MAJOR" ] && [ -n "$JQ_MINOR" ]; then
|
||||||
if [ "$JQ_MAJOR" -lt 1 ] || { [ "$JQ_MAJOR" -eq 1 ] && [ "$JQ_MINOR" -lt 6 ]; }; then
|
if [ "$JQ_MAJOR" -lt 1 ] || { [ "$JQ_MAJOR" -eq 1 ] && [ "$JQ_MINOR" -lt 6 ]; }; then
|
||||||
echo "ERROR: jq 1.6+ is required (found jq-$JQ_VERSION)"
|
echo "jq $JQ_VERSION found but too old (need 1.6+), trying python3..."
|
||||||
echo "Please upgrade jq: brew install jq (macOS) or apt-get install jq (Linux)"
|
if command -v python3 &> /dev/null; then
|
||||||
exit 1
|
register_with_python
|
||||||
|
else
|
||||||
|
echo "ERROR: jq 1.6+ or python3 is required for plugin registration"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Plugin registered successfully using python3"
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate marketplace path exists and contains expected files
|
|
||||||
if [ ! -d "$MARKETPLACE_PATH" ]; then
|
|
||||||
echo "Warning: Marketplace directory not found at $MARKETPLACE_PATH"
|
|
||||||
echo "Plugin files may not be copied to cache correctly."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure cache directory exists and copy plugin files
|
|
||||||
mkdir -p "$CACHE_PATH/.claude-plugin"
|
|
||||||
mkdir -p "$CACHE_PATH/hooks"
|
|
||||||
mkdir -p "$CACHE_PATH/commands"
|
|
||||||
|
|
||||||
# Copy files from marketplace to cache
|
|
||||||
cp -r "$MARKETPLACE_PATH/"* "$CACHE_PATH/" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Use jq for proper JSON manipulation
|
# Use jq for proper JSON manipulation
|
||||||
PLUGIN_ENTRY=$(cat <<EOF
|
PLUGIN_ENTRY=$(cat <<EOF
|
||||||
[{
|
[{
|
||||||
@@ -117,8 +203,6 @@ EOF
|
|||||||
echo "Plugin registered in installed_plugins.json"
|
echo "Plugin registered in installed_plugins.json"
|
||||||
|
|
||||||
# Enable the plugin in settings.json and configure statusline
|
# Enable the plugin in settings.json and configure statusline
|
||||||
# First ensure enabledPlugins object exists, then add our plugin
|
|
||||||
STATUSLINE_CMD="$MARKETPLACE_PATH/hooks/statusline"
|
|
||||||
STATUSLINE_ENTRY=$(cat <<EOF
|
STATUSLINE_ENTRY=$(cat <<EOF
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
@@ -151,38 +235,13 @@ EOF
|
|||||||
'.[$key] = $entry' "$MARKETPLACES_FILE"
|
'.[$key] = $entry' "$MARKETPLACES_FILE"
|
||||||
|
|
||||||
echo "Marketplace registered in known_marketplaces.json"
|
echo "Marketplace registered in known_marketplaces.json"
|
||||||
|
|
||||||
# Register MCP server in settings.json
|
|
||||||
MCP_BINARY="$MARKETPLACE_PATH/mcp-server"
|
|
||||||
if [ -f "$MCP_BINARY" ]; then
|
|
||||||
echo "Registering MCP server in settings.json..."
|
|
||||||
|
|
||||||
# MCP server entry - note the escaped ${CLAUDE_PROJECT}
|
|
||||||
MCP_ENTRY=$(cat <<'EOF'
|
|
||||||
{
|
|
||||||
"command": "MCP_BINARY_PLACEHOLDER",
|
|
||||||
"args": ["--project", "${CLAUDE_PROJECT}"],
|
|
||||||
"env": {}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
# Replace placeholder with actual path
|
|
||||||
MCP_ENTRY=$(echo "$MCP_ENTRY" | sed "s|MCP_BINARY_PLACEHOLDER|$MCP_BINARY|g")
|
|
||||||
|
|
||||||
# Add or update mcpServers field
|
|
||||||
if safe_jq_write --arg key "claude-mnemonic" --argjson entry "$MCP_ENTRY" \
|
|
||||||
'.mcpServers //= {} | .mcpServers[$key] = $entry' "$SETTINGS_FILE"; then
|
|
||||||
echo "MCP server registered successfully"
|
|
||||||
else
|
|
||||||
echo "Warning: Failed to register MCP server (jq error)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "MCP server binary not found at $MCP_BINARY, skipping MCP registration"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Plugin registered successfully using jq"
|
echo "Plugin registered successfully using jq"
|
||||||
|
|
||||||
|
elif command -v python3 &> /dev/null; then
|
||||||
|
register_with_python
|
||||||
|
echo "Plugin registered successfully using python3"
|
||||||
else
|
else
|
||||||
echo "ERROR: jq is required for plugin registration"
|
echo "ERROR: jq or python3 is required for plugin registration"
|
||||||
echo "Please install jq: brew install jq (macOS) or apt-get install jq (Linux)"
|
echo "Please install one: brew install jq (macOS) or apt-get install jq (Linux)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-mnemonic-dashboard",
|
"name": "claude-mnemonic-dashboard",
|
||||||
"version": "v0.10.5-15-g385d05a-dirty",
|
"version": "v0.11.47-3-gfcab3eb-dirty",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "claude-mnemonic-dashboard",
|
"name": "claude-mnemonic-dashboard",
|
||||||
"version": "v0.10.5-15-g385d05a-dirty",
|
"version": "v0.11.47-3-gfcab3eb-dirty",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vis-data": "^7.1.9",
|
"vis-data": "^7.1.9",
|
||||||
"vis-network": "^9.1.9",
|
"vis-network": "^9.1.9",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-mnemonic-dashboard",
|
"name": "claude-mnemonic-dashboard",
|
||||||
"version": "v0.10.5-15-g385d05a-dirty",
|
"version": "v0.11.47-3-gfcab3eb-dirty",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user