# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [v0.15.1] - 2026-06-10

### Changed

- **GCF v2.0 Stable**: bumped gcf-go to v1.0.0. Mandatory `profile=graph` header, `edges=N` in header, `## edges [N]` section headers with count. Streaming support available (not yet used by knowing).
- **Cal.com corpus**: 17th benchmark repo (TypeScript/Next.js scheduling platform, 11 tasks, enriched with tsserver). Scheduling equiv classes: booking, availability, calendar, webhook, attendee, limits, seats. Calcom P@10 = 0.409.
- **E-commerce equiv classes**: 5 domain classes for saleor (checkout, shipping, account, auth backend, async tasks). Saleor P@10: 0.264 -> 0.527 (+99.6%).
- **Corpus DB packaging**: `corpus-setup.sh package/restore` for per-repo tarballs as GitHub release assets.
- **Benchmark whitepaper**: prepared for publication (dev notes removed, session refs cleaned, all numbers current).

### Fixed

- **GCF test assertions**: updated to expect `GCF profile=graph` header (was `GCF tool=`).

## [v0.15.0] - 2026-06-04

### Added

- **GCF as default output format** (session 27): all MCP context tools (`context_for_task`, `context_for_files`, `context_for_pr`, `explain_symbol`, etc.) now emit GCF (Graph Compact Format) by default. 84% fewer tokens than JSON, 100% LLM comprehension accuracy at 500 symbols. Wire format selection via `KNOWING_FORMAT` env var or `--format` flag.
- **GCF extracted to standalone library**: `github.com/blackwell-systems/gcf-go` (zero dependencies). All knowing consumers import gcf-go directly for types (`gcf.Symbol`, `gcf.Edge`, `gcf.Payload`, `gcf.Session`, `gcf.DeltaPayload`). The internal `wire` package retains knowing-specific functions (`FromContextBlock`, `EncodeWith`, registry, binary/json encoders).
- **Delta context packing** (session 27): structural diff on `pack_root` mismatch. When consecutive queries return overlapping context, only the delta is transmitted. 81.2% token savings on re-queries. Session statefulness: previously-transmitted symbols sent as bare references, 92.7% savings by 5th call.
- **LLM format comprehension eval** (session 27): evaluation of GCF vs JSON at 500 symbols, 200 edges. GCF 100% accuracy, JSON 66.7%. Eval in `gcf-go/eval/` as separate Go module. Results stored in `eval/results/`.
- **Code pattern keyword extraction** (session 27): extracts structural patterns from task descriptions (e.g., "error handling", "retry logic") as additional seed terms. Improves seed quality for tasks describing patterns rather than naming symbols.
- **Multi-phrase equiv class gate**: `isStrongEquivMatch` requires either >= 2 phrases matched or a multi-word phrase before framework injection fires. Prevents single generic words (e.g., "command") from triggering VS Code framework injection that floods top-10 with infrastructure symbols. New `equivalenceMatch.phrases` and `phraseCount` fields track all matched phrases per class.
- **Ruby/Java/C# test file detection** (session 28): `isTestFilePath` now covers Ruby (`/test/` excluding `/lib/`), Java (`src/test/java/`), and C# (`*.UnitTests/`, `*.AcceptanceTests/`, `*.IntegrationTests/`). Previously only Go, Python, TypeScript, and Rust test files were penalized. Rails: 0.325 -> 0.360 (+10.8%).
- **E-commerce equiv classes** (session 28): 5 generalizable e-commerce pattern classes (`equiv_saleor.go`): checkout flow, shipping zones, account management, auth backends, async tasks. Saleor P@10: 0.264 -> 0.527 (+99.6%). 4/5 zeros cracked, `saleor-hard-002` hit perfect 1.00. Aggregate P@10: 0.320 -> 0.335 (+4.7%). Crosses 0.333 for the first time.
- **Test penalty tuned to 0.15** (session 28): swept 12 values (0.01-0.50) on Rails. Rails variance (+-0.030) dominates signal; 0.15 is a reasonable default. `BENCH_TEST_PENALTY` env var for future sweeps.
- **Cal.com benchmark corpus** (session 28): 17th repo, TypeScript/Next.js scheduling platform. 11 tasks across easy/medium/hard tiers. Enriched with tsserver (80K nodes, 246K edges, 137K LSP-resolved). First typical TypeScript app repo. Calcom P@10 = 0.409.
- **Scheduling equiv classes** (session 28): 9 generalizable scheduling pattern classes (`equiv_scheduling.go`): booking creation, cancellation, availability, calendar integration, recurring events, webhooks, attendees, booking limits, seat-based booking. Calcom: 0.064 -> 0.409 (+497%).
- **Corpus DB package/restore** (session 28): `corpus-setup.sh package` creates per-repo compressed tarballs with SHA256 manifest for GitHub release assets. `corpus-setup.sh restore` extracts them. Each DB under 2GB limit (largest: vscode 849MB). Total compressed: ~2.8GB.
- **Supply chain held-out validation** (session 27): 100 additional packages as independent validation corpus. 1.0% FP rate confirmed independently of the primary 200-package corpus.
- **Manual npm publish workflow**: `workflow_dispatch` trigger for npm publishing.

### Fixed

- **VSCODE_COMMAND equiv class regression**: bare word "command" was triggering forced injection, overriding correct BM25 results. Fixed by multi-phrase gate (see above).
- **validate-fixtures auto-discovery**: was hardcoded to 7 repos, now auto-discovers all repos in corpus directory.
- **npm publish CI**: removed `|| true` that was silently swallowing publish failures.
- **MCP handler tests**: updated to expect GCF default output format.

### Removed

- **TOON format support**: removed `internal/wire/toon.go`, `toon-go` dependency, and TOON cases from eval tests. GCF is the only compact format. One fewer third-party dependency.
- **17 invalid benchmark fixtures**: 8 fixtures with unresolvable ground truth + 9 ripgrep fixtures with ground truth from dependency crates (not the repo itself). Task count: 308 -> 291.

### Changed

- **P@10 = 0.330** (302 tasks, 17 repos, cold start, honest measurement, 3 runs: 0.328/0.331/0.330). Up from 0.293 (300 tasks, session 27). Multi-phrase equiv gate (+9.6%), e-commerce equiv classes (saleor +99.6%), scheduling equiv classes (calcom +497%), test file detection for Ruby/Java/C# (Rails +10.8%), calcom corpus addition, fixture cleanup, code pattern extraction.
- **Competitive ratios updated**: 3.79x codegraph, 6.00x GitNexus, 6.35x Gortex, 14.3x Aider, 22.0x grep.
- **14 architecture docs audited and updated** (session 27): wire-formats.md, wire-formats-guide.md, context-packing.md, system-overview.md, introduction.md, retrieval-pipeline.md, design-principles.md, context-engine.md, embedding-reranker.md, adaptive-retrieval.md, data-flow.md, and guide docs. All verified against current codebase.

### Tested negative (session 28)

- **Sibling dedup by leaf name**: global (-0.009) and package-scoped (-0.006). Common method names (Close, String, Error) too frequent within packages. Reverted.
- **Test penalty sweep**: 12 values on Rails (0.01-0.50). Variance +-0.030 on 20 tasks dominates signal. No consistent peak.

## [v0.14.0] - 2026-06-03

### Added

- **FTS fallback decomposition** (session 25): when compound keywords (dotted names, CamelCase) return 0 FTS results, decompose into leaf-segment symbol_name-targeted OR terms. Django P@10: 0.194 -> 0.203 (+4.6%). `django-medium-103` cracked from 0.00 to 0.40. Full corpus neutral (0.278).
- **Per-cluster implicit feedback** (session 25): `keyword_cluster` column on feedback table scopes noise demotion to keyword clusters, preventing cross-task interference. Django 5-round compounding: R@10 +5.2%, MRR +12.6%, round 5 regression eliminated. Migration 020.
- **Vocabulary expansion from usage** (session 25): learned keyword -> symbol associations from agent usage. When an agent uses a symbol after a `context_for_task` query, the association is recorded. After 2+ observations, becomes a learned equivalence class bridging vocabulary gaps. Both engine and MCP server paths wired. Migration 021.
- **Change-aware scoring** (session 25): `commitRecencyScore` uses `Node.LastCommitAt` from git blame: +0.05 (day), +0.03 (week), +0.01 (month). Mechanism #12 in adaptive retrieval. Neutral on benchmark (no blame data); activates in production.
- **Configuration reference** (session 25): `docs/guide/configuration.md` with all env vars, CLI flags, MCP server options, and vocabulary expansion documentation.
- **debug-seeds aligned with production**: FTS fallback decomposition visible in Step 3 output.
- **`debug-feedback` CLI** (session 25): show feedback records for symbols with positive/negative counts, per-cluster breakdown, and score.
- **`debug-equiv` CLI** (session 25): show which equivalence classes match a task description from all three sources (hand-curated, graph-derived, learned vocab).
- **`debug-vocab` CLI** (session 25): show learned keyword -> symbol associations with count and keyword filter.
- **Adaptive proximity exponent** (session 25): `adaptiveProximityExponent` adjusts packing exponent based on phantom-to-real node ratio in candidates. Normal repos: 0.3. Extreme phantom ratios (>2x): up to 0.7. Zero cost (computed from packing input).
- **LSP edge weight attenuation** (session 25): `lsp_resolved` provenance edges attenuated to 0.3x weight in RWR walk. Prevents enrichment from inflating centrality of framework wiring symbols above implementation symbols. 4-point sweep on enriched saleor: 0.3=0.218 (+19.8%), 1.0=0.182 (baseline). Full corpus: 0.283, 0.279 (neutral). Default 0.3. Override with `BENCH_LSP_EDGE_WEIGHT`.
- **Cross-task vocab validation** (session 26): proves vocabulary bridging across tasks. Task A's learned associations help task B via shared keywords. Django +41.4% in isolation, full corpus 0.0% aggregate (safe). 100% of improvements are cross-task. `TestCrossTaskVocab` benchmark with per-task attribution.
- **Vocab noise keyword filter** (session 26): `isVocabWorthy` filters ~80 common English words (use, not, find, whether, etc.) from vocab recording. Prevents spurious cross-task associations.
- **Confidence-weighted vocab injection** (session 26): observation count scales RRF weight from 0.3 (count=2) to 0.8 (count>=10). Reinforced associations get stronger each round. `VocabProviderWithCounts` interface.
- **Context packing benchmark** (session 26): `bench/context-packing/` compares 4 strategies (density-ranked, top-K, file-grouped, random) on GT coverage, token utilization, file coherence. 308 tasks, 16 repos. Extractable as standalone benchmark.
- **`debug-vocab -task` flag** (session 26): preview which keywords pass/fail the vocab filter for a task description.
- **`BENCH_PACK_STRATEGY` env var** (session 26): A/B test packing strategies (density/file-grouped/top-k) on the cross-system benchmark.
- **Mechanism #13: cross-task vocabulary bridging** (session 26): 13th self-adapting mechanism. Noise filter + soft RRF injection + confidence weighting. Added to `docs/architecture/adaptive-retrieval.md`.
- **Compounding test wired with vocab** (session 26): `TestCompounding` now records vocab associations alongside task memory and implicit feedback. 10-round full corpus (308 tasks): P@10 0.277 -> 0.283 peak (+2.2%), MRR 0.459 -> 0.497 peak (+8.1%). Never regresses below baseline.
- **CRET extraction audit** (session 26): 18 files extractable as-is, 5 trivial decouples, ~2 hours estimated. Documented in `docs/proposals/code-retrieval-eval-toolkit.md`.
- **Incremental RWR with Merkle-cached walks** (session 26): cache RWR results in notes table keyed by hash(sorted seeds + weights + alpha + snapshot hash). On cache hit, skip BFS adjacency load and iteration entirely. Django cold 3.9s -> warm 1.9s (2x). Structural invalidation via snapshot hash. `RWRCacheEnabled` flag, `BENCH_RWR_CACHE` env var, `debug-rwr-cache` CLI. P@10 correctness verified (delta within run variance).
- **Merkle-based vocab association expiration** (#3c, session 26): per-package SubgraphRoots anchored to vocab associations at recording time. When a package changes, only that package's associations expire (not the entire graph). `persistPackageRoots` stores htree.PackageRoots to notes table during indexing. `LoadPackageRoots` + `PackageRootForSymbol` at query time. Migration 022 (subgraph_root column). Both engine and MCP paths wired.

### Fixed

- **RWR cache invalidation on feedback** (session 26): clear rwr_cache entries alongside context_pack when feedback is recorded, preventing stale cached walks from producing different rankings.
- **CI flaky tests** (session 26): skip 4 merkle-diff bench tests in short mode (context pack determinism, persistence, dedup, scoped FTS). These index the live repo and produce non-deterministic results on CI runners.
- **CI eval timeout** (session 26): removed eval regression gate from CI (indexes live repo, times out on runners). Eval is a local-only regression gate.

- **Release workflow unblocked**: exclude eval/ from release test glob. Was timing out and blocking GHCR image push on every release since v0.7.0.
- **CI short-mode failures**: added `t.Skip` for `TestCompounding` and `TestRewriteGroundTruth*` in `-short` mode.

### Changed

- **Soft vocab injection** (session 26): learned vocab now goes through RRF competition instead of forced injection. Prevents displacement of correct results on tasks with good BM25 coverage. Forced injection retained only for hand-curated framework classes.
- **Feedback weight mode**: `BENCH_FEEDBACK_WEIGHT` env var for sweep testing (none/sqrt/linear/asym). Default `none` (raw scoring). 4-mode sweep confirmed cluster-only (no weighting) optimal.
- **13 self-adapting mechanisms**: was 10. Added RWR proximity packing (#10, session 24), implicit feedback (#11, session 24), change-aware scoring (#12, session 25), cross-task vocab bridging (#13, session 26).
- **Renamed `CONTEXT-PACKING-STUDY.md` to `EVALUATION-OVERVIEW.md`**: clearer name for the umbrella evaluation document.
- **Deleted `AGENT-EFFICIENCY-STUDY.md`**: superseded by cross-system benchmark (308 tasks, 16 repos) and EVALUATION-OVERVIEW.

### Tested negative (session 26)

- **File-grouped packing**: packing benchmark showed +15% GT coverage via substring matching, but P@10 dropped -10.8% on Django. Budget wasted on low-value siblings from same file. Density-ranked remains optimal.

## [v0.13.0] - 2026-06-01

### Added

- **Framework equivalence classes with forced injection** (session 23): 263 concept-to-symbol mappings across 30 per-framework files. High-confidence matches (weight >= 0.9, source "framework") bypass RWR scoring and inject directly into ranked results. Covers Django, Flask, FastAPI, Terraform, Kubernetes, Kafka, Rails, Spring, ASP.NET, Ocelot, Caddy, Cargo, Spark-Java, VS Code, NestJS, Next.js, Angular, React, Jekyll + cross-cutting (testing, ORM, auth, CLI, config, errors, web, containers, crypto). P@10: 0.176 -> 0.278 (+57%).
- **Language scoping for equiv classes**: `Lang` field on EquivalenceClass restricts framework classes to matching repos. `detectRepoLanguage()` samples node QNs. Prevents Go router classes from firing on C# repos.
- **Adaptive retrieval for massive repos**: when RWR produces flat results on repos >200K nodes, falls back to direct FTS + contains-edge expansion. VS Code: +43%.
- **Debug tools** (3 new CLI commands): `knowing debug-fts` (raw FTS5 query probe), `knowing debug-walk` (RWR walk visualization), `knowing bench-task` (single-task benchmark with hit/miss analysis).
- **Zero-task audit methodology**: systematic diagnosis of every zero-scoring task using bench-task. Categorize as vocab gap, missing edge, or genuinely hard. Add defensible equiv classes. Verify per-repo. Run full corpus.
- **Dotted Python base class resolution**: `resolveBaseClassQName` now handles dotted module paths (`validators.RegexValidator`). Fix committed, pending testing.
- **Java language detection fallback**: `detectRepoLanguage` recognizes dotted package name patterns (`org.*/com.*/io.*/net.*`) for repos like Kafka that don't use `.java.` in QNs.
- **Containers and cryptography equiv classes**: cross-cutting patterns for Docker, container registries, encryption, hashing, signatures, TLS.
- **All 7 in-process resolvers wired** (session 24): Python, TypeScript, Java, C#, Rust added alongside Go and Ruby. Generic `runLanguageResolver` dispatch via `resolverSpec` table. Produces `resolver_resolved` edges (confidence 0.9) without external LSP. Validated: Kafka/Java 596K edges, Django/Python 58K, Cargo/Rust 27K, VS Code/TS 19K, Ocelot/C# 1.3K.
- **Saleor benchmark corpus** (session 24): first framework-USING repo (vs framework source code). saleor/saleor Django e-commerce app. 11 tasks. P@10=0.236 unenriched, proving equiv classes generalize to app code.
- **Proximity-weighted BFS scoring** (session 24): actual graph distance from seeds replaces binary 0/1. BFS distances computed from RWR adjacency maps (zero extra queries). P@10 neutral on current corpus; infrastructure for handling enrichment-induced density.
- **RWR proximity packing** (session 24): `density * rwrScore^0.3` in `packIntoBudget`. Seeds with higher RWR scores get boosted packing density, preventing distant high-centrality noise from filling budget slots. Exponent 0.3 optimal from 9-point sweep (11/15 repos improved). Enriched saleor regression halved from -23% to -11%.
- **Implicit feedback engine** (session 24): moved from MCP-only to context engine. `FlushUnused` records negative feedback for returned-but-unused symbols. `DetectUsed` records positive for agent-referenced symbols. Django: +5.9% P@10 peak at round 3. Task memory disabled (confirmed neutral).
- **v0.13.0 release** (session 24): tag pushed, GitHub release created. 308 tasks, 16 repos, 8 languages.
- **context-retrieval-benchmark repo** (session 24): `blackwell-systems/context-retrieval-benchmark` created. README, MIT license, 20 topics.

### Fixed

- **Phantom Python extends edges eliminated** (session 24): skip 50+ Python builtins (Exception, object, dict, etc.), return empty for unresolvable module paths, skip dotted paths through unknown modules. Django: 5,581 phantom extends edges removed, 2,493 real targets preserved.

- **CRITICAL: Task memory contamination** (session 23): discovered 26,096 stale task memory entries in terraform corpus DB, inflating all P@10 measurements since session 8. Task memory disabled in benchmark adapter. Protocol: clear `task_memory` table before A/B comparisons. Within-session deltas remain valid; absolute cross-session numbers were unreliable.
- **Embeddings confirmed neutral**: three runs with and without embeddings produced identical P@10 (0.176, 0.175, 0.176). Previous "+11% gap-fill" was task memory contamination feedback loop. Gap-fill and re-ranker both disabled.
- **equivSeen injection bypass**: framework injection now checks before equivSeen dedup, so earlier lower-weight classes can't block framework targets from being injected.
- **Persistent cache in bench-task**: `DisablePersistentCache()` added to bench-task tool for fresh results.
- **CI mcp-assert threshold**: raised lint threshold for false-positive E112 (token_budget as sensitive data) and E107 (circular dependency on context tools).

### Changed

- **Embeddings off by default**: reversed v0.12.0 decision. Embeddings confirmed neutral on cold-start benchmarks (session 23). No 30MB model download for new users. Use `--embeddings` to opt in. The `--no-embeddings` flag is now a no-op (accepted for backward compatibility).
- **Equivalence classes refactored**: split from single 1500-line `language_seeds.go` into 30 per-framework files with 30-line aggregator. Each file is self-contained and independently reviewable.
- **Measurement protocol**: CLAUDE.md updated with mandatory task memory clearing step in experiment workflow. All benchmark runs now start from clean state.
- **P@10 official number**: 0.278 +/- 0.003 (4 runs confirmed). Honest cold-start, no task memory, no embeddings.
- **Competitive ratios recalculated**: 3.20x codegraph, 5.05x GitNexus, 5.35x Gortex, 12.1x Aider, 18.5x grep.
- **Published paper updated to v1.1**: corrected retrieval measurements in Section 7 (15 repos, 297 tasks, 5 competitors).

### Removed

- **Ripgrep equiv classes**: removed as curve-fit risk. Application internals (`DecompressionMatcher`, `pattern_from_bytes`) don't pass the defensibility test ("would this appear in official docs?").

### Documentation

- 30+ files updated across docs/, bench/, research/, npm/, pypi/, README.md
- Every stale P@10 number, competitive ratio, equiv class count, and embedding claim corrected
- Session 21-23 measurement narrative added to `session-21-measurement-calibration.md`
- Research agenda: Paper 6 added (framework knowledge injection)
- Diagnostic tools documented in cli.md and diagnostic-tools.md

## [v0.12.1] - 2026-05-31

### Added

- **In-process language resolvers** (session 22): 7 Go-native resolvers in `internal/typresolve/` (~36,000 LOC). Go, Python, TypeScript, Ruby, Java, C#, Rust. Shared infrastructure: type representation (16 type kinds), registry with fallback chaining, scope chain, resolver interface, router. Go + Ruby wired into index pipeline. Produces `resolver_resolved` edges (0.6-0.9 confidence) without external LSP servers.
- **`knowing enrich resolver` CLI command**: runs in-process resolvers retroactively on existing DBs. Adds resolver edges without re-extracting.
- **`knowing debug-seeds` CLI command**: shows seed selection pipeline (keywords, BM25, path boost, ForTask top 10).
- **Three-layer enrichment model**: tree-sitter (ast_inferred, 0.5) -> resolver (resolver_resolved, 0.6-0.9) -> external LSP (lsp_resolved, 0.9). Each layer fills gaps left by the previous.
- **Ground truth rewrite tool**: upgrades 175 bare symbol names to qualified names in benchmark fixtures.
- **Multi-package gopls warmup**: opens 172 files across packages before blast to ensure full loading.
- **LSP install suggestions**: `knowing index` suggests installing language servers when none detected.
- **Test file enrichment**: removed test file skip in enricher (was causing 49K edge loss on terraform).

## [v0.12.0] - 2026-05-28

### Added

- **Embeddings on by default**: embedding gap-fill seeds enabled without `--embeddings` flag. `--no-embeddings` to disable. Re-ranker disabled (net negative on P@10, session 19). **Note:** v0.13.0 reverses this default; embeddings confirmed neutral on cold start (session 23).
- **MCP startup summary**: server logs graph stats, feature status (gap-fill, equivalence classes), and pre-embedded vector count on startup.
- **Post-index guidance**: `knowing index` prints a tip to run `knowing enrich embeddings` when vectors are missing.
- **C# equivalence classes** (15 concepts): CS_MIDDLEWARE, CS_DI, CS_CONFIG, CS_ROUTING, CS_AUTH, CS_LOADBALANCE, CS_CACHE, CS_RATELIMIT, CS_HTTP_CLIENT, CS_QUALITY_OF_SERVICE, CS_HEADER_TRANSFORM, CS_AGGREGATION, CS_WEBSOCKET, CS_SECURITY, CS_ERROR_HANDLING. Ocelot P@10: 0.175 -> 0.265 (+51%). Full corpus: +4%.
- **FastAPI equivalence classes** (10 concepts): dependency injection, routers, background tasks, file uploads, validation, exception handlers, lifespan, security, WebSocket.
- **Terraform equivalence classes** (11 concepts): providers, state backends, plan/apply, graph/DAG, resources, modules, config/HCL, variables, provisioners, formatting, CLI commands.
- **Corpus DB tarballs in releases**: `make corpus-backup` creates split tarballs (under 2GB each). `make corpus-upload` / `make corpus-download` for GitHub release assets.
- **Embedding gap-fill seeds**: when BM25 returns < 5 candidates, vector search finds supplemental seeds. Django +43% (0.176 -> 0.252), flask +22%. Zero regressions. 20 lines of code.
- **`knowing enrich embeddings` command**: batch pre-embeds all real nodes, skips phantoms (70% reduction). Incremental: skips already-cached vectors.
- **Brute-force vector search from SQLite**: `LoadAndSearchFromStore` does O(n) cosine from cached vectors. No HNSW index rebuild needed. Lazy loading: vectors loaded on first gap-fill query, not at startup (3% memory vs 91%).
- **Parallel benchmark harness**: `BENCH_PARALLEL=1` runs repos in parallel goroutines. 5 min vs 20 min (4x speedup). P@10 = 0.220 +-0.002 (consistent, 0.022 below sequential due to ONNX CPU contention).
- **GraphNodeCount per-engine field**: moved from global to `ContextEngine.nodeCount`. Thread-safe for parallel execution. `SetNodeCount`/`effectiveNodeCount` with fallback to global.
- **Spark-java fixtures expanded**: 5 -> 20 tasks (15 new). Covers filters, sessions, templates, SSL, WebSocket, Jetty lifecycle.
- **Adaptive retrieval architecture doc**: `docs/architecture/adaptive-retrieval.md` threading all 6 self-adapting mechanisms with ablation table.
- **nomic-embed-text-v1.5 as default model**: P@10 0.247 sequential (was 0.242 with jina-code). Faster inference (14 min vs 20 min). All 12 repos pre-embedded with both models (coexist via model column).
- **`BENCH_GAP_THRESHOLD` env var**: configurable gap-fill activation threshold.
- **Round 2 per-task logging**: warm pass now prints per-task P@10 lines (was silent).

### Fixed

- **`knowing init` Go-only bug**: was registering only the Go extractor. Non-Go repos got 0 nodes. Now uses `registerAllExtractors` (23 extractors).
- **Stale `--embed-model` help text**: said "jina-code (default)" but actual default was nomic-code.
- **Fixture quality**: removed duplicate ground truth in fastapi (File, Depends normalization collision). Fixed wrong symbol in ocelot (IClientWebSocket -> IClientWebSocketConnector). Added missing pipeline middleware to ocelot hard-001.

### Tested Neutral

- Gap-fill threshold < 3, < 8, < 10: all within variance of baseline < 5.
- Hub dampening (BENCH_HUB_DAMPEN=50) on enriched graphs: 0.219 vs 0.220. Still neutral.
- codesage-large, voyage-code-3, nomic-embed-code: all non-viable for pure Go ONNX inference.
- FastAPI + Terraform equivalence classes: no measurable delta beyond C# on full corpus (C# was the main driver).

## [v0.11.0] - 2026-05-27

### Added

- **`knowing enrich lsp` command**: standalone LSP enrichment that runs on an already-indexed database without reindexing. Opens existing DB, detects language servers, upgrades edge confidence, discovers cross-module edges, creates phantom external nodes. Supports `-concurrency`, `-db`, `-url` flags.
- **Dangling type_hint_of edge resolution**: post-processing step that fixes type_hint_of edges computed with wrong node kind (type vs interface). Resolves by matching (repo, package, name) across all type-like kinds. 3,836 edges fixed across k8s (1,087), vscode (2,068), terraform (521), kafka (160).
- **Interface type hint propagation**: after resolution, propagates type_hint_of through interfaces to concrete implementors. Creates direct paths from functions to the concrete types they work with. 808 new edges across k8s (237), terraform (473), kafka (98).
- **`EdgeCount` method on SQLiteStore**: lightweight edge counting via `SELECT COUNT(*)` without loading all edges into memory.
- **Per-phase indexing timings**: `IndexTimings` struct emitted to stderr after every `IndexRepo` call. Measures file discovery, extraction, each post-processing step, authorship, snapshot, and FTS rebuild independently.
- **`TestCrossSystemRound2` fix**: Round2 benchmark now respects `BENCH_REPOS` filter (was loading all 167 tasks regardless, causing timeouts).
- **Introduction docs rewrite**: retrieval pipeline section with concrete definitions of all 7 stages, worked example, architecture doc cross-references.
- **Pre-computed embedding vector cache**: re-rank latency reduced from 660ms to 220ms (3x speedup). Vectors stored in SQLite alongside the graph (migration 019). On re-rank, only the query is embedded (1 inference call, ~120ms); candidate vectors are read from cache. Cache misses fall back to on-the-fly embedding and auto-persist for next time. Zero behavior change for users without embeddings enabled.
- `ReRankByHashes` method on `VectorReRanker` interface: hash-based vector lookup with text fallback
- `EmbeddingStore` interface (`embedding.EmbeddingStore`): `BatchPutEmbeddings`, `GetEmbeddings`
- `embeddings` table in SQLite schema (node_hash, model, vector)
- **Similarity OOM fix**: skip packages with >500 functions in similarity computation. Kafka's `org.apache.kafka.streams` (16,781 functions) caused 140M pairwise comparisons, consuming 10GB+ RAM and crashing the indexer before snapshot creation. Similarity edges are weighted 0.15 (lowest) and P@10-neutral; skipping oversized packages loses nothing measurable.
- **Adaptive seed count**: auto-increases RWR seeds on large graphs (>40K nodes: 25 seeds, >10K: 20 seeds, default 15). Django P@10 +14.2%. Full corpus P@10 0.242.
- **Package-level supply chain verdict**: "clean"/"review"/"suspicious" based on suspicious file ratio (>10%) AND count (>=2). Reduces FP rate from 21.5% (file-level) to 1.0% (package-level) on 200 clean packages.
- **Benign process target classification**: 22 known-safe executables (node, python, git, cargo, etc.) excluded from supply chain danger scoring.
- **Test/benchmark file exclusion**: files in /test/, /benchmarks/, _test.go, .spec.ts skipped in supply chain scanning.
- **Env-only attenuation**: `reads_env` without `executes_process` gets 0.2x weight in isolation scoring.
- **Coherence-aware context packing** (experimental, default off): `CoherenceBonus` parameter boosts density for co-located symbols. Tested neutral on Flask (-1.8%), available via `BENCH_COHERENCE_BONUS`.
- **200-package FP evaluation**: `scripts/false-positive-eval.sh` scans 100 npm + 100 PyPI packages. Results at `bench/supply-chain/false-positive-results-v2.jsonl`.
- **GHA action**: `blackwell-systems/knowing-supply-scan` (v1.0.0), free action for supply chain scanning on PRs.
- **Platform API scaffold**: `blackwell-systems/platform` (private), SaaS backend for paid scanning.
- **Two-phase gopls warmup**: fixed OpenDocument argument order bug + didOpen before GetDefinition. Enables Go enrichment for the first time. 128 concurrent workers post-warmup.
- **Kubernetes enriched**: 39,678 edges upgraded, 192,271 new edges discovered, 169,517 phantom nodes. P@10: 0.000 -> 0.232.
- **Terraform enriched**: 5,850 edges upgraded, 82,721 new edges discovered, 73,079 phantom nodes. P@10: ~0.095 -> 0.275.
- **Caddy Go benchmark corpus**: cloned, indexed, enriched (13,257 new edges, 12,003 phantoms). 20 fixtures. P@10 = 0.285.
- **FastAPI Python benchmark corpus**: cloned, indexed, enriched with pyright (4,433 new edges, 10,647 phantoms). 20 fixtures.
- **Ocelot C# benchmark corpus**: 20 fixtures (first C# benchmark). P@10 = 0.175. Enriched with csharp-ls.
- **csharp-ls support**: enrichment config detects csharp-ls as fallback when OmniSharp unavailable.
- **Skip test/generated files in edge upgrade**: filters `_test.go` and `zz_generated` from upgrade phase. 70% reduction on k8s.
- **Package-sorted edges**: sort workItems by URI for better gopls cache locality.
- **Readiness probe for enrichment**: escalating timeout probes (5s, 10s, 30s, 60s, 120s).
- **`RealNodeCount` method on SQLiteStore**: COUNT excluding phantom nodes (JOIN against files table).
- **Corpus expanded**: 9 repos/167 tasks/6 languages -> 12 repos/222 tasks/7 languages.
- **Benchmark result**: P@10 = 0.223 cold start, 0.249 with task memory compounding (+11.5%). 1.65x codegraph, 2.97x GitNexus, 3.54x Gortex, 17.2x grep.
- **Task memory compounding quantified**: +11.5% P@10, +15.0% R@10 from passive learning (round 1 to round 2).
- **Platform deployment**: DEPLOY.md and scripts/deploy.sh for bare metal DigitalOcean + Cloudflare Tunnel.
- **Makefile**: corpus-rebuild, corpus-enrich, corpus-backup, corpus-restore targets.

### Tested and Reverted

- **Reachability gap injection**: BM25 candidates that RWR couldn't reach, filtered by embedding cosine similarity. Django +3.2% but aggregate neutral (0.238 vs 0.242 without). Reverted. BM25 is too noisy as a gap candidate source. 15-config parameter sweep (threshold 0.1-0.5, maxgap 3-10) confirmed parameters are irrelevant.
- **Coherence-aware context packing**: file-based density boost for co-located symbols. Flask -1.8%. Greedy density packing already near-optimal.
- **Bidirectional inheritance edges**: parent.method -> child.method reverse edges. Django -2.5%. Adds noise without new reachability.
- **Seed count sweep**: 10/15/20/25/30/40/50 seeds on Django all produce identical P@10. Confirms the reachability finding.
- **Density-adaptive RWR alpha**: alpha=0.15 on dense repos (flask 5.9, cargo 13.5, kafka 12.5). P@10 0.280 vs baseline 0.278. Within run variance.
- **Density-adaptive inherits weight**: boosted implements/overrides/extends to 1.0 on repos with >1.5% inherits edges. Django +0.009, kafka+flask -0.008. Net neutral.
- **Interface type hint propagation (pre-resolution)**: attempted before fixing dangling edges. Edge structure mismatch: type_hint_of and implements shared 0 target hashes on Java/Python. Go (k8s): 393 edges on 523K, P@10 neutral.
- **GraphNodeCount excluding phantoms**: hypothesis that phantom inflation triggers PreferTypeSeeds incorrectly. Terraform 0.265->0.220 (worse), cargo 0.168->0.164 (neutral). Phantom nodes are a valid density signal because enrichment edges make the graph genuinely denser.

### Documentation

- **Benchmark paper**: "Evaluating Code Context Retrieval for AI Agents" drafted at `docs/research/whitepapers/code-context-retrieval-benchmark.md`. 222 tasks, 7 systems, 12 repos, conflict of interest disclosure, per-tier breakdown, scale tolerance analysis.
- **Supply chain whitepaper evaluation**: Section 7 written with 200-package FP data (1.0% rate).
- All docs updated to P@10=0.223/0.249 with new competitive ratios across 20+ files (12 repos, 222 tasks, 7 languages).
- Comprehensive experiment log in roadmap: 15 tested-negative, 7 tested-positive.
- **Confidence values corrected** across 5 docs: ast_resolved 0.85 (was 1.0/0.95), scip_resolved 0.95 (was 1.0).
- **Enrichment finding reversed**: "net-neutral" -> "strongly positive" across retrieval-pipeline.md, FINDINGS.md, system-overview.md.
- **enrichment.md renamed to enrichment-pipeline.md**, all cross-references updated.
- **Architecture README**: 10 missing docs added, reading order restructured.
- **CLI reference**: enrich lsp subcommand documented.
- **Concurrency docs**: LSP enrichment rewritten from "sequential" to concurrent (128 workers, two-phase warmup).
- **METHODOLOGY.md**: testing protocol added (django acid test, three-step workflow, output capture rules).
- **Extraction pipeline**: complete architecture doc (23 extractors, post-processing, hashing, CLI, troubleshooting, FAQ).

### Fixed

- **Extraction errors now logged** (was silent `continue`). Failures visible in stderr.
- **go.mod fallback**: `computePkgPath` falls back to `opts.RepoURL` when go.mod is missing.
- **VS Code/Ocelot re-ranker regressions resolved**: session 15 reported -16%/-30.8%, session 16 confirmed 0% delta on both repos. Artifacts of pre-vector-cache build.

### Fixed (post v0.10.0)

- **ReRankOriginalWeight default set to 0.0** (pure re-rank): the validated configuration that produces +17% P@10. Previously defaulted to 0.7 which gave no improvement.
- **jina-code as default embedding model**: changed from bge-small to jina-code (the model validated on the full corpus)
- **`--embeddings` and `--embed-model` CLI flags** on `knowing mcp`: proper UX for enabling embeddings (was env-var only)
- **Clear local/offline messaging**: CLI help and log messages emphasize no API keys, no cloud calls, no charges
- **Module-level TS extraction**: `process.env.X` and `spawn()` at top level of JS/TS files now detected (real malware executes at module load)
- **Isolation score formula tuned**: gentler inbound curve, steeper outbound curve, default threshold 0.3 (was 0.7)
- **`--scan-all` mode** for `audit-supply-chain` (for cross-DB comparisons)
- **Supply chain demo workflows** passing in CI with rich job summaries

## [v0.10.0] - 2026-05-26

### Added

#### Supply chain attack detection (verified end-to-end on real malware patterns)
- `reads_env` edge type (37th): function -> environment variable it reads (Go, Python, TypeScript, Rust, Java)
- `executes_process` edge type (38th): function -> process it spawns (Go, Python, TypeScript, Rust, Java)
- `consumes_endpoint` enhanced: detects `http.request({hostname: '...'})` object literal pattern
- Extraction wired into main extractor dispatch for all 5 languages (runs during `knowing index`)
- `knowing audit-supply-chain` CLI command: structural diff + isolation scoring + capability path detection
- Isolation score computation (`internal/diff/isolation.go`): scores files 0.0-1.0 based on graph connectivity, outbound edges to dangerous sinks, and lifecycle hook execution
- **Verified on TanStack pattern**: `process.env.GITHUB_TOKEN` + `spawn('curl')` + `fetch()` -> all detected
- **Verified on event-stream pattern**: `http.request({hostname: '111.90.151.35'})` -> `consumes_endpoint` detected
- Attack detection registry with reproducible demo scripts (`demos/supply-chain-attacks/`)

#### Embedding re-ranker breakthrough (+4.5% P@10, +16.6% R@10)
- Discovered: embeddings as independent Channel 3 are NEUTRAL (3 models tested: BGE, jina-code, nomic)
- Discovered: persistent pack cache was masking all embedding experiments
- Implemented re-ranker: embed top-50 RWR candidates, blend original score with cosine similarity
- **jina-embeddings-v2-base-code as re-ranker: P@10 0.332 -> 0.347 (+4.5%), R@10 0.447 -> 0.521 (+16.6%)**
- Blended scoring (`BENCH_RERANK_WEIGHT`): tunable 0.0-1.0, default 0.7 (0.7 original + 0.3 embedding)
- `KNOWING_EMBED_MODEL` env var: switch between `bge-small`, `nomic-code`, `jina-code`
- `DisablePersistentCache()` method for accurate benchmark measurements
- First P@10 improvement since PreferTypeSeeds (session 14)

#### `accesses_field` edge type (36th edge type, P@10 neutral)
- Connects methods to the struct/class fields they read/write via receiver
- **Go**: extracts `self.field` access from method bodies, creates field nodes from struct declarations. 660 edges on knowing codebase, 1,170 field nodes.
- **Rust**: extracts `self.field` from impl method bodies, field nodes from struct_item
- **Python**: extracts `self.field` from method bodies, field nodes from `__init__` assignments and class-level type annotations
- **Java**: extracts `this.field` from method bodies, field nodes from class field declarations
- **C#**: extracts `this.Field` from method bodies, field nodes from class field declarations
- **TypeScript**: extracts `this.field` from method bodies, field nodes from class property declarations
- Filters common noise fields (mu, logger, ctx, err, lock, wg, once)
- Field nodes use kind="field", QN pattern "repo://pkg.TypeName.fieldName"
- Automatically connected to parent type via generateContainsEdges (member_of/contains)
- RWR weight: 0.6, adjacency cache ID: 34

#### Wire format codec overhaul
- GCF: added 6 missing kind abbreviations (field, route, ext, file, pkg, svc)
- Binary (GCB1): added 6 kinds (IDs 11-16), 27 edge types (IDs 10-36), 3 provenances (IDs 5-7)
- Binary codec previously encoded unknown edge types as 0 (silent data loss on roundtrip)
- All 36 edge types, 16 node kinds, 7 provenance tiers now encode correctly
- `similar_to` added to edgetype constants (was used but undeclared)

#### `type_hint_of` edge type (P@10 0.204 -> 0.210, +3%)
- 34th edge type: connects functions to types referenced in parameter/return annotations
- **Go**: extracts from `parameter_declaration` nodes, resolves imported types via import map. k8s: 33,689 edges. Skips builtins (string, int, error, etc.)
- **Java**: extracts from `formal_parameter` nodes, handles generics (`List<T>` -> `List`) and scoped types. Kafka: 1,445 edges. Skips primitives and boxed types.
- **TypeScript**: extracts from required/optional/rest parameters via `type_annotation`. Handles generics and nested type identifiers. VS Code: 32,830 edges (after export fix).
- **Python**: extracts from `typed_parameter` nodes with import-map resolution. Django has ~0 type annotations (untyped codebase), so no impact there.

#### Fixed: TypeScript extractor missing `export_statement` handling
- Pre-existing bug: all exported classes, functions, and interfaces were silently skipped
- VS Code was extracting only 72 TS nodes from ~1M LOC (should be 87K nodes)
- Fix: unwrap `export_statement` -> declaration child and recurse in `extractNodeWithImports`
- Impact: VS Code nodes 43K -> 87K, edges 131K -> 422K
- **Tradeoff**: correct extraction causes VS Code P@10 to drop from 0.163 to 0.100 due to graph density dilution (same pattern as k8s staging in session 12). The old 0.163 was artificially inflated by sparse, broken extraction. The 0.100 with correct extraction is the honest baseline; improving it requires better seed selection for dense graphs.
- Aggregate P@10 with correct extraction: 0.203 (honest) vs 0.210 (with broken TS extraction)
- Per-repo: Kafka +14.5% (0.221->0.253), VS Code +23.5% (0.132->0.163), Terraform +1.9%, Django +1.7%
- k8s regresses -8.9% (0.168->0.153): 33K type_hint_of edges may dilute RWR probability on the largest graph
- RWR weight: 0.5, adjacency cache ID: 33

#### `--edge-types` ablation filter for indexing
- New CLI flag: `knowing index --edge-types calls,imports,implements`
- Only generates and stores edges of specified types
- Useful for: ablation studies, debugging dilution, fast iteration (skip similarity edges)
- Filter applies at batch-write time and skips post-processing for excluded types

#### Type-method path seeding (P@10 0.202 -> 0.204, Kafka +10.5%)
- When path terms match a package, checks if types in that package have methods matching task keywords
- Seeds the type so RWR walks to its methods via contains edges
- Example: "consumer group coordinator" finds ConsumerCoordinator in kafka's group/ package
- Kafka P@10: 0.200 -> 0.216. Aggregate: 0.202 -> 0.204

#### Concept thesaurus for BM25 keyword expansion
- Static thesaurus of ~80 programming domain concept clusters
- Expands BM25 queries with related code vocabulary ("consumer" also searches "subscriber", "listener", "handler")
- Covers: messaging, concurrency, serialization, validation, patterns, networking, caching, testing, configuration, lifecycle, error handling
- Kafka P@10: 0.216 -> 0.221 (stacked with type-method seeding)

#### `co_tested_with` edge type (33rd edge type)
- Lateral connections between non-test symbols referenced from the same test file
- If test file T calls/imports both symbol A and symbol B, creates co_tested_with edge
- Bridges structurally disconnected symbols that serve the same feature
- IsTestFile() detects test files across Go, Python, TypeScript, Rust, Java, C#
- Caps: 20 targets per file, 20 pairs per file (prevents N^2 explosion)
- RWR weight: 0.5. Confidence: 0.6. Provenance: co_test_inference

#### `NodesByFileHash` interface method
- New GraphStore method returns all nodes belonging to a given file hash
- Implemented in SQLiteStore + all mock stores
- Infrastructure for file-scoped queries without needing repo hash + path

#### Session 14 experiments (tested and rejected)
- **Call-chain seeding**: inject callees of top seeds as supplemental RWR seeds. Neutral (P@10 unchanged). Callees already reachable via RWR traversal.
- **File-scoped co-retrieval**: inject sibling symbols from same file. Neutral. Siblings already reachable via contains/member_of edges.
- **AND-semantics path matching**: intersect multiple path terms. Neutral. Ground truth symbols don't contain all task terms in their QN.
- **Expanded framework thesaurus** ("backend"->"base", "custom"->"abstract"): Hurts Kafka (-0.005). Too noisy for BM25.
- **Higher seed weight (0.6) for type-method matches**: Slightly worse than 0.3. RWR handles seed weighting internally.

#### Self-adapting type-seed preference (P@10 0.202 -> 0.207, VS Code +44%)
- On dense graphs (>40K nodes), automatically reorder RRF candidates to prefer type/interface/class nodes as RWR seeds over methods/functions
- Types are better seeds because they have contains edges to their methods (more productive walk)
- VS Code: 0.095 -> 0.137 (+44%). Aggregate: 0.202 -> 0.207 (+2.5%). Zero regressions.
- Self-adapting: auto-enables when `GraphNodeCount > 40000` (no manual configuration)
- Threshold 40K chosen empirically: VS Code DB has 49K nodes, k8s 117K, kafka 80K, django 42K
- Also available as manual override: `BENCH_PREFER_TYPE_SEEDS=1`
- Hub dampening (H1) tested and rejected: no effect on VS Code (0.095 unchanged)

#### Phrase-boosted BM25 from adjacent Components
- Generates FTS5 phrase queries from adjacent word pairs in Components list
- "code actions" as a quoted phrase matches only symbols with adjacent words in FTS index
- VS Code: 0.084 -> 0.095. No regressions. Aggregate: 0.201 -> 0.202.

#### Diagnostic tools for retrieval investigation
- `BENCH_EXCLUDE_EDGES=similar_to,type_hint_of`: query-time edge exclusion (no reindex)
- `BENCH_BFS_DEPTH=2`: configurable BFS expansion depth
- `BENCH_HUB_DAMPEN=50`: hub node dampening (penalize high-in-degree nodes)
- `BENCH_PREFER_TYPE_SEEDS=1`: manual type-seed preference override
- All filter at adjacency cache BFS and fallback BFS paths
- Documented in `docs/guide/diagnostic-tools.md`

#### Dense-graph dilution investigation (docs/research/dense-graph-dilution-analysis.md)
- 5 hypotheses tested, 3 ruled out (similarity edges, type_hint_of edges, BFS depth)
- Root cause confirmed: seed selection degrades on dense FTS indexes (keyword competition)
- PreferTypeSeeds (H8) confirmed as effective fix for VS Code (+44%)

### Fixed
- CI timing contracts: loosen Louvain 0-changes (10ms -> 15ms) and scoped FTS (50ms -> 75ms) for noisy CI runners

#### Benchmark corpus expansion (9 repos, 167 tasks)
- Added Terraform (Go, 2M LOC, 37K nodes, 184K edges, 20 tasks)
- Added Kafka (Java, 500K LOC, 74K nodes, 780K edges, 19 tasks)
- Expanded Flask to 19 tasks (from 14)
- Total: 9 repos, 6 languages, 167 tasks (from 117)
- P@10 = 0.202 on full corpus (Kafka 0.300, Terraform 0.250 pull average up)

#### Go structural edge extraction
- Interface embedding: `type A struct { B }` creates A --implements--> B
- Channel send/receive: creates references edges for producer/consumer relationships
- Type assertions: `v.(Type)` creates references edge to the asserted type
- All four extracted from Go AST in `go_structural_edges.go`

#### Docstring FTS indexing (P@10 0.180 -> 0.202, +12.2%)
- New FTS5 column `doc` (weight 3.0) indexes node docstrings for BM25 retrieval
- Bridges the vocabulary gap: task descriptions use natural language, docstrings are natural language descriptions of what code does
- Migration 018 adds doc column to `nodes_fts_content` and rebuilds FTS virtual table
- Shared `docextract` package provides language-agnostic extraction from preceding comments
- **6 languages**: Go (//), Python (body docstrings), TypeScript (JSDoc), Rust (///), Java (Javadoc), C# (XML ///)
- BM25 column weights: symbol_name=10, concepts=5, qualified_name=3, file_path=4, doc=3, signature=1
- Flask P@10: 0.250 -> 0.271 (+8.4%). Full corpus (167 tasks, 9 repos): 0.180 -> 0.202 (+12.2%)
- MRR improved +4.9% (first relevant result ranks higher thanks to docstring matching)

#### Fixed: feedback compounding regression
- Root cause: weight-0 edges (contains, member_of, authored_by) were traversed during adjacency BFS, flooding the subgraph with thousands of extra nodes that diluted RWR probability and made feedback boosts ineffective
- Fix: exclude weight-0 edges from BFS frontier expansion in `buildAdjacencyMap`
- Result: TestFeedbackCompounding passes again (baseline 44%, feedback 44%, no regression)

#### Python import resolution fix
- `resolveCallTarget` now handles `from X import Y` where Y is a submodule (file) correctly
- Previously: `base.Operation.state_forwards()` resolved to `operations.py.base.Operation.state_forwards` (wrong hash)
- Now: correctly resolves to `operations/base.py.Operation.state_forwards` (matching the actual node)
- `extractImport` resolves internal imports to actual file paths (verifies file exists on disk)
- Django: 36,226 unresolved call edges -> 0 (all calls now point to real targets)

#### Compact binary adjacency cache for RWR
- Replaces gob+base64 format with compact binary: 65 bytes/edge (source:32 + target:32 + type_id:1)
- k8s (268K edges): ~17MB raw vs 252MB with gob (15x smaller)
- Edge count threshold raised from 50K to 500K (covers all practical repos)
- 30 edge types mapped to uint8 IDs via `adjEdgeTypeToID`/`adjIDToEdgeType`
- Cache version bumped to v2 (automatically invalidates old v1 caches)

#### RWR early termination
- Stop iterating when top-10 ranking unchanged for 2 consecutive iterations
- Saves ~50% iterations on large graphs (fewer matrix multiplications)
- Zero P@10 regression (ranking converges well before full iteration count)

#### Time-to-consistency benchmark (`bench/time-to-consistency/`)
- Measures how quickly retrieval reflects a code change (edit -> reindex -> query finds it)
- Protocol: inject new function into Flask, trigger incremental, query for it
- knowing: 167ms total (16ms reindex + 151ms query). codegraph: 805ms (4.8x slower). Aider: 3150ms (and fails to find new symbols)
- Includes correctness test: function absent before injection, present after reindex

#### Agent efficiency Phase 2 (`bench/agent-efficiency/phase2_test.go`)
- k8s ambiguity tasks: grep returns 10,840 matches per task, knowing returns 10 ranked results
- Knowing ground truth hit rate: 72% (vs codegraph 56%, GitNexus 0%)
- Validates that graph-ranked retrieval resolves ambiguity grep cannot

#### k8s adjacency cache latency validation
- Measured: 9.04s uncached -> 1.9ms cached (4,717x speedup)
- 500x faster than codegraph on k8s-scale graphs (268K edges)

#### Stdlib node filter
- Filter `stdlib://` nodes from retrieval results
- Fixes k8s results being dominated by fmt.Errorf (5,809 callers pulling stdlib into top-10)
- Zero cross-system P@10 impact (stdlib nodes were noise, not signal)

#### Channel balance regression test
- `TestChannelBalance_EquivNeverDominates` prevents Run 22 class of regression
- Asserts equivalence channel never exceeds 2x primary channels in RRF

#### P@10 regression gate (`TestP10Regression_Flask`)
- Runs 4 fixed tasks against Flask, asserts ground truth hits don't drop below baselines
- Catches silent quality degradation without full 117-task benchmark

#### codebase-memory-mcp adapter
- New competitor adapter for codebase-memory-mcp (2.6K stars, BM25 + semantic edges)
- P@10=0.137 on Flask+Cargo (knowing 1.51x better)
- Documented scale limitation: hangs on Django (300K LOC), killed on k8s (3.5M LOC)

#### Determinism benchmark (`TestDeterminism`)
- Runs same task 10x per system, counts unique outputs
- knowing/codegraph/codebase-memory/Gortex: deterministic (1 unique output)
- GitNexus: 7-9 unique outputs (wildly non-deterministic)
- Aider: 3 unique outputs (moderately non-deterministic)

#### Query robustness benchmark (`TestQueryRobustness`)
- Same task rephrased 5 ways, measures Jaccard similarity of outputs
- Honest negative: all keyword-seeded systems (knowing 0.07, codegraph 0.08) are volatile
- Aider is stable (0.74) but imprecise (P@10=0.050): stability without precision is useless

#### Zlib-compressed context pack cache
- Context packs in graph_notes now zlib-compressed (~6x smaller)
- Backwards-compatible read (tries zlib, falls back to raw JSON)
- Reduces storage footprint for frequently-queried repos

#### Incremental file reindexing (`IndexFilesIncremental`)
- New method on `Indexer` that only extracts/stores specified changed files (no directory walk)
- Daemon's `IndexFunc` now uses it when `changedFiles` are available from git watcher
- 494x faster than full index for 1-file edits (24ms vs 11.8s on 7803-node repo)
- Scales linearly: 5 files = 59ms, 20 files = 93ms
- Benchmark: `bench/incremental-reindex/`

#### Enterprise-scale multi-module LSP enrichment
- **Multi-module gopls**: parses `go.work`, spawns one gopls per module instead of one for the whole workspace
- Root module processed solo first (1.2GB gopls), then sub-modules in parallel (4 concurrent, ~200MB each)
- **Progress persistence**: `.knowing/enrich-progress.json` tracks per-module completion; interrupted runs resume automatically
- **Per-symbol timeout**: `WithSymbolTimeout` (10s default) prevents individual hung LSP calls from blocking the pipeline
- **Graceful degradation**: failed modules are logged and skipped; enrichment continues with remaining modules
- Concurrent LSP resolution with serialized DB writes (producer-consumer pattern)
- Default 8 parallel requests per module; configurable via `-enrich-concurrency N` on `index` and `reindex`
- Skip-resolved: edges already at `lsp_resolved` provenance are not re-processed
- Batched file discovery (50 files at a time, no bulk didOpen)
- **k8s result**: 57,441 edges upgraded to lsp_resolved (0.9). Previously: 0 (gopls crashed)
- Workspace root resolved to absolute path (fixes gopls "no views" error on relative paths)
- Cross-module edge attenuation in RWR (0.3x for transitions between top-level directories)
- Repo-scoped search filtering via `TaskOptions.RepoURL` (prevents cross-repo noise in multi-module DBs)

#### Structural `contains` edges (type -> method)
- New edge type: `contains` (RWR weight 0.6) connects type/class nodes to their method/field nodes
- Generated from QN structure during indexing: if `Foo.Bar` exists and `Foo` is a type, emit `Foo --contains--> Foo.Bar`
- Fixes: 77% of type/class nodes (5,457/7,086 in k8s) had zero edges, completely disconnected from the graph
- Impact: 19 ground truth symbols moved from "unreachable" to "ranked_low" (reachable but below top-10)
- spark-java: 0 unreachable symbols (from 1). k8s: 44 (from 47). flask: 23 (from 25).
- `django-hard-002` P@10 went from 0.00 to 1.00 (custom migration operation task)

#### Path-context seeding (Channel 5 in retrieval pipeline)
- Extracts package/directory-like terms from task descriptions
- Finds TYPE nodes in matching packages, prioritizing types with methods (rich types)
- Injects as supplemental RWR seeds (weight 0.3), bypassing RRF competition
- Bridges concept-to-implementation gap: "migration" in task -> finds types in migrations/ package

#### P@10 failure analysis tool (`bench/cross-system/failure_analysis_test.go`)
- Categorizes every ground truth miss: not_in_db, no_seeds, unreachable, ranked_low, matched
- Baseline results: 168 matched (25.7%), 175 ranked_low (26.8%), 310 unreachable (47.5%)
- After contains+path: 168 matched (25.7%), 194 ranked_low (29.7%), 291 unreachable (44.6%)
- Identifies most impactful tasks for targeted improvement (top: django-hard-001, vscode-hard-003)

#### Parameter sweep benchmark (`bench/cross-system/sweep_test.go`)
- 26-config grid search across all tunable retrieval parameters
- Sweeps: RWR alpha (0.10-0.40), max seeds (10-30), score cutoff (0.005-0.10), ranking weights (blast/distance/confidence/recency), RRF k (20-100), test penalty (0.0-0.7), combined configs
- Result: ALL configurations produce identical P@10=0.180, R@10=0.263, MRR=0.349
- Proves definitively that P@10 is determined by graph reachability, not parameter tuning
- Sweep infrastructure retained for regression detection on future changes

#### Exported `ExtractKeywordSet` for benchmark tooling
- Public entry point for the structured keyword extraction pipeline
- Used by failure analysis tool to inspect what keywords are extracted per task

### Changed

#### LSP enrichment ROI measured (neutral for P@10, confirmed at enterprise scale)
- Flask/Django: identical P@10 with and without enrichment (previously measured)
- k8s: P@10 0.181 with 57K lsp_resolved edges, same as without. Confirmed flat.
- Confidence-weighted RWR (multiply edge weight by confidence): tested, P@10 0.180 (neutral). Reverted.
- Staging indexing tested and reverted: indexing go.work sub-modules dilutes P@10 -20% (136K extra nodes absorb probability)
- Conclusion: P@10 bottleneck is seed selection (keyword extraction stage), not the walk phase or edge confidence
- Enrichment value is correctness (audit trail, cross-repo resolution), not retrieval ranking

### Fixed

#### Feedback compounding was defeated by context pack cache
- `RecordFeedback` now invalidates all cached context packs (`context_pack` notes)
- Previously, feedback was recorded but never affected results because `ForTask` returned
  the cached pack from the first query (keyed by task hash, only invalidated on snapshot change)
- After fix: feedback compounding produces +10pp P@10 on feedback-loop bench (34% -> 44%)

### Changed

#### Asymmetric feedback weighting (tuned via automated sweep)
- Positive feedback boost: 0.15 -> 0.25 (score=1.0 gives +0.25 to ranking)
- Negative feedback penalty: 0.15 -> 0.10 (score=0.0 gives -0.10 to ranking)
- Asymmetric prevents over-penalizing symbols incorrectly marked "not useful"
- Exposed as `FeedbackPosWeight` / `FeedbackNegWeight` package vars for tuning
- Added `TestFeedbackWeightSweep` (7x4 grid search across pos/neg weight combinations)

## [0.7.1] - 2026-05-23

### Fixed

#### Equivalence Channel Noise (P@10 regression fix)
- Root cause: equivalence class matching returned unbounded results (66 on small repos) that dominated RRF fusion, causing flat RWR scores across all seeds
- Generic target filter: skip resolving equiv targets <=3 chars or common method names (`get`, `set`, `do`, `new`, `run`, `put`, `post`, `call`, `add`, `pop`)
- Equiv cap: limit equiv results to 2x(tiered+BM25) count, preventing channel domination
- `buildFTSQuery`: removed redundant unquoted compound that searched all FTS columns
- Cleaned universal seed phrases: removed single-word triggers ("request", "fetch") and generic targets from HTTP_CLIENT class
- Flask P@10: 0.20 -> 0.336 (+68%). Full corpus: 0.101 -> 0.226 (+124%)

#### Other Fixes
- Exclude phantom external nodes from RWR walk BFS expansion (prevents enrichment-created externals from diffusing scores)
- Restore `extractKeywordSet` (accidentally reverted during debug)
- Aider adapter: suppress stdout progress bars polluting JSON output
- Gortex adapter: handle log lines before JSON response

### Added

#### Zero-Config MCP Onboarding
- MCP server (`knowing mcp`) now auto-indexes the git repository on first launch if no database exists
- Detects git root from current working directory, resolves repo URL from git remote
- Creates database, runs full index (tree-sitter extraction across all 24 language extractors), registers in roster
- Subsequent sessions resolve the database automatically via the roster (no path configuration needed)
- Removes the previous requirement to run `knowing index` or `knowing add` before using MCP tools
- Error path preserved: if not inside a git repository, reports actionable error with fallback instructions

### Changed

#### Code Quality Cleanup (7 Audit Findings)
- **Node kind constants** (`internal/types/kinds.go`): 11 `types.Kind*` constants replace raw string literals across all 24 extractors
- **Edge type constants**: all extractors now use `edgetype.*` constants instead of raw strings for edge types
- **Provenance constants** (`internal/types/provenance.go`): 5 provenance tier strings + 4 confidence float64 values as named constants
- **Dead type removal**: deleted `ComputationCache` interface, `DerivedResult` struct, and `TraversalOptions` struct (unreferenced since initial design)
- **Shared mock store** (`internal/testutil/mockstore.go`): single `MockGraphStore` implementation replaces 6 independent per-package mocks (~300 lines of boilerplate removed)
- **Shared external URL inference** (`internal/resolve/external.go`): `InferExternalRepoURL` with `LangConfig` for TypeScript, Python, Rust, Java, C# replaces 5 duplicated per-extractor functions (~280 lines removed)
- **Chunked batch helper** (`internal/store/batch.go`): generic `ChunkedExec[T]` replaces 3 manually-duplicated chunk loops in `BatchPutNodes`/`BatchPutEdges`/`BatchPutFiles`

### Added

#### Staleness Reporting (`knowing stale`)
- `knowing stale` CLI command detects files changed since last snapshot (via git diff) and reports stale node counts
- Uses `StaleNodesByFiles` store method to look up nodes affected by changed files
- Exits with code 1 when stale files are found (CI-friendly gate)
- Implementation: `cmd/knowing/stale.go`, `internal/store/sqlite.go` (`StaleNodesByFiles` method)

#### Cross-Repo Awareness for Non-Go Extractors
- All 5 OOP extractors (Python, TypeScript, Rust, Java, C#) now have `inferExternalRepoURL` functions
- Detects external packages and computes target hashes with `"external://{packageName}"` or `"stdlib"` prefix instead of the local repo URL
- Gives cross-repo identity for import edges without full registry lookups
- Python: `site-packages/` detection + ~50 stdlib modules
- TypeScript: bare specifiers (non-relative imports) treated as npm packages
- Rust: `std::`/`core::`/`alloc::` = stdlib, other non-crate paths = external
- Java: `java.*`/`javax.*` = stdlib, third-party by package prefix
- C#: `System.*`/`Microsoft.*` = stdlib, third-party by namespace

#### Daemon Lifecycle Commands
- `knowing daemon start [--detach]`: start the daemon, optionally in background mode
- `knowing daemon stop`: stop a running daemon by PID
- `knowing daemon status`: check whether the daemon is running
- `knowing daemon restart`: stop and restart the daemon
- PID file stored at `~/.knowing/daemon.pid`
- Implementation: `cmd/knowing/daemon.go`, `internal/daemon/pidfile.go`

#### `untrack_repo` (28th MCP tool)
- `knowing remove <path-or-url>` CLI command now evicts all data for a repository: nodes, edges, files, snapshots, feedback, task_memory, and graph_notes
- Also available as the `untrack_repo` MCP tool (28th tool) for agent-driven repo management
- Parameters: `repo_url` (required)
- Implementation: `internal/store/evict.go`, `internal/mcp/untrack.go`

#### Community-Aware Random Walk with Restart
- RWR walk now constrained to seed communities when candidates cluster in 1-3 communities
- `CommunityFilteredRWR`: BFS expansion skips nodes outside the allowed community set
- `buildAdjacencyMapFiltered`: community-filtered variant of the adjacency pre-load
- `CommunitiesForNodes` on SQLiteStore: batch lookup of community_id notes
- When seeds span 4+ communities (diverse query), falls back to unconstrained walk (backward compatible)
- Prevents RWR from drifting into unrelated packages on large repos
- Benchmark adapter now runs Louvain community detection on index (matching daemon behavior)

#### Cross-File Import Resolution (Java, C#)
- **Java**: `buildJavaImportMap` extracts `import com.pkg.Class` and `import static com.pkg.Class.method` declarations into a lookup map
- **C#**: `buildCSharpImportMap` extracts `using Namespace.Sub` and `using static Namespace.Class` directives
- Both resolve call targets through the import map when the object name matches an imported class (uppercase-first heuristic)
- Resolved edges get provenance `ast_resolved` with confidence 0.85 (up from `ast_inferred` / 0.7)
- Follows the established Rust pattern (`buildRustImportMap` / `resolveCallEdgeWithImports`)
- Wildcard imports (`import com.pkg.*`) correctly skipped (cannot resolve individual names)
- Completes cross-file import resolution for all 5 OOP languages: Python, TypeScript, Rust, Java, C#

### Fixed

#### Claude Code Hooks Fully Operational (three fixes)
- **Wrong input field**: hooks read `data.get('input', {})` but Claude Code sends `tool_input`. All edits silently produced empty file paths. Fix: `data.get('tool_input', data.get('input', {}))`
- **Wrong output format**: hooks output `{"message": "..."}` which is not recognized by Claude Code. Context was produced but never delivered to the model. Fix: output `{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow", "additionalContext": "..."}}` 
- **Dead format string**: `kwf` format removed during GCF migration; every query errored silently. Fix: default to `gcf`
- All three fixes combined: pre-edit hook now fires on every Edit/Write, injects graph-ranked context (top 20 symbols, 250ms), and delivers it as a system reminder the model reads
- Trimmed hook output: strips edges section, caps at 20 most relevant symbols (~2-3KB inline vs 22KB before)
- Lowered default budget from 800 to 400 tokens (engine only needs to score enough candidates to fill top-20)
- Re-ran hook benchmarks: precision 33.2%, recall 60.8%, 100% coverage (hook fully replaces manual context calls)

#### Phantom External Nodes Dominating Retrieval Results
- External nodes (kind="external", `external://` prefix) from failed LSP enrichment entered results via RWR walk
- On repos with many phantom nodes (e.g., Spark Java: 2282 externals), they occupied all top-10 positions
- Fix: filter at two points: `filterNoisySymbols` (seed candidates) and RWR result loop (before scoring)
- Spark Java: P@10 0.00 -> 0.10 (was returning only phantom nodes, now finds real symbols)

### Changed

#### Compound-First Keyword Extraction (Language-Aware Tiered Search)
- Tiered search now queries compound identifiers (snake_case, CamelCase, dotted) before their split components
- New `KeywordSet` struct separates Exact (backtick-quoted), Compounds, and Components by specificity tier
- Backtick-quoted identifiers in task descriptions (e.g., `` `before_request` ``) are treated as highest-priority exact symbol names
- Components ("before", "request") only used as fallback when compounds yield < 5 results
- Eliminated code duplication: `ForTask` and `ExplainSymbol` now share a single `tieredSearchSet` method
- Fixed `bm25Search` in ExplainSymbol to use `buildFTSQuery` (compound-targeted) instead of naive OR join
- Flask P@10: 0.321 -> 0.329 (+0.8pp). Overall P@10: 0.230 (neutral, no regression)

### Added

#### Passive Task Memory Persistence (Session Compounding)
- MCP server records top-5 returned symbols in `task_memory` table after each `context_for_task` call
- Future queries with similar keywords recall stored symbols and boost them (0.5 + score * 0.4)
- Persists across process restarts via SQLite (migration 008 `task_memory` table)
- Fixed memory boost scoring: was producing negative boosts (score < 0.5 treated as penalty)
- Real-user impact: quality compounds over time as the system learns which symbols matter for which tasks
- Independent proof: `bench/feedback-loop/` shows +20pp precision after one feedback round

#### FTS Concepts Column (File-Name Derived Vocabulary Bridging)
- New `concepts` column in FTS index stores CamelCase-split tokens from file names and directories
- "src/compiler/commandLineParser.ts" -> concepts "compiler command Line Parser commandLineParser"
- BM25 weights: symbol_name=10x, concepts=5x, qualified_name=3x, signature=1x, file_path=1x
- Migration 017 adds concepts column and recreates FTS virtual table
- Bridges vocabulary gap where developers say "parser" but symbol is "parseOptionValue"

#### TypeScript extends_clause Fix
- Tree-sitter TypeScript nests `extends_clause` inside `class_heritage` (not direct child of class_declaration)
- Extractor now searches one level deeper for the heritage wrapper
- VS Code: 901 extends edges + 337 inheritance edges (was 0)
- P@10 0.226 -> 0.230 with VS Code inheritance propagation active

#### Deeper Call Chain Extraction (Python)
- Walk into call arguments to extract nested calls, callbacks, and lambda references
- Previously: `map(process, items)` only extracted the `map` call, missing `process` as a target
- Now: all identifier and call references inside arguments produce call edges
- Lambda bodies (`lambda: get_users()`) are walked for calls
- Nested function bodies walked with import resolution context (pyImports preserved)
- Flask: 5,022 -> 9,237 edges (+84%). Django: 151,431 -> 185,393 edges (+22%).

#### Cross-File Import Resolution (Python, TypeScript, Rust)
- **Python**: `buildPythonImportMap` extracts `import`/`from...import` statements, `resolveCallTarget` resolves call edges through the import map. 63 resolved cross-file edges on Flask.
- **TypeScript**: `buildTSImportMap` extracts `import`/`require` declarations, `resolveCallEdgeWithImports` resolves call targets through the map. 5,684 resolved cross-file edges on TypeScript compiler.
- **Rust**: `buildRustImportMap` extracts `use` declarations, `resolveCallEdgeWithImports` resolves `crate::`, `super::`, `self::` paths. 9,795 resolved cross-file edges on Cargo.
- Import resolution creates more edges for RWR to walk, improving recall on cross-file tasks.

#### Inheritance Propagation (language-agnostic)
- `propagateInheritance` post-processing pass finds all `extends` edges and creates `inherits` edges from child classes to parent class methods
- Enables RWR to walk from `Flask` -> `Scaffold.before_request` via inheritance chain
- Uses import-resolved qualified names to match extends edge targets to actual class node hashes
- 83 edges in Flask, 14,539 edges in Django (deep class hierarchies)
- Works on any language whose extractor produces `extends` edges and `method` nodes (Python, TypeScript, Java, C#, Rust)

#### Test File Deprioritization
- 0.3x score penalty for symbols from test files in ranking
- Detection by file path patterns (not symbol names): `/tests/`, `_test.go`, `.test.ts`, `.spec.ts`, `/__tests__/`
- Penalty removed when task description mentions testing (conditional, not absolute)
- Avoids false positives on production code with "test" in legitimate names

#### Failure Analysis Tool
- `bench/cross-system/cmd/failure-analysis/` diagnoses miss categories across all benchmark tasks
- Categories: noise (56%), test_symbol (36%), related_name (5%), same_package (2%)
- Key finding: bottleneck is RWR reach (graph connectivity), not ranking

### Fixed

#### FTS was never populated in CLI mode (critical)
- Background goroutine running `RebuildFTS` was killed on process exit before completing
- FTS index was always empty in `knowing index` (CLI) mode; only daemon kept it populated
- Fix: `RebuildFTS` now runs synchronously after snapshot computation
- FTS adds ~500ms to index time (acceptable for correct results)

#### FTS tokenizer: underscore now a token character
- `before_request` was tokenized as two tokens (`before`, `request`), preventing exact match
- Migration 016 updated: `tokenchars '_'` added to FTS5 tokenizer configuration
- Multi-word identifiers using snake_case now match as single tokens

### Changed

#### RRF channel weights equalized (tiered=2, BM25=2, equivalence=2)
- Was: tiered=3, BM25=1, equivalence=2
- Investigation showed BM25 and tiered find the same symbols in practice
- Equalizing weights removes artificial suppression of BM25 channel
- Cross-system benchmark: P@10 improved from 0.141 to 0.154 across Runs 7-10

#### P2 Edge Type Expansion (24 -> 30 edge types)
- `documents`: comment/docstring association with documented symbols
- `gated_by_flag`: feature flag references (LaunchDarkly, OpenFeature, custom `isEnabled` patterns)
- `consumes_endpoint`: HTTP client call sites in Go (`http.Get/Post/Do`) and TypeScript (`fetch/axios`)
- `implements_rpc`: gRPC service method implementations linked to proto definitions
- `consumes_rpc`: gRPC client call sites linked to proto service methods
- `deployed_by`: GitHub Actions workflow deploys linked to deployed services
- `tested_by`: GitHub Actions workflow test jobs linked to tested packages
- All 7 new types have RWR weights in `internal/edgetype` constants package
- Total: 30 edge types, 28 MCP tools

#### Indexer Performance Overhaul
- **Parallel extraction**: GOMAXPROCS workers with producer-consumer pipeline
- **Streaming commits**: batch of 500 files committed to SQLite immediately (kill-safe)
- **Single-pass body walk**: one recursive AST traversal dispatches calls/throws/routes/flags/endpoints (was 5 separate traversals)
- **Shared tree parsing**: tree-sitter parses once per file, all extractors share the result
- **Thread-safe extractors**: per-call parser creation (11 extractors fixed for parallel use)
- **In-memory snapshot**: `ComputeSnapshotFromEdges` builds Merkle tree from pipeline data (no DB re-read)
- **Synchronous FTS**: full-text search rebuilds synchronously after snapshot (~500ms)
- **Skip edge events on first index**: no parent = no diff to record (saves 268K INSERT ops)
- **Skip generated files**: checks first 512 bytes for `Code generated`/`DO NOT EDIT` markers
- **Skip non-source dirs**: `.git`, `vendor`, `node_modules`, `staging`, `third_party`, etc.
- **Per-file timeout**: 10s watchdog with fire-and-forget for stuck CGO calls
- **Progress output**: real-time `[N/total] X files/s, Y edges, ETA Zs` on stderr
- **`--skip-blame` flag**: skip git blame authorship extraction (expensive on large repos)
- **`--no-enrich` flag**: skip LSP enrichment for structural-only indexing
- **`--workers N` flag**: control extraction parallelism

#### Cross-System Benchmark Framework
- 100 tasks across 5 repos (kubernetes, VS Code, Django, Cargo, Flask)
- 5 difficulty levels: easy, medium, hard, cross-file, architectural
- Metrics: P@K, R@K, NDCG@10, MRR, token efficiency, latency
- Statistical rigor: Wilcoxon signed-rank, Cohen's d, bootstrap CI
- Adapter interface for pluggable retrieval systems (knowing, grep, future: gitnexus, aider)
- Symbol normalization for cross-system comparison
- Ground truth achievability filter (only count symbols present in DB)

#### Language Equivalence Classes
- 31 language-specific equivalence classes for improved keyword matching
- Python: `__init__`/constructor, `self`/`this`, `def`/`function`, Django/Flask patterns
- TypeScript: React hooks, Express/Fastify/Hono patterns, `interface`/`type`
- Rust: trait/impl, `Result`/`Option`, `unwrap`/`expect`
- Java: Spring annotations, `@Override`/`implements`
- Kubernetes: resource type aliases, `spec`/`template`/`containers`

#### FTS terminal symbol name column (retrieval quality)
- New `symbol_name` column in FTS index stores just the terminal identifier (e.g., `QuerySet.filter` instead of the full `github.com/django/django://django/db/models/query.py.QuerySet.filter`)
- BM25 weights: symbol_name=10x, qualified_name=3x, signature=1x, file_path=1x
- `extractSymbolName` strips repo URL, package path, and file extension prefix
- Eliminates path token dilution that buried relevant symbols in BM25 ranking
- Migration 016: adds `symbol_name` column, recreates FTS5 virtual table
- Expected impact: +5-10pp P@10 on non-Go repos where qualified names include file paths

#### Cross-system benchmark: all 5 repos indexed
- kubernetes: 4,877 files, 117,401 nodes, 268,249 edges (18.6s)
- VS Code: 38,260 files, 43,379 nodes, 93,382 edges (4.1s)
- Django: 2,937 files, 42,947 nodes, 185,393 edges (3.3s)
- Cargo: 979 files, 8,075 nodes, 79,305 edges (1.4s)
- Flask: 97 files, 1,658 nodes, 9,237 edges (0.1s)
- Total: 47,150 files in ~52s

### Fixed

#### Indexer: CGO timeout hang on large repos
- Tree-sitter CGO calls are not interruptible by Go context cancellation
- `context.WithTimeout` was ineffective: stuck CGO call blocks worker goroutine forever
- Pipeline deadlock: `extractWg.Wait()` never returns -> `close(resultCh)` never fires -> consumer loop hangs indefinitely
- Fix: watchdog goroutine pattern with timer select. Extraction runs in a fire-and-forget goroutine; 10s timer races against it. If timer wins, worker sends empty result and moves on.
- Result: kubernetes (4877 files, 268K edges) indexes in 18.6s. Was hanging indefinitely.

#### FTS + snapshot WAL contention
- Running FTS rebuild concurrently with snapshot computation caused both to stall
- Fix: sequential ordering (snapshot first, then FTS in background)

#### Test: mockSnapshotComputer parent chain behavior
- `TestIndexRepo_CleanupOnChange` was failing because mock always returned zero `ParentHash`
- Edge event recording condition (`snap.ParentHash != zero`) was never true in tests
- Fix: mock now tracks call count and returns proper parent chain on subsequent invocations

#### SQLite performance pragmas
- `synchronous=NORMAL`: safe with WAL, skips fsync per-commit (only on checkpoint)
- `mmap_size=256MB`: memory-mapped reads skip userspace buffer copy
- `cache_size=64MB`: larger page cache reduces disk I/O on warm workloads
- `busy_timeout=5000`: graceful retry on lock contention
- `temp_store=MEMORY`: temp indexes in RAM

#### Multi-row batch INSERT
- Edges: 100 rows per INSERT statement (was 1 row per exec)
- Nodes: 99 rows per INSERT statement
- Files: 249 rows per INSERT statement
- Reduces per-row SQL parsing overhead and CGO crossing count

### Changed
- Indexer architecture: sequential file loop replaced with producer-consumer pipeline
- Snapshot computation: from DB re-read to in-memory construction (9ms for knowing, 95ms for kubernetes)
- SQLite batch writes: single-row prepared statement loop replaced with multi-row VALUES
- Edge types: 24 -> 30 (7 new P2 types)
- MCP tools: 24 -> 27 (ownership_query + prove + prove_absent + fsck)

## 2026-05-19

#### MCP audit tools (27 tools total with ownership_query)
- `prove` MCP tool: generate inclusion proofs from agent conversations
- `prove_absent` MCP tool: generate absence proofs from agent conversations
- `fsck` MCP tool: verify graph integrity from agent conversations
- Enables agent-native compliance workflows without CLI

#### Database management
- `knowing reset`: delete all graph data (nodes, edges, snapshots) without removing DB file
- `knowing vacuum`: compact database after deletions (reports before/after size)
- `knowing remove --purge`: remove from roster AND delete the DB file
- `snapMgr` now initialized in plain MCP stdio mode (prove tools work without --watch)

#### Human-readable proof output
- `knowing prove -human` and `knowing prove-absent -human` for terminal-friendly output
- Clean format for screenshots and demos (default remains JSON)

#### Java extractor: proper package paths
- Qualified names now use Java package declaration (e.g., `org.springframework.samples.petclinic.owner.OwnerController`)
- Previously embedded absolute file paths; now extracts from `package_declaration` AST node
- Validated on Spring PetClinic (47 files, 5522 nodes, 3048 edges, 21 Spring routes)

#### Grafana scale validation
- Indexed Grafana (~500K LOC Go+TypeScript): 338K nodes, 714K edges, 15,921 files
- Hierarchical tree build: 88ms for 249K edges (3,552 packages)
- Context retrieval operational at 50x primary codebase scale

#### Named snapshot refs
- `knowing diff @latest @prev` (diff last two snapshots)
- `knowing diff @0 @3` (offset from most recent)
- `knowing audit-diff @prev @latest`
- Supports: `@latest`, `@first`, `@prev`, `@N` (offset), or raw hex hash
- Inspired by git's ref system (HEAD, HEAD~1)

### Changed

#### Merkle tree implementation extracted to `merkle-strata` library
- Internal `computeMerkleRoot` replaced by `github.com/blackwell-systems/merkle-strata` v0.1.1
- `BuildMerkleTree` delegates to `forest.Build` with `WithPrefix([]byte("merkle\x00"))` for hash parity
- `BuildHierarchicalTree` delegates to `forest.BuildMultiLevel`
- All exported API preserved unchanged (zero-breaking-change refactor)
- `combineHashes` retained for proof.go compatibility
- Net: -44 lines from knowing, delegated to standalone library
- Library: https://github.com/blackwell-systems/merkle-strata

### Added

#### `knowing stats` CLI
- Cumulative graph statistics: repos, nodes, edges, files, snapshots, communities, graph notes
- Feedback metrics: total, useful, not useful, unique symbols, merkleized count, usefulness rate
- Supports `-json` flag for structured output
- Supports `-db` flag for custom database path

#### Generation numbers on snapshots
- Schema migration 015: `generation INTEGER NOT NULL DEFAULT 0` on snapshots table
- `Snapshot.Generation` field: `parent.Generation + 1` on each new snapshot
- Enables O(1) ancestry checks without walking the chain
- Inspired by git's commit-graph `generation_number`

#### Auto-GC threshold
- After indexing, if `edge_events` table exceeds 5,000 rows, automatically prunes old snapshots (keeps 10)
- Inspired by git's `gc.auto` threshold (6,700 loose objects triggers gc)
- Prevents unbounded edge_events growth without manual intervention

#### Merkleized Feedback Validity (v0.5.0)
- Feedback records now store `neighborhood_root` (SubgraphRoot of symbol's package)
- Feedback automatically expires when code changes (neighborhood changes)
- 11% overhead (255µs baseline -> 284µs per 100 symbols)
- Schema migration 014: `neighborhood_root` column + index on feedback table
- `computeNeighborhoodRoot` helper in MCP server computes package root for a symbol
- `FeedbackBoosts` method accepts optional `neighborhoodRoots` map for merkleized expiration

#### Merkle Proofs and Audit Primitives
- `knowing prove`: generates cryptographic Merkle proofs (72µs, ~3KB)
- `knowing verify`: offline verification without database access (1.2µs)
- `knowing prove-absent`: absence proofs using adjacent sorted leaves
- `knowing audit`: compliance report with integrity check, edge inventory, and Merkle proofs
- Auto-substring matching in prove/prove-absent (no `%` prefix needed)
- Human-readable prove/verify output

#### Cross-Repo Resolution
- Phantom external nodes for stdlib/external edge targets
- Enricher creates phantom nodes for all dangling edges post-enrichment
- `ExtractPackagePath` handles method qualified names correctly
- Fsck roster awareness + cross-repo method resolution

### Changed

- Cross-repo edges now fully resolved via roster-based module mapping
- Tree depth locked at 3 levels (repo -> package -> edge-type)

### Added

#### Extractors (6 -> 17 languages)
- Protobuf/gRPC extractor: service, message, enum, RPC declarations with type reference edges
- Event/MQ extractor: Kafka, NATS, SQS, RabbitMQ patterns across Go/TS/Python/Java
- Schema extractor: OpenAPI 3.x, Swagger 2.x, JSON Schema document parsing
- Cloud extractor package: CloudFormation/SAM, Docker Compose, GitHub Actions, Serverless Framework
- Terraform HCL extractor: resources, data sources, modules, variables with dependency edges
- SQL extractor: tables, views, functions, procedures with FK/reference edges
- K8s YAML extractor: deployments, services, configmaps with label-selector edges
- CSS extractor: class/ID selectors, custom properties, var() dependency edges
- Python: Flask, FastAPI, Django route detection
- TypeScript: Fastify, Hono, NestJS, Next.js route detection
- FindAllExtractors multi-dispatch: all matching extractors run per file (not just first)
- All 25 extractors registered in CLI (includes 7 new infrastructure extractors: Dockerfile, Makefile, Helm, GitLab CI, package.json/npm, GraphQL, Ansible)

#### SCIP Ingest
- `internal/indexer/scipingest/` package: parses SCIP protobuf index files
- `knowing ingest-scip` CLI command for external dependency resolution
- Provenance `scip_resolved` at confidence 0.95

#### Context Engine
- HITS (Hyperlink-Induced Topic Search) reranking on RWR subgraph
- Density-ranked knapsack packing: score/cost ratio optimization for token budgets
- 5-tier seeding: exact, prefix, substring, file-path matching, interface-aware
- FeedbackProvider interface wired into ContextEngine with centered scoring
- Community-scoped RWR preparation (interface defined, activates when store implements)
- Random Walk with Restart (RWR) algorithm for graph-based relevance scoring
- Improved keyword extraction with stop word filtering, CamelCase splitting, abbreviation expansion
- Relative normalization in ranking and base recency score for static-only edges

#### MCP Server (16 -> 22 tools)
- `knowing mcp` subcommand for stdio MCP server mode
- `feedback` tool: record/query symbol usefulness for agent learning loop
- `test_scope` tool: backward BFS from changed symbols to affected test functions
- `flow_between` tool: BFS path finding between two symbols (up to 10 paths)
- `plan_turn` tool: keyword-based task-to-tool recommender with pre-filled arguments
- `communities` tool: Louvain modularity clustering with `list` and `for_symbol` actions
- `context_for_pr` tool (17th tool, added earlier in session)
- 3 MCP prompts: `refactor_safely`, `review_pr`, `investigate_dead_code`

#### Wire Format
- Graph Compact Format (GCF): line-oriented LLM-optimized encoding (84% token savings vs JSON)
- Graph Compact Binary (GCB): varint-encoded transport format (74% byte savings vs JSON)
- Session statefulness: cross-call deduplication (47% dedup on repeated symbols)
- Round-trip integrity: encode -> decode -> re-encode for all codecs

#### Benchmarks (6 harnesses with auto-generated FINDINGS.md)
- `bench/feedback-loop/`: precision 16% -> 36% (+20pp) with feedback compounding
- `bench/context-relevance/`: 3 configs x 10 fixtures, feedback adds +9pp precision
- `bench/token-savings/`: 52.8% fewer tool calls, 55.6% fewer tokens vs manual grep
- `bench/edge-accuracy/`: tree-sitter vs go/ast comparison (26.7% confirmation, 53.6% imports)
- `bench/test-scope-accuracy/`: predictions vs Go import DAG ground truth (98.9% precision)
- `bench/wire-format/`: GCF 84% token savings, GCB 74% byte savings across 6 fixtures

#### CLI
- `knowing test-scope`: find affected tests from changed files via call graph BFS
- `knowing init`: auto-generated CLAUDE.md with progressive disclosure
- `knowing export -format dot`: Graphviz DOT with Louvain community subgraphs
- `knowing reindex`: rebuild graph without full re-extraction
- Community-annotated JSON export: nodes include `community` ID, edges include `cross_community` flag

#### Infrastructure
- `KNOWING_DB` env var for global database path (all subcommands)
- Global MCP config support in ~/.claude.json (knowing available in every Claude session)
- Claude Code hooks with A/B measurement harness (proven net-positive after benchmarking)
- Docker image publishing in goreleaser config
- PyPI and npm distribution packages
- mcp-assert CI action for MCP server correctness testing
- `NodesByFilePath` store method (joins nodes to files via SQL)
- Migration 005: feedback table for persistent symbol usefulness tracking
- `DeleteSnapshot` for real garbage collection

### Fixed

- `test-scope` command: `symbolsInFiles` returning empty results (stale FileHash mismatch)
- `test-scope` command: package path extraction producing invalid `go test` paths
- Context engine `ForFiles`/`ForPR` broken with stale FileHash matching (now uses NodesByFilePath)
- HITS node selection on random map iteration order (now sorted by RWR score first)
- Context engine exact match requirement (now uses substring search)
- K8s extractor not matching `kubernetes-manifests/` directory names (was exact `/kubernetes/`)
- All subcommands now use KNOWING_DB env var (mcp.go was still hardcoded)
- 9 extractors were dead code (registered but never called due to first-match dispatch)
- Duplicate `extractPackage` helper in testscope.go and communities.go
- Community label deduplication (Louvain producing 3 "mcp" communities)
- Indexer cleans up nodes/edges from deleted files
- Duplicate nodes from mismatched repo URL vs go.mod module path
- Architecture doc updated to reflect actual codebase structure
- All 6 benchmark harnesses audited: stale FINDINGS data corrected, circular ground truth replaced with independent Go import DAG, missing FINDINGS.md generated, misleading interpretations rewritten

### Changed

- Extractors: 6 -> 17 languages (Go, Python, TS/JS, Rust, Java, C#, Terraform, SQL, K8s, CSS, Proto, Event/MQ, Schema, CloudFormation, Docker Compose, GitHub Actions, Serverless)
- MCP server: 16 -> 22 tools
- Wire format renamed from KWF/KWB to GCF/GCB (Graph Compact Format/Binary)
- Default hooks now recommended (proven net-positive with benchmarks)

## 2026-05-15

### Added

#### Core Graph Engine
- Content-addressed knowledge graph with Merkle DAG snapshots (SHA-256 node/edge/root hashes)
- SQLite-backed GraphStore with WAL mode, 20+ methods, recursive CTEs for transitive queries
- 4 schema migrations (initial, dangling edges, call-site columns, runtime observation columns)
- Append-only edge event log with "added"/"removed" recording on every index run
- Snapshot chain with parent pointers, Merkle root computation, diff, and garbage collection
- Content-addressed file identity for rename survival and deduplication
- Deterministic reindexing (same input produces identical snapshot hashes)

#### Incremental Change Detection
- Git-based change detection: watches `.git/HEAD` and `.git/refs/heads/*` (1-2 file descriptors)
- `GitDiffFiles` resolves changed/added/deleted files via `git diff --name-status`
- Old symbol cleanup: `DeleteNodesByFile` and `DeleteEdgesBySourceFile` remove stale data before re-extraction
- Edge event recording: computes diff between old and new edges per file, writes to edge_events table
- Scoped enrichment: LSP enrichment processes only edges from changed files
- Snapshot-commit alignment: every snapshot corresponds to a single commit

#### Language Extractors
- Go tree-sitter extractor (default fast path): declarations, imports, call edges with positions, confidence 0.7
- Go packages extractor (`--full` flag): full type resolution via `go/packages`, confidence 1.0
- Python tree-sitter extractor: functions, classes, methods, imports, calls
- TypeScript/JavaScript extractor: Express.js route detection
- Rust extractor: Actix, Axum, Rocket route detection
- Java extractor: Spring annotation route detection
- C# extractor: ASP.NET attribute route detection
- HTTP route detection for 10+ framework patterns (net/http, chi, gin, echo, gorilla/mux, Express, Actix, Axum, Rocket, Spring, ASP.NET)
- Worker pool parallelism (`runtime.GOMAXPROCS` goroutines, order-preserving fan-out/fan-in)

#### LSP Enrichment
- Two-tier extraction: tree-sitter for instant graph (~1.5s), LSP for accuracy (background)
- Enrichment via `agent-lsp/pkg/lsp` starts gopls, opens all Go files, upgrades edges to `lsp_resolved` (0.9 confidence)
- Call-site positions (line, column, file) stored on edges for LSP confirmation
- Discovery of `implements` and `references` edges via document symbols
- Cold index benchmark: 9.1 seconds (108x faster than go/packages baseline of 16m 24s)

#### Cross-Repo Resolution
- `internal/resolver/` package for retargeting dangling edges across repositories
- 4 GraphStore methods: `DanglingEdges`, `AllRepos`, `NodesByQualifiedName`, `DeleteEdge`
- `ModuleToRepoURL` map populated from go.mod of indexed repos
- Verified: 228 cross-repo edges between polywave-web and polywave-go

#### Runtime Trace Ingestion
- OTel trace pipeline: `TraceSpan` normalization, span-to-edge conversion, batch accumulation
- OTLP gRPC receiver (`collectortrace.TraceServiceServer`) on configurable endpoint
- Symbol resolver: maps HTTP routes and gRPC methods to graph node hashes via `route_symbols` table
- Observation-based confidence scoring: 0.95 (>1000 obs), 0.85 (100+), 0.7 (10+), 0.5 (1+), 0.2 (stale)
- Confidence decay over time without re-observation; GC-eligible after 90 days
- Batch accumulation with configurable flush interval
- Daemon `traceIngestLoop` goroutine with periodic flush and decay
- Migration 004: `observation_count`, `last_observed` columns on edges; `route_symbols` table

#### Semantic PR Diff
- `internal/diff/` package: `SemanticDiff` (enriches snapshot diff with node metadata, detects modifications)
- `PRImpact`: blast radius for changed symbols, risk classification (low/medium/high), transitive callees (depth 3)
- GitHub Action (`pr-semantic-diff.yml`): indexes both branches, computes diff, posts/updates PR comment

#### Graph-Aware Context Packing
- `internal/context/` package: `ContextEngine` with task-based and file-based context queries
- Random Walk with Restart for relevance scoring from seed nodes
- Token-budgeted output in XML, Markdown, or JSON format
- Ranking by blast radius, confidence, recency, and graph distance
- Keyword extraction with stop word filtering and CamelCase splitting

#### Developer CLI
- `knowing index` (default: tree-sitter fast path; `--full`: go/packages)
- `knowing serve` (daemon with MCP server, git watcher, optional `--trace` for OTel ingestion)
- `knowing diff` (semantic PR diff with JSON and human-readable output)
- `knowing export` (full graph dump for visualization, `--format json`, `--repo` filter)
- `knowing context` (`--task` or `--files`, `--budget`, `--format`)
- `knowing query` (symbol search by qualified name prefix)
- `knowing mcp` (stdio MCP server for AI agent integration)
- `knowing version`

#### MCP Server (16 tools over stdio + HTTP)
- Execution plane: `index_repo`, `cross_repo_callers`, `graph_query`, `repo_graph`
- Intelligence plane: `blast_radius`, `trace_dataflow`, `stale_edges`, `snapshot_diff`, `semantic_diff`, `pr_impact`, `ownership`
- Runtime plane: `runtime_traffic`, `dead_routes`, `trace_stats`
- Context plane: `context_for_task`, `context_for_files`

#### Infrastructure
- CI workflow (`.github/workflows/ci.yml`): build, vet, test on push/PR
- Release workflow (`.github/workflows/release.yml`): GoReleaser with 6 platform binaries
- Docs workflow (`.github/workflows/docs.yml`): mkdocs-material to GitHub Pages
- GoReleaser v2 config: Homebrew formula, Docker multi-arch images, npm/PyPI/Winget publishing
- Distribution strategy: Homebrew, Scoop, Winget, npm, PyPI, Docker (GHCR + Docker Hub), go install, curl|sh

#### Documentation
- Architecture doc with 15 design decisions, concepts section, concurrency model, data flow
- FEATURES.md: 30 features with packages, entry points, limitations
- CLI reference (`docs/CLI.md`): all subcommands with flags and examples
- MCP tools reference (`docs/MCP-TOOLS.md`): all 16 tools with parameters and return formats
- Distribution strategy (`docs/DISTRIBUTION.md`)
- Runtime trace design (`docs/runtime-traces.md`)
- Implementation log (`docs/implementation-log.md`)
- Deployment models (`docs/deployment.md`)
- Package-level and exported-symbol doc comments across all 18 packages

### Fixed

- `ComputeNodeHash` no longer includes contentHash in hash computation (was causing cross-package caller queries to return empty)
- GoExtractor uses `types.EmptyHash` consistently for node hash computation
- `File.ContentHash` correctly set to `sha256(file_contents)` instead of FileHash
- MCP `handleOwnership` uses NodesByName grouping instead of nonexistent "contains" edges
- Cross-repo resolver: module path vs filesystem path mismatch in repo URL resolution
- Enrichment: removed broken per-edge upgrade path (declaration position != call-site position)
- File walker: skip `.claude` and `testdata` directories to prevent 3x node inflation
- Enrichment: open all files via `textDocument/didOpen` before cross-package LSP queries

### Changed

- Default indexing switched from go/packages (16 min) to tree-sitter + LSP (9 seconds)
- Daemon uses GitWatcher (commit-driven) instead of FileWatcher (filesystem-event-driven)
- MCP server expanded from 11 to 16 tools
- IndexRepo records edge events and cleans up stale nodes/edges before re-extraction

## 2026-05-14

### Added

- Separate roadmap document (`docs/roadmap.md`) with parallel workstreams and dependency constraints
- Storage interface (`GraphStore`) for backend swappability
- Three-tier traversal cache design (L1 LRU, L2 materialized closures, L3 bounded traversal)
- Runtime trace ingestion architecture design
- Semantic PR diff design
- `TraceIngestor` interface for normalizing observability data into graph edges
- `SemanticDiffResult`, `BlastRadiusDelta`, `OwnershipDelta` types for PR impact analysis

### Changed

- Removed "v0" hedging language; architecture treats full system as the target
- README roadmap slimmed to summary table linking to full roadmap doc

## 2026-05-13

### Added

- Content-addressed architecture document (`docs/architecture.md`) with 11 foundational design decisions
- Merkle DAG graph model: node hashes, edge hashes, snapshot root hashes
- Symbol identity scheme (`{repo}://{module_path}/{package_path}.{TypeName}.{MemberName}`)
- Append-only edge log with event sourcing
- Edge provenance model with confidence tiers
- Content-addressed file identity for rename survival
- Causal ordering via Lamport timestamps
- Schema migration framework (embedded numbered SQL migrations)
- Deterministic reindexing rules
- SQLite storage decision with full schema
- Daemon process model with MCP transport (stdio and HTTP)
- Brand assets: banner PNG and social preview JPG

## 2026-05-12

### Added

- Initial README: problem statement, core idea, cross-boundary edge types
- Positioning, roadmap, and comparison sections
