commit 28e04853f12c19e5dadf86c7f7e242866c392da5 Author: qyh15 Date: Wed May 27 21:14:31 2026 +0800 Initial QQwrite skill diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..421b7f6 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,75 @@ +--- +name: qqwrite +description: Adjust manuscript Word documents to match journal or group writing templates. Use when the user asks QQwrite to format, restructure, clean, or align a .docx manuscript according to a Word template from the local template library, especially for scientific manuscripts, AFM-style templates, section order, title page metadata, abstract/keywords placement, headings, captions, references, SI pointers, and submission-ready Word formatting. +--- + +# QQwrite + +QQwrite is the Word-template execution skill for manuscript writing workflows. Its current scope is intentionally narrow: use a template from the local template library to adjust a user's manuscript Word file while preserving scientific meaning. + +## Core Rule + +Do not overwrite the source manuscript. Always create a revised copy and a short Markdown report. + +Default outputs: + +- `*_qqwrite_adjusted.docx` +- `*_qqwrite_report.md` + +## Template Library + +Use bundled templates first: + +- `assets/templates/afm/article-template.docx` for Advanced Functional Materials-style manuscript formatting. + +If the user provides another template path, use the user-provided template instead. + +For template handling details, read `references/template-adjustment.md`. + +## Workflow + +1. Identify inputs: + - source manuscript `.docx` + - template `.docx` + - target journal or template name if provided + - output directory + +2. Preflight both documents: + - inspect section order and heading hierarchy + - compare title page fields, abstract, keywords, main text, methods, references, captions, acknowledgements, and supporting information pointers + - inspect paragraph styles, table count, figure-caption patterns, references section, and obvious empty or duplicated sections + - run `scripts/docx_template_audit.py` when a quick structural report is useful + +3. Adjust only formatting and template structure unless the user explicitly asks for rewriting: + - move or relabel sections to match the template + - normalize heading levels + - normalize title, author, affiliation, abstract, keywords, captions, references, and acknowledgements placement + - preserve scientific claims, numbers, units, equations, citations, and figure/table labels + - flag unclear template conflicts in the report instead of guessing silently + +4. Produce the revised Word file: + - copy the original manuscript first + - apply template-compatible styles and section order + - keep original content recoverable + - avoid converting citations to plain text unless unavoidable and reported + +5. Produce the report: + - list template used + - list changed sections and formatting changes + - list unresolved issues requiring author decision + - list any content that appears missing relative to the template + +## Boundaries + +- QQwrite does not decide manuscript novelty, literature strength, or reviewer risk. Route those tasks to QQsci. +- QQwrite does not generate Zotero/DeepSeek literature notes. Route those tasks to QQnote. +- QQwrite may lightly fix obvious Word-format inconsistencies, but substantial scientific rewriting belongs in QQsci or a future writing module expansion. +- For citation suggestions inside Word, follow QQsci's DOI-only comment convention if the task explicitly involves citation comments. + +## Current Capability + +Only one capability is active now: + +`template-adjust-docx`: adjust a manuscript `.docx` according to a `.docx` template from the local library. + +Future capabilities can be added later, but keep this skill focused on Word-template execution. diff --git a/agents/openai.yaml b/agents/openai.yaml new file mode 100644 index 0000000..037eeba --- /dev/null +++ b/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "QQwrite" + short_description: "Adjust manuscript Word documents to match writing templates." + default_prompt: "Help me adjust this manuscript Word document using a template from the template library. Preserve scientific meaning, compare structure and formatting with the template, and produce a revised .docx plus a short Markdown report." diff --git a/assets/templates/afm/article-template.docx b/assets/templates/afm/article-template.docx new file mode 100644 index 0000000..50ae265 Binary files /dev/null and b/assets/templates/afm/article-template.docx differ diff --git a/references/template-adjustment.md b/references/template-adjustment.md new file mode 100644 index 0000000..3ca4626 --- /dev/null +++ b/references/template-adjustment.md @@ -0,0 +1,62 @@ +# Word Template Adjustment + +## Goal + +Make the user's manuscript follow the selected Word template without changing the scientific meaning. + +## What To Compare + +Compare these elements before editing: + +- title page fields: title, authors, affiliations, corresponding author, abstract, keywords +- section order: introduction, results/discussion, conclusion, experimental/methods, acknowledgements, conflict of interest, data availability, references +- heading hierarchy and visible heading text +- figure and table caption style and numbering +- references section placement and numbering style +- SI references, graphical abstract/TOC notes, highlights, and cover letter cross-references when present +- page setup, margins, columns, headers/footers, and line spacing when detectable + +## Editing Rules + +- Work on a copied `.docx`; never overwrite the source. +- Prefer Word-native styles when available. +- Preserve all numbers, units, chemical formulas, gene/protein names, sample names, equations, citation markers, figure labels, and table labels. +- Do not silently delete content that has no obvious template location. Move it to the closest suitable section or flag it in the report. +- If the template contains placeholder text, replace only with matching manuscript content; remove unused placeholders in the revised copy. +- Keep references and citations intact. If field codes are lost by tooling, report that explicitly. + +## Report Format + +Use this Markdown shape: + +```markdown +# QQwrite Template Adjustment Report + +## Inputs +- Manuscript: +- Template: +- Output: + +## Changes Made +- ... + +## Missing Or Unclear Items +- ... + +## Checks +- Section order: +- Heading hierarchy: +- Captions: +- References: +- Citation fields: +``` + +## When To Stop And Ask + +Ask the user before continuing if: + +- the manuscript and template are for clearly different article types +- the manuscript has no recognizable title/abstract/main sections +- a template-required section is missing and cannot be inferred +- editing would require scientific rewriting rather than formatting/structure adjustment +- citation fields, equations, or embedded objects are at risk of being flattened diff --git a/scripts/docx_template_audit.py b/scripts/docx_template_audit.py new file mode 100644 index 0000000..e15c6f9 --- /dev/null +++ b/scripts/docx_template_audit.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Create a lightweight structural audit for a manuscript/template DOCX pair. + +This script intentionally uses only the Python standard library. It inspects the +OOXML package directly and reports headings, paragraph-style usage, section-like +paragraphs, table count, and image count. It does not modify documents. +""" + +from __future__ import annotations + +import argparse +import collections +import json +import re +import sys +import zipfile +from pathlib import Path +from xml.etree import ElementTree as ET + + +NS = { + "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "a": "http://schemas.openxmlformats.org/drawingml/2006/main", +} + + +SECTION_PATTERNS = [ + "abstract", + "keywords", + "introduction", + "results", + "discussion", + "conclusion", + "experimental", + "methods", + "acknowledgements", + "acknowledgments", + "conflict of interest", + "data availability", + "references", +] + + +def read_xml(zf: zipfile.ZipFile, name: str) -> ET.Element | None: + try: + return ET.fromstring(zf.read(name)) + except KeyError: + return None + + +def text_from_paragraph(p: ET.Element) -> str: + parts = [] + for t in p.findall(".//w:t", NS): + if t.text: + parts.append(t.text) + return "".join(parts).strip() + + +def style_from_paragraph(p: ET.Element) -> str: + style = p.find("./w:pPr/w:pStyle", NS) + if style is None: + return "(none)" + return style.attrib.get(f"{{{NS['w']}}}val", "(unknown)") + + +def inspect_docx(path: Path) -> dict: + with zipfile.ZipFile(path) as zf: + document = read_xml(zf, "word/document.xml") + if document is None: + raise ValueError(f"{path} does not contain word/document.xml") + + paragraphs = document.findall(".//w:p", NS) + tables = document.findall(".//w:tbl", NS) + drawings = document.findall(".//w:drawing", NS) + + style_counts: collections.Counter[str] = collections.Counter() + headings = [] + section_hits = [] + nonempty_count = 0 + + for index, p in enumerate(paragraphs, start=1): + text = text_from_paragraph(p) + style = style_from_paragraph(p) + style_counts[style] += 1 + if not text: + continue + nonempty_count += 1 + normalized = re.sub(r"\s+", " ", text).strip().lower() + if style.lower().startswith("heading") or style.lower().startswith("title"): + headings.append({"index": index, "style": style, "text": text[:160]}) + if any(pattern == normalized or normalized.startswith(pattern + ":") for pattern in SECTION_PATTERNS): + section_hits.append({"index": index, "style": style, "text": text[:160]}) + + return { + "path": str(path), + "paragraphs": len(paragraphs), + "nonempty_paragraphs": nonempty_count, + "tables": len(tables), + "drawings": len(drawings), + "top_styles": style_counts.most_common(20), + "headings": headings[:80], + "section_hits": section_hits[:80], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description="Audit a manuscript DOCX against a template DOCX.") + parser.add_argument("--manuscript", required=True, type=Path) + parser.add_argument("--template", required=True, type=Path) + parser.add_argument("--out", type=Path, help="Optional JSON output path.") + args = parser.parse_args() + + report = { + "manuscript": inspect_docx(args.manuscript), + "template": inspect_docx(args.template), + } + + payload = json.dumps(report, ensure_ascii=False, indent=2) + if args.out: + args.out.write_text(payload, encoding="utf-8") + else: + print(payload) + return 0 + + +if __name__ == "__main__": + sys.exit(main())