use serde_json::{Value, json};
pub(super) const RESOURCE_SESSION_HANDOFF_URI: &str = "spool://docs/session-handoff";
pub(super) const RESOURCE_CURRENT_PLAN_URI: &str = "spool://docs/mcp-prompts-round-8";
pub(super) const RESOURCE_RESTART_GUIDE_URI: &str = "spool://docs/restart-guide";
pub(super) fn tool_definitions() -> Vec<Value> {
vec![
json!({
"name": "prompt_optimize",
"description": "Generate a combined wakeup + context prompt block for terminal AI clients from cwd, task, and optional files.",
"inputSchema": prompt_optimize_schema()
}),
json!({
"name": "memory_search",
"description": "Build a routed context bundle for a task using the existing spool retrieval core.",
"inputSchema": route_schema(false)
}),
json!({
"name": "memory_explain",
"description": "Explain why spool matched the current project, notes, and candidates.",
"inputSchema": route_schema(false)
}),
json!({
"name": "memory_wakeup",
"description": "Build a wakeup packet for a developer or project profile.",
"inputSchema": wakeup_schema()
}),
json!({
"name": "memory_review_queue",
"description": "Return the current pending review queue from the lifecycle ledger.",
"inputSchema": { "type": "object", "properties": {} }
}),
json!({
"name": "memory_wakeup_ready",
"description": "Return memories that are currently wakeup-ready.",
"inputSchema": { "type": "object", "properties": {} }
}),
json!({
"name": "memory_get",
"description": "Get a single lifecycle record by record_id.",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_history",
"description": "Get the full event history for a lifecycle record.",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_record_manual",
"description": "Create an accepted manual memory record.",
"inputSchema": memory_write_schema()
}),
json!({
"name": "memory_propose",
"description": "Create a candidate AI-proposed memory record.",
"inputSchema": memory_write_schema()
}),
json!({
"name": "memory_accept",
"description": "Accept a candidate lifecycle record.",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_promote",
"description": "Promote an accepted lifecycle record to canonical.",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_archive",
"description": "Archive a lifecycle record.",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_import_session",
"description": "Heuristically extract candidate memories from a Claude Code / Codex session transcript. Default dry-run; set apply=true to write them into the ledger as AI proposals.",
"inputSchema": import_session_schema()
}),
json!({
"name": "memory_sync_vault",
"description": "Sync accepted/canonical lifecycle records to vault as canonical notes in 50-Memory-Ledger/Extracted/. Honors body-hash-based user edit protection. Default live write; set dry_run=true to preview.",
"inputSchema": sync_vault_schema()
}),
json!({
"name": "memory_distill_pending",
"description": "Drain the post-tool-use queue and scan the session transcript with Tier 1 heuristics (self-tag → accepted, incident extraction → candidate). Use after a long working session to flush pending signals into the ledger without waiting for the Stop hook. R4a: Tier 1 only; R4b will prefer MCP sampling.",
"inputSchema": distill_pending_schema()
}),
json!({
"name": "memory_check_contradictions",
"description": "Check a lifecycle record against existing wakeup-ready memories for contradictions. Returns a list of conflicting record IDs with signal type (negation/replacement).",
"inputSchema": record_id_schema()
}),
json!({
"name": "memory_staleness_report",
"description": "Report which wakeup-ready memories are stale (not recently retrieved). Shows days since last reference and the staleness penalty applied. Use to identify memories that may need review or archival.",
"inputSchema": { "type": "object", "properties": {} }
}),
json!({
"name": "memory_import_git",
"description": "Scan recent git commits in the current repo and extract decision/incident/pattern candidates from conventional commit messages. Candidates are written as AI proposals for user review. Set dry_run=true to preview without writing.",
"inputSchema": {
"type": "object",
"properties": {
"cwd": { "type": "string", "description": "Repository path (defaults to current directory)" },
"limit": { "type": "integer", "description": "Number of recent commits to scan (default 30)" },
"dry_run": { "type": "boolean", "description": "Preview only, don't write to ledger" }
}
}
}),
json!({
"name": "memory_dedup_suggestions",
"description": "Find duplicate or highly similar memories that could be merged. Returns pairs of records with similarity scores above 50%.",
"inputSchema": { "type": "object", "properties": {} }
}),
json!({
"name": "memory_consolidate",
"description": "Detect clusters of related fragmented memories (3+ records sharing entities/tags) and optionally merge them. Default dry_run=true returns suggestions only.",
"inputSchema": {
"type": "object",
"properties": {
"dry_run": { "type": "boolean", "default": true, "description": "When true (default), only return suggestions. When false, execute the merge." }
}
}
}),
json!({
"name": "memory_prune",
"description": "Detect stale, expired, or superseded memories and optionally archive them. Default dry_run=true returns suggestions only.",
"inputSchema": {
"type": "object",
"properties": {
"dry_run": { "type": "boolean", "default": true, "description": "When true (default), only return suggestions. When false, execute the archival." }
}
}
}),
json!({
"name": "memory_crystallize",
"description": "Detect clusters of related memory fragments and synthesize them into structured knowledge pages. Uses LLM synthesis when sampling is available, falls back to template-based synthesis otherwise. Knowledge pages are persisted as candidate records for user review.",
"inputSchema": {
"type": "object",
"properties": {
"topic": { "type": "string", "description": "Optional topic filter — only crystallize clusters matching this topic (case-insensitive substring match on entities/tags/title)." },
"dry_run": { "type": "boolean", "default": false, "description": "When true, detect clusters and show what would be synthesized without persisting." }
}
}
}),
json!({
"name": "memory_lint",
"description": "Run a wiki-style lint pass over the ledger and Obsidian vault. Returns prune suggestions (stale / superseded / expired), broken cross-references, and orphan notes (vault files without matching ledger records). Pure read — no mutations.",
"inputSchema": {
"type": "object",
"properties": {}
}
}),
]
}
pub(super) fn prompt_definitions() -> Vec<Value> {
vec![
json!({
"name": "review_lifecycle_queue",
"description": "Review pending lifecycle memories and decide which tools to call next.",
"arguments": [{
"name": "focus",
"description": "Optional reviewer focus, such as preferences, constraints, or decisions.",
"required": false
}]
}),
json!({
"name": "generate_project_wakeup",
"description": "Build and summarize a project wakeup packet.",
"arguments": [
{ "name": "cwd", "description": "Repository working directory.", "required": false },
{ "name": "task", "description": "Optional wakeup task wording.", "required": false }
]
}),
json!({
"name": "retrieve_project_context",
"description": "Retrieve routed project context and explain why it matched.",
"arguments": [
{ "name": "cwd", "description": "Repository working directory.", "required": false },
{ "name": "task", "description": "Context retrieval task wording.", "required": false }
]
}),
]
}
pub(super) fn resource_definitions() -> Vec<Value> {
vec![
json!({
"uri": RESOURCE_SESSION_HANDOFF_URI,
"name": "Session Handoff",
"description": "Current spool handoff and restart status.",
"mimeType": "text/markdown"
}),
json!({
"uri": RESOURCE_CURRENT_PLAN_URI,
"name": "Current Plan",
"description": "Current MCP prompts/resources implementation plan.",
"mimeType": "text/markdown"
}),
json!({
"uri": RESOURCE_RESTART_GUIDE_URI,
"name": "Restart Guide",
"description": "Restart entry points and current next steps.",
"mimeType": "text/markdown"
}),
]
}
pub(super) fn record_id_schema() -> Value {
json!({
"type": "object",
"properties": {
"record_id": { "type": "string" },
"actor": { "type": "string" },
"reason": { "type": "string" },
"evidence_refs": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["record_id"]
})
}
pub(super) fn import_session_schema() -> Value {
json!({
"type": "object",
"properties": {
"provider": {
"type": "string",
"enum": ["claude", "codex"],
"description": "Source provider for the session transcript."
},
"session_id": {
"type": "string",
"description": "Raw session id (no provider prefix)."
},
"apply": {
"type": "boolean",
"default": false,
"description": "When false (default) only returns candidates. When true writes them as AI proposals."
},
"actor": {
"type": "string",
"description": "Optional provenance actor recorded on each written proposal."
}
},
"required": ["provider", "session_id"]
})
}
pub(super) fn sync_vault_schema() -> Value {
json!({
"type": "object",
"properties": {
"dry_run": {
"type": "boolean",
"default": false,
"description": "Preview actions without writing to vault."
},
"enrich": {
"type": "boolean",
"default": false,
"description": "Backfill entities/tags/triggers on records that lack them using heuristic extraction."
}
}
})
}
pub(super) fn distill_pending_schema() -> Value {
json!({
"type": "object",
"properties": {
"cwd": {
"type": "string",
"description": "Absolute project root. Required: heuristics resolve the .spool/ runtime dir + Claude Code transcript dir relative to this."
},
"transcript_path": {
"type": "string",
"description": "Optional explicit path to a Claude Code session jsonl. If absent, the tool falls back to scanning ~/.claude/projects/<sanitized-cwd>/ for the most recently modified jsonl."
}
},
"required": ["cwd"]
})
}
pub(super) fn memory_write_schema() -> Value {
json!({
"type": "object",
"properties": {
"title": { "type": "string" },
"summary": { "type": "string" },
"memory_type": { "type": "string" },
"scope": {
"type": "string",
"enum": ["user", "project", "workspace", "team", "agent"]
},
"source_ref": { "type": "string" },
"project_id": { "type": "string" },
"user_id": { "type": "string" },
"sensitivity": { "type": "string" },
"actor": { "type": "string" },
"reason": { "type": "string" },
"evidence_refs": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["title", "summary", "memory_type", "scope", "source_ref"]
})
}
pub(super) fn route_schema(include_profile: bool) -> Value {
let mut properties = serde_json::Map::from_iter([
("task".to_string(), json!({ "type": "string" })),
("cwd".to_string(), json!({ "type": "string" })),
(
"files".to_string(),
json!({
"type": "array",
"items": { "type": "string" }
}),
),
(
"target".to_string(),
json!({
"type": "string",
"enum": ["claude", "codex", "opencode"]
}),
),
(
"format".to_string(),
json!({
"type": "string",
"enum": ["prompt", "markdown", "json"]
}),
),
]);
if include_profile {
properties.insert(
"profile".to_string(),
json!({
"type": "string",
"enum": ["developer", "project"]
}),
);
}
json!({
"type": "object",
"properties": properties,
"required": ["task", "cwd"]
})
}
pub(super) fn wakeup_schema() -> Value {
route_schema(true)
}
pub(super) fn prompt_optimize_schema() -> Value {
json!({
"type": "object",
"properties": {
"task": { "type": "string" },
"cwd": { "type": "string" },
"files": {
"type": "array",
"items": { "type": "string" }
},
"target": {
"type": "string",
"enum": ["claude", "codex", "opencode"]
},
"profile": {
"type": "string",
"enum": ["developer", "project"]
},
"provider": { "type": "string" },
"session_id": { "type": "string" }
},
"required": ["task", "cwd"]
})
}