linkml-reference-validator 0.2.0rc1__tar.gz → 0.2.1rc2__tar.gz

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 (190) hide show
  1. linkml_reference_validator-0.2.1rc2/.claude/settings.json +2 -0
  2. linkml_reference_validator-0.2.1rc2/.devcontainer/devcontainer.json +27 -0
  3. linkml_reference_validator-0.2.1rc2/.devcontainer/post_create.sh +6 -0
  4. linkml_reference_validator-0.2.1rc2/.github/workflows/claude-code-review.yml +61 -0
  5. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/claude-ontology-review.yml +13 -27
  6. linkml_reference_validator-0.2.1rc2/.github/workflows/claude.yml +62 -0
  7. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/dragon-ai.yml +2 -2
  8. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.gitignore +1 -0
  9. linkml_reference_validator-0.2.1rc2/.goosehints +1 -0
  10. linkml_reference_validator-0.2.1rc2/AGENTS.md +1 -0
  11. linkml_reference_validator-0.2.1rc2/BUG_GEO_ACCESSION_TO_UID.md +137 -0
  12. linkml_reference_validator-0.2.1rc2/BUG_ZENODO_DOI_AND_URL_HANDLING.md +258 -0
  13. linkml_reference_validator-0.2.1rc2/CLI-ISSUES.md +31 -0
  14. linkml_reference_validator-0.2.1rc2/FEATURE_REQUEST_REPAIR.md +255 -0
  15. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/PKG-INFO +64 -6
  16. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/README.md +62 -5
  17. linkml_reference_validator-0.2.1rc2/copilot-instructions.md +1 -0
  18. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/concepts/content-types.md +61 -2
  19. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/concepts/editorial-conventions.md +5 -1
  20. linkml_reference_validator-0.2.1rc2/docs/how-to/fetch-full-text-and-pdfs.md +185 -0
  21. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/repair-validation-errors.md +1 -1
  22. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/use-local-files-and-urls.md +6 -2
  23. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-dois.md +95 -7
  24. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-urls.md +13 -7
  25. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/notebooks/01_getting_started.ipynb +3 -2
  26. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/notebooks/02_advanced_usage.ipynb +3 -2
  27. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/notebooks/04_obo_validation.ipynb +4 -3
  28. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/notebooks/05_geo_validation.ipynb +5 -4
  29. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/reference/cli.md +243 -21
  30. linkml_reference_validator-0.2.1rc2/docs/setup-guide.md +699 -0
  31. linkml_reference_validator-0.2.1rc2/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +2969 -0
  32. linkml_reference_validator-0.2.1rc2/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +260 -0
  33. linkml_reference_validator-0.2.1rc2/docs/troubleshooting.md +706 -0
  34. linkml_reference_validator-0.2.1rc2/docs/tutorials/complete-workflow.md +900 -0
  35. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/mkdocs.yml +4 -0
  36. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/pyproject.toml +1 -0
  37. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/lookup.py +38 -1
  38. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/shared.py +8 -0
  39. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/validate.py +66 -33
  40. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/acquire.py +134 -0
  41. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/__init__.py +16 -0
  42. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/base.py +64 -0
  43. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/html.py +46 -0
  44. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/pdf.py +67 -0
  45. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/xml.py +46 -0
  46. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/__init__.py +21 -0
  47. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/base.py +70 -0
  48. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/epmc_preprint.py +142 -0
  49. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/json_api.py +135 -0
  50. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/loader.py +94 -0
  51. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/openalex.py +70 -0
  52. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/pmc.py +124 -0
  53. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/unpaywall.py +69 -0
  54. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/identifiers.py +62 -0
  55. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/reference_fetcher.py +332 -1
  56. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/__init__.py +2 -0
  57. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +15 -2
  58. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/sources/doi.py +526 -0
  59. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/entrez.py +29 -4
  60. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/pmid.py +110 -21
  61. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/sources/ppr.py +159 -0
  62. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/url.py +43 -12
  63. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/sources/utils.py +106 -0
  64. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/field_detection.py +43 -2
  65. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/models.py +220 -0
  66. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/plugins/reference_validation_plugin.py +26 -12
  67. linkml_reference_validator-0.2.1rc2/tests/data/test_data_deep_nesting_invalid.yaml +17 -0
  68. linkml_reference_validator-0.2.1rc2/tests/data/test_data_deep_nesting_valid.yaml +23 -0
  69. linkml_reference_validator-0.2.1rc2/tests/data/test_schema_deep_nesting.yaml +74 -0
  70. linkml_reference_validator-0.2.1rc2/tests/test_acquire.py +116 -0
  71. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_cli.py +266 -0
  72. linkml_reference_validator-0.2.1rc2/tests/test_deep_nesting.py +401 -0
  73. linkml_reference_validator-0.2.1rc2/tests/test_doi_preprint.py +103 -0
  74. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_e2e_integration.py +58 -0
  75. linkml_reference_validator-0.2.1rc2/tests/test_epmc_preprint_provider.py +196 -0
  76. linkml_reference_validator-0.2.1rc2/tests/test_extractors.py +104 -0
  77. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_field_detection.py +47 -0
  78. linkml_reference_validator-0.2.1rc2/tests/test_fulltext_loader.py +97 -0
  79. linkml_reference_validator-0.2.1rc2/tests/test_fulltext_providers.py +175 -0
  80. linkml_reference_validator-0.2.1rc2/tests/test_identifiers.py +56 -0
  81. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_models.py +38 -0
  82. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_optional_linkml_dependency.py +31 -3
  83. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_plugin_integration.py +2 -1
  84. linkml_reference_validator-0.2.1rc2/tests/test_ppr_source.py +125 -0
  85. linkml_reference_validator-0.2.1rc2/tests/test_reference_fetcher.py +1032 -0
  86. linkml_reference_validator-0.2.1rc2/tests/test_source_utils.py +193 -0
  87. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_sources.py +149 -33
  88. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_supporting_text_validator.py +20 -0
  89. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_title_validation.py +2 -0
  90. linkml_reference_validator-0.2.1rc2/tests/test_unknown_slot_traversal.py +131 -0
  91. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_validation_config.py +17 -0
  92. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/uv.lock +14 -0
  93. linkml_reference_validator-0.2.0rc1/.claude/settings.json +0 -14
  94. linkml_reference_validator-0.2.0rc1/.github/workflows/claude-code-review.yml +0 -78
  95. linkml_reference_validator-0.2.0rc1/.github/workflows/claude.yml +0 -90
  96. linkml_reference_validator-0.2.0rc1/src/linkml_reference_validator/etl/sources/doi.py +0 -182
  97. linkml_reference_validator-0.2.0rc1/tests/test_reference_fetcher.py +0 -312
  98. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.claude/.mcp.json +0 -0
  99. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.copier-answers.yml +0 -0
  100. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.editorconfig +0 -0
  101. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/actions/claude-code-action/action.yml +0 -0
  102. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
  103. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/actions/claude-issue-triage-action/action.yml +0 -0
  104. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/ai-controllers.json +0 -0
  105. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/copilot-instructions.md +0 -0
  106. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/copilot-setup-steps.yml +0 -0
  107. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/dependabot.yml +0 -0
  108. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/claude-issue-summarize.yml +0 -0
  109. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/claude-issue-triage.yml +0 -0
  110. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/deploy-docs.yaml +0 -0
  111. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/main.yaml +0 -0
  112. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.github/workflows/pypi-publish.yaml +0 -0
  113. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.pre-commit-config.yaml +0 -0
  114. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/.yamllint.yaml +0 -0
  115. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/CLAUDE.md +0 -0
  116. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/CODE_OF_CONDUCT.md +0 -0
  117. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/CONTRIBUTING.md +0 -0
  118. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/LICENSE +0 -0
  119. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/REVIEW2.md +0 -0
  120. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/REVIEW3.md +0 -0
  121. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/ai.just +0 -0
  122. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/about.md +0 -0
  123. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/concepts/how-it-works.md +0 -0
  124. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/elements/.gitkeep +0 -0
  125. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/add-reference-source.md +0 -0
  126. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/skip-unsupported-references.md +0 -0
  127. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-clinical-trials.md +0 -0
  128. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-entrez.md +0 -0
  129. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-obo-files.md +0 -0
  130. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/how-to/validate-titles.md +0 -0
  131. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/index.md +0 -0
  132. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/notebooks/03_python_api.ipynb +0 -0
  133. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/quickstart.md +0 -0
  134. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/templates-linkml/README.md +0 -0
  135. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/docs/todo.md +0 -0
  136. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/examples/README.md +0 -0
  137. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/justfile +0 -0
  138. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/mypy.ini +0 -0
  139. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/notebooks/01_getting_started.ipynb +0 -0
  140. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/notebooks/02_advanced_usage.ipynb +0 -0
  141. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/notebooks/03_python_api.ipynb +0 -0
  142. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/notebooks/README.md +0 -0
  143. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/project/README.md +0 -0
  144. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/project.justfile +0 -0
  145. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/pytest.ini +0 -0
  146. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/__init__.py +0 -0
  147. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/_version.py +0 -0
  148. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/__init__.py +0 -0
  149. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/cache.py +0 -0
  150. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/cli/repair.py +0 -0
  151. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/__init__.py +0 -0
  152. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/base.py +0 -0
  153. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/file.py +0 -0
  154. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/json_api.py +0 -0
  155. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
  156. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
  157. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
  158. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/plugins/__init__.py +0 -0
  159. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/validation/__init__.py +0 -0
  160. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +0 -0
  161. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/validation/repairer.py +0 -0
  162. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/src/linkml_reference_validator/validation/supporting_text_validator.py +0 -0
  163. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/__init__.py +0 -0
  164. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/conftest.py +0 -0
  165. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/data/test_data_invalid.yaml +0 -0
  166. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/data/test_data_valid.yaml +0 -0
  167. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/data/test_schema.yaml +0 -0
  168. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/CLINICALTRIALS_NCT00000001.md +0 -0
  169. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/GEO_GSE12345.md +0 -0
  170. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMC_TEST001.md +0 -0
  171. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMC_TEST001.txt +0 -0
  172. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMC_TEST002.md +0 -0
  173. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMC_TEST002.txt +0 -0
  174. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST001.md +0 -0
  175. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST001.txt +0 -0
  176. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST002.md +0 -0
  177. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST002.txt +0 -0
  178. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST003.md +0 -0
  179. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/PMID_TEST003.txt +0 -0
  180. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/fixtures/sample.obo +0 -0
  181. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_cli_lookup.py +0 -0
  182. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_cli_repair.py +0 -0
  183. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_clinicaltrials_integration.py +0 -0
  184. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_fuzzy_suggestions.py +0 -0
  185. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_geo_integration.py +0 -0
  186. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_json_api_source.py +0 -0
  187. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_pmc_fulltext.py +0 -0
  188. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_real_validation.py +0 -0
  189. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_repair.py +0 -0
  190. {linkml_reference_validator-0.2.0rc1 → linkml_reference_validator-0.2.1rc2}/tests/test_text_extractor.py +0 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "odkfull-codespace",
3
+ // consider also: Codespaces Prebuilds
4
+ "image": "obolibrary/odkfull:latest",
5
+
6
+ // Mount your repo where ODK expects it
7
+ "workspaceMount": "source=${localWorkspaceFolder},target=/work,type=bind,consistency=cached",
8
+ "workspaceFolder": "/work",
9
+
10
+ // Keep VS Code in control of the container command (safer than using image ENTRYPOINT)
11
+ "overrideCommand": true,
12
+
13
+ // Be root so you can add small tools if you need them
14
+ "remoteUser": "root",
15
+ //"features": {
16
+ // "ghcr.io/devcontainers/features/node:1": { "version": "lts" }
17
+ //},
18
+ "extensions": [
19
+ "anthropic.claude-code",
20
+ "ms-python.python",
21
+ "ms-azuretools.vscode-docker"
22
+ ],
23
+ "remoteEnv": {
24
+ "TEST_ENV": "123"
25
+ },
26
+ "postCreateCommand": "bash .devcontainer/post_create.sh"
27
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ odk --help || true
4
+ #curl -LsSf https://astral.sh/uv/install.sh | sh
5
+ #uv pip install oaklib
6
+ npm install -g @anthropic-ai/claude-code
@@ -0,0 +1,61 @@
1
+ name: Claude Code Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+ # Optional: Only run on specific file changes
7
+ # paths:
8
+ # - "src/**/*.ts"
9
+ # - "src/**/*.tsx"
10
+ # - "src/**/*.js"
11
+ # - "src/**/*.jsx"
12
+
13
+ jobs:
14
+ claude-review:
15
+ # Optional: Filter by PR author
16
+ # if: |
17
+ # github.event.pull_request.user.login == 'external-contributor' ||
18
+ # github.event.pull_request.user.login == 'new-developer' ||
19
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20
+
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: read
24
+ pull-requests: read
25
+ issues: read
26
+ id-token: write
27
+
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v6
31
+ with:
32
+ fetch-depth: 1
33
+
34
+ - name: Run Claude Code Review
35
+ id: claude-review
36
+ uses: anthropics/claude-code-action@v1
37
+ with:
38
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39
+
40
+ # In v1, providing a `prompt` runs in automation mode, which does NOT
41
+ # post a comment by default. track_progress restores the v0.x
42
+ # "post the review as a PR comment" behavior and auto-injects PR context.
43
+ track_progress: true
44
+
45
+ # Prompt for the automated review (no @claude mention needed).
46
+ prompt: |
47
+ Please review this pull request and provide feedback on:
48
+ - Code quality and best practices
49
+ - Potential bugs or issues
50
+ - Performance considerations
51
+ - Security concerns
52
+ - Test coverage
53
+
54
+ Be constructive and helpful in your feedback.
55
+
56
+ # Args passed through to the Claude CLI. Use the `opus` alias rather
57
+ # than a dated model id so the workflow tracks the current Opus and
58
+ # won't 404 when a specific version is retired.
59
+ claude_args: |
60
+ --model opus
61
+
@@ -35,15 +35,23 @@ jobs:
35
35
 
36
36
  - name: Run Claude Code Ontology Review
37
37
  id: claude-review
38
- uses: anthropics/claude-code-action@beta
38
+ uses: anthropics/claude-code-action@v1
39
39
  with:
40
40
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
41
41
 
42
- # Optional: Specify model (commenting defaults to Claude Sonnet 4)
43
- model: "claude-opus-4-1-20250805"
42
+ # In v1, providing a `prompt` runs in automation mode, which does NOT
43
+ # post a comment by default. track_progress restores the v0.x
44
+ # "post the review as a PR comment" behavior and auto-injects PR context.
45
+ track_progress: true
44
46
 
45
- # Direct prompt for automated review (no @claude mention needed)
46
- direct_prompt: |
47
+ # Model is passed through to the Claude CLI via claude_args. Use the
48
+ # `opus` alias so the workflow tracks the current Opus and won't 404
49
+ # when a specific dated model id is retired.
50
+ claude_args: |
51
+ --model opus
52
+
53
+ # Prompt for the automated review (no @claude mention needed)
54
+ prompt: |
47
55
  REPO: ${{ github.repository }}
48
56
  PR NUMBER: ${{ github.event.pull_request.number }}
49
57
 
@@ -90,26 +98,4 @@ jobs:
90
98
 
91
99
  # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
92
100
  use_sticky_comment: true
93
-
94
- # Optional: Customize review based on file types
95
- # direct_prompt: |
96
- # Review this PR focusing on:
97
- # - For TypeScript files: Type safety and proper interface usage
98
- # - For API endpoints: Security, input validation, and error handling
99
- # - For React components: Performance, accessibility, and best practices
100
- # - For tests: Coverage, edge cases, and test quality
101
-
102
- # Optional: Different prompts for different authors
103
- # direct_prompt: |
104
- # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
105
- # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
106
- # 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
107
-
108
- # Optional: Add specific tools for running tests or linting
109
- # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
110
-
111
- # Optional: Skip review for certain conditions
112
- # if: |
113
- # !contains(github.event.pull_request.title, '[skip-review]') &&
114
- # !contains(github.event.pull_request.title, '[WIP]')
115
101
 
@@ -0,0 +1,62 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: write
23
+ pull-requests: write
24
+ issues: write
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v6
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Install uv
34
+ uses: astral-sh/setup-uv@v7
35
+
36
+ - name: Install python tools
37
+ run: |
38
+ uv sync
39
+
40
+ - name: Run Claude Code
41
+ id: claude
42
+ uses: anthropics/claude-code-action@v1
43
+
44
+ with:
45
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
46
+
47
+ # This is an optional setting that allows Claude to read CI results on PRs
48
+ additional_permissions: |
49
+ actions: read
50
+
51
+ # In v1 the model, MCP servers, and allowed tools are passed through to
52
+ # the Claude CLI via claude_args (the v0.x model / mcp_config /
53
+ # allowed_tools inputs were removed). The `opus` alias tracks the
54
+ # current Opus so this won't 404 when a dated model id is retired.
55
+ claude_args: |
56
+ --model opus
57
+ --mcp-config '{"mcpServers":{"ols":{"command":"uvx","args":["ols-mcp"]},"sequential-thinking":{"command":"npx","args":["-y","@modelcontextprotocol/server-sequential-thinking"]}}}'
58
+ --allowedTools "Bash(*),FileEdit,Edit,MultiEdit,WebSearch,WebFetch,mcp__ols_mcp__search_all_ontologies,mcp__ols_mcp__get_terms_from_ontology"
59
+
60
+ # Optional: Customize the trigger phrase (default: @claude)
61
+ # trigger_phrase: "/claude"
62
+
@@ -29,7 +29,7 @@ jobs:
29
29
 
30
30
  - name: Check for qualifying mention
31
31
  id: check
32
- uses: actions/github-script@v6
32
+ uses: actions/github-script@v9
33
33
  with:
34
34
  github-token: ${{ secrets.PAT_FOR_PR }} # Use PAT instead of default token
35
35
  script: |
@@ -131,7 +131,7 @@ jobs:
131
131
  ls -alt ${{ env.TOOLS_DIR }}
132
132
 
133
133
  - name: Install uv
134
- uses: astral-sh/setup-uv@v5
134
+ uses: astral-sh/setup-uv@v7
135
135
 
136
136
  - name: Install python tools
137
137
  run: |
@@ -141,3 +141,4 @@ dmypy.json
141
141
  .idea
142
142
  # Local vscode editor config
143
143
  .vscode
144
+ .cursor
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,137 @@
1
+ # Bug: GEOSource fails to fetch GEO datasets
2
+
3
+ ## Summary
4
+
5
+ The `GEOSource` class in `linkml_reference_validator/etl/sources/entrez.py` cannot fetch GEO dataset metadata because it passes GSE accessions directly to `esummary`, but the GDS Entrez database requires numeric UIDs.
6
+
7
+ ## Error Observed
8
+
9
+ ```
10
+ WARNING:linkml_reference_validator.etl.sources.entrez:Failed to fetch Entrez summary for GEO:GSE67472: Invalid uid GSE67472 at position= 0
11
+ ```
12
+
13
+ ## Root Cause
14
+
15
+ The `EntrezSummarySource.fetch()` method calls:
16
+
17
+ ```python
18
+ handle = Entrez.esummary(db=self.ENTREZ_DB, id=identifier)
19
+ ```
20
+
21
+ For GEO, this becomes `esummary(db='gds', id='GSE67472')`, but the GDS database doesn't accept accession numbers as IDs - it requires numeric UIDs like `200067472`.
22
+
23
+ ## Proof of Concept
24
+
25
+ ```python
26
+ from Bio import Entrez
27
+ Entrez.email = 'test@example.com'
28
+
29
+ # This FAILS - accession not accepted as UID
30
+ handle = Entrez.esummary(db='gds', id='GSE67472')
31
+ # Error: Invalid uid GSE67472 at position=0
32
+
33
+ # This WORKS - use esearch first to get UID
34
+ handle = Entrez.esearch(db='gds', term='GSE67472[Accession]')
35
+ result = Entrez.read(handle)
36
+ handle.close()
37
+ # result['IdList'] = ['200067472', ...]
38
+
39
+ uid = result['IdList'][0] # '200067472'
40
+
41
+ handle = Entrez.esummary(db='gds', id=uid)
42
+ summary = Entrez.read(handle)
43
+ handle.close()
44
+ print(summary[0].get('title'))
45
+ # Output: "Airway epithelial gene expression in asthma versus healthy controls"
46
+ ```
47
+
48
+ ## Proposed Fix
49
+
50
+ Override `fetch()` in `GEOSource` to add an `esearch` step that converts accessions to UIDs:
51
+
52
+ ```python
53
+ @ReferenceSourceRegistry.register
54
+ class GEOSource(EntrezSummarySource):
55
+ """Fetch GEO series and dataset summaries from Entrez."""
56
+
57
+ PREFIX = "GEO"
58
+ ENTREZ_DB = "gds"
59
+ TITLE_FIELDS = ("title", "description", "summary")
60
+ CONTENT_FIELDS = ("summary", "description", "title")
61
+ ID_PATTERNS = (r"^GSE\d+$", r"^GDS\d+$")
62
+
63
+ def fetch(
64
+ self, identifier: str, config: ReferenceValidationConfig
65
+ ) -> Optional[ReferenceContent]:
66
+ """Fetch GEO dataset metadata, converting accession to UID first."""
67
+ Entrez.email = config.email
68
+ time.sleep(config.rate_limit_delay)
69
+
70
+ # Convert accession to UID via esearch
71
+ uid = self._accession_to_uid(identifier)
72
+ if not uid:
73
+ logger.warning(f"Could not find GDS UID for {identifier}")
74
+ return None
75
+
76
+ # Now fetch summary with numeric UID
77
+ handle = None
78
+ try:
79
+ handle = Entrez.esummary(db=self.ENTREZ_DB, id=uid)
80
+ records = Entrez.read(handle)
81
+ except Exception as exc:
82
+ logger.warning(f"Failed to fetch Entrez summary for {self.prefix()}:{identifier}: {exc}")
83
+ return None
84
+ finally:
85
+ if handle is not None:
86
+ handle.close()
87
+
88
+ record = self._extract_record(records)
89
+ if not record:
90
+ logger.warning(f"No Entrez summary found for {self.prefix()}:{identifier}")
91
+ return None
92
+
93
+ title = self._get_first_field_value(record, self.TITLE_FIELDS)
94
+ content = self._get_first_field_value(record, self.CONTENT_FIELDS)
95
+ content_type = "summary" if content else "unavailable"
96
+
97
+ return ReferenceContent(
98
+ reference_id=f"{self.prefix()}:{identifier}",
99
+ title=title,
100
+ content=content,
101
+ content_type=content_type,
102
+ metadata={"entrez_db": self.ENTREZ_DB, "entrez_uid": uid},
103
+ )
104
+
105
+ def _accession_to_uid(self, accession: str) -> Optional[str]:
106
+ """Convert a GEO accession (GSE/GDS) to its Entrez UID."""
107
+ handle = None
108
+ try:
109
+ handle = Entrez.esearch(db=self.ENTREZ_DB, term=f"{accession}[Accession]")
110
+ result = Entrez.read(handle)
111
+ if result.get("IdList"):
112
+ return result["IdList"][0]
113
+ except Exception as exc:
114
+ logger.warning(f"esearch failed for {accession}: {exc}")
115
+ finally:
116
+ if handle is not None:
117
+ handle.close()
118
+ return None
119
+ ```
120
+
121
+ ## Testing
122
+
123
+ After the fix, validation should catch title mismatches like:
124
+
125
+ ```yaml
126
+ # In kb/disorders/Asthma.yaml
127
+ datasets:
128
+ - accession: geo:GSE67472
129
+ title: xxxAirway epithelial gene expression in asthma versus healthy controls # Wrong!
130
+ ```
131
+
132
+ Expected validation error:
133
+ ```
134
+ [ERROR] Title mismatch for geo:GSE67472
135
+ Expected: "Airway epithelial gene expression in asthma versus healthy controls"
136
+ Found: "xxxAirway epithelial gene expression in asthma versus healthy controls"
137
+ ```
@@ -0,0 +1,258 @@
1
+ # Bug Report: Zenodo DOIs and bare HTTPS URLs not handled
2
+
3
+ ## Summary
4
+
5
+ Two related bugs prevent fetching Zenodo DOIs and bare HTTPS URLs:
6
+
7
+ 1. **Zenodo DOIs return 404**: DOIs like `DOI:10.5281/zenodo.17993529` fail because the DOI source only queries Crossref, but Zenodo DOIs are registered with DataCite.
8
+
9
+ 2. **Bare HTTPS URLs not recognized**: URLs like `https://doi.org/10.5281/zenodo.17993529` fail because the prefix parser extracts `https` as the prefix, which gets uppercased to `HTTPS` and doesn't match any source.
10
+
11
+ ## Reproduction
12
+
13
+ ```bash
14
+ # Both of these fail:
15
+ linkml-reference-validator lookup --no-cache https://doi.org/10.5281/zenodo.17993529
16
+ # Output: No source found for reference type: HTTPS://doi.org/10.5281/zenodo.17993529
17
+
18
+ linkml-reference-validator lookup --no-cache DOI:10.5281/zenodo.17993529
19
+ # Output: Failed to fetch DOI:10.5281/zenodo.17993529 - status 404
20
+ ```
21
+
22
+ ## Root Cause Analysis
23
+
24
+ ### Bug 1: URL Prefix Handling
25
+
26
+ In `src/linkml_reference_validator/etl/reference_fetcher.py`:
27
+
28
+ ```python
29
+ # Line 141 - regex parses https://example.com as:
30
+ # prefix = "https"
31
+ # identifier = "//example.com"
32
+ match = re.match(r"^([A-Za-z_]+)[:\s]+(.+)$", stripped)
33
+
34
+ # Lines 174-178 - prefix normalization uppercases non-file/url prefixes:
35
+ def _normalize_prefix(self, prefix: str) -> str:
36
+ if prefix.lower() in ("file", "url"):
37
+ return prefix.lower()
38
+ return prefix.upper() # "https" becomes "HTTPS"
39
+ ```
40
+
41
+ No source is registered for `HTTPS:`, so lookup fails.
42
+
43
+ ### Bug 2: Crossref-only DOI Resolution
44
+
45
+ In `src/linkml_reference_validator/etl/sources/doi.py`:
46
+
47
+ ```python
48
+ # Line 72 - only queries Crossref API
49
+ url = f"https://api.crossref.org/works/{doi}"
50
+ ```
51
+
52
+ Zenodo DOIs (prefix `10.5281/zenodo.*`) are registered with **DataCite**, not Crossref. Crossref returns 404 for these DOIs.
53
+
54
+ ## Solution
55
+
56
+ ### Fix 1: Handle bare HTTP/HTTPS URLs
57
+
58
+ In `reference_fetcher.py`, add detection for bare URLs before the prefix regex matching:
59
+
60
+ ```python
61
+ def _parse_reference_id(self, reference_id: str) -> tuple[str, str]:
62
+ stripped = reference_id.strip()
63
+
64
+ # NEW: Handle bare URLs (http:// or https://)
65
+ if stripped.lower().startswith(("http://", "https://")):
66
+ return "url", stripped
67
+
68
+ # Existing prefix:identifier parsing...
69
+ match = re.match(r"^([A-Za-z_]+)[:\s]+(.+)$", stripped)
70
+ # ... rest of method
71
+ ```
72
+
73
+ ### Fix 2: Add DataCite fallback for DOIs
74
+
75
+ In `doi.py`, try Crossref first, then fall back to DataCite if 404:
76
+
77
+ ```python
78
+ def fetch(
79
+ self, identifier: str, config: ReferenceValidationConfig
80
+ ) -> Optional[ReferenceContent]:
81
+ doi = identifier.strip()
82
+ time.sleep(config.rate_limit_delay)
83
+
84
+ # Try Crossref first
85
+ result = self._fetch_from_crossref(doi, config)
86
+ if result:
87
+ return result
88
+
89
+ # Fall back to DataCite (handles Zenodo, Figshare, etc.)
90
+ return self._fetch_from_datacite(doi, config)
91
+
92
+ def _fetch_from_crossref(self, doi: str, config: ReferenceValidationConfig) -> Optional[ReferenceContent]:
93
+ """Fetch from Crossref API."""
94
+ url = f"https://api.crossref.org/works/{doi}"
95
+ headers = {
96
+ "User-Agent": f"linkml-reference-validator/1.0 (mailto:{config.email})",
97
+ }
98
+
99
+ response = requests.get(url, headers=headers, timeout=30)
100
+ if response.status_code != 200:
101
+ logger.debug(f"Crossref returned {response.status_code} for DOI:{doi}, will try DataCite")
102
+ return None
103
+
104
+ data = response.json()
105
+ if data.get("status") != "ok":
106
+ return None
107
+
108
+ message = data.get("message", {})
109
+ return self._parse_crossref_response(doi, message)
110
+
111
+ def _fetch_from_datacite(self, doi: str, config: ReferenceValidationConfig) -> Optional[ReferenceContent]:
112
+ """Fetch from DataCite API (handles Zenodo, Figshare, Dryad, etc.)."""
113
+ url = f"https://api.datacite.org/dois/{doi}"
114
+ headers = {
115
+ "User-Agent": f"linkml-reference-validator/1.0 (mailto:{config.email})",
116
+ "Accept": "application/json",
117
+ }
118
+
119
+ response = requests.get(url, headers=headers, timeout=30)
120
+ if response.status_code != 200:
121
+ logger.warning(f"Failed to fetch DOI:{doi} from both Crossref and DataCite")
122
+ return None
123
+
124
+ data = response.json()
125
+ attributes = data.get("data", {}).get("attributes", {})
126
+
127
+ # Extract title
128
+ titles = attributes.get("titles", [])
129
+ title = titles[0].get("title", "") if titles else ""
130
+
131
+ # Extract authors
132
+ authors = self._parse_datacite_creators(attributes.get("creators", []))
133
+
134
+ # Extract year
135
+ year = str(attributes.get("publicationYear", ""))
136
+
137
+ # Extract abstract from descriptions
138
+ abstract = ""
139
+ for desc in attributes.get("descriptions", []):
140
+ if desc.get("descriptionType") == "Abstract":
141
+ abstract = desc.get("description", "")
142
+ break
143
+
144
+ # Publisher as journal equivalent
145
+ publisher = attributes.get("publisher", "")
146
+
147
+ return ReferenceContent(
148
+ reference_id=f"DOI:{doi}",
149
+ title=title,
150
+ content=abstract if abstract else None,
151
+ content_type="abstract_only" if abstract else "unavailable",
152
+ authors=authors,
153
+ journal=publisher,
154
+ year=year,
155
+ doi=doi,
156
+ )
157
+
158
+ def _parse_datacite_creators(self, creators: list) -> list[str]:
159
+ """Parse creator list from DataCite response."""
160
+ result = []
161
+ for creator in creators:
162
+ # DataCite may have full name or given/family
163
+ name = creator.get("name")
164
+ if not name:
165
+ given = creator.get("givenName", "")
166
+ family = creator.get("familyName", "")
167
+ if given and family:
168
+ name = f"{given} {family}"
169
+ elif family:
170
+ name = family
171
+ elif given:
172
+ name = given
173
+ if name:
174
+ result.append(name)
175
+ return result
176
+ ```
177
+
178
+ ## DataCite API Response Structure
179
+
180
+ The DataCite API at `https://api.datacite.org/dois/{doi}` returns:
181
+
182
+ ```json
183
+ {
184
+ "data": {
185
+ "attributes": {
186
+ "doi": "10.5281/zenodo.17993529",
187
+ "titles": [{"title": "Gene Ontology Curators AI Workshop (Part 1)"}],
188
+ "creators": [
189
+ {
190
+ "name": "Mungall, Christopher",
191
+ "givenName": "Christopher",
192
+ "familyName": "Mungall",
193
+ "affiliation": [{"name": "Lawrence Berkeley National Laboratory"}]
194
+ }
195
+ ],
196
+ "publicationYear": 2025,
197
+ "publisher": "Zenodo",
198
+ "descriptions": [
199
+ {
200
+ "description": "Workshop aims to equip curators with...",
201
+ "descriptionType": "Abstract"
202
+ }
203
+ ]
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ ## Test Cases
210
+
211
+ Add tests for both fixes:
212
+
213
+ ```python
214
+ @pytest.mark.integration
215
+ def test_zenodo_doi():
216
+ """Test that Zenodo DOIs are fetched via DataCite."""
217
+ config = ReferenceValidationConfig()
218
+ fetcher = ReferenceFetcher(config)
219
+ ref = fetcher.fetch("DOI:10.5281/zenodo.17993529", force_refresh=True)
220
+ assert ref is not None
221
+ assert "Gene Ontology" in ref.title
222
+ assert ref.year == "2025"
223
+
224
+ @pytest.mark.integration
225
+ def test_bare_https_url():
226
+ """Test that bare HTTPS URLs are handled."""
227
+ config = ReferenceValidationConfig()
228
+ fetcher = ReferenceFetcher(config)
229
+ ref = fetcher.fetch("https://example.com", force_refresh=True)
230
+ assert ref is not None
231
+ assert ref.reference_id == "url:https://example.com"
232
+
233
+ def test_parse_bare_url():
234
+ """Test URL parsing for bare HTTP/HTTPS URLs."""
235
+ config = ReferenceValidationConfig()
236
+ fetcher = ReferenceFetcher(config)
237
+
238
+ assert fetcher._parse_reference_id("https://example.com") == ("url", "https://example.com")
239
+ assert fetcher._parse_reference_id("http://example.com/path") == ("url", "http://example.com/path")
240
+ assert fetcher._parse_reference_id("url:https://example.com") == ("url", "https://example.com")
241
+ ```
242
+
243
+ ## Files to Modify
244
+
245
+ 1. `src/linkml_reference_validator/etl/reference_fetcher.py` - Add bare URL detection
246
+ 2. `src/linkml_reference_validator/etl/sources/doi.py` - Add DataCite fallback
247
+ 3. `tests/test_reference_fetcher.py` - Add unit tests
248
+ 4. `tests/test_doi_source.py` or similar - Add integration tests
249
+
250
+ ## Verification
251
+
252
+ After fixes, these should work:
253
+
254
+ ```bash
255
+ linkml-reference-validator lookup DOI:10.5281/zenodo.17993529
256
+ linkml-reference-validator lookup https://doi.org/10.5281/zenodo.17993529
257
+ linkml-reference-validator lookup https://zenodo.org/records/17993529
258
+ ```
@@ -0,0 +1,31 @@
1
+ # CLI Issues
2
+
3
+ ## 1) `validate data` summary label is misleading
4
+
5
+ **Current behavior**
6
+ The CLI prints:
7
+ ```
8
+ Validation Summary:
9
+ Total checks: <N>
10
+ ```
11
+ where `<N>` is the **number of issues** (`len(all_results)`), not the number of checks performed. This makes successful runs look like they performed zero checks (e.g., “Total checks: 0”), which is confusing.
12
+
13
+ **Where**
14
+ `linkml_reference_validator/cli/validate.py` (near the end of `validate data` command).
15
+
16
+ **Repro**
17
+ Run with a file that has valid evidence/snippets and get:
18
+ ```
19
+ Validation Summary:
20
+ Total checks: 0
21
+ All validations passed!
22
+ ```
23
+
24
+ **Suggested fix**
25
+ Either:
26
+ - Rename the label to **“Total issues”**, or
27
+ - Add two counters:
28
+ - **Checks performed** (number of excerpt/reference pairs found)
29
+ - **Issues found** (`len(all_results)`)
30
+
31
+ This would make the summary accurate and less surprising.