diff --git a/internal/cmd/browser.go b/internal/cmd/browser.go index da44e89..dc1450a 100644 --- a/internal/cmd/browser.go +++ b/internal/cmd/browser.go @@ -5,9 +5,9 @@ import ( "log" "strings" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/ingest" + "github.com/spf13/cobra" ) func NewBrowserCmd(kdb *db.KnoxDB) *cobra.Command { @@ -65,10 +65,10 @@ Run periodically to capture browsing exhaust.`, for _, r := range results { domains[extractDomain(r.Summary)]++ } - fmt.Println("\nTop domains:") - for _, d := range topDomains(domains, 10) { - fmt.Printf(" %-40s %d\n", d.Name, d.Count) - } + fmt.Println("\nTop domains:") + for _, d := range topDomains(domains, 10) { + fmt.Printf(" %-40s %d\n", d.Name, d.Count) + } return nil }, diff --git a/internal/cmd/gitea.go b/internal/cmd/gitea.go index 45783bb..1236814 100644 --- a/internal/cmd/gitea.go +++ b/internal/cmd/gitea.go @@ -4,9 +4,9 @@ import ( "fmt" "log" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/ingest" + "github.com/spf13/cobra" ) func NewGiteaCmd(kdb *db.KnoxDB) *cobra.Command { diff --git a/internal/cmd/gossip.go b/internal/cmd/gossip.go index dc3bf63..d0b18e1 100644 --- a/internal/cmd/gossip.go +++ b/internal/cmd/gossip.go @@ -3,9 +3,9 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/watch" + "github.com/spf13/cobra" ) // NewGossipCmd exposes peer status/diff for the gossip protocol. @@ -154,4 +154,4 @@ func min(a, b int) int { return a } return b -} \ No newline at end of file +} diff --git a/internal/cmd/ingest.go b/internal/cmd/ingest.go index 5bfee3f..cbb1039 100644 --- a/internal/cmd/ingest.go +++ b/internal/cmd/ingest.go @@ -5,10 +5,10 @@ import ( "log" "path/filepath" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/ingest" "github.com/david/knox/internal/watch" + "github.com/spf13/cobra" ) func NewIngestCmd(kdb *db.KnoxDB) *cobra.Command { @@ -27,92 +27,92 @@ func NewIngestCmd(kdb *db.KnoxDB) *cobra.Command { ingest.NewSkillsIngester(), } - var totalNew, totalUpdated int + var totalNew, totalUpdated int - record := func(result *ingest.IngestResult) { - _, isNew, err := kdb.RecordObservation(db.ObservationRecord{ - Fingerprint: result.Fingerprint, - SourceID: result.SourceID, - SourcePath: result.SourcePath, - Project: result.Project, - ContentType: result.ContentType, - Title: result.Title, - Summary: result.Summary, - CreatedAt: result.CreatedAt, - LineStart: result.LineStart, - LineEnd: result.LineEnd, - Confidence: result.Confidence, - IngesterVersion: result.IngesterVersion, - Trigger: "ingest", - Provenance: ingest.ProvenanceJSON(result.Provenance), - }) - if err != nil { - log.Printf("[knox] db error: %v", err) - return - } - if isNew { - totalNew++ - } else { - totalUpdated++ - } - - if result.SourceID == "opencode-session" { - sessionID, _ := result.Provenance["session_id"].(string) - if sessionID != "" { - kdb.UpsertSession(sessionID, result.Project, result.Title, "active") + record := func(result *ingest.IngestResult) { + _, isNew, err := kdb.RecordObservation(db.ObservationRecord{ + Fingerprint: result.Fingerprint, + SourceID: result.SourceID, + SourcePath: result.SourcePath, + Project: result.Project, + ContentType: result.ContentType, + Title: result.Title, + Summary: result.Summary, + CreatedAt: result.CreatedAt, + LineStart: result.LineStart, + LineEnd: result.LineEnd, + Confidence: result.Confidence, + IngesterVersion: result.IngesterVersion, + Trigger: "ingest", + Provenance: ingest.ProvenanceJSON(result.Provenance), + }) + if err != nil { + log.Printf("[knox] db error: %v", err) + return } - } - } - - for _, ing := range ingesters { - sid := ing.SourceID() - log.Printf("[knox] ingesting %s...", sid) - - for _, dir := range dirs { - patterns := []string{ - dir + "/*", - dir + "/*/SKILL.md", + if isNew { + totalNew++ + } else { + totalUpdated++ } - for _, pattern := range patterns { - entries, _ := filepath.Glob(pattern) - for _, path := range entries { - if !watch.MatchesIngester(path, sid) { - continue - } - result, err := ing.Ingest(path) - if err != nil || result == nil { - continue - } - record(result) + if result.SourceID == "opencode-session" { + sessionID, _ := result.Provenance["session_id"].(string) + if sessionID != "" { + kdb.UpsertSession(sessionID, result.Project, result.Title, "active") } } } - } - // Non-file sources: obsidian vault, browser history, gitea - if vault, err := ingest.DetectObsidianVault(); err == nil { - log.Printf("[knox] ingesting obsidian...") - if results, err := ingest.NewObsidianIngester(vault).IngestAll(); err == nil { + for _, ing := range ingesters { + sid := ing.SourceID() + log.Printf("[knox] ingesting %s...", sid) + + for _, dir := range dirs { + patterns := []string{ + dir + "/*", + dir + "/*/SKILL.md", + } + for _, pattern := range patterns { + entries, _ := filepath.Glob(pattern) + for _, path := range entries { + if !watch.MatchesIngester(path, sid) { + continue + } + + result, err := ing.Ingest(path) + if err != nil || result == nil { + continue + } + record(result) + } + } + } + } + + // Non-file sources: obsidian vault, browser history, gitea + if vault, err := ingest.DetectObsidianVault(); err == nil { + log.Printf("[knox] ingesting obsidian...") + if results, err := ingest.NewObsidianIngester(vault).IngestAll(); err == nil { + for _, r := range results { + record(r) + } + } + } + log.Printf("[knox] ingesting browser-history...") + if results, err := ingest.NewBrowserHistoryIngester().IngestAll(); err == nil { for _, r := range results { record(r) } } - } - log.Printf("[knox] ingesting browser-history...") - if results, err := ingest.NewBrowserHistoryIngester().IngestAll(); err == nil { - for _, r := range results { - record(r) + log.Printf("[knox] ingesting gitea...") + if results, err := ingest.NewGiteaIngester().IngestAll(); err == nil { + for _, r := range results { + record(r) + } } - } - log.Printf("[knox] ingesting gitea...") - if results, err := ingest.NewGiteaIngester().IngestAll(); err == nil { - for _, r := range results { - record(r) - } - } - fmt.Printf("\nIngest complete: %d new entries, %d updated\n", totalNew, totalUpdated) + fmt.Printf("\nIngest complete: %d new entries, %d updated\n", totalNew, totalUpdated) }, } cmd.Flags().StringSliceVarP(&dirs, "dir", "d", nil, "Directories to scan (default: opencode storage/log + skills)") diff --git a/internal/cmd/obsidian.go b/internal/cmd/obsidian.go index 26b8e88..937e526 100644 --- a/internal/cmd/obsidian.go +++ b/internal/cmd/obsidian.go @@ -4,9 +4,9 @@ import ( "fmt" "log" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/ingest" + "github.com/spf13/cobra" ) func NewObsidianCmd(kdb *db.KnoxDB) *cobra.Command { diff --git a/internal/cmd/reconcile.go b/internal/cmd/reconcile.go index 13b74d6..ede2e58 100644 --- a/internal/cmd/reconcile.go +++ b/internal/cmd/reconcile.go @@ -3,9 +3,9 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/watch" + "github.com/spf13/cobra" ) // NewReconcileCmd rebuilds all derived state (entries cache, threads) from the @@ -51,4 +51,4 @@ func dryRunReconcile(kdb *db.KnoxDB) error { fmt.Printf("drift: %d entries current, %d from log (%+d), %d threads would be created, %d obs would be linked\n", current, logCount, logCount-current, created, linked) return nil -} \ No newline at end of file +} diff --git a/internal/cmd/status.go b/internal/cmd/status.go index 2c0cbd6..7a7f4a2 100644 --- a/internal/cmd/status.go +++ b/internal/cmd/status.go @@ -6,9 +6,9 @@ import ( "os" "strings" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/index" + "github.com/spf13/cobra" ) func paginate(entries []db.Entry, page, limit int) []db.Entry { diff --git a/internal/cmd/topics.go b/internal/cmd/topics.go index 32a0c77..23719ca 100644 --- a/internal/cmd/topics.go +++ b/internal/cmd/topics.go @@ -5,9 +5,9 @@ import ( "regexp" "strings" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/index" + "github.com/spf13/cobra" ) func NewTopicsCmd(kdb *db.KnoxDB) *cobra.Command { diff --git a/internal/cmd/watch.go b/internal/cmd/watch.go index bc995c8..f9a3461 100644 --- a/internal/cmd/watch.go +++ b/internal/cmd/watch.go @@ -4,9 +4,9 @@ import ( "io" "log" - "github.com/spf13/cobra" "github.com/david/knox/internal/db" "github.com/david/knox/internal/watch" + "github.com/spf13/cobra" ) func NewWatchCmd(kdb *db.KnoxDB) *cobra.Command { diff --git a/internal/db/gossip.go b/internal/db/gossip.go index 613f03f..3d81592 100644 --- a/internal/db/gossip.go +++ b/internal/db/gossip.go @@ -286,4 +286,4 @@ func (k *KnoxDB) ThreadStatusByCluster() (map[string]string, error) { out[key] = status } return out, rows.Err() -} \ No newline at end of file +} diff --git a/internal/db/metrics.go b/internal/db/metrics.go index 765ecf8..77c9b4b 100644 --- a/internal/db/metrics.go +++ b/internal/db/metrics.go @@ -11,7 +11,7 @@ type MetricsSnapshot struct { PendingReflections int ThreadsByStatus map[string]int Peers int - ByOriginNode map[string]int // node_id → observation count + ByOriginNode map[string]int // node_id → observation count KnowledgeVector map[string]int64 // node_id → max hcl EarliestObservation string } @@ -105,4 +105,4 @@ func (k *KnoxDB) MetricsSnapshot() (*MetricsSnapshot, error) { return nil, err } return s, nil -} \ No newline at end of file +} diff --git a/internal/hlc/hlc.go b/internal/hlc/hlc.go index 2827e16..c1b7e36 100644 --- a/internal/hlc/hlc.go +++ b/internal/hlc/hlc.go @@ -57,4 +57,4 @@ func (c *Clock) Now() (int64, time.Time) { // WallTime extracts the wall-clock component embedded in a packed HLC value. func WallTime(v int64) time.Time { return time.UnixMilli(v >> wallShift).UTC() -} \ No newline at end of file +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 2652583..341e8ac 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -19,23 +19,23 @@ import ( // scrape-time gauges derived from the database (refreshed on each scrape). type Metrics struct { // Gossip counters (live). - pullsTotal prometheus.Counter - pushesTotal prometheus.Counter - obsPulledTotal prometheus.Counter - obsPushedTotal prometheus.Counter - errorsTotal prometheus.Counter + pullsTotal prometheus.Counter + pushesTotal prometheus.Counter + obsPulledTotal prometheus.Counter + obsPushedTotal prometheus.Counter + errorsTotal prometheus.Counter // Snapshot gauges (updated per scrape). - observationsGauge *prometheus.GaugeVec - entriesGauge prometheus.Gauge - projectsGauge prometheus.Gauge - sessionsGauge prometheus.Gauge - pendingReflections prometheus.Gauge - peersGauge prometheus.Gauge - threadsByStatus *prometheus.GaugeVec - byOriginNode *prometheus.GaugeVec - knowledgeVector *prometheus.GaugeVec - observationsLast24h prometheus.Gauge + observationsGauge *prometheus.GaugeVec + entriesGauge prometheus.Gauge + projectsGauge prometheus.Gauge + sessionsGauge prometheus.Gauge + pendingReflections prometheus.Gauge + peersGauge prometheus.Gauge + threadsByStatus *prometheus.GaugeVec + byOriginNode *prometheus.GaugeVec + knowledgeVector *prometheus.GaugeVec + observationsLast24h prometheus.Gauge registry *prometheus.Registry kdb *db.KnoxDB @@ -161,4 +161,4 @@ func (m *Metrics) Handler() http.Handler { } h.ServeHTTP(w, r) }) -} \ No newline at end of file +} diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 283ad5c..10c1955 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -111,4 +111,4 @@ func TestMetricsCounters(t *testing.T) { if got := testutil.ToFloat64(m.errorsTotal); got != 1 { t.Errorf("errorsTotal = %v, want 1", got) } -} \ No newline at end of file +} diff --git a/internal/watch/gossip.go b/internal/watch/gossip.go index 313b845..207e00d 100644 --- a/internal/watch/gossip.go +++ b/internal/watch/gossip.go @@ -33,11 +33,11 @@ type Node struct { // pingResponse is the anti-entropy summary returned by /v1/ping. type pingResponse struct { - NodeID string `json:"node_id"` - Name string `json:"name"` - Vector map[string]int64 `json:"vector"` // node_id → max hcl - Peers []db.PeerInfo `json:"peers"` // swarm membership this node knows - MaxHCL *int64 `json:"max_hcl,omitempty"` + NodeID string `json:"node_id"` + Name string `json:"name"` + Vector map[string]int64 `json:"vector"` // node_id → max hcl + Peers []db.PeerInfo `json:"peers"` // swarm membership this node knows + MaxHCL *int64 `json:"max_hcl,omitempty"` } func (n *Node) Handler() http.Handler { @@ -123,11 +123,11 @@ func (n *Node) handleBatch(w http.ResponseWriter, r *http.Request) { // DiffSummary is the divergence snapshot served at /v1/diff: everything this // node observes, plus the status of auto-threaded threads (for tombstones). type DiffSummary struct { - NodeID string `json:"node_id"` - Name string `json:"name"` - Fingerprints []string `json:"fingerprints"` - ThreadStatus map[string]string `json:"thread_status"` - ObsCount int `json:"obs_count"` + NodeID string `json:"node_id"` + Name string `json:"name"` + Fingerprints []string `json:"fingerprints"` + ThreadStatus map[string]string `json:"thread_status"` + ObsCount int `json:"obs_count"` } func (n *Node) handleDiff(w http.ResponseWriter, r *http.Request) { @@ -163,9 +163,9 @@ func writeJSON(w http.ResponseWriter, v any) { // Client is the pull/push half used by the exchange loop. type Client struct { - NodeID string - Addr string - Timeout time.Duration + NodeID string + Addr string + Timeout time.Duration } func (c *Client) Ping() (*pingResponse, error) { @@ -410,4 +410,4 @@ func PeerAddrs() []string { } } return out -} \ No newline at end of file +} diff --git a/internal/watch/gossip_test.go b/internal/watch/gossip_test.go index 5d3ba34..76b6255 100644 --- a/internal/watch/gossip_test.go +++ b/internal/watch/gossip_test.go @@ -240,4 +240,4 @@ func TestGossipIdempotent(t *testing.T) { if before != after { t.Errorf("second sweep changed entry count: %d -> %d", before, after) } -} \ No newline at end of file +} diff --git a/internal/watch/reconcile.go b/internal/watch/reconcile.go index e64eac2..cb3ef28 100644 --- a/internal/watch/reconcile.go +++ b/internal/watch/reconcile.go @@ -20,4 +20,4 @@ func Reconcile(kdb *db.KnoxDB) (int, int, error) { return 0, 0, fmt.Errorf("auto-thread: %w", err) } return created, linked, nil -} \ No newline at end of file +} diff --git a/main.go b/main.go index 3ad9313..6cfa0e8 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,11 @@ import ( "log" "os" - mcpServer "github.com/mark3labs/mcp-go/server" - "github.com/spf13/cobra" knoxcmd "github.com/david/knox/internal/cmd" "github.com/david/knox/internal/db" knoxserver "github.com/david/knox/internal/server" + mcpServer "github.com/mark3labs/mcp-go/server" + "github.com/spf13/cobra" ) func newServeCmd(kdb *db.KnoxDB) *cobra.Command {