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,600 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 Ali Soroush
3
+ # Vendored from https://github.com/alisoroushmd/zotero-mcp
4
+ # src/zotero_mcp/citation_writer.py @ ed5dfb71
5
+ # Imported into medsci-skills 2026-05-01 (originally vendored into an active meta-analysis
6
+ # the same day, relocated here after an active meta-analysis project validation). No functional
7
+ # modifications — `inject_zotero_cwyw.py` patches `zotero_to_csl_json` at import
8
+ # time to use Zotero's native `?format=csljson` endpoint, which handles
9
+ # webpage / report / non-journal item types correctly. See ../NOTICE.md.
10
+ # Full license: ../LICENSE.zotero-mcp
11
+
12
+ """Citation writer -- builds Word documents with live Zotero field codes.
13
+
14
+ Creates .docx files containing ADDIN ZOTERO_ITEM and ADDIN ZOTERO_BIBL
15
+ field codes that the Zotero Word plugin recognizes as live citations.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import re
23
+ import uuid
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+
27
+ from docx import Document
28
+ from docx.oxml import OxmlElement
29
+ from docx.oxml.ns import qn
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # 1. Citation Parser
33
+ # ---------------------------------------------------------------------------
34
+
35
+ _CITATION_RE = re.compile(r"\[@([A-Za-z0-9]+(?:,\s*@[A-Za-z0-9]+)*)\]")
36
+
37
+
38
+ @dataclass
39
+ class TextBlock:
40
+ """A segment of parsed text -- either plain text or a citation group."""
41
+
42
+ kind: str # "text" or "citation"
43
+ content: str # raw text for "text", empty for "citation"
44
+ keys: list[str] = field(default_factory=list)
45
+ numbers: list[int] = field(default_factory=list)
46
+
47
+
48
+ def parse_citations(text: str) -> tuple[list[TextBlock], dict[str, int]]:
49
+ """Parse text with [@KEY] markers into blocks with Vancouver numbering.
50
+
51
+ Supports single ``[@KEY]`` and grouped ``[@KEY1, @KEY2]`` citations.
52
+ Numbers are assigned sequentially by order of first appearance.
53
+ Same key reuses its number.
54
+
55
+ Args:
56
+ text: Input text containing citation markers.
57
+
58
+ Returns:
59
+ Tuple of (list of TextBlocks, dict mapping item_key -> vancouver_number).
60
+ """
61
+ blocks: list[TextBlock] = []
62
+ mapping: dict[str, int] = {}
63
+ counter = 0
64
+ last_end = 0
65
+
66
+ for match in _CITATION_RE.finditer(text):
67
+ start, end = match.span()
68
+
69
+ # Text before this citation
70
+ if start > last_end:
71
+ blocks.append(TextBlock("text", text[last_end:start]))
72
+
73
+ # Parse keys from the match group
74
+ raw_keys = match.group(1)
75
+ keys = [k.strip().lstrip("@") for k in raw_keys.split(",")]
76
+
77
+ numbers: list[int] = []
78
+ for key in keys:
79
+ if key not in mapping:
80
+ counter += 1
81
+ mapping[key] = counter
82
+ numbers.append(mapping[key])
83
+
84
+ blocks.append(TextBlock("citation", "", keys, numbers))
85
+ last_end = end
86
+
87
+ # Trailing text
88
+ if last_end < len(text):
89
+ blocks.append(TextBlock("text", text[last_end:]))
90
+
91
+ return blocks, mapping
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # 2. CSL-JSON Converter
96
+ # ---------------------------------------------------------------------------
97
+
98
+ _ITEM_TYPE_MAP: dict[str, str] = {
99
+ "journalArticle": "article-journal",
100
+ "book": "book",
101
+ "bookSection": "chapter",
102
+ "conferencePaper": "paper-conference",
103
+ "report": "report",
104
+ "thesis": "thesis",
105
+ }
106
+
107
+ _FIELD_MAP: dict[str, str] = {
108
+ "title": "title",
109
+ "publicationTitle": "container-title",
110
+ "volume": "volume",
111
+ "issue": "issue",
112
+ "pages": "page",
113
+ "DOI": "DOI",
114
+ "ISSN": "ISSN",
115
+ "ISBN": "ISBN",
116
+ "abstractNote": "abstract",
117
+ "url": "URL",
118
+ "publisher": "publisher",
119
+ }
120
+
121
+
122
+ def _parse_date(date_str: str) -> dict:
123
+ """Parse a date string into CSL-JSON date format.
124
+
125
+ Handles ``2024``, ``2024-03``, ``2024-03-15``, and ``/``-separated
126
+ variants.
127
+
128
+ Args:
129
+ date_str: Date string from Zotero.
130
+
131
+ Returns:
132
+ Dict with ``date-parts`` key.
133
+ """
134
+ if not date_str:
135
+ return {"date-parts": [[]]}
136
+ parts_str = re.split(r"[-/\s]", date_str)
137
+ parts = [int(p) for p in parts_str if p.isdigit()]
138
+ return {"date-parts": [parts]} if parts else {"date-parts": [[]]}
139
+
140
+
141
+ def zotero_to_csl_json(item_data: dict, user_id: str) -> dict:
142
+ """Convert Zotero item data to CSL-JSON format.
143
+
144
+ Maps Zotero fields to CSL-JSON fields for embedding in Word field codes.
145
+
146
+ Args:
147
+ item_data: Zotero item metadata dict.
148
+ user_id: Zotero user ID for URI construction.
149
+
150
+ Returns:
151
+ CSL-JSON dict suitable for embedding in a Zotero field code.
152
+ """
153
+ item_key = item_data.get("key", "")
154
+ item_type = item_data.get("itemType", "")
155
+
156
+ csl: dict = {
157
+ "type": _ITEM_TYPE_MAP.get(item_type, "article"),
158
+ "id": int(hashlib.md5(item_key.encode()).hexdigest()[:8], 16),
159
+ "_uris": [f"http://zotero.org/users/{user_id}/items/{item_key}"],
160
+ }
161
+
162
+ # Map simple fields
163
+ for zotero_field, csl_field in _FIELD_MAP.items():
164
+ value = item_data.get(zotero_field)
165
+ if value:
166
+ csl[csl_field] = value
167
+
168
+ # Creators -- authors only
169
+ creators = item_data.get("creators", [])
170
+ authors = [
171
+ {"family": c.get("lastName", ""), "given": c.get("firstName", "")}
172
+ for c in creators
173
+ if c.get("creatorType") == "author"
174
+ ]
175
+ if authors:
176
+ csl["author"] = authors
177
+
178
+ # Date
179
+ date_str = item_data.get("date", "")
180
+ if date_str:
181
+ csl["issued"] = _parse_date(date_str)
182
+
183
+ return csl
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # 3. Field Code Builder
188
+ # ---------------------------------------------------------------------------
189
+
190
+
191
+ def _make_run() -> OxmlElement:
192
+ """Create a new ``w:r`` element."""
193
+ return OxmlElement("w:r")
194
+
195
+
196
+ def _make_fld_char(fld_char_type: str) -> OxmlElement:
197
+ """Create a ``w:fldChar`` element with the given type.
198
+
199
+ Args:
200
+ fld_char_type: One of ``begin``, ``separate``, ``end``.
201
+
202
+ Returns:
203
+ A ``w:r`` element containing the fldChar.
204
+ """
205
+ run = _make_run()
206
+ fld_char = OxmlElement("w:fldChar")
207
+ fld_char.set(qn("w:fldCharType"), fld_char_type)
208
+ run.append(fld_char)
209
+ return run
210
+
211
+
212
+ def _make_instr_text(text: str) -> OxmlElement:
213
+ """Create a ``w:r`` element containing ``w:instrText``.
214
+
215
+ Args:
216
+ text: The instruction text content.
217
+
218
+ Returns:
219
+ A ``w:r`` element with instrText child.
220
+ """
221
+ run = _make_run()
222
+ instr = OxmlElement("w:instrText")
223
+ instr.set(qn("xml:space"), "preserve")
224
+ instr.text = text
225
+ run.append(instr)
226
+ return run
227
+
228
+
229
+ def _make_superscript_run(text: str) -> OxmlElement:
230
+ """Create a ``w:r`` element with superscript text.
231
+
232
+ Args:
233
+ text: Display text to render as superscript.
234
+
235
+ Returns:
236
+ A ``w:r`` element with superscript formatting.
237
+ """
238
+ run = _make_run()
239
+ rpr = OxmlElement("w:rPr")
240
+ vert_align = OxmlElement("w:vertAlign")
241
+ vert_align.set(qn("w:val"), "superscript")
242
+ rpr.append(vert_align)
243
+ run.append(rpr)
244
+ t_elem = OxmlElement("w:t")
245
+ t_elem.set(qn("xml:space"), "preserve")
246
+ t_elem.text = text
247
+ run.append(t_elem)
248
+ return run
249
+
250
+
251
+ def add_citation_field(paragraph, citation_json: dict, display_text: str) -> None:
252
+ """Insert a Zotero citation field code into a Word paragraph.
253
+
254
+ Builds the XML structure:
255
+ begin fldChar -> instrText -> separate fldChar -> display run -> end fldChar
256
+
257
+ Args:
258
+ paragraph: A python-docx Paragraph object.
259
+ citation_json: The full citation JSON dict for the field code.
260
+ display_text: Text to display (typically the Vancouver number).
261
+ """
262
+ p_elem = paragraph._element
263
+ json_str = json.dumps(citation_json, ensure_ascii=False)
264
+ instr_content = f"ADDIN ZOTERO_ITEM CSL_CITATION {json_str}"
265
+
266
+ p_elem.append(_make_fld_char("begin"))
267
+ p_elem.append(_make_instr_text(instr_content))
268
+ p_elem.append(_make_fld_char("separate"))
269
+ p_elem.append(_make_superscript_run(display_text))
270
+ p_elem.append(_make_fld_char("end"))
271
+
272
+
273
+ def add_bibliography_field(paragraph) -> None:
274
+ """Insert a Zotero bibliography field code into a Word paragraph.
275
+
276
+ Args:
277
+ paragraph: A python-docx Paragraph object.
278
+ """
279
+ p_elem = paragraph._element
280
+ instr_content = 'ADDIN ZOTERO_BIBL {"uncited":[],"custom":[]} CSL_BIBLIOGRAPHY'
281
+
282
+ p_elem.append(_make_fld_char("begin"))
283
+ p_elem.append(_make_instr_text(instr_content))
284
+ p_elem.append(_make_fld_char("separate"))
285
+ p_elem.append(_make_fld_char("end"))
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # 4. Document Assembler
290
+ # ---------------------------------------------------------------------------
291
+
292
+ # Regex for inline markdown formatting
293
+ _BOLD_RE = re.compile(r"\*\*(.+?)\*\*")
294
+ _ITALIC_RE = re.compile(r"\*(.+?)\*")
295
+
296
+
297
+ def _add_formatted_text(paragraph, text: str) -> None:
298
+ """Add text with basic markdown formatting (bold/italic) to a paragraph.
299
+
300
+ Processes ``**bold**`` and ``*italic*`` markers. Text without markers
301
+ is added as plain runs.
302
+
303
+ Args:
304
+ paragraph: A python-docx Paragraph object.
305
+ text: Text that may contain markdown bold/italic markers.
306
+ """
307
+ # Split on bold markers first
308
+ parts = _BOLD_RE.split(text)
309
+ for i, part in enumerate(parts):
310
+ if not part:
311
+ continue
312
+ if i % 2 == 1:
313
+ # Bold segment
314
+ run = paragraph.add_run(part)
315
+ run.bold = True
316
+ else:
317
+ # Check for italic within non-bold segments
318
+ italic_parts = _ITALIC_RE.split(part)
319
+ for j, ipart in enumerate(italic_parts):
320
+ if not ipart:
321
+ continue
322
+ if j % 2 == 1:
323
+ run = paragraph.add_run(ipart)
324
+ run.italic = True
325
+ else:
326
+ paragraph.add_run(ipart)
327
+
328
+
329
+ def build_document(
330
+ content: str,
331
+ item_data: dict[str, dict],
332
+ user_id: str,
333
+ output_path: str,
334
+ ) -> str:
335
+ """Build a Word document with live Zotero citations.
336
+
337
+ Args:
338
+ content: Markdown text with ``[@KEY]`` citation markers.
339
+ item_data: Dict mapping item keys to their Zotero metadata.
340
+ user_id: Zotero user ID for URI construction.
341
+ output_path: Where to save the .docx file.
342
+
343
+ Returns:
344
+ Absolute path of the saved file.
345
+ """
346
+ doc = Document()
347
+
348
+ # Parse global citation numbering across the whole document
349
+ _, key_to_number = parse_citations(content)
350
+
351
+ # Split into paragraphs on blank lines
352
+ raw_paragraphs = re.split(r"\n\n+", content.strip())
353
+
354
+ for raw_para in raw_paragraphs:
355
+ raw_para = raw_para.strip()
356
+ if not raw_para:
357
+ continue
358
+
359
+ # Detect headings
360
+ heading_match = re.match(r"^(#{1,3})\s+(.+)$", raw_para)
361
+ if heading_match:
362
+ level = len(heading_match.group(1))
363
+ heading_text = heading_match.group(2)
364
+ doc.add_heading(heading_text, level=level)
365
+ continue
366
+
367
+ # Regular paragraph -- parse citations within it
368
+ # Use global key_to_number for consistent numbering across paragraphs
369
+ blocks, _ = parse_citations(raw_para)
370
+ for block in blocks:
371
+ if block.kind == "citation":
372
+ block.numbers = [key_to_number[k] for k in block.keys if k in key_to_number]
373
+
374
+ paragraph = doc.add_paragraph()
375
+
376
+ for block in blocks:
377
+ if block.kind == "text":
378
+ _add_formatted_text(paragraph, block.content)
379
+ elif block.kind == "citation":
380
+ # Build the citation field code
381
+ citation_items = []
382
+ for key in block.keys:
383
+ if key in item_data:
384
+ csl = zotero_to_csl_json(item_data[key], user_id)
385
+ uris = csl.pop("_uris", [])
386
+ citation_items.append(
387
+ {
388
+ "id": csl["id"],
389
+ "uris": uris,
390
+ "itemData": csl,
391
+ }
392
+ )
393
+
394
+ # Vancouver display: e.g. "1" or "1,2"
395
+ display = ",".join(str(n) for n in block.numbers)
396
+
397
+ citation_json = {
398
+ "citationID": f"cite_{uuid.uuid4().hex[:8]}",
399
+ "properties": {
400
+ "formattedCitation": display,
401
+ "plainCitation": display,
402
+ "noteIndex": 0,
403
+ },
404
+ "citationItems": citation_items,
405
+ "schema": (
406
+ "https://github.com/citation-style-language"
407
+ "/schema/raw/master/csl-citation.json"
408
+ ),
409
+ }
410
+ add_citation_field(paragraph, citation_json, display)
411
+
412
+ # Add References heading and bibliography
413
+ doc.add_heading("References", level=1)
414
+ bib_para = doc.add_paragraph()
415
+ add_bibliography_field(bib_para)
416
+
417
+ # Save
418
+ output = Path(output_path).resolve()
419
+ doc.save(str(output))
420
+ return str(output)
421
+
422
+
423
+ # ---------------------------------------------------------------------------
424
+ # 5. In-place Citation Insertion (preserves existing document formatting)
425
+ # ---------------------------------------------------------------------------
426
+
427
+
428
+ def _paragraph_full_text(paragraph) -> str:
429
+ """Extract full text from a paragraph including all runs.
430
+
431
+ Args:
432
+ paragraph: A python-docx Paragraph object.
433
+
434
+ Returns:
435
+ Concatenated text of all runs.
436
+ """
437
+ return "".join(run.text for run in paragraph.runs)
438
+
439
+
440
+ def _has_citation_markers(text: str) -> bool:
441
+ """Check whether text contains [@KEY] citation markers.
442
+
443
+ Args:
444
+ text: Text to check.
445
+
446
+ Returns:
447
+ True if citation markers are found.
448
+ """
449
+ return bool(_CITATION_RE.search(text))
450
+
451
+
452
+ def _rebuild_paragraph_with_citations(
453
+ paragraph,
454
+ blocks: list[TextBlock],
455
+ item_data: dict[str, dict],
456
+ user_id: str,
457
+ ) -> None:
458
+ """Replace a paragraph's content with citation field codes in-place.
459
+
460
+ Clears existing runs and rebuilds with text blocks and Zotero field codes.
461
+ Preserves the paragraph's style (heading level, alignment, spacing, etc.).
462
+
463
+ Args:
464
+ paragraph: A python-docx Paragraph object to modify.
465
+ blocks: Parsed TextBlocks from parse_citations.
466
+ item_data: Dict mapping item keys to Zotero metadata.
467
+ user_id: Zotero user ID for URI construction.
468
+ """
469
+ # Preserve paragraph style before clearing
470
+ style = paragraph.style
471
+
472
+ # Remove all existing runs from the paragraph XML
473
+ p_elem = paragraph._element
474
+ for child in list(p_elem):
475
+ if child.tag == qn("w:r"):
476
+ p_elem.remove(child)
477
+
478
+ # Restore style
479
+ paragraph.style = style
480
+
481
+ for block in blocks:
482
+ if block.kind == "text":
483
+ _add_formatted_text(paragraph, block.content)
484
+ elif block.kind == "citation":
485
+ citation_items = []
486
+ for key in block.keys:
487
+ if key in item_data:
488
+ csl = zotero_to_csl_json(item_data[key], user_id)
489
+ uris = csl.pop("_uris", [])
490
+ citation_items.append(
491
+ {
492
+ "id": csl["id"],
493
+ "uris": uris,
494
+ "itemData": csl,
495
+ }
496
+ )
497
+
498
+ display = ",".join(str(n) for n in block.numbers)
499
+
500
+ citation_json = {
501
+ "citationID": f"cite_{uuid.uuid4().hex[:8]}",
502
+ "properties": {
503
+ "formattedCitation": display,
504
+ "plainCitation": display,
505
+ "noteIndex": 0,
506
+ },
507
+ "citationItems": citation_items,
508
+ "schema": (
509
+ "https://github.com/citation-style-language/schema/raw/master/csl-citation.json"
510
+ ),
511
+ }
512
+ add_citation_field(paragraph, citation_json, display)
513
+
514
+
515
+ def insert_citations(
516
+ document_path: str,
517
+ item_data: dict[str, dict],
518
+ user_id: str,
519
+ output_path: str | None = None,
520
+ ) -> tuple[str, int]:
521
+ """Insert Zotero citation field codes into an existing Word document.
522
+
523
+ Opens the document, scans all paragraphs for [@KEY] markers, replaces
524
+ them with live Zotero field codes, and appends a bibliography if one
525
+ is not already present. All other document formatting (styles, headers,
526
+ footers, images, tables, page layout) is preserved.
527
+
528
+ Args:
529
+ document_path: Path to the existing .docx file.
530
+ item_data: Dict mapping item keys to their Zotero metadata.
531
+ user_id: Zotero user ID for URI construction.
532
+ output_path: Where to save. If None, overwrites the original.
533
+
534
+ Returns:
535
+ Tuple of (saved file path, number of citation markers replaced).
536
+ """
537
+ doc = Document(document_path)
538
+ save_to = Path(output_path or document_path).resolve()
539
+
540
+ # First pass: collect all citation keys across the entire document
541
+ # for consistent Vancouver numbering
542
+ all_text_parts: list[str] = []
543
+ for paragraph in doc.paragraphs:
544
+ text = _paragraph_full_text(paragraph)
545
+ if _has_citation_markers(text):
546
+ all_text_parts.append(text)
547
+
548
+ # Also scan tables
549
+ for table in doc.tables:
550
+ for row in table.rows:
551
+ for cell in row.cells:
552
+ for paragraph in cell.paragraphs:
553
+ text = _paragraph_full_text(paragraph)
554
+ if _has_citation_markers(text):
555
+ all_text_parts.append(text)
556
+
557
+ if not all_text_parts:
558
+ doc.save(str(save_to))
559
+ return str(save_to), 0
560
+
561
+ # Build global numbering from concatenated text
562
+ combined = "\n\n".join(all_text_parts)
563
+ _, key_to_number = parse_citations(combined)
564
+ citation_count = len(key_to_number)
565
+
566
+ # Second pass: rebuild paragraphs that contain citation markers
567
+ def _process_paragraph(paragraph) -> None:
568
+ text = _paragraph_full_text(paragraph)
569
+ if not _has_citation_markers(text):
570
+ return
571
+ blocks, _ = parse_citations(text)
572
+ # Remap to global numbering
573
+ for block in blocks:
574
+ if block.kind == "citation":
575
+ block.numbers = [key_to_number[k] for k in block.keys if k in key_to_number]
576
+ _rebuild_paragraph_with_citations(paragraph, blocks, item_data, user_id)
577
+
578
+ for paragraph in doc.paragraphs:
579
+ _process_paragraph(paragraph)
580
+
581
+ for table in doc.tables:
582
+ for row in table.rows:
583
+ for cell in row.cells:
584
+ for paragraph in cell.paragraphs:
585
+ _process_paragraph(paragraph)
586
+
587
+ # Add bibliography if not already present
588
+ has_bibliography = False
589
+ for paragraph in doc.paragraphs:
590
+ if "ADDIN ZOTERO_BIBL" in paragraph._element.xml:
591
+ has_bibliography = True
592
+ break
593
+
594
+ if not has_bibliography:
595
+ doc.add_heading("References", level=1)
596
+ bib_para = doc.add_paragraph()
597
+ add_bibliography_field(bib_para)
598
+
599
+ doc.save(str(save_to))
600
+ return str(save_to), citation_count
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ """check_citation_keys.py — Validate pandoc-style [@bibkey] citations against a .bib file.
3
+
4
+ Reports:
5
+ - keys cited in markdown but missing from .bib (UNDEFINED) — always fail
6
+ - keys present in .bib but never cited (UNUSED) — warn by default,
7
+ suppress with --allow-unused, or escalate with --strict-unused
8
+
9
+ Usage:
10
+ check_citation_keys.py manuscript.md references.bib
11
+ check_citation_keys.py manuscript.md references.bib --allow-unused
12
+ check_citation_keys.py manuscript.md references.bib --strict-unused
13
+
14
+ Why --allow-unused exists:
15
+ During early drafting, the .bib often holds a working set of candidate
16
+ references that have not yet been cited in the manuscript. UNUSED output
17
+ is noise in that phase and makes the diagnostic harder to read. Pass
18
+ --allow-unused to suppress UNUSED reporting entirely; UNDEFINED remains a
19
+ hard failure.
20
+
21
+ Why --strict-unused exists:
22
+ At submission-package freeze time, UNUSED entries usually represent
23
+ forgotten edits (a citation was removed from the manuscript but the .bib
24
+ entry remains). Pass --strict-unused to treat any UNUSED entry as a
25
+ build failure so the freeze gate catches it.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import re
31
+ import sys
32
+ from pathlib import Path
33
+
34
+ # pandoc citation syntax: [@key], [@key, p. 3], [@key1; @key2], [-@key] (suppress author)
35
+ # A key is alnum + : . _ - / + (per pandoc docs)
36
+ CITE_RE = re.compile(r"(?<![A-Za-z0-9_])-?@([A-Za-z][\w:.\-/+]*)")
37
+ BIB_KEY_RE = re.compile(r"^@\w+\s*\{\s*([^,\s]+)\s*,", re.MULTILINE)
38
+
39
+
40
+ def extract_md_keys(md_path: Path) -> set[str]:
41
+ text = md_path.read_text(encoding="utf-8")
42
+ # strip code fences to avoid false positives
43
+ text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
44
+ text = re.sub(r"`[^`\n]+`", "", text)
45
+ return set(CITE_RE.findall(text))
46
+
47
+
48
+ def extract_bib_keys(bib_path: Path) -> set[str]:
49
+ text = bib_path.read_text(encoding="utf-8", errors="replace")
50
+ return set(BIB_KEY_RE.findall(text))
51
+
52
+
53
+ def main() -> int:
54
+ parser = argparse.ArgumentParser(
55
+ description=__doc__,
56
+ formatter_class=argparse.RawDescriptionHelpFormatter,
57
+ )
58
+ parser.add_argument("markdown", type=Path, help="Path to manuscript.md")
59
+ parser.add_argument("bib", type=Path, help="Path to references.bib")
60
+ group = parser.add_mutually_exclusive_group()
61
+ group.add_argument(
62
+ "--allow-unused",
63
+ action="store_true",
64
+ help="Suppress UNUSED reporting entirely (drafting mode).",
65
+ )
66
+ group.add_argument(
67
+ "--strict-unused",
68
+ action="store_true",
69
+ help="Treat any UNUSED entry as a build failure (submission gate).",
70
+ )
71
+ args = parser.parse_args()
72
+
73
+ if not args.markdown.exists():
74
+ print(f"ERROR: markdown not found: {args.markdown}", file=sys.stderr)
75
+ return 2
76
+ if not args.bib.exists():
77
+ print(f"ERROR: bib not found: {args.bib}", file=sys.stderr)
78
+ return 2
79
+
80
+ cited = extract_md_keys(args.markdown)
81
+ defined = extract_bib_keys(args.bib)
82
+
83
+ undefined = sorted(cited - defined)
84
+ unused = sorted(defined - cited)
85
+
86
+ print(f"[check_citation_keys] cited={len(cited)} defined={len(defined)}")
87
+ if undefined:
88
+ print(f"\nUNDEFINED ({len(undefined)}) — cited in markdown but not in .bib:")
89
+ for k in undefined:
90
+ print(f" [@{k}]")
91
+ show_unused = unused and not args.allow_unused
92
+ if show_unused:
93
+ label = "UNUSED" if not args.strict_unused else "UNUSED (--strict-unused: treated as failure)"
94
+ print(f"\n{label} ({len(unused)}) — defined in .bib but never cited:")
95
+ for k in unused:
96
+ print(f" {k}")
97
+ if not undefined and not show_unused:
98
+ if args.allow_unused and unused:
99
+ print(f"OK: all cited keys defined ({len(unused)} UNUSED suppressed by --allow-unused).")
100
+ else:
101
+ print("OK: all cited keys defined and all defined keys used.")
102
+
103
+ exit_code = 0
104
+ if undefined:
105
+ exit_code = 1
106
+ elif unused and args.strict_unused:
107
+ exit_code = 1
108
+ return exit_code
109
+
110
+
111
+ if __name__ == "__main__":
112
+ sys.exit(main())