diff --git a/cmd/hooks/stop/main.go b/cmd/hooks/stop/main.go index 87f2d8b..cbb8861 100644 --- a/cmd/hooks/stop/main.go +++ b/cmd/hooks/stop/main.go @@ -75,13 +75,16 @@ func parseTranscript(path string) (lastUser, lastAssistant string) { continue } - if msg.Type == "message" { + // Transcript entries have type: "user" or "assistant" (not "message") + // Check if this is a user/assistant message with content + if msg.Type == "user" || msg.Type == "assistant" { text := extractTextContent(msg.Message.Content) if text == "" { continue } - switch msg.Message.Role { + // Use the outer type field for role (message.role may differ) + switch msg.Type { case "user": lastUser = text case "assistant": @@ -98,6 +101,9 @@ func main() { } func handleStop(ctx *hooks.HookContext, input *Input) (string, error) { + // Debug: dump raw input + fmt.Fprintf(os.Stderr, "[stop] Raw input: %s\n", string(ctx.RawInput)) + // Find session result, err := hooks.GET(ctx.Port, fmt.Sprintf("/api/sessions?claudeSessionId=%s", ctx.SessionID)) if err != nil || result == nil { @@ -116,6 +122,17 @@ func handleStop(ctx *hooks.HookContext, input *Input) (string, error) { lastUser, lastAssistant = parseTranscript(input.TranscriptPath) } + // Debug: log what we extracted + fmt.Fprintf(os.Stderr, "[stop] Transcript path: %s\n", input.TranscriptPath) + fmt.Fprintf(os.Stderr, "[stop] Last user message length: %d\n", len(lastUser)) + fmt.Fprintf(os.Stderr, "[stop] Last assistant message length: %d\n", len(lastAssistant)) + if len(lastAssistant) > 0 { + preview := lastAssistant + if len(preview) > 300 { + preview = preview[:300] + "..." + } + fmt.Fprintf(os.Stderr, "[stop] Last assistant preview: %s\n", preview) + } fmt.Fprintf(os.Stderr, "[stop] Requesting summary for session %d (transcript: %v)\n", int64(sessionID), input.TranscriptPath != "") // Request summary with message context from transcript diff --git a/internal/db/sqlite/observation.go b/internal/db/sqlite/observation.go index c5ca9a4..d95085f 100644 --- a/internal/db/sqlite/observation.go +++ b/internal/db/sqlite/observation.go @@ -175,6 +175,29 @@ func (s *ObservationStore) GetRecentObservations(ctx context.Context, project st return scanObservationRows(rows) } +// GetObservationsByProjectStrict retrieves observations strictly for a specific project. +// Unlike GetRecentObservations, this does NOT include global observations from other projects. +// Use this for dashboard filtering where the user expects to see only that project's data. +func (s *ObservationStore) GetObservationsByProjectStrict(ctx context.Context, project string, limit int) ([]*models.Observation, error) { + const query = ` + SELECT id, sdk_session_id, project, COALESCE(scope, 'project') as scope, type, title, subtitle, facts, narrative, + concepts, files_read, files_modified, file_mtimes, prompt_number, discovery_tokens, + created_at, created_at_epoch + FROM observations + WHERE project = ? + ORDER BY created_at_epoch DESC + LIMIT ? + ` + + rows, err := s.store.QueryContext(ctx, query, project, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + return scanObservationRows(rows) +} + // GetObservationCount returns the count of observations for a project (including global). func (s *ObservationStore) GetObservationCount(ctx context.Context, project string) (int, error) { const query = ` diff --git a/internal/embedding/assets/lib/darwin-arm64/.version b/internal/embedding/assets/lib/darwin-arm64/.version new file mode 100644 index 0000000..14bee92 --- /dev/null +++ b/internal/embedding/assets/lib/darwin-arm64/.version @@ -0,0 +1 @@ +1.23.2 diff --git a/internal/embedding/assets/lib/linux-amd64/.version b/internal/embedding/assets/lib/linux-amd64/.version new file mode 100644 index 0000000..14bee92 --- /dev/null +++ b/internal/embedding/assets/lib/linux-amd64/.version @@ -0,0 +1 @@ +1.23.2 diff --git a/internal/embedding/assets/lib/linux-arm64/.version b/internal/embedding/assets/lib/linux-arm64/.version new file mode 100644 index 0000000..14bee92 --- /dev/null +++ b/internal/embedding/assets/lib/linux-arm64/.version @@ -0,0 +1 @@ +1.23.2 diff --git a/internal/embedding/assets/lib/windows-amd64/.version b/internal/embedding/assets/lib/windows-amd64/.version new file mode 100644 index 0000000..14bee92 --- /dev/null +++ b/internal/embedding/assets/lib/windows-amd64/.version @@ -0,0 +1 @@ +1.23.2 diff --git a/internal/worker/handlers.go b/internal/worker/handlers.go index 891677b..6d08df8 100644 --- a/internal/worker/handlers.go +++ b/internal/worker/handlers.go @@ -478,8 +478,8 @@ func (s *Service) handleGetObservations(w http.ResponseWriter, r *http.Request) // Fall back to SQLite if vector search not used if !usedVector { if project != "" { - // Filter by project - includes project-scoped and global observations - observations, err = s.observationStore.GetRecentObservations(r.Context(), project, limit) + // Strict project filtering for dashboard - only observations from this project + observations, err = s.observationStore.GetObservationsByProjectStrict(r.Context(), project, limit) } else { // All projects observations, err = s.observationStore.GetAllRecentObservations(r.Context(), limit) diff --git a/internal/worker/sdk/processor.go b/internal/worker/sdk/processor.go index a0894b1..8cc2342 100644 --- a/internal/worker/sdk/processor.go +++ b/internal/worker/sdk/processor.go @@ -234,11 +234,19 @@ func (p *Processor) ProcessObservation(ctx context.Context, sdkSessionID, projec // ProcessSummary processes a session summary request. func (p *Processor) ProcessSummary(ctx context.Context, sessionDBID int64, sdkSessionID, project, userPrompt, lastUserMsg, lastAssistantMsg string) error { + // Debug: log what we received + log.Debug(). + Int64("sessionId", sessionDBID). + Int("lastAssistantMsgLen", len(lastAssistantMsg)). + Str("lastAssistantMsgPreview", truncateForLog(lastAssistantMsg, 200)). + Msg("ProcessSummary called") + // Skip summary generation if there's no meaningful assistant response // This prevents generic "initial session setup" summaries if !hasMeaningfulContent(lastAssistantMsg) { log.Info(). Int64("sessionId", sessionDBID). + Int("msgLen", len(lastAssistantMsg)). Msg("Skipping summary - no meaningful assistant response") return nil } @@ -727,6 +735,14 @@ func hasMeaningfulContent(assistantMsg string) bool { return matchCount >= 2 } +// truncateForLog truncates a string for logging purposes. +func truncateForLog(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen] + "..." +} + const systemPrompt = `You are a memory extraction agent for Claude Code sessions. Your job is to analyze tool executions and extract meaningful observations that would be useful for future sessions. GUIDELINES: diff --git a/scripts/download-onnx-libs.sh b/scripts/download-onnx-libs.sh index aeca08e..4e522c2 100755 --- a/scripts/download-onnx-libs.sh +++ b/scripts/download-onnx-libs.sh @@ -1,13 +1,22 @@ #!/bin/bash # Download ONNX Runtime libraries for embedding -# Usage: ./download-onnx-libs.sh [platform] +# Usage: ./download-onnx-libs.sh [platform] [--force] # Platform: darwin-arm64, linux-amd64, windows-amd64, or "all" (default) +# Use --force to re-download even if libraries exist set -e ONNX_VERSION="1.23.2" ASSETS_DIR="internal/embedding/assets/lib" PLATFORM="${1:-all}" +FORCE_DOWNLOAD=false + +# Check for --force flag +for arg in "$@"; do + if [ "$arg" = "--force" ]; then + FORCE_DOWNLOAD=true + fi +done # Auto-detect platform if not specified if [ "$PLATFORM" = "auto" ]; then @@ -31,12 +40,38 @@ fi TEMP_DIR=$(mktemp -d) trap "rm -rf ${TEMP_DIR}" EXIT +# Get the installed version for a platform +get_installed_version() { + local plat="$1" + local version_file="${ASSETS_DIR}/${plat}/.version" + if [ -f "$version_file" ]; then + cat "$version_file" + else + echo "" + fi +} + +# Write version file after successful download +write_version_file() { + local plat="$1" + echo "${ONNX_VERSION}" > "${ASSETS_DIR}/${plat}/.version" +} + +# Check if version matches +version_matches() { + local plat="$1" + local installed_version + installed_version=$(get_installed_version "$plat") + [ "$installed_version" = "$ONNX_VERSION" ] +} + download_darwin_arm64() { echo "Downloading darwin-arm64..." mkdir -p "${ASSETS_DIR}/darwin-arm64" curl -fsSL "https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-osx-arm64-${ONNX_VERSION}.tgz" -o "${TEMP_DIR}/darwin-arm64.tgz" tar -xzf "${TEMP_DIR}/darwin-arm64.tgz" -C "${TEMP_DIR}" cp "${TEMP_DIR}/onnxruntime-osx-arm64-${ONNX_VERSION}/lib/libonnxruntime.${ONNX_VERSION}.dylib" "${ASSETS_DIR}/darwin-arm64/libonnxruntime.dylib" + write_version_file "darwin-arm64" } download_linux_amd64() { @@ -46,6 +81,7 @@ download_linux_amd64() { tar -xzf "${TEMP_DIR}/linux-amd64.tgz" -C "${TEMP_DIR}" cp "${TEMP_DIR}/onnxruntime-linux-x64-${ONNX_VERSION}/lib/libonnxruntime.so.${ONNX_VERSION}" "${ASSETS_DIR}/linux-amd64/libonnxruntime.so" cp "${TEMP_DIR}/onnxruntime-linux-x64-${ONNX_VERSION}/lib/libonnxruntime_providers_shared.so" "${ASSETS_DIR}/linux-amd64/libonnxruntime_providers_shared.so" 2>/dev/null || true + write_version_file "linux-amd64" } download_linux_arm64() { @@ -55,6 +91,7 @@ download_linux_arm64() { tar -xzf "${TEMP_DIR}/linux-arm64.tgz" -C "${TEMP_DIR}" cp "${TEMP_DIR}/onnxruntime-linux-aarch64-${ONNX_VERSION}/lib/libonnxruntime.so.${ONNX_VERSION}" "${ASSETS_DIR}/linux-arm64/libonnxruntime.so" cp "${TEMP_DIR}/onnxruntime-linux-aarch64-${ONNX_VERSION}/lib/libonnxruntime_providers_shared.so" "${ASSETS_DIR}/linux-arm64/libonnxruntime_providers_shared.so" 2>/dev/null || true + write_version_file "linux-arm64" } download_windows_amd64() { @@ -66,6 +103,7 @@ download_windows_amd64() { echo "Downloaded file size: $(wc -c < "${TEMP_DIR}/windows-amd64.zip") bytes" unzip -q "${TEMP_DIR}/windows-amd64.zip" -d "${TEMP_DIR}" cp "${TEMP_DIR}/onnxruntime-win-x64-${ONNX_VERSION}/lib/onnxruntime.dll" "${ASSETS_DIR}/windows-amd64/onnxruntime.dll" + write_version_file "windows-amd64" } # Check if library already exists for a platform @@ -79,19 +117,40 @@ lib_exists() { esac } -# Download only if not present +# Download only if not present or version mismatch download_if_needed() { local plat="$1" - if lib_exists "$plat"; then - echo "Library for ${plat} already exists, skipping download" - return 0 + local need_download=false + local reason="" + + if [ "$FORCE_DOWNLOAD" = true ]; then + need_download=true + reason="forced" + elif ! lib_exists "$plat"; then + need_download=true + reason="not found" + elif ! version_matches "$plat"; then + local installed_version + installed_version=$(get_installed_version "$plat") + need_download=true + reason="version mismatch (installed: ${installed_version:-unknown}, required: ${ONNX_VERSION})" + fi + + if [ "$need_download" = true ]; then + if [ -n "$reason" ] && [ "$reason" != "not found" ]; then + echo "Re-downloading ${plat}: ${reason}" + fi + # Remove old library before downloading + rm -rf "${ASSETS_DIR}/${plat}" + case "$plat" in + darwin-arm64) download_darwin_arm64 ;; + linux-amd64) download_linux_amd64 ;; + linux-arm64) download_linux_arm64 ;; + windows-amd64) download_windows_amd64 ;; + esac + else + echo "Library for ${plat} already exists (v${ONNX_VERSION}), skipping download" fi - case "$plat" in - darwin-arm64) download_darwin_arm64 ;; - linux-amd64) download_linux_amd64 ;; - linux-arm64) download_linux_arm64 ;; - windows-amd64) download_windows_amd64 ;; - esac } echo "ONNX Runtime v${ONNX_VERSION} - Platform: ${PLATFORM}" diff --git a/ui/package-lock.json b/ui/package-lock.json index 54f84d8..8e8a4e2 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-mnemonic-dashboard", - "version": "v0.6.5-23-g21bcbfa", + "version": "v0.6.5-25-g372942e-dirty", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-mnemonic-dashboard", - "version": "v0.6.5-23-g21bcbfa", + "version": "v0.6.5-25-g372942e-dirty", "dependencies": { "vue": "^3.5.13" }, diff --git a/ui/package.json b/ui/package.json index a944e7e..482c01f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "claude-mnemonic-dashboard", - "version": "v0.6.5-23-g21bcbfa", + "version": "v0.6.5-25-g372942e-dirty", "private": true, "type": "module", "scripts": {