
Nsfc Research Content Writer
- 271 installs
- 2.6k repo stars
- Updated July 20, 2026
- huangwb8/chineseresearchlatex
nsfc-research-content-writer is an agent skill at version 0.2.3 that drafts NSFC grant section (二) research content, innovations, and three-year annual plans as LaTeX extraTex files for developers and researchers using C
About
nsfc-research-content-writer is an agent skill from huangwb8/chineseresearchlatex version 0.2.3 that orchestrates NSFC proposal section (二) writing. It decomposes 3–4 verifiable subgoals, drafts 2.1 research content using problem-to-goal-to-route-to-validation logic, derives 2.2 innovation claims mapped to 2.1 tasks, and builds a three-year milestone plan in 2.3 without cross-year overlap. Output lands in three extraTex files: extraTex/2.1.研究内容.tex, extraTex/2.2.特色与创新.tex, and extraTex/2.3.年度研究计划.tex. Preview mode returns paste-ready drafts; apply mode writes only those three targets without touching main.tex or .cls files. Researchers and academic developers reach for this skill when NSFC Young or general NSFC templates need internally consistent, LaTeX-ready research narratives aligned to references/info_form.md baselines.
- NSFC research plan sections
- Methods and milestones
- Template-aligned structure
- Objectives and outcomes
- Chinese funding format
Nsfc Research Content Writer by the numbers
- 271 all-time installs (skills.sh)
- +3 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #465 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/huangwb8/chineseresearchlatex --skill nsfc-research-content-writerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 271 |
|---|---|
| repo stars | ★ 2.6k |
| Last updated | July 20, 2026 |
| Repository | huangwb8/chineseresearchlatex ↗ |
How do you write NSFC research content in LaTeX?
Produce NSFC research plan content—objectives, methods, timeline, and expected outcomes—for grant proposals following Chinese funding template requirements.
Who is it for?
Researchers using ChineseResearchLaTeX NSFC templates who need aligned 2.1/2.2/2.3 LaTeX fragments with consistent terminology and verifiable milestones.
Skip if: Non-Chinese grant proposals, teams not using the ChineseResearchLaTeX extraTex structure, or writers who need main.tex or document-class edits.
When should I use this skill?
The developer asks to write or revise NSFC 研究内容, 特色与创新, or 年度研究计划 sections for a LaTeX grant template.
What you get
Three extraTex LaTeX files (2.1, 2.2, 2.3) with aligned subgoals S1–S4, innovation claims, and verifiable three-year milestones.
- 2.1 research content .tex
- 2.2 innovations .tex
- 2.3 annual plan .tex
By the numbers
- Skill version 0.2.3
- Outputs 3 extraTex LaTeX files for NSFC section (二)
- Decomposes research into 3–4 verifiable subgoals
Files
NSFC(二)研究内容编排写作器
与 bensz-collect-bugs 的协作约定
- 当用户环境中出现因本 skill 设计缺陷导致的 bug 时,优先使用
bensz-collect-bugs按规范记录到~/.bensz-skills/bugs/,严禁直接修改用户本地 Claude Code / Codex 中已安装的 skill 源码。 - 若 AI 仍可通过 workaround 继续完成用户任务,应先记录 bug,再继续完成当前任务。
- 当用户明确要求“report bensz skills bugs”等公开上报动作时,调用本地
gh与bensz-collect-bugs,仅上传新增 bug 到huangwb8/bensz-bugs;不要 pull / clone 整个 bug 仓库。
目标输出(契约)
- 写入落点(3 个文件):
extraTex/2.1.研究内容.texextraTex/2.2.特色与创新.texextraTex/2.3.年度研究计划.tex- 禁止改动:
main.tex、extraTex/@config.tex、任何.cls/.sty - 编排原则:先把
2.1写成“可验证闭环”,再从2.1抽取创新点生成2.2,最后把2.1的任务拆分成三年里程碑生成2.3。
参数与输出模式(建议显式提供)
project_root:标书项目根目录(如projects/NSFC_Young)output_mode(默认apply):preview:不直接写入文件;输出三段可复制粘贴的 LaTeX 正文草稿,并标注应写入的目标文件路径apply:仅写入三份目标文件(见“目标输出”),不触碰其他文件
必需输入(最小信息表)
- 若用户未提供,请先收集/补全:references/info_form.md
写入安全约束(必须遵守)
1. 仅编辑三份 extraTex/2.*.tex 文件;不得修改 main.tex、extraTex/@config.tex、任何 .cls/.sty 2. 目标文件若已包含标题命令(如 \\subsection{...} / \\subsubsection{...}),只替换正文内容,不改标题与结构层级 3. 信息不全时先提问补齐,不要用“看起来像真的”的细节硬写
工作流(按顺序执行)
1. 定位项目与目标文件:确认 project_root,读取并仅编辑三份 extraTex/2.*.tex 文件;如目标文件不存在,提示用户先初始化/拷贝模板项目。 2. 固定”子目标三件套”:把目标拆成 3–4 个子目标(内部规划时可用 S1–S4 编号便于自检回溯,此编号仅用于 AI 内部规划,禁止出现在最终 LaTeX 正文中),并对每个子目标强制写清:
- 指标(可判定/可验收)
- 对照/基线(与谁比、怎么比)
- 数据来源/验证方案(样本/实验体系/评估方法)
3. 生成 `2.1 研究内容`(以”问题→目标→内容→路线→验证”为主线):
- 篇幅控制原则(推荐值,非强制):
- 推荐页数:12–15 页(含图表),约占标书总页数(≤28 页)的 50%
- 推荐字数:12000–15000 字(纯文字部分)
- 图表策略:插入 10–20 张图通常不会显著压缩文字篇幅;图片是“提质”的重要手段
- 核心原则:评审标准已从“字数控制”转向“页数控制”,不要以字数为导向规划篇幅
- 组织逻辑框架(按研究类型选择):新版不再预设提纲,可按研究的内在逻辑自主组织:
- 基础研究推荐框架:
科学问题提出 → 研究假说 → 验证思路 → 预期结果 - 应用研究推荐框架:
技术瓶颈 → 解决方案 → 实验设计 → 效果验证 - 通用主线(兜底):
问题 → 目标 → 内容 → 路线 → 验证 - 研究问题与总体目标(不超过 2 段,用连贯段落而非条目)
- 研究内容与任务展开(以科学叙事驱动,把验证逻辑自然编织进行文,而非逐条填写三件套)
- 技术路线与验证口径(对照/消融/外部验证/泄漏防控/统计方法,融入叙述而非单独罗列)
4. 从 `2.1` 抽取 `2.2 特色与创新`:
- 1–3 条即可,少而精(调研报告强调:创新点数量不在多,在于说服力);每条从”为什么这个选择是必然的”出发,说清楚现有路线的局限、本项目的不同之处、以及这个差异预期带来什么——让评审感受到研究者真的想清楚了,而不是在填写创新点模板。
- 避免绝对化措辞(如”首次””领先”);如确需使用,必须给出可核验证据或改写为可审稿的相对表述。
5. 从 `2.1` 推导 `2.3 年度研究计划`(三年不跨年):
- 每年:年度目标 → 关键任务 → 里程碑(可验收)→ 可交付成果(论文/数据/原型/规范/软件等)
- 里程碑必须与子目标挂钩(否则评审会认为“计划与研究内容脱节”)
- 推进逻辑:让评审看到研究的依赖关系和递进节奏——第一年为什么先做这个、第二年为什么能做那个(避免“第一年做基础研究;第二年做深入研究;第三年做总结”的流水账)
6. 一致性校验:
- 检查
2.2创新点是否能回溯到2.1的具体任务与验证; - 检查
2.3里程碑是否覆盖全部子目标,且每年都有可交付物。 - 术语口径对齐:研究对象/缩写/指标命名尽量与
(一)立项依据、(三)研究基础保持一致(如项目中已存在) - 输出净化:最终写入
.tex文件前,确认正文中不含任何S1/S2/Sx/Ty/Vz等内部规划编号;如需表达对应关系,改用自然语言(如"针对第一个研究目标")
7. 任务完成后的用户提醒:
- 技术路线图建议放在研究内容开头。
验收标准(Definition of Done)
- 见:references/dod_checklist.md
写作哲学:像人类专家一样写
两阶段工作模式:
- 规划阶段(内部,不写入正文):用三件套、S1–S4 编号、验证口径菜单把研究逻辑想清楚,确保每个目标都有指标、对照和数据来源。
- 写作阶段(输出到
.tex):切换到叙事模式。把规划阶段的结论融化进连贯的段落里,让读者感受到研究者真的想清楚了,而不是在填表。
专家写作的核心特征:
- 有科学故事主线:读者能感受到"为什么做这个、为什么这样做、怎么证明做对了"的内在逻辑,而不是三个并列条目。
- 验证逻辑是叙述的一部分:不是"验证方案:对照/消融/外部验证",而是在描述研究内容时自然说明"将通过……与……对比,以排除……的干扰"。
- 创新点有说服力:不是填写"相对 A,差异在 X,预计 Y"的公式,而是从问题出发,说清楚为什么现有路线走不通、本项目的选择是必然的。
- 年度计划体现推进逻辑:不是三年的四级结构填空,而是让评审看到研究的依赖关系和递进节奏——第一年为什么先做这个、第二年为什么能做那个。
一个判断标准:写完后,把正文给一位不了解这个项目的同行看,他能否在不看任何框架标注的情况下,自然地理解这个研究的逻辑?如果能,写作是成功的。
写作小抄(可选)
- 子目标“三件套”示例:references/subgoal_triplet_examples.md
- 创新点“相对坐标系”示例:references/relative_coordinate_examples.md
- 年度计划模板(确保里程碑可验收):references/yearly_plan_template.md
- 三个输出文件的最小结构骨架(可复制粘贴):references/output_skeletons.md
- 研究内容篇幅控制参考(页数/字数/图表策略):references/page_budget.md
- 常见写作反模式与改写:references/anti_patterns.md
- 验证口径菜单(对照/消融/外部验证/统计/泄漏防控):references/validation_menu.md
- 术语口径对齐表(跨章节一致):references/terminology_sheet.md
变更记录
- 本技能不在本文档内维护变更历史;统一记录在根级
CHANGELOG.md。
Changelog — nsfc-research-content-writer
格式基于 Keep a Changelog。
[Unreleased]
(暂无)
[1.0.0] - 2026-02-24
Changed
config.yaml:版本号0.2.3 → 1.0.0,标记为正式稳定版本
# ================================
# nsfc-research-content-writer 默认配置
# ================================
skill_info:
name: nsfc-research-content-writer
version: 1.0.0
# template_year: 移除年份限制,使技能适用于不同年份的 NSFC 模板
description: NSFC 标书正文“(二)研究内容”写作/重构,并同步编排“特色与创新”“三年年度研究计划”,输出 2.1/2.2/2.3 三份 extraTex 文件
category: writing
author: "Bensz Conan"
parameters:
project_root:
type: string
required: true
description: 项目根目录(如 projects/NSFC_Young 或你的标书项目路径)
output_mode:
type: string
required: false
default: apply
allowed_values: [preview, apply]
targets:
research_content_tex: extraTex/2.1.研究内容.tex
innovation_tex: extraTex/2.2.特色与创新.tex
yearly_plan_tex: extraTex/2.3.年度研究计划.tex
guardrails:
allowed_write_files:
- extraTex/2.1.研究内容.tex
- extraTex/2.2.特色与创新.tex
- extraTex/2.3.年度研究计划.tex
forbidden_write_files:
- main.tex
- extraTex/@config.tex
forbidden_write_globs:
- "**/*.cls"
- "**/*.sty"
quality_contract:
research_content_must_cover:
- 研究问题与总体目标
- 3–4 个可检验子目标(指标/对照/数据来源)
- 研究内容与任务分解(对齐子目标)
- 技术路线与验证口径(防泄漏/对照/消融/统计)
innovation_must_be:
- 可理解(评审能看懂)
- 可对照(相对坐标系)
- 可验证(至少有指标或验证实验)
yearly_plan_must_include:
- 三年分年任务(不跨年)
- 里程碑与可交付成果(每年都有)
# scripts/check_project_outputs.py 的轻量自检参数(可选覆盖默认值)
checks:
# 绝对化措辞:通常建议避免;脚本可扫描并给出 WARN/ERROR(见 --fail-on-risk-phrases)
risk_phrases:
- 首次
- 领先
- 填补空白
- 突破性
- 国际领先
- 世界领先
# 研究内容里至少出现多少个子目标标记(默认 3):S1/S2/S3(可多于 3)
subgoal_markers_min: 3
nsfc-research-content-writer
用于 NSFC 标书正文 (二)研究内容 的写作/重构,并同时编排:
2.2 特色与创新2.3 年度研究计划
目标是形成“研究内容 → 创新点 → 年度计划”的一致闭环。
参数(建议显式提供)
project_root:标书项目根目录(如projects/NSFC_Young)output_mode(默认apply)preview:只输出三段可复制的 LaTeX 草稿,不写文件apply:仅写入三份目标文件(不触碰其他文件)
推荐用法(Prompt 模板)
请使用 nsfc-research-content-writer:
project_root:projects/NSFC_Young
信息表:请按 skills/nsfc-research-content-writer/references/info_form.md 提供
输出:写入 extraTex/2.1.研究内容.tex、extraTex/2.2.特色与创新.tex、extraTex/2.3.年度研究计划.tex
output_mode:apply(默认)/ preview(只预览不写入)
研究类型:基础研究 / 应用研究(用于选择更贴合的组织框架;不确定时按“问题→目标→内容→路线→验证”通用主线组织)
篇幅目标(推荐):研究内容 12–15 页(含图表),纯文字约 12000–15000 字;以“页数控制”为主,不要以字数为导向
额外要求:子目标编号 S1–S4;每个子目标必须写清 指标+对照+数据来源;2.2/2.3 标注回溯到对应 Sx
禁止改动:不要改 main.tex、extraTex/@config.tex、任何 .cls/.sty篇幅与图表(写作提醒)
- 研究内容推荐页数:12–15 页(含图表),约占标书总页数(≤28 页)的 50%
- 研究内容推荐字数:12000–15000 字(纯文字部分)
- 评审标准已从“字数控制”转向“页数控制”,建议先按页数规划结构,再用图表提质
- 技术路线图建议放在研究内容开头
- 参考:
skills/nsfc-research-content-writer/references/page_budget.md
推荐工作流(先预览再写入)
1. output_mode=preview 生成三份草稿(用于审阅口径与结构)
- 可参考
skills/nsfc-research-content-writer/references/output_skeletons.md的最小结构骨架快速起草
2. 人工确认后切换 output_mode=apply 写入三份 extraTex/2.*.tex 文件
验收自检
- 按
skills/nsfc-research-content-writer/references/dod_checklist.md快速自检(重点看 2.2 可回溯、2.3 覆盖 S1–S4) - 可选脚本自检(只读):
python3 skills/nsfc-research-content-writer/scripts/check_project_outputs.py --project-root projects/NSFC_Young - 更严格(将“首次/领先”等绝对化措辞视为错误):
python3 skills/nsfc-research-content-writer/scripts/check_project_outputs.py --project-root projects/NSFC_Young --fail-on-risk-phrases - 一键执行(先校验 skill 再自检输出):
python3 skills/nsfc-research-content-writer/scripts/run_checks.py --project-root projects/NSFC_Young --fail-on-risk-phrases
开发者:一致性校验与可追溯测试会话
- 校验(必需):
python3 skills/nsfc-research-content-writer/scripts/validate_skill.py - 创建 A/B 轮会话骨架(在本 skill 目录下执行):
- A轮:
python3 scripts/create_test_session.py --kind a --id vYYYYMMDDHHMM --create-plan - B轮:
python3 scripts/create_test_session.py --kind b --id vYYYYMMDDHHMM --create-plan
常见写作反模式
用于快速自检“研究内容 / 创新 / 年度计划”是否写成了空话、流水账或内部规划稿。
高频反模式
1. 绝对化自夸
- 错:首次提出、国际领先、填补空白
- 对:相对主流路线说明差异,并写清验证方式
2. 堆方法不说解决什么
- 错:深度学习、图网络、注意力、多模态融合一起堆
- 对:每个方法都要绑定到具体目标、指标和对照
3. 指标不可判定
- 错:显著提升、效果更好
- 对:写清指标、阈值、对照基线和统计口径
4. 对照缺失
- 错:证明方法有效
- 对:至少有主流基线和一个消融
5. 数据来源含糊
- 错:使用真实数据/公开数据
- 对:写明来源、时间窗、纳排、样本量和泄漏防控
6. 创新点与研究内容脱节
- 错:创新点只是口号
- 对:创新点必须能在研究内容里找到落点与证据
7. 年度计划流水账
- 错:第一年基础、第二年深入、第三年总结
- 对:每年都要有目标、任务、里程碑、可交付成果
8. 内部编号渗透正文
- 错:
S1/T2/V3 - 对:全部改成自然语言
9. 把规划框架直接粘进正文
- 错:目标、指标、对照全按条目硬塞
- 对:先讲科学问题,再自然展开验证逻辑
10. 只看字数不看页数
- 错:字够多就算详细
- 对:按页数和阅读负担控制密度
Definition of Done((二)研究内容 + 特色与创新 + 年度计划)
A. 交付物与边界
- 输出落点正确:只写
extraTex/2.1.研究内容.tex、extraTex/2.2.特色与创新.tex、extraTex/2.3.年度研究计划.tex - 不破坏模板结构:不修改
main.tex、不修改extraTex/@config.tex,不改任何.cls/.sty;目标文件若含标题命令,只替换正文
B. 研究内容闭环(2.1)
- 子目标数量与结构:3–4 个子目标(建议编号
S1–S4) - 子目标“三件套”齐全:每个子目标都有 指标 + 对照/基线 + 数据来源/验证方案
- 任务分解对齐子目标:
2.1的任务分解能逐条对应到S1–S4,且每条任务都有验证口径(对照/消融/外部验证/统计)
C. 创新点可审稿(2.2)
- 创新点数量:1–3 条(少而准)
- 相对坐标系:每条创新点都写清“相对对象 A/B + 差异点 + 预期收益 + 验证方式”
- 可回溯性:每条创新点能回溯到
2.1的具体子目标/任务/验证(内部自检时可用Sx/Ty标注,最终正文中须改为自然语言表述) - 风险措辞检查:避免绝对化/自我定性词(如“首次/领先/填补空白/突破性/国际领先”);如必须使用,需给出可核验依据或改写为相对表述
D. 年度计划可验收(2.3)
- 三年分年任务(不跨年)
- 每年都有:年度目标 → 关键任务 → 里程碑(可验收)→ 可交付成果
- 子目标覆盖:
2.3的三年里程碑覆盖全部S1–S4,且每个子目标至少有一个明确的“可验收里程碑”
E. 术语一致(跨章节)
- 研究对象/缩写/指标口径一致:与
(一)立项依据、(三)研究基础(如已存在)保持一致 - 首次出现给出缩写与定义;全文缩写保持一致(不要同一概念多种叫法)
快速自检方法(建议)
1. 在 2.1 里列出 S1–S4,并在每个任务/验证处标注对应的 Sx(仅用于内部自检,不写入正文) 2. 在 2.2 的每条创新点末尾确认能回溯到 2.1 的具体任务与验证(正文中用自然语言表述,不出现 `Sx/Ty/Vz` 等编号) 3. 在 2.3 的每个里程碑处确认三年里程碑覆盖所有子目标(正文中用自然语言表述,不出现 `Sx` 等编号) 4. 输出净化:写入 .tex 文件前,全文检索 S1/S2/S3/S4/Sx/Ty/Vz,确认正文中无任何内部规划编号残留
NSFC 写作信息表(面向(二)研究内容)
请尽量一次性提供(缺失则本技能会先提问):
1. 研究对象/应用场景:一句话边界(人群/疾病/材料/系统/任务)。 2. 核心科学问题与假说:1 句假说 + 1–3 条关键科学问题。 3. 总体目标:一句话目标(可被验收)。 4. 子目标(3–4 个,强制可检验):对每个子目标给出
- 指标(量化/可判定)
- 对照/基线(用谁做对照)
- 数据来源/样本/实验体系
- 预期结果(不绝对化)
5. 技术路线概览:输入→处理→模型/实验→验证→交付。 6. 关键方法与验证:
- 关键算法/实验手段
- 统计/机器学习验证口径(交叉验证、外部验证、误差来源、泄漏防控等)
7. 年度资源约束(可选):样本量上限、算力/设备、伦理合规周期、合作单位等。
8. 任务分解(建议提供):围绕 3–4 个子目标,每个子目标拆 2–4 个关键任务(方法/实验/数据/系统/验证),并标注依赖关系(先做什么、后做什么)。 9. 创新对照坐标系(建议提供):你希望与哪些主流路线 A/B 对比?对比维度是什么(机制/假设/数据/验证体系/可解释性/可部署性等)? 10. 三年计划的硬约束(建议提供):哪些任务必须在第 1 年完成(如伦理、样本获取、平台搭建)?哪些成果希望每年都有(论文/软件/数据/原型/规范)? 11. 关键风险与备选路线(可选但推荐):列出 2–3 个主要风险(技术/数据/进度/资源),并给出可执行的备选方案/降级目标。
三个输出文件的最小结构骨架(可复制粘贴)
用途:在 output_mode=preview 时先按“最小结构”生成草稿;在 apply 时按相同结构落到目标文件,确保 2.1→2.2→2.3 可回溯闭环。
通用规则(先读)
约定(仅用于 AI 内部规划与自检,禁止出现在最终 `.tex` 正文中):
Sx:子目标编号(如S1)Ty:任务编号(如T1,可选)Vz:验证编号(如V1,可选)
1. 若目标 *.tex 已包含标题命令(如 \subsection{...} / \subsubsection{...}),只替换标题命令之后的正文,不改标题与层级。 2. 2.2/2.3 的回溯关系必须用自然语言表达(如”针对第一个研究目标”),不得在正文中出现 Sx/Ty/Vz 等编号。 3. 避免绝对化措辞;创新点用”相对坐标系”表达。
---
extraTex/2.1.研究内容.tex(建议结构)
(正文建议从“问题→目标→内容→路线→验证”展开;以下为最小骨架)
% (可选)研究问题与总体目标(<=2段)
% 【规划占位,不写入正文】子目标(建议 S1–S4;每条强制:指标/对照/数据来源)
% S1:指标...;对照...;数据来源/验证...
% S2:...
% S3:...
% S4(可选):...
% 研究内容与任务分解(正文中用自然语言对应子目标,不出现 S1/S2 等编号)
% 任务一(对应第一个研究目标):...(验证口径:对照/消融/外部验证/统计/防泄漏)
% 任务二(对应第二个研究目标):...
% ...
% 技术路线与验证口径(可按子目标分组写)---
extraTex/2.2.特色与创新.tex(建议结构)
(1–3 条即可;每条都要”相对对象 A/B + 差异点 X + 预期收益 Y + 验证 Z”;回溯关系用自然语言表达,不出现 Sx/Ty/Vz 等编号)
% 创新点1:相对 A/B,差异在 X,预计带来 Y;通过 Z 验证。(针对第一个研究目标)
% 创新点2:...
% 创新点3(可选):...---
extraTex/2.3.年度研究计划.tex(建议结构)
(三年不跨年;每年:年度目标→关键任务→里程碑→可交付物;里程碑与子目标的对应关系用自然语言表达,不出现 Sx 等编号)
% 第1年:年度目标...
% - 关键任务:...(围绕第一、第二研究目标)
% - 里程碑(可验收):...(完成第一研究目标的核心验证)
% - 可交付成果:论文/数据/原型/规范/软件...
% 第2年:...
% 第3年:...NSFC研究内容篇幅控制参考
官方约束
- 总页数:原则上不超过 30 页,建议控制在 28 页以内留缓冲
- 研究内容推荐页数:12–15 页(含图表),占标书总页数的约 50%
- 研究内容推荐字数:12000–15000 字(纯文字部分)
图表策略
- 本 skill 不生成图表,仅在任务结束后提醒用户
- 研究内容章节的核心图表:技术路线图
- 技术路线图建议放在研究内容开头
- 注:机制示意图属于“立项依据”章节,由
nsfc-justification-writer负责 - 注:预实验数据图属于“研究基础”章节,由
nsfc-research-foundation-writer负责
核心原则
评审标准已从“字数控制”转向“页数控制”,不要再以字数为导向规划篇幅。
格式红线
- 字体:小四楷体(不要缩小字体变相增加内容)
- 行距:1.5 倍行距(不要缩小行距)
- 页数:原则上不超过 30 页,建议控制在 28 页以内留缓冲
创新点“相对坐标系”表达示例
本文件用于帮助你把创新点写成“可审稿、可对照、可验证”的句式,降低“绝对化/自我定性”风险。
句式模板(推荐)
1. 相对对象 + 差异点 + 预期收益 + 验证方式
- 与主流路线 A/B 相比,本项目在 X(机制/假设/表示/实验设计) 上采用 Y(关键差异),预计带来 Z(性能/可靠性/可解释性) 的提升;将通过 V(对照/消融/外部验证) 进行验证。
2. 痛点约束 + 设计选择 + 可验证结论
- 针对现有方法在 C(约束/瓶颈) 上的不足,本项目选择 D(设计选择),并以 M(指标/现象) 作为主要证据链,验证其是否能带来 E(改进方向)。
示例 1(方法学创新)
- 与主流路线 A(端到端黑箱)相比,本项目引入 可检验的机制假设 与 对照实验/消融验证,预计提升结论的可解释性与可复现性;将以外部验证集与预注册评估口径作为主要证据。
示例 2(验证体系创新)
- 相比常见的单中心回顾性验证,本项目采用 跨中心/跨时间外部验证 与 泄漏防控的评估流程,以降低过拟合与数据偏倚风险;将通过统一指标口径与显著性检验报告验证可信度提升。
示例 3(工程落地创新)
- 相比仅给出算法性能的工作,本项目将“算法—原型—场景验证”打通,建立可复用的端到端评估基准;通过时延/稳定性/鲁棒性等工程指标与场景对照实验验证可部署性。
子目标“三件套”示例(指标 + 对照/基线 + 数据/验证)
本文件用于帮助你把“子目标”写成可判定、可验收的形式,避免“只有方法没有目标/验证口径”的常见问题。
示例 1(算法/模型类)
- 子目标:构建可解释的风险预测模型,提升对高风险样本的识别能力。
- 指标:AUC、AUPRC、灵敏度@固定特异度(或召回@固定误报率)、校准误差(ECE/Brier)
- 对照/基线:主流方法 A/B(例如传统统计模型/常见深度模型),以及不含关键模块的消融版本
- 数据/验证:训练/验证/测试严格划分;外部验证(独立中心/独立时间段);预注册的评估方案与显著性检验(如 DeLong)
示例 2(实验/材料类)
- 子目标:实现材料 X 的性能提升并验证其机理假说。
- 指标:关键性能指标(如强度/导电率/稳定性/循环寿命)及其统计分布;重复次数 n
- 对照/基线:现有材料/工艺 A(文献或自建基线);去掉关键组分/步骤的消融对照
- 数据/验证:表征手段(XRD/SEM 等)+ 机理验证实验;盲测/复现实验;关键参数的敏感性分析
示例 3(系统/工程类)
- 子目标:构建可部署原型系统并完成端到端验证。
- 指标:端到端准确率/时延/吞吐/资源占用;关键场景下的鲁棒性(噪声、缺失、偏移)
- 对照/基线:现有系统/流程 A;简化版原型(去掉模块)作为对照
- 数据/验证:真实场景或仿真基准;分场景评估;压力测试与失效模式分析(FMEA/故障注入)
术语口径对齐表(跨章节一致)
本文件用于减少 (一)立项依据、(二)研究内容、(三)研究基础 三部分之间的术语漂移。建议在写作前先填一遍,写作过程中持续对齐。
1) 研究对象与边界
- 研究对象(中文全称):
- 英文名(如有):
- 缩写(如有,首次出现处需给出全称):
- 适用范围/边界(人群/疾病/材料/系统/任务):
2) 关键概念与定义
| 概念/变量 | 中文名 | 英文/缩写 | 定义/口径(1 句) | 首次出现章节 |
|---|---|---|---|---|
3) 指标口径(强烈建议统一)
| 指标 | 计算/评估口径 | 阈值/分层口径(如有) | 对照/基线 | 备注 |
|---|---|---|---|---|
4) 数据/样本口径(写清一致)
| 数据来源/样本 | 时间窗/范围 | 纳排标准(摘要) | 样本量范围 | 划分策略(防泄漏) |
|---|---|---|---|---|
5) 子目标编号(用于回溯)
S1:S2:S3:S4(可选):
6) 跨章节对齐检查(建议)
- 立项依据(1.1)中的研究对象/关键指标/缩写,与本表一致
- 研究内容(2.1/2.2/2.3)中所有缩写首次出现均给出全称,并与本表一致
- 研究基础(3.1/3.2)中平台/数据/团队描述不引入新的概念叫法(如需引入,先更新本表)
验证口径菜单(可按子目标选择 2–4 项组合)
本文件用于帮助你把”验证口径”写具体,避免只有方法没有验证、或验证写得过于空泛。
使用原则:以下”参考句式”仅用于理解每类验证的核心要素,写作时应根据具体研究内容自然表达,不要逐字套用。
1) 对照(Baseline)
- 最少 1 个主流基线 A(文献/公开实现/现有流程)
- 对照口径写清:同数据、同评估指标、同划分策略
核心要素(参考,不要照抄):说明选用哪个主流方法作基线、保持相同数据划分与评估指标、对比结果如何。
2) 消融(Ablation)
- 对关键模块/关键假设做消融:去掉模块、替换模块、固定某参数
- 目标:证明“创新点/关键设计”确实贡献了增益
核心要素(参考,不要照抄):说明消融哪个模块/假设、替换方式、以及该模块对目标指标的贡献如何体现。
3) 外部验证(External Validation)
- 独立时间段/独立中心/独立数据源,避免“同源数据自嗨”
- 写清“外部”的定义:来源、时间窗、纳排标准差异
核心要素(参考,不要照抄):说明外部数据的来源/时间窗/纳排标准差异,以及用于评估泛化能力的具体指标。
4) 泄漏防控(Leakage Control)
常见检查点(按需选用):
- 训练/验证/测试严格划分(按个体/按时间/按实验批次)
- 特征工程只在训练集拟合,再应用到验证/测试
- 预处理/归一化/缺失填补的拟合过程不可使用测试信息
核心要素(参考,不要照抄):说明数据划分策略(按个体/时间/批次)、预处理拟合范围、以及如何确保测试信息不泄漏到训练过程。
5) 统计与不确定性(Statistics & Uncertainty)
按场景选用:
- 置信区间(bootstrap)、显著性检验、效应量
- 多次重复实验与方差报告
- 校准与不确定性评估(如 ECE/Brier)
核心要素(参考,不要照抄):说明选用哪种统计方法(置信区间/显著性检验/效应量)、重复实验次数、以及如何报告不确定性。
6) 鲁棒性与失效模式(Robustness & Failure Modes)
- 噪声/缺失/偏移/极端条件下的性能与稳定性
- 失败样本分析:失败类型、触发条件、缓解策略
核心要素(参考,不要照抄):说明在哪些扰动条件下测试(噪声/缺失/分布偏移)、失败样本的分类方式、以及缓解策略。
年度研究计划模板(三年不跨年)
本文件用于帮助你把 2.1 的任务拆分成"三年不跨年"的可验收计划,并确保每年都有里程碑与可交付成果。
使用方法(建议)
1. 先在内部规划中列出 3–4 个子目标(每个子目标必须有指标/对照/数据来源) 2. 对每个子目标拆 2–4 个关键任务(方法/实验/数据/系统/验证) 3. 把关键任务按依赖关系放入三年,并给出每年的里程碑与交付物 4. 写入正文时,用自然语言描述任务与目标的对应关系,不出现 S1/T1/M1 等内部编号
模板(可直接填)
第一年(基础与可行性验证)
- 年度目标:完成[第一/第二研究目标]的关键可行性验证,形成可复现实验/数据/原型基线。
- 关键任务:
- [任务描述,说明解决哪个研究问题]:……
- [任务描述,说明解决哪个研究问题]:……
- 里程碑(可验收):
- [指标 A] 达到 …(对照:…;数据:…)
- 完成外部/交叉验证流程与泄漏防控检查(证据:…)
- 可交付成果:
- 数据/代码/原型/规范/论文投稿 1 篇(可选)……
第二年(规模化验证与关键创新验证)
- 年度目标:完成[第二/第三研究目标]的规模化验证与关键创新点验证。
- 关键任务:
- [任务描述]:……
- [任务描述]:……
- 里程碑(可验收):
- 在外部数据/独立条件下达到 …(对照:…;显著性检验:…)
- 完成关键消融/对照实验,证明[核心创新点]的贡献(证据:…)
- 可交付成果:
- 论文/专利/数据集/软件版本 v1.0 ……
第三年(系统集成与场景验证)
- 年度目标:完成[第三/第四研究目标]的系统集成与场景化验证,形成可总结的证据链。
- 关键任务:
- [任务描述]:……
- [任务描述]:……
- 里程碑(可验收):
- 端到端验证通过(指标:…;鲁棒性:…;失败模式:…)
- 形成可复用流程/规范/数据资源(证据:…)
- 可交付成果:
- 结题可用的原型/标准/软件、论文 1–2 篇、开源/数据发布(如允许)……
from __future__ import annotations
import re
from typing import Iterable
def extract_yaml_value_under_block(lines: Iterable[str], block_key: str, key: str) -> str | None:
"""
Minimal YAML extractor for a scalar value under a top-level mapping block.
This skill intentionally avoids external YAML dependencies; keep this helper
small and predictable, and only support the patterns we generate in config.yaml.
"""
in_block = False
block_indent: int | None = None
key_re = re.compile(rf"^(\s*){re.escape(key)}:\s*(.*?)\s*$")
for line in lines:
if not in_block:
if re.match(rf"^{re.escape(block_key)}:\s*$", line):
in_block = True
block_indent = len(line) - len(line.lstrip(" "))
continue
if line.strip() == "":
continue
indent = len(line) - len(line.lstrip(" "))
if block_indent is not None and indent <= block_indent and not line.startswith(" " * (block_indent + 1)):
break
m = key_re.match(line)
if m and indent >= (block_indent or 0) + 2:
return m.group(2).strip().strip('"').strip("'")
return None
def extract_yaml_list_under_block(lines: Iterable[str], block_key: str, key: str) -> list[str] | None:
"""Extract a simple YAML list under `block_key: { key: [ - item ] }` style."""
in_block = False
block_indent: int | None = None
key_indent: int | None = None
items: list[str] = []
for line in lines:
if not in_block:
if re.match(rf"^{re.escape(block_key)}:\s*$", line):
in_block = True
block_indent = len(line) - len(line.lstrip(" "))
continue
if line.strip() == "":
continue
indent = len(line) - len(line.lstrip(" "))
if block_indent is not None and indent <= block_indent and not line.startswith(" " * (block_indent + 1)):
break
if key_indent is None:
if re.match(rf"^\s*{re.escape(key)}:\s*$", line) and indent >= (block_indent or 0) + 2:
key_indent = indent
continue
if indent <= key_indent:
break
stripped = line.strip()
if stripped.startswith("- "):
items.append(stripped.removeprefix("- ").strip().strip('"').strip("'"))
return items if items else None
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
from _yaml_utils import extract_yaml_list_under_block, extract_yaml_value_under_block
DEFAULT_RISK_PHRASES = ["首次", "领先", "填补空白", "突破性", "国际领先", "世界领先"]
DEFAULT_SUBGOAL_MARKERS_MIN = 3
def _err(message: str) -> int:
print(f"ERROR: {message}", file=sys.stderr)
return 1
def _read_targets_from_config(config_yaml: Path) -> dict[str, str]:
lines = config_yaml.read_text(encoding="utf-8").splitlines()
research = extract_yaml_value_under_block(lines, "targets", "research_content_tex")
innovation = extract_yaml_value_under_block(lines, "targets", "innovation_tex")
yearly = extract_yaml_value_under_block(lines, "targets", "yearly_plan_tex")
if not research or not innovation or not yearly:
raise ValueError("config.yaml missing targets.*_tex")
return {
"research_content_tex": research,
"innovation_tex": innovation,
"yearly_plan_tex": yearly,
}
def _read_checks_from_config(config_yaml: Path) -> tuple[list[str], int]:
lines = config_yaml.read_text(encoding="utf-8").splitlines()
risk_phrases = extract_yaml_list_under_block(lines, "checks", "risk_phrases") or DEFAULT_RISK_PHRASES
raw_min = extract_yaml_value_under_block(lines, "checks", "subgoal_markers_min")
try:
subgoal_markers_min = int(raw_min) if raw_min is not None else DEFAULT_SUBGOAL_MARKERS_MIN
except ValueError:
subgoal_markers_min = DEFAULT_SUBGOAL_MARKERS_MIN
subgoal_markers_min = max(1, subgoal_markers_min)
return risk_phrases, subgoal_markers_min
def _check_file_exists(project_root: Path, relpath: str) -> str | None:
path = project_root / relpath
if not path.exists():
return f"missing file: {path}"
if not path.is_file():
return f"not a file: {path}"
return None
def _check_minimal_content(path: Path, *, kind: str, subgoal_markers_min: int) -> list[str]:
text = path.read_text(encoding="utf-8", errors="replace")
problems: list[str] = []
if kind == "research":
markers = {int(m.group(1)) for m in re.finditer(r"\bS(\d+)\b", text)}
if len(markers) < subgoal_markers_min:
problems.append(
f"{path}: not enough subgoal markers like S1/S2/... (found={len(markers)} min={subgoal_markers_min})"
)
elif kind == "innovation":
if not re.search(r"对应\s*S\d+", text):
problems.append(f"{path}: missing backreference marker like '对应 S1'")
elif kind == "yearly":
year_patterns = {
"第1年/第一年": [r"第\s*1\s*年", r"第一年"],
"第2年/第二年": [r"第\s*2\s*年", r"第二年"],
"第3年/第三年": [r"第\s*3\s*年", r"第三年"],
}
for label, patterns in year_patterns.items():
if not any(re.search(p, text) for p in patterns):
problems.append(f"{path}: missing yearly header ({label})")
if not re.search(r"对应\s*S\d+", text) and not re.search(r"\bS\d+\b", text):
problems.append(f"{path}: missing subgoal backreference like '对应 S1'")
else:
problems.append(f"{path}: unknown kind {kind}")
return problems
def main() -> int:
parser = argparse.ArgumentParser(
description="Lightweight checker for nsfc-research-content-writer outputs under a given project_root.",
)
parser.add_argument(
"--project-root",
required=True,
help="NSFC LaTeX project root (must contain extraTex/).",
)
parser.add_argument(
"--no-content-check",
action="store_true",
help="Only check that target files exist (skip content heuristics).",
)
parser.add_argument(
"--no-risk-scan",
action="store_true",
help="Skip scanning for risk phrases like '首次/领先' (default: scan and warn).",
)
parser.add_argument(
"--fail-on-risk-phrases",
action="store_true",
help="Treat risk phrases as errors (default: warnings).",
)
args = parser.parse_args()
skill_root = Path(__file__).resolve().parents[1]
config_yaml = skill_root / "config.yaml"
if not config_yaml.exists():
return _err(f"missing config.yaml: {config_yaml}")
project_root = Path(args.project_root).expanduser().resolve()
if not project_root.exists() or not project_root.is_dir():
return _err(f"project_root does not exist or is not a directory: {project_root}")
if not (project_root / "extraTex").exists():
return _err(f"project_root missing extraTex/: {project_root}")
try:
targets = _read_targets_from_config(config_yaml)
except ValueError as exc:
return _err(str(exc))
risk_phrases, subgoal_markers_min = _read_checks_from_config(config_yaml)
errors: list[str] = []
errors.extend(
e
for e in [
_check_file_exists(project_root, targets["research_content_tex"]),
_check_file_exists(project_root, targets["innovation_tex"]),
_check_file_exists(project_root, targets["yearly_plan_tex"]),
]
if e
)
if errors:
for e in errors:
print(f"ERROR: {e}", file=sys.stderr)
return 1
if not args.no_content_check:
checks = [
(targets["research_content_tex"], "research"),
(targets["innovation_tex"], "innovation"),
(targets["yearly_plan_tex"], "yearly"),
]
for relpath, kind in checks:
errors.extend(
_check_minimal_content(project_root / relpath, kind=kind, subgoal_markers_min=subgoal_markers_min)
)
warnings: list[str] = []
if not args.no_risk_scan:
for relpath in [
targets["research_content_tex"],
targets["innovation_tex"],
targets["yearly_plan_tex"],
]:
path = project_root / relpath
text = path.read_text(encoding="utf-8", errors="replace")
for phrase in risk_phrases:
if phrase in text:
msg = f"{path}: contains risk phrase '{phrase}'"
if args.fail_on_risk_phrases:
errors.append(msg)
else:
warnings.append(msg)
if errors:
for e in errors:
print(f"ERROR: {e}", file=sys.stderr)
return 1
for w in warnings:
print(f"WARN: {w}", file=sys.stderr)
print("OK: project outputs check passed")
print(f"- project_root: {project_root}")
print("- targets:")
print(f" - {targets['research_content_tex']}")
print(f" - {targets['innovation_tex']}")
print(f" - {targets['yearly_plan_tex']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import datetime as dt
import re
import shutil
import sys
import typing
from pathlib import Path
def _generate_test_id(now: dt.datetime) -> str:
return f"v{now:%Y%m%d%H%M}"
def _ensure_dir(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
def _safe_write(path: Path, content: str, *, overwrite: bool) -> None:
if path.exists() and not overwrite:
raise FileExistsError(f"Refusing to overwrite existing file: {path}")
path.write_text(content, encoding="utf-8")
def _render_template(template: str, *, values: dict[str, str]) -> str:
rendered = template
for key, value in values.items():
rendered = rendered.replace(f"{{{{{key}}}}}", value)
return rendered
def _copy_or_template(
*,
dst_path: Path,
src_path: Path | None,
template_path: Path | None,
template_values: dict[str, str] | None,
overwrite: bool,
) -> None:
if dst_path.exists() and not overwrite:
return
if src_path is not None and src_path.exists():
if dst_path.exists():
dst_path.unlink()
shutil.copyfile(src_path, dst_path)
return
if template_path is not None and template_path.exists():
template_text = template_path.read_text(encoding="utf-8")
if template_values:
template_text = _render_template(template_text, values=template_values)
_safe_write(dst_path, template_text, overwrite=overwrite)
return
_safe_write(
dst_path,
"# TEST_PLAN\n\n(未找到可复制的计划文档或模板,请手动补全)\n",
overwrite=overwrite,
)
def _normalize_kind(kind: str) -> str:
kind = kind.strip().lower()
if kind in {"a", "a_round", "a-round"}:
return "a"
if kind in {"b", "b_round", "b-round"}:
return "b"
raise ValueError("kind must be 'a' or 'b'")
def _fail(parser: argparse.ArgumentParser, message: str) -> typing.NoReturn:
parser.print_usage(sys.stderr)
print(f"error: {message}", file=sys.stderr)
raise SystemExit(2)
def main() -> int:
parser = argparse.ArgumentParser(
description="Create a nsfc-research-content-writer test session skeleton (A round or B round).",
)
parser.add_argument("--kind", default="a", help="Session kind: a (default) or b.")
parser.add_argument("--id", default="", help="Explicit test id like vYYYYMMDDHHMM (optional).")
parser.add_argument(
"--create-plan",
action="store_true",
help="Create missing plan doc skeleton under plans/ (optional).",
)
parser.add_argument(
"--seed-test-plan-from-plan",
action="store_true",
help="If plan doc exists, seed TEST_PLAN.md from it (optional).",
)
parser.add_argument("--overwrite", action="store_true", help="Overwrite existing session files.")
args = parser.parse_args()
skill_root = Path(__file__).resolve().parents[1]
if not (skill_root / "SKILL.md").exists():
_fail(parser, f"missing SKILL.md at expected skill root: {skill_root}")
try:
kind = _normalize_kind(args.kind)
except ValueError as exc:
_fail(parser, str(exc))
test_id = args.id.strip() or _generate_test_id(dt.datetime.now())
if args.id.strip() and not re.fullmatch(r"v\d{12}", test_id):
_fail(parser, "explicit test id must match vYYYYMMDDHHMM, e.g. v202602042053")
if not test_id.startswith("v"):
_fail(parser, "test id must start with 'v'")
plans_dir = skill_root / "plans"
tests_dir = skill_root / "tests"
templates_dir = skill_root / "templates"
required_templates = [
templates_dir / "OPTIMIZATION_PLAN_TEMPLATE.md",
templates_dir / "B_ROUND_CHECK_TEMPLATE.md",
templates_dir / "TEST_PLAN_TEMPLATE.md",
templates_dir / "TEST_REPORT_TEMPLATE.md",
]
missing_templates = [p for p in required_templates if not p.exists()]
if missing_templates:
missing_list = "\n".join(f"- {p.relative_to(skill_root)}" for p in missing_templates)
_fail(
parser,
"missing required templates (run validate_skill.py for details):\n" + missing_list,
)
_ensure_dir(plans_dir)
_ensure_dir(tests_dir)
template_values = {
"TEST_ID": test_id,
"TARGET_SKILL_NAME": skill_root.name,
"TARGET_SKILL_ROOT": str(skill_root),
"PLAN_TIME": dt.datetime.now().isoformat(timespec="minutes"),
"CHECK_TIME": dt.datetime.now().isoformat(timespec="minutes"),
"PLAN_DATE": dt.datetime.now().date().isoformat(),
"KIND_ARG": kind,
}
if kind == "a":
session_name = test_id
test_plan_template = templates_dir / "TEST_PLAN_TEMPLATE.md"
plan_doc_path = plans_dir / f"{test_id}.md"
plan_template = templates_dir / "OPTIMIZATION_PLAN_TEMPLATE.md"
round_kind = "A轮"
else:
session_name = f"B轮-{test_id}"
test_plan_template = templates_dir / "TEST_PLAN_TEMPLATE.md"
plan_doc_path = plans_dir / f"B轮-{test_id}.md"
plan_template = templates_dir / "B_ROUND_CHECK_TEMPLATE.md"
round_kind = "B轮"
template_values["ROUND_KIND"] = round_kind
template_values["SESSION_NAME"] = session_name
template_values["PLAN_DOC_PATH"] = str(plan_doc_path.relative_to(skill_root))
session_dir = tests_dir / session_name
template_values["SESSION_DIR_REL"] = str(session_dir.relative_to(skill_root))
template_values["TEST_PLAN_REL"] = str((session_dir / "TEST_PLAN.md").relative_to(skill_root))
template_values["TEST_REPORT_REL"] = str((session_dir / "TEST_REPORT.md").relative_to(skill_root))
if args.create_plan and (not plan_doc_path.exists() or args.overwrite):
if plan_template.exists():
_safe_write(
plan_doc_path,
_render_template(plan_template.read_text(encoding="utf-8"), values=template_values),
overwrite=args.overwrite,
)
else:
_safe_write(
plan_doc_path,
f"# 计划文档({session_name})\n\n(未找到模板,请手动补全)\n",
overwrite=args.overwrite,
)
_ensure_dir(session_dir)
_ensure_dir(session_dir / "_artifacts")
_ensure_dir(session_dir / "_scripts")
_copy_or_template(
dst_path=session_dir / "TEST_PLAN.md",
src_path=plan_doc_path if (args.seed_test_plan_from_plan and plan_doc_path.exists()) else None,
template_path=test_plan_template if test_plan_template.exists() else None,
template_values=template_values,
overwrite=args.overwrite,
)
report_path = session_dir / "TEST_REPORT.md"
test_report_template = templates_dir / "TEST_REPORT_TEMPLATE.md"
if not report_path.exists() or args.overwrite:
if test_report_template.exists():
_safe_write(
report_path,
_render_template(test_report_template.read_text(encoding="utf-8"), values=template_values),
overwrite=args.overwrite,
)
else:
_safe_write(
report_path,
"# 测试报告(TEST_REPORT)\n\n"
f"**测试会话**: {session_name}\n\n"
"## 结果\n\n"
"- 状态:✅ 通过 / ❌ 失败 / ⚠️ 部分通过\n\n"
"## 证据\n\n"
"- (填入命令输出、文件路径、对比结果等)\n",
overwrite=args.overwrite,
)
print(str(session_dir))
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
def _run(cmd: list[str]) -> int:
print("+ " + " ".join(cmd), flush=True)
proc = subprocess.run(cmd, text=True)
return proc.returncode
def main() -> int:
parser = argparse.ArgumentParser(
description="Run nsfc-research-content-writer minimal checks (validate skill + optional project output checks).",
)
parser.add_argument(
"--project-root",
default="",
help="Optional LaTeX project root to check outputs (must contain extraTex/).",
)
parser.add_argument(
"--no-content-check",
action="store_true",
help="Only check that target files exist (skip content heuristics).",
)
parser.add_argument(
"--no-risk-scan",
action="store_true",
help="Skip scanning for risk phrases like '首次/领先'.",
)
parser.add_argument(
"--fail-on-risk-phrases",
action="store_true",
help="Treat risk phrases as errors (default: warnings).",
)
args = parser.parse_args()
skill_root = Path(__file__).resolve().parents[1]
validate = skill_root / "scripts" / "validate_skill.py"
check_outputs = skill_root / "scripts" / "check_project_outputs.py"
rc = _run([sys.executable, str(validate)])
if rc != 0:
return rc
if args.project_root:
cmd = [
sys.executable,
str(check_outputs),
"--project-root",
args.project_root,
]
if args.no_content_check:
cmd.append("--no-content-check")
if args.no_risk_scan:
cmd.append("--no-risk-scan")
if args.fail_on_risk_phrases:
cmd.append("--fail-on-risk-phrases")
rc = _run(cmd)
if rc != 0:
return rc
print("OK: all checks passed", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
from __future__ import annotations
import re
import sys
from pathlib import Path
from _yaml_utils import extract_yaml_list_under_block, extract_yaml_value_under_block
def _err(message: str) -> int:
print(f"ERROR: {message}", file=sys.stderr)
return 1
def _extract_frontmatter(text: str) -> str:
if not text.startswith("---"):
return ""
parts = text.split("\n---\n", 1)
if len(parts) != 2:
return ""
return parts[0]
def _extract_frontmatter_field(frontmatter: str, key: str) -> str | None:
match = re.search(rf"(?m)^{re.escape(key)}:\s*(.+?)\s*$", frontmatter)
if not match:
return None
return match.group(1).strip().strip('"').strip("'")
def main() -> int:
skill_root = Path(__file__).resolve().parents[1]
repo_root = skill_root.parents[1]
skill_md = skill_root / "SKILL.md"
config_yaml = skill_root / "config.yaml"
templates_dir = skill_root / "templates"
plans_dir = skill_root / "plans"
tests_dir = skill_root / "tests"
required_files = [
skill_md,
config_yaml,
skill_root / "README.md",
skill_root / "scripts" / "create_test_session.py",
skill_root / "scripts" / "check_project_outputs.py",
skill_root / "scripts" / "run_checks.py",
skill_root / "scripts" / "_yaml_utils.py",
skill_root / "references" / "info_form.md",
skill_root / "references" / "dod_checklist.md",
templates_dir / "OPTIMIZATION_PLAN_TEMPLATE.md",
templates_dir / "B_ROUND_CHECK_TEMPLATE.md",
templates_dir / "TEST_PLAN_TEMPLATE.md",
templates_dir / "TEST_REPORT_TEMPLATE.md",
]
for path in required_files:
if not path.exists():
return _err(f"missing required file: {path}")
for d in [plans_dir, tests_dir, templates_dir]:
if not d.exists() or not d.is_dir():
return _err(f"missing required directory: {d}")
skill_text = skill_md.read_text(encoding="utf-8")
frontmatter = _extract_frontmatter(skill_text)
if not frontmatter:
return _err("SKILL.md missing YAML frontmatter block")
fm_name = _extract_frontmatter_field(frontmatter, "name")
fm_version = _extract_frontmatter_field(frontmatter, "version")
fm_config = _extract_frontmatter_field(frontmatter, "config")
fm_references = _extract_frontmatter_field(frontmatter, "references")
if not fm_name or not fm_version or not fm_config or not fm_references:
return _err("SKILL.md frontmatter missing required fields: name/version/config/references")
fm_config_path = Path(fm_config)
resolved_config_path = (
(repo_root / fm_config_path) if str(fm_config_path).startswith("skills/") else (skill_root / fm_config_path)
).resolve()
if resolved_config_path != config_yaml.resolve():
return _err(f"config path mismatch: SKILL.md={resolved_config_path} expected={config_yaml.resolve()}")
fm_refs_path = Path(fm_references)
resolved_refs_path = (
(repo_root / fm_refs_path) if str(fm_refs_path).startswith("skills/") else (skill_root / fm_refs_path)
).resolve()
if not resolved_refs_path.exists() or not resolved_refs_path.is_dir():
return _err(f"references path invalid: {resolved_refs_path}")
config_lines = config_yaml.read_text(encoding="utf-8").splitlines()
cfg_name = extract_yaml_value_under_block(config_lines, "skill_info", "name")
cfg_version = extract_yaml_value_under_block(config_lines, "skill_info", "version")
cfg_description = extract_yaml_value_under_block(config_lines, "skill_info", "description")
cfg_category = extract_yaml_value_under_block(config_lines, "skill_info", "category")
if not cfg_name or not cfg_version:
return _err("config.yaml missing skill_info.name or skill_info.version")
if not cfg_description or not cfg_category:
return _err("config.yaml missing skill_info.description or skill_info.category")
if fm_name != cfg_name:
return _err(f"name mismatch: SKILL.md={fm_name} config.yaml={cfg_name}")
if fm_version != cfg_version:
return _err(f"version mismatch: SKILL.md={fm_version} config.yaml={cfg_version}")
targets = [
extract_yaml_value_under_block(config_lines, "targets", "research_content_tex"),
extract_yaml_value_under_block(config_lines, "targets", "innovation_tex"),
extract_yaml_value_under_block(config_lines, "targets", "yearly_plan_tex"),
]
if any(t is None for t in targets):
return _err("config.yaml missing one of targets.*_tex")
allowed = extract_yaml_list_under_block(config_lines, "guardrails", "allowed_write_files")
if not allowed:
return _err("config.yaml missing guardrails.allowed_write_files")
if set(allowed) != set(t for t in targets if t is not None):
return _err("config.yaml mismatch: guardrails.allowed_write_files must equal targets.*_tex values")
forbidden = extract_yaml_list_under_block(config_lines, "guardrails", "forbidden_write_files") or []
for must_forbid in ["main.tex", "extraTex/@config.tex"]:
if must_forbid not in forbidden:
return _err(f"config.yaml missing guardrails.forbidden_write_files entry: {must_forbid}")
forbidden_globs = extract_yaml_list_under_block(config_lines, "guardrails", "forbidden_write_globs") or []
for must_forbid_glob in ["**/*.cls", "**/*.sty"]:
if must_forbid_glob not in forbidden_globs:
return _err(f"config.yaml missing guardrails.forbidden_write_globs entry: {must_forbid_glob}")
required_skill_snippets = [
"project_root",
"output_mode",
"写入安全约束",
"references/output_skeletons.md",
"main.tex",
"extraTex/@config.tex",
".cls",
".sty",
]
for snippet in required_skill_snippets:
if snippet not in skill_text:
return _err(f"SKILL.md missing required snippet: {snippet}")
for t in targets:
if t and t not in skill_text:
return _err(f"SKILL.md missing target path from config.yaml: {t}")
link_paths = re.findall(r"\((references/[^)]+)\)", skill_text)
missing_links = []
for rel in link_paths:
if not (skill_root / rel).exists():
missing_links.append(rel)
if missing_links:
missing = "\n".join(f"- {p}" for p in missing_links)
return _err(f"SKILL.md contains missing references links:\n{missing}")
stray = "( 二)"
readme_path = skill_root / "README.md"
md_files = [skill_md, readme_path, *sorted((skill_root / "references").glob("*.md"))]
for md in md_files:
txt = md.read_text(encoding="utf-8")
if stray in txt:
return _err(f"found stray title variant '{stray}' in {md}")
readme_text = readme_path.read_text(encoding="utf-8")
for required_snippet in [
"project_root",
"output_mode",
"禁止改动",
"check_project_outputs.py",
"run_checks.py",
"--fail-on-risk-phrases",
]:
if required_snippet not in readme_text:
return _err(f"README.md missing required snippet: {required_snippet}")
for required_path in [
"skills/nsfc-research-content-writer/references/info_form.md",
"skills/nsfc-research-content-writer/references/dod_checklist.md",
"skills/nsfc-research-content-writer/references/output_skeletons.md",
]:
if required_path not in readme_text:
return _err(f"README.md missing required reference path: {required_path}")
print("OK: validate-skill passed")
print(f"- skill: {fm_name}")
print(f"- version: {fm_version}")
print(f"- referenced files: {len(link_paths)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
B轮质量原则检查({{TEST_ID}})
检查日期: {{PLAN_DATE}} 检查ID: B轮-{{TEST_ID}} 目标技能: {{TARGET_SKILL_NAME}} 目标技能路径: {{TARGET_SKILL_ROOT}} 检查时间: {{CHECK_TIME}}
---
检查维度(8项)
1) 硬编码/AI 功能规划(哪些应写死,哪些应可配置) 2) 冗余残留错误检查(重复实现、死代码、过期示例) 3) 安全性检查(只读/写入边界、路径/命令注入风险) 4) 过度设计检查(能否更简单、是否引入不必要抽象) 5) 通用性检查(跨年份/跨项目路径/跨平台) 6) 一致性检查(SKILL/README/config/scripts/references 口径一致) 7) 配置集中化检查(可变参数是否集中在 config.yaml) 8) SKILL.md 瘦身检查(细节是否下沉到 references/,主文档聚焦流程/约束)
---
发现问题(按优先级)
P0(必须修复)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
P1(建议修复)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
P2(可选)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
---
本轮验证
- 会话目录:
{{SESSION_DIR_REL}}/ - 测试计划:
{{TEST_PLAN_REL}} - 测试报告:
{{TEST_REPORT_REL}}
优化计划({{TEST_ID}})
计划日期: {{PLAN_DATE}} 计划ID: {{TEST_ID}} 目标技能: {{TARGET_SKILL_NAME}} 目标技能路径: {{TARGET_SKILL_ROOT}} 计划时间: {{PLAN_TIME}}
---
独立评估声明(强制)
- [ ] 本轮仅基于目标 skill 的当前状态进行审查(不依赖历史
plans//tests/) - [ ] 已扫描:
SKILL.md、config.yaml、scripts/、references/、templates/
扫描证据(建议填入命令):
rg -n \"...\" skills/nsfc-research-content-writerfind skills/nsfc-research-content-writer -maxdepth 2 -type f
---
问题清单(按优先级)
每个问题至少包含:位置(文件:行号)、现象、影响、修复方案、验证方法。
P0(必须修复)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
P1(建议修复)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
P2(可选)
1) 标题:
- 位置:
- 现象:
- 影响:
- 修复:
- 验证:
---
执行步骤(按顺序)
1) ... 2) ...
---
本轮轻量测试
- 会话目录:
{{SESSION_DIR_REL}}/ - 测试计划:
{{TEST_PLAN_REL}} - 测试报告:
{{TEST_REPORT_REL}}
轻量测试计划({{SESSION_NAME}})
测试ID: {{TEST_ID}} 轮次类型: {{ROUND_KIND}} 目标技能: {{TARGET_SKILL_NAME}} 目标技能路径: {{TARGET_SKILL_ROOT}} 关联规划文档: {{PLAN_DOC_PATH}} 计划时间: {{PLAN_TIME}}
---
本轮目标
- 验证本轮修复点是否生效(P0/P1/P2 全闭环)
- 确认自检脚本可运行:
scripts/validate_skill.py/scripts/run_checks.py
---
变更范围(本轮)
- 修改文件:
- (填写本轮实际改动文件)
---
验证点(按优先级)
P0(必须通过)
- [ ]
python3 scripts/validate_skill.py通过 - [ ]
python3 scripts/create_test_session.py --kind a --id {{TEST_ID}} --create-plan可运行(不覆盖已有文件时应无异常)
P1(建议通过)
- [ ]
python3 scripts/run_checks.py(不带 --project-root)通过
P2(可选)
- [ ] 如提供样例项目:
python3 scripts/check_project_outputs.py --project-root <path>通过
测试报告({{SESSION_NAME}})
测试ID: {{TEST_ID}} 轮次类型: {{ROUND_KIND}} 目标技能: {{TARGET_SKILL_NAME}} 目标技能路径: {{TARGET_SKILL_ROOT}} 关联规划文档: {{PLAN_DOC_PATH}} 测试时间: {{PLAN_TIME}}
---
结论
- 状态:✅ 通过 / ❌ 失败 / ⚠️ 部分通过
- 一句话结论:
---
覆盖的变更(本轮)
- 修改文件:
- (填写本轮实际改动文件)
---
执行命令与证据
按时间顺序记录,确保可复现:
1) 命令:...
- 期望:
- 实际:
- 证据:
_artifacts/...(如有)
---
验证点清单
P0
- [ ] ...
P1
- [ ] ...
P2
- [ ] ...
---
遗留问题(如有)
- P0/P1/P2:问题描述 + 原因 + 后续建议(如本轮不修复需写明理由)
Related skills
How it compares
Pick nsfc-research-content-writer over generic LaTeX skills when you need NSFC-specific 2.1/2.2/2.3 alignment inside ChineseResearchLaTeX extraTex contracts.
FAQ
Which files does nsfc-research-content-writer modify?
nsfc-research-content-writer writes only three extraTex files: extraTex/2.1.研究内容.tex, extraTex/2.2.特色与创新.tex, and extraTex/2.3.年度研究计划.tex. It does not edit main.tex, @config.tex, or any .cls or .sty files.
What output modes does nsfc-research-content-writer support?
nsfc-research-content-writer version 0.2.3 supports preview mode for paste-ready LaTeX drafts and apply mode that writes the three target extraTex files. Preview mode labels each draft with its destination file path.