medsci-skills 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (702) hide show
  1. package/LICENSE +50 -0
  2. package/README.md +602 -0
  3. package/README_FIRST.md +27 -0
  4. package/bin/medsci-skills.js +159 -0
  5. package/installers/install-macos.command +19 -0
  6. package/installers/install-windows.cmd +26 -0
  7. package/installers/install-windows.ps1 +17 -0
  8. package/installers/install.py +218 -0
  9. package/metadata/skills_catalog.json +452 -0
  10. package/package.json +48 -0
  11. package/skills/academic-aio/SKILL.md +408 -0
  12. package/skills/academic-aio/references/case_studies/kjr_mllm_2025.md +82 -0
  13. package/skills/academic-aio/references/checklists/AIO_GENERAL.md +354 -0
  14. package/skills/academic-aio/references/journal_summarybox_templates.yaml +126 -0
  15. package/skills/academic-aio/references/oac_funding_checklist.yaml +129 -0
  16. package/skills/academic-aio/references/reporting_guideline_mapping.md +39 -0
  17. package/skills/academic-aio/references/schema_markup_templates/CodeRepository.jsonld +32 -0
  18. package/skills/academic-aio/references/schema_markup_templates/Dataset.jsonld +36 -0
  19. package/skills/academic-aio/references/schema_markup_templates/Person.jsonld +30 -0
  20. package/skills/academic-aio/references/schema_markup_templates/README.md +43 -0
  21. package/skills/academic-aio/references/schema_markup_templates/ScholarlyArticle.jsonld +55 -0
  22. package/skills/academic-aio/scripts/batch_metadata_audit.py +169 -0
  23. package/skills/academic-aio/scripts/validate_schema.py +118 -0
  24. package/skills/academic-aio/skill.yml +36 -0
  25. package/skills/academic-aio/templates/aio_audit_checklist.md.j2 +108 -0
  26. package/skills/add-journal/SKILL.md +482 -0
  27. package/skills/add-journal/skill.yml +33 -0
  28. package/skills/analyze-stats/SKILL.md +598 -0
  29. package/skills/analyze-stats/references/analysis_guides/missing_data.md +109 -0
  30. package/skills/analyze-stats/references/analysis_guides/nhis_icd10_mapping.md +247 -0
  31. package/skills/analyze-stats/references/analysis_guides/propensity_score.md +132 -0
  32. package/skills/analyze-stats/references/analysis_guides/regression.md +115 -0
  33. package/skills/analyze-stats/references/analysis_guides/repeated_measures.md +160 -0
  34. package/skills/analyze-stats/references/analysis_guides/survey_weighted.md +366 -0
  35. package/skills/analyze-stats/references/analysis_guides/test_selection.md +86 -0
  36. package/skills/analyze-stats/references/style/figure_style.mplstyle +69 -0
  37. package/skills/analyze-stats/references/style/theme_publication.R +147 -0
  38. package/skills/analyze-stats/references/table-standards/journal-profiles/ajr.yaml +51 -0
  39. package/skills/analyze-stats/references/table-standards/journal-profiles/european_radiology.yaml +55 -0
  40. package/skills/analyze-stats/references/table-standards/journal-profiles/jama.yaml +66 -0
  41. package/skills/analyze-stats/references/table-standards/journal-profiles/lancet.yaml +57 -0
  42. package/skills/analyze-stats/references/table-standards/journal-profiles/nejm.yaml +51 -0
  43. package/skills/analyze-stats/references/table-standards/journal-profiles/radiology.yaml +66 -0
  44. package/skills/analyze-stats/references/table-standards/table-standards.md +287 -0
  45. package/skills/analyze-stats/references/table-standards/table-types/diagnostic_accuracy.md +36 -0
  46. package/skills/analyze-stats/references/table-standards/table-types/meta_analysis.md +58 -0
  47. package/skills/analyze-stats/references/table-standards/table-types/model_comparison.md +36 -0
  48. package/skills/analyze-stats/references/table-standards/table-types/regression_results.md +50 -0
  49. package/skills/analyze-stats/references/table-standards/table-types/table1_demographics.md +51 -0
  50. package/skills/analyze-stats/references/table-standards/tool-comparison.md +79 -0
  51. package/skills/analyze-stats/references/templates/agreement_analysis.py +436 -0
  52. package/skills/analyze-stats/references/templates/dca_plot.R +237 -0
  53. package/skills/analyze-stats/references/templates/diagnostic_accuracy.py +401 -0
  54. package/skills/analyze-stats/references/templates/dta_meta_analysis.R +384 -0
  55. package/skills/analyze-stats/references/templates/forest_plot.py +412 -0
  56. package/skills/analyze-stats/references/templates/likert_summary.py +356 -0
  57. package/skills/analyze-stats/references/templates/meta_analysis.R +365 -0
  58. package/skills/analyze-stats/references/templates/propensity_score.py +478 -0
  59. package/skills/analyze-stats/references/templates/regression.py +425 -0
  60. package/skills/analyze-stats/references/templates/repeated_measures.py +434 -0
  61. package/skills/analyze-stats/references/templates/sample_size.R +382 -0
  62. package/skills/analyze-stats/references/templates/survey_weighted_analysis.py +411 -0
  63. package/skills/analyze-stats/references/templates/survival_analysis.py +325 -0
  64. package/skills/analyze-stats/references/templates/table1_demographics.py +287 -0
  65. package/skills/analyze-stats/scripts/check_generated_code.py +335 -0
  66. package/skills/analyze-stats/skill.yml +38 -0
  67. package/skills/analyze-stats/tests/fixtures/gen_bad.R +16 -0
  68. package/skills/analyze-stats/tests/fixtures/gen_bad.py +24 -0
  69. package/skills/analyze-stats/tests/fixtures/gen_clean.py +21 -0
  70. package/skills/analyze-stats/tests/test_generated_code.sh +59 -0
  71. package/skills/analyze-stats/tests/test_survival_template.sh +53 -0
  72. package/skills/author-strategy/SKILL.md +117 -0
  73. package/skills/author-strategy/analyze_patterns.py +303 -0
  74. package/skills/author-strategy/fetch_pubmed.py +374 -0
  75. package/skills/author-strategy/skill.yml +34 -0
  76. package/skills/batch-cohort/SKILL.md +223 -0
  77. package/skills/batch-cohort/references/base_template_knhanes.R +210 -0
  78. package/skills/batch-cohort/references/batch_template_generator.R +222 -0
  79. package/skills/batch-cohort/references/variable_coding_registry.md +136 -0
  80. package/skills/batch-cohort/skill.yml +35 -0
  81. package/skills/calc-sample-size/SKILL.md +491 -0
  82. package/skills/calc-sample-size/references/formulas.md +655 -0
  83. package/skills/calc-sample-size/references/observational_cohort.md +49 -0
  84. package/skills/calc-sample-size/skill.yml +51 -0
  85. package/skills/check-reporting/SKILL.md +534 -0
  86. package/skills/check-reporting/references/LICENSES.md +41 -0
  87. package/skills/check-reporting/references/checklists/AMSTAR2.md +54 -0
  88. package/skills/check-reporting/references/checklists/ARRIVE_2.md +234 -0
  89. package/skills/check-reporting/references/checklists/CARE.md +102 -0
  90. package/skills/check-reporting/references/checklists/CLAIM_2024.md +128 -0
  91. package/skills/check-reporting/references/checklists/CLEAR.md +113 -0
  92. package/skills/check-reporting/references/checklists/CONSORT.md +86 -0
  93. package/skills/check-reporting/references/checklists/COSMIN_RoB.md +136 -0
  94. package/skills/check-reporting/references/checklists/GRRAS.md +61 -0
  95. package/skills/check-reporting/references/checklists/MI_CLEAR_LLM.md +167 -0
  96. package/skills/check-reporting/references/checklists/MOOSE.md +85 -0
  97. package/skills/check-reporting/references/checklists/NOS.md +88 -0
  98. package/skills/check-reporting/references/checklists/PRISMA_2020.md +135 -0
  99. package/skills/check-reporting/references/checklists/PRISMA_DTA.md +36 -0
  100. package/skills/check-reporting/references/checklists/PRISMA_P.md +56 -0
  101. package/skills/check-reporting/references/checklists/PROBAST.md +75 -0
  102. package/skills/check-reporting/references/checklists/PROBAST_AI.md +130 -0
  103. package/skills/check-reporting/references/checklists/QUADAS2.md +77 -0
  104. package/skills/check-reporting/references/checklists/QUADAS_C.md +131 -0
  105. package/skills/check-reporting/references/checklists/ROBINS_E.md +179 -0
  106. package/skills/check-reporting/references/checklists/ROBINS_I.md +87 -0
  107. package/skills/check-reporting/references/checklists/ROBIS.md +114 -0
  108. package/skills/check-reporting/references/checklists/ROB_ME.md +126 -0
  109. package/skills/check-reporting/references/checklists/RoB2.md +79 -0
  110. package/skills/check-reporting/references/checklists/RoB_NMA.md +96 -0
  111. package/skills/check-reporting/references/checklists/SPIRIT.md +112 -0
  112. package/skills/check-reporting/references/checklists/SQUIRE_2.md +68 -0
  113. package/skills/check-reporting/references/checklists/STARD.md +129 -0
  114. package/skills/check-reporting/references/checklists/STARD_AI.md +211 -0
  115. package/skills/check-reporting/references/checklists/STROBE.md +80 -0
  116. package/skills/check-reporting/references/checklists/SWiM.md +33 -0
  117. package/skills/check-reporting/references/checklists/TRIPOD.md +157 -0
  118. package/skills/check-reporting/references/checklists/TRIPOD_AI.md +140 -0
  119. package/skills/check-reporting/references/step4c_registration_timing.md +93 -0
  120. package/skills/check-reporting/references/step4d_prisma_figure_audit.md +137 -0
  121. package/skills/check-reporting/scripts/check_checklist_exists.py +183 -0
  122. package/skills/check-reporting/scripts/check_checklist_version.py +168 -0
  123. package/skills/check-reporting/scripts/check_framework_naming.py +206 -0
  124. package/skills/check-reporting/scripts/check_prisma_figure.py +209 -0
  125. package/skills/check-reporting/scripts/prisma_cascade_check.py +274 -0
  126. package/skills/check-reporting/skill.yml +41 -0
  127. package/skills/check-reporting/tests/fixtures/framework_bad.md +8 -0
  128. package/skills/check-reporting/tests/fixtures/framework_clean.md +7 -0
  129. package/skills/check-reporting/tests/test_checklist_fail_fast.sh +77 -0
  130. package/skills/check-reporting/tests/test_checklist_version.sh +72 -0
  131. package/skills/check-reporting/tests/test_framework_naming.sh +45 -0
  132. package/skills/check-reporting/tests/test_prisma_cascade.sh +104 -0
  133. package/skills/clean-data/SKILL.md +180 -0
  134. package/skills/clean-data/references/cleaning_patterns.md +299 -0
  135. package/skills/clean-data/references/profiling_template.py +304 -0
  136. package/skills/clean-data/scripts/check_structural_zero.py +174 -0
  137. package/skills/clean-data/skill.yml +35 -0
  138. package/skills/clean-data/tests/fixtures/smoking.csv +8 -0
  139. package/skills/clean-data/tests/test_structural_zero.sh +49 -0
  140. package/skills/cross-national/SKILL.md +264 -0
  141. package/skills/cross-national/skill.yml +37 -0
  142. package/skills/define-variables/SKILL.md +146 -0
  143. package/skills/define-variables/references/common_definitions.md +190 -0
  144. package/skills/define-variables/skill.yml +34 -0
  145. package/skills/define-variables/templates/variable_operationalization.md +64 -0
  146. package/skills/deidentify/SKILL.md +203 -0
  147. package/skills/deidentify/deidentify.py +1224 -0
  148. package/skills/deidentify/locales/_template.json +45 -0
  149. package/skills/deidentify/locales/au.json +43 -0
  150. package/skills/deidentify/locales/ca.json +44 -0
  151. package/skills/deidentify/locales/cn.json +47 -0
  152. package/skills/deidentify/locales/de.json +48 -0
  153. package/skills/deidentify/locales/fr.json +48 -0
  154. package/skills/deidentify/locales/in.json +48 -0
  155. package/skills/deidentify/locales/jp.json +48 -0
  156. package/skills/deidentify/locales/kr.json +48 -0
  157. package/skills/deidentify/locales/uk.json +45 -0
  158. package/skills/deidentify/locales/us.json +43 -0
  159. package/skills/deidentify/references/date_shift_guide.md +82 -0
  160. package/skills/deidentify/references/hipaa_18_identifiers.md +48 -0
  161. package/skills/deidentify/references/korean_phi_patterns.md +135 -0
  162. package/skills/deidentify/skill.yml +43 -0
  163. package/skills/deidentify/tests/README.md +26 -0
  164. package/skills/deidentify/tests/test_clean.csv +16 -0
  165. package/skills/deidentify/tests/test_edge_cases.csv +11 -0
  166. package/skills/deidentify/tests/test_phi_korean.csv +11 -0
  167. package/skills/design-ai-benchmarking/SKILL.md +214 -0
  168. package/skills/design-ai-benchmarking/references/benchmark_export_schema.json +69 -0
  169. package/skills/design-ai-benchmarking/references/elicitation_rubric_template.md +37 -0
  170. package/skills/design-ai-benchmarking/skill.yml +38 -0
  171. package/skills/design-study/SKILL.md +298 -0
  172. package/skills/design-study/skill.yml +33 -0
  173. package/skills/fill-icmje-coi/SKILL.md +216 -0
  174. package/skills/fill-icmje-coi/scripts/fill_icmje_coi.py +140 -0
  175. package/skills/fill-icmje-coi/skill.yml +35 -0
  176. package/skills/fill-icmje-coi/templates/icmje_coi_seed_synthetic.docx +0 -0
  177. package/skills/fill-protocol/SKILL.md +248 -0
  178. package/skills/fill-protocol/examples/example_irb_template.yaml +53 -0
  179. package/skills/fill-protocol/references/best_practices.md +121 -0
  180. package/skills/fill-protocol/scripts/doc_to_docx.py +111 -0
  181. package/skills/fill-protocol/scripts/fill_form.py +611 -0
  182. package/skills/fill-protocol/scripts/inspect_template.py +61 -0
  183. package/skills/fill-protocol/setup.sh +162 -0
  184. package/skills/fill-protocol/skill.yml +37 -0
  185. package/skills/find-cohort-gap/SKILL.md +309 -0
  186. package/skills/find-cohort-gap/references/cohort_profile_template.md +93 -0
  187. package/skills/find-cohort-gap/references/onepager_template.md +84 -0
  188. package/skills/find-cohort-gap/references/pattern_scoring_rubric.md +169 -0
  189. package/skills/find-cohort-gap/references/saturation_query_templates.md +143 -0
  190. package/skills/find-cohort-gap/skill.yml +35 -0
  191. package/skills/find-journal/POLICY.md +87 -0
  192. package/skills/find-journal/SKILL.md +340 -0
  193. package/skills/find-journal/references/journal_profiles/AJNR.md +29 -0
  194. package/skills/find-journal/references/journal_profiles/AJR.md +30 -0
  195. package/skills/find-journal/references/journal_profiles/Abdominal_Radiology.md +30 -0
  196. package/skills/find-journal/references/journal_profiles/Academic_Radiology.md +30 -0
  197. package/skills/find-journal/references/journal_profiles/Annals_of_Internal_Medicine.md +33 -0
  198. package/skills/find-journal/references/journal_profiles/Artificial_Intelligence_in_Medicine.md +28 -0
  199. package/skills/find-journal/references/journal_profiles/BMC_Medicine.md +31 -0
  200. package/skills/find-journal/references/journal_profiles/British_Journal_of_Radiology.md +39 -0
  201. package/skills/find-journal/references/journal_profiles/CVIR.md +30 -0
  202. package/skills/find-journal/references/journal_profiles/Chest.md +39 -0
  203. package/skills/find-journal/references/journal_profiles/Clinical_Radiology.md +30 -0
  204. package/skills/find-journal/references/journal_profiles/Clinical_and_Molecular_Hepatology.md +32 -0
  205. package/skills/find-journal/references/journal_profiles/Diabetes_Metabolism_Journal.md +36 -0
  206. package/skills/find-journal/references/journal_profiles/Diagnostic_and_Interventional_Radiology.md +32 -0
  207. package/skills/find-journal/references/journal_profiles/Endocrinology_and_Metabolism.md +37 -0
  208. package/skills/find-journal/references/journal_profiles/European_Journal_of_Preventive_Cardiology.md +39 -0
  209. package/skills/find-journal/references/journal_profiles/European_Radiology.md +29 -0
  210. package/skills/find-journal/references/journal_profiles/Hepatology_Communications.md +40 -0
  211. package/skills/find-journal/references/journal_profiles/Hepatology_International.md +37 -0
  212. package/skills/find-journal/references/journal_profiles/IEEE_JBHI.md +28 -0
  213. package/skills/find-journal/references/journal_profiles/IEEE_TMI.md +28 -0
  214. package/skills/find-journal/references/journal_profiles/INSI.md +29 -0
  215. package/skills/find-journal/references/journal_profiles/Investigative_Radiology.md +25 -0
  216. package/skills/find-journal/references/journal_profiles/JACC_Advances.md +41 -0
  217. package/skills/find-journal/references/journal_profiles/JACC_Asia.md +30 -0
  218. package/skills/find-journal/references/journal_profiles/JACR.md +28 -0
  219. package/skills/find-journal/references/journal_profiles/JAMA.md +40 -0
  220. package/skills/find-journal/references/journal_profiles/JAMA_Network_Open.md +30 -0
  221. package/skills/find-journal/references/journal_profiles/JCSM.md +39 -0
  222. package/skills/find-journal/references/journal_profiles/JKMS.md +32 -0
  223. package/skills/find-journal/references/journal_profiles/JMIR.md +29 -0
  224. package/skills/find-journal/references/journal_profiles/JMIR_Medical_Education.md +29 -0
  225. package/skills/find-journal/references/journal_profiles/JNIS.md +35 -0
  226. package/skills/find-journal/references/journal_profiles/JVIR.md +31 -0
  227. package/skills/find-journal/references/journal_profiles/Journal_of_Biomedical_Informatics.md +29 -0
  228. package/skills/find-journal/references/journal_profiles/Journal_of_Clinical_Endocrinology_and_Metabolism.md +40 -0
  229. package/skills/find-journal/references/journal_profiles/Journal_of_Magnetic_Resonance_Imaging.md +30 -0
  230. package/skills/find-journal/references/journal_profiles/Journal_of_Nuclear_Medicine.md +31 -0
  231. package/skills/find-journal/references/journal_profiles/Journal_of_Stroke.md +32 -0
  232. package/skills/find-journal/references/journal_profiles/KJR.md +38 -0
  233. package/skills/find-journal/references/journal_profiles/Korean_Circulation_Journal.md +38 -0
  234. package/skills/find-journal/references/journal_profiles/Korean_Journal_of_Internal_Medicine.md +36 -0
  235. package/skills/find-journal/references/journal_profiles/Lancet_Diabetes_and_Endocrinology.md +40 -0
  236. package/skills/find-journal/references/journal_profiles/Lancet_Gastroenterology_and_Hepatology.md +49 -0
  237. package/skills/find-journal/references/journal_profiles/Lancet_Infectious_Diseases.md +38 -0
  238. package/skills/find-journal/references/journal_profiles/Lancet_Neurology.md +39 -0
  239. package/skills/find-journal/references/journal_profiles/Lancet_Oncology.md +40 -0
  240. package/skills/find-journal/references/journal_profiles/Lancet_Psychiatry.md +38 -0
  241. package/skills/find-journal/references/journal_profiles/Lancet_Public_Health.md +30 -0
  242. package/skills/find-journal/references/journal_profiles/Lancet_Respiratory_Medicine.md +39 -0
  243. package/skills/find-journal/references/journal_profiles/Liver_International.md +33 -0
  244. package/skills/find-journal/references/journal_profiles/Medical_Image_Analysis.md +28 -0
  245. package/skills/find-journal/references/journal_profiles/NEJM.md +33 -0
  246. package/skills/find-journal/references/journal_profiles/Nature_Machine_Intelligence.md +31 -0
  247. package/skills/find-journal/references/journal_profiles/Nature_Medicine.md +39 -0
  248. package/skills/find-journal/references/journal_profiles/Neuroradiology.md +31 -0
  249. package/skills/find-journal/references/journal_profiles/Nutrition_Metabolism_and_Cardiovascular_Diseases.md +39 -0
  250. package/skills/find-journal/references/journal_profiles/PLOS_Medicine.md +32 -0
  251. package/skills/find-journal/references/journal_profiles/RYAI.md +28 -0
  252. package/skills/find-journal/references/journal_profiles/Radiology.md +29 -0
  253. package/skills/find-journal/references/journal_profiles/Skeletal_Radiology.md +31 -0
  254. package/skills/find-journal/references/journal_profiles/Stroke.md +37 -0
  255. package/skills/find-journal/references/journal_profiles/The_BMJ.md +31 -0
  256. package/skills/find-journal/references/journal_profiles/The_Lancet.md +31 -0
  257. package/skills/find-journal/references/journal_profiles/The_Lancet_Digital_Health.md +29 -0
  258. package/skills/find-journal/references/journal_profiles/World_Journal_of_Hepatology.md +53 -0
  259. package/skills/find-journal/references/journal_profiles/npj_Digital_Medicine.md +29 -0
  260. package/skills/find-journal/skill.yml +34 -0
  261. package/skills/fulltext-retrieval/SKILL.md +174 -0
  262. package/skills/fulltext-retrieval/fetch_oa.py +433 -0
  263. package/skills/fulltext-retrieval/pdf_to_md.py +160 -0
  264. package/skills/fulltext-retrieval/skill.yml +41 -0
  265. package/skills/generate-codebook/SKILL.md +155 -0
  266. package/skills/generate-codebook/references/codebook_schema.md +76 -0
  267. package/skills/generate-codebook/scripts/generate_codebook.py +278 -0
  268. package/skills/generate-codebook/skill.yml +35 -0
  269. package/skills/generate-codebook/tests/test_generate_codebook.sh +76 -0
  270. package/skills/grant-builder/SKILL.md +251 -0
  271. package/skills/grant-builder/skill.yml +34 -0
  272. package/skills/humanize/SKILL.md +251 -0
  273. package/skills/humanize/references/ai_patterns.md +571 -0
  274. package/skills/humanize/skill.yml +33 -0
  275. package/skills/intake-project/SKILL.md +264 -0
  276. package/skills/intake-project/skill.yml +34 -0
  277. package/skills/lit-sync/SKILL.md +448 -0
  278. package/skills/lit-sync/references/locale/ko/note_templates.md +110 -0
  279. package/skills/lit-sync/skill.yml +52 -0
  280. package/skills/lit-sync/tests/test_poll_logic.sh +92 -0
  281. package/skills/ma-scout/SKILL.md +640 -0
  282. package/skills/ma-scout/references/project_readme_template.md +95 -0
  283. package/skills/ma-scout/references/project_readme_template_ko.md +82 -0
  284. package/skills/ma-scout/skill.yml +33 -0
  285. package/skills/make-figures/SKILL.md +957 -0
  286. package/skills/make-figures/references/critic_rubrics/data_plot.md +166 -0
  287. package/skills/make-figures/references/critic_rubrics/flow_diagram.md +169 -0
  288. package/skills/make-figures/references/design_principles.md +181 -0
  289. package/skills/make-figures/references/exemplar_diagrams/README.md +65 -0
  290. package/skills/make-figures/references/exemplar_diagrams/consort/README.md +15 -0
  291. package/skills/make-figures/references/exemplar_diagrams/consort/template_input.yaml +37 -0
  292. package/skills/make-figures/references/exemplar_diagrams/consort/template_output.pdf +0 -0
  293. package/skills/make-figures/references/exemplar_diagrams/consort/template_output.png +0 -0
  294. package/skills/make-figures/references/exemplar_diagrams/consort/template_output_600.png +0 -0
  295. package/skills/make-figures/references/exemplar_diagrams/other/other_02.meta.yaml +4 -0
  296. package/skills/make-figures/references/exemplar_diagrams/other/other_02.png +0 -0
  297. package/skills/make-figures/references/exemplar_diagrams/other/other_02_why.md +13 -0
  298. package/skills/make-figures/references/exemplar_diagrams/pipeline/README.md +15 -0
  299. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_01.meta.yaml +4 -0
  300. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_01.png +0 -0
  301. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_01_why.md +13 -0
  302. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_03.meta.yaml +4 -0
  303. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_03.png +0 -0
  304. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_03_why.md +13 -0
  305. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_04.meta.yaml +4 -0
  306. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_04.png +0 -0
  307. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_04_why.md +13 -0
  308. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_05.meta.yaml +4 -0
  309. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_05.png +0 -0
  310. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_05_why.md +13 -0
  311. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_06.meta.yaml +4 -0
  312. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_06.png +0 -0
  313. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_06_why.md +13 -0
  314. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_07.meta.yaml +4 -0
  315. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_07.png +0 -0
  316. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_07_why.md +13 -0
  317. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_08.meta.yaml +4 -0
  318. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_08.png +0 -0
  319. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_08_why.md +13 -0
  320. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_09.meta.yaml +4 -0
  321. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_09.png +0 -0
  322. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_09_why.md +13 -0
  323. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_10.meta.yaml +4 -0
  324. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_10.png +0 -0
  325. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_10_why.md +13 -0
  326. package/skills/make-figures/references/exemplar_diagrams/prisma/README.md +15 -0
  327. package/skills/make-figures/references/exemplar_diagrams/prisma/template_input.yaml +47 -0
  328. package/skills/make-figures/references/exemplar_diagrams/prisma/template_output.pdf +0 -0
  329. package/skills/make-figures/references/exemplar_diagrams/prisma/template_output.png +0 -0
  330. package/skills/make-figures/references/exemplar_diagrams/prisma/template_output_600.png +0 -0
  331. package/skills/make-figures/references/exemplar_diagrams/stard/README.md +15 -0
  332. package/skills/make-figures/references/exemplar_diagrams/stard/template_input.yaml +40 -0
  333. package/skills/make-figures/references/exemplar_diagrams/stard/template_output.pdf +0 -0
  334. package/skills/make-figures/references/exemplar_diagrams/stard/template_output.png +0 -0
  335. package/skills/make-figures/references/exemplar_diagrams/stard/template_output_600.png +0 -0
  336. package/skills/make-figures/references/exemplar_diagrams/strobe/template_input.yaml +43 -0
  337. package/skills/make-figures/references/exemplar_diagrams/strobe/template_input_pptx.yaml +43 -0
  338. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output.pdf +0 -0
  339. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output.png +0 -0
  340. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output.pptx +0 -0
  341. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output_600.png +0 -0
  342. package/skills/make-figures/references/figure_specs.md +291 -0
  343. package/skills/make-figures/references/flow_diagram_lessons.md +164 -0
  344. package/skills/make-figures/references/jacc_central_illustration_principles.md +91 -0
  345. package/skills/make-figures/references/medical_illustration_sources.md +98 -0
  346. package/skills/make-figures/references/pipeline_concepts_medical_ai.md +240 -0
  347. package/skills/make-figures/references/reporting_guideline_figure_map.md +104 -0
  348. package/skills/make-figures/references/visual_abstract_templates/european_radiology.pptx +0 -0
  349. package/skills/make-figures/references/visual_abstract_templates/jacc_central_illustration.pptx +0 -0
  350. package/skills/make-figures/references/visual_abstract_templates/medsci_default.pptx +0 -0
  351. package/skills/make-figures/references/visual_abstract_templates/template_guide.md +114 -0
  352. package/skills/make-figures/scripts/build_jacc_template.py +77 -0
  353. package/skills/make-figures/scripts/build_prisma2020_template.py +371 -0
  354. package/skills/make-figures/scripts/build_strobe_template.py +351 -0
  355. package/skills/make-figures/scripts/critic_figure.py +264 -0
  356. package/skills/make-figures/scripts/derive_figure_legend_counts.py +138 -0
  357. package/skills/make-figures/scripts/extract_exemplar_from_pdf.py +186 -0
  358. package/skills/make-figures/scripts/fetch_official_templates.sh +88 -0
  359. package/skills/make-figures/scripts/fill_prisma_template.py +142 -0
  360. package/skills/make-figures/scripts/generate_flow_diagram.R +133 -0
  361. package/skills/make-figures/scripts/generate_image.py +99 -0
  362. package/skills/make-figures/scripts/generate_visual_abstract.py +438 -0
  363. package/skills/make-figures/scripts/validate_pptx_mac_compat.py +233 -0
  364. package/skills/make-figures/skill.yml +52 -0
  365. package/skills/make-figures/templates/official/NOTES.md +62 -0
  366. package/skills/make-figures/templates/official/consort2010/CONSORT_2025_editable_checklist.docx +0 -0
  367. package/skills/make-figures/templates/official/consort2010/CONSORT_2025_flow_diagram.docx +0 -0
  368. package/skills/make-figures/templates/official/prisma2020/PRISMA_2020_flow_new_v1.pptx +0 -0
  369. package/skills/make-figures/templates/official/prisma2020/PRISMA_2020_flow_new_v2.pptx +0 -0
  370. package/skills/make-figures/templates/official/prisma2020/PRISMA_2020_flow_updated_v2.pptx +0 -0
  371. package/skills/make-figures/templates/official/spirit2013/SPIRIT_2025_editable_checklist.docx +0 -0
  372. package/skills/make-figures/templates/official/spirit2013/SPIRIT_2025_participant_timeline.docx +0 -0
  373. package/skills/make-figures/templates/official/stard2015/STARD_2015_checklist.docx +0 -0
  374. package/skills/make-figures/templates/official/stard2015/STARD_2015_flow_diagram.pdf +0 -0
  375. package/skills/make-figures/tests/fixtures/figure1_flow.yaml +8 -0
  376. package/skills/make-figures/tests/fixtures/manuscript_ok.md +9 -0
  377. package/skills/make-figures/tests/fixtures/manuscript_stale.md +4 -0
  378. package/skills/make-figures/tests/test_legend_reconcile.sh +36 -0
  379. package/skills/manage-project/SKILL.md +358 -0
  380. package/skills/manage-project/references/pre_submission_checklist.md +53 -0
  381. package/skills/manage-project/references/project_state_template.json +37 -0
  382. package/skills/manage-project/references/scaffold_templates.md +118 -0
  383. package/skills/manage-project/references/status_output_format.md +44 -0
  384. package/skills/manage-project/references/timeline_example.md +20 -0
  385. package/skills/manage-project/skill.yml +36 -0
  386. package/skills/manage-project/templates/SSOT.yaml.template +41 -0
  387. package/skills/manage-refs/LICENSE.zotero-mcp +21 -0
  388. package/skills/manage-refs/NOTICE.md +29 -0
  389. package/skills/manage-refs/SKILL.md +289 -0
  390. package/skills/manage-refs/citation_styles/README.md +40 -0
  391. package/skills/manage-refs/citation_styles/american-journal-of-roentgenology.csl +211 -0
  392. package/skills/manage-refs/citation_styles/cardiovascular-and-interventional-radiology.csl +19 -0
  393. package/skills/manage-refs/citation_styles/european-radiology.csl +19 -0
  394. package/skills/manage-refs/citation_styles/journal-of-cachexia-sarcopenia-and-muscle.csl +150 -0
  395. package/skills/manage-refs/citation_styles/journal-of-korean-medical-science-strict.csl +533 -0
  396. package/skills/manage-refs/citation_styles/journal-of-korean-medical-science.csl +16 -0
  397. package/skills/manage-refs/citation_styles/korean-journal-of-radiology.csl +155 -0
  398. package/skills/manage-refs/citation_styles/nature.csl +189 -0
  399. package/skills/manage-refs/citation_styles/nlm-citation-sequence.csl +535 -0
  400. package/skills/manage-refs/citation_styles/radiology.csl +228 -0
  401. package/skills/manage-refs/citation_styles/springer-basic-brackets.csl +187 -0
  402. package/skills/manage-refs/citation_styles/springer-vancouver-brackets.csl +276 -0
  403. package/skills/manage-refs/citation_styles/vancouver-superscript.csl +536 -0
  404. package/skills/manage-refs/citation_styles/vancouver.csl +535 -0
  405. package/skills/manage-refs/references/REFERENCE_STYLE_SPECS.md +59 -0
  406. package/skills/manage-refs/references/check_xref_symptoms.md +35 -0
  407. package/skills/manage-refs/scripts/_vendor_citation_writer.py +600 -0
  408. package/skills/manage-refs/scripts/check_citation_keys.py +112 -0
  409. package/skills/manage-refs/scripts/check_csl_render.py +102 -0
  410. package/skills/manage-refs/scripts/check_xref.py +633 -0
  411. package/skills/manage-refs/scripts/fill_journal_abbrev.py +104 -0
  412. package/skills/manage-refs/scripts/inject_zotero_cwyw.py +133 -0
  413. package/skills/manage-refs/scripts/md_marker_convert.py +193 -0
  414. package/skills/manage-refs/scripts/pre_submission_gate.sh +238 -0
  415. package/skills/manage-refs/scripts/render_pandoc.sh +88 -0
  416. package/skills/manage-refs/skill.yml +70 -0
  417. package/skills/manage-refs/tests/fixtures/pre_submission_gate/README.md +32 -0
  418. package/skills/manage-refs/tests/fixtures/pre_submission_gate/manuscript.md +10 -0
  419. package/skills/manage-refs/tests/fixtures/pre_submission_gate/refs.bib +34 -0
  420. package/skills/manage-refs/tests/fixtures/pre_submission_gate/run.sh +117 -0
  421. package/skills/manage-refs/tests/test_vN_docx_check.sh +145 -0
  422. package/skills/meta-analysis/SKILL.md +739 -0
  423. package/skills/meta-analysis/references/LICENSES.md +21 -0
  424. package/skills/meta-analysis/references/PROSPERO_template.md +221 -0
  425. package/skills/meta-analysis/references/ai_pre_screening_template.py +245 -0
  426. package/skills/meta-analysis/references/checklists/JBI_Case_Series.md +45 -0
  427. package/skills/meta-analysis/references/checklists/NOS.md +88 -0
  428. package/skills/meta-analysis/references/checklists/PRISMA_DTA.md +36 -0
  429. package/skills/meta-analysis/references/checklists/PROBAST.md +75 -0
  430. package/skills/meta-analysis/references/checklists/QUADAS2.md +77 -0
  431. package/skills/meta-analysis/references/checklists/ROBINS_I.md +87 -0
  432. package/skills/meta-analysis/references/checklists/RoB2.md +79 -0
  433. package/skills/meta-analysis/references/data_integrity_checklist.md +57 -0
  434. package/skills/meta-analysis/references/icmje_coi_guide.md +181 -0
  435. package/skills/meta-analysis/references/phase10_recovery.md +136 -0
  436. package/skills/meta-analysis/references/phase4_km_composite.md +58 -0
  437. package/skills/meta-analysis/references/phase6_statistical_synthesis.md +148 -0
  438. package/skills/meta-analysis/references/phase9_circulation.md +84 -0
  439. package/skills/meta-analysis/references/post_submission_release_ops.md +41 -0
  440. package/skills/meta-analysis/references/r_templates.md +132 -0
  441. package/skills/meta-analysis/references/review_orchestration.md +40 -0
  442. package/skills/meta-analysis/references/submission_package_drift.md +71 -0
  443. package/skills/meta-analysis/scripts/check_pool_consistency.py +201 -0
  444. package/skills/meta-analysis/scripts/cohort_overlap_check.py +242 -0
  445. package/skills/meta-analysis/scripts/dta_extraction_qc.py +137 -0
  446. package/skills/meta-analysis/scripts/screening_reconcile.py +160 -0
  447. package/skills/meta-analysis/skill.yml +47 -0
  448. package/skills/meta-analysis/templates/FINAL_POOL_LOCK.yaml.template +70 -0
  449. package/skills/meta-analysis/templates/extraction_form_v2.md +129 -0
  450. package/skills/meta-analysis/templates/supplementary_8file_checklist.md +94 -0
  451. package/skills/meta-analysis/tests/test_pool_consistency.sh +123 -0
  452. package/skills/orchestrate/SKILL.md +501 -0
  453. package/skills/orchestrate/references/dialogue_nodes.md +196 -0
  454. package/skills/orchestrate/references/report_template.md +109 -0
  455. package/skills/orchestrate/references/report_template_ko.md +88 -0
  456. package/skills/orchestrate/skill.yml +44 -0
  457. package/skills/peer-review/SKILL.md +381 -0
  458. package/skills/peer-review/references/aczel_2021_reviewer2_patterns.md +88 -0
  459. package/skills/peer-review/references/domain-probes/ai_overclaiming.md +47 -0
  460. package/skills/peer-review/references/domain-probes/narrative_review.md +44 -0
  461. package/skills/peer-review/references/domain-probes/observational_confounding.md +48 -0
  462. package/skills/peer-review/references/domain-probes/radiomics.md +38 -0
  463. package/skills/peer-review/references/domain-probes/sr_ma.md +87 -0
  464. package/skills/peer-review/references/domain-probes/survival_prognostic.md +68 -0
  465. package/skills/peer-review/references/exemplar_reviews/README.md +43 -0
  466. package/skills/peer-review/references/exemplar_reviews/ai_overclaiming.md +47 -0
  467. package/skills/peer-review/references/exemplar_reviews/calibration_missing.md +44 -0
  468. package/skills/peer-review/references/exemplar_reviews/data_leakage.md +48 -0
  469. package/skills/peer-review/references/exemplar_reviews/reference_standard_validity.md +45 -0
  470. package/skills/peer-review/references/narrative_review_audit.md +67 -0
  471. package/skills/peer-review/references/reviewer_calibration/README.md +34 -0
  472. package/skills/peer-review/references/reviewer_calibration/compliance_floor.md +52 -0
  473. package/skills/peer-review/references/reviewer_profiles/AJR.md +82 -0
  474. package/skills/peer-review/references/reviewer_profiles/EURE.md +64 -0
  475. package/skills/peer-review/references/reviewer_profiles/INSI.md +57 -0
  476. package/skills/peer-review/references/reviewer_profiles/KJR.md +100 -0
  477. package/skills/peer-review/references/reviewer_profiles/README.md +32 -0
  478. package/skills/peer-review/references/reviewer_profiles/RYAI.md +86 -0
  479. package/skills/peer-review/skill.yml +39 -0
  480. package/skills/present-paper/SKILL.md +675 -0
  481. package/skills/present-paper/references/critic_rubrics/slide.md +155 -0
  482. package/skills/present-paper/references/generate_pptx_templates.py +604 -0
  483. package/skills/present-paper/references/medical_presentation_templates.md +277 -0
  484. package/skills/present-paper/references/slide_design_principles.md +202 -0
  485. package/skills/present-paper/references/slide_visual_styles/nature_lancet.md +168 -0
  486. package/skills/present-paper/references/workflow-checklist.md +109 -0
  487. package/skills/present-paper/scripts/extract_pdf_figures.py +243 -0
  488. package/skills/present-paper/scripts/inject_pronunciation_notes.py +178 -0
  489. package/skills/present-paper/scripts/inject_speaker_notes.py +133 -0
  490. package/skills/present-paper/scripts/strip_notes_for_sharing.py +140 -0
  491. package/skills/present-paper/scripts/trim_caption.py +271 -0
  492. package/skills/present-paper/skill.yml +41 -0
  493. package/skills/present-paper/templates/build_pptx_nature_lancet.py +688 -0
  494. package/skills/publish-skill/SKILL.md +370 -0
  495. package/skills/publish-skill/references/license-compatibility-matrix.md +132 -0
  496. package/skills/publish-skill/references/pii-patterns.md +130 -0
  497. package/skills/publish-skill/scripts/audit_skill.sh +278 -0
  498. package/skills/publish-skill/skill.yml +35 -0
  499. package/skills/render-pdf-doc/SKILL.md +146 -0
  500. package/skills/render-pdf-doc/references/known_pitfalls.md +53 -0
  501. package/skills/render-pdf-doc/references/pandoc_korean_cheatsheet.md +77 -0
  502. package/skills/render-pdf-doc/scripts/check_deps.sh +42 -0
  503. package/skills/render-pdf-doc/scripts/infer_colwidths.py +164 -0
  504. package/skills/render-pdf-doc/scripts/render_pdf.sh +98 -0
  505. package/skills/render-pdf-doc/skill.yml +57 -0
  506. package/skills/render-pdf-doc/templates/anchor-doc.md +27 -0
  507. package/skills/render-pdf-doc/templates/anchor-doc_ko.md +25 -0
  508. package/skills/render-pdf-doc/templates/briefing-handout.md +33 -0
  509. package/skills/render-pdf-doc/templates/briefing-handout_ko.md +31 -0
  510. package/skills/render-pdf-doc/templates/proposal-cover.md +33 -0
  511. package/skills/render-pdf-doc/templates/proposal-cover_ko.md +31 -0
  512. package/skills/render-pdf-doc/templates/reference-table.md +22 -0
  513. package/skills/render-pdf-doc/templates/reference-table_ko.md +20 -0
  514. package/skills/replicate-study/SKILL.md +150 -0
  515. package/skills/replicate-study/references/harmonization_3country.csv +47 -0
  516. package/skills/replicate-study/references/harmonization_knhanes_nhanes.csv +68 -0
  517. package/skills/replicate-study/references/methodology_extraction_template.md +134 -0
  518. package/skills/replicate-study/skill.yml +37 -0
  519. package/skills/review-paper/SKILL.md +104 -0
  520. package/skills/review-paper/references/macro_skeleton.md +6 -0
  521. package/skills/review-paper/skill.yml +25 -0
  522. package/skills/revise/SKILL.md +515 -0
  523. package/skills/revise/references/r2r_voice.md +346 -0
  524. package/skills/revise/skill.yml +43 -0
  525. package/skills/search-lit/SKILL.md +443 -0
  526. package/skills/search-lit/references/parse_pubmed.py +326 -0
  527. package/skills/search-lit/references/pubmed_eutils.sh +111 -0
  528. package/skills/search-lit/skill.yml +46 -0
  529. package/skills/self-review/SKILL.md +1045 -0
  530. package/skills/self-review/references/domain-probes/ai_overclaiming.md +47 -0
  531. package/skills/self-review/references/domain-probes/narrative_review.md +44 -0
  532. package/skills/self-review/references/domain-probes/observational_confounding.md +48 -0
  533. package/skills/self-review/references/domain-probes/radiomics.md +38 -0
  534. package/skills/self-review/references/domain-probes/sr_ma.md +87 -0
  535. package/skills/self-review/references/domain-probes/survival_prognostic.md +68 -0
  536. package/skills/self-review/references/exemplar_findings/README.md +43 -0
  537. package/skills/self-review/references/exemplar_findings/cohort_arithmetic_mismatch.md +35 -0
  538. package/skills/self-review/references/exemplar_findings/estimand_drift_posthoc_primary.md +39 -0
  539. package/skills/self-review/references/exemplar_findings/scope_overreach_cross_sectional.md +35 -0
  540. package/skills/self-review/references/exemplar_findings/unadjusted_confounder.md +36 -0
  541. package/skills/self-review/references/panel_review_template.md +177 -0
  542. package/skills/self-review/scripts/check_artifact_coverage.py +301 -0
  543. package/skills/self-review/scripts/check_claim_artifact.py +248 -0
  544. package/skills/self-review/scripts/check_classical_style.py +185 -0
  545. package/skills/self-review/scripts/check_cohort_arithmetic.py +481 -0
  546. package/skills/self-review/scripts/check_confounding_completeness.py +287 -0
  547. package/skills/self-review/scripts/check_panel_diversity.py +336 -0
  548. package/skills/self-review/scripts/check_reference_adequacy.py +392 -0
  549. package/skills/self-review/scripts/check_reviewer_team_consistency.py +412 -0
  550. package/skills/self-review/scripts/check_scope_coherence.py +177 -0
  551. package/skills/self-review/skill.yml +47 -0
  552. package/skills/self-review/tests/fixtures/claim_manuscript.md +17 -0
  553. package/skills/self-review/tests/fixtures/claim_prereg.md +6 -0
  554. package/skills/self-review/tests/fixtures/cohort_bad.md +21 -0
  555. package/skills/self-review/tests/fixtures/cohort_clean.md +21 -0
  556. package/skills/self-review/tests/fixtures/cohort_partition.csv +5 -0
  557. package/skills/self-review/tests/fixtures/coverage_analysis/31_delong_nested_added_value.csv +3 -0
  558. package/skills/self-review/tests/fixtures/coverage_analysis/table1_demographics.csv +3 -0
  559. package/skills/self-review/tests/fixtures/coverage_clean.md +13 -0
  560. package/skills/self-review/tests/fixtures/coverage_manuscript.md +11 -0
  561. package/skills/self-review/tests/fixtures/panel_collapse.json +27 -0
  562. package/skills/self-review/tests/fixtures/panel_good.json +32 -0
  563. package/skills/self-review/tests/fixtures/panel_monoculture.json +32 -0
  564. package/skills/self-review/tests/fixtures/refadeq_letter.md +13 -0
  565. package/skills/self-review/tests/fixtures/refadeq_original_fixed.md +42 -0
  566. package/skills/self-review/tests/fixtures/refadeq_original_uncited.md +40 -0
  567. package/skills/self-review/tests/fixtures/scope_bad.md +9 -0
  568. package/skills/self-review/tests/fixtures/scope_clean.md +8 -0
  569. package/skills/self-review/tests/fixtures/scope_surrogate.md +8 -0
  570. package/skills/self-review/tests/fixtures/style_bad.md +13 -0
  571. package/skills/self-review/tests/fixtures/style_clean.md +11 -0
  572. package/skills/self-review/tests/fixtures/table1_by_exposure.csv +11 -0
  573. package/skills/self-review/tests/test_artifact_coverage.sh +44 -0
  574. package/skills/self-review/tests/test_claim_artifact.sh +50 -0
  575. package/skills/self-review/tests/test_classical_style.sh +44 -0
  576. package/skills/self-review/tests/test_cohort_arithmetic.sh +49 -0
  577. package/skills/self-review/tests/test_confounding_completeness.sh +66 -0
  578. package/skills/self-review/tests/test_panel_diversity.sh +55 -0
  579. package/skills/self-review/tests/test_panel_mode.sh +69 -0
  580. package/skills/self-review/tests/test_reference_adequacy.sh +68 -0
  581. package/skills/self-review/tests/test_reviewer_team_consistency.sh +138 -0
  582. package/skills/self-review/tests/test_scope_coherence.sh +46 -0
  583. package/skills/setup-medsci/SKILL.md +110 -0
  584. package/skills/setup-medsci/references/setup-checklist.md +51 -0
  585. package/skills/setup-medsci/skill.yml +30 -0
  586. package/skills/sync-submission/SKILL.md +382 -0
  587. package/skills/sync-submission/scripts/author_registry_example.yaml +36 -0
  588. package/skills/sync-submission/scripts/blind_sweep.py +203 -0
  589. package/skills/sync-submission/scripts/check_asset_anonymization.py +300 -0
  590. package/skills/sync-submission/scripts/check_cross_artifact_stale.py +211 -0
  591. package/skills/sync-submission/scripts/cover_letter_drift_check.py +451 -0
  592. package/skills/sync-submission/scripts/cross_document_n_check.py +486 -0
  593. package/skills/sync-submission/scripts/detect_copy_divergence.py +136 -0
  594. package/skills/sync-submission/scripts/preflight_gate.py +458 -0
  595. package/skills/sync-submission/scripts/scope_drift_check.py +362 -0
  596. package/skills/sync-submission/scripts/sync_submission.py +169 -0
  597. package/skills/sync-submission/skill.yml +43 -0
  598. package/skills/sync-submission/tests/fixtures/copy_ok.md +5 -0
  599. package/skills/sync-submission/tests/fixtures/copy_stale.md +5 -0
  600. package/skills/sync-submission/tests/fixtures/ssot.md +5 -0
  601. package/skills/sync-submission/tests/test_asset_anonymization.sh +99 -0
  602. package/skills/sync-submission/tests/test_copy_divergence.sh +44 -0
  603. package/skills/sync-submission/tests/test_cross_artifact_stale.sh +80 -0
  604. package/skills/sync-submission/tests/test_cross_document_n.sh +132 -0
  605. package/skills/sync-submission/tests/test_preflight_gate.sh +112 -0
  606. package/skills/sync-submission/tests/test_scope_drift.sh +122 -0
  607. package/skills/sync-submission/tests/test_vN_docx_assertion.sh +51 -0
  608. package/skills/verify-refs/SKILL.md +177 -0
  609. package/skills/verify-refs/references/manual_checkpoint_guide.md +100 -0
  610. package/skills/verify-refs/scripts/verify_cli.sh +62 -0
  611. package/skills/verify-refs/scripts/verify_refs.py +782 -0
  612. package/skills/verify-refs/skill.yml +44 -0
  613. package/skills/verify-refs/tests/fixtures/pagination_placeholder.bib +17 -0
  614. package/skills/verify-refs/tests/test_pagination_placeholder.sh +42 -0
  615. package/skills/version-dataset/SKILL.md +143 -0
  616. package/skills/version-dataset/references/manifest_schema.md +72 -0
  617. package/skills/version-dataset/scripts/version_dataset.py +242 -0
  618. package/skills/version-dataset/skill.yml +35 -0
  619. package/skills/version-dataset/tests/test_version_dataset.sh +52 -0
  620. package/skills/write-paper/SKILL.md +1148 -0
  621. package/skills/write-paper/references/exemplar_methods/README.md +38 -0
  622. package/skills/write-paper/references/exemplar_methods/ai_validation_tripod_claim.md +47 -0
  623. package/skills/write-paper/references/exemplar_methods/diagnostic_accuracy_stard.md +50 -0
  624. package/skills/write-paper/references/exemplar_methods/observational_cohort_strobe.md +43 -0
  625. package/skills/write-paper/references/journal_profiles/AJNR.md +185 -0
  626. package/skills/write-paper/references/journal_profiles/AJR.md +149 -0
  627. package/skills/write-paper/references/journal_profiles/Abdominal_Radiology.md +139 -0
  628. package/skills/write-paper/references/journal_profiles/Academic_Radiology.md +90 -0
  629. package/skills/write-paper/references/journal_profiles/Annals_of_Internal_Medicine.md +150 -0
  630. package/skills/write-paper/references/journal_profiles/Artificial_Intelligence_in_Medicine.md +82 -0
  631. package/skills/write-paper/references/journal_profiles/British_Journal_of_Radiology.md +161 -0
  632. package/skills/write-paper/references/journal_profiles/CVIR.md +157 -0
  633. package/skills/write-paper/references/journal_profiles/Chest.md +270 -0
  634. package/skills/write-paper/references/journal_profiles/Clinical_Radiology.md +160 -0
  635. package/skills/write-paper/references/journal_profiles/Clinical_and_Molecular_Hepatology.md +147 -0
  636. package/skills/write-paper/references/journal_profiles/Diabetes_Metabolism_Journal.md +163 -0
  637. package/skills/write-paper/references/journal_profiles/Diagnostic_and_Interventional_Radiology.md +216 -0
  638. package/skills/write-paper/references/journal_profiles/Endocrinology_and_Metabolism.md +167 -0
  639. package/skills/write-paper/references/journal_profiles/European_Journal_of_Preventive_Cardiology.md +192 -0
  640. package/skills/write-paper/references/journal_profiles/European_Radiology.md +159 -0
  641. package/skills/write-paper/references/journal_profiles/Hepatology_Communications.md +110 -0
  642. package/skills/write-paper/references/journal_profiles/Hepatology_International.md +106 -0
  643. package/skills/write-paper/references/journal_profiles/IEEE_TMI.md +180 -0
  644. package/skills/write-paper/references/journal_profiles/INSI.md +163 -0
  645. package/skills/write-paper/references/journal_profiles/Investigative_Radiology.md +86 -0
  646. package/skills/write-paper/references/journal_profiles/JACC_Advances.md +197 -0
  647. package/skills/write-paper/references/journal_profiles/JACC_Asia.md +168 -0
  648. package/skills/write-paper/references/journal_profiles/JACR.md +87 -0
  649. package/skills/write-paper/references/journal_profiles/JAMA.md +188 -0
  650. package/skills/write-paper/references/journal_profiles/JAMA_Network_Open.md +170 -0
  651. package/skills/write-paper/references/journal_profiles/JCSM.md +266 -0
  652. package/skills/write-paper/references/journal_profiles/JKMS.md +201 -0
  653. package/skills/write-paper/references/journal_profiles/JMIR.md +88 -0
  654. package/skills/write-paper/references/journal_profiles/JMIR_Medical_Education.md +86 -0
  655. package/skills/write-paper/references/journal_profiles/JNIS.md +227 -0
  656. package/skills/write-paper/references/journal_profiles/JVIR.md +158 -0
  657. package/skills/write-paper/references/journal_profiles/Journal_of_Clinical_Endocrinology_and_Metabolism.md +191 -0
  658. package/skills/write-paper/references/journal_profiles/Journal_of_Stroke.md +176 -0
  659. package/skills/write-paper/references/journal_profiles/KJR.md +185 -0
  660. package/skills/write-paper/references/journal_profiles/Korean_Circulation_Journal.md +184 -0
  661. package/skills/write-paper/references/journal_profiles/Korean_Journal_of_Internal_Medicine.md +178 -0
  662. package/skills/write-paper/references/journal_profiles/Lancet_Gastroenterology_and_Hepatology.md +127 -0
  663. package/skills/write-paper/references/journal_profiles/Liver_International.md +165 -0
  664. package/skills/write-paper/references/journal_profiles/Medical_Image_Analysis.md +147 -0
  665. package/skills/write-paper/references/journal_profiles/NEJM.md +147 -0
  666. package/skills/write-paper/references/journal_profiles/Nature_Medicine.md +181 -0
  667. package/skills/write-paper/references/journal_profiles/Neuroradiology.md +151 -0
  668. package/skills/write-paper/references/journal_profiles/Nutrition_Metabolism_and_Cardiovascular_Diseases.md +184 -0
  669. package/skills/write-paper/references/journal_profiles/PLOS_Medicine.md +166 -0
  670. package/skills/write-paper/references/journal_profiles/RYAI.md +124 -0
  671. package/skills/write-paper/references/journal_profiles/Radiology.md +173 -0
  672. package/skills/write-paper/references/journal_profiles/Skeletal_Radiology.md +135 -0
  673. package/skills/write-paper/references/journal_profiles/Stroke.md +210 -0
  674. package/skills/write-paper/references/journal_profiles/The_BMJ.md +121 -0
  675. package/skills/write-paper/references/journal_profiles/The_Lancet.md +112 -0
  676. package/skills/write-paper/references/journal_profiles/The_Lancet_Digital_Health.md +104 -0
  677. package/skills/write-paper/references/journal_profiles/World_Journal_of_Hepatology.md +106 -0
  678. package/skills/write-paper/references/journal_profiles/npj_Digital_Medicine.md +93 -0
  679. package/skills/write-paper/references/paper_types/ai_validation.md +270 -0
  680. package/skills/write-paper/references/paper_types/animal_study.md +194 -0
  681. package/skills/write-paper/references/paper_types/case_report.md +237 -0
  682. package/skills/write-paper/references/paper_types/cross_national.md +328 -0
  683. package/skills/write-paper/references/paper_types/letter.md +127 -0
  684. package/skills/write-paper/references/paper_types/meta_analysis.md +181 -0
  685. package/skills/write-paper/references/paper_types/nhis_cohort.md +297 -0
  686. package/skills/write-paper/references/paper_types/original_article.md +221 -0
  687. package/skills/write-paper/references/paper_types/technical_note.md +131 -0
  688. package/skills/write-paper/references/section_guides/discussion.md +155 -0
  689. package/skills/write-paper/references/section_guides/introduction.md +108 -0
  690. package/skills/write-paper/references/section_guides/methods.md +144 -0
  691. package/skills/write-paper/references/section_guides/results.md +113 -0
  692. package/skills/write-paper/references/section_guides/step7_1_classical_qc.md +67 -0
  693. package/skills/write-paper/references/section_guides/step7_4a_audit_recovery.md +74 -0
  694. package/skills/write-paper/references/section_guides/title_abstract.md +123 -0
  695. package/skills/write-paper/references/section_templates/methods_statistical.md +147 -0
  696. package/skills/write-paper/scripts/check_placeholders.py +182 -0
  697. package/skills/write-paper/skill.yml +48 -0
  698. package/skills/write-paper/tests/test_placeholders.sh +107 -0
  699. package/skills/write-protocol/SKILL.md +243 -0
  700. package/skills/write-protocol/references/ethics_checklist.md +150 -0
  701. package/skills/write-protocol/references/protocol_template.md +304 -0
  702. package/skills/write-protocol/skill.yml +34 -0
@@ -0,0 +1,611 @@
1
+ #!/usr/bin/env python3
2
+ """Fill a Korean Word form template while preserving styles, tables, fonts, and page layout.
3
+
4
+ Core principles (DO NOT BREAK):
5
+ 1. Always open existing template via Document(path) — never create from scratch.
6
+ 2. Modify cell/paragraph TEXT only. Preserve all run-level styles.
7
+ 3. Apply cantSplit to every row that gets filled (prevents page-break-mid-row).
8
+ 4. Set Korean font with eastAsia attribute (run.font.name alone fails for Korean).
9
+ 5. Validate: report empty cells and paragraphs that didn't match.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import re
15
+ import sys
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Iterable
19
+
20
+ import yaml
21
+ from docx import Document
22
+ from docx.oxml import OxmlElement
23
+ from docx.oxml.ns import qn
24
+ from docx.shared import Pt
25
+ from docx.text.paragraph import Paragraph
26
+ from docx.table import _Cell
27
+
28
+
29
+ DEFAULT_KOREAN_FONT = "맑은 고딕"
30
+
31
+
32
+ # ---------- Style preservation helpers ----------
33
+
34
+ def _set_run_korean_font(run, font_name: str) -> None:
35
+ """Set font for a run including eastAsia attribute (mandatory for Hangul)."""
36
+ run.font.name = font_name
37
+ rPr = run._element.get_or_add_rPr()
38
+ rFonts = rPr.find(qn("w:rFonts"))
39
+ if rFonts is None:
40
+ rFonts = OxmlElement("w:rFonts")
41
+ rPr.append(rFonts)
42
+ for attr in ("w:ascii", "w:hAnsi", "w:cs", "w:eastAsia"):
43
+ rFonts.set(qn(attr), font_name)
44
+
45
+
46
+ def _apply_cant_split(row) -> None:
47
+ """Mark row to never split across pages."""
48
+ trPr = row._tr.get_or_add_trPr()
49
+ if trPr.find(qn("w:cantSplit")) is None:
50
+ trPr.append(OxmlElement("w:cantSplit"))
51
+
52
+
53
+ def _make_blank_paragraph() -> "OxmlElement":
54
+ """Create an empty paragraph that renders as a single Enter press.
55
+
56
+ Forces single line height (line=240) and zero spacing-before/after,
57
+ so the blank line is exactly one body-text line tall — never inflates
58
+ the document's apparent line spacing.
59
+ """
60
+ p = OxmlElement("w:p")
61
+ pPr = OxmlElement("w:pPr")
62
+ spacing = OxmlElement("w:spacing")
63
+ spacing.set(qn("w:line"), "240")
64
+ spacing.set(qn("w:lineRule"), "auto")
65
+ spacing.set(qn("w:before"), "0")
66
+ spacing.set(qn("w:after"), "0")
67
+ pPr.append(spacing)
68
+ p.append(pPr)
69
+ return p
70
+
71
+
72
+ def _replace_paragraph_text_keep_style(para: Paragraph, new_text: str,
73
+ korean_font: str | None = None) -> None:
74
+ """Replace the entire text content of a paragraph while keeping its style.
75
+
76
+ Strategy: keep the first run's properties as the template style. Remove all
77
+ other runs. Replace the first run's text with new_text. For multi-line
78
+ content, split on \n and use w:br between lines (within same run-style block).
79
+ """
80
+ # Capture template run (first one) style by copying its rPr
81
+ runs = para.runs
82
+ template_rPr = None
83
+ if runs:
84
+ template_run_elem = runs[0]._element
85
+ rPr = template_run_elem.find(qn("w:rPr"))
86
+ if rPr is not None:
87
+ template_rPr = rPr
88
+
89
+ # Remove all existing runs
90
+ for r in list(para._element.findall(qn("w:r"))):
91
+ para._element.remove(r)
92
+
93
+ # Add new run with the captured style
94
+ new_run = OxmlElement("w:r")
95
+ if template_rPr is not None:
96
+ # Deep copy template rPr
97
+ from copy import deepcopy
98
+ new_run.append(deepcopy(template_rPr))
99
+
100
+ # Split on \n — insert w:br between lines, w:t for text segments
101
+ lines = new_text.split("\n")
102
+ for i, line in enumerate(lines):
103
+ if i > 0:
104
+ br = OxmlElement("w:br")
105
+ new_run.append(br)
106
+ if line:
107
+ t = OxmlElement("w:t")
108
+ t.text = line
109
+ t.set(qn("xml:space"), "preserve")
110
+ new_run.append(t)
111
+
112
+ para._element.append(new_run)
113
+
114
+ if korean_font:
115
+ # Reapply Korean font to the new run
116
+ from docx.text.run import Run
117
+ run_obj = Run(new_run, para)
118
+ _set_run_korean_font(run_obj, korean_font)
119
+
120
+
121
+ def _replace_cell_text(cell: _Cell, new_text: str,
122
+ korean_font: str | None = None) -> None:
123
+ """Replace a cell's text content. Use the first paragraph as template."""
124
+ if not cell.paragraphs:
125
+ # Cell has no paragraph — add one
126
+ cell.add_paragraph(new_text)
127
+ if korean_font:
128
+ for r in cell.paragraphs[0].runs:
129
+ _set_run_korean_font(r, korean_font)
130
+ return
131
+
132
+ # Replace first paragraph, then remove the rest
133
+ template_para = cell.paragraphs[0]
134
+
135
+ # If the new content has multiple lines, we replace first paragraph
136
+ # with the first line, and add additional paragraphs for remaining lines.
137
+ lines = new_text.split("\n")
138
+
139
+ _replace_paragraph_text_keep_style(template_para, lines[0],
140
+ korean_font=korean_font)
141
+
142
+ # Remove all paragraphs after the first
143
+ for p in list(cell._tc.findall(qn("w:p")))[1:]:
144
+ cell._tc.remove(p)
145
+
146
+ # Add new paragraphs for remaining lines (cloning first paragraph's pPr)
147
+ if len(lines) > 1:
148
+ from copy import deepcopy
149
+ first_p = cell._tc.find(qn("w:p"))
150
+ first_pPr = first_p.find(qn("w:pPr")) if first_p is not None else None
151
+ first_rPr = None
152
+ first_r = first_p.find(qn("w:r")) if first_p is not None else None
153
+ if first_r is not None:
154
+ first_rPr = first_r.find(qn("w:rPr"))
155
+
156
+ for line in lines[1:]:
157
+ new_p = OxmlElement("w:p")
158
+ if first_pPr is not None:
159
+ new_p.append(deepcopy(first_pPr))
160
+ new_r = OxmlElement("w:r")
161
+ if first_rPr is not None:
162
+ new_r.append(deepcopy(first_rPr))
163
+ t = OxmlElement("w:t")
164
+ t.text = line
165
+ t.set(qn("xml:space"), "preserve")
166
+ new_r.append(t)
167
+ new_p.append(new_r)
168
+ cell._tc.append(new_p)
169
+
170
+ if korean_font:
171
+ for p in cell.paragraphs:
172
+ for r in p.runs:
173
+ _set_run_korean_font(r, korean_font)
174
+
175
+
176
+ # ---------- FormFiller class ----------
177
+
178
+ @dataclass
179
+ class FillResult:
180
+ matched: list[str] = field(default_factory=list)
181
+ unmatched: list[str] = field(default_factory=list)
182
+
183
+
184
+ class FormFiller:
185
+ def __init__(self, template_path: str | Path,
186
+ korean_font: str = DEFAULT_KOREAN_FONT,
187
+ blank_between_paragraphs: bool = True,
188
+ blank_around_section_header: bool = True,
189
+ blank_around_all_section_headers: bool = False,
190
+ normalize_page_breaks: bool = True):
191
+ self.path = Path(template_path).expanduser().resolve()
192
+ if not self.path.exists():
193
+ raise FileNotFoundError(self.path)
194
+ self.doc = Document(str(self.path))
195
+ self.korean_font = korean_font
196
+ self.blank_between_paragraphs = blank_between_paragraphs
197
+ self.blank_around_section_header = blank_around_section_header
198
+ self.blank_around_all_section_headers = blank_around_all_section_headers
199
+ self.normalize_page_breaks_flag = normalize_page_breaks
200
+ self._filled_rows: set[int] = set()
201
+ self._table_results = FillResult()
202
+ self._paragraph_results = FillResult()
203
+
204
+ # ---- Table cell filling ----
205
+
206
+ def _cell_text(self, cell: _Cell) -> str:
207
+ return "\n".join(p.text for p in cell.paragraphs).strip()
208
+
209
+ def _label_match(self, cell_text: str, label: str) -> bool:
210
+ # Normalize whitespace and newlines
211
+ norm_cell = re.sub(r"\s+", "", cell_text)
212
+ norm_label = re.sub(r"\s+", "", label)
213
+ return norm_cell == norm_label
214
+
215
+ def fill_table_kv(self, label: str, value: str) -> bool:
216
+ """Find a cell whose text == label, fill the next cell on the right.
217
+
218
+ Returns True if filled, False otherwise.
219
+ Skips merged duplicate cells (same _tc reference).
220
+ """
221
+ for table in self.doc.tables:
222
+ for row_idx, row in enumerate(table.rows):
223
+ # Track unique cells in this row (skip merged duplicates)
224
+ seen_tcs: set[int] = set()
225
+ cells_in_row: list[_Cell] = []
226
+ for c in row.cells:
227
+ if id(c._tc) not in seen_tcs:
228
+ seen_tcs.add(id(c._tc))
229
+ cells_in_row.append(c)
230
+
231
+ for ci, cell in enumerate(cells_in_row):
232
+ if self._label_match(self._cell_text(cell), label):
233
+ # Found label cell. Fill the next cell on the right.
234
+ if ci + 1 < len(cells_in_row):
235
+ target = cells_in_row[ci + 1]
236
+ _replace_cell_text(target, value,
237
+ korean_font=self.korean_font)
238
+ _apply_cant_split(row)
239
+ self._table_results.matched.append(label)
240
+ return True
241
+ self._table_results.unmatched.append(label)
242
+ return False
243
+
244
+ # ---- Paragraph (section) filling ----
245
+
246
+ def replace_paragraphs_after(self, header_text: str, new_content: str,
247
+ stop_pattern: str | None = None) -> bool:
248
+ """Find a paragraph matching header_text, then replace all paragraphs
249
+ between this header and the next section header (or stop_pattern) with
250
+ new_content.
251
+
252
+ new_content is split by \n\n into separate paragraphs (preserving the
253
+ style of the first replaced paragraph).
254
+ """
255
+ body = self.doc.element.body
256
+ all_ps = list(self.doc.paragraphs)
257
+
258
+ # Find header paragraph
259
+ header_idx = None
260
+ for i, p in enumerate(all_ps):
261
+ if self._label_match(p.text, header_text):
262
+ header_idx = i
263
+ break
264
+
265
+ if header_idx is None:
266
+ self._paragraph_results.unmatched.append(header_text)
267
+ return False
268
+
269
+ # Determine end paragraph (next numbered section header or stop_pattern)
270
+ if stop_pattern:
271
+ end_re = re.compile(stop_pattern)
272
+ else:
273
+ # Match patterns like "1. ", "2. ", ... "18. "
274
+ end_re = re.compile(r"^\s*\d+\.\s+\S")
275
+
276
+ end_idx = len(all_ps)
277
+ for i in range(header_idx + 1, len(all_ps)):
278
+ if end_re.match(all_ps[i].text):
279
+ end_idx = i
280
+ break
281
+
282
+ # Paragraphs to replace: header_idx+1 .. end_idx-1
283
+ # Strategy: replace first paragraph in range, remove rest, add new paragraphs
284
+ if header_idx + 1 >= end_idx:
285
+ # No paragraphs between header and next section — just insert
286
+ from copy import deepcopy
287
+ template_p = all_ps[header_idx]._element
288
+ template_pPr = template_p.find(qn("w:pPr"))
289
+ template_r = template_p.find(qn("w:r"))
290
+ template_rPr = template_r.find(qn("w:rPr")) if template_r is not None else None
291
+
292
+ insert_after = template_p
293
+ # Blank line right after section header
294
+ if self.blank_around_section_header:
295
+ blank_p = _make_blank_paragraph()
296
+ insert_after.addnext(blank_p)
297
+ insert_after = blank_p
298
+ chunks = new_content.split("\n\n")
299
+ for ci, chunk in enumerate(chunks):
300
+ if ci > 0 and self.blank_between_paragraphs:
301
+ blank_p = _make_blank_paragraph()
302
+ insert_after.addnext(blank_p)
303
+ insert_after = blank_p
304
+ new_p = OxmlElement("w:p")
305
+ # New paragraph should NOT have header style — use default (no pPr)
306
+ new_r = OxmlElement("w:r")
307
+ t = OxmlElement("w:t")
308
+ t.text = chunk
309
+ t.set(qn("xml:space"), "preserve")
310
+ new_r.append(t)
311
+ new_p.append(new_r)
312
+ insert_after.addnext(new_p)
313
+ # Apply Korean font
314
+ from docx.text.run import Run
315
+ _set_run_korean_font(Run(new_r, None), self.korean_font)
316
+ insert_after = new_p
317
+ # Blank line right before next section header
318
+ if self.blank_around_section_header:
319
+ blank_p = _make_blank_paragraph()
320
+ insert_after.addnext(blank_p)
321
+ self._paragraph_results.matched.append(header_text)
322
+ return True
323
+
324
+ # Replace first paragraph in range
325
+ first_target = all_ps[header_idx + 1]
326
+ chunks = new_content.split("\n\n")
327
+ _replace_paragraph_text_keep_style(first_target, chunks[0],
328
+ korean_font=self.korean_font)
329
+
330
+ # Remove all paragraphs after first_target up to end_idx
331
+ for i in range(header_idx + 2, end_idx):
332
+ p_elem = all_ps[i]._element
333
+ p_elem.getparent().remove(p_elem)
334
+
335
+ # Insert blank paragraph right after section header (before first body)
336
+ first_target_elem = first_target._element
337
+ if self.blank_around_section_header:
338
+ blank_p = _make_blank_paragraph()
339
+ first_target_elem.addprevious(blank_p)
340
+
341
+ # Add additional chunks as new paragraphs after first_target
342
+ from copy import deepcopy
343
+ first_pPr = first_target_elem.find(qn("w:pPr"))
344
+ first_r = first_target_elem.find(qn("w:r"))
345
+ first_rPr = first_r.find(qn("w:rPr")) if first_r is not None else None
346
+
347
+ insert_after = first_target_elem
348
+ for chunk in chunks[1:]:
349
+ if self.blank_between_paragraphs:
350
+ blank_p = _make_blank_paragraph()
351
+ insert_after.addnext(blank_p)
352
+ insert_after = blank_p
353
+ new_p = OxmlElement("w:p")
354
+ if first_pPr is not None:
355
+ new_p.append(deepcopy(first_pPr))
356
+ new_r = OxmlElement("w:r")
357
+ if first_rPr is not None:
358
+ new_r.append(deepcopy(first_rPr))
359
+ t = OxmlElement("w:t")
360
+ t.text = chunk
361
+ t.set(qn("xml:space"), "preserve")
362
+ new_r.append(t)
363
+ new_p.append(new_r)
364
+ insert_after.addnext(new_p)
365
+ from docx.text.run import Run
366
+ _set_run_korean_font(Run(new_r, None), self.korean_font)
367
+ insert_after = new_p
368
+
369
+ # Blank line right before next section header
370
+ if self.blank_around_section_header:
371
+ blank_p = _make_blank_paragraph()
372
+ insert_after.addnext(blank_p)
373
+
374
+ self._paragraph_results.matched.append(header_text)
375
+ return True
376
+
377
+ # ---- Single-paragraph in-place text replace ----
378
+
379
+ def replace_paragraph_matching(self, matcher: str, new_text: str,
380
+ mode: str = "startswith") -> bool:
381
+ """Replace the entire text of the first paragraph that matches.
382
+
383
+ mode: 'startswith' | 'contains' | 'exact'
384
+ Preserves the paragraph's pPr and the first run's rPr (style).
385
+ """
386
+ for p in self.doc.paragraphs:
387
+ text = p.text
388
+ ok = False
389
+ if mode == "startswith":
390
+ ok = text.startswith(matcher)
391
+ elif mode == "contains":
392
+ ok = matcher in text
393
+ elif mode == "exact":
394
+ ok = text.strip() == matcher.strip()
395
+ if ok:
396
+ _replace_paragraph_text_keep_style(p, new_text,
397
+ korean_font=self.korean_font)
398
+ self._paragraph_results.matched.append(f"<para>{matcher}")
399
+ return True
400
+ self._paragraph_results.unmatched.append(f"<para>{matcher}")
401
+ return False
402
+
403
+ # ---- Document-wide passes ----
404
+
405
+ def apply_blank_around_all_section_headers(self) -> int:
406
+ """Scan all top-level paragraphs and add blank lines above and below
407
+ every numbered section header (e.g. '1. ', '12. ').
408
+
409
+ OPT-IN ONLY. Use this when the institutional review will tolerate
410
+ layout drift (page count change). For strict form-fidelity submissions,
411
+ leave disabled (default) and rely on per-section blanks added during
412
+ replace_paragraphs_after().
413
+
414
+ Skips:
415
+ - Headers whose previous sibling is already an empty paragraph
416
+ (avoids double-blanks when section was filled via section_replace)
417
+ - Headers whose next sibling is already an empty paragraph
418
+ - Paragraphs inside tables (only top-level body paragraphs scanned)
419
+
420
+ Returns the number of blank paragraphs inserted.
421
+ """
422
+ header_re = re.compile(r"^\s*\d+\.\s+\S")
423
+ body = self.doc.element.body
424
+ # Collect all top-level <w:p> elements (skip those inside <w:tbl>)
425
+ all_top_ps = [el for el in body if el.tag == qn("w:p")]
426
+ inserted = 0
427
+
428
+ def is_blank(p_elem) -> bool:
429
+ if p_elem is None or p_elem.tag != qn("w:p"):
430
+ return False
431
+ # Empty if no <w:t> with text content
432
+ for t in p_elem.iter(qn("w:t")):
433
+ if t.text and t.text.strip():
434
+ return False
435
+ return True
436
+
437
+ def text_of(p_elem) -> str:
438
+ return "".join(t.text or "" for t in p_elem.iter(qn("w:t")))
439
+
440
+ for p_elem in all_top_ps:
441
+ text = text_of(p_elem)
442
+ if not header_re.match(text):
443
+ continue
444
+ prev = p_elem.getprevious()
445
+ nxt = p_elem.getnext()
446
+ if not is_blank(prev):
447
+ p_elem.addprevious(_make_blank_paragraph())
448
+ inserted += 1
449
+ if not is_blank(nxt):
450
+ p_elem.addnext(_make_blank_paragraph())
451
+ inserted += 1
452
+ return inserted
453
+
454
+ # ---- Validation & save ----
455
+
456
+ def validate(self) -> list[str]:
457
+ warnings: list[str] = []
458
+ for label in self._table_results.unmatched:
459
+ warnings.append(f"[TABLE-MISS] Label not found: {label!r}")
460
+ for header in self._paragraph_results.unmatched:
461
+ warnings.append(f"[SECTION-MISS] Header not found: {header!r}")
462
+ return warnings
463
+
464
+ def report(self) -> str:
465
+ n_table_ok = len(self._table_results.matched)
466
+ n_table_miss = len(self._table_results.unmatched)
467
+ n_para_ok = len(self._paragraph_results.matched)
468
+ n_para_miss = len(self._paragraph_results.unmatched)
469
+ return (
470
+ f"Filled {n_table_ok} table cells, {n_para_ok} sections.\n"
471
+ f"Missed: {n_table_miss} cells, {n_para_miss} sections."
472
+ )
473
+
474
+ def normalize_page_breaks(self) -> int:
475
+ """Remove dangling empty paragraphs whose sole content is a page break,
476
+ and transfer the break to the next content paragraph via pageBreakBefore.
477
+
478
+ Why: templates often place `<w:p><w:r><w:br w:type="page"/></w:r></w:p>`
479
+ after a table or section header to force the next block onto a new page.
480
+ When the preceding content's height varies (e.g. an abstract table grows
481
+ with content), the empty paragraph can spill onto a page by itself and
482
+ the page break then forces the next block one more page forward —
483
+ producing a visibly blank page.
484
+
485
+ Replacing this pattern with `<w:pageBreakBefore/>` on the next content
486
+ paragraph's `pPr` preserves the "start on a new page" intent regardless
487
+ of where the preceding content ends, eliminating the blank page.
488
+
489
+ Returns the number of paragraphs normalized.
490
+ """
491
+ from copy import deepcopy # noqa: F401 (kept for parity with other helpers)
492
+
493
+ body = self.doc.element.body
494
+ children = list(body)
495
+ fixed = 0
496
+
497
+ for i, el in enumerate(children):
498
+ if not el.tag.endswith("}p"):
499
+ continue
500
+ # Only collapse paragraphs with NO real text, containing a page break
501
+ text = "".join((t.text or "") for t in el.iter(qn("w:t")))
502
+ if text.strip():
503
+ continue
504
+ page_brs = [b for b in el.iter(qn("w:br"))
505
+ if b.get(qn("w:type")) == "page"]
506
+ if not page_brs:
507
+ continue
508
+ # Find the next sibling content paragraph (non-empty p or table)
509
+ target = None
510
+ for j in range(i + 1, len(children)):
511
+ sib = children[j]
512
+ if sib.tag.endswith("}p"):
513
+ sib_text = "".join((t.text or "") for t in sib.iter(qn("w:t")))
514
+ if sib_text.strip():
515
+ target = sib
516
+ break
517
+ elif sib.tag.endswith("}tbl"):
518
+ # A table has no pPr; leave the break alone.
519
+ target = None
520
+ break
521
+ if target is None:
522
+ continue
523
+ # Attach pageBreakBefore to target's pPr (idempotent)
524
+ pPr = target.find(qn("w:pPr"))
525
+ if pPr is None:
526
+ pPr = OxmlElement("w:pPr")
527
+ target.insert(0, pPr)
528
+ if pPr.find(qn("w:pageBreakBefore")) is None:
529
+ pbb = OxmlElement("w:pageBreakBefore")
530
+ pPr.insert(0, pbb)
531
+ # Remove the dangling empty paragraph
532
+ el.getparent().remove(el)
533
+ fixed += 1
534
+
535
+ return fixed
536
+
537
+ def save(self, output_path: str | Path) -> Path:
538
+ if self.normalize_page_breaks_flag:
539
+ self.normalize_page_breaks()
540
+ out = Path(output_path).expanduser().resolve()
541
+ out.parent.mkdir(parents=True, exist_ok=True)
542
+ self.doc.save(str(out))
543
+ return out
544
+
545
+
546
+ # ---------- CLI ----------
547
+
548
+ def fill_from_yaml(template: Path, content_yaml: Path, output: Path) -> None:
549
+ with open(content_yaml, "r", encoding="utf-8") as f:
550
+ cfg = yaml.safe_load(f)
551
+
552
+ protections = cfg.get("protections", {}) or {}
553
+ korean_font = protections.get("korean_font", DEFAULT_KOREAN_FONT)
554
+ blank_between = protections.get("blank_between_paragraphs", True)
555
+ blank_around = protections.get("blank_around_section_header", True)
556
+ blank_around_all = protections.get("blank_around_all_section_headers", False)
557
+ normalize_pb = protections.get("normalize_page_breaks", True)
558
+ filler = FormFiller(template, korean_font=korean_font,
559
+ blank_between_paragraphs=blank_between,
560
+ blank_around_section_header=blank_around,
561
+ blank_around_all_section_headers=blank_around_all,
562
+ normalize_page_breaks=normalize_pb)
563
+
564
+ # Fill table key-value pairs
565
+ for label, value in (cfg.get("table_kv") or {}).items():
566
+ ok = filler.fill_table_kv(str(label), str(value))
567
+ status = "OK " if ok else "MISS"
568
+ print(f" [{status}] table_kv: {label!r}")
569
+
570
+ # Replace section content (between headers)
571
+ for header, content in (cfg.get("section_replace") or {}).items():
572
+ ok = filler.replace_paragraphs_after(str(header), str(content))
573
+ status = "OK " if ok else "MISS"
574
+ print(f" [{status}] section: {header!r}")
575
+
576
+ # Replace single paragraph in-place (e.g., title line)
577
+ for matcher, content in (cfg.get("paragraph_replace") or {}).items():
578
+ ok = filler.replace_paragraph_matching(str(matcher), str(content),
579
+ mode="startswith")
580
+ status = "OK " if ok else "MISS"
581
+ print(f" [{status}] paragraph: {matcher!r}")
582
+
583
+ # Document-wide pass: blank lines around ALL numbered section headers
584
+ if blank_around_all:
585
+ n = filler.apply_blank_around_all_section_headers()
586
+ print(f" [OK ] blank lines around all numbered headers: {n} inserted")
587
+
588
+ print()
589
+ print(filler.report())
590
+ print()
591
+
592
+ warnings = filler.validate()
593
+ for w in warnings:
594
+ print(f" WARN: {w}")
595
+
596
+ saved = filler.save(output)
597
+ print(f"\nSaved: {saved}")
598
+
599
+
600
+ def main():
601
+ parser = argparse.ArgumentParser(description=__doc__)
602
+ parser.add_argument("--template", required=True, help="Path to template .docx")
603
+ parser.add_argument("--content", required=True, help="Path to content YAML")
604
+ parser.add_argument("--output", required=True, help="Output .docx path")
605
+ args = parser.parse_args()
606
+
607
+ fill_from_yaml(Path(args.template), Path(args.content), Path(args.output))
608
+
609
+
610
+ if __name__ == "__main__":
611
+ main()
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """Inspect a Word template — list all tables, cells, and paragraphs.
3
+
4
+ Output identifies fillable cells (likely empty after a label cell).
5
+
6
+ Usage: python3 inspect_template.py <template.docx>
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from docx import Document
12
+
13
+
14
+ def cell_text(cell) -> str:
15
+ return "\n".join(p.text for p in cell.paragraphs).strip()
16
+
17
+
18
+ def main():
19
+ if len(sys.argv) < 2:
20
+ print(__doc__)
21
+ sys.exit(1)
22
+
23
+ path = Path(sys.argv[1]).expanduser().resolve()
24
+ doc = Document(str(path))
25
+
26
+ print(f"=== Template: {path.name} ===\n")
27
+
28
+ print(f"Sections: {len(doc.sections)}")
29
+ sec = doc.sections[0]
30
+ print(
31
+ f" Page: {sec.page_width.cm:.1f} × {sec.page_height.cm:.1f} cm, "
32
+ f"margins L/R/T/B: {sec.left_margin.cm:.1f}/{sec.right_margin.cm:.1f}/"
33
+ f"{sec.top_margin.cm:.1f}/{sec.bottom_margin.cm:.1f}"
34
+ )
35
+ print()
36
+
37
+ print(f"Tables: {len(doc.tables)}")
38
+ for ti, table in enumerate(doc.tables):
39
+ n_rows = len(table.rows)
40
+ n_cols = len(table.columns)
41
+ print(f"\n[Table {ti}] rows={n_rows}, cols={n_cols}")
42
+ for ri, row in enumerate(table.rows):
43
+ for ci, cell in enumerate(row.cells):
44
+ text = cell_text(cell)
45
+ preview = text.replace("\n", " ⏎ ")
46
+ if len(preview) > 70:
47
+ preview = preview[:67] + "..."
48
+ marker = " [empty]" if not text else ""
49
+ print(f" ({ri},{ci}): {preview!r}{marker}")
50
+
51
+ print(f"\nParagraphs (top-level, not in tables): {len(doc.paragraphs)}")
52
+ for pi, p in enumerate(doc.paragraphs):
53
+ text = p.text.strip()
54
+ if not text:
55
+ continue
56
+ preview = text[:80] + ("..." if len(text) > 80 else "")
57
+ print(f" P{pi}: {preview!r}")
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()