Skip to content

feat(kb): project-scoped serving (SP-1) — serve the active project's knowledge first#22

Merged
Cain-Ish merged 6 commits into
mainfrom
feat/sp1-project-scoped-serving-impl
Jun 3, 2026
Merged

feat(kb): project-scoped serving (SP-1) — serve the active project's knowledge first#22
Cain-Ish merged 6 commits into
mainfrom
feat/sp1-project-scoped-serving-impl

Conversation

@Cain-Ish

@Cain-Ish Cain-Ish commented Jun 3, 2026

Copy link
Copy Markdown
Owner

SP-1 — Project-scoped serving

When a project is active, knowledge_search now 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):

  • T1 — same project: facet, plus the active project's own local-docs (registry pages are tier-1 by construction)
  • T2 — the graph-neighbourhood of the project's anchor pages (SB_SCOPE_HOPS, default 2, range 0–4)
  • T3 — shared / no-facet pages
  • T4 — other project

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_search entry point. knowledge-search-cli forwards SB_ACTIVE_SLUG/BRAIN_DIR; persona-context.sh (per-prompt) and session-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

  • Build unblock: the 0.24.7 EDGE_TYPES source-of-truth migration left server.ts casting the zod edge enum to [string, …], which widened the knowledge_relate/knowledge_neighbors arg types back to string and broke tsc (and thus tsc && bundle) — silently, since the committed dist/ was stale. Fixed to the [EdgeType, …] tuple.
  • Deep-review hardening (gate found these before merge):
    • C1 (critical): the slug→project map + anchors are now built from wiki docs only, so a local-doc (project:'') never overwrites a same-basename wiki page's project and leaks that other-project page into scope. Local-docs are tiered T1 directly.
    • W1: session-load.sh now scopes the session-start wiki enrichment too (was global while per-prompt was scoped — inconsistent surfaces).
    • slug read in persona-context.sh is whitespace-stripped to match the server's resolveActiveSlug().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 the second-brain:code-review-deep gate (the findings above were surfaced and fixed; no remaining confirmed issues).

🤖 Generated with Claude Code

Cain-Ish and others added 6 commits June 3, 2026 08:50
…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>
Copilot AI review requested due to automatic review settings June 3, 2026 10:42
@Cain-Ish Cain-Ish merged commit 63739fa into main Jun 3, 2026
2 checks passed
@Cain-Ish Cain-Ish deleted the feat/sp1-project-scoped-serving-impl branch June 3, 2026 10:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_DIR through knowledge-search-cli and hook scripts (persona-context.sh, session-load.sh) and add a shell guard/functional test for the CLI scoping.
  • Fix server.ts zod 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants