feat(kb): project-scoped serving (SP-1) — serve the active project's knowledge first#22
Merged
Merged
Conversation
…ask 1) Tier model (project / graph-neighbourhood / shared / other-project): in project A, serve A + shared first, drop other-project pages unless in-scope hits < SB_SCOPE_MIN_HITS (default 3) then auto-broaden. Reuses the graph for the neighbourhood. Kill switch SB_PROJECT_SCOPE=off; scope:'all' override; no projectSlug -> unchanged (back-compat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SP-1 Task 2) Wire SB_ACTIVE_SLUG/BRAIN_DIR through knowledge-search-cli so the per-prompt persona injection is project-scoped; persona-context.sh reads the active slug from .active-session-slug and exports it on the search-CLI invocation. Also unblocks the build: the 0.24.7 EDGE_TYPES migration left server.ts casting the zod enum to [string, ...] which widened the relate/neighbors arg types back to string and broke 'tsc' (and thus 'tsc && bundle'). Cast to the EdgeType tuple so the union is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- C1 (critical): build the slug->project map and anchors from WIKI docs only, and tier local-docs as the active project's own files (tier 1). Previously a local-doc (project:'') with the same basename as an other-project wiki page overwrote that page's project in the map, leaking the other-project page into scope — the exact noise SP-1 prevents. New regression test. - W1: session-load.sh now forwards SB_ACTIVE_SLUG/BRAIN_DIR to the search CLI, so the session-start wiki enrichment is scoped the same as the per-prompt path (one chokepoint, consistent both surfaces). Guarded in test-search-cli-scope.sh. - persona-context.sh: strip whitespace from the pinned slug to match the server's resolveActiveSlug() .trim() contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on row Plugin 0.24.8 -> 0.24.9, MCP server 2.6.3 -> 2.6.4. SP-1 makes knowledge_search serve the active project's knowledge first (tiered, scoped-first, auto-broaden), scoped consistently across the per-prompt and session-start injection surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Implements SP-1 “project-scoped serving” by re-ranking knowledge_search results to prefer the active project (and its neighbourhood/shared layer) while preserving back-compat via kill switches (SB_PROJECT_SCOPE=off, scope:"all") and auto-broaden when in-scope hits are thin. It also wires the active project slug through the CLI + hooks so both session-start and per-prompt wiki enrichment share the same scoping behavior, and fixes a TypeScript type-widening issue for graph edge enums that was breaking tsc.
Changes:
- Add project-scoped tiering + scoped-first ordering + auto-broaden behavior to
knowledgeSearch, plus vitest coverage for SP-1 scenarios. - Forward
SB_ACTIVE_SLUG/BRAIN_DIRthroughknowledge-search-cliand hook scripts (persona-context.sh,session-load.sh) and add a shell guard/functional test for the CLI scoping. - Fix
server.tszod enum typing for edge types, bump plugin/server versions, and update upgrade migration docs.
Reviewed changes
Copilot reviewed 12 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test-search-cli-scope.sh |
Adds a shell test to assert the CLI/hooks forward project context and that scoping suppresses other-project pages. |
skills/upgrade/SKILL.md |
Adds the 0.24.9 migration row documenting SP-1 behavior, switches, and the build/type fix. |
scripts/session-load.sh |
Forwards SB_ACTIVE_SLUG and BRAIN_DIR to the search CLI for session-start wiki enrichment. |
scripts/persona-context.sh |
Reads and whitespace-trims the pinned active slug and forwards it to the search CLI for per-prompt wiki injection. |
mcp/src/tools/knowledge-search.ts |
Implements tiering (T1–T4), scoped-first sorting, and auto-broaden gating within knowledgeSearch. |
mcp/src/tools/knowledge-search.test.ts |
Adds SP-1 vitest cases covering scoped suppression, auto-broaden, neighbourhood behavior, and kill switches. |
mcp/src/tools/knowledge-search-cli.ts |
Forwards SB_ACTIVE_SLUG/BRAIN_DIR into knowledgeSearch so CLI output is scoped. |
mcp/src/server.ts |
Bumps MCP server version and fixes EDGE_TYPES zod enum typing to preserve EdgeType literal unions. |
mcp/dist/tools/knowledge-search.test.js.map |
Rebuilt dist sourcemap for updated SP-1 tests. |
mcp/dist/tools/knowledge-search.test.js |
Rebuilt dist test output including SP-1 test suite. |
mcp/dist/tools/knowledge-search.js |
Rebuilt dist output including SP-1 scoping logic. |
mcp/dist/tools/knowledge-search.d.ts.map |
Rebuilt dist typings sourcemap for updated knowledge-search. |
mcp/dist/tools/knowledge-search-cli.js.map |
Rebuilt dist sourcemap for CLI scoping wiring. |
mcp/dist/tools/knowledge-search-cli.js |
Rebuilt dist CLI output forwarding brainDir/projectSlug. |
mcp/dist/tools/knowledge-search-cli.bundle.js |
Rebuilt bundled CLI including scoping wiring and knowledge-search changes. |
mcp/dist/server.js.map |
Rebuilt dist sourcemap reflecting server version/type changes. |
mcp/dist/server.js |
Rebuilt dist server output with version bump. |
mcp/dist/server.bundle.js |
Rebuilt server bundle reflecting edge-type enum sourcing and version bump. |
mcp/dist/cli/sb-entry.bundle.js |
Rebuilt CLI bundle including updated knowledge-search logic. |
docs/specs/2026-06-03-project-scoped-serving-design.md |
Adds SP-1 design spec describing tier model, knobs, and expected behavior. |
docs/plans/2026-06-03-project-scoped-serving.md |
Adds implementation plan outlining TDD steps and wiring changes. |
.claude-plugin/plugin.json |
Bumps plugin version to 0.24.9. |
.claude-plugin/marketplace.json |
Bumps marketplace version to 0.24.9. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+283
to
+293
| const projBySlug = new Map( | ||
| allDocs.filter(d => d.source === 'wiki').map(d => [slugFromPath(d.doc.path), d.doc.project ?? ''])); | ||
| const anchors = allDocs.filter(d => d.source === 'wiki' && (d.doc.project ?? '') === slug) | ||
| .map(d => slugFromPath(d.doc.path)); | ||
| const neigh = graphNeighbourhood(anchors, graphEdges, clampEnvInt('SB_SCOPE_HOPS', 2, 0, 4)); | ||
| for (const s of scored) { | ||
| if (s.source === 'local-doc') { s.tier = 1; continue; } // active project's own registry pages | ||
| const sl = slugFromPath(s.path); | ||
| const proj = projBySlug.get(sl) ?? ''; | ||
| s.tier = proj === slug ? 1 : neigh.has(sl) ? 2 : proj === '' ? 3 : 4; | ||
| } |
Comment on lines
+34
to
+35
| | **T2 — neighbourhood** | within `K` graph hops (default `K=2`) of any T1 page **or** the active `PROJECT.md`'s `[[cross-references]]` | catches A-relevant pages that aren't facet-tagged yet (the bridge while facets are sparse) | | ||
| | **T3 — shared** | `doc.project === ''` (no facet) and not in any other project's neighbourhood | genuinely cross-cutting knowledge (general learnings, security doctrine, …) | |
|
|
||
| `activeSlug` is resolved exactly as the MCP server already does (`resolveActiveSlug()` → pin or `slugFromProjectDir(activeProjectDir())`), so SP-1 introduces no new project-resolution logic. | ||
|
|
||
| **Neighbourhood (T2)** is computed from the existing typed graph (`graph-store`): seed = T1 slugs ∪ PROJECT.md cross-reference slugs; expand `K` hops over current (`valid_to == null`) edges. This reuses the graph the search already loads for its relevance boost — no extra I/O beyond reading the edge log once. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SP-1 — Project-scoped serving
When a project is active,
knowledge_searchnow serves that project's knowledge first instead of the whole wiki — killing the cross-project noise the per-prompt persona injection used to feed Claude. This is SP-1 of the consolidation vision (project A → project-A knowledge; switch to B → B's; shared/common pages still surface; don't create noise).How it works
Each candidate is tagged a tier, then results are sorted scoped-first (tier ascending, then score descending):
project:facet, plus the active project's own local-docs (registry pages are tier-1 by construction)SB_SCOPE_HOPS, default 2, range 0–4)Auto-broaden: if fewer than
SB_SCOPE_MIN_HITS(default 3) in-scope hits clear the score floor, fall back to the full global pool — so a legitimately cross-project answer is never starved.One chokepoint, both surfaces: scoping rides on the single
knowledge_searchentry point.knowledge-search-cliforwardsSB_ACTIVE_SLUG/BRAIN_DIR;persona-context.sh(per-prompt) andsession-load.sh(session-start) export the active slug — so cold-start and per-prompt context scope identically.Kill switches
SB_PROJECT_SCOPE=off·scope:"all"·SB_SCOPE_MIN_HITS·SB_SCOPE_HOPS. With no active project (or any kill switch), the ranking is byte-for-byte the prior global search — additive + back-compat.Also in this PR
EDGE_TYPESsource-of-truth migration leftserver.tscasting the zod edge enum to[string, …], which widened theknowledge_relate/knowledge_neighborsarg types back tostringand broketsc(and thustsc && bundle) — silently, since the committeddist/was stale. Fixed to the[EdgeType, …]tuple.project:'') never overwrites a same-basename wiki page's project and leaks that other-project page into scope. Local-docs are tiered T1 directly.session-load.shnow scopes the session-start wiki enrichment too (was global while per-prompt was scoped — inconsistent surfaces).persona-context.shis whitespace-stripped to match the server'sresolveActiveSlug().trim()contract.Versions
Plugin 0.24.8 → 0.24.9; MCP server 2.6.3 → 2.6.4; migration row added.
Tests
6 SP-1 vitest cases (incl. local-doc tier-1 + the same-basename leak guard) +
tests/test-search-cli-scope.sh. Full suite green (77 bash / 0 fail / 1 known skip; 389 vitest). Passed thesecond-brain:code-review-deepgate (the findings above were surfaced and fixed; no remaining confirmed issues).🤖 Generated with Claude Code