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,782 @@
1
+ #!/usr/bin/env python3
2
+ """Reference verification helper for medsci-skills.
3
+
4
+ The script is deliberately stdlib-only. It extracts reference-like entries from
5
+ Markdown, DOCX, BibTeX, plain text, or TSV, verifies DOI/PMID when possible, and
6
+ writes a single audit artifact: qc/reference_audit.json. Per v1.1.1 artifact
7
+ contract, this skill is sole writer of that file and MUST NOT touch references/.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import csv
14
+ import html
15
+ import json
16
+ import re
17
+ import sys
18
+ import time
19
+ import urllib.parse
20
+ import urllib.request
21
+ import zipfile
22
+ from dataclasses import dataclass, asdict, field
23
+ from pathlib import Path
24
+ from xml.etree import ElementTree as ET
25
+
26
+
27
+ DOI_RE = re.compile(r"\b10\.\d{4,9}/[-._;()/:A-Z0-9]+\b", re.I)
28
+ PMID_RE = re.compile(r"\bPMID\s*:?\s*(\d{5,9})\b", re.I)
29
+ YEAR_RE = re.compile(r"\b(19|20)\d{2}\b")
30
+
31
+
32
+ @dataclass
33
+ class RefRecord:
34
+ ref_id: str
35
+ raw: str
36
+ title_guess: str = ""
37
+ doi: str = ""
38
+ pmid: str = ""
39
+ year_guess: str = ""
40
+ first_author_guess: str = "" # back-compat (= cited_authors[0] when available)
41
+ # v1.3.0: full author cross-check (AI-assisted-drafting hallucination motivation)
42
+ cited_authors: list = field(default_factory=list) # family names parsed from bib/tsv/text
43
+ actual_authors: list = field(default_factory=list) # family names from authoritative source
44
+ cited_author_count: int = 0
45
+ actual_author_count: int = 0
46
+ # v1.3.0: intentional truncate marker. Set via BibTeX field `_audit_truncated = N`
47
+ # (any non-empty value); when present, count mismatch is downgraded to a note
48
+ # and does not trigger MISMATCH status. Use when CSL renders first-1 or first-5
49
+ # + et al. and the trailing authors are deliberately omitted from the bib.
50
+ audit_truncated: bool = False
51
+ status: str = "UNVERIFIED"
52
+ evidence: str = ""
53
+ note: str = ""
54
+
55
+
56
+ def normalize_space(text: str) -> str:
57
+ return re.sub(r"\s+", " ", text).strip()
58
+
59
+
60
+ def clean_doi(doi: str) -> str:
61
+ return doi.rstrip(".,;)].").lower()
62
+
63
+
64
+ def normalize_doi_for_dup(doi: str) -> str:
65
+ """Strict DOI normalization for duplicate detection.
66
+
67
+ Beyond clean_doi(): strips common URL prefixes and trailing slashes so that
68
+ `https://doi.org/10.1234/abc/` and `10.1234/abc` collapse to the same key.
69
+ """
70
+ if not doi:
71
+ return ""
72
+ s = doi.strip().lower()
73
+ for prefix in ("https://doi.org/", "http://doi.org/",
74
+ "https://dx.doi.org/", "http://dx.doi.org/", "doi:"):
75
+ if s.startswith(prefix):
76
+ s = s[len(prefix):]
77
+ break
78
+ s = s.strip().rstrip("/")
79
+ return clean_doi(s)
80
+
81
+
82
+ def read_docx(path: Path) -> str:
83
+ with zipfile.ZipFile(path) as zf:
84
+ xml = zf.read("word/document.xml")
85
+ root = ET.fromstring(xml)
86
+ ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
87
+ paragraphs = []
88
+ for p in root.findall(".//w:p", ns):
89
+ parts = [t.text or "" for t in p.findall(".//w:t", ns)]
90
+ if parts:
91
+ paragraphs.append("".join(parts))
92
+ return "\n".join(paragraphs)
93
+
94
+
95
+ def read_input(path: Path) -> str:
96
+ if path.suffix.lower() == ".docx":
97
+ return read_docx(path)
98
+ return path.read_text(encoding="utf-8", errors="replace")
99
+
100
+
101
+ def parse_bib(text: str) -> list[RefRecord]:
102
+ records: list[RefRecord] = []
103
+ entries = re.split(r"\n(?=@\w+\{)", "\n" + text)
104
+ for entry in entries:
105
+ entry = entry.strip()
106
+ if not entry.startswith("@"):
107
+ continue
108
+ key_match = re.match(r"@\w+\{([^,]+),", entry)
109
+ title_match = re.search(r"title\s*=\s*[\{\"](.+?)[\}\"]\s*,", entry, re.I | re.S)
110
+ doi_match = re.search(r"doi\s*=\s*[\{\"](.+?)[\}\"]\s*,", entry, re.I | re.S)
111
+ pmid_match = re.search(r"pmid\s*=\s*[\{\"]?(\d{5,9})", entry, re.I)
112
+ year_match = re.search(r"year\s*=\s*[\{\"]?((?:19|20)\d{2})", entry, re.I)
113
+ raw = normalize_space(entry)
114
+ # Balanced-brace aware author capture (handles "\~{n}" LaTeX escapes that
115
+ # would otherwise terminate a non-greedy {.+?} match prematurely).
116
+ author_field = ""
117
+ am = re.search(r"author\s*=\s*\{", entry, re.I)
118
+ if am:
119
+ start = am.end()
120
+ depth = 1
121
+ j = start
122
+ while j < len(entry) and depth > 0:
123
+ if entry[j] == "{":
124
+ depth += 1
125
+ elif entry[j] == "}":
126
+ depth -= 1
127
+ j += 1
128
+ author_field = entry[start : j - 1]
129
+ cited = parse_bib_authors(author_field)
130
+ # v1.3.0: intentional truncate marker (any non-empty `_audit_truncated`)
131
+ trunc_match = re.search(r"_audit_truncated\s*=\s*[\{\"]?([^,}\"]+)", entry, re.I)
132
+ records.append(
133
+ RefRecord(
134
+ ref_id=key_match.group(1) if key_match else f"ref_{len(records)+1}",
135
+ raw=raw,
136
+ title_guess=normalize_space(title_match.group(1)) if title_match else "",
137
+ doi=clean_doi(doi_match.group(1)) if doi_match else "",
138
+ pmid=pmid_match.group(1) if pmid_match else "",
139
+ year_guess=year_match.group(1) if year_match else "",
140
+ first_author_guess=cited[0] if cited else (parse_first_author(author_field) if author_field else ""),
141
+ cited_authors=cited,
142
+ cited_author_count=len(cited),
143
+ audit_truncated=bool(trunc_match and trunc_match.group(1).strip().lower() not in ("", "false", "0", "no")),
144
+ )
145
+ )
146
+ return records
147
+
148
+
149
+ def parse_tsv(text: str) -> list[RefRecord]:
150
+ rows = list(csv.DictReader(text.splitlines(), delimiter="\t"))
151
+ records: list[RefRecord] = []
152
+ for i, row in enumerate(rows, 1):
153
+ joined = " ".join(str(v) for v in row.values() if v)
154
+ doi = ""
155
+ pmid = ""
156
+ for key, value in row.items():
157
+ lk = (key or "").lower()
158
+ if lk == "doi" and value:
159
+ doi = clean_doi(value)
160
+ if lk == "pmid" and value:
161
+ pmid = re.sub(r"\D", "", value)
162
+ title = row.get("title") or row.get("Title") or ""
163
+ author_field = row.get("author") or row.get("authors") or row.get("Author") or row.get("Authors") or ""
164
+ records.append(
165
+ RefRecord(
166
+ ref_id=f"ref_{i}",
167
+ raw=normalize_space(joined),
168
+ title_guess=title,
169
+ doi=doi,
170
+ pmid=pmid,
171
+ first_author_guess=parse_first_author(author_field) if author_field else "",
172
+ )
173
+ )
174
+ return records
175
+
176
+
177
+ def reference_section(text: str) -> str:
178
+ match = re.search(r"(?im)^\s*(references|bibliography|reference list)\s*$", text)
179
+ if match:
180
+ return text[match.end() :]
181
+ return text
182
+
183
+
184
+ def parse_reference_lines(text: str) -> list[RefRecord]:
185
+ section = reference_section(text)
186
+ lines = [normalize_space(line) for line in section.splitlines()]
187
+ candidates: list[str] = []
188
+ current = ""
189
+ for line in lines:
190
+ if not line:
191
+ continue
192
+ starts_ref = bool(re.match(r"^(\[\d+\]|\d+[\.\)]|\-\s+)", line))
193
+ if starts_ref and current:
194
+ candidates.append(current)
195
+ current = line
196
+ else:
197
+ current = f"{current} {line}".strip() if current else line
198
+ if current:
199
+ candidates.append(current)
200
+
201
+ if len(candidates) < 2:
202
+ candidates = [line for line in lines if DOI_RE.search(line) or PMID_RE.search(line) or len(line) > 60]
203
+
204
+ records: list[RefRecord] = []
205
+ for i, raw in enumerate(candidates, 1):
206
+ raw = normalize_space(raw)
207
+ doi_match = DOI_RE.search(raw)
208
+ pmid_match = PMID_RE.search(raw)
209
+ year_match = YEAR_RE.search(raw)
210
+ records.append(
211
+ RefRecord(
212
+ ref_id=f"ref_{i}",
213
+ raw=raw,
214
+ title_guess=guess_title(raw),
215
+ doi=clean_doi(doi_match.group(0)) if doi_match else "",
216
+ pmid=pmid_match.group(1) if pmid_match else "",
217
+ year_guess=year_match.group(0) if year_match else "",
218
+ first_author_guess=parse_first_author(raw),
219
+ )
220
+ )
221
+ return records
222
+
223
+
224
+ _NAME_PARTICLES = {"von", "van", "de", "del", "della", "dos", "da", "le", "la", "du", "den", "der", "ten"}
225
+
226
+
227
+ def parse_bib_authors(author_field: str) -> list:
228
+ """Parse BibTeX author field into a list of family-name strings.
229
+
230
+ Handles "Last, First and Last, First" and "First Last and First Last" forms.
231
+ Strips simple LaTeX accents and braces.
232
+ """
233
+ if not author_field:
234
+ return []
235
+ raw = re.sub(r"\s+", " ", author_field).strip()
236
+ parts = re.split(r"\s+and\s+", raw)
237
+ families: list[str] = []
238
+ for name in parts:
239
+ n = name.strip()
240
+ if not n:
241
+ continue
242
+ if "," in n:
243
+ family = n.split(",", 1)[0].strip()
244
+ else:
245
+ toks = n.split()
246
+ family = toks[-1] if toks else ""
247
+ # Strip simple LaTeX accents: \~{n}, \"{o}, \`{a} → underlying char
248
+ family = re.sub(r"\\[\"'`~^=.]?\{?([A-Za-zà-ÿ])\}?", r"\1", family)
249
+ family = re.sub(r"[{}]", "", family).strip()
250
+ if family and family != "others":
251
+ families.append(family)
252
+ return families
253
+
254
+
255
+ def parse_first_author(raw: str) -> str:
256
+ """Extract first-author surname from a Vancouver/AMA/BibTeX-style citation.
257
+
258
+ Conservative: returns "" when the format is ambiguous so author-mismatch
259
+ checks degrade gracefully rather than firing false MISMATCH alerts.
260
+ """
261
+ text = re.sub(r"^\s*(\[\d+\]|\d+[\.\)])\s*", "", raw).strip()
262
+ bib_m = re.search(r"author\s*=\s*[{\"]([^}\"]+)", text, re.I)
263
+ if bib_m:
264
+ text = bib_m.group(1)
265
+ text = re.split(r"\s+and\s+", text, maxsplit=1)[0]
266
+ parts = [p.strip() for p in text.split(",") if p.strip()]
267
+ if not parts:
268
+ return ""
269
+ # "Lastname, Firstname H." style (BibTeX expanded)
270
+ if len(parts) >= 2 and re.match(r"^[A-Z][a-zA-Z .\-']*$", parts[1]) and not re.search(r"\d", parts[1]):
271
+ if re.match(r"^[A-Z][a-zA-Zà-ÿ'\- ]+$", parts[0]):
272
+ return parts[0].strip()
273
+ first = parts[0]
274
+ # "Surname Initials" — strip trailing initials block (e.g., "DH", "J", "F.D.")
275
+ m = re.match(
276
+ r"^((?:(?:" + "|".join(_NAME_PARTICLES) + r")\s+)?[A-Zà-ÿ][\wà-ÿ'\-]*(?:\s+[A-Zà-ÿ][\wà-ÿ'\-]*)?)\s+(?:[A-Z]\.?\s*){1,4}$",
277
+ first,
278
+ )
279
+ if m:
280
+ return m.group(1).strip()
281
+ tokens = first.split()
282
+ if tokens and tokens[0].lower() in _NAME_PARTICLES and len(tokens) >= 2:
283
+ return f"{tokens[0]} {tokens[1]}"
284
+ return tokens[0] if tokens else ""
285
+
286
+
287
+ def _normalize_surname(name: str) -> str:
288
+ """Strip diacritics + lowercase for surname comparison.
289
+
290
+ Coverage (v1.3.0): Latin-with-accents (NFKD decomposes), Turkish
291
+ (ş→s, ğ→g, ı→i), Polish/Czech (ł, đ — not NFKD-decomposable, handled below),
292
+ German ß→ss, Nordic ø/æ/œ → o/ae/oe. Motivation: a Turkish surname
293
+ `Çolakoğlu` vs PubMed `Colakoglu` false-positive MISMATCH.
294
+ """
295
+ import unicodedata
296
+ n = unicodedata.normalize("NFKD", name)
297
+ n = "".join(c for c in n if not unicodedata.combining(c))
298
+ n = n.lower().strip()
299
+ # Multi-char + non-NFKD-decomposable mappings
300
+ multi = {
301
+ "ß": "ss", "þ": "th", "ł": "l", "đ": "d", "ı": "i",
302
+ "ø": "o", "æ": "ae", "œ": "oe",
303
+ }
304
+ for k, v in multi.items():
305
+ n = n.replace(k, v)
306
+ n = re.sub(r"[^a-z\s\-]", "", n)
307
+ n = re.sub(r"\s+", " ", n).strip()
308
+ return n
309
+
310
+
311
+ def author_surnames_match(cited: str, actual: str) -> bool:
312
+ """Tolerant comparison: handles particle variants and hyphenation."""
313
+ if not cited or not actual:
314
+ return True # cannot judge → do not flag
315
+ a = _normalize_surname(cited)
316
+ b = _normalize_surname(actual)
317
+ if not a or not b:
318
+ return True
319
+ if a == b:
320
+ return True
321
+ # Particle-stripped variants ("von elm" vs "elm")
322
+ a_core = re.sub(r"^(?:" + "|".join(_NAME_PARTICLES) + r")\s+", "", a)
323
+ b_core = re.sub(r"^(?:" + "|".join(_NAME_PARTICLES) + r")\s+", "", b)
324
+ if a_core and b_core and (a_core == b_core or a_core in b_core or b_core in a_core):
325
+ return True
326
+ # Hyphen vs space ("Abd-alrazaq" vs "abd alrazaq")
327
+ if a.replace("-", " ") == b.replace("-", " "):
328
+ return True
329
+ return False
330
+
331
+
332
+ def guess_title(raw: str) -> str:
333
+ no_prefix = re.sub(r"^(\[\d+\]|\d+[\.\)]|\-\s+)\s*", "", raw)
334
+ parts = [p.strip() for p in re.split(r"\.\s+", no_prefix) if p.strip()]
335
+ for part in parts:
336
+ words = part.split()
337
+ if 4 <= len(words) <= 30 and not re.search(r"\b(doi|pmid|journal|vol)\b", part, re.I):
338
+ return part.strip('"')
339
+ return ""
340
+
341
+
342
+ def http_json(url: str, timeout: int) -> dict | None:
343
+ req = urllib.request.Request(url, headers={"User-Agent": "medsci-skills/verify-refs (mailto:example@example.com)"})
344
+ try:
345
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
346
+ return json.loads(resp.read().decode("utf-8", "replace"))
347
+ except Exception:
348
+ return None
349
+
350
+
351
+ def verify_crossref(doi: str, timeout: int) -> tuple[str, str, list]:
352
+ """Returns (status, evidence, family_names).
353
+
354
+ v1.3.0: returns full author family list instead of first-author only.
355
+ CrossRef API is not authoritative for given names (documented case: CrossRef
356
+ returned "Vasileios", PubMed efetch & the curated record = "Victoria").
357
+ Use verify_pubmed_efetch as the truth source when PMID is available.
358
+ """
359
+ url = "https://api.crossref.org/works/" + urllib.parse.quote(doi)
360
+ data = http_json(url, timeout)
361
+ if not data or data.get("status") != "ok":
362
+ return "UNVERIFIED", "CrossRef DOI lookup failed", []
363
+ msg = data.get("message", {})
364
+ title = " ".join(msg.get("title") or [])
365
+ year_parts = (((msg.get("issued") or {}).get("date-parts") or [[None]])[0])
366
+ year = str(year_parts[0]) if year_parts and year_parts[0] else ""
367
+ authors_raw = msg.get("author") or []
368
+ families: list[str] = []
369
+ for a in authors_raw:
370
+ fam = (a.get("family") or a.get("name") or "").strip()
371
+ if fam:
372
+ families.append(fam)
373
+ evidence = "CrossRef DOI OK"
374
+ if title:
375
+ evidence += f"; title={title[:120]}"
376
+ if year:
377
+ evidence += f"; year={year}"
378
+ if families:
379
+ evidence += f"; authors={len(families)} (first={families[0]})"
380
+ return "OK", evidence, families
381
+
382
+
383
+ def verify_pubmed_pmid(pmid: str, timeout: int) -> tuple[str, str, list]:
384
+ """Returns (status, evidence, family_names).
385
+
386
+ Uses esummary (fast). Returns family-name approximation by stripping trailing
387
+ initial block from "Surname Initials" form. Authoritative names → call
388
+ verify_pubmed_efetch().
389
+ """
390
+ url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?" + urllib.parse.urlencode(
391
+ {"db": "pubmed", "id": pmid, "retmode": "json"}
392
+ )
393
+ data = http_json(url, timeout)
394
+ if not data:
395
+ return "UNVERIFIED", "PubMed PMID lookup failed", []
396
+ result = data.get("result", {})
397
+ item = result.get(pmid)
398
+ if not item:
399
+ return "FABRICATED", "PMID not found in PubMed", []
400
+ if item.get("error"):
401
+ return "FABRICATED", f"PubMed PMID error: {item['error']}", []
402
+ title = html.unescape(item.get("title", ""))
403
+ authors_raw = item.get("authors") or []
404
+ families: list[str] = []
405
+ for a in authors_raw:
406
+ if a.get("authtype") not in (None, "Author"):
407
+ continue
408
+ full = (a.get("name") or "").strip()
409
+ # esummary "name" is "Surname Initials" e.g. "Reichheld FF"
410
+ m = re.match(r"^(.+?)\s+[A-Z]{1,4}$", full)
411
+ fam = m.group(1).strip() if m else full
412
+ if fam:
413
+ families.append(fam)
414
+ evidence = f"PubMed PMID OK; title={title[:120]}; authors={len(families)}"
415
+ if families:
416
+ evidence += f" (first={families[0]})"
417
+ return "OK", evidence, families
418
+
419
+
420
+ def verify_pubmed_efetch(pmid: str, timeout: int) -> tuple[str, str, list, list]:
421
+ """Authoritative PubMed full author record via efetch.fcgi (XML).
422
+
423
+ Returns (status, evidence, family_names, given_names). Use given_names for
424
+ given-name cross-check (CrossRef-vs-PubMed disagreement, e.g. a documented
425
+ case: CrossRef "Vasileios" vs PubMed "Victoria" — PubMed is authoritative).
426
+ """
427
+ url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?" + urllib.parse.urlencode(
428
+ {"db": "pubmed", "id": pmid, "retmode": "xml"}
429
+ )
430
+ req = urllib.request.Request(
431
+ url,
432
+ headers={"User-Agent": "medsci-skills/verify-refs (mailto:example@example.com)"},
433
+ )
434
+ try:
435
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
436
+ xml_text = resp.read().decode("utf-8", "replace")
437
+ except Exception:
438
+ return "UNVERIFIED", "PubMed efetch failed", [], []
439
+ families: list[str] = []
440
+ givens: list[str] = []
441
+ # Per-Author block: <Author ValidYN="Y"><LastName>X</LastName><ForeName>Y</ForeName>...
442
+ for am in re.finditer(
443
+ r'<Author\s+ValidYN="Y"[^>]*>(.*?)</Author>', xml_text, re.S
444
+ ):
445
+ block = am.group(1)
446
+ lm = re.search(r"<LastName>([^<]+)</LastName>", block)
447
+ fm = re.search(r"<ForeName>([^<]+)</ForeName>", block)
448
+ if lm:
449
+ families.append(html.unescape(lm.group(1)).strip())
450
+ givens.append(html.unescape(fm.group(1)).strip() if fm else "")
451
+ if not families:
452
+ return "UNVERIFIED", "PubMed efetch returned no author elements", [], []
453
+ return (
454
+ "OK",
455
+ f"PubMed efetch OK; authors={len(families)} (first={families[0]})",
456
+ families,
457
+ givens,
458
+ )
459
+
460
+
461
+ def verify_pubmed_title(title: str, timeout: int) -> tuple[str, str, list]:
462
+ """Title-only search returns no confident author list."""
463
+ if not title:
464
+ return "UNVERIFIED", "No DOI, PMID, or usable title", []
465
+ url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" + urllib.parse.urlencode(
466
+ {"db": "pubmed", "term": title, "retmode": "json", "retmax": "3"}
467
+ )
468
+ data = http_json(url, timeout)
469
+ if not data:
470
+ return "UNVERIFIED", "PubMed title search failed", []
471
+ ids = data.get("esearchresult", {}).get("idlist", [])
472
+ if not ids:
473
+ return "UNVERIFIED", "No PubMed title match", []
474
+ return "OK", f"PubMed title match; PMID candidates={','.join(ids)}", []
475
+
476
+
477
+ def verify_record(record: RefRecord, offline: bool, timeout: int) -> RefRecord:
478
+ """v1.3.0: full-author cross-check.
479
+
480
+ Authoritative source priority for the actual author list:
481
+ 1. PubMed efetch (XML full-record) — best (motivation: CrossRef returned a
482
+ wrong given name "Vasileios" vs PubMed efetch authoritative "Victoria";
483
+ also catches AI-generated bib entries with hallucinated #2..#N family
484
+ names — a real AI-assembled bib registered 7 of 10 fabricated co-author names).
485
+ 2. CrossRef DOI (fallback when no PMID).
486
+ 3. PubMed esummary (fast count check; family-name approximation only).
487
+ All cited authors (BibTeX) are compared family-by-family against the
488
+ authoritative list AND total counts are compared. Any cited author beyond
489
+ the actual list, any per-index family mismatch, and any count mismatch are
490
+ each reported. When no full cited list was parsed (TSV / plain text), the
491
+ check degrades to the first-author surname comparison (Gate 4 behaviour).
492
+ """
493
+ if offline:
494
+ if record.doi or record.pmid:
495
+ record.status = "UNVERIFIED"
496
+ record.evidence = "Identifier extracted; offline mode"
497
+ else:
498
+ record.status = "UNVERIFIED"
499
+ record.evidence = "No identifier; offline mode"
500
+ return record
501
+
502
+ statuses: list[str] = []
503
+ evidence_parts: list[str] = []
504
+ actual_authors: list[str] = []
505
+ actual_givens: list[str] = []
506
+ sources_consulted: list[str] = []
507
+
508
+ # Step 1 — PubMed efetch (authoritative) when PMID present.
509
+ if record.pmid:
510
+ st, ev, fams, givens = verify_pubmed_efetch(record.pmid, timeout)
511
+ time.sleep(0.2)
512
+ statuses.append(st)
513
+ evidence_parts.append(ev)
514
+ if st == "OK" and fams:
515
+ actual_authors = fams
516
+ actual_givens = givens
517
+ sources_consulted.append("pubmed_efetch")
518
+ # also run esummary for FABRICATED detection (efetch returns valid XML even for
519
+ # unknown PMIDs in some edge cases; esummary's "error" field is decisive).
520
+ st_es, ev_es, fams_es = verify_pubmed_pmid(record.pmid, timeout)
521
+ time.sleep(0.2)
522
+ statuses.append(st_es)
523
+ evidence_parts.append(ev_es)
524
+ if not actual_authors and st_es == "OK" and fams_es:
525
+ actual_authors = fams_es
526
+ sources_consulted.append("pubmed_esummary")
527
+
528
+ # Step 2 — CrossRef DOI (used only when efetch did not provide a list).
529
+ if record.doi:
530
+ st_cr, ev_cr, fams_cr = verify_crossref(record.doi, timeout)
531
+ time.sleep(0.2)
532
+ statuses.append(st_cr)
533
+ evidence_parts.append(ev_cr)
534
+ if not actual_authors and st_cr == "OK" and fams_cr:
535
+ actual_authors = fams_cr
536
+ sources_consulted.append("crossref")
537
+
538
+ # Step 3 — title-only fallback (no confident author list).
539
+ if not statuses:
540
+ st_t, ev_t, _ = verify_pubmed_title(record.title_guess, timeout)
541
+ time.sleep(0.2)
542
+ statuses.append(st_t)
543
+ evidence_parts.append(ev_t)
544
+
545
+ # Full-author cross-check
546
+ record.actual_authors = actual_authors
547
+ record.actual_author_count = len(actual_authors)
548
+ if record.cited_authors and not record.cited_author_count:
549
+ record.cited_author_count = len(record.cited_authors)
550
+
551
+ mismatches: list[str] = []
552
+ if record.cited_authors and actual_authors:
553
+ compare_n = min(len(record.cited_authors), len(actual_authors))
554
+ for i in range(compare_n):
555
+ cited = record.cited_authors[i]
556
+ if not author_surnames_match(cited, actual_authors[i]):
557
+ mismatches.append(
558
+ f"#{i+1} family: cited='{cited}' vs source='{actual_authors[i]}'"
559
+ )
560
+ # cited has more authors than source — always flag (cannot be intentional)
561
+ for i in range(compare_n, len(record.cited_authors)):
562
+ mismatches.append(
563
+ f"#{i+1} extra cited='{record.cited_authors[i]}' (source has only {len(actual_authors)} authors)"
564
+ )
565
+ # source has more authors than cited — count mismatch, suppressed under
566
+ # `_audit_truncated` marker (intentional CSL et-al truncation).
567
+ if record.cited_author_count != record.actual_author_count:
568
+ if record.audit_truncated and record.cited_author_count < record.actual_author_count:
569
+ evidence_parts.append(
570
+ f"NOTE: intentional truncate ({record.cited_author_count} of {record.actual_author_count}; "
571
+ f"`_audit_truncated` marker set)"
572
+ )
573
+ else:
574
+ mismatches.append(
575
+ f"AUTHOR COUNT: cited={record.cited_author_count} vs source={record.actual_author_count}"
576
+ )
577
+ elif record.first_author_guess and actual_authors:
578
+ # No parsed cited author list (TSV / plain-text input) — degrade to the
579
+ # first-author surname cross-check (Gate 4 behaviour).
580
+ if not any(author_surnames_match(record.first_author_guess, a) for a in actual_authors):
581
+ mismatches.append(
582
+ f"#1 family: cited='{record.first_author_guess}' vs source='{actual_authors[0]}'"
583
+ )
584
+
585
+ author_mismatch = bool(mismatches)
586
+ if author_mismatch:
587
+ evidence_parts.append("AUTHOR MISMATCH | " + " | ".join(mismatches))
588
+
589
+ # Status precedence
590
+ if "OK" in statuses and "FABRICATED" in statuses:
591
+ record.status = "MISMATCH"
592
+ elif "OK" in statuses:
593
+ record.status = "MISMATCH" if author_mismatch else "OK"
594
+ elif "FABRICATED" in statuses:
595
+ record.status = "FABRICATED"
596
+ else:
597
+ record.status = "UNVERIFIED"
598
+
599
+ # Note classification (most informative wins)
600
+ if author_mismatch and not record.note:
601
+ # Distinguish first-author hallucination (high reviewer salience)
602
+ first_cited = record.cited_authors[0] if record.cited_authors else record.first_author_guess
603
+ first_bad = (
604
+ first_cited
605
+ and actual_authors
606
+ and not author_surnames_match(first_cited, actual_authors[0])
607
+ )
608
+ if first_bad:
609
+ record.note = "first-author hallucination suspected (DOI/PMID correct, family differs)"
610
+ else:
611
+ record.note = "non-first-author hallucination or count mismatch (DOI/PMID correct)"
612
+ record.evidence = " | ".join(p for p in evidence_parts if p)
613
+ if sources_consulted:
614
+ record.evidence += f" | source={'+'.join(sources_consulted)}"
615
+ return record
616
+
617
+
618
+ def detect_duplicates(records: list[RefRecord]) -> list[dict]:
619
+ """Detect verbatim PMID or DOI duplicates within the reference list.
620
+
621
+ Verbatim duplicates (same PMID or normalized DOI) are a common LLM
622
+ citation-compilation artifact and require cite renumbering before
623
+ submission.
624
+ """
625
+ seen_pmids: dict[str, str] = {}
626
+ seen_dois: dict[str, str] = {}
627
+ findings: list[dict] = []
628
+ for rec in records:
629
+ rec_id = rec.ref_id or "<unknown>"
630
+ pmid = (rec.pmid or "").strip()
631
+ if pmid:
632
+ if pmid in seen_pmids:
633
+ findings.append({
634
+ "severity": "MAJOR",
635
+ "category": "duplicate_pmid",
636
+ "ref_ids": [seen_pmids[pmid], rec_id],
637
+ "pmid": pmid,
638
+ "note": "Verbatim duplicate reference. Cite renumbering required.",
639
+ })
640
+ else:
641
+ seen_pmids[pmid] = rec_id
642
+ doi = normalize_doi_for_dup(rec.doi or "")
643
+ if doi:
644
+ if doi in seen_dois:
645
+ findings.append({
646
+ "severity": "MAJOR",
647
+ "category": "duplicate_doi",
648
+ "ref_ids": [seen_dois[doi], rec_id],
649
+ "doi": doi,
650
+ "note": "Verbatim duplicate reference. Cite renumbering required.",
651
+ })
652
+ else:
653
+ seen_dois[doi] = rec_id
654
+ return findings
655
+
656
+
657
+ # Pagination / publication-stage placeholders. A reference whose pages or status is
658
+ # still "e000–e000", "in press", "TBD", or "forthcoming" is not yet a fully citable
659
+ # record. verify-refs is manuscript-agnostic, so it only flags these as UNVERIFIED
660
+ # with note="pagination_placeholder"; the centrality call (is this a method- or
661
+ # headline-load-bearing cite, hence a P0 blocker?) is made by /self-review Phase 2.5c,
662
+ # which has the manuscript in hand. (Gate 6, added 2026-06.)
663
+ PAGINATION_PLACEHOLDER_RE = re.compile(
664
+ r"e0{3}.{0,3}e0{3}|in[ .]?press|\bTBD\b|forthcoming", re.I)
665
+
666
+
667
+ def flag_pagination_placeholder(record: RefRecord) -> None:
668
+ """If the raw entry carries a pagination/publication-stage placeholder, attach a
669
+ note and downgrade a would-be VERIFIED record to UNVERIFIED (an in-press/e000
670
+ citation is not yet locatable to the page). Worse statuses are left unchanged."""
671
+ if not PAGINATION_PLACEHOLDER_RE.search(record.raw or ""):
672
+ return
673
+ tag = "pagination_placeholder"
674
+ record.note = f"{record.note} | {tag}".strip(" |") if record.note else tag
675
+ if record.status == "VERIFIED":
676
+ record.status = "UNVERIFIED"
677
+ ev = "identifier resolved but pagination/publication-stage placeholder unresolved"
678
+ record.evidence = f"{record.evidence} | {ev}".strip(" |") if record.evidence else ev
679
+
680
+
681
+ def write_outputs(records: list[RefRecord], project_root: Path, source: Path,
682
+ duplicate_findings: list[dict]) -> None:
683
+ """Audit-only writer (v1.3.0).
684
+
685
+ Per docs/artifact_contract.md, /verify-refs is sole writer of qc/reference_audit.json
686
+ only. It MUST NOT write to references/ (that directory is owned by /search-lit and
687
+ /lit-sync). All per-record details live inside reference_audit.json.
688
+
689
+ v1.2.0 (2026-05): adds duplicate_findings[] for PMID/DOI duplicate detection
690
+ (Gate 5; resolves /peer-review Phase 2A P7). submission_safe and fully_verified
691
+ both require duplicate_findings to be empty.
692
+
693
+ v1.3.0 (2026-05): full-author cross-check. records[] now carry cited_authors[],
694
+ actual_authors[], and author counts; schema_version bumps to 4. MISMATCH now
695
+ fires on any #2..#N family hallucination or author-count mismatch, not just the
696
+ first author (motivation: a bib entry with a real first author but 7/10
697
+ fabricated co-author given names previously passed audit).
698
+ """
699
+ qc_dir = project_root / "qc"
700
+ qc_dir.mkdir(parents=True, exist_ok=True)
701
+
702
+ counts: dict[str, int] = {}
703
+ for rec in records:
704
+ counts[rec.status] = counts.get(rec.status, 0) + 1
705
+ audit = {
706
+ "schema_version": 4,
707
+ "source": str(source),
708
+ "total_references": len(records),
709
+ "counts": counts,
710
+ "duplicate_findings": duplicate_findings,
711
+ "submission_safe": (
712
+ counts.get("FABRICATED", 0) == 0
713
+ and counts.get("MISMATCH", 0) == 0
714
+ and len(duplicate_findings) == 0
715
+ ),
716
+ "fully_verified": (
717
+ counts.get("UNVERIFIED", 0) == 0
718
+ and counts.get("FABRICATED", 0) == 0
719
+ and counts.get("MISMATCH", 0) == 0
720
+ and len(duplicate_findings) == 0
721
+ ),
722
+ "requires_manual_reference_check": counts.get("UNVERIFIED", 0) > 0,
723
+ "records": [asdict(rec) for rec in records],
724
+ }
725
+ (qc_dir / "reference_audit.json").write_text(json.dumps(audit, indent=2, ensure_ascii=False), encoding="utf-8")
726
+
727
+
728
+ def main() -> int:
729
+ parser = argparse.ArgumentParser(description="Verify manuscript references.")
730
+ parser.add_argument("input", help="Input .md, .docx, .bib, .txt, or .tsv file")
731
+ parser.add_argument("--project-root", default=".", help="Project root for output artifacts")
732
+ parser.add_argument("--offline", action="store_true", help="Do not call PubMed/CrossRef APIs")
733
+ parser.add_argument("--timeout", type=int, default=10, help="HTTP timeout seconds")
734
+ parser.add_argument("--strict", action="store_true", help="Exit non-zero on any UNVERIFIED row, and forbid --offline")
735
+ args = parser.parse_args()
736
+
737
+ if args.strict and args.offline:
738
+ print("--strict is incompatible with --offline", file=sys.stderr)
739
+ return 2
740
+
741
+ input_path = Path(args.input).resolve()
742
+ project_root = Path(args.project_root).resolve()
743
+ if not input_path.exists():
744
+ print(f"Input not found: {input_path}", file=sys.stderr)
745
+ return 2
746
+
747
+ text = read_input(input_path)
748
+ suffix = input_path.suffix.lower()
749
+ if suffix == ".bib":
750
+ records = parse_bib(text)
751
+ elif suffix == ".tsv":
752
+ records = parse_tsv(text)
753
+ else:
754
+ records = parse_reference_lines(text)
755
+
756
+ if not records:
757
+ print("No references detected.", file=sys.stderr)
758
+ return 3
759
+
760
+ verified = [verify_record(rec, args.offline, args.timeout) for rec in records]
761
+ for rec in verified:
762
+ flag_pagination_placeholder(rec)
763
+ duplicate_findings = detect_duplicates(verified)
764
+ write_outputs(verified, project_root, input_path, duplicate_findings)
765
+
766
+ counts: dict[str, int] = {}
767
+ for rec in verified:
768
+ counts[rec.status] = counts.get(rec.status, 0) + 1
769
+ print(json.dumps({
770
+ "total": len(verified),
771
+ "counts": counts,
772
+ "duplicate_findings_count": len(duplicate_findings),
773
+ }, indent=2))
774
+ if counts.get("FABRICATED", 0) or counts.get("MISMATCH", 0) or duplicate_findings:
775
+ return 1
776
+ if args.strict and counts.get("UNVERIFIED", 0):
777
+ return 1
778
+ return 0
779
+
780
+
781
+ if __name__ == "__main__":
782
+ sys.exit(main())