medsci-skills 5.20.1 → 5.22.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 (207) hide show
  1. package/LICENSE +55 -16
  2. package/README.md +74 -6
  3. package/installers/install-macos.command +84 -13
  4. package/installers/install-windows.cmd +99 -17
  5. package/installers/install.py +72 -0
  6. package/installers/medsci_txn.py +64 -0
  7. package/installers/update-macos.command +46 -11
  8. package/installers/update-windows.cmd +41 -6
  9. package/installers/update.py +13 -0
  10. package/metadata/distribution_files.json +533 -323
  11. package/metadata/distribution_manifest.json +2 -1
  12. package/metadata/skills_catalog.json +11 -1
  13. package/package.json +1 -1
  14. package/skills/MAINTENANCE.md +45 -1
  15. package/skills/academic-aio/scripts/check_summary_box.py +1 -1
  16. package/skills/add-journal/SKILL.md +31 -116
  17. package/skills/add-journal/references/write_paper_profile_template.md +124 -0
  18. package/skills/analyze-stats/SKILL.md +20 -0
  19. package/skills/analyze-stats/scripts/check_generated_code.py +1 -1
  20. package/skills/analyze-stats/scripts/check_separation.py +262 -0
  21. package/skills/analyze-stats/tests/test_separation.sh +154 -0
  22. package/skills/check-reporting/SKILL.md +34 -112
  23. package/skills/check-reporting/references/checklists/CLEAR.md +176 -107
  24. package/skills/check-reporting/references/checklists/MI_CLEAR_LLM.md +94 -136
  25. package/skills/check-reporting/references/checklists/TRIPOD_AI.md +127 -117
  26. package/skills/check-reporting/references/report_templates.md +140 -0
  27. package/skills/check-reporting/scripts/check_checklist_version.py +1 -1
  28. package/skills/check-reporting/scripts/check_framework_naming.py +1 -1
  29. package/skills/check-reporting/scripts/check_prisma_figure.py +1 -1
  30. package/skills/check-reporting/scripts/verify_checklist_fidelity.py +162 -0
  31. package/skills/check-reporting/tests/test_checklist_fidelity.sh +148 -0
  32. package/skills/clean-data/scripts/check_reverse_coding.py +1 -1
  33. package/skills/clean-data/scripts/check_structural_zero.py +1 -1
  34. package/skills/contribute/SKILL.md +226 -0
  35. package/skills/contribute/scripts/check_contribution_safety.py +241 -0
  36. package/skills/contribute/scripts/contribution_prefs.py +159 -0
  37. package/skills/contribute/scripts/find_local_changes.py +198 -0
  38. package/skills/contribute/scripts/star_repo.py +165 -0
  39. package/skills/contribute/scripts/submit_contribution.py +203 -0
  40. package/skills/contribute/skill.yml +50 -0
  41. package/skills/contribute/tests/test_contribution_safety.sh +264 -0
  42. package/skills/design-study/SKILL.md +16 -88
  43. package/skills/design-study/references/reader_elicitation_design.md +97 -0
  44. package/skills/explainability/scripts/check_explainability_report.py +1 -1
  45. package/skills/find-cohort-gap/SKILL.md +57 -16
  46. package/skills/find-cohort-gap/references/cohort_profile_template.md +18 -4
  47. package/skills/find-cohort-gap/scripts/build_cohort_profile.py +528 -0
  48. package/skills/find-cohort-gap/skill.yml +11 -2
  49. package/skills/find-cohort-gap/tests/test_cohort_profile.sh +206 -0
  50. package/skills/find-journal/SKILL.md +2 -72
  51. package/skills/humanize/SKILL.md +2 -2
  52. package/skills/lit-sync/SKILL.md +4 -10
  53. package/skills/make-figures/SKILL.md +39 -93
  54. package/skills/make-figures/references/exemplar_diagrams/README.md +65 -50
  55. package/skills/make-figures/references/exemplar_diagrams/consort/template_output.png +0 -0
  56. package/skills/make-figures/references/exemplar_diagrams/consort/template_output_600.png +0 -0
  57. package/skills/make-figures/references/exemplar_diagrams/prisma/template_output.png +0 -0
  58. package/skills/make-figures/references/exemplar_diagrams/prisma/template_output_600.png +0 -0
  59. package/skills/make-figures/references/exemplar_diagrams/stard/template_output.png +0 -0
  60. package/skills/make-figures/references/exemplar_diagrams/stard/template_output_600.png +0 -0
  61. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output.png +0 -0
  62. package/skills/make-figures/references/exemplar_diagrams/strobe/template_output_600.png +0 -0
  63. package/skills/make-figures/references/figure_manifest.md +38 -0
  64. package/skills/make-figures/references/flow_diagram_recipe.md +68 -0
  65. package/skills/make-figures/references/pipeline_concepts_medical_ai.md +1 -1
  66. package/skills/make-figures/references/visual_abstract_templates/template_guide.md +20 -4
  67. package/skills/make-figures/scripts/derive_figure_legend_counts.py +1 -1
  68. package/skills/make-figures/scripts/extract_exemplar_from_pdf.py +14 -2
  69. package/skills/manage-project/SKILL.md +20 -88
  70. package/skills/manage-project/references/init_scaffold.md +118 -0
  71. package/skills/manage-refs/SKILL.md +2 -1
  72. package/skills/manage-refs/scripts/check_bib_title_markup.py +167 -0
  73. package/skills/manage-refs/scripts/check_csl_render.py +1 -1
  74. package/skills/manage-refs/scripts/check_reference_duplication.py +1 -1
  75. package/skills/manage-refs/scripts/check_xref.py +1 -1
  76. package/skills/manage-refs/scripts/pre_submission_gate.sh +29 -8
  77. package/skills/manage-refs/skill.yml +1 -0
  78. package/skills/manage-refs/tests/test_bib_title_markup.sh +125 -0
  79. package/skills/meta-analysis/SKILL.md +115 -273
  80. package/skills/meta-analysis/references/phase3_screening_detail.md +148 -0
  81. package/skills/meta-analysis/references/phase4_extraction_detail.md +189 -0
  82. package/skills/meta-analysis/scripts/check_pool_consistency.py +1 -1
  83. package/skills/mllm-eval/scripts/check_mllm_eval_completeness.py +1 -1
  84. package/skills/model-card/scripts/check_model_card_complete.py +1 -1
  85. package/skills/model-evaluation/scripts/check_metric_reporting.py +1 -1
  86. package/skills/model-scaffold/scripts/check_training_hygiene.py +1 -1
  87. package/skills/model-validation/scripts/check_split_leakage.py +1 -1
  88. package/skills/orchestrate/SKILL.md +1 -0
  89. package/skills/peer-review/SKILL.md +45 -67
  90. package/skills/peer-review/references/domain-probes/self_improving_system.md +113 -0
  91. package/skills/peer-review/references/review_draft_template.md +71 -0
  92. package/skills/peer-review/scripts/check_pdf_injection.py +1 -1
  93. package/skills/peer-review/scripts/check_review_request_types.py +293 -0
  94. package/skills/peer-review/scripts/check_review_request_types_challenge/expected/disciplined.txt +3 -0
  95. package/skills/peer-review/scripts/check_review_request_types_challenge/expected/undisciplined.txt +8 -0
  96. package/skills/peer-review/scripts/check_review_request_types_challenge/fixture/disciplined.md +19 -0
  97. package/skills/peer-review/scripts/check_review_request_types_challenge/fixture/undisciplined.md +16 -0
  98. package/skills/peer-review/scripts/check_review_request_types_challenge/problem.md +87 -0
  99. package/skills/peer-review/scripts/check_review_request_types_challenge/verify.sh +24 -0
  100. package/skills/peer-review/scripts/check_self_improvement_claims.py +250 -0
  101. package/skills/peer-review/tests/test_self_improvement_claims.sh +142 -0
  102. package/skills/polish-language/SKILL.md +1 -1
  103. package/skills/preprocess-imaging/scripts/check_preprocessing_leakage.py +1 -1
  104. package/skills/present-paper/SKILL.md +115 -25
  105. package/skills/present-paper/references/ai_slide_tells.md +167 -0
  106. package/skills/present-paper/references/critic_rubrics/slide.md +30 -0
  107. package/skills/present-paper/references/presentation_archetypes.md +257 -0
  108. package/skills/present-paper/references/slide_visual_styles/nature_lancet.md +23 -5
  109. package/skills/present-paper/scripts/check_deck_budget.py +263 -0
  110. package/skills/present-paper/scripts/check_deck_budget_challenge/make_fixtures.py +96 -0
  111. package/skills/present-paper/scripts/check_deck_budget_challenge/verify.sh +86 -0
  112. package/skills/present-paper/scripts/check_slide_tells.py +540 -0
  113. package/skills/present-paper/scripts/check_slide_tells_challenge/make_fixtures.py +143 -0
  114. package/skills/present-paper/scripts/check_slide_tells_challenge/verify.sh +86 -0
  115. package/skills/present-paper/scripts/inject_speaker_notes.py +15 -3
  116. package/skills/present-paper/skill.yml +6 -0
  117. package/skills/present-paper/templates/build_pptx_nature_lancet.py +74 -48
  118. package/skills/present-paper/tests/test_backup_off_the_clock.py +141 -0
  119. package/skills/present-paper/tests/test_builder_no_chrome.py +183 -0
  120. package/skills/present-paper/tests/test_md_parity.py +129 -0
  121. package/skills/radiomics-ml/scripts/check_radiomics_ml.py +1 -1
  122. package/skills/revise/SKILL.md +38 -5
  123. package/skills/revise/scripts/check_density_complaint.py +190 -0
  124. package/skills/revise/scripts/check_response_claims.py +1 -1
  125. package/skills/revise/scripts/density_complaint_challenge/fixture/decision_letter.md +9 -0
  126. package/skills/revise/scripts/density_complaint_challenge/fixture/v20_longer.md +20 -0
  127. package/skills/revise/scripts/density_complaint_challenge/fixture/v21_shorter.md +20 -0
  128. package/skills/revise/scripts/density_complaint_challenge/fixture/v_prev.md +20 -0
  129. package/skills/revise/scripts/density_complaint_challenge/verify.sh +65 -0
  130. package/skills/revise/skill.yml +2 -0
  131. package/skills/self-review/SKILL.md +246 -614
  132. package/skills/self-review/references/domain-probes/self_improving_system.md +113 -0
  133. package/skills/self-review/references/phases/phase2_5a_source_fidelity.md +98 -0
  134. package/skills/self-review/references/phases/phase2_5b_screening_counts.md +127 -0
  135. package/skills/self-review/references/phases/phase2_5d_xref_qc.md +94 -0
  136. package/skills/self-review/references/phases/phase2_5f_claim_artifact.md +183 -0
  137. package/skills/self-review/references/phases/phase2_systematic_check.md +214 -0
  138. package/skills/self-review/scripts/check_analysis_definitions.py +279 -0
  139. package/skills/self-review/scripts/check_analysis_definitions_challenge/expected/defined.txt +4 -0
  140. package/skills/self-review/scripts/check_analysis_definitions_challenge/expected/undefined.txt +6 -0
  141. package/skills/self-review/scripts/check_analysis_definitions_challenge/fixture/defined.md +25 -0
  142. package/skills/self-review/scripts/check_analysis_definitions_challenge/fixture/undefined.md +20 -0
  143. package/skills/self-review/scripts/check_analysis_definitions_challenge/problem.md +57 -0
  144. package/skills/self-review/scripts/check_analysis_definitions_challenge/verify.sh +21 -0
  145. package/skills/self-review/scripts/check_artifact_coverage.py +1 -1
  146. package/skills/self-review/scripts/check_binning_consistency.py +1 -1
  147. package/skills/self-review/scripts/check_citation_order.py +1 -1
  148. package/skills/self-review/scripts/check_claim_artifact.py +1 -1
  149. package/skills/self-review/scripts/check_classical_style.py +1 -1
  150. package/skills/self-review/scripts/check_cohort_arithmetic.py +1 -1
  151. package/skills/self-review/scripts/check_confounding_completeness.py +1 -1
  152. package/skills/self-review/scripts/check_cv_leakage.py +1 -1
  153. package/skills/self-review/scripts/check_dta_denominators.py +1 -1
  154. package/skills/self-review/scripts/check_editorial_impression.py +1 -1
  155. package/skills/self-review/scripts/check_emphasis_density.py +1 -1
  156. package/skills/self-review/scripts/check_figure_citation.py +1 -1
  157. package/skills/self-review/scripts/check_nested_group_comparison.py +1 -1
  158. package/skills/self-review/scripts/check_null_calibration.py +1 -1
  159. package/skills/self-review/scripts/check_paired_difference_estimator.py +1 -1
  160. package/skills/self-review/scripts/check_panel_diversity.py +1 -1
  161. package/skills/self-review/scripts/check_paren_spans.py +1 -1
  162. package/skills/self-review/scripts/check_reference_adequacy.py +1 -1
  163. package/skills/self-review/scripts/check_reported_p_from_counts.py +1 -1
  164. package/skills/self-review/scripts/check_reviewer_team_consistency.py +1 -3
  165. package/skills/self-review/scripts/check_rounded_delta.py +1 -1
  166. package/skills/self-review/scripts/check_scope_coherence.py +1 -1
  167. package/skills/self-review/scripts/check_supplement_hygiene.py +1 -1
  168. package/skills/self-review/scripts/check_table_percentages.py +2 -2
  169. package/skills/sync-submission/SKILL.md +49 -0
  170. package/skills/sync-submission/scripts/build_marked_manuscript.py +162 -0
  171. package/skills/sync-submission/scripts/check_asset_anonymization.py +1 -1
  172. package/skills/sync-submission/scripts/check_checklist_dump_leak.py +1 -1
  173. package/skills/sync-submission/scripts/check_cross_artifact_stale.py +1 -1
  174. package/skills/sync-submission/scripts/check_disclosure_availability.py +1 -1
  175. package/skills/sync-submission/scripts/check_marked_manuscript.py +333 -0
  176. package/skills/sync-submission/scripts/check_wordcount_cap.py +1 -1
  177. package/skills/sync-submission/scripts/detect_copy_divergence.py +1 -1
  178. package/skills/sync-submission/skill.yml +2 -0
  179. package/skills/sync-submission/tests/test_marked_manuscript.sh +208 -0
  180. package/skills/uncertainty-imaging/scripts/check_uncertainty_reporting.py +1 -1
  181. package/skills/verify-refs/scripts/verify_refs.py +38 -8
  182. package/skills/verify-refs/tests/test_author_normalization.sh +88 -0
  183. package/skills/write-paper/SKILL.md +116 -363
  184. package/skills/write-paper/references/phase0_init_detail.md +139 -0
  185. package/skills/write-paper/references/phase7_polish_detail.md +283 -0
  186. package/skills/write-paper/scripts/check_placeholders.py +1 -1
  187. package/skills/make-figures/references/exemplar_diagrams/other/other_02.meta.yaml +0 -4
  188. package/skills/make-figures/references/exemplar_diagrams/other/other_02.png +0 -0
  189. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_01.meta.yaml +0 -4
  190. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_01.png +0 -0
  191. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_03.meta.yaml +0 -4
  192. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_03.png +0 -0
  193. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_04.meta.yaml +0 -4
  194. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_04.png +0 -0
  195. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_05.meta.yaml +0 -4
  196. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_05.png +0 -0
  197. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_06.meta.yaml +0 -4
  198. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_06.png +0 -0
  199. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_07.meta.yaml +0 -4
  200. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_07.png +0 -0
  201. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_08.meta.yaml +0 -4
  202. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_08.png +0 -0
  203. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_09.meta.yaml +0 -4
  204. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_09.png +0 -0
  205. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_10.meta.yaml +0 -4
  206. package/skills/make-figures/references/exemplar_diagrams/pipeline/pipeline_10.png +0 -0
  207. package/skills/make-figures/references/visual_abstract_templates/european_radiology.pptx +0 -0
package/LICENSE CHANGED
@@ -24,22 +24,61 @@ SOFTWARE.
24
24
 
25
25
  ## Third-Party Content Licenses
26
26
 
27
- The following reporting guideline checklists are bundled under their original licenses
28
- (NOT under MIT). See each file for attribution:
29
-
30
- - STROBE checklist: CC BY (https://www.strobe-statement.org)
31
- - STARD 2015 checklist: CC BY 4.0 (https://www.equator-network.org/reporting-guidelines/stard/)
32
- - TRIPOD+AI checklist: CC BY 4.0 (https://www.tripod-statement.org)
33
- - PRISMA 2020 checklist: CC BY (https://www.prisma-statement.org)
34
- - ARRIVE 2.0 checklist: CC0 (https://arriveguidelines.org)
35
-
36
- The following checklists are NOT bundled due to license restrictions.
37
- Users should download them directly from official sources:
38
-
39
- - CONSORT 2010: https://www.consort-statement.org (CC BY-NC)
40
- - CARE: https://www.care-statement.org
41
- - SPIRIT: https://www.spirit-statement.org (CC BY-NC-ND)
42
- - CLAIM 2024: https://pubs.rsna.org/doi/10.1148/radiol.2020200267
27
+ Parts of this package are not ours, and are bundled under their own licenses (NOT under MIT).
28
+ This section is the index of them. It is checked against the tree on every build by
29
+ `scripts/check_third_party_index.py` — a license file that describes a package other than the one
30
+ you downloaded is worse than no license file at all.
31
+
32
+ ### Reporting guideline checklists our summaries (`skills/*/references/checklists/*.md`)
33
+
34
+ These are educational summaries written in our own words, each citing its source. They do not
35
+ relicense the underlying guideline.
36
+
37
+ - STROBE: CC BY (https://www.strobe-statement.org)
38
+ - STARD 2015 / STARD-AI: CC BY 4.0 (https://www.equator-network.org/reporting-guidelines/stard/)
39
+ - TRIPOD / TRIPOD+AI: CC BY 4.0 (https://www.tripod-statement.org)
40
+ - PRISMA 2020 / PRISMA-DTA: CC BY (https://www.prisma-statement.org)
41
+ - ARRIVE 2.0: CC0 (https://arriveguidelines.org)
42
+ - CONSORT 2025, SPIRIT 2025: CC BY 4.0 (https://www.consort-spirit.org)
43
+
44
+ **Non-commercial restrictions.** The following summaries derive from instruments whose materials are
45
+ CC BY-NC, and are therefore free to use and redistribute **for non-commercial purposes with
46
+ attribution**; commercial use requires permission from the rights holders:
47
+
48
+ - CARE, MI-CLEAR-LLM, DECIDE-AI: CC BY-NC 4.0
49
+ - CLAIM 2024: educational summary of an RSNA open-access checklist (© RSNA)
50
+
51
+ See `skills/check-reporting/references/LICENSES.md` for the per-file detail.
52
+
53
+ ### Official guideline templates, verbatim (`skills/make-figures/templates/official/`)
54
+
55
+ These are the guideline authors' own documents, redistributed unmodified:
56
+
57
+ - CONSORT 2025 checklist + flow diagram (`.docx`): CC BY 4.0.
58
+ © 2025 Hopewell S et al. *BMJ* 2025;388:e081123. doi:10.1136/bmj-2024-081123
59
+ - SPIRIT 2025 checklist + participant timeline (`.docx`): CC BY 4.0.
60
+ © 2025 Chan A-W et al. *BMJ* 2025;389:e081477. doi:10.1136/bmj-2024-081477
61
+ - STARD 2015 checklist (`.docx`) and flow diagram (`.pdf`): CC BY 4.0.
62
+ Bossuyt PM et al. *BMJ* 2015;351:h5527
63
+ - PRISMA 2020 flow diagrams (`.pptx`): built by us with
64
+ `skills/make-figures/scripts/build_prisma2020_template.py`, reproducing the published layout
65
+ (Page MJ et al. *BMJ* 2021;372:n71, Fig 1, CC BY 4.0)
66
+
67
+ The CONSORT and SPIRIT **2025** updates are CC BY 4.0 — "unrestricted use, distribution, and
68
+ reproduction in any medium, provided the original work is properly cited". Their 2010 / 2013
69
+ predecessors were CC BY-NC / CC BY-NC-ND and could not have been bundled; this file said so for a
70
+ long time after the 2025 versions had replaced them.
71
+
72
+ ### Citation styles (`skills/manage-refs/citation_styles/*.csl`)
73
+
74
+ 15 CSL files from the Citation Style Language project: **CC BY-SA 3.0**, unmodified, each retaining
75
+ its own `<rights>` element. See `skills/manage-refs/NOTICE.md`.
76
+
77
+ ### Not bundled — download these yourself
78
+
79
+ - **European Radiology graphical-abstract template** (`EURA-GA-Jan2025.pptx`):
80
+ https://www.springer.com/journal/330 — the journal's own file, with no license permitting
81
+ redistribution. `/make-figures` accepts it via `--template /absolute/path.pptx`.
43
82
 
44
83
  ## Optional Dependencies
45
84
 
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  # MedSci Skills
4
4
 
5
- **55 skills that actually work.** Built by a physician-researcher, tested on real publications.
5
+ **56 skills that actually work.** Built by a physician-researcher, tested on real publications.
6
6
 
7
7
  *MedSci Skills is an end-to-end research tool for physician and medical-engineering researchers — design → scaffold → validate → publish — for the clinical manuscript and the medical-AI model behind it. Its moat is the compliance layer — 46 reporting guidelines and risk-of-bias tools, reference/citation verification, and deterministic integrity gates before peer review — now extended by a model-engineering lane that scaffolds reproducible, leakage-safe training repos and audits model validation. Clinical AI model research engineering is in scope; a general AI-scientist platform is not. It competes on clinical submission reliability, not skill count.*
8
8
 
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
10
  [![Release](https://img.shields.io/github/v/release/Aperivue/medsci-skills?style=flat-square&color=blue)](https://github.com/Aperivue/medsci-skills/releases/latest)
11
11
  [![CI](https://img.shields.io/github/actions/workflow/status/Aperivue/medsci-skills/validate.yml?branch=main&style=flat-square&label=CI)](https://github.com/Aperivue/medsci-skills/actions/workflows/validate.yml)
12
- ![Skills](https://img.shields.io/badge/Skills-55-brightgreen?style=flat-square)
12
+ ![Skills](https://img.shields.io/badge/Skills-56-brightgreen?style=flat-square)
13
13
  [![npm](https://img.shields.io/npm/v/medsci-skills?style=flat-square&label=npm&color=cb3837)](https://www.npmjs.com/package/medsci-skills)
14
14
  [![npm downloads](https://img.shields.io/npm/dw/medsci-skills?style=flat-square&label=npm%20downloads&color=cb3837)](https://www.npmjs.com/package/medsci-skills)
15
15
  [![Watch the 2-min intro](https://img.shields.io/badge/▶_Watch-2--min_intro-FF0000?style=flat-square&logo=youtube&logoColor=white)](https://youtu.be/MclQ_RIofpE)
@@ -288,6 +288,8 @@ The E2E pipeline (`orchestrate --e2e`) produces everything up to `qc/`. The `sub
288
288
 
289
289
  ## What's New
290
290
 
291
+ **v5.21** — verification-layer batch, mostly promoted from real submission failures. A **marked (tracked-changes) manuscript** is now built by driving Word's Compare from the command line and proved by a **round trip** — accepting every revision must reproduce the revised paper exactly, rejecting every revision must reproduce the original — and the gate is **move-aware**, because Word encodes a relocated paragraph as `w:moveFrom`/`w:moveTo` and an insert-and-delete-only verifier calls a good file corrupt. Every detector's `qc/*.json` now **names the detector that wrote it**, so an artifact can be traced back to the check that produced it (a CI-enforced contract). Two `/verify-refs` precision defects: a Unicode hyphen in a surname fired `MISMATCH` — the verdict that means *fabricated author* — on a correct reference, and a Better BibTeX brace-protected surname was read as a corporate author, **silently skipping** the author check the tool exists to perform. New gates: publisher markup in a `.bib` title (`<scp>WHO</scp>` renders as garbage and no gate looked at the printed title), **complete/quasi separation** before a logistic model is fitted (a pathognomonic sign has an empty cell by construction; `glm` returns OR = 0.00, *p* = 0.99, and an AUC that gets reported), and a probe + gate for manuscripts claiming a system **improved itself** (which rung of the verification hierarchy said so?). `/find-cohort-gap` now accepts a **local codebook** — an institutional registry or EMR export, not only a named public cohort — enumerating variables verbatim with provenance rather than letting a model summarise them. Additive and backward-compatible; **55 skills / 46 guidelines / 61 integrity detectors / 23 domain-probe modules**. (See the [CHANGELOG](CHANGELOG.md) for v5.0–v5.20.)
292
+
291
293
  **v5.20.1** — audit-driven coherence fixes. A real `/orchestrate --e2e` state-transition bug (the pipeline halted at step 3 because it required a DOCX that is only rendered at step 7), all 55 skills made routable from the single entry point with a CI reachability gate, and a README plugin-count that had drifted from the marketplace SSOT (now gated). No skill/detector change.
292
294
 
293
295
  **v5.20** — reviewer-arithmetic gates. Five deterministic `self-review` detectors promoted from a peer-review cycle, each recomputing what a manuscript already prints: `check_table_percentages` (an `n (%)` cell vs its column denominator), `check_nested_group_comparison` (a P value comparing an analysed subset against the parent cohort that **contains** it — nested, invalid), `check_reported_p_from_counts` (rebuilds each 2×2 row and recomputes Fisher / Pearson χ² ± Yates in **pure stdlib**, calibrating the family on rows that reproduce), `check_dta_denominators` (sensitivity/specificity denominators vs the reference-standard category counts, behind a matching grand total), and `check_paired_difference_estimator` (an odd-n integer-scale median cannot be non-integer; a zero-width CI; an unnamed estimator). Plus `/peer-review` request-type discipline (disclosure vs computation) and impossibility-claim verification. Additive and backward-compatible; **55 skills / 46 guidelines / 57 integrity detectors / 22 domain-probe modules**. (See the [CHANGELOG](CHANGELOG.md) for v5.0–v5.19.)
@@ -714,9 +716,33 @@ Prints a checklist showing which components are present, which are missing, and
714
716
 
715
717
  ## Requirements
716
718
 
719
+ **Python 3.9+ and an agent host. That is the whole hard requirement.** Every integrity detector is
720
+ stdlib-only, and so is drafting, reviewing, and auditing a manuscript. If you have no Python, the
721
+ double-click installer will offer to install it for you (`winget` on Windows, or the official
722
+ download page) rather than leaving you at a dead end.
723
+
717
724
  - An [Agent Skills](https://agentskills.io)-compatible host — [Claude Code](https://claude.ai/code) (primary), or Codex / Cursor / GitHub Copilot (see [`docs/host_compatibility.md`](docs/host_compatibility.md); some live-data workflows rely on Claude MCP servers)
718
- - Python 3.9+ (for statistical analysis and figure generation)
719
- - R 4.0+ with `meta` (>=7.0), `metafor` (>=4.0), `mada` (>=0.5.11) packages (for meta-analysis)
725
+ - Python 3.9+ — the floor is CI-enforced (`scripts/check_python_floor.py`). Newer is better; if you are installing Python today, take the latest.
726
+
727
+ Everything else is needed by *some* skills and not others. Rather than a shopping list of packages
728
+ you have never heard of, ask the toolkit what **this** computer can do:
729
+
730
+ ```bash
731
+ python3 installers/doctor.py # what works, what does not, and the exact fix for each
732
+ python3 installers/doctor.py --fix # offers to install what is missing — asking before each one
733
+ ```
734
+ (Or double-click `installers/check-setup-macos.command` / `installers/check-setup-windows.cmd`.)
735
+
736
+ It reports in terms of what you were trying to *do* — "turn your manuscript into a journal-formatted
737
+ Word file" needs **pandoc**; "read and QC submission PDFs" needs **poppler**; "open a .docx at all"
738
+ needs **python-docx** — and installs the small things on request. Large things (a TeX distribution,
739
+ R, PyTorch) are never installed for you: it prints the size and the command and leaves the choice
740
+ alone.
741
+
742
+ **R is not required.** `/analyze-stats` writes Python by default and only emits R if you ask it to;
743
+ the toolkit itself never executes R. Install R (with `meta`, `metafor`, `mada` for meta-analysis)
744
+ only if you want to run the R code it writes for you. The same is true of PyTorch and
745
+ `/model-scaffold`: writing the training code needs nothing; running it needs torch.
720
746
 
721
747
  ## Use Cases
722
748
 
@@ -786,8 +812,32 @@ Or equivalently: `/write-paper --autonomous` if analysis and figures already exi
786
812
 
787
813
  ## Contributing
788
814
 
789
- Contributions are welcome and most are **one small, self-contained file** that a
790
- template walks you through. You do not need to understand the whole pipeline to add value.
815
+ **If you have already changed something on your own machine, `/contribute` will send it for you.**
816
+ Most people who use this are clinicians, not git users: they add the journal they publish in, fix a
817
+ checklist item that was wrong for their specialty, adapt a skill to their department — and the
818
+ change dies on one laptop.
819
+
820
+ ```
821
+ /contribute
822
+ ```
823
+
824
+ It compares your installed skills against what was shipped, tells you exactly what you changed,
825
+ **scans it for patient data, hospital names, IRB numbers and manuscript IDs** (that scan blocks, and
826
+ it is the reason this is a skill and not a button), shows you every line that would leave your
827
+ machine, and only then opens the pull request — you never type a git command. No GitHub CLI? It
828
+ reaches the project as an issue instead.
829
+
830
+ The same skill files a **false positive** ("this flagged my paper and it was wrong") or a failed
831
+ step. That is not a lesser contribution: it is the only evidence anyone has of how a detector
832
+ behaves on a real manuscript rather than a synthetic fixture.
833
+
834
+ **You will not be nagged.** Reminders are off by default; the installer mentions the option once
835
+ and then never again. `/contribute` only ever runs when you run it.
836
+
837
+ ---
838
+
839
+ Contributions are also welcome the ordinary way — and most are **one small, self-contained file**
840
+ that a template walks you through. You do not need to understand the whole pipeline to add value.
791
841
  Pick a [**good first issue**](https://github.com/Aperivue/medsci-skills/contribute), or start
792
842
  from one of these:
793
843
 
@@ -823,6 +873,24 @@ Community members who have reported, verified, or shaped the project are credite
823
873
 
824
874
  ## In the Wild
825
875
 
876
+ ### Cited in the literature
877
+
878
+ **Chen, Wang & Qu (2026), *Recursive Self-Improvement in AI*** — [arXiv:2607.07663](https://arxiv.org/abs/2607.07663), a
879
+ survey of **1,250 papers** — cites this project's methods paper
880
+ ([arXiv:2606.09500](https://arxiv.org/abs/2606.09500)) twice, as the reference for what fails when an AI
881
+ audits its own scientific output, and names the approach taken here as the response:
882
+
883
+ > "In regulated domains, **deterministic integrity gates are interposed** because 'self-critique
884
+ > inherits the blind spots that produce confident fabrication'."
885
+ > — §6.3 (quoting the methods paper; also cited in §5.3, on the self-confirming loop)
886
+
887
+ That is the argument this repository is built on: a model asked to check its own work inherits the
888
+ blind spots that produced the error, and a script that **recomputes** the number does not.
889
+
890
+ Every citation we know of is logged in [`docs/citations.md`](docs/citations.md).
891
+
892
+ ### Adoption
893
+
826
894
  Adoption is tracked openly in [`IMPACT.md`](IMPACT.md) (stars, forks, traffic,
827
895
  release downloads — snapshotted weekly into [`metrics/traffic_log.csv`](metrics/traffic_log.csv))
828
896
  and academic use is logged in [`docs/citations.md`](docs/citations.md).
@@ -1,4 +1,16 @@
1
1
  #!/usr/bin/env bash
2
+ # MedSci Skills — double-click installer for macOS.
3
+ #
4
+ # The person running this is a clinician who double-clicked a file. Every failure must end in a
5
+ # sentence they can act on — never a Python traceback, and never a silent "done" that installed
6
+ # nothing. Two things had to change for that to be true:
7
+ #
8
+ # * `python` is not necessarily Python 3. Handing the installer to whatever `command -v python`
9
+ # finds could hand it to a Python 2 that dies on the first f-string.
10
+ # * Python 3.8 is worse than Python 2, not better: install.py *parses* there, so instead of a
11
+ # clean "wrong version" it dies halfway through with a wall of traceback.
12
+ #
13
+ # So the interpreter is checked for a real version BEFORE anything runs.
2
14
  set -u
3
15
 
4
16
  cd "$(dirname "$0")/.."
@@ -6,23 +18,82 @@ cd "$(dirname "$0")/.."
6
18
  echo "MedSci Skills Installer for macOS"
7
19
  echo
8
20
 
21
+ MIN_MAJOR=3
22
+ MIN_MINOR=9
23
+
24
+ usable() { # true only if $1 is really Python >= 3.9
25
+ command -v "$1" >/dev/null 2>&1 || return 1
26
+ "$1" -c "import sys; raise SystemExit(0 if sys.version_info >= ($MIN_MAJOR, $MIN_MINOR) else 1)" \
27
+ >/dev/null 2>&1
28
+ }
29
+
9
30
  PY=""
10
- if command -v python3 >/dev/null 2>&1; then
11
- PY=python3
12
- elif command -v python >/dev/null 2>&1; then
13
- PY=python
31
+ for candidate in python3 python; do
32
+ if usable "$candidate"; then
33
+ PY="$candidate"
34
+ break
35
+ fi
36
+ done
37
+
38
+ if [ -z "$PY" ]; then
39
+ # "No Python" and "too old a Python" need different actions, and a clinician cannot be expected
40
+ # to work out which one they have from a version string.
41
+ if command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1; then
42
+ # No pipe to `head` — an installer must not depend on tools that may not be on PATH in a
43
+ # stripped-down environment. `python --version` prints one line anyway.
44
+ FOUND="$( python3 --version 2>/dev/null || python --version 2>/dev/null )"
45
+ echo "The Python on this Mac is too old for MedSci Skills."
46
+ echo " Found: ${FOUND:-an older version}"
47
+ echo " Needed: Python ${MIN_MAJOR}.${MIN_MINOR} or newer"
48
+ else
49
+ echo "Python was not found on this Mac."
50
+ fi
51
+ echo
52
+ echo "MedSci Skills is written in Python, so Python has to be on the computer first."
53
+ echo "It is free, official, and takes about two minutes."
54
+ echo
55
+ echo " 1. Download the latest Python for macOS (the page opens by itself in a moment)"
56
+ echo " 2. Open the file you downloaded and click through the installer"
57
+ echo " 3. Double-click THIS installer again"
58
+ echo
59
+ echo "Nothing has been changed on your computer."
60
+ echo
61
+
62
+ # Open the page for them. Telling a clinician to "go to python.org" is a step they have to
63
+ # perform; opening it is a step they do not. `open` is standard on macOS, but never let a
64
+ # convenience break the message they still need to read.
65
+ if command -v open >/dev/null 2>&1; then
66
+ read -r -p "Press Enter to open the Python download page..."
67
+ open "https://www.python.org/downloads/" >/dev/null 2>&1 || \
68
+ echo "Could not open the page. It is at: https://www.python.org/downloads/"
69
+ else
70
+ echo "The download page is at: https://www.python.org/downloads/"
71
+ fi
72
+
73
+ echo
74
+ read -r -p "Press Enter to close..."
75
+ exit 1
14
76
  fi
15
77
 
16
- if [ -n "$PY" ]; then
17
- "$PY" installers/install.py --target all --desktop-launcher
18
- # Turn on the in-app "update available" reminder for this turnkey install so you are told when a
19
- # new version is out (no terminal needed afterward). Best-effort; turn off later with
20
- # `install.py --disable-update-notify` or MEDSCI_NO_UPDATE_CHECK=1.
21
- "$PY" installers/install.py --enable-update-notify || true
22
- else
23
- echo "Python was not found."
24
- echo "Install Python 3 from https://www.python.org/downloads/ and run this installer again."
78
+ "$PY" installers/install.py --target all --desktop-launcher
79
+ STATUS=$?
80
+
81
+ if [ "$STATUS" -ne 0 ]; then
82
+ echo
83
+ echo "The installation did not finish (it stopped with error $STATUS)."
84
+ echo "Nothing was left half-installed — the installer undoes its own work if it cannot complete."
85
+ echo
86
+ echo "If you tell us what the message above said, we can fix it:"
87
+ echo " https://github.com/Aperivue/medsci-skills/issues/new"
88
+ echo
89
+ read -r -p "Press Enter to close..."
90
+ exit "$STATUS"
25
91
  fi
26
92
 
93
+ # Turn on the in-app "update available" reminder for this turnkey install, so you are told when a
94
+ # new version ships instead of staying on this one forever. Best-effort: a failure here is not a
95
+ # failed install. Turn it off later with `install.py --disable-update-notify`.
96
+ "$PY" installers/install.py --enable-update-notify >/dev/null 2>&1 || true
97
+
27
98
  echo
28
99
  read -r -p "Press Enter to close..."
@@ -1,30 +1,112 @@
1
1
  @echo off
2
- setlocal
2
+ rem MedSci Skills - double-click installer for Windows.
3
+ rem
4
+ rem Windows is where most of these downloads go, and it has a trap the old script fell into.
5
+ rem
6
+ rem THE MICROSOFT STORE STUB. On a Windows machine with no Python, `python` still EXISTS: it is an
7
+ rem App Execution Alias that opens the Microsoft Store. So `where python` succeeds, errorlevel is
8
+ rem 0, the script cheerfully runs it -- and a Store page opens, nothing is installed, and the
9
+ rem installer reports it is done. The clinician is told everything worked and has nothing.
10
+ rem
11
+ rem A TOO-OLD PYTHON. install.py PARSES on 3.8, so it does not fail cleanly: it dies partway
12
+ rem through with a traceback instead of saying "wrong version".
13
+ rem
14
+ rem So an interpreter is only accepted after it proves, BY RUNNING, that it is Python 3.9 or newer.
15
+ rem Asking `where` only proves a name exists.
16
+ setlocal EnableDelayedExpansion
3
17
  cd /d "%~dp0\.."
4
18
 
5
19
  echo MedSci Skills Installer for Windows
6
20
  echo.
7
21
 
8
- where py >nul 2>nul
9
- if %errorlevel%==0 (
10
- py -3 installers\install.py --target all --desktop-launcher
11
- rem Turn on the in-app "update available" reminder for this turnkey install (disable later with --disable-update-notify).
12
- py -3 installers\install.py --enable-update-notify
13
- goto done
22
+ set "PY="
23
+
24
+ rem The py launcher is the reliable path when it exists.
25
+ py -3 -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)" >nul 2>nul
26
+ if !errorlevel!==0 set "PY=py -3"
27
+
28
+ rem Fall back to `python` -- but only if it actually runs. The Store stub fails this test, which is
29
+ rem the entire reason for testing instead of asking `where`.
30
+ if not defined PY (
31
+ python -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)" >nul 2>nul
32
+ if !errorlevel!==0 set "PY=python"
14
33
  )
15
34
 
16
- where python >nul 2>nul
17
- if %errorlevel%==0 (
18
- python installers\install.py --target all --desktop-launcher
19
- rem Turn on the in-app "update available" reminder for this turnkey install (disable later with --disable-update-notify).
20
- python installers\install.py --enable-update-notify
21
- goto done
35
+ if not defined PY (
36
+ echo Python 3.9 or newer was not found on this computer.
37
+ echo.
38
+ echo MedSci Skills is written in Python, so Python has to be installed first.
39
+ echo It is free, official, and takes a couple of minutes.
40
+ echo.
41
+
42
+ rem winget ships with Windows 10 ^(1809+^) and Windows 11, so on most hospital machines Python can
43
+ rem simply be installed for the user -- no admin rights, no download page, no PATH checkbox to
44
+ rem miss. We ASK first: installing software on someone's computer without asking is not ours to do.
45
+ where winget >nul 2>nul
46
+ if !errorlevel!==0 (
47
+ echo This installer can install Python for you now, from the official Python
48
+ echo repository, into your own user account ^(no administrator password needed^).
49
+ echo.
50
+ set /p "DOIT=Install Python now? [Y/N] "
51
+ if /i "!DOIT!"=="Y" (
52
+ echo.
53
+ echo Installing Python. This takes a couple of minutes...
54
+ echo.
55
+ winget install --exact --id Python.Python.3.13 --scope user ^
56
+ --accept-source-agreements --accept-package-agreements
57
+ echo.
58
+ if !errorlevel!==0 (
59
+ echo Python is installed.
60
+ echo.
61
+ echo Windows only notices a new program in NEW windows, so this one cannot use it yet:
62
+ echo.
63
+ echo ^>^> Close this window and double-click the installer again. ^<^<
64
+ echo.
65
+ echo Nothing else has been changed on your computer.
66
+ echo.
67
+ pause
68
+ exit /b 1
69
+ )
70
+ echo Python could not be installed automatically ^(error !errorlevel!^).
71
+ echo Use the download page instead - it is opening now.
72
+ echo.
73
+ )
74
+ )
75
+
76
+ echo 1. Download the latest Python for Windows ^(the page opens by itself in a moment^)
77
+ echo 2. Run the file you downloaded.
78
+ echo IMPORTANT: on the FIRST screen, tick the box "Add python.exe to PATH"
79
+ echo before pressing Install. It is easy to miss, and nothing works without it.
80
+ echo 3. Double-click THIS installer again.
81
+ echo.
82
+ echo If a Microsoft Store page opened when you tried Python before, that page is a
83
+ echo placeholder, not Python. Use python.org instead.
84
+ echo.
85
+ echo Nothing has been changed on your computer.
86
+ echo.
87
+ start "" "https://www.python.org/downloads/"
88
+ pause
89
+ exit /b 1
22
90
  )
23
91
 
24
- echo Python was not found.
25
- echo Please install Python 3 from https://www.python.org/downloads/ and run this installer again.
26
- echo.
92
+ %PY% installers\install.py --target all --desktop-launcher
93
+ if not !errorlevel!==0 (
94
+ set "RC=!errorlevel!"
95
+ echo.
96
+ echo The installation did not finish ^(it stopped with error !RC!^).
97
+ echo Nothing was left half-installed - the installer undoes its own work if it cannot complete.
98
+ echo.
99
+ echo If you tell us what the message above said, we can fix it:
100
+ echo https://github.com/Aperivue/medsci-skills/issues/new
101
+ echo.
102
+ pause
103
+ exit /b !RC!
104
+ )
105
+
106
+ rem Turn on the in-app "update available" reminder for this turnkey install, so you are told when a
107
+ rem new version ships instead of staying on this one forever. Best-effort: a failure here is not a
108
+ rem failed install.
109
+ %PY% installers\install.py --enable-update-notify >nul 2>nul
27
110
 
28
- :done
29
111
  echo.
30
112
  pause
@@ -15,6 +15,20 @@ import os
15
15
  import sys
16
16
  from pathlib import Path
17
17
 
18
+ # The floor the README promises. Checked here as well as in the double-click installers, because
19
+ # the failure it prevents is a clinician staring at a Python traceback: on 3.8 this file *parses*
20
+ # (so there is no clean syntax error to explain itself) and then dies somewhere in the middle,
21
+ # leaving a half-explained wall of text and no idea what to do next.
22
+ MIN_PYTHON = (3, 9)
23
+ if sys.version_info < MIN_PYTHON:
24
+ have = ".".join(str(n) for n in sys.version_info[:3])
25
+ sys.exit(
26
+ f"\nMedSci Skills needs Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]} or newer. This computer has {have}.\n\n"
27
+ " Install the latest Python from https://www.python.org/downloads/\n"
28
+ " then run this installer again.\n\n"
29
+ "Nothing has been changed on your computer.\n"
30
+ )
31
+
18
32
  sys.path.insert(0, str(Path(__file__).resolve().parent)) # allow `import medsci_txn` when run as a script
19
33
  import medsci_txn # noqa: E402
20
34
 
@@ -231,6 +245,47 @@ def parse_args() -> argparse.Namespace:
231
245
  return parser.parse_args()
232
246
 
233
247
 
248
+
249
+ def _offer_contribution_reminders_once(log_lines) -> None:
250
+ """Mention — once, ever — that the option exists. Then never again, whatever they do.
251
+
252
+ A person who ignores the question has answered it. Asking a second time would be the exact
253
+ nagging this setting exists to prevent, so `asked_once` is recorded whether or not they act.
254
+ """
255
+ import json as _json
256
+ home = medsci_txn.state_home()
257
+ cfg_path = home / "config.json"
258
+ try:
259
+ cfg = _json.loads(cfg_path.read_text(encoding="utf-8")) if cfg_path.is_file() else {}
260
+ except (ValueError, OSError):
261
+ cfg = {}
262
+ if cfg.get("asked_once"):
263
+ return
264
+
265
+ log(
266
+ "\nTwo things, once — then we will not bring either up again:\n"
267
+ "\n"
268
+ " 1. If you ever adapt a skill (add your journal, fix something wrong for your specialty),\n"
269
+ " that change can be offered back with /contribute. Reminders are OFF by default, and\n"
270
+ " nothing is ever sent without a patient-data scan and your confirmation on every line.\n"
271
+ " python3 ~/.claude/skills/contribute/scripts/contribution_prefs.py --on\n"
272
+ "\n"
273
+ " 2. If this ends up saving you time, the way to say so is a star on the repository.\n"
274
+ " Not applause — it is how the next researcher with your problem finds it, and for\n"
275
+ " software with no DOI in anyone's reference list it is the closest thing to a citation.\n"
276
+ " Most people who write to say thanks have never done it, because nobody told them.\n"
277
+ " One command, no browser:\n"
278
+ " python3 ~/.claude/skills/contribute/scripts/star_repo.py --now\n"
279
+ " (or one click: https://github.com/Aperivue/medsci-skills)",
280
+ log_lines,
281
+ )
282
+ cfg["asked_once"] = True
283
+ try:
284
+ cfg_path.parent.mkdir(parents=True, exist_ok=True)
285
+ medsci_txn.atomic_write_json(cfg_path, cfg)
286
+ except OSError:
287
+ pass # never fail an install over a preference file
288
+
234
289
  def main() -> int:
235
290
  args = parse_args()
236
291
  if args.self_test:
@@ -319,6 +374,23 @@ def main() -> int:
319
374
  print(f"\nInstall log: {log_path}")
320
375
  return 1
321
376
 
377
+ # Say what ELSE this computer needs, while they are still looking at the screen.
378
+ #
379
+ # Every integrity detector is stdlib-only, so the install above is enough to use most of the
380
+ # toolkit. But a few skills need a program we do not ship — pandoc to render a manuscript into
381
+ # a journal-formatted Word file, poppler to read a submission PDF. Those skills already fail
382
+ # politely; the problem is that they fail *later*, in the middle of the work, to someone who
383
+ # will not go and install a package manager at that moment. Tell them now, when the answer is
384
+ # one command. Read-only, asks nothing, installs nothing, and can never fail the install.
385
+ if not args.dry_run:
386
+ try:
387
+ import doctor # noqa: PLC0415
388
+
389
+ doctor.brief_summary(lambda m: log(m, log_lines))
390
+ except Exception: # noqa: BLE001 - a setup *check* must never break an install that worked
391
+ pass
392
+
393
+ _offer_contribution_reminders_once(log_lines)
322
394
  log("\nDone. Restart Claude Code, Codex, or Cursor before testing the skills.", log_lines)
323
395
  log("First test prompt:", log_lines)
324
396
  log("MedSci Skills가 설치됐는지 확인하고, 오늘 실습에 쓸 대표 스킬 5개만 보여줘.", log_lines)
@@ -38,6 +38,7 @@ so staging + holding dirs are kept on the destination's filesystem.
38
38
 
39
39
  from __future__ import annotations
40
40
 
41
+ import datetime
41
42
  import hashlib
42
43
  import json
43
44
  import os
@@ -144,6 +145,50 @@ def permanent_backup(skill_path: Path, target: str, home: Path, reason: str, log
144
145
  return dest
145
146
 
146
147
 
148
+
149
+ # ------------------------------------------------------- contribution reminders (opt-in, off)
150
+
151
+ def _contrib_config(home: Path) -> Path:
152
+ return home / "config.json"
153
+
154
+
155
+ def _contribution_reminder_wanted(home: Path) -> bool:
156
+ """Off unless the user explicitly asked to be reminded, and then at most monthly.
157
+
158
+ An installer that nags is an installer people stop running, and this audience already
159
+ under-updates. Defaulting to silence costs a few contributions; defaulting to noise costs
160
+ the update path itself.
161
+ """
162
+ p = _contrib_config(home)
163
+ if not p.is_file():
164
+ return False
165
+ try:
166
+ cfg = json.loads(p.read_text(encoding="utf-8"))
167
+ except (ValueError, OSError):
168
+ return False
169
+ if cfg.get("contribution_reminders") != "on":
170
+ return False
171
+ last = cfg.get("last_reminded")
172
+ if not last:
173
+ return True
174
+ try:
175
+ y, m, d = (int(x) for x in str(last).split("-"))
176
+ return (datetime.date.today() - datetime.date(y, m, d)).days >= 30
177
+ except (ValueError, TypeError):
178
+ return True
179
+
180
+
181
+ def _mark_reminded(home: Path) -> None:
182
+ p = _contrib_config(home)
183
+ try:
184
+ cfg = json.loads(p.read_text(encoding="utf-8")) if p.is_file() else {}
185
+ cfg["last_reminded"] = datetime.date.today().isoformat()
186
+ p.parent.mkdir(parents=True, exist_ok=True)
187
+ atomic_write_json(p, cfg)
188
+ except OSError:
189
+ pass # a reminder is not worth failing an install over
190
+
191
+
147
192
  # ---------------------------------------------------------------- recovery
148
193
 
149
194
  def recover_target(target: str, home: Path, log) -> bool:
@@ -256,6 +301,25 @@ def install_target(
256
301
  else:
257
302
  permanent_backup(d, target, home, "legacy-collision", log)
258
303
 
304
+ # A user-modified skill is usually a clinician adapting the toolkit to their journal, their
305
+ # specialty, their department. The backup preserves it; nothing ever read the backup again.
306
+ #
307
+ # But the person who installed a research tool did not sign up to be asked for things, and a
308
+ # physician finishing a paper does not need a lecture on open-source etiquette from their
309
+ # installer. So this is OPT-IN and OFF BY DEFAULT: we say nothing unless they asked to be
310
+ # told, and even then not more than once a month. Silence is the default, and it is correct.
311
+ modified = [
312
+ name for name in owned_skills
313
+ if (dest / name).exists()
314
+ and name in prior_skills
315
+ and skill_inventory(dest / name) != prior_skills[name].get("inventory", {})
316
+ ]
317
+ if modified and _contribution_reminder_wanted(home):
318
+ log(f" You had changed {len(modified)} skill(s): {', '.join(sorted(modified))}. Your version")
319
+ log(" is backed up (above); if the change is worth keeping, /contribute will offer it back")
320
+ log(" to the project — patient-data scan first, nothing sent without your confirmation.")
321
+ _mark_reminded(home)
322
+
259
323
  prune = [n for n in prior_skills if n not in set(owned_skills)]
260
324
  for name in prune: # a removed/renamed owned skill: back up before pruning, then remove in txn
261
325
  d = dest / name