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,633 @@
1
+ #!/usr/bin/env python3
2
+ """check_xref.py — Manuscript ↔ rendered DOCX cross-reference QC.
3
+
4
+ Catches the failure mode where in-text references to Tables / Figures /
5
+ Supplementary Tables / Supplementary Figures point to labels that either
6
+ (a) do not exist in the rendered DOCX, (b) have no caption definition in
7
+ the manuscript body, or (c) carry a caption text in the rendered DOCX
8
+ that disagrees with the body's caption definition.
9
+
10
+ Precedent: an STROBE cohort manuscript revision — body cited
11
+ "Supp Table S4 (a sensitivity-analysis)" but the rendered DOCX S4 was
12
+ a diagnostics table; S1, S6, S7 mismatched; S8, S9 cited but absent
13
+ from DOCX. Internal consistency checks did not catch this because the
14
+ build script carried its own legacy SSOT.
15
+
16
+ Inputs
17
+ ------
18
+ --md PATH manuscript markdown (the in-text citation source)
19
+ --docx PATH rendered DOCX (optional but recommended)
20
+ --out PATH JSON audit (default: qc/xref_audit.json)
21
+ --strict exit 1 on any non-OK finding (submission gate)
22
+ --quiet suppress stdout summary table
23
+ --vN-docx-md5 PATH
24
+ v_N (previous version) docx path. When supplied:
25
+ (a) asserts the new --docx MD5 differs from this docx
26
+ (identity = unmodified seed copy);
27
+ (b) when --vN-md is also supplied, computes the
28
+ markdown-only diff and verifies each diff line
29
+ appears verbatim in the new docx body XML.
30
+ --vN-md PATH v_N markdown path (used by --vN-docx-md5 diff check).
31
+
32
+ v_(N+1) docx regeneration check
33
+ -------------------------------
34
+ Defense-in-depth at build time (complements verify_package_integrity.py
35
+ --assert-vN-docx-changed which runs at submission time). If a markdown
36
+ body change exists between v_N and v_(N+1) but the v_(N+1) docx is a
37
+ byte-identical copy of v_N, the change will silently revert at peer
38
+ review. The check fails fast at the QC stage.
39
+
40
+ Output
41
+ ------
42
+ qc/xref_audit.json with submission_safe boolean and per-label rows.
43
+ When --vN-docx-md5 is used, the JSON also carries a `vN_docx_check`
44
+ block with `identical_bytes` and `diff_line_misses` fields.
45
+
46
+ Exit codes
47
+ ----------
48
+ 0 all OK (or non-strict and only warnings)
49
+ 1 --strict and at least one non-OK finding
50
+ OR v_N docx identity / diff-miss assertion failure
51
+ 2 argument / IO error
52
+
53
+ Dependencies
54
+ ------------
55
+ python-docx (only if --docx is passed). Falls back to body-only audit
56
+ with a warning if python-docx is unavailable.
57
+ """
58
+ from __future__ import annotations
59
+
60
+ import argparse
61
+ import json
62
+ import re
63
+ import sys
64
+ from dataclasses import dataclass, field, asdict
65
+ from pathlib import Path
66
+ from typing import Optional
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Regex
71
+ # ---------------------------------------------------------------------------
72
+
73
+ # In-text citation token. Captures:
74
+ # group 1: "Supplementary " | "Supp " | "" (supplementary marker)
75
+ # group 2: Table | Figure
76
+ # group 3: number, possibly S-prefixed, with optional letter suffix (e.g. S4, 2A)
77
+ CITE_RE = re.compile(
78
+ r"(?<![A-Za-z])(Supplementary\s+|Supp\s+|Supp\.\s+|S\.\s*)?"
79
+ r"(Table|Figure|Fig\.|Fig)\s+"
80
+ r"(S?\d+[A-Za-z]?)",
81
+ re.IGNORECASE,
82
+ )
83
+
84
+ # Caption definition (start of line, optional bold markdown). Matches:
85
+ # Table 1. Caption text...
86
+ # **Table 1.** Caption...
87
+ # Supplementary Table S4. Caption...
88
+ CAPTION_RE = re.compile(
89
+ r"^\s*(?:\*\*)?\s*"
90
+ r"(Supplementary\s+|Supp\s+|Supp\.\s+)?"
91
+ r"(Table|Figure|Fig\.|Fig)\s+"
92
+ r"(S?\d+[A-Za-z]?)"
93
+ r"\s*[.:]\s*(?:\*\*)?\s*"
94
+ r"(.+?)\s*$",
95
+ re.IGNORECASE | re.MULTILINE,
96
+ )
97
+
98
+ # Section header patterns for the manuscript body's caption sections.
99
+ # Longer alternatives MUST come first so that "FIGURE LEGENDS" is not
100
+ # truncated to "FIGURE" by an earlier short match.
101
+ # Markdown bold wrappers (``## **FIGURE LEGENDS**``) are tolerated.
102
+ CAPTION_SECTION_RE = re.compile(
103
+ r"^#{1,3}\s+\*{0,2}"
104
+ r"(Supplementary\s+Tables?|Supplementary\s+Figures?|Supplementary\s+Materials?|"
105
+ r"Figure\s+Legends?|Table\s+(?:Captions?|Legends?)|"
106
+ r"Tables?|Figures?)"
107
+ r"\*{0,2}",
108
+ re.IGNORECASE | re.MULTILINE,
109
+ )
110
+
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # Data classes
114
+ # ---------------------------------------------------------------------------
115
+
116
+ @dataclass
117
+ class Label:
118
+ """Canonical (kind, supplementary, number) tuple for a Table/Figure."""
119
+ kind: str # "Table" | "Figure"
120
+ supplementary: bool
121
+ number: str # "1", "S4", "2A"
122
+
123
+ @property
124
+ def key(self) -> str:
125
+ prefix = "S-" if self.supplementary else ""
126
+ return f"{self.kind}:{prefix}{self.number}"
127
+
128
+ @property
129
+ def display(self) -> str:
130
+ supp = "Supplementary " if self.supplementary else ""
131
+ return f"{supp}{self.kind} {self.number}"
132
+
133
+
134
+ @dataclass
135
+ class Caption:
136
+ label: Label
137
+ text: str
138
+ source: str # "body" | "docx"
139
+
140
+
141
+ @dataclass
142
+ class Finding:
143
+ label: str
144
+ status: str # OK | MISSING_DOCX | MISSING_BODY | MISMATCH | UNCITED | NOT_CITED_NO_BODY
145
+ cited: bool
146
+ in_body: bool
147
+ in_docx: Optional[bool] # None if --docx not provided
148
+ body_caption: Optional[str]
149
+ docx_caption: Optional[str]
150
+ note: str = ""
151
+
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Parsing
155
+ # ---------------------------------------------------------------------------
156
+
157
+ def _normalize(kind_raw: str, supp_raw: Optional[str], number: str) -> Label:
158
+ kind = "Figure" if kind_raw.lower().startswith("fig") else "Table"
159
+ supplementary = bool(supp_raw) or number.upper().startswith("S")
160
+ # Normalize number capitalization (S4a -> S4A, lowercase letter suffix uppercase)
161
+ if len(number) > 1 and number[-1].isalpha():
162
+ number = number[:-1].upper() + number[-1].upper()
163
+ else:
164
+ number = number.upper() if number.upper().startswith("S") else number
165
+ return Label(kind=kind, supplementary=supplementary, number=number)
166
+
167
+
168
+ def extract_citations(md_text: str, body_caption_offsets: list[tuple[int, int]]) -> list[Label]:
169
+ """Extract in-text citations excluding ranges that are caption sections."""
170
+ # Mask caption sections so caption first lines don't double as citations
171
+ masked = list(md_text)
172
+ for start, end in body_caption_offsets:
173
+ for i in range(start, min(end, len(masked))):
174
+ masked[i] = " "
175
+ text = "".join(masked)
176
+
177
+ # Strip fenced code
178
+ text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
179
+ text = re.sub(r"`[^`\n]+`", "", text)
180
+
181
+ labels: list[Label] = []
182
+ for m in CITE_RE.finditer(text):
183
+ labels.append(_normalize(m.group(2), m.group(1), m.group(3)))
184
+ return labels
185
+
186
+
187
+ def find_caption_section_ranges(md_text: str) -> list[tuple[int, int]]:
188
+ """Return (start, end) byte offsets for body caption sections."""
189
+ headers = list(CAPTION_SECTION_RE.finditer(md_text))
190
+ ranges: list[tuple[int, int]] = []
191
+ for i, h in enumerate(headers):
192
+ start = h.start()
193
+ end = headers[i + 1].start() if i + 1 < len(headers) else len(md_text)
194
+ # If next non-caption section header appears (## something else), cut there
195
+ # Find next ^#{1,3}\s heading after start that is NOT a caption section
196
+ next_section = re.search(r"\n#{1,3}\s+\S", md_text[h.end():end])
197
+ if next_section:
198
+ tentative_end = h.end() + next_section.start()
199
+ # Only cut if the next heading is not itself a caption section
200
+ after = md_text[h.end() + next_section.start():end]
201
+ if not CAPTION_SECTION_RE.match(after):
202
+ end = tentative_end
203
+ ranges.append((start, end))
204
+ return ranges
205
+
206
+
207
+ def extract_body_captions(md_text: str) -> dict[str, Caption]:
208
+ """Extract caption definitions from the manuscript body's caption sections."""
209
+ ranges = find_caption_section_ranges(md_text)
210
+ captions: dict[str, Caption] = {}
211
+ for start, end in ranges:
212
+ chunk = md_text[start:end]
213
+ for m in CAPTION_RE.finditer(chunk):
214
+ label = _normalize(m.group(2), m.group(1), m.group(3))
215
+ text = m.group(4).strip().rstrip("*").strip()
216
+ # Keep first definition per label (later ones likely continuation lines)
217
+ if label.key not in captions:
218
+ captions[label.key] = Caption(label=label, text=text, source="body")
219
+ return captions
220
+
221
+
222
+ def extract_docx_captions(docx_path: Path) -> dict[str, Caption]:
223
+ """Extract caption paragraphs from a rendered DOCX using python-docx."""
224
+ try:
225
+ from docx import Document # type: ignore
226
+ except ImportError:
227
+ print(
228
+ "[check_xref] WARNING: python-docx not installed; "
229
+ "skipping rendered-DOCX audit. Install with: pip install python-docx",
230
+ file=sys.stderr,
231
+ )
232
+ return {}
233
+
234
+ doc = Document(str(docx_path))
235
+ captions: dict[str, Caption] = {}
236
+ for para in doc.paragraphs:
237
+ text = para.text.strip()
238
+ if not text:
239
+ continue
240
+ m = CAPTION_RE.match(text)
241
+ if not m:
242
+ continue
243
+ label = _normalize(m.group(2), m.group(1), m.group(3))
244
+ caption_text = m.group(4).strip()
245
+ if label.key not in captions:
246
+ captions[label.key] = Caption(label=label, text=caption_text, source="docx")
247
+
248
+ # Also scan tables (Word can put captions in adjacent paragraphs that are inside cells
249
+ # or before the table). The paragraph scan above is usually sufficient.
250
+ return captions
251
+
252
+
253
+ # ---------------------------------------------------------------------------
254
+ # Reconciliation
255
+ # ---------------------------------------------------------------------------
256
+
257
+ def _tokens(text: str) -> set[str]:
258
+ return set(re.findall(r"[A-Za-z][A-Za-z0-9]+", text.lower()))
259
+
260
+
261
+ def caption_agreement(a: str, b: str, threshold: float = 0.4) -> tuple[bool, float]:
262
+ """Heuristic agreement: Jaccard token overlap >= threshold = agree."""
263
+ ta, tb = _tokens(a), _tokens(b)
264
+ if not ta or not tb:
265
+ return False, 0.0
266
+ inter = len(ta & tb)
267
+ union = len(ta | tb)
268
+ j = inter / union if union else 0.0
269
+ return j >= threshold, j
270
+
271
+
272
+ def reconcile(
273
+ citations: list[Label],
274
+ body: dict[str, Caption],
275
+ docx: Optional[dict[str, Caption]],
276
+ ) -> list[Finding]:
277
+ cited_keys = {lbl.key for lbl in citations}
278
+ body_keys = set(body.keys())
279
+ docx_keys = set(docx.keys()) if docx is not None else set()
280
+ all_keys = cited_keys | body_keys | docx_keys
281
+
282
+ findings: list[Finding] = []
283
+ for key in sorted(all_keys, key=_sort_key):
284
+ is_cited = key in cited_keys
285
+ in_body = key in body_keys
286
+ in_docx = (key in docx_keys) if docx is not None else None
287
+
288
+ body_text = body[key].text if in_body else None
289
+ docx_text = docx[key].text if (docx is not None and key in docx_keys) else None
290
+
291
+ # Panel-letter fallback: "Figure 2A" cite resolves to "Figure 2" caption.
292
+ panel_note = ""
293
+ if is_cited and not in_body:
294
+ base = _strip_panel(key)
295
+ if base and base in body_keys:
296
+ in_body = True
297
+ body_text = body[base].text
298
+ panel_note = f"panel reference; resolved to {base.replace(':', ' ')}"
299
+ if is_cited and in_docx is False:
300
+ base = _strip_panel(key)
301
+ if base and base in docx_keys:
302
+ in_docx = True
303
+ docx_text = docx[base].text # type: ignore[index]
304
+ panel_note = (panel_note + "; " if panel_note else "") + \
305
+ f"panel reference resolved to {base.replace(':', ' ')} in DOCX"
306
+
307
+ status, note = _classify(is_cited, in_body, in_docx, body_text, docx_text)
308
+ if panel_note and status == "OK":
309
+ note = panel_note
310
+
311
+ findings.append(Finding(
312
+ label=key,
313
+ status=status,
314
+ cited=is_cited,
315
+ in_body=in_body,
316
+ in_docx=in_docx,
317
+ body_caption=body_text,
318
+ docx_caption=docx_text,
319
+ note=note,
320
+ ))
321
+ return findings
322
+
323
+
324
+ def _strip_panel(key: str) -> Optional[str]:
325
+ """Map 'Figure:2A' -> 'Figure:2' (panel letter strip). Returns None if no change."""
326
+ kind, num = key.split(":", 1)
327
+ m = re.match(r"^(S?)(\d+)([A-Z])$", num)
328
+ if m and m.group(3):
329
+ return f"{kind}:{m.group(1)}{m.group(2)}"
330
+ return None
331
+
332
+
333
+ def _classify(
334
+ cited: bool,
335
+ in_body: bool,
336
+ in_docx: Optional[bool],
337
+ body_text: Optional[str],
338
+ docx_text: Optional[str],
339
+ ) -> tuple[str, str]:
340
+ if not cited:
341
+ if in_body or in_docx:
342
+ return "UNCITED", "defined or rendered but never cited in main text"
343
+ return "NOT_CITED_NO_BODY", ""
344
+
345
+ # Cited
346
+ if in_docx is False:
347
+ return "MISSING_DOCX", "cited but absent from rendered DOCX"
348
+ if not in_body and in_docx is None:
349
+ return "MISSING_BODY", "cited but no caption definition in markdown body sections"
350
+ if not in_body and in_docx:
351
+ return "MISSING_BODY", "cited and rendered, but no body caption definition (build SSOT drift risk)"
352
+
353
+ # Both body and docx (or body only when in_docx is None)
354
+ if body_text and docx_text:
355
+ agree, j = caption_agreement(body_text, docx_text)
356
+ if not agree:
357
+ return "MISMATCH", f"caption text disagrees (Jaccard={j:.2f})"
358
+ return "OK", ""
359
+
360
+
361
+ def _sort_key(key: str) -> tuple:
362
+ # "Table:S4" -> ("Table", 1, 4) ; "Figure:2A" -> ("Figure", 0, 2, "A")
363
+ kind, num = key.split(":", 1)
364
+ is_supp = 1 if num.startswith("S") else 0
365
+ digits = num.lstrip("S")
366
+ m = re.match(r"^(\d+)([A-Z]?)$", digits)
367
+ if m:
368
+ return (kind, is_supp, int(m.group(1)), m.group(2))
369
+ return (kind, is_supp, 999, digits)
370
+
371
+
372
+ # ---------------------------------------------------------------------------
373
+ # CLI
374
+ # ---------------------------------------------------------------------------
375
+
376
+ def render_summary(findings: list[Finding], cited_count: int) -> str:
377
+ counts: dict[str, int] = {}
378
+ for f in findings:
379
+ counts[f.status] = counts.get(f.status, 0) + 1
380
+
381
+ lines = []
382
+ lines.append(f"\n[check_xref] in-text citations: {cited_count}, unique labels: {len(findings)}")
383
+ lines.append("Status summary: " + ", ".join(f"{k}={v}" for k, v in sorted(counts.items())))
384
+ lines.append("")
385
+ lines.append(f" {'LABEL':<14} {'STATUS':<18} {'CITED':<6} {'BODY':<5} {'DOCX':<5} NOTE")
386
+ for f in findings:
387
+ docx_mark = "—" if f.in_docx is None else ("✓" if f.in_docx else "✗")
388
+ lines.append(
389
+ f" {f.label:<14} {f.status:<18} "
390
+ f"{'✓' if f.cited else '✗':<6} "
391
+ f"{'✓' if f.in_body else '✗':<5} "
392
+ f"{docx_mark:<5} {f.note}"
393
+ )
394
+ return "\n".join(lines)
395
+
396
+
397
+ def _md5_of(path: Path) -> str:
398
+ import hashlib
399
+ h = hashlib.md5()
400
+ with path.open("rb") as fh:
401
+ for chunk in iter(lambda: fh.read(65536), b""):
402
+ h.update(chunk)
403
+ return h.hexdigest()
404
+
405
+
406
+ def _docx_body_text(docx_path: Path) -> str:
407
+ """Concatenate all w:t text content from a docx for substring search."""
408
+ import zipfile
409
+ try:
410
+ with zipfile.ZipFile(docx_path, "r") as z:
411
+ xml = z.read("word/document.xml").decode("utf-8", errors="replace")
412
+ except (zipfile.BadZipFile, KeyError, OSError):
413
+ return ""
414
+ # Strip XML tags — we only need text content for verbatim grep.
415
+ return re.sub(r"<[^>]+>", " ", xml)
416
+
417
+
418
+ def _markdown_diff_lines(vN_md: Path, new_md: Path) -> list[str]:
419
+ """Lines present in new_md but not in vN_md (added/changed lines).
420
+
421
+ Trimmed to non-trivial substrings (≥40 chars after stripping markdown
422
+ metacharacters) so that the verbatim grep is meaningful.
423
+ """
424
+ old_lines = set(vN_md.read_text(encoding="utf-8").splitlines())
425
+ out: list[str] = []
426
+ for ln in new_md.read_text(encoding="utf-8").splitlines():
427
+ if ln in old_lines:
428
+ continue
429
+ # Strip leading markdown noise (headers, list markers) and YAML.
430
+ clean = re.sub(r"^[\s#>*\-_+~`|]+", "", ln).strip()
431
+ if len(clean) >= 40:
432
+ out.append(clean)
433
+ return out
434
+
435
+
436
+ def run_vN_docx_check(
437
+ new_docx: Path,
438
+ vN_docx: Path,
439
+ new_md: Path | None,
440
+ vN_md: Path | None,
441
+ ) -> dict:
442
+ """Returns {identical_bytes, diff_line_misses, error?}.
443
+
444
+ identical_bytes is True if v_N and new docx have the same MD5.
445
+ diff_line_misses lists v_N→v_(N+1) markdown additions that do NOT
446
+ appear in the new docx body XML.
447
+ """
448
+ out: dict = {
449
+ "vN_docx": str(vN_docx),
450
+ "new_docx": str(new_docx),
451
+ "identical_bytes": False,
452
+ "diff_line_misses": [],
453
+ }
454
+ if not vN_docx.is_file():
455
+ out["error"] = f"v_N docx not found: {vN_docx}"
456
+ return out
457
+ if not new_docx.is_file():
458
+ out["error"] = f"new docx not found: {new_docx}"
459
+ return out
460
+ if _md5_of(vN_docx) == _md5_of(new_docx):
461
+ out["identical_bytes"] = True
462
+ return out # Identity already disqualifies — no point checking diff.
463
+
464
+ if new_md is not None and vN_md is not None:
465
+ if not new_md.is_file() or not vN_md.is_file():
466
+ out["error"] = "v_N md or new md not found"
467
+ return out
468
+ diff_lines = _markdown_diff_lines(vN_md, new_md)
469
+ body_text = _docx_body_text(new_docx)
470
+ # Light normalization: collapse runs of whitespace.
471
+ body_norm = re.sub(r"\s+", " ", body_text).lower()
472
+ misses: list[str] = []
473
+ for diff in diff_lines:
474
+ needle = re.sub(r"\s+", " ", diff).lower()
475
+ if needle not in body_norm:
476
+ misses.append(diff[:120])
477
+ out["diff_line_misses"] = misses
478
+ return out
479
+
480
+
481
+ def main() -> int:
482
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
483
+ parser.add_argument("--md", required=True, type=Path, help="manuscript.md path")
484
+ parser.add_argument("--docx", type=Path, default=None, help="rendered DOCX path (optional)")
485
+ parser.add_argument("--out", type=Path, default=Path("qc/xref_audit.json"))
486
+ parser.add_argument("--strict", action="store_true", help="exit 1 on any non-OK finding")
487
+ parser.add_argument("--quiet", action="store_true")
488
+ parser.add_argument(
489
+ "--allow-separate-attachments", action="store_true",
490
+ help="Downgrade MISSING_DOCX from FAIL to WARN for figures/tables that are "
491
+ "submitted as separate attachment files (common in radiology and many "
492
+ "medical journals). MISSING_BODY and MISMATCH remain FAIL regardless.",
493
+ )
494
+ parser.add_argument(
495
+ "--vN-docx-md5",
496
+ dest="vN_docx_md5",
497
+ type=Path,
498
+ default=None,
499
+ help=(
500
+ "v_N docx path. Asserts the new --docx MD5 != this docx. "
501
+ "Identity = unmodified seed copy = FAIL."
502
+ ),
503
+ )
504
+ parser.add_argument(
505
+ "--vN-md",
506
+ dest="vN_md",
507
+ type=Path,
508
+ default=None,
509
+ help=(
510
+ "v_N manuscript markdown path (companion to --vN-docx-md5). "
511
+ "When supplied, every line added in v_(N+1) markdown must "
512
+ "appear verbatim in the new docx body XML; missing diff "
513
+ "lines fail."
514
+ ),
515
+ )
516
+ args = parser.parse_args()
517
+
518
+ if not args.md.exists():
519
+ print(f"ERROR: markdown not found: {args.md}", file=sys.stderr)
520
+ return 2
521
+
522
+ md_text = args.md.read_text(encoding="utf-8")
523
+ section_ranges = find_caption_section_ranges(md_text)
524
+ citations = extract_citations(md_text, section_ranges)
525
+ body_captions = extract_body_captions(md_text)
526
+
527
+ docx_captions: Optional[dict[str, Caption]] = None
528
+ if args.docx is not None:
529
+ if not args.docx.exists():
530
+ print(f"ERROR: docx not found: {args.docx}", file=sys.stderr)
531
+ return 2
532
+ docx_captions = extract_docx_captions(args.docx)
533
+
534
+ findings = reconcile(citations, body_captions, docx_captions)
535
+
536
+ # Submission safety: any cited label whose status is not OK or UNCITED is a blocker.
537
+ # With --allow-separate-attachments, MISSING_DOCX is downgraded to a warning;
538
+ # MISSING_BODY and MISMATCH remain FAIL regardless because they indicate SSOT
539
+ # drift rather than journal-policy attachment style.
540
+ if args.allow_separate_attachments:
541
+ blocking_statuses = {"MISSING_BODY", "MISMATCH"}
542
+ else:
543
+ blocking_statuses = {"MISSING_DOCX", "MISSING_BODY", "MISMATCH"}
544
+ blockers = [f for f in findings if f.status in blocking_statuses]
545
+ warnings = [
546
+ f for f in findings
547
+ if args.allow_separate_attachments and f.status == "MISSING_DOCX"
548
+ ]
549
+ submission_safe = len(blockers) == 0
550
+
551
+ vN_check: dict | None = None
552
+ vN_check_failed = False
553
+ if args.vN_docx_md5 is not None:
554
+ if args.docx is None:
555
+ print(
556
+ "ERROR: --vN-docx-md5 requires --docx (the new manuscript docx).",
557
+ file=sys.stderr,
558
+ )
559
+ return 2
560
+ vN_check = run_vN_docx_check(
561
+ new_docx=args.docx,
562
+ vN_docx=args.vN_docx_md5,
563
+ new_md=args.md,
564
+ vN_md=args.vN_md,
565
+ )
566
+ if vN_check.get("error"):
567
+ print(f"ERROR: vN docx check: {vN_check['error']}", file=sys.stderr)
568
+ return 2
569
+ if vN_check["identical_bytes"]:
570
+ vN_check_failed = True
571
+ if vN_check["diff_line_misses"]:
572
+ vN_check_failed = True
573
+
574
+ payload = {
575
+ "version": "1.2",
576
+ "manuscript": str(args.md),
577
+ "docx": str(args.docx) if args.docx else None,
578
+ "policy": {
579
+ "allow_separate_attachments": bool(args.allow_separate_attachments),
580
+ },
581
+ "summary": {
582
+ "total_in_text_citations": len(citations),
583
+ "unique_labels": len(findings),
584
+ "ok": sum(1 for f in findings if f.status == "OK"),
585
+ "missing_docx": sum(1 for f in findings if f.status == "MISSING_DOCX"),
586
+ "missing_body": sum(1 for f in findings if f.status == "MISSING_BODY"),
587
+ "mismatch": sum(1 for f in findings if f.status == "MISMATCH"),
588
+ "uncited": sum(1 for f in findings if f.status == "UNCITED"),
589
+ "blockers": len(blockers),
590
+ "warnings": len(warnings),
591
+ },
592
+ "submission_safe": submission_safe and not vN_check_failed,
593
+ "findings": [asdict(f) for f in findings],
594
+ "vN_docx_check": vN_check,
595
+ }
596
+
597
+ args.out.parent.mkdir(parents=True, exist_ok=True)
598
+ args.out.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
599
+
600
+ if not args.quiet:
601
+ print(render_summary(findings, len(citations)))
602
+ print(f"\n[check_xref] wrote {args.out}")
603
+ if warnings:
604
+ print(
605
+ f"[check_xref] WARN: {len(warnings)} MISSING_DOCX row(s) downgraded "
606
+ f"under --allow-separate-attachments."
607
+ )
608
+ if not submission_safe:
609
+ print(f"[check_xref] SUBMISSION BLOCKED: {len(blockers)} cross-reference defect(s).")
610
+ if vN_check is not None:
611
+ if vN_check["identical_bytes"]:
612
+ print(
613
+ "[check_xref] FAIL: v_(N+1) docx is byte-identical to "
614
+ "v_N docx — unmodified seed copy, regenerate via "
615
+ "pandoc / Zotero CWYW."
616
+ )
617
+ elif vN_check["diff_line_misses"]:
618
+ print(
619
+ f"[check_xref] FAIL: {len(vN_check['diff_line_misses'])} "
620
+ "markdown diff line(s) absent from new docx body. "
621
+ "v_(N+1) docx body did not pick up the markdown edits."
622
+ )
623
+ for miss in vN_check["diff_line_misses"][:5]:
624
+ print(f" - {miss}")
625
+
626
+ block_exit = args.strict and not submission_safe
627
+ if block_exit or vN_check_failed:
628
+ return 1
629
+ return 0
630
+
631
+
632
+ if __name__ == "__main__":
633
+ sys.exit(main())