# Page AI Mindmap Skill Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add the `mnote-mindmap` built-in Page AI skill and a first working `mnote.mindmap.create_from_outline` tool that generates MNote-compatible `.mindmap.json` envelopes from outlines. **Architecture:** Keep Page AI skill discovery in `hermes_tools::skill`, keep tool manifest shape in `hermes_tools::manifest`, and keep resource file behavior in `hermes_tools::resource`. The first implementation uses local-folder resource files and does not introduce PDF OCR; PDF is represented by outline fixtures that later PDF/Office tools can feed into `create_from_outline`. **Tech Stack:** Rust `mnote-web`, Axum route tests, serde_json, local-folder access guards, existing Hermes tool call pipeline. --- ## Files - Create: `skills/mnote-mindmap/SKILL.md` - Modify: `rust/crates/mnote-web/src/hermes_tools/skill.rs` - Modify: `rust/crates/mnote-web/src/hermes_tools/manifest.rs` - Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` - Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` - Modify: `rust/crates/mnote-web/src/routes/hermes_client.rs` - Modify: `scripts/task502-page-ai-agent-selector-context-smoke.js` - Test: `cargo test -p mnote-web --lib hermes_tools_mindmap --manifest-path rust/Cargo.toml` - Test: `cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml` - Test: `cargo test -p mnote-web --lib skill_read_returns_mindmap_skill_content --manifest-path rust/Cargo.toml` - Test: `cargo test -p mnote-web --lib page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly --manifest-path rust/Cargo.toml` - Test: `node --check scripts/task502-page-ai-agent-selector-context-smoke.js` - Smoke: `node scripts/task502-page-ai-agent-selector-context-smoke.js` - Check: `git diff --check -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md` ## Task 1: Built-in Skill Registration **Files:** - Create: `skills/mnote-mindmap/SKILL.md` - Modify: `rust/crates/mnote-web/src/hermes_tools/skill.rs` - [x] **Step 1: Write the failing skill registry test** Add a test in `rust/crates/mnote-web/src/hermes_tools/skill.rs`: ```rust #[test] fn skill_registry_exposes_mindmap_skill_to_agents() { let reasonix_skills = skill_summaries_for_agent(Some("reasonix")); let skill = reasonix_skills .iter() .find(|skill| skill["id"] == "mnote-mindmap") .expect("reasonix should see mindmap skill"); assert_eq!(skill["readOnly"], false); assert!( skill["requiresContextRefs"] .as_array() .expect("context refs") .iter() .any(|value| value == "resource") ); assert!( skill["toolNames"] .as_array() .expect("tool names") .iter() .any(|name| name == "mnote.mindmap.create_from_outline") ); } ``` - [x] **Step 2: Run the targeted failing test** Run: ```bash cargo test -p mnote-web --lib skill_registry_exposes_mindmap_skill_to_agents --manifest-path rust/Cargo.toml ``` Expected: FAIL because `mnote-mindmap` is not registered. - [x] **Step 3: Add the skill file** Create `skills/mnote-mindmap/SKILL.md` with the tool decision rules from `design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md`. - [x] **Step 4: Register the skill** Add a `MnoteSkill` entry in `rust/crates/mnote-web/src/hermes_tools/skill.rs`: ```rust MnoteSkill { id: "mnote-mindmap", title: "MNote mindmap editing", description: "Read, update, summarize, or create MNote mindmap resources, including generating a new mindmap from PDF or document outlines.", agent_ids: &["hermes", "reasonix"], read_only: false, requires_context_refs: &["current_page", "file", "folder", "resource"], tool_names: &[ "mnote.context.snapshot", "mnote.context.resolve_target", "mnote.mindmap.fetch", "mnote.mindmap.apply_ops", "mnote.mindmap.create_from_outline", ], content: include_str!("../../../../../skills/mnote-mindmap/SKILL.md"), }, ``` - [x] **Step 5: Re-run the skill tests** Run: ```bash cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml ``` Expected: PASS for the skill registry tests. ## Task 2: Tool Manifest Contract **Files:** - Modify: `rust/crates/mnote-web/src/hermes_tools/manifest.rs` - Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` - [x] **Step 1: Write the failing manifest test** Extend `hermes_tools_manifest_returns_first_batch_tools` or add a dedicated test that asserts: ```rust let create_tool = tools .iter() .find(|tool| tool["name"] == "mnote.mindmap.create_from_outline") .expect("create_from_outline tool"); assert_eq!( create_tool["inputSchema"]["properties"]["outline"]["type"], "array" ); assert!( create_tool["capabilityScope"] .as_array() .expect("scope") .iter() .any(|scope| scope == "mindmap.write") ); ``` - [x] **Step 2: Run the failing manifest test** Run: ```bash cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml ``` Expected: FAIL because the manifest does not expose `mnote.mindmap.create_from_outline`. - [x] **Step 3: Add `mindmap_create_from_outline_tool()`** Add a write tool with required fields `documentId`, `mindmapId`, `outline`, `sessionId`, `runId`, `toolCallId`, `traceId`, and `actorId`. Include optional `title`, `rootUri`, `resourcePath`, `sourceRefs`, `embedIntoPage`, and `aiAccessScope`. - [x] **Step 4: Add routing for the new tool** In `execute_mnote_tool_call`, route: ```rust "mnote.mindmap.create_from_outline" => resource::mindmap_create_from_outline(&context, &input).await, ``` - [x] **Step 5: Re-run the manifest test** Run: ```bash cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml ``` Expected: PASS for manifest exposure. ## Task 3: Mindmap Envelope Generation **Files:** - Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` - Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` - [x] **Step 1: Write the failing create-from-outline route test** Add an Axum test in `rust/crates/mnote-web/src/routes/hermes_tools.rs` that calls `mnote.mindmap.create_from_outline` with: ```json { "mindmapId": "maps/generated.mindmap.json", "resourcePath": "maps/generated.mindmap.json", "title": "PDF 摘要导图", "outline": [ { "text": "章节一", "children": [ { "text": "要点 1", "children": [] } ] } ], "aiAccessScope": { "permissionLevel": "read_write", "allowedResourceIds": ["maps/generated.mindmap.json"] } } ``` Assert the response and written file: ```rust assert_eq!(payload["result"]["resourceKind"], "mindmap"); assert_eq!(payload["result"]["root"]["data"]["text"], "PDF 摘要导图"); assert_eq!(payload["result"]["root"]["children"][0]["data"]["text"], "章节一"); assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); assert!(root.join("maps").join("generated.mindmap.json").exists()); ``` - [x] **Step 2: Run the failing create test** Run: ```bash cargo test -p mnote-web --lib hermes_tools_mindmap_create_from_outline_writes_default_envelope --manifest-path rust/Cargo.toml ``` Expected: FAIL because the tool does not exist. - [x] **Step 3: Implement outline conversion helpers** Implement helpers in `resource.rs`: - `default_mindmap_view() -> Value` - `mindmap_envelope_from_outline(title: &str, outline: &Value) -> Result` - `mindmap_outline_items_to_children(outline: &[Value], path: &str) -> Vec` Generated child node shape: ```json { "data": { "expand": true, "isActive": false, "text": "节点文本", "uid": "node_1_1" }, "children": [] } ``` - [x] **Step 4: Implement `mindmap_create_from_outline`** Use existing write guard and path guard: - call `ensure_resource_write_contract` - build `ResourceToolTarget` - call `ensure_resource_scope_allowed` - resolve write path under authorized root - create parent directory - write pretty JSON - return envelope, root, markdown summary, revision, and changed file path - [x] **Step 5: Re-run the create test** Run: ```bash cargo test -p mnote-web --lib hermes_tools_mindmap_create_from_outline_writes_default_envelope --manifest-path rust/Cargo.toml ``` Expected: PASS. ## Task 4: Fetch Envelope Awareness **Files:** - Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` - Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` - [x] **Step 1: Write a failing fetch test for the default envelope** Add a test that writes a file shaped as: ```json { "data": { "children": [], "data": { "expand": true, "isActive": false, "text": "KMIND", "uid": "root" } }, "view": { "state": { "scale": 1, "sx": 0, "sy": 0, "x": 0, "y": 0 }, "transform": { "a": 1, "b": 0, "c": 0, "d": 1, "e": 0, "f": 0 } } } ``` Then call `mnote.mindmap.fetch` with `scope=full_envelope` and assert: ```rust assert_eq!(payload["result"]["root"]["data"]["text"], "KMIND"); assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); ``` - [x] **Step 2: Run the failing fetch test** Run: ```bash cargo test -p mnote-web --lib hermes_tools_mindmap_fetch_reads_default_envelope --manifest-path rust/Cargo.toml ``` Expected: FAIL if fetch only understands naked simple-mind-map trees. - [x] **Step 3: Normalize envelope root** Update `collect_mindmap_nodes` and response building to use: ```rust fn mindmap_root_value(value: &Value) -> &Value { value.get("data") .filter(|data| data.get("data").is_some() || data.get("children").is_some()) .unwrap_or(value) } ``` Return `envelope` only when `scope == "full_envelope"`. - [x] **Step 4: Re-run fetch tests** Run: ```bash cargo test -p mnote-web --lib hermes_tools_mindmap_fetch --manifest-path rust/Cargo.toml ``` Expected: PASS for existing and envelope fetch tests. ## Task 5: Final Verification **Files:** - All touched files from prior tasks. - [x] **Step 1: Run non-mutating Rust format audit** Run: ```bash cargo fmt --manifest-path rust/Cargo.toml --all --check ``` Actual: command reports existing workspace-wide rustfmt drift, including unrelated dirty files and pre-existing touched-file formatting. Do not run mutating `cargo fmt` in this dirty worktree without an explicit cleanup decision. - [x] **Step 2: Run targeted Rust tests** Run: ```bash cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml cargo test -p mnote-web --lib skill_read_returns_mindmap_skill_content --manifest-path rust/Cargo.toml cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml cargo test -p mnote-web --lib hermes_tools_mindmap --manifest-path rust/Cargo.toml cargo test -p mnote-web --lib page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly --manifest-path rust/Cargo.toml ``` Expected: all targeted tests pass. - [x] **Step 3: Run JS syntax and browser smoke checks** Run: ```bash node --check scripts/task502-page-ai-agent-selector-context-smoke.js node scripts/task502-page-ai-agent-selector-context-smoke.js ``` Expected: syntax check passes; smoke captures `skillPreferences.mnote["mnote-mindmap"] = false` in the Page AI run payload. - [x] **Step 4: Run diff whitespace check** Run: ```bash git diff --check -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md ``` Expected: no output. - [x] **Step 5: Inspect scoped git diff** Run: ```bash git diff -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md ``` Expected: diff only contains `mnote-mindmap`, `create_from_outline`, envelope fetch support, tests, and docs.