use serde_json::{Value, json};
pub(super) use super::skill_docs::{
RULE_DIFF_SKILL_MD, RULE_GAP_SKILL_MD, RULE_JOURNEY_SKILL_MD, RULE_SEARCH_SKILL_MD,
RULE_WHY_FIRED_SKILL_MD, SMART_EXPLORE_SKILL_MD,
};
pub(super) fn tools_list() -> Value {
json!([
{
"name": "search_rules",
"description": "Compact index search. Returns rule ids/titles/origins/confidence plus explicit evidence records for file-pattern and retrieval-match reasoning before fetching details. Recall is scoped to the current git remotes; pass `repo_full_name` (GitHub owner/repo) when auto-detection is unavailable. When cloud proof is available, results include citedCount and trustRate so agents can prefer rules that led to accepted edits. Use with get_rules for 2-stage retrieval (saves tokens for large rule libraries).",
"inputSchema": {
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Current file path (enables file-pattern cascade)"
},
"intent": {
"type": "string",
"description": "What the user is trying to do"
},
"repo_full_name": {
"type": "string",
"description": "GitHub `owner/repo` for the current project. Omit only when the MCP server can detect the current repo from git remotes."
},
"top_k": {
"type": "number",
"default": 5,
"minimum": 1,
"maximum": 50,
"description": "Maximum number of rule candidates to return; smaller values save tokens, default 5."
},
"session_id": {
"type": "string",
"description": "Optional agent session id used only for local DiffLore flywheel observation correlation."
}
},
"required": ["intent"]
}
},
{
"name": "get_rules",
"description": "Fetch full rule text + examples by ID. Use after search_rules to expand only the IDs worth the tokens. Batch multiple IDs in one call. Pass the current file path when editing so DiffLore can record file-scoped local proof.",
"inputSchema": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": { "type": "string", "maxLength": 128 },
"minItems": 1,
"maxItems": 20
},
"file": {
"type": "string",
"description": "Optional repo-relative file path being edited; records file-scoped local proof when agents fetch rule details."
},
"session_id": {
"type": "string",
"description": "Optional agent session id used only for local DiffLore flywheel observation correlation."
}
},
"required": ["ids"]
}
},
{
"name": "get_past_verdicts",
"description": "Search historical review verdicts (WHAT the team decided on similar code before). Recall is scoped to the current repo/project only; pass `repo_full_name` (GitHub owner/repo) when auto-detection is unavailable. Pass `file` (the path you're editing) so the cloud can apply file-pattern cascade ranking and zero-result calls feed gap-detection telemetry pinpointed to that file. Use this to cite concrete prior decisions; use `rule_timeline` when you need the *why this rule exists* narrative for a specific rule.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query describing the code pattern"
},
"repo_full_name": {
"type": "string",
"description": "GitHub `owner/repo` for the current project. Omit only when the MCP server can detect the current repo from git origin."
},
"file": {
"type": "string",
"description": "Repo-relative path of the file you're editing (e.g. `src/auth/session.ts`). When supplied, cloud-side recall ranks rules whose `file_patterns` match this path first; without it, ordering falls back to pure relevance. Also fills `mcp.gap` telemetry on zero-result calls so the dashboard can show 'you asked but had no rules here'."
},
"top_k": {
"type": "number",
"default": 10,
"minimum": 1,
"maximum": 10,
"description": "Maximum past verdicts to return; default 10."
}
},
"required": ["query"]
}
},
{
"name": "rule_timeline",
"description": "Chronological event stream for ONE rule — why it exists, how it got reinforced. Returns compact rows (~80 tok each) with explicit evidence records for creation/update/example/feedback provenance. Use when the user asks 'where did this rule come from?' or you need provenance for a citation. Complements `get_past_verdicts` (timeline = why this rule; verdicts = what did we decide before).",
"inputSchema": {
"type": "object",
"properties": {
"rule_id": {
"type": "string",
"description": "Skill/rule id (e.g. `conv-my-rule-abc12345`). Accepts the id returned by `remember_rule` / `search_rules`."
},
"depth_before": {
"type": "number",
"default": 5,
"minimum": 0,
"maximum": 20,
"description": "Events before the focal timestamp (rule's created_at). Capped at 20."
},
"depth_after": {
"type": "number",
"default": 5,
"minimum": 0,
"maximum": 20,
"description": "Events after the focal timestamp. Capped at 20."
}
},
"required": ["rule_id"]
}
},
{
"name": "remember_rule",
"description": "Persist a coding rule the user just stated so it survives this conversation and gets recalled in future agent edits + PR reviews. Call WHENEVER the user signals intent to make a rule stick (\"remember this\", \"from now on\", \"don't do X again\", \"always require tests before merge\", \"make this a rule\"). Pass `title` as a short imperative and `body` containing the user's verbatim reasoning — the WHY, not just what. Returns the saved rule_id; echo it back so the user knows the rule landed. Saying \"got it\" without calling this tool drops the rule the moment the session ends. Full trigger guide at difflore://skills/remember_rule.",
"inputSchema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Short imperative title (≤80 chars). E.g. \"Avoid Promise.race for timeout in fetch wrappers\"."
},
"body": {
"type": "string",
"description": "Full natural-language explanation. Include WHY, not just what — quote the user's reason if they gave one. Multi-paragraph OK."
},
"file_patterns": {
"type": "array",
"items": { "type": "string" },
"description": "Optional glob patterns the rule applies to (e.g. [\"**/*.ts\", \"src/api/**\"]). Omit for repo-wide rules. Drives strict file-pattern cascade at recall time."
},
"bad_code": {
"type": "string",
"description": "Optional snippet of the offending pattern. Pair with `good_code`."
},
"good_code": {
"type": "string",
"description": "Optional snippet of the corrected version. Pair with `bad_code`."
},
"severity": {
"type": "string",
"enum": ["low", "medium", "high"],
"description": "Optional severity hint. Defaults to medium."
}
},
"required": ["title", "body"]
}
},
{
"name": "plan_pr",
"description": "Predict the influence scope BEFORE editing: given an issue/PR description (`intent`), returns the expected file count, file-category mix, and the closest historical PRs from this team's review history. Use this to avoid silently under-completing — when the team's prior pattern for similar work touches 4+ files, finishing at 2 is the failure mode this prevents. Falls back to an empty prediction with a hint when no local PR review data exists — run `difflore import-reviews` to populate.",
"inputSchema": {
"type": "object",
"properties": {
"intent": {
"type": "string",
"description": "Issue/PR description text. Title + first paragraph of the body works well."
},
"top_k": {
"type": "number",
"default": 5,
"minimum": 1,
"maximum": 20,
"description": "How many nearest historical PRs to use for the prediction."
}
},
"required": ["intent"]
}
}
])
}
pub(super) fn resources_list() -> Value {
json!([
{
"uri": "difflore://rules/active",
"name": "Active Rules",
"description": "All active rules for the current project, formatted as Markdown",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/remember_rule",
"name": "remember_rule trigger guide",
"description": "Full guide for when to call the remember_rule MCP tool, with trigger phrases and anti-patterns.",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/rule-search",
"name": "rule-search SKILL",
"description": "2-layer workflow for querying team rules via MCP: search_rules → get_rules with token-efficient batching.",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/rule-gap",
"name": "rule-gap SKILL",
"description": "3-step recipe for finding review feedback patterns not yet covered by a team rule.",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/rule-diff",
"name": "rule-diff SKILL",
"description": "Summarize team rule changes since the last `difflore cloud sync` — added, confidence-bumped, removed.",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/rule-why-fired",
"name": "rule-why-fired SKILL",
"description": "Explain why a specific rule matched the current file or diff (file-pattern / semantic / past-verdict reasons).",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/rule-journey",
"name": "rule-journey SKILL",
"description": "Narrative report workflow for the evolution of a team's DiffLore rule library.",
"mimeType": "text/markdown"
},
{
"uri": "difflore://skills/smart-explore",
"name": "smart-explore SKILL",
"description": "Cheap repo-map workflow before agents read files or expand rules.",
"mimeType": "text/markdown"
}
])
}
/// URI-template resources (MCP `resources/templates/list`). Concrete URIs
/// are built by the client by filling in the placeholders — the static
/// `resources/list` above intentionally does NOT enumerate every possible
/// verdict or signature id, since the set is effectively unbounded and
/// cloud-scoped.
pub(super) fn resource_templates_list() -> Value {
json!([
{
"uriTemplate": "difflore://verdicts/{id}",
"name": "Past verdict detail",
"description": "Full detail of a past review verdict (what the team decided, why). Cite by id in agent replies so the user can click through to the cloud dashboard.",
"mimeType": "application/json"
},
{
"uriTemplate": "difflore://signatures/{hash}",
"name": "Fix signature",
"description": "Canonical fix-signature pointer. Returns the hash echo plus a cloud deep link; signature clustering data lives in the cloud.",
"mimeType": "application/json"
}
])
}
/// Full trigger guide for the `remember_rule` tool. Kept as a resource so
/// the MCP tool description can stay terse (saves ~1.5KiB per initialize)
/// while agents can still pull the full decision tree when they need it.
pub(super) const REMEMBER_RULE_GUIDE_MD: &str = r#"# `remember_rule` — Full Trigger Guide
**Persist a coding rule to DiffLore so it applies to future reviews and agent sessions.** This tool is the ONLY way a rule the user mentions in chat actually gets saved — saying "got it, I'll remember" without calling this tool means the rule is lost the moment the conversation ends.
## MUST CALL when the user expresses intent like (in any language):
- "remember this" / "save this rule" / "note this down"
- "don't do X again" / "never do X" / "stop doing X"
- "from now on, X" / "going forward, X" / "whenever you write X, do Y"
- "add a rule that X" / "make a rule for X"
- "in this codebase we always X" / "our convention is X"
The trigger is the user's *intent* to make the rule stick across sessions — phrasing varies by language and tone, so match on intent, not exact words. If you're unsure, prefer calling the tool: a wrong-but-recoverable capture beats a silently dropped rule.
## MUST NOT call when:
- The user is just asking a question or making an observation ("what does X do?")
- You inferred the rule from code review without the user saying so (use `search_rules` then `get_rules` for that flow)
- The user asked you to remember something that's NOT a coding rule (e.g. a meeting time)
## Transcribe verbatim
Put the user's own words in `body` — don't paraphrase or summarise the reasoning away. Their wording often carries the WHY that makes the rule useful. If the user wrote in a non-English language, keep their original wording in `body` (the rule body is opaque text); use English for the `title` so audit listings stay scannable.
## After calling, confirm to the user
Echo the returned `rule_id` and one sentence ("Saved as Rule X — applies to next review of TS files"). The rule lands with `origin=conversation`, base confidence 0.6, and is available to local recall immediately.
"#;