Load relevant stored memories and codemap entries for the current task. Use when the user types /discover, asks to "load context," or a task is starting with stored context unsurfaced. Also callable by other skills (prep, review).
Scanned 9/6/2026
Install to Claude Code
npx -y skills add AcKeskin/contexture --skill discover --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Discover?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ackeskin-discover)More formats (shields.io, HTML) on the badges page.
---
name: discover
description: Load relevant stored memories and codemap entries for the current task. Use when the user types /discover, asks to "load context," or a task is starting with stored context unsurfaced. Also callable by other skills (prep, review).
---
# discover
Unified retrieval layer. Aggregates stored memory fragments (what we've decided/learned) and codebase context (what the code actually looks like) into the current session.
## When to run
- User types `/discover` (explicit trigger).
- User says "load context," "prep yourself," "what do you know about X."
- Another skill invokes this one (prep, review).
- Do **not** auto-fire at session start — explicit invocation only.
## Inputs
1. **Task text.** The most recent user message, plus any arguments passed after `/discover`.
2. **Project root.** `$CLAUDE_PROJECT_DIR` if set, otherwise the current working directory.
3. **Optional caller filters** (when invoked by another skill, not by a user): `scopes`, `kind`, `relevance_phases`. See "Query interface" below.
## Architecture — MCP-primary, skill-fallback
Retrieval is **MCP-primary**: memory + recap + codemap retrieval is owned by the **`project-memory` MCP engine**, out of context. This skill is a **thin client** over it, with a **static always-on floor** when the engine is unreachable.
Two responsibilities stay in this skill, NOT the MCP:
- **Rules-overlay resolution (§4a)** — the rules-overlay tier/patch/disable subsystem is a separate module; v1 keeps it skill-resident.
- **The fallback** — when the MCP is down, surface the static floor (§0b). The skill does **not** re-implement the full ranking as its primary path.
### 0. Primary path — query the MCP engine
Call `mcp__project-memory__discover` with the task-derived query:
```
mcp__project-memory__discover({
cwd: <project root>, // the engine resolves the layered tier
include_codemap: true,
... // remaining params per "Query interface" below
})
```
The engine returns ranked memory fragments (with `📛` warning prefixes, `[related_to]` / `⚡ contradicts` flags, scope/relevance metadata), plus a `## Codemap` block, plus any `⚠️` shadowed-tier / case-split warnings. Surface that result directly (§9 shaping is mostly done by the engine). Then run §4a (rules-overlay) and merge: overlay-resolved rules are appended to the engine's result list and ranked by the same signals the engine reports (scope match, then kind priority, then description keyword overlap with the task); ties break toward the overlay rule, since a tier override outranks a shipped default. The merged list honours the caller's top_n as a single combined cap — overlay rules never bypass the cap. Deliver then renders the merged list's bodies exactly as in the degraded path (§9a applies to engine-returned bodies too).
**Detecting MCP-unavailable:** the tool call errors, returns "No memory tree found" with no result, or the `project-memory` server is not connected. On any of these → fall through to §0b. Do not retry in a loop.
### 0b. Fallback — static always-on floor
When the engine is unreachable, do **not** silently re-run the full in-context scan as if nothing happened. Surface the **static floor** and say the engine is down:
1. Read `~/.claude/projects/<slug>/memory/MEMORY.md` (the fallback index).
2. Surface only the `relevance: always` entries (the load-bearing always-applicable set) plus any `kind: warning` entries whose scope plausibly matches the task.
3. Tell the user: *"project-memory MCP unavailable — showing the always-on floor only; ranked retrieval resumes when the MCP reconnects (restart the session or `/mcp reconnect`)."*
The richer in-context scan (§1–§9 below) remains documented as the **degraded deep-scan** the skill MAY run if the user explicitly asks for full retrieval while the MCP is down — but it is not the default fallback. Default fallback = the cheap static floor.
---
## Procedure (degraded deep-scan / reference contract)
*Contract-of-record for the engine; run in-context only on explicit request while the MCP is down (§0 supersedes for normal operation).*
### 1. Resolve the project memory directory
Use Glob to find: `~/.claude/projects/*/memory/MEMORY.md`.
Pick the match whose path contains the project root directory name (case-insensitive, separator-insensitive).
If no match → report "no project memory found for <cwd>" and proceed with whatever global context is already loaded via `~/.claude/CLAUDE.md`. Do not try to derive the slug manually — Claude Code's slug convention is not documented and attempts to reconstruct it are fragile.
### 2. Read MEMORY.md
Read the full file. It is the index: one line per entry, format `- [Title](path) — one-line hook`.
### 3. Extract task keywords
From the task text plus any `/discover` arguments, extract keywords:
- Lowercase, split on whitespace and punctuation.
- Drop common stopwords (`the`, `a`, `an`, `to`, `of`, `and`, `or`, `is`, `for`, `with`, `from`, `on`, `in`, `at`, `by`, `as`, `this`, `that`, `it`, `i`, `you`).
- Keep nouns, verbs, names. Keep technical terms even if short (`api`, `jwt`, `ui`).
### 4. Score candidates from the index
For each MEMORY.md entry, compute a first-pass score:
- `keywords_hit` = count of task keywords appearing in the title or hook.
- If `keywords_hit == 0` and the entry is not tagged `relevance: always` (unknown at this stage — flag for second pass), drop from primary candidates.
Keep entries with `keywords_hit ≥ 1` as **primary candidates**.
### 4a. Architectural-rule overlay resolution
When the candidate set includes `kind: architectural-rule` files — or the caller passed `kind: "architectural-rule"` (the prep / review path) — resolve them through the **tier-aware overlay** before scoring. This step is a no-op for memory / recap / codemap candidates; it only touches rule files.
The resolution algorithm (manifest precedence, tier walk, replace/patch modes, disables) lives in [`docs/architectural-rules-overlay.md`](../../docs/architectural-rules-overlay.md) § "Resolution algorithm (owned by discover)", implemented as the hook-callable `hooks/lib/resolve-rules.js` subroutine the rule-prime hook calls — same semantics, no model turn. Run it, then **strip `<!-- id: ... -->` anchors** from every resolved body — they are patch targets, never context (deliver also strips them, defense-in-depth). The surviving, stripped, patched files become the candidate set for §5 scoring. Carry per-file resolution metadata (winning tier, patch deltas, orphaned anchors, locked-divergence) into §9 so the report can annotate **non-default** rules only. If no manifest exists and no overlay dirs are present, this step resolves to "shipped tier only" — the overlay is inert until populated.
**Scope-resolution corpus source.** A resolver `corpus-source` from §6 selects *which corpus* the overlay walks: `submodule` → the submodule's own `<submodule>/.claude/architectural-rules/` layered as the most-local tier (above project, last-write-wins by relative path, per the [resolver merge order](../../docs/scope-resolution-resolver.md)); `parent` → the enclosing tiers scoped to the submodule's tags; `none` → **short-circuit: load no rules for this subtree**. Absent a resolver input → the standard tier walk, unchanged.
### 5. Read frontmatter of primary candidates
For each primary candidate, Read the file and parse frontmatter. Extract `scope`, `relevance`, `kind`, `relations`, `superseded_by`.
**Suppress superseded memories.** If `superseded_by:` is set, drop the candidate from the surfaced set unless the caller passed it by explicit name (e.g. `task_keywords` includes the candidate's `name` field exactly). This keeps superseded entries inspectable when asked for, hidden from normal discovery.
Compute a second-pass score per candidate:
- `+2 × |task_scopes ∩ scope|` (tag overlap is the strongest signal; task_scopes come from keyword → scope guess, see §6)
- `+1` if `relevance` includes `always`, or matches the current phase
- `+1 × keywords_hit` (already computed)
- `+2` if `kind == "warning"` (warnings are landmines — surface them ahead of generic lessons when scope matches)
If a caller passed `scopes` / `kind` / `relevance_phases` filters, apply them as hard filters (drop non-matching entries) before scoring.
Sort candidates by score descending. Keep top 8. Cap adjustable by caller.
### 5a. Single-hop relation expansion
For each candidate that survived §5 scoring, follow its `relations:` entries one hop:
| relation | action |
|---|---|
| `supersedes` | already filtered by §5 superseded suppression; do not re-pull the target. |
| `contradicts` | pull the target even if it didn't independently match; flag the pair `[contradicts]` in the report so the user can reconcile. |
| `supports` | if the target is already in the surfaced set, bump its score `+1`; if not, do *not* pull it in — a weight bonus, not a magnet. |
| `related_to` | soft pointer: pull the target with a `[related_to <source>]` flag. No transitive expansion — single-hop only. |
Cap relation-pulled additions at 3 per source candidate; beyond that keep the first 3 by `relations` order and note `[N more relations not expanded]` in the report.
After 5a, re-sort the surfaced set by score (relation-pulled entries take their source's score minus 1 unless they had their own §5 score, in which case the higher of the two).
### 6. Inferring task scopes
**Resolver-provided scopes are hard inputs.** When the caller (prep) ran the [scope-resolution resolver](../../docs/scope-resolution-resolver.md) for the task path, treat its resolved scopes as hard scope inputs (a task under a `go` submodule carries the `go`/region scopes, not the sibling region's) and pass its `corpus-source` to §4a. **Absent manifests → no resolver input → infer scopes exactly as below (no regression).**
From the task text, infer candidate scope tags (seeded by any resolver-provided scopes):
- Strong hints: filenames or module names mentioned (`src/auth/` → scope `auth`).
- Medium hints: domain words (`login`, `password`, `session` → scope `auth`).
- Fall back to `[global]` only if nothing specific surfaces.
Use discovered scopes as the matching set in §5. When uncertain, include `[global]` so universally-scoped entries are never excluded.
### 7. Keyword fallback (only if §4–§5 returns thin)
If fewer than 2 entries survive with a score ≥ 2, widen:
- Grep the body of every memory file for task keywords.
- Any file with ≥ 2 keyword hits is a low-confidence match.
- Flag these as "possibly relevant" in the report, separate from the high-confidence matches.
### 8. Read the codemap (if present)
Check `<project-root>/.claude/codemap.md`. If missing, skip §8 silently (the project may not have a codemap yet).
If present:
- Read and identify files/exports matching the inferred scopes and task keywords.
- Note the codemap's last-modified timestamp.
### 8a. Scan session recaps
Path: `<memory-root>/sessions/` (the project memory resolved in §1).
Skip silently if the folder does not exist — the project may have no recaps yet.
Scan filenames matching `YYYY-MM-DD-<slug>.md`. Parse the date.
Apply the **30-day auto-surface cutoff**: recaps with `date` within the last 30 days are auto-candidates; older recaps only if the task text explicitly names a matching project slug, branch, or date (e.g. "the auth branch from March", "last week's recap").
For each surviving recap, Read frontmatter. Score:
- `+2 × |task_scopes ∩ scope|` — scope overlap (same weight as other candidates).
- `+1 × keywords_hit` on `name` / `description`. Also scan the `Learned` section headings for bonus keyword hits.
- **Recency bonus:** `+2` if `date` is within 7 days; `+1` if 8–30 days.
Scored recaps join the §9 candidate set; when bodies are rendered (§9a) they deliver as tier `session-recap`. Recaps are opt-in: callers pass `include_recaps: true`; the default is false and §8a is skipped entirely.
### 9. Report
Report to the user in this shape:
```
Loaded N memories + R recaps + M codemap entries (codemap age: X days):
Memories (high confidence):
- 📛 [Warning Title] (scope: <tags>, relevance: <phase>)
- [Title] (scope: <tags>, relevance: <phase>)
- [Title] [related_to <source>] (scope: <tags>, relevance: <phase>)
- [Title-A] ⚡ contradicts [Title-B] (both surfaced — needs reconciliation)
- ...
Memories (possibly relevant):
- [Title] (scope: <tags>, relevance: <phase>)
- ...
Session recaps:
- [Title] (date: <YYYY-MM-DD>, scope: <tags>)
- ...
Codemap:
- path — summary
- ...
No stored context found for: [list of task aspects with no matches].
Want to provide context, or should I proceed with what I have?
```
Display rules:
- `kind: warning` entries get the `📛` prefix and appear at the top of their tier.
- `[related_to <source>]` annotations explain why a fragment surfaced via single-hop expansion (the user can tell direct match from relation pull at a glance).
- `⚡ contradicts <other>` flags pairs where the user needs to reconcile two memories that disagree.
Overlay annotations (architectural-rule candidates only) — **non-default rules only**, a plain shipped rule shows nothing extra:
- `[company override]` / `[user override]` — a higher tier replaced the shipped file.
- `[user patch over shipped (−1 ~1 +1)]` — a field patch applied, with delta counts.
- `[⚠ orphaned patch anchor]` — a patch referenced an id that no longer exists; base loaded un-patched.
- `[⚠ override of LOCKED company rule]` — the user diverged from a locked company rule (divergence recorded).
Sections are omitted when empty. If nothing matched at all, say so and go to §10.
### 9a. Optional — render fragment bodies via deliver
When the caller passes `render_bodies: true` (programmatic invocation — prep, review, plan/execute), after producing the summary above, invoke [`skills/deliver/SKILL.md`](../deliver/SKILL.md) with the selected fragments and append its rendered block to the output under a `## Delivered fragments` heading.
User-invoked `/discover` does **not** pass `render_bodies: true` by default — the summary report stays concise. Bodies are pulled only when a consumer explicitly asks for them.
Construction of the `fragments[]` argument:
- Include the top high-confidence memories (those surviving §5 scoring, ordered by score descending).
- Include codemap entries from §8 if any (render as `kind: codemap`, path = file path, body = the codemap's section for that file).
- Skip "possibly relevant" (keyword-fallback) matches unless the caller passes `include_fallback: true`.
- For each memory fragment, Read the file body once and pass it verbatim — delivery does not re-read. Batch the Reads: independent files go in one message, not one call per turn.
The deliver skill enforces ordering, the 20-fragment cap, and the verbatim source-of-truth rule per its format contract.
### 10. Escalate when coverage is thin
Thin = any of:
- Zero high-confidence memory matches **and** zero codemap matches.
- The task names specific aspects that no match addressed (e.g., user asked about "payment gateway webhooks" and nothing in memory or codemap mentions payment).
- Claude's own judgment: "I'm about to guess."
Escalation shape: ask **up to 3** targeted, numbered questions. Each names the specific gap.
```
Before I proceed, I need clarity on:
1. [specific gap — phrased as a question]
2. [specific gap]
3. [specific gap]
```
Do not ask generic questions ("tell me more about X"). Each question must identify a specific piece of missing context.
## Query interface (for other skills)
These filters map to the `mcp__project-memory__discover` parameters (§0), with two skill-side exceptions: `include_fallback` is resolved by this skill (the engine has no such parameter), and this skill passes `top_n` explicitly (default 8) rather than inheriting the engine's default (10) — the skill passes them straight through to the engine on the primary path, and uses them for the degraded deep-scan on the fallback path. When invoked by another skill rather than a user, accept these optional filters:
```
{
task_keywords: string[], // overrides the "last user message" extraction
scopes: string[], // hard filter: only entries whose scope overlaps this
kind: string | null, // hard filter: only entries with this kind (e.g. "architectural-rule")
relevance_phases: string[], // hard filter: only entries matching at least one phase
top_n: number, // default 8
render_bodies: boolean, // default false; when true, §9a invokes deliver and appends the rendered block
include_fallback: boolean, // default false; when true, include keyword-fallback matches in the delivered selection
include_recaps: boolean // default false; when false, §8a is skipped entirely
}
```
Return the report content as structured data rather than the prose template — the caller decides how to present. When `render_bodies: true`, also return the delivered block (per the deliver format contract) so the caller can splice it into working context.
## What discover does NOT do
- Does not re-implement full ranking in-context as its primary path — see Architecture (§0).
- Does not auto-fire at SessionStart (future evolution, not v1).
- Does not cache results between invocations (stateless).
- Does not use embeddings or semantic search (string match only).
- Does not modify memory files (read-only).
- Does not suggest new tags for existing memories.
- Does not cross projects (only the current project's memory dir + global context already loaded).
- Does not *manage* the overlay (add/disable/edit rules) — that's the `rules` skill. Discover only *resolves* the tiers at read time; it never writes a manifest or rule file.
## Limits (v1)
This matching logic is **deliberately simple** — tag overlap + keyword match + escalation. The MCP engine (`mcps/project-memory/src/retrieval/score.ts`) is the live implementation; any scoring change lands in the engine first, then this spec is updated to match (the parity rule). Do not add recency weighting beyond recaps, AND/OR tuning, scope hierarchies, or precedence rules speculatively; embeddings / semantic search are an open future direction, not v1.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!