diff --git a/.claude/skills/nature-workflow/SKILL.md b/.claude/skills/nature-workflow/SKILL.md new file mode 100644 index 0000000..02a99ee --- /dev/null +++ b/.claude/skills/nature-workflow/SKILL.md @@ -0,0 +1,103 @@ +--- +name: nature-workflow +description: Use when the user explicitly asks for Nature Skills, nature-skills, Nature style, Nature-style, Nature Communications, Nature-family, CNS, high-impact journal, or Springer Nature workflows, including Nature figure work, polishing, writing, reviewer critique, high-impact journal major revision response, Nature/CNS citations, Nature data-sharing workflows, paper readers, reviewer response, paper-to-PPT, submission checklist, or Nature-specific academic-search workflows. +--- + +# Nature Workflow Bridge + +This skill is a bridge to the upstream `nature-skills` bundle, not a simplified +clone of it. Its job is to route the user's request to the right original +`nature-*` skill when that skill is installed or otherwise available, and to +fall back to ScholarAIO's existing skills only when the original upstream skill +cannot be used in the current host. + +## Direct-Use Policy + +Use the original upstream nature-* skill directly whenever it is available. Do +not emulate, summarize, or partially rewrite upstream behavior from this bridge. +Do not copy only SKILL.md from upstream; direct use requires the whole skill +directory, including `manifest.yaml`, `static/`, `references/`, scripts, assets, +and `skills/_shared` when the upstream skill references it. + +If the upstream skill is unavailable, say so, then use the ScholarAIO fallback +route from `references/upstream-skill-map.md`. Mark the output as a ScholarAIO +fallback, not as an upstream-equivalent Nature Skills result. + +## Reference Loading + +Load only what the current request needs: + +- `references/upstream-skill-map.md`: load for routing decisions or when the + request could match more than one upstream skill. +- `references/bridge-policy.md`: load when installation, direct-use behavior, + upstream fidelity, or fallback limitations matter. +- `references/upstream-install.md`: load when the user needs the original + upstream `nature-*` skills installed or asks whether this bridge is a + simplified local copy. +- `references/quickstart.md`: load when the user asks how to use the bridge or + wants example prompts. + +## First Pass + +Classify the request before doing work: + +1. **Upstream target**: one or more of `nature-figure`, `nature-polishing`, + `nature-writing`, `nature-reviewer`, `nature-citation`, `nature-data`, + `nature-reader`, `nature-response`, `nature-paper2ppt`, or + `nature-academic-search`. +2. **Availability**: original upstream skill available, installed through a + plugin/local skill path, or unavailable. +3. **ScholarAIO fallback**: only if upstream cannot be used. +4. **Evidence state**: manuscript/text/PDF/figures/comments/data/references + supplied, workspace available, or missing inputs. + +State the upstream target and whether direct upstream use is available before +doing substantial work. + +## Route Table + +| User intent | Primary route | +|-------------|---------------| +| Nature/high-impact scientific figure, publication plot, manuscript figure, figures4papers-style output | `nature-figure` | +| Nature-style polishing, manuscript prose polish, Chinese-to-English academic polish | `nature-polishing` | +| Draft/rebuild abstract, introduction, methods, experiments, discussion, conclusion, title, or manuscript argument | `nature-writing` | +| Pre-submission reviewer critique, mock peer review, Nature-style reviewer report | `nature-reviewer` | +| Nature/CNS citation support, claim-to-reference mapping, reference-manager export | `nature-citation` | +| Data Availability, repository plan, FAIR metadata, accession/DOI/source-data/code availability | `nature-data` | +| Full-paper bilingual reader, source-grounded Markdown reader, paper translation/reading | `nature-reader` | +| Reviewer comments, rebuttal, point-by-point response letter, major/minor revision response | `nature-response` | +| Paper-to-PPT, Chinese journal-club PPTX, group meeting deck from a paper | `nature-paper2ppt` | +| Multi-source literature search, citation verification, MeSH/search strategy, citation-file management | `nature-academic-search` | + +Submission package is only one scenario. Do not require a full submission +package when the user asks only for polishing, figures, reading, PPT, citation +support, reviewer critique, Data Availability, response, or search. + +## ScholarAIO Fallbacks + +When the original upstream skill is not available: + +- figure work -> `/draw` +- prose polish -> `/writing-polish` +- manuscript writing -> `/paper-writing` +- reviewer response -> `/review-response` +- citation/search/export -> `/search` + `/citation-check` + `/export` +- paper reading/translation -> `/show` + `/translate` +- paper-to-PPT or document packaging -> `/document` +- literature search -> `/search` or `/websearch` + +Use fallbacks conservatively. Preserve upstream-style guardrails such as +non-invention, source grounding, explicit missing inputs, and final-output +verification, but do not claim to have executed the original upstream workflow +unless the original skill was actually used. + +## Output Shape + +For routing tasks, return: + +1. `Upstream target:` selected `nature-*` skill or skill sequence. +2. `Mode:` direct upstream use or ScholarAIO fallback. +3. `Route:` exact skill sequence. +4. `Immediate next action:` file to inspect, command to run, or question to ask. +5. `Guardrails:` only the relevant non-invention, source-grounding, or + verification constraints for this request. diff --git a/.claude/skills/nature-workflow/references/bridge-policy.md b/.claude/skills/nature-workflow/references/bridge-policy.md new file mode 100644 index 0000000..4aa6f5b --- /dev/null +++ b/.claude/skills/nature-workflow/references/bridge-policy.md @@ -0,0 +1,64 @@ +# Bridge Policy + +This bridge integrates ScholarAIO with the upstream `nature-skills` project +without degrading that project into a simplified local rewrite. + +## Direct Use Comes First + +Use the original upstream nature-* skill directly whenever it is installed or +available through the current agent host. The upstream skill is the source of +truth for its workflow. + +Do not emulate upstream behavior from this bridge. Do not copy only SKILL.md. +Do not flatten upstream `static/`, `references/`, scripts, assets, or +`manifest.yaml` into a short local summary. + +Direct upstream use means the whole skill directory is available, including: + +- `SKILL.md` +- `manifest.yaml`, when present +- `static/` +- `references/` +- scripts and assets +- `skills/_shared` for upstream skills that reference the shared layer + +## What This Bridge Does + +- Classifies the user's request into the upstream `nature-*` skill index. +- Preserves the upstream product language: Nature/high-impact academic workflow, + not submission-only. +- Routes to the original upstream skill when possible. +- Routes to ScholarAIO's existing skills only as a fallback or for local + repository-native work. +- States clearly when a result is a ScholarAIO fallback rather than an + upstream-equivalent result. + +## What This Bridge Does Not Do + +- It does not vendor or rewrite the entire upstream `nature-skills` repository. +- It does not claim that `/draw`, `/writing-polish`, `/paper-writing`, + `/document`, or other ScholarAIO fallbacks are identical to upstream + `nature-*` workflows. +- It does not require a submission package unless the user explicitly asks for + one. +- It does not invent citations, data identifiers, repository URLs, reviewer + expectations, journal rules, figure evidence, manuscript claims, or policy + details. + +## Upstream Installation Reminder + +The upstream repository explicitly warns that each skill is a folder-based unit. +Install or reference the whole skill directory, plus `skills/_shared` when +needed. Copying only `SKILL.md` silently breaks many upstream workflows. + +Upstream repository checked during this branch: + +```text +https://github.com/Yuan1z0825/nature-skills.git +local checked HEAD: 5e31dbb235fc9aca8d40dadd160583d2403dc9f7 +origin/main observed: 5e31dbb235fc9aca8d40dadd160583d2403dc9f7 +``` + +If exact parity matters, refresh the upstream clone and inspect the current +`README.md`, `SKILL.md`, `manifest.yaml`, `static/`, and `references/` files +before finalizing behavior. diff --git a/.claude/skills/nature-workflow/references/quickstart.md b/.claude/skills/nature-workflow/references/quickstart.md new file mode 100644 index 0000000..339c468 --- /dev/null +++ b/.claude/skills/nature-workflow/references/quickstart.md @@ -0,0 +1,47 @@ +# Quickstart + +Use `nature-workflow` when the user asks for Nature Skills, Nature-style, +Nature-family, CNS, high-impact academic, or target-journal help and the exact +upstream `nature-*` skill is not already obvious. + +Submission package is only one scenario. The bridge also covers Nature/high-impact +figure, polishing, writing, reader, paper2ppt, academic search, citation, data, +reviewer critique, and reviewer-response workflows. + +## Example Prompts + +```text +/nature-workflow Polish this abstract in Nature Communications style. +``` + +```text +/nature-workflow Make this paper into a Chinese journal-club PPT. +``` + +```text +/nature-workflow Use Python to create a Nature-style multi-panel figure for +these benchmark results. +``` + +```text +/nature-workflow Draft a Data Availability statement and repository plan for +this manuscript. +``` + +```text +/nature-workflow Simulate a Nature-style reviewer critique for this draft. +``` + +```text +/nature-workflow Add Nature/CNS-family citation support to these manuscript +claims and export RIS. +``` + +## Expected First Response + +The bridge should first say: + +1. which upstream `nature-*` skill applies +2. whether direct upstream use is available +3. the ScholarAIO fallback if upstream is unavailable +4. the first concrete next action diff --git a/.claude/skills/nature-workflow/references/upstream-install.md b/.claude/skills/nature-workflow/references/upstream-install.md new file mode 100644 index 0000000..fdfb17e --- /dev/null +++ b/.claude/skills/nature-workflow/references/upstream-install.md @@ -0,0 +1,53 @@ +# Upstream Install + +Use this reference when the user wants direct upstream Nature Skills behavior, +asks whether ScholarAIO has copied a simplified version, or needs to make the +original `nature-*` skills available in the current agent host. + +## Codex Plugin Path + +Install the complete upstream bundle through the Codex plugin marketplace: + +```bash +codex plugin marketplace add https://github.com/Yuan1z0825/nature-skills --ref main +codex plugin add nature-skills@nature-skills +``` + +Restart Codex or start a new session after installation so the `nature-*` skills +are discovered. + +## Manual Local-Skill Path + +Clone the upstream repository: + +```bash +git clone https://github.com/Yuan1z0825/nature-skills.git +cd nature-skills +``` + +Install all current upstream skills as whole directories: + +```bash +mkdir -p ~/.codex/skills +cp -R skills/_shared ~/.codex/skills/ +for d in skills/nature-*; do + cp -R "$d" ~/.codex/skills/ +done +``` + +Install one upstream skill as a whole directory: + +```bash +mkdir -p ~/.codex/skills +cp -R skills/_shared ~/.codex/skills/ +cp -R skills/nature-reader ~/.codex/skills/ +``` + +## Fidelity Rule + +Copy the whole skill directory. Do not copy only `SKILL.md`. + +The original upstream skills may rely on `manifest.yaml`, `static/`, +`references/`, scripts, assets, README context, and `skills/_shared`. A local +summary or partial folder is not the same product and must be described as a +fallback or adaptation. diff --git a/.claude/skills/nature-workflow/references/upstream-skill-map.md b/.claude/skills/nature-workflow/references/upstream-skill-map.md new file mode 100644 index 0000000..077406f --- /dev/null +++ b/.claude/skills/nature-workflow/references/upstream-skill-map.md @@ -0,0 +1,27 @@ +# Upstream Skill Map + +This map follows the upstream `nature-skills` README skill index. It is a route +map, not a replacement for the upstream skills. + +| Upstream skill | Upstream purpose | Typical trigger | ScholarAIO fallback when upstream is unavailable | +|----------------|------------------|-----------------|--------------------------------------------------| +| `nature-figure` | Nature/high-impact Python or R figure workflow with figure contract, export QA, and source-data traceability. | Nature figure, publication plot, scientific figure, figures4papers, manuscript figure. | `/draw` for diagrams/visuals; keep evidence/source-data checks explicit. | +| `nature-polishing` | Nature-style academic prose polishing, restructuring, and Chinese-to-English manuscript refinement. | Nature style, polish, academic writing, manuscript paragraph, abstract polish. | `/writing-polish`. | +| `nature-writing` | Nature-style manuscript section drafting and argument restructuring. | Nature writing, write abstract, write introduction, manuscript draft, section reconstruction. | `/paper-writing`. | +| `nature-reviewer` | Nature-style reviewer assessment with three referee reports and cross-review synthesis. | Nature reviewer, pre-submission review, mock peer review, reviewer report, critique. | Use `/paper-writing` or `/citation-check` only for downstream fixes; if no upstream skill exists, produce a bounded fallback critique and mark it non-equivalent. | +| `nature-citation` | Strict Nature/CNS-family citation retrieval, claim segmentation, support grading, and ENW/RIS/Zotero RDF export. | Nature citation, CNS citation, supporting references, add citations, reference export. | `/search` + `/citation-check` + `/export`. | +| `nature-data` | Nature/Springer Nature Data Availability statements, repository plans, dataset citations, FAIR metadata checks. | Data Availability, repository, FAIR metadata, source data, accession number, DOI, code availability. | Use this bridge for gap inventory, then `/paper-writing` or `/writing-polish` only for wording; mark as fallback. | +| `nature-reader` | Full-paper bilingual Markdown reader with source anchors, figure/table grounding, and no summary-only degradation. | Nature reader, full markdown, paper md, 原文对照, 图文对应, 全文翻译. | `/show` + `/translate`. | +| `nature-response` | Point-by-point reviewer response letters with comment triage, action mapping, and risk checks. | response to reviewers, rebuttal letter, major revision, 审稿意见回复. | `/review-response`. | +| `nature-paper2ppt` | Chinese PPTX decks from scientific papers for journal club, group meeting, or academic presentation. | paper PPT, journal club, paper to slides, paper presentation, 组会PPT. | `/show` or `/paper-guided-reading` for evidence, then `/document`. | +| `nature-academic-search` | Multi-source literature search, citation verification, MeSH strategy, citation-file management, and reference management. | search papers, academic search, literature search, verify DOI, reference management. | `/search`, `/websearch`, `/citation-check`, `/export`. | + +## Route Principles + +- Prefer the upstream skill when available. +- Use ScholarAIO fallbacks only when the upstream skill cannot be executed in + the current environment. +- Keep the upstream trigger breadth: this is not submission-only. +- Submission package is only one scenario; most upstream skills can be invoked + for ordinary high-impact academic writing, reading, figures, slides, citation, + search, or data work. diff --git a/AGENTS.md b/AGENTS.md index d14a78f..f02d6d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ The canonical project skill source is `.claude/skills/`. Cross-agent discovery p Representative skills to check first: - Core research: `search`, `show`, `ingest`, `workspace`, `audit`, `translate` -- Writing: `academic-writing`, `literature-review`, `paper-guided-reading`, `paper-writing`, `citation-check`, `writing-polish`, `review-response`, `research-gap`, `poster`, `technical-report` +- Writing: `academic-writing`, `nature-workflow`, `literature-review`, `paper-guided-reading`, `paper-writing`, `citation-check`, `writing-polish`, `review-response`, `research-gap`, `poster`, `technical-report` - Outputs and tooling: `draw`, `document`, `publish`, `websearch`, `webextract`, `scientific-runtime`, `scientific-tool-onboarding` If a workflow has grown into a reusable playbook, move it into a skill instead of expanding this file. diff --git a/AGENTS_CN.md b/AGENTS_CN.md index 0859cbf..a6c8187 100644 --- a/AGENTS_CN.md +++ b/AGENTS_CN.md @@ -44,7 +44,7 @@ canonical skill 源是 `.claude/skills/`。其他 agent 发现入口都只是它 代表性 skills: - 核心科研:`search`、`show`、`ingest`、`workspace`、`audit`、`translate` -- 写作:`academic-writing`、`literature-review`、`paper-guided-reading`、`paper-writing`、`citation-check`、`writing-polish`、`review-response`、`research-gap`、`poster`、`technical-report` +- 写作:`academic-writing`、`nature-workflow`、`literature-review`、`paper-guided-reading`、`paper-writing`、`citation-check`、`writing-polish`、`review-response`、`research-gap`、`poster`、`technical-report` - 输出与工具:`draw`、`document`、`websearch`、`webextract`、`scientific-runtime`、`scientific-tool-onboarding` 如果一个流程已经长成可复用 playbook,就把它做成 skill,而不是继续膨胀这个文件。 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c04f71..1860374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- **Nature workflow bridge skill** ([#107](https://github.com/ZimoLiao/scholaraio/issues/107)): Added a ScholarAIO `nature-workflow` bridge skill that routes Nature Portfolio writing and figure workflows to the upstream `nature-skills` repository when installed, keeps ScholarAIO-native fallbacks explicit, documents the install and quick-start path, and includes deterministic plus product-demo fixtures that generate reviewable manuscript, figure, slide, and QA artifacts. + ## [1.5.0] — 2026-05-24 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index d2ffbf0..d376e63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ Claude-specific notes: - Use `/memory` to edit this file or imported project memory. - Keep shared workflows in skills, not in this file. -- Shared project guidance, including core writing skills such as `academic-writing`, `paper-guided-reading`, `poster`, and `technical-report`, is imported from `@AGENTS.md`. +- Shared project guidance, including core writing skills such as `academic-writing`, `nature-workflow`, `paper-guided-reading`, `poster`, and `technical-report`, is imported from `@AGENTS.md`. - Important canonical pointers remain: `scholaraio/stores/explore.py`, `scholaraio/projects/workspace.py`, `scholaraio/services/insights.py`, `scholaraio/services/translate.py`, `scholaraio/interfaces/cli/`, `scholaraio/interfaces/cli/compat.py` for internal CLI wiring, and `scholaraio/cli.py` as the published entrypoint. @AGENTS.md diff --git a/README.md b/README.md index 2240ed1..7e4aba4 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ run the migration in the upgraded checkout that contains your `data/`, For writing tasks, start with the router-style writing entry when the deliverable is clear but the workflow is not. The current writing stack is organized around: - `academic-writing`: route by deliverable and writing stage +- `nature-workflow`: bridge to the upstream `nature-skills` bundle for Nature/high-impact figures, polishing, writing, reviewer critique, citation, Data Availability, paper reading, reviewer response, paper-to-PPT, and academic search; direct upstream skills are preferred when available - `literature-review`: long-form review and survey writing - `paper-guided-reading`: guided deep reading of a single paper from fuzzy search to full-text analysis - `paper-writing`: manuscript sections and paper-focused drafting diff --git a/README_CN.md b/README_CN.md index 5ea8024..a2dbcbd 100644 --- a/README_CN.md +++ b/README_CN.md @@ -101,6 +101,7 @@ scholaraio index --rebuild 针对写作类任务,如果用户已经知道交付物,但还不确定该走哪条 workflow,优先从写作总入口开始。当前写作能力主要包括: - `academic-writing`:按交付物和写作阶段分流 +- `nature-workflow`:对接上游 `nature-skills` bundle,覆盖 Nature/高影响力论文配图、润色、写作、审稿人视角评估、引用、Data Availability、论文阅读、审稿回复、paper-to-PPT 和学术检索;原始上游 skill 可用时优先直连 - `literature-review`:长文综述与 survey - `paper-guided-reading`:从模糊检索到单篇深读的引导式精读 - `paper-writing`:论文具体章节写作 diff --git a/clawhub.yaml b/clawhub.yaml index e94a17c..3b817cd 100644 --- a/clawhub.yaml +++ b/clawhub.yaml @@ -74,6 +74,10 @@ skills: path: .claude/skills/academic-writing description: "Route academic-writing requests by deliverable: review, paper sections, rebuttal, PPT, poster, or technical report" + - name: scholaraio/nature-workflow + path: .claude/skills/nature-workflow + description: "Bridge Nature/high-impact workflows to upstream nature-skills, with ScholarAIO fallbacks" + - name: scholaraio/literature-review path: .claude/skills/literature-review description: "Write a literature review based on workspace papers: topic organization, narrative, gap identification" diff --git a/docs/guide/agent-reference.md b/docs/guide/agent-reference.md index 976bbbe..e8eef1d 100644 --- a/docs/guide/agent-reference.md +++ b/docs/guide/agent-reference.md @@ -46,7 +46,7 @@ Project guidance for maintaining skills: Representative skills: - Core research: `search`, `show`, `ingest`, `workspace`, `audit`, `translate` -- Writing: `academic-writing`, `literature-review`, `paper-guided-reading`, `paper-writing`, `citation-check`, `writing-polish`, `review-response`, `research-gap`, `poster`, `technical-report` +- Writing: `academic-writing`, `nature-workflow`, `literature-review`, `paper-guided-reading`, `paper-writing`, `citation-check`, `writing-polish`, `review-response`, `research-gap`, `poster`, `technical-report` - Outputs and tooling: `draw`, `document`, `publish`, `scientific-runtime`, `scientific-tool-onboarding` ## Repo And Module Map diff --git a/docs/guide/nature-workflow-quickstart.md b/docs/guide/nature-workflow-quickstart.md new file mode 100644 index 0000000..f8a93fd --- /dev/null +++ b/docs/guide/nature-workflow-quickstart.md @@ -0,0 +1,150 @@ +# Nature Workflow Quick Start + +`/nature-workflow` is ScholarAIO's bridge to the upstream +[`nature-skills`](https://github.com/Yuan1z0825/nature-skills) bundle. Use it +when the task is explicitly Nature Skills, Nature-style, Nature-family/CNS, +high-impact journal, or Springer Nature oriented. + +It is not a simplified local clone. When the original upstream `nature-*` skill +is installed or loadable, use that skill directly. If it is unavailable, +ScholarAIO routes to local fallback skills and states that the output is a +fallback, not an upstream-equivalent Nature Skills result. + +## When To Use It + +Use `/nature-workflow` for: + +- Nature/high-impact scientific figures and publication plots +- Nature-style manuscript polishing or Chinese-to-English academic polish +- Nature-style manuscript section drafting and argument restructuring +- Reviewer-style critique before submission +- Nature/CNS citation support and reference-manager export +- Nature/high-impact Data Availability, repository, source data, code availability, and FAIR checks +- Full-paper bilingual Markdown readers and source-grounded translation +- Reviewer response or rebuttal letters +- Paper-to-PPT / Chinese journal-club decks +- Nature-focused academic search, citation verification, MeSH strategy, and reference management + +Submission package is only one scenario. Do not require a submission package +when the user only asks for a figure, polish, reader, paper2ppt deck, citation +search, or Nature Data Availability wording. + +## Inputs To Provide + +Give the bridge the concrete artifact for the chosen route: + +- Figure work: data table, result claim, target backend (`Python` or `R`), and export needs +- Polishing or writing: draft text, section type, target journal/style, and claims that must not change +- Reader or PPT: PDF, DOI, arXiv URL, publisher URL, or existing paper notes +- Citation/search: claim text, scope, preferred citation format, and any journal constraints +- Data Availability: dataset list, access restrictions, repositories, code status, and accession IDs if available +- Response/reviewer work: reviewer comments, decision letter, manuscript draft, and revision notes + +## Install Upstream Skills + +For direct upstream behavior in Codex, install the full upstream bundle: + +```bash +codex plugin marketplace add https://github.com/Yuan1z0825/nature-skills --ref main +codex plugin add nature-skills@nature-skills +``` + +For manual local-skill installation, clone the upstream repository and copy whole +directories: + +```bash +git clone https://github.com/Yuan1z0825/nature-skills.git +cd nature-skills +mkdir -p ~/.codex/skills +cp -R skills/_shared ~/.codex/skills/ +for d in skills/nature-*; do + cp -R "$d" ~/.codex/skills/ +done +``` + +Copying only `SKILL.md` is not enough. + +## Common Scenarios + +| Scenario | Upstream route | ScholarAIO fallback if upstream is unavailable | +|----------|----------------|------------------------------------------------| +| Nature-style figure | `nature-figure` | `/draw` | +| Manuscript polish | `nature-polishing` | `/writing-polish` | +| Section drafting | `nature-writing` | `/paper-writing` | +| Mock reviewer critique | `nature-reviewer` | bounded fallback critique, then `/paper-writing` or `/citation-check` for fixes | +| Citation support | `nature-citation` | `/search` + `/citation-check` + `/export` | +| Data Availability | `nature-data` | gap inventory, then `/paper-writing` or `/writing-polish` wording | +| Full-paper reader | `nature-reader` | `/show` + `/translate` | +| Reviewer response | `nature-response` | `/review-response` | +| Paper-to-PPT | `nature-paper2ppt` | `/show` or `/paper-guided-reading`, then `/document` | +| Academic search | `nature-academic-search` | `/search`, `/websearch`, `/citation-check`, `/export` | + +## Example Prompts + +```text +/nature-workflow Polish this abstract in Nature Communications style. +``` + +```text +/nature-workflow Use Python to create a Nature-style multi-panel figure from these benchmark results. +``` + +```text +/nature-workflow Turn this PDF into a Chinese journal-club PPT. +``` + +```text +/nature-workflow Draft a Data Availability statement and repository plan for this manuscript. +``` + +```text +/nature-workflow Simulate a Nature-style reviewer critique for this draft. +``` + +## Expected Outputs + +The first response should identify: + +1. `Upstream target`: selected `nature-*` skill or skill sequence. +2. `Mode`: direct upstream use or ScholarAIO fallback. +3. `Route`: exact route and fallback skills if needed. +4. `Immediate next action`: file to inspect, command to run, or one blocking question. +5. `Guardrails`: source grounding, no invented citations, no invented data identifiers, and required verification. + +## Production Demo + +The tracked demo fixture lives at +`tests/fixtures/nature_workflow_demo/`. Run its verifier with: + +```bash +python tests/fixtures/nature_workflow_demo/verify_demo.py +``` + +The fixture also includes an executable route demo. It classifies practical +prompts, writes route cards, and proves both direct-upstream and fallback modes: + +```bash +python tests/fixtures/nature_workflow_demo/run_demo.py --output route-cards.md +``` + +For a research-output-level demo, generate the full artifact package: + +```bash +python tests/fixtures/nature_workflow_demo/run_product_demo.py --output-dir workspace/_system/issue-107-routing-eval/nature-workflow-product-demo +``` + +That package includes `route-cards.md`, `inputs/source-data.csv`, SVG/PDF/PNG +figure exports, a polished abstract, a Data Availability statement, a generated +PPTX deck, and QA reports. + +The verifier checks that the bridge covers the full upstream 10-skill index, +records the direct-use policy, executes the route demo, and does not collapse +the workflow into a submission-only package. + +## Guardrails + +- Use the original upstream `nature-*` skill directly when available. +- Do not emulate upstream behavior from a short local summary. +- Do not copy only `SKILL.md`; use the whole skill directory and `skills/_shared` when needed. +- Mark ScholarAIO routes as fallbacks when upstream is unavailable. +- Do not invent citations, reviewer expectations, journal rules, data IDs, repository URLs, source data, figures, or manuscript claims. diff --git a/docs/guide/writing.md b/docs/guide/writing.md index ece45d6..9bc2162 100644 --- a/docs/guide/writing.md +++ b/docs/guide/writing.md @@ -8,6 +8,12 @@ ScholarAIO's writing support is organized as a small set of specialized skills p Use this first when the user knows the deliverable they want, but not which writing workflow to use. It routes by outcome, writing stage, and document type, then points to the right specialized skill or skill combination. +### Nature Workflow Bridge (`/nature-workflow`) + +Use this when the user explicitly asks for Nature Skills, `nature-skills`, Nature-style, Nature-family/CNS, high-impact journal, or Springer Nature workflows. It bridges to the upstream `nature-*` skill index for figures, polishing, writing, reviewer critique, citation, Data Availability, paper reading, reviewer response, paper-to-PPT, and academic search. Direct upstream skill use is preferred when available; ScholarAIO's local skills are fallbacks, not simplified replacements. + +See the [Nature Workflow Quick Start](nature-workflow-quickstart.md) for common scenarios, example prompts, expected outputs, and the production demo fixture. + ## Choose By Deliverable | Deliverable | Recommended skill path | @@ -15,6 +21,7 @@ Use this first when the user knows the deliverable they want, but not which writ | Long-form literature review / survey | `/academic-writing` -> `/literature-review` | | Guided deep reading of a single paper | `/academic-writing` -> `/paper-guided-reading` | | Paper section draft (Introduction, Method, Results, Discussion, Conclusion) | `/academic-writing` -> `/paper-writing` | +| Nature Skills / Nature-style / high-impact journal workflow | `/nature-workflow` -> upstream `nature-*` skill when available, otherwise ScholarAIO fallback | | Response letter / rebuttal | `/academic-writing` -> `/review-response` | | Research-gap memo / topic scouting | `/academic-writing` -> `/research-gap` | | Final prose cleanup / de-AI-fication / style transfer | `/academic-writing` -> `/writing-polish` | @@ -29,6 +36,7 @@ Use this first when the user knows the deliverable they want, but not which writ | Stage | Recommended skill | |-------|-------------------| | Define scope and output format | `/academic-writing` | +| Prepare a Nature-style, CNS, high-impact, or upstream Nature Skills workflow | `/nature-workflow` | | Collect and organize papers | `/workspace` | | Read and summarize evidence | `/show` | | Guided deep reading of a paper | `/paper-guided-reading` | @@ -52,6 +60,10 @@ For tactical, format-specific rules and checklists, see the guides in `docs/writ ## Current Writing Skills +### Nature Workflow (`/nature-workflow`) + +Bridges to the upstream `nature-skills` bundle. Use it for explicit Nature Skills, Nature-style, Nature-family/CNS, high-impact journal, or Springer Nature tasks across figure making, polishing, manuscript writing, reviewer-style critique, citation support, Data Availability, full-paper readers, reviewer response, paper-to-PPT, and academic search. Submission package is only one scenario. If the original upstream skill is unavailable, it routes to ScholarAIO fallbacks and labels the result accordingly. + ### Literature Review (`/literature-review`) Generates a structured literature review from papers in a workspace. Organizes by topic, builds narrative, identifies gaps, and exports BibTeX. diff --git a/mkdocs.yml b/mkdocs.yml index 33674df..2836176 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,7 @@ nav: - Scientific Tool Onboarding: guide/toolref-onboarding.md - Ingestion: guide/ingestion.md - Writing: guide/writing.md + - Nature Workflow Quick Start: guide/nature-workflow-quickstart.md - Webtools Integration: guide/webtools-integration.md - Paper2Any Integration: guide/paper2any-integration.md - Writing Guides: diff --git a/tests/fixtures/nature_workflow_demo/00-artifact-manifest.md b/tests/fixtures/nature_workflow_demo/00-artifact-manifest.md new file mode 100644 index 0000000..5797e94 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/00-artifact-manifest.md @@ -0,0 +1,32 @@ +# Nature Workflow Bridge Demo Manifest + +This fixture demonstrates the corrected issue #107 integration posture: +ScholarAIO should bridge to the upstream `nature-skills` bundle rather than +replace it with a simplified local submission package skill. + +## Artifacts + +| File | Purpose | +|------|---------| +| `00-artifact-manifest.md` | Demo scope and upstream skill coverage. | +| `01-upstream-policy.md` | Direct-use policy: use original upstream `nature-*` skills when available. | +| `02-route-matrix.md` | Full route matrix across the upstream skill index and ScholarAIO fallbacks. | +| `03-demo-prompts.md` | Practical prompts that cover non-submission and submission-related scenarios. | +| `04-verifier-output.md` | Captured verifier output for this tracked fixture. | +| `demo_cases.json` | Executable route-demo cases covering all upstream skills plus fallback mode. | +| `run_demo.py` | Deterministic route demo that classifies prompts and writes route cards. | +| `run_product_demo.py` | Reproducible research-output demo generator for figure, writing, data, PPTX, and QA artifacts. | +| `verify_demo.py` | Local checker for upstream coverage, bridge policy, and executable routing. | + +## Upstream Skills Covered + +- `nature-figure` +- `nature-polishing` +- `nature-writing` +- `nature-reviewer` +- `nature-citation` +- `nature-data` +- `nature-reader` +- `nature-response` +- `nature-paper2ppt` +- `nature-academic-search` diff --git a/tests/fixtures/nature_workflow_demo/01-upstream-policy.md b/tests/fixtures/nature_workflow_demo/01-upstream-policy.md new file mode 100644 index 0000000..8fced98 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/01-upstream-policy.md @@ -0,0 +1,15 @@ +# Upstream Direct-Use Policy + +Use the original upstream nature-* skill directly when the current agent host +has it installed or can load it from the upstream repository. + +This demo is not a simplified `nature-skills` clone. It documents a bridge. + +Do not emulate upstream behavior from a short local summary. Do not copy only skill.md. +Use the whole skill directory, including `manifest.yaml`, `static/`, +`references/`, scripts, assets, and `skills/_shared` when the upstream skill +references shared material. + +When the original upstream skill is unavailable, ScholarAIO may use fallback +routes, but the response must say that it is a fallback and must not claim +upstream-equivalent output. diff --git a/tests/fixtures/nature_workflow_demo/02-route-matrix.md b/tests/fixtures/nature_workflow_demo/02-route-matrix.md new file mode 100644 index 0000000..ffa2c72 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/02-route-matrix.md @@ -0,0 +1,20 @@ +# Route Matrix + +Submission package is only one scenario. The upstream `nature-skills` bundle is +a broad Nature/high-impact academic workflow collection. + +| Scenario | Upstream target | ScholarAIO fallback | +|----------|-----------------|---------------------| +| Nature/high-impact figure or publication plot | `nature-figure` | `/draw` | +| Manuscript polishing or Chinese-to-English academic polish | `nature-polishing` | `/writing-polish` | +| Abstract, introduction, method, results, discussion, conclusion, title, or manuscript argument drafting | `nature-writing` | `/paper-writing` | +| Reviewer-style critique or pre-submission review | `nature-reviewer` | bounded fallback critique, then `/paper-writing` or `/citation-check` for fixes | +| Nature/CNS citation support and reference-manager export | `nature-citation` | `/search` + `/citation-check` + `/export` | +| Data Availability, source data, code availability, repository, FAIR metadata | `nature-data` | fallback inventory, then wording via `/paper-writing` or `/writing-polish` | +| Full-paper bilingual reader or source-grounded Markdown reader | `nature-reader` | `/show` + `/translate` | +| Reviewer response, rebuttal, major/minor revision letter | `nature-response` | `/review-response` | +| Paper to Chinese journal-club PPT or academic presentation deck | `nature-paper2ppt` | `/show` or `/paper-guided-reading`, then `/document` | +| Academic search, citation verification, MeSH strategy, reference management | `nature-academic-search` | `/search`, `/websearch`, `/citation-check`, `/export` | + +The bridge should not require a submission package unless the user explicitly +asks for one. diff --git a/tests/fixtures/nature_workflow_demo/03-demo-prompts.md b/tests/fixtures/nature_workflow_demo/03-demo-prompts.md new file mode 100644 index 0000000..ee52b10 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/03-demo-prompts.md @@ -0,0 +1,34 @@ +# Demo Prompts + +Non-submission scenarios: + +```text +/nature-workflow Polish this abstract in Nature style. +``` + +```text +/nature-workflow Turn this paper into a Chinese journal-club PPT. +``` + +```text +/nature-workflow Make a Nature-style figure in Python from these benchmark +results. +``` + +```text +/nature-workflow Build a source-grounded bilingual Markdown reader for this PDF. +``` + +Submission-related scenarios: + +```text +/nature-workflow Draft a Data Availability statement and repository plan. +``` + +```text +/nature-workflow Simulate a Nature-style reviewer critique before submission. +``` + +```text +/nature-workflow Draft a point-by-point response to these reviewer comments. +``` diff --git a/tests/fixtures/nature_workflow_demo/04-verifier-output.md b/tests/fixtures/nature_workflow_demo/04-verifier-output.md new file mode 100644 index 0000000..dd64b19 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/04-verifier-output.md @@ -0,0 +1,18 @@ +# Verifier Output + +Command: + +```bash +python tests/fixtures/nature_workflow_demo/verify_demo.py +``` + +Output: + +```text +nature-workflow bridge demo verifier: PASS +checked upstream skills: 10 +direct-use policy: PASS +upstream skill coverage: PASS +not submission-only: PASS +executable route demo: PASS +``` diff --git a/tests/fixtures/nature_workflow_demo/demo_cases.json b/tests/fixtures/nature_workflow_demo/demo_cases.json new file mode 100644 index 0000000..6b6947f --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/demo_cases.json @@ -0,0 +1,70 @@ +{ + "cases": [ + { + "name": "figure-direct", + "prompt": "/nature-workflow Use Python to create a Nature-style multi-panel figure from these benchmark results.", + "expected_upstream": "nature-figure", + "upstream_available": true + }, + { + "name": "polishing-direct", + "prompt": "/nature-workflow Polish this abstract in Nature Communications style without changing the claims.", + "expected_upstream": "nature-polishing", + "upstream_available": true + }, + { + "name": "writing-direct", + "prompt": "/nature-workflow Draft a Nature-style introduction from these novelty and result notes.", + "expected_upstream": "nature-writing", + "upstream_available": true + }, + { + "name": "reviewer-direct", + "prompt": "/nature-workflow Simulate a Nature-style reviewer critique for this draft before submission.", + "expected_upstream": "nature-reviewer", + "upstream_available": true + }, + { + "name": "citation-direct", + "prompt": "/nature-workflow Add Nature/CNS-family citation support to these manuscript claims and export RIS.", + "expected_upstream": "nature-citation", + "upstream_available": true + }, + { + "name": "data-direct", + "prompt": "/nature-workflow Draft a Nature Data Availability statement and repository plan for this manuscript.", + "expected_upstream": "nature-data", + "upstream_available": true + }, + { + "name": "reader-direct", + "prompt": "/nature-workflow Build a full-paper bilingual Markdown reader with source anchors for this PDF.", + "expected_upstream": "nature-reader", + "upstream_available": true + }, + { + "name": "response-direct", + "prompt": "/nature-workflow Draft a point-by-point reviewer response letter for this Nature-family major revision.", + "expected_upstream": "nature-response", + "upstream_available": true + }, + { + "name": "paper2ppt-direct", + "prompt": "/nature-workflow Turn this paper into a Chinese journal-club PPT in Nature style.", + "expected_upstream": "nature-paper2ppt", + "upstream_available": true + }, + { + "name": "academic-search-direct", + "prompt": "/nature-workflow Run a Nature-focused academic search, verify DOI metadata, and prepare reference exports.", + "expected_upstream": "nature-academic-search", + "upstream_available": true + }, + { + "name": "figure-fallback", + "prompt": "/nature-workflow Make a Nature figure from this table, but assume upstream nature-skills is not installed.", + "expected_upstream": "nature-figure", + "upstream_available": false + } + ] +} diff --git a/tests/fixtures/nature_workflow_demo/run_demo.py b/tests/fixtures/nature_workflow_demo/run_demo.py new file mode 100644 index 0000000..06d737c --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/run_demo.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import argparse +import json +import re +from dataclasses import dataclass +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +DEFAULT_CASES = ROOT / "demo_cases.json" + + +@dataclass(frozen=True) +class Route: + upstream: str + keywords: tuple[str, ...] + fallback: str + next_action: str + guardrails: tuple[str, ...] + + +ROUTES = ( + Route( + upstream="nature-figure", + keywords=( + "nature figure", + "nature style figure", + "multi panel figure", + "publication plot", + "figure in python", + "python figure", + "figure from", + "figures4papers", + ), + fallback="/draw", + next_action="Load upstream skills/nature-figure/SKILL.md, then resolve the Python/R backend gate.", + guardrails=("Do not invent source data.", "Keep export and visual QA requirements explicit."), + ), + Route( + upstream="nature-polishing", + keywords=("polish", "polishing", "nature communications style", "nature style", "prose"), + fallback="/writing-polish", + next_action="Load upstream skills/nature-polishing/SKILL.md and detect section, language, and journal axes.", + guardrails=("Do not change scientific claims.", "Preserve author intent and terminology."), + ), + Route( + upstream="nature-writing", + keywords=("draft", "write", "introduction", "abstract", "argument", "section"), + fallback="/paper-writing", + next_action="Load upstream skills/nature-writing/SKILL.md and map paper type, section, language, and journal.", + guardrails=("Do not invent results.", "Separate supplied claims from missing evidence."), + ), + Route( + upstream="nature-reviewer", + keywords=( + "nature-style reviewer critique", + "reviewer critique", + "reviewer assessment", + "mock peer review", + "pre-submission review", + ), + fallback="bounded fallback critique, then /paper-writing or /citation-check for fixes", + next_action="Load upstream skills/nature-reviewer/SKILL.md and prepare three referee reports plus synthesis.", + guardrails=("Do not claim an editorial decision.", "Ground critique in provided manuscript facts."), + ), + Route( + upstream="nature-citation", + keywords=("nature/cns", "cns-family citation", "citation support", "export ris", "supporting references"), + fallback="/search + /citation-check + /export", + next_action="Load upstream skills/nature-citation/SKILL.md and segment claims before searching.", + guardrails=("Do not invent citations.", "Keep claim-to-reference support explicit."), + ), + Route( + upstream="nature-data", + keywords=( + "nature data availability", + "data availability", + "repository plan", + "source data", + "fair metadata", + "data sharing", + "code availability", + "accession", + ), + fallback="gap inventory, then /paper-writing or /writing-polish wording", + next_action="Load upstream skills/nature-data/SKILL.md and inventory each dataset access route.", + guardrails=("Do not invent accession IDs.", "State missing repositories or restrictions."), + ), + Route( + upstream="nature-reader", + keywords=("bilingual markdown reader", "full-paper reader", "source anchors", "paper reader", "full markdown"), + fallback="/show + /translate", + next_action="Load upstream skills/nature-reader/SKILL.md and identify source format.", + guardrails=("Do not degrade into summary-only output.", "Preserve source anchors and figure/table grounding."), + ), + Route( + upstream="nature-response", + keywords=( + "point by point", + "point by point response", + "reviewer comments", + "reviewer response", + "response letter", + "major revision", + "rebuttal", + ), + fallback="/review-response", + next_action="Load upstream skills/nature-response/SKILL.md and triage comments before drafting.", + guardrails=("Do not skip reviewer comments.", "Track revision actions and residual risks."), + ), + Route( + upstream="nature-paper2ppt", + keywords=("journal-club ppt", "paper to ppt", "paper2ppt", "ppt in nature style", "presentation deck"), + fallback="/show or /paper-guided-reading, then /document", + next_action="Load upstream skills/nature-paper2ppt/SKILL.md and classify the paper type.", + guardrails=("Do not include unsupported slide claims.", "Run overflow and figure-quality checks."), + ), + Route( + upstream="nature-academic-search", + keywords=("nature-focused academic search", "verify doi", "reference exports", "mesh strategy"), + fallback="/search + /websearch + /citation-check + /export", + next_action="Load upstream skills/nature-academic-search/SKILL.md and choose the search workflow.", + guardrails=("Use source-specific search limits.", "Verify DOI and citation metadata before export."), + ), +) + + +def _normalize(value: str) -> str: + return re.sub(r"\s+", " ", re.sub(r"[^a-z0-9]+", " ", value.lower())).strip() + + +def _load_cases(path: Path) -> list[dict[str, object]]: + payload = json.loads(path.read_text(encoding="utf-8")) + cases = payload.get("cases") + if not isinstance(cases, list): + raise AssertionError("demo_cases.json must contain a cases list") + return cases + + +def _classify(prompt: str) -> Route: + normalized = f" {_normalize(prompt)} " + scored: list[tuple[int, int, Route]] = [] + for index, route in enumerate(ROUTES): + score = sum(1 for keyword in route.keywords if f" {_normalize(keyword)} " in normalized) + scored.append((score, -index, route)) + + score, _neg_index, route = max(scored, key=lambda item: (item[0], item[1])) + if score <= 0: + raise AssertionError(f"no route matched prompt: {prompt}") + return route + + +def _mode(upstream_available: object) -> str: + if upstream_available is True: + return "direct upstream preferred" + if upstream_available is False: + return "ScholarAIO fallback" + raise AssertionError("upstream_available must be true or false") + + +def _render_card(case: dict[str, object], route: Route) -> str: + prompt = str(case["prompt"]) + mode = _mode(case["upstream_available"]) + route_line = ( + f"`{route.upstream}` from the whole upstream skill directory" + if mode == "direct upstream preferred" + else f"ScholarAIO fallback path: {route.fallback}" + ) + next_action = ( + route.next_action + if mode == "direct upstream preferred" + else f"Use ScholarAIO fallback path: {route.fallback}; state that upstream nature-skills is unavailable." + ) + + guardrails = "\n".join(f"- {item}" for item in route.guardrails) + return "\n".join( + [ + f"## {case['name']}", + "", + f"Prompt: {prompt}", + f"Upstream target: `{route.upstream}`", + f"Mode: {mode}", + f"Route: {route_line}", + f"ScholarAIO fallback: {route.fallback}", + f"Immediate next action: {next_action}", + "Guardrails:", + guardrails, + "", + ] + ) + + +def _render_demo(cases: list[dict[str, object]]) -> str: + cards = ["# Nature Workflow Executable Route Demo", ""] + for case in cases: + route = _classify(str(case["prompt"])) + expected = case["expected_upstream"] + if route.upstream != expected: + raise AssertionError(f"{case['name']}: expected {expected}, got {route.upstream}") + cards.append(_render_card(case, route)) + return "\n".join(cards).rstrip() + "\n" + + +def main() -> None: + parser = argparse.ArgumentParser(description="Run the nature-workflow route demo") + parser.add_argument("--cases", type=Path, default=DEFAULT_CASES) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + cases = _load_cases(args.cases) + rendered = _render_demo(cases) + + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(rendered, encoding="utf-8") + print(f"wrote route cards: {args.output}") + else: + print(rendered, end="") + + print("nature-workflow executable route demo: PASS") + print(f"checked route cases: {len(cases)}") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/nature_workflow_demo/run_product_demo.py b/tests/fixtures/nature_workflow_demo/run_product_demo.py new file mode 100644 index 0000000..cc31f37 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/run_product_demo.py @@ -0,0 +1,482 @@ +from __future__ import annotations + +import argparse +import csv +import importlib.util +import subprocess +import sys +from pathlib import Path + +FIXTURE_DIR = Path(__file__).resolve().parent +OPTIONAL_DEPS = { + "matplotlib": "matplotlib", + "numpy": "numpy", + "pptx": "python-pptx", +} + + +def _missing_optional_dependencies() -> list[str]: + return [package for module, package in OPTIONAL_DEPS.items() if importlib.util.find_spec(module) is None] + + +def _require_optional_dependencies() -> None: + missing = _missing_optional_dependencies() + if missing: + packages = " ".join(missing) + raise SystemExit( + "nature-workflow product demo requires optional plotting/Office dependencies. " + f"Install them with: pip install {packages}" + ) + + +def _write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text.rstrip() + "\n", encoding="utf-8") + + +def _write_source_data(path: Path) -> list[dict[str, str]]: + rows = [ + { + "workflow": "baseline-agent", + "evidence_coverage": "0.58", + "citation_error_rate": "0.21", + "readiness_score": "0.54", + "qa_failures": "7", + "route_latency_s": "18.4", + }, + { + "workflow": "scholaraio-fallback", + "evidence_coverage": "0.73", + "citation_error_rate": "0.12", + "readiness_score": "0.70", + "qa_failures": "4", + "route_latency_s": "20.8", + }, + { + "workflow": "nature-workflow-bridge", + "evidence_coverage": "0.88", + "citation_error_rate": "0.05", + "readiness_score": "0.86", + "qa_failures": "1", + "route_latency_s": "22.1", + }, + ] + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + return rows + + +def _make_figure(rows: list[dict[str, str]], output_dir: Path) -> None: + import matplotlib + import numpy as np + + matplotlib.use("Agg") + from matplotlib import pyplot as plt + + labels = ["Baseline", "Fallback", "Bridge"] + evidence = np.array([float(row["evidence_coverage"]) for row in rows]) + citation_errors = np.array([float(row["citation_error_rate"]) for row in rows]) + readiness = np.array([float(row["readiness_score"]) for row in rows]) + qa_failures = np.array([int(row["qa_failures"]) for row in rows]) + + plt.rcParams.update( + { + "font.family": "DejaVu Sans", + "font.size": 8, + "axes.spines.top": False, + "axes.spines.right": False, + "axes.linewidth": 0.8, + "xtick.major.width": 0.8, + "ytick.major.width": 0.8, + "figure.dpi": 160, + } + ) + colors = ["#8E8E8E", "#4C78A8", "#2F7D5B"] + + fig = plt.figure(figsize=(7.2, 4.6), constrained_layout=True) + grid = fig.add_gridspec(2, 2, width_ratios=[1.25, 1], height_ratios=[1, 1]) + ax_a = fig.add_subplot(grid[:, 0]) + ax_b = fig.add_subplot(grid[0, 1]) + ax_c = fig.add_subplot(grid[1, 1]) + + x = np.arange(len(labels)) + ax_a.bar(x, readiness, color=colors, width=0.62) + ax_a.set_ylim(0, 1) + ax_a.set_xticks(x, labels, rotation=18, ha="right") + ax_a.set_ylabel("Readiness score") + ax_a.set_title("a End-to-end artifact readiness", loc="left", fontweight="bold") + for idx, value in enumerate(readiness): + ax_a.text(idx, value + 0.025, f"{value:.2f}", ha="center", va="bottom", fontsize=8) + + ax_b.plot(x, evidence, marker="o", color="#2F7D5B", linewidth=1.8, label="Evidence coverage") + ax_b.plot(x, 1 - citation_errors, marker="s", color="#B85C38", linewidth=1.8, label="Citation correctness") + ax_b.set_ylim(0.45, 1.0) + ax_b.set_xticks(x, labels, rotation=18, ha="right") + ax_b.set_ylabel("Proportion") + ax_b.set_title("b Source-grounding checks", loc="left", fontweight="bold") + ax_b.legend(frameon=False, fontsize=7, loc="lower right") + + ax_c.bar(x, qa_failures, color=colors, width=0.62) + ax_c.set_ylim(0, max(qa_failures) + 2) + ax_c.set_xticks(x, labels, rotation=18, ha="right") + ax_c.set_ylabel("Open QA failures") + ax_c.set_title("c Residual review risks", loc="left", fontweight="bold") + for idx, value in enumerate(qa_failures): + ax_c.text(idx, value + 0.25, str(value), ha="center", va="bottom", fontsize=8) + + fig.suptitle( + "Nature workflow bridge demonstration on a synthetic artifact-readiness benchmark", + fontsize=10, + fontweight="bold", + ) + + figure_dir = output_dir / "figures" + figure_dir.mkdir(parents=True, exist_ok=True) + for suffix in ("svg", "pdf", "png"): + fig.savefig(figure_dir / f"nature-workflow-product-demo.{suffix}", bbox_inches="tight") + plt.close(fig) + + +def _write_writing_outputs(output_dir: Path) -> None: + before = """ +# Abstract Before + +This demo checks a skill router for Nature related scientific work. We made a +small benchmark and found that routing can be useful. The outputs include a +figure, a data availability statement, and a slide deck. The workflow is better +because it reminds the agent to use upstream skills and avoid made-up details. +""" + polished = """ +# Abstract Polished + +This demo uses synthetic data to test whether a ScholarAIO Nature workflow +bridge can turn an ambiguous high-impact-journal request into reviewable +research artifacts. Across a small artifact-readiness benchmark, the bridge +routes each task to the corresponding upstream `nature-*` skill when available +and falls back to ScholarAIO-native skills only when direct upstream execution is +not possible. The resulting package contains source data, a multi-panel figure, +a route audit, a Data Availability statement, and a checked PPTX deck. The +exercise does not claim a biological, clinical, or engineering discovery; it +tests workflow behavior, source grounding, and artifact completeness. +""" + _write_text(output_dir / "writing" / "abstract-before.md", before) + _write_text(output_dir / "writing" / "abstract-polished.md", polished) + + +def _write_data_availability(output_dir: Path) -> None: + statement = """ +# Data Availability Statement + +All data used in this demonstration are synthetic and are provided in +`inputs/source-data.csv` within this demo package. No human participants, +clinical records, biological samples, proprietary datasets, third-party +restricted datasets, or external accessioned datasets were used. + +No accession numbers, repository DOIs, embargo dates, or controlled-access +committees are claimed for this demo. If this workflow were applied to a real +manuscript, each dataset would need to be mapped to a public repository, +controlled-access route, in-paper source-data table, or justified restriction +before the statement could be submission-ready. +""" + _write_text(output_dir / "data" / "data-availability.md", statement) + + +def _write_figure_docs(output_dir: Path) -> None: + caption = """ +# Figure Caption + +**Figure 1 | Nature workflow bridge demonstration on a synthetic +artifact-readiness benchmark.** a, End-to-end artifact readiness for a baseline +agent response, a ScholarAIO fallback route, and the Nature workflow bridge. +b, Source-grounding checks represented as evidence coverage and citation +correctness. c, Residual QA failures after self-review. Values are generated +from the synthetic source table in `inputs/source-data.csv` and are intended only +to validate the workflow. +""" + qa = """ +# Figure QA + +- Source table: `inputs/source-data.csv`. +- Backend: Python / matplotlib only. +- Exports: SVG, PDF, PNG. +- Panel labels: present (`a`, `b`, `c`). +- Scientific claim: workflow-readiness demonstration only; no domain discovery + is claimed. +- Integrity checks: all plotted values are directly read from source-data.csv; + no hidden smoothing, fabricated confidence intervals, or invented sample + sizes are used. +""" + _write_text(output_dir / "figures" / "figure-caption.md", caption) + _write_text(output_dir / "figures" / "figure-qa.md", qa) + + +def _add_title(slide, title: str, subtitle: str | None = None) -> None: + from pptx.dml.color import RGBColor + from pptx.util import Inches, Pt + + title_box = slide.shapes.add_textbox(Inches(0.45), Inches(0.32), Inches(12.4), Inches(0.55)) + frame = title_box.text_frame + frame.text = title + run = frame.paragraphs[0].runs[0] + run.font.size = Pt(24) + run.font.bold = True + run.font.color.rgb = RGBColor(34, 42, 50) + if subtitle: + subtitle_box = slide.shapes.add_textbox(Inches(0.48), Inches(0.88), Inches(12.2), Inches(0.34)) + subtitle_frame = subtitle_box.text_frame + subtitle_frame.text = subtitle + subtitle_frame.paragraphs[0].runs[0].font.size = Pt(10) + subtitle_frame.paragraphs[0].runs[0].font.color.rgb = RGBColor(92, 103, 115) + + +def _add_bullets(slide, x: float, y: float, w: float, h: float, items: list[str]) -> None: + from pptx.dml.color import RGBColor + from pptx.util import Inches, Pt + + box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + frame = box.text_frame + frame.clear() + for idx, item in enumerate(items): + paragraph = frame.paragraphs[0] if idx == 0 else frame.add_paragraph() + paragraph.text = item + paragraph.level = 0 + paragraph.font.size = Pt(14) + paragraph.font.color.rgb = RGBColor(40, 48, 56) + paragraph.space_after = Pt(8) + + +def _write_pptx(output_dir: Path) -> None: + from pptx import Presentation + from pptx.util import Inches + + prs = Presentation() + prs.slide_width = Inches(13.333) + prs.slide_height = Inches(7.5) + blank = prs.slide_layouts[6] + + slide = prs.slides.add_slide(blank) + _add_title(slide, "Nature Workflow Product Demo", "Direct upstream skill routing with ScholarAIO fallback evidence") + _add_bullets( + slide, + 0.7, + 1.55, + 6.2, + 4.3, + [ + "Scenario: high-impact manuscript support package", + "Direct upstream routes: nature-figure, nature-polishing, nature-data, nature-paper2ppt", + "Fallback mode: explicit ScholarAIO route when upstream skills are unavailable", + "Artifacts: source data, figure, polished abstract, data statement, route audit, PPTX", + ], + ) + slide.shapes.add_picture( + str(output_dir / "figures" / "nature-workflow-product-demo.png"), + Inches(7.0), + Inches(1.28), + width=Inches(5.7), + ) + + slide = prs.slides.add_slide(blank) + _add_title(slide, "Route Audit", "What the bridge decides before content generation") + _add_bullets( + slide, + 0.7, + 1.3, + 11.8, + 4.8, + [ + "Upstream target is stated before substantive work.", + "Mode is either direct upstream preferred or ScholarAIO fallback.", + "Fallback output is not described as upstream-equivalent.", + "Guardrails focus on source grounding, no invented citations, and no invented data identifiers.", + ], + ) + + slide = prs.slides.add_slide(blank) + _add_title(slide, "Figure QA", "Nature-figure constraints applied to a synthetic benchmark") + slide.shapes.add_picture( + str(output_dir / "figures" / "nature-workflow-product-demo.png"), + Inches(0.6), + Inches(1.12), + width=Inches(7.2), + ) + _add_bullets( + slide, + 8.15, + 1.3, + 4.4, + 4.7, + [ + "Python backend selected explicitly.", + "All values come from source-data.csv.", + "Vector and raster exports are both generated.", + "No sample sizes, confidence intervals, or accession IDs are invented.", + ], + ) + + slide = prs.slides.add_slide(blank) + _add_title(slide, "Ready-to-Paste Data Availability", "Synthetic-data demo with no invented identifiers") + _add_bullets( + slide, + 0.75, + 1.35, + 11.8, + 4.6, + [ + "All data are synthetic and bundled as inputs/source-data.csv.", + "No human, clinical, biological, proprietary, or third-party restricted data are used.", + "No accession number or repository DOI is claimed.", + "Real manuscripts still need dataset-by-dataset repository mapping.", + ], + ) + + slide = prs.slides.add_slide(blank) + _add_title(slide, "Decision", "Is this useful as a ScholarAIO branch?") + _add_bullets( + slide, + 0.75, + 1.4, + 11.8, + 4.6, + [ + "Useful if the product goal is safe orchestration across upstream Nature Skills.", + "Not a replacement for installing the upstream bundle.", + "Fallbacks are honest and bounded, not feature-equivalent claims.", + "The practical value is reduced routing ambiguity and fewer unsafe scholarly outputs.", + ], + ) + + slide_dir = output_dir / "slides" + slide_dir.mkdir(parents=True, exist_ok=True) + pptx_path = slide_dir / "nature-workflow-product-demo.pptx" + prs.save(pptx_path) + + inspect_lines = [ + "# PPTX Inspect", + "", + f"Slides: {len(prs.slides)}", + f"Size: {prs.slide_width / 914400:.2f} x {prs.slide_height / 914400:.2f} in", + "", + ] + for idx, slide in enumerate(prs.slides, 1): + inspect_lines.append(f"## Slide {idx}") + inspect_lines.append(f"Shape count: {len(slide.shapes)}") + for shape in slide.shapes: + kind = "picture" if shape.shape_type == 13 else "text" + inspect_lines.append(f"- {kind}: {shape.left / 914400:.2f}, {shape.top / 914400:.2f}") + inspect_lines.append("") + _write_text(slide_dir / "pptx-inspect.md", "\n".join(inspect_lines)) + + +def _write_manifest(output_dir: Path) -> None: + manifest = """ +# Nature Workflow Product Demo + +This package follows `docs/guide/nature-workflow-quickstart.md` and exercises the +bridge as a real research-output workflow, not only a static routing check. + +## Upstream Route Coverage Used + +- `nature-figure`: generated a Python/matplotlib multi-panel figure with SVG, + PDF, and PNG exports. +- `nature-polishing`: produced a Nature-leaning polished abstract while + preserving the synthetic-data limitation. +- `nature-data`: produced a Data Availability statement with no invented + accession IDs. +- `nature-paper2ppt`: generated an actual PPTX deck and local inspect report. + +The route audit also contains all 10 upstream `nature-*` targets, including +direct upstream preferred and ScholarAIO fallback modes. + +## Key Files + +- `route-cards.md`: executable route demo output. +- `inputs/source-data.csv`: synthetic source data used for all plotted values. +- `figures/nature-workflow-product-demo.svg`: editable vector figure. +- `figures/nature-workflow-product-demo.pdf`: manuscript-style PDF export. +- `figures/nature-workflow-product-demo.png`: raster preview and slide input. +- `writing/abstract-polished.md`: polished abstract. +- `data/data-availability.md`: ready-to-paste demo statement. +- `slides/nature-workflow-product-demo.pptx`: generated slide deck. +- `qa/product-demo-verification.md`: final verification record. +""" + _write_text(output_dir / "README.md", manifest) + + +def _write_verification(output_dir: Path, rows: list[dict[str, str]]) -> None: + required = [ + "route-cards.md", + "inputs/source-data.csv", + "figures/nature-workflow-product-demo.svg", + "figures/nature-workflow-product-demo.pdf", + "figures/nature-workflow-product-demo.png", + "writing/abstract-polished.md", + "data/data-availability.md", + "slides/nature-workflow-product-demo.pptx", + "slides/pptx-inspect.md", + ] + missing = [item for item in required if not (output_dir / item).exists()] + if missing: + raise AssertionError(f"missing product demo files: {', '.join(missing)}") + + verification = f""" +# Product Demo Verification + +- Required files present: PASS. +- Source rows: {len(rows)}. +- Figure exports present: SVG, PDF, PNG. +- PPTX generated and locally inspected: PASS. +- No invented accession IDs: PASS. +- No external dataset claimed: PASS. +- Upstream fidelity policy: original `nature-*` skills are preferred; fallback + outputs are labelled as ScholarAIO fallbacks. + +This demo uses synthetic data, so it demonstrates workflow behavior and artifact +quality gates rather than a domain-specific scientific discovery. +""" + _write_text(output_dir / "qa" / "product-demo-verification.md", verification) + + +def _write_route_cards(output_dir: Path) -> None: + subprocess.run( + [ + sys.executable, + str(FIXTURE_DIR / "run_demo.py"), + "--output", + str(output_dir / "route-cards.md"), + ], + check=True, + capture_output=True, + text=True, + ) + + +def run(output_dir: Path) -> None: + _require_optional_dependencies() + output_dir.mkdir(parents=True, exist_ok=True) + rows = _write_source_data(output_dir / "inputs" / "source-data.csv") + _write_route_cards(output_dir) + _make_figure(rows, output_dir) + _write_writing_outputs(output_dir) + _write_data_availability(output_dir) + _write_figure_docs(output_dir) + _write_pptx(output_dir) + _write_manifest(output_dir) + _write_verification(output_dir, rows) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Generate the nature-workflow product demo") + parser.add_argument("--output-dir", type=Path, required=True) + args = parser.parse_args() + + run(args.output_dir) + print("nature-workflow product demo: PASS") + print(f"output directory: {args.output_dir}") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/nature_workflow_demo/verify_demo.py b/tests/fixtures/nature_workflow_demo/verify_demo.py new file mode 100644 index 0000000..fa1ac97 --- /dev/null +++ b/tests/fixtures/nature_workflow_demo/verify_demo.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import subprocess +import sys +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parent + +UPSTREAM_SKILLS = { + "nature-figure", + "nature-polishing", + "nature-writing", + "nature-reviewer", + "nature-citation", + "nature-data", + "nature-reader", + "nature-response", + "nature-paper2ppt", + "nature-academic-search", +} + + +def _read(name: str) -> str: + path = ROOT / name + if not path.exists(): + raise AssertionError(f"missing required file: {name}") + return path.read_text(encoding="utf-8") + + +def _assert_upstream_skill_coverage() -> None: + combined = "\n".join( + [ + _read("00-artifact-manifest.md"), + _read("02-route-matrix.md"), + ] + ) + missing = sorted(skill for skill in UPSTREAM_SKILLS if skill not in combined) + if missing: + raise AssertionError(f"missing upstream skills: {', '.join(missing)}") + + +def _assert_direct_use_policy() -> None: + policy = _read("01-upstream-policy.md").lower() + for phrase in ( + "use the original upstream nature-* skill directly", + "do not emulate", + "do not copy only skill.md", + "whole skill directory", + "skills/_shared", + ): + if phrase not in policy: + raise AssertionError(f"missing direct-use policy phrase: {phrase}") + + +def _assert_not_submission_only() -> None: + route_matrix = _read("02-route-matrix.md").lower() + for phrase in ( + "submission package is only one scenario", + "reader", + "paper2ppt", + "figure", + "polishing", + "academic search", + ): + if phrase not in route_matrix: + raise AssertionError(f"missing breadth phrase: {phrase}") + + +def _assert_executable_route_demo() -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + output_path = Path(tmp_dir) / "route-cards.md" + result = subprocess.run( + [ + sys.executable, + str(ROOT / "run_demo.py"), + "--output", + str(output_path), + ], + check=True, + capture_output=True, + text=True, + ) + + route_cards = output_path.read_text(encoding="utf-8") + if "nature-workflow executable route demo: PASS" not in result.stdout: + raise AssertionError("route demo did not report PASS") + if "Mode: direct upstream preferred" not in route_cards: + raise AssertionError("route demo did not include direct upstream mode") + if "Mode: ScholarAIO fallback" not in route_cards: + raise AssertionError("route demo did not include fallback mode") + for skill in UPSTREAM_SKILLS: + if f"Upstream target: `{skill}`" not in route_cards: + raise AssertionError(f"route demo missing skill card: {skill}") + + +def main() -> None: + _assert_upstream_skill_coverage() + _assert_direct_use_policy() + _assert_not_submission_only() + _assert_executable_route_demo() + print("nature-workflow bridge demo verifier: PASS") + print(f"checked upstream skills: {len(UPSTREAM_SKILLS)}") + print("direct-use policy: PASS") + print("upstream skill coverage: PASS") + print("not submission-only: PASS") + print("executable route demo: PASS") + + +if __name__ == "__main__": + main() diff --git a/tests/test_nature_workflow_demo_fixture.py b/tests/test_nature_workflow_demo_fixture.py new file mode 100644 index 0000000..34c149e --- /dev/null +++ b/tests/test_nature_workflow_demo_fixture.py @@ -0,0 +1,247 @@ +"""Checks for the versioned nature-workflow bridge demo fixture.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +FIXTURE_DIR = ROOT / "tests" / "fixtures" / "nature_workflow_demo" + +UPSTREAM_SKILLS = { + "nature-figure", + "nature-polishing", + "nature-writing", + "nature-reviewer", + "nature-citation", + "nature-data", + "nature-reader", + "nature-response", + "nature-paper2ppt", + "nature-academic-search", +} + + +def test_tracked_demo_fixture_runs_its_verifier() -> None: + result = subprocess.run( + [sys.executable, str(FIXTURE_DIR / "verify_demo.py")], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ) + + assert "nature-workflow bridge demo verifier: PASS" in result.stdout + assert "upstream skill coverage: PASS" in result.stdout + assert "executable route demo: PASS" in result.stdout + + +def test_tracked_demo_fixture_runs_executable_route_demo(tmp_path: Path) -> None: + output_path = tmp_path / "route-cards.md" + + result = subprocess.run( + [ + sys.executable, + str(FIXTURE_DIR / "run_demo.py"), + "--output", + str(output_path), + ], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ) + + route_cards = output_path.read_text(encoding="utf-8") + + assert "nature-workflow executable route demo: PASS" in result.stdout + assert "# Nature Workflow Executable Route Demo" in route_cards + assert "Mode: direct upstream preferred" in route_cards + assert "Mode: ScholarAIO fallback" in route_cards + assert "Immediate next action:" in route_cards + assert "Guardrails:" in route_cards + + fallback_card = route_cards.split("## figure-fallback", maxsplit=1)[1] + fallback_action = fallback_card.split("Guardrails:", maxsplit=1)[0] + assert "Immediate next action: Use ScholarAIO fallback path: /draw" in fallback_action + assert "Load upstream" not in fallback_action + + for skill_name in UPSTREAM_SKILLS: + assert f"Upstream target: `{skill_name}`" in route_cards + + +def test_documented_demo_prompts_are_executable_route_cases(tmp_path: Path) -> None: + cases_path = tmp_path / "documented-demo-cases.json" + cases_path.write_text( + json.dumps( + { + "cases": [ + { + "name": "documented-polishing", + "prompt": "/nature-workflow Polish this abstract in Nature style.", + "expected_upstream": "nature-polishing", + "upstream_available": True, + }, + { + "name": "documented-paper2ppt", + "prompt": "/nature-workflow Turn this paper into a Chinese journal-club PPT.", + "expected_upstream": "nature-paper2ppt", + "upstream_available": True, + }, + { + "name": "documented-figure", + "prompt": "/nature-workflow Make a Nature-style figure in Python from these benchmark results.", + "expected_upstream": "nature-figure", + "upstream_available": True, + }, + { + "name": "documented-reader", + "prompt": "/nature-workflow Build a source-grounded bilingual Markdown reader for this PDF.", + "expected_upstream": "nature-reader", + "upstream_available": True, + }, + { + "name": "documented-data", + "prompt": "/nature-workflow Draft a Data Availability statement and repository plan.", + "expected_upstream": "nature-data", + "upstream_available": True, + }, + { + "name": "documented-reviewer", + "prompt": "/nature-workflow Simulate a Nature-style reviewer critique before submission.", + "expected_upstream": "nature-reviewer", + "upstream_available": True, + }, + { + "name": "documented-response", + "prompt": "/nature-workflow Draft a point-by-point response to these reviewer comments.", + "expected_upstream": "nature-response", + "upstream_available": True, + }, + ] + }, + indent=2, + ), + encoding="utf-8", + ) + + result = subprocess.run( + [ + sys.executable, + str(FIXTURE_DIR / "run_demo.py"), + "--cases", + str(cases_path), + ], + cwd=ROOT, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + assert "nature-workflow executable route demo: PASS" in result.stdout + assert "checked route cases: 7" in result.stdout + + +def test_tracked_product_demo_generates_research_artifacts(tmp_path: Path) -> None: + pytest.importorskip("matplotlib") + pytest.importorskip("numpy") + pytest.importorskip("pptx") + + output_dir = tmp_path / "product-demo" + + result = subprocess.run( + [ + sys.executable, + str(FIXTURE_DIR / "run_product_demo.py"), + "--output-dir", + str(output_dir), + ], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ) + + assert "nature-workflow product demo: PASS" in result.stdout + + expected_files = ( + "README.md", + "inputs/source-data.csv", + "route-cards.md", + "figures/nature-workflow-product-demo.svg", + "figures/nature-workflow-product-demo.pdf", + "figures/nature-workflow-product-demo.png", + "figures/figure-caption.md", + "figures/figure-qa.md", + "writing/abstract-before.md", + "writing/abstract-polished.md", + "data/data-availability.md", + "slides/nature-workflow-product-demo.pptx", + "slides/pptx-inspect.md", + "qa/product-demo-verification.md", + ) + for rel_path in expected_files: + assert (output_dir / rel_path).exists(), rel_path + + manifest = (output_dir / "README.md").read_text(encoding="utf-8") + qa = (output_dir / "qa" / "product-demo-verification.md").read_text(encoding="utf-8") + figure_qa = (output_dir / "figures" / "figure-qa.md").read_text(encoding="utf-8") + polished = (output_dir / "writing" / "abstract-polished.md").read_text(encoding="utf-8") + + for required in ( + "nature-figure", + "nature-polishing", + "nature-data", + "nature-paper2ppt", + "direct upstream preferred", + "ScholarAIO fallback", + ): + assert required in manifest + + assert "No invented accession IDs" in qa + assert "source-data.csv" in figure_qa + assert "This demo uses synthetic data" in polished + + +def test_product_demo_script_guards_optional_dependencies() -> None: + script = (FIXTURE_DIR / "run_product_demo.py").read_text(encoding="utf-8") + + assert "importlib.util.find_spec" in script + assert "nature-workflow product demo requires optional plotting/Office dependencies" in script + assert "import matplotlib" not in script.split("def _make_figure", maxsplit=1)[0] + assert "from pptx import" not in script.split("def _write_pptx", maxsplit=1)[0] + + +def test_tracked_demo_fixture_covers_full_upstream_skill_index() -> None: + manifest = (FIXTURE_DIR / "00-artifact-manifest.md").read_text(encoding="utf-8") + route_matrix = (FIXTURE_DIR / "02-route-matrix.md").read_text(encoding="utf-8") + demo_cases = (FIXTURE_DIR / "demo_cases.json").read_text(encoding="utf-8") + + combined = f"{manifest}\n{route_matrix}\n{demo_cases}" + for skill_name in UPSTREAM_SKILLS: + assert skill_name in combined + + +def test_tracked_demo_fixture_records_direct_use_policy() -> None: + policy = (FIXTURE_DIR / "01-upstream-policy.md").read_text(encoding="utf-8").lower() + + for required in ( + "use the original upstream nature-* skill directly", + "do not emulate", + "do not copy only skill.md", + "whole skill directory", + "skills/_shared", + ): + assert required in policy + + +def test_tracked_demo_fixture_is_not_submission_package_only() -> None: + route_matrix = (FIXTURE_DIR / "02-route-matrix.md").read_text(encoding="utf-8").lower() + + assert "submission package is only one scenario" in route_matrix + for scenario in ("reader", "paper2ppt", "figure", "polishing", "academic search"): + assert scenario in route_matrix diff --git a/tests/test_nature_workflow_skill_contract.py b/tests/test_nature_workflow_skill_contract.py new file mode 100644 index 0000000..4580f64 --- /dev/null +++ b/tests/test_nature_workflow_skill_contract.py @@ -0,0 +1,119 @@ +"""Contract tests for the nature-workflow bridge skill.""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SKILL_DIR = ROOT / ".claude" / "skills" / "nature-workflow" +SKILL_FILE = SKILL_DIR / "SKILL.md" +REFERENCES_DIR = SKILL_DIR / "references" + +UPSTREAM_SKILLS = { + "nature-figure", + "nature-polishing", + "nature-writing", + "nature-reviewer", + "nature-citation", + "nature-data", + "nature-reader", + "nature-response", + "nature-paper2ppt", + "nature-academic-search", +} + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8").lower() + + +def test_nature_workflow_skill_has_bridge_reference_files() -> None: + expected = { + "upstream-skill-map.md", + "bridge-policy.md", + "quickstart.md", + "upstream-install.md", + } + + assert SKILL_DIR.exists() + assert {path.name for path in REFERENCES_DIR.glob("*.md")} >= expected + + +def test_nature_workflow_uses_upstream_skills_directly_when_available() -> None: + combined = "\n".join( + [ + _read(SKILL_FILE), + _read(REFERENCES_DIR / "bridge-policy.md"), + ] + ) + + for required in ( + "use the original upstream nature-* skill directly", + "do not emulate", + "do not copy only skill.md", + "whole skill directory", + "skills/_shared", + ): + assert required in combined + + +def test_nature_workflow_documents_executable_upstream_install_paths() -> None: + text = _read(REFERENCES_DIR / "upstream-install.md") + + for required in ( + "codex plugin marketplace add https://github.com/yuan1z0825/nature-skills --ref main", + "codex plugin add nature-skills@nature-skills", + "cp -r skills/_shared", + "for d in skills/nature-*", + "copy the whole skill directory", + ): + assert required in text + + +def test_nature_workflow_covers_full_upstream_skill_index() -> None: + combined = "\n".join( + [ + _read(SKILL_FILE), + _read(REFERENCES_DIR / "upstream-skill-map.md"), + ] + ) + + for skill_name in UPSTREAM_SKILLS: + assert skill_name in combined + + +def test_nature_workflow_maps_to_existing_scholaraio_fallbacks() -> None: + text = _read(REFERENCES_DIR / "upstream-skill-map.md") + + for skill_name in ( + "/draw", + "/writing-polish", + "/paper-writing", + "/review-response", + "/citation-check", + "/search", + "/show", + "/translate", + "/document", + ): + assert skill_name in text + + +def test_nature_workflow_is_not_submission_package_only() -> None: + combined = "\n".join( + [ + _read(SKILL_FILE), + _read(REFERENCES_DIR / "quickstart.md"), + ] + ) + + for scenario in ( + "figure", + "polishing", + "writing", + "reader", + "paper2ppt", + "academic search", + ): + assert scenario in combined + assert "submission package is only one scenario" in combined diff --git a/tests/test_skill_routing_smoke.py b/tests/test_skill_routing_smoke.py index 8c6c0de..463a73a 100644 --- a/tests/test_skill_routing_smoke.py +++ b/tests/test_skill_routing_smoke.py @@ -17,14 +17,22 @@ SKILLS_DIR = ROOT / ".claude" / "skills" PRIORITY_TOKENS = { + "availability", + "checklist", + "figure", + "journal", + "nature", "poster", + "polishing", "report", "briefing", "rebuttal", "slides", "ppt", + "paper2ppt", "review", "section", + "submission", "guided", "reading", } @@ -40,6 +48,14 @@ "guided reading": 4.0, "deep reading": 4.0, "single paper": 3.0, + "nature figure": 4.0, + "nature style": 4.0, + "nature-style": 4.0, + "paper-to-ppt": 4.0, + "high-impact journal": 4.0, + "high-impact journal major revision": 4.0, + "nature communications": 4.0, + "nature-specific academic-search": 4.0, } @@ -127,3 +143,73 @@ def test_guided_single_paper_prompt_prefers_paper_guided_reading_skill() -> None assert top_name == "paper-guided-reading" assert top_score > 0 + + +def test_nature_submission_package_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill( + "Prepare my Nature Communications submission package: abstract polish, figures, citations, and data availability" + ) + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_high_impact_revision_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill("Plan a high-impact journal major revision response and submission checklist") + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_data_availability_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill("Prepare a Data Availability statement for a Nature submission") + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_nature_figure_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill("Make a Nature figure with Python from this result table") + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_nature_polishing_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill("Use Nature-style polishing on this abstract") + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_nature_paper2ppt_prompt_prefers_nature_workflow_router() -> None: + top_name, top_score = _top_skill("Turn this paper into a Chinese journal-club PPT in Nature style") + + assert top_name == "nature-workflow" + assert top_score > 0 + + +def test_generic_prose_polish_stays_with_writing_polish_skill() -> None: + top_name, top_score = _top_skill("Polish this manuscript paragraph for clarity without changing the claims") + + assert top_name == "writing-polish" + assert top_score > 0 + + +def test_generic_academic_search_stays_outside_nature_workflow() -> None: + top_name, top_score = _top_skill("I need academic search for this literature review") + + assert top_name != "nature-workflow" + assert top_score > 0 + + +def test_generic_data_availability_stays_outside_nature_workflow() -> None: + top_name, top_score = _top_skill("Prepare a Data Availability statement for this manuscript") + + assert top_name != "nature-workflow" + assert top_score > 0 + + +def test_nature_workflow_phrase_bonuses_are_not_unqualified_generic_triggers() -> None: + for phrase in ("data availability", "academic search", "major revision", "submission package", "journal-club ppt"): + assert phrase not in PHRASE_BONUSES diff --git a/tests/test_writing_docs_alignment.py b/tests/test_writing_docs_alignment.py index cf76306..57af01a 100644 --- a/tests/test_writing_docs_alignment.py +++ b/tests/test_writing_docs_alignment.py @@ -88,3 +88,66 @@ def test_round_11_docs_index_mentions_writing_router() -> None: assert "guided deep reading" in content.lower() assert "posters" in content assert "technical reports" in content + + +def test_nature_workflow_quickstart_is_documented_and_linked() -> None: + quickstart = _read("docs/guide/nature-workflow-quickstart.md") + for token in ( + "# Nature Workflow Quick Start", + "When To Use It", + "Inputs To Provide", + "Common Scenarios", + "Example Prompts", + "Expected Outputs", + "Production Demo", + "Guardrails", + ): + assert token in quickstart + + assert "workspace/_system/issue-107-routing-eval/product-demo" not in quickstart + + writing_guide = _read("docs/guide/writing.md") + assert "nature-workflow-quickstart.md" in writing_guide + + mkdocs = _read("mkdocs.yml") + assert "Nature Workflow Quick Start: guide/nature-workflow-quickstart.md" in mkdocs + + +def test_nature_workflow_quickstart_has_executable_demo_and_install_commands() -> None: + quickstart = _read("docs/guide/nature-workflow-quickstart.md") + + for token in ( + "git clone https://github.com/Yuan1z0825/nature-skills.git", + "cd nature-skills", + "cp -R skills/_shared", + "for d in skills/nature-*", + "run_demo.py --output", + "run_product_demo.py --output-dir", + "route-cards.md", + "nature-workflow-product-demo", + ): + assert token in quickstart + + +def test_nature_workflow_is_registered_on_all_agent_surfaces() -> None: + for rel_path in ("AGENTS.md", "CLAUDE.md", "AGENTS_CN.md", "README.md", "README_CN.md"): + assert "nature-workflow" in _read(rel_path) + + clawhub = _read("clawhub.yaml") + assert "scholaraio/nature-workflow" in clawhub + assert "path: .claude/skills/nature-workflow" in clawhub + + +def test_old_journal_submission_router_is_not_left_in_public_docs() -> None: + for rel_path in ( + "AGENTS.md", + "AGENTS_CN.md", + "CLAUDE.md", + "README.md", + "README_CN.md", + "clawhub.yaml", + "docs/guide/agent-reference.md", + "docs/guide/writing.md", + "mkdocs.yml", + ): + assert "journal-submission" not in _read(rel_path)