Files
mnote/docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

13 KiB

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

  • Step 1: Write the failing skill registry test

Add a test in rust/crates/mnote-web/src/hermes_tools/skill.rs:

#[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")
    );
}
  • Step 2: Run the targeted failing test

Run:

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.

  • 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.

  • Step 4: Register the skill

Add a MnoteSkill entry in rust/crates/mnote-web/src/hermes_tools/skill.rs:

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"),
},
  • Step 5: Re-run the skill tests

Run:

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

  • Step 1: Write the failing manifest test

Extend hermes_tools_manifest_returns_first_batch_tools or add a dedicated test that asserts:

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")
);
  • Step 2: Run the failing manifest test

Run:

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.

  • 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.

  • Step 4: Add routing for the new tool

In execute_mnote_tool_call, route:

"mnote.mindmap.create_from_outline" => resource::mindmap_create_from_outline(&context, &input).await,
  • Step 5: Re-run the manifest test

Run:

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

  • 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:

{
  "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:

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());
  • Step 2: Run the failing create test

Run:

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.

  • Step 3: Implement outline conversion helpers

Implement helpers in resource.rs:

  • default_mindmap_view() -> Value
  • mindmap_envelope_from_outline(title: &str, outline: &Value) -> Result<Value, WebError>
  • mindmap_outline_items_to_children(outline: &[Value], path: &str) -> Vec<Value>

Generated child node shape:

{
  "data": {
    "expand": true,
    "isActive": false,
    "text": "节点文本",
    "uid": "node_1_1"
  },
  "children": []
}
  • 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

  • Step 5: Re-run the create test

Run:

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

  • Step 1: Write a failing fetch test for the default envelope

Add a test that writes a file shaped as:

{
  "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:

assert_eq!(payload["result"]["root"]["data"]["text"], "KMIND");
assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root");
  • Step 2: Run the failing fetch test

Run:

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.

  • Step 3: Normalize envelope root

Update collect_mindmap_nodes and response building to use:

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".

  • Step 4: Re-run fetch tests

Run:

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.

  • Step 1: Run non-mutating Rust format audit

Run:

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.

  • Step 2: Run targeted Rust tests

Run:

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.

  • Step 3: Run JS syntax and browser smoke checks

Run:

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.

  • Step 4: Run diff whitespace check

Run:

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.

  • Step 5: Inspect scoped git diff

Run:

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.