general improvements (#17)

* refactor(hooks): simplify hook execution with shared context

- [x] Extract BaseInput struct to eliminate duplicate fields across hooks
- [x] Create RunHook handler pattern for session-start and user-prompt
- [x] Create RunStatuslineHook for fast statusline rendering without worker startup
- [x] Add HookContext struct to pass port, project, CWD, SessionID to handlers
- [x] Add db/interface.go with ObservationReader/Writer interfaces
- [x] Add comprehensive conflict management tests in sqlite/conflict_test.go
- [x] Add vector client tests for Count, ModelVersion, NeedsRebuild, GetStaleVectors
- [x] Add FilterByThreshold helper tests for query result filtering
- [x] Make handlers_test more robust for network-dependent update checks
- [x] Update package versions in UI

* Move to GORM + general cleanup

* feat(mcp): add observation relations discovery and scoring integration

- [x] Add find_related_observations MCP tool for discovering related observations by confidence
- [x] Integrate scoring calculator and recalculator into MCP server initialization
- [x] Add pattern, relation, and session stores to MCP server dependencies
- [x] Register MCP server in Claude Code settings during plugin installation
- [x] Update install scripts (bash, PowerShell) to configure MCP server settings
- [x] Switch plugin manifest files to template-based versioning (plugin.json.tpl, marketplace.json.tpl)
- [x] Update all MCP server tests to pass new dependency parameters
This commit is contained in:
2026-01-07 00:26:20 +00:00
committed by GitHub
parent 92a99c7615
commit 7a061c85eb
85 changed files with 8445 additions and 8202 deletions
+35
View File
@@ -143,3 +143,38 @@ func RunHook[T any](hookName string, handler HookHandler[T]) {
WriteResponse(hookName, true)
}
// StatuslineHandler is a function that handles statusline-specific logic.
// It receives input and port, returns formatted status string.
// No context injection or worker startup - just display.
type StatuslineHandler[T any] func(input *T, port int) string
// RunStatuslineHook executes a statusline hook with minimal overhead.
// Unlike RunHook, this:
// - Does NOT check CLAUDE_MNEMONIC_INTERNAL (statuslines always run)
// - Uses GetWorkerPort() instead of EnsureWorkerRunning() (no startup)
// - Prints output directly to stdout (no JSON wrapping)
// This keeps statusline fast (<100ms requirement).
func RunStatuslineHook[T any](handler StatuslineHandler[T]) {
// Read input from stdin
inputData, err := io.ReadAll(os.Stdin)
if err != nil {
// On error, handler receives nil and should return offline status
fmt.Println(handler(nil, 0))
return
}
// Parse input
var input T
if err := json.Unmarshal(inputData, &input); err != nil {
// On parse error, handler receives nil and should return offline status
fmt.Println(handler(nil, 0))
return
}
// Get worker port (does NOT start worker)
port := GetWorkerPort()
// Run handler and print result
fmt.Println(handler(&input, port))
}
+12 -12
View File
@@ -34,7 +34,7 @@ func TestIsWorkerRunning(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/health" {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
} else {
w.WriteHeader(http.StatusNotFound)
}
@@ -68,7 +68,7 @@ func TestGetWorkerVersion(t *testing.T) {
name: "returns version from server",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/version" {
json.NewEncoder(w).Encode(map[string]string{"version": "1.2.3"})
_ = json.NewEncoder(w).Encode(map[string]string{"version": "1.2.3"})
}
},
expectedResult: "1.2.3",
@@ -83,7 +83,7 @@ func TestGetWorkerVersion(t *testing.T) {
{
name: "returns empty on invalid JSON",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("not json"))
_, _ = w.Write([]byte("not json"))
},
expectedResult: "",
},
@@ -332,7 +332,7 @@ func TestPOST(t *testing.T) {
assert.Equal(t, http.MethodPost, r.Method)
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"status": "ok"})
_ = json.NewEncoder(w).Encode(map[string]interface{}{"status": "ok"})
},
body: map[string]string{"key": "value"},
expectError: false,
@@ -358,7 +358,7 @@ func TestPOST(t *testing.T) {
name: "POST with non-JSON response",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("not json"))
_, _ = w.Write([]byte("not json"))
},
body: map[string]string{"key": "value"},
expectError: false,
@@ -403,7 +403,7 @@ func TestGET(t *testing.T) {
serverHandler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"data": "test"})
_ = json.NewEncoder(w).Encode(map[string]interface{}{"data": "test"})
},
expectError: false,
expectedResult: map[string]interface{}{"data": "test"},
@@ -419,7 +419,7 @@ func TestGET(t *testing.T) {
name: "GET with invalid JSON",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("not valid json"))
_, _ = w.Write([]byte("not valid json"))
},
expectError: true,
},
@@ -666,7 +666,7 @@ func TestGetWorkerVersion_WithServer(t *testing.T) {
serverHandler: func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/version" {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"version": "v1.2.3"})
_ = json.NewEncoder(w).Encode(map[string]string{"version": "v1.2.3"})
}
},
expectedResult: "v1.2.3",
@@ -682,7 +682,7 @@ func TestGetWorkerVersion_WithServer(t *testing.T) {
name: "returns empty on invalid JSON",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("not json"))
_, _ = w.Write([]byte("not json"))
},
expectedResult: "",
},
@@ -690,7 +690,7 @@ func TestGetWorkerVersion_WithServer(t *testing.T) {
name: "returns empty on missing version field",
serverHandler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"other": "field"})
_ = json.NewEncoder(w).Encode(map[string]string{"other": "field"})
},
expectedResult: "",
},
@@ -1082,7 +1082,7 @@ func TestPOST_EmptyBody(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}))
defer server.Close()
@@ -1101,7 +1101,7 @@ func TestGET_WithQueryParams(t *testing.T) {
assert.Equal(t, http.MethodGet, r.Method)
assert.Equal(t, "/test?foo=bar", r.URL.String())
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}))
defer server.Close()