calkit-python 0.41.20__tar.gz → 0.41.21__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 (356) hide show
  1. calkit_python-0.41.21/.gitmodules +3 -0
  2. {calkit_python-0.41.20 → calkit_python-0.41.21}/PKG-INFO +8 -7
  3. {calkit_python-0.41.20 → calkit_python-0.41.21}/README.md +7 -6
  4. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/list.py +35 -1
  5. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/main/core.py +13 -0
  6. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/new.py +4 -8
  7. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/overleaf.py +12 -2
  8. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/detect.py +102 -25
  9. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/git.py +29 -0
  10. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/models/core.py +11 -1
  11. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/main/test_core.py +67 -0
  12. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_list.py +41 -1
  13. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_new.py +14 -19
  14. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_overleaf.py +47 -0
  15. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/models/test_core.py +22 -1
  16. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_detect.py +59 -2
  17. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/calkit-yaml.md +1 -1
  18. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/cli-reference.md +3 -3
  19. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/index.md +7 -6
  20. calkit_python-0.41.21/docs/questions.md +31 -0
  21. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/latex-codespaces.md +27 -4
  22. {calkit_python-0.41.20 → calkit_python-0.41.21}/mkdocs.yml +1 -0
  23. {calkit_python-0.41.20 → calkit_python-0.41.21}/pyproject.toml +0 -1
  24. {calkit_python-0.41.20 → calkit_python-0.41.21}/.claude-plugin/marketplace.json +0 -0
  25. {calkit_python-0.41.20 → calkit_python-0.41.21}/.gitattributes +0 -0
  26. {calkit_python-0.41.20 → calkit_python-0.41.21}/.gitignore +0 -0
  27. {calkit_python-0.41.20 → calkit_python-0.41.21}/.pre-commit-config.yaml +0 -0
  28. {calkit_python-0.41.20 → calkit_python-0.41.21}/.prettierignore +0 -0
  29. {calkit_python-0.41.20 → calkit_python-0.41.21}/.prettierrc.json +0 -0
  30. {calkit_python-0.41.20 → calkit_python-0.41.21}/.python-version +0 -0
  31. {calkit_python-0.41.20 → calkit_python-0.41.21}/.vscode/launch.json +0 -0
  32. {calkit_python-0.41.20 → calkit_python-0.41.21}/.vscode/tasks.json +0 -0
  33. {calkit_python-0.41.20 → calkit_python-0.41.21}/AGENTS.md +0 -0
  34. {calkit_python-0.41.20 → calkit_python-0.41.21}/CITATION.cff +0 -0
  35. {calkit_python-0.41.20 → calkit_python-0.41.21}/CODE_OF_CONDUCT.md +0 -0
  36. {calkit_python-0.41.20 → calkit_python-0.41.21}/CONTRIBUTING.md +0 -0
  37. {calkit_python-0.41.20 → calkit_python-0.41.21}/LICENSE +0 -0
  38. {calkit_python-0.41.20 → calkit_python-0.41.21}/Makefile +0 -0
  39. {calkit_python-0.41.20 → calkit_python-0.41.21}/agent-plugin/.claude-plugin/plugin.json +0 -0
  40. {calkit_python-0.41.20 → calkit_python-0.41.21}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
  41. {calkit_python-0.41.20 → calkit_python-0.41.21}/agent-plugin/skills/conventions/SKILL.md +0 -0
  42. {calkit_python-0.41.20 → calkit_python-0.41.21}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
  43. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/__init__.py +0 -0
  44. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/__main__.py +0 -0
  45. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/calc.py +0 -0
  46. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/check.py +0 -0
  47. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/__init__.py +0 -0
  48. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/check.py +0 -0
  49. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/cloud.py +0 -0
  50. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/config.py +0 -0
  51. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/core.py +0 -0
  52. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/delete.py +0 -0
  53. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/describe.py +0 -0
  54. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/dev.py +0 -0
  55. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/import_.py +0 -0
  56. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/latex.py +0 -0
  57. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/main/__init__.py +0 -0
  58. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/main/xr.py +0 -0
  59. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/notebooks.py +0 -0
  60. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/office.py +0 -0
  61. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/scheduler.py +0 -0
  62. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cli/update.py +0 -0
  63. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/cloud.py +0 -0
  64. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/conda.py +0 -0
  65. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/config.py +0 -0
  66. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/core.py +0 -0
  67. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/datasets.py +0 -0
  68. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/dependencies.py +0 -0
  69. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/docker.py +0 -0
  70. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/dvc/__init__.py +0 -0
  71. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/dvc/core.py +0 -0
  72. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/dvc/zip.py +0 -0
  73. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/environments.py +0 -0
  74. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/fs.py +0 -0
  75. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/github.py +0 -0
  76. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/gui.py +0 -0
  77. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/install.py +0 -0
  78. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/invenio.py +0 -0
  79. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/julia.py +0 -0
  80. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/jupyter.py +0 -0
  81. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/jupyterlab/__init__.py +0 -0
  82. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/jupyterlab/routes.py +0 -0
  83. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/package.json +0 -0
  84. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  85. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  86. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  87. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  88. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  89. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  90. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  91. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  92. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  93. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/remoteEntry.ac9035764d5b2adbb542.js +0 -0
  94. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/style.js +0 -0
  95. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/labextension/static/third-party-licenses.json +0 -0
  96. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/licenses.py +0 -0
  97. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/magics.py +0 -0
  98. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/matlab.py +0 -0
  99. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/models/__init__.py +0 -0
  100. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/models/io.py +0 -0
  101. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/models/iteration.py +0 -0
  102. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/models/pipeline.py +0 -0
  103. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/notebooks.py +0 -0
  104. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/office.py +0 -0
  105. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/ops.py +0 -0
  106. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/overleaf.py +0 -0
  107. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/pipeline.py +0 -0
  108. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/releases.py +0 -0
  109. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/server.py +0 -0
  110. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/__init__.py +0 -0
  111. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/core.py +0 -0
  112. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/__init__.py +0 -0
  113. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/article/paper.tex +0 -0
  114. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/core.py +0 -0
  115. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/jfm/jfm.bst +0 -0
  116. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/jfm/jfm.cls +0 -0
  117. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  118. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/jfm/paper.tex +0 -0
  119. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/templates/latex/jfm/upmath.sty +0 -0
  120. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/__init__.py +0 -0
  121. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/__init__.py +0 -0
  122. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/main/__init__.py +0 -0
  123. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/main/test_lock.py +0 -0
  124. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/main/test_subprojects.py +0 -0
  125. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/main/test_xr.py +0 -0
  126. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_check.py +0 -0
  127. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_cloud.py +0 -0
  128. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_config.py +0 -0
  129. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_delete.py +0 -0
  130. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_import.py +0 -0
  131. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_latex.py +0 -0
  132. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_notebooks.py +0 -0
  133. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_scheduler.py +0 -0
  134. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/cli/test_update.py +0 -0
  135. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/dvc/__init__.py +0 -0
  136. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/dvc/test_core.py +0 -0
  137. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/dvc/test_zip.py +0 -0
  138. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/jupyterlab/__init__.py +0 -0
  139. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/jupyterlab/conftest.py +0 -0
  140. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/jupyterlab/test_routes.py +0 -0
  141. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/models/__init__.py +0 -0
  142. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/models/test_iteration.py +0 -0
  143. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/models/test_pipeline.py +0 -0
  144. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_calc.py +0 -0
  145. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_check.py +0 -0
  146. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_cloud.py +0 -0
  147. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_conda.py +0 -0
  148. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_core.py +0 -0
  149. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_dependencies.py +0 -0
  150. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_docker.py +0 -0
  151. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_environments.py +0 -0
  152. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_fs.py +0 -0
  153. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_git.py +0 -0
  154. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_install.py +0 -0
  155. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_invenio.py +0 -0
  156. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_julia.py +0 -0
  157. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_jupyter.py +0 -0
  158. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_licenses.py +0 -0
  159. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_magics.py +0 -0
  160. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_matlab.py +0 -0
  161. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_notebooks.py +0 -0
  162. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_pipeline.py +0 -0
  163. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_releases.py +0 -0
  164. {calkit_python-0.41.20 → calkit_python-0.41.21}/calkit/tests/test_templates.py +0 -0
  165. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/CNAME +0 -0
  166. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/acknowledgements.md +0 -0
  167. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/ai-tools.md +0 -0
  168. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/apps.md +0 -0
  169. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/calculations.md +0 -0
  170. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/cloud-integration.md +0 -0
  171. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/datasets.md +0 -0
  172. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/dependencies.md +0 -0
  173. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/environments.md +0 -0
  174. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/examples.md +0 -0
  175. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/governance.md +0 -0
  176. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/help.md +0 -0
  177. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/hpc.md +0 -0
  178. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/c-to-the-k-white.svg +0 -0
  179. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/calkit-fragmentation-compendium.png +0 -0
  180. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/calkit-no-bg.png +0 -0
  181. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/caltech.png +0 -0
  182. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/connect-zenodo.png +0 -0
  183. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab/all-green.png +0 -0
  184. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  185. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab/new-env.png +0 -0
  186. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab/new-notebook.png +0 -0
  187. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  188. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/jupyterlab-params.png +0 -0
  189. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/pipeline.png +0 -0
  190. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/plos-osi-code-2024-03.png +0 -0
  191. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/schmidt-sciences.png +0 -0
  192. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/img/vscode-nb-params.png +0 -0
  193. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/installation.md +0 -0
  194. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/jupyterlab.md +0 -0
  195. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/local-server.md +0 -0
  196. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/notebooks.md +0 -0
  197. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/overleaf.md +0 -0
  198. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/pipeline/index.md +0 -0
  199. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/pipeline/manual-steps.md +0 -0
  200. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/pipeline/running-and-logging.md +0 -0
  201. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/quickstart.md +0 -0
  202. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/references.md +0 -0
  203. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/releases.md +0 -0
  204. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/reproducibility.md +0 -0
  205. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/subprojects.md +0 -0
  206. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  207. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/ai-agents.md +0 -0
  208. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/conda-envs.md +0 -0
  209. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/existing-project.md +0 -0
  210. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/first-project.md +0 -0
  211. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/github-actions.md +0 -0
  212. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  213. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  214. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  215. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  216. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  217. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  218. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  219. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  220. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  221. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  222. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  223. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  224. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  225. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  226. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  227. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/create-project.png +0 -0
  228. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  229. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/excel-chart.png +0 -0
  230. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/excel-data.png +0 -0
  231. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  232. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/needs-clone.png +0 -0
  233. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/new-stage.png +0 -0
  234. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  235. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  236. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/status-more-rows.png +0 -0
  237. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  238. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/untracked-data.png +0 -0
  239. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/updated-publication.png +0 -0
  240. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  241. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/office/workflow-page.png +0 -0
  242. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/clone.png +0 -0
  243. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/create-project.png +0 -0
  244. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  245. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  246. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  247. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/new-token.png +0 -0
  248. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/reclone.png +0 -0
  249. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  250. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/quick-actions.png +0 -0
  251. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/run-proc.png +0 -0
  252. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
  253. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
  254. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
  255. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
  256. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
  257. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
  258. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
  259. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/index.md +0 -0
  260. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/jupyterlab.md +0 -0
  261. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/matlab.md +0 -0
  262. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/notebook-pipeline.md +0 -0
  263. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/office.md +0 -0
  264. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/openfoam.md +0 -0
  265. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/procedures.md +0 -0
  266. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/tutorials/vscode-slurm-notebook.md +0 -0
  267. {calkit_python-0.41.20 → calkit_python-0.41.21}/docs/version-control.md +0 -0
  268. {calkit_python-0.41.20 → calkit_python-0.41.21}/flake.lock +0 -0
  269. {calkit_python-0.41.20 → calkit_python-0.41.21}/flake.nix +0 -0
  270. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/.yarnrc.yml +0 -0
  271. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/babel.config.js +0 -0
  272. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/install.json +0 -0
  273. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/jest.config.js +0 -0
  274. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/jupyter-config/server-config/calkit.json +0 -0
  275. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/package.json +0 -0
  276. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/schema/plugin.json +0 -0
  277. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/__tests__/useQueries.spec.ts +0 -0
  278. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/calkit-config.ts +0 -0
  279. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/cell-output-marker.ts +0 -0
  280. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/commit-dialog.tsx +0 -0
  281. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/environment-editor.tsx +0 -0
  282. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/notebook-registration.tsx +0 -0
  283. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/notebook-toolbar.tsx +0 -0
  284. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/pipeline-status-bar.tsx +0 -0
  285. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/project-info-editor.tsx +0 -0
  286. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/sidebar-settings.tsx +0 -0
  287. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/sidebar.tsx +0 -0
  288. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/components/stage-editor.tsx +0 -0
  289. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/feature-flags.ts +0 -0
  290. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/file-browser-menu.ts +0 -0
  291. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/hooks/__tests__/useQueries.test.tsx +0 -0
  292. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/hooks/useQueries.ts +0 -0
  293. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/icons.ts +0 -0
  294. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/index.ts +0 -0
  295. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/io-tracker.ts +0 -0
  296. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/pipeline-state.ts +0 -0
  297. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/queryClient.ts +0 -0
  298. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/request.ts +0 -0
  299. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/src/shims-mainmenu.d.ts +0 -0
  300. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/base.css +0 -0
  301. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/cell-output-marker.css +0 -0
  302. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/environment-editor.css +0 -0
  303. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/environment-selector.css +0 -0
  304. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/img/calkit-no-bg.png +0 -0
  305. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/index.css +0 -0
  306. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/index.js +0 -0
  307. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/notebook-toolbar.css +0 -0
  308. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/pipeline-status-bar.css +0 -0
  309. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/style/sidebar.css +0 -0
  310. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/tsconfig.json +0 -0
  311. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/tsconfig.test.json +0 -0
  312. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/.gitignore +0 -0
  313. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/README.md +0 -0
  314. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/jupyter_server_test_config.py +0 -0
  315. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/package.json +0 -0
  316. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/playwright.config.js +0 -0
  317. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/tests/calkit.spec.ts +0 -0
  318. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/ui-tests/yarn.lock +0 -0
  319. {calkit_python-0.41.20 → calkit_python-0.41.21}/jupyterlab-ext/yarn.lock +0 -0
  320. {calkit_python-0.41.20 → calkit_python-0.41.21}/scripts/generate-docs-references.py +0 -0
  321. {calkit_python-0.41.20 → calkit_python-0.41.21}/scripts/install.ps1 +0 -0
  322. {calkit_python-0.41.20 → calkit_python-0.41.21}/scripts/install.sh +0 -0
  323. {calkit_python-0.41.20 → calkit_python-0.41.21}/scripts/make-calk9.sh +0 -0
  324. {calkit_python-0.41.20 → calkit_python-0.41.21}/scripts/sync-docs.py +0 -0
  325. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  326. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/nb-julia.ipynb +0 -0
  327. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/nb-params.ipynb +0 -0
  328. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/nb-subdir.ipynb +0 -0
  329. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/pipeline.ipynb +0 -0
  330. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/script.py +0 -0
  331. {calkit_python-0.41.20 → calkit_python-0.41.21}/test/test-log.log +0 -0
  332. {calkit_python-0.41.20 → calkit_python-0.41.21}/uv.lock +0 -0
  333. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/.gitignore +0 -0
  334. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/.vscodeignore +0 -0
  335. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/CHANGELOG.md +0 -0
  336. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/LICENSE +0 -0
  337. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/README.md +0 -0
  338. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/images/calkit-icon.svg +0 -0
  339. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/images/calkit-no-bg.png +0 -0
  340. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/package-lock.json +0 -0
  341. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/package.json +0 -0
  342. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/scripts/set-proposed-api.js +0 -0
  343. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/environments.ts +0 -0
  344. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/extension.ts +0 -0
  345. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/figures/core.ts +0 -0
  346. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/figures/view.ts +0 -0
  347. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/notebooks.ts +0 -0
  348. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/pipeline/core.ts +0 -0
  349. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/sidebar.ts +0 -0
  350. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/test/environments.test.ts +0 -0
  351. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/test/figures.test.ts +0 -0
  352. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
  353. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/test/notebooks.test.ts +0 -0
  354. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/test/pipeline.test.ts +0 -0
  355. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/src/types.ts +0 -0
  356. {calkit_python-0.41.20 → calkit_python-0.41.21}/vscode-ext/tsconfig.json +0 -0
@@ -0,0 +1,3 @@
1
+ [submodule "examples/basic"]
2
+ path = examples/basic
3
+ url = https://github.com/calkit/example-basic.git
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.41.20
3
+ Version: 0.41.21
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -78,15 +78,16 @@ Description-Content-Type: text/markdown
78
78
 
79
79
  <!-- INCLUDE: docs/index.md -->
80
80
 
81
- It's six months since you submitted your paper,
82
- do you know exactly how your figures and results were generated?
83
-
84
- You will if they're part of a Calkit project.
81
+ Typical research workflows are horizontally-siloed, i.e.,
82
+ various stages--data collection, analysis, writing--are performed in
83
+ disconnected systems,
84
+ turning research into a slow, error-prone, and tedious
85
+ [waterfall](https://en.wikipedia.org/wiki/Waterfall_model) process.
85
86
 
86
87
  Calkit helps you integrate code, data, figures, results, publications,
87
88
  and more into a cohesive, traceable, and portable _knowledge creation system_,
88
- so every output can be traced back to its source and reproduced with a
89
- single command.
89
+ so every output can be traced back to its source (provenance)
90
+ and reproduced with a single command.
90
91
 
91
92
  With industry standard tools combined into a unified and simplified experience
92
93
  tailored for research,
@@ -19,15 +19,16 @@
19
19
 
20
20
  <!-- INCLUDE: docs/index.md -->
21
21
 
22
- It's six months since you submitted your paper,
23
- do you know exactly how your figures and results were generated?
24
-
25
- You will if they're part of a Calkit project.
22
+ Typical research workflows are horizontally-siloed, i.e.,
23
+ various stages--data collection, analysis, writing--are performed in
24
+ disconnected systems,
25
+ turning research into a slow, error-prone, and tedious
26
+ [waterfall](https://en.wikipedia.org/wiki/Waterfall_model) process.
26
27
 
27
28
  Calkit helps you integrate code, data, figures, results, publications,
28
29
  and more into a cohesive, traceable, and portable _knowledge creation system_,
29
- so every output can be traced back to its source and reproduced with a
30
- single command.
30
+ so every output can be traced back to its source (provenance)
31
+ and reproduced with a single command.
31
32
 
32
33
  With industry standard tools combined into a unified and simplified experience
33
34
  tailored for research,
@@ -174,6 +174,40 @@ def list_presentations(
174
174
  _list_artifacts("presentations", json_output, declared_only)
175
175
 
176
176
 
177
+ def _echo_question(n: int, question: str | dict) -> None:
178
+ """Print a single question in the human-readable YAML-ish listing format.
179
+
180
+ String questions are shown as a numbered line. Rich (dict) questions are
181
+ shown with their text on the first line followed by hypothesis, answer,
182
+ evidence, and any other fields in YAML-like indented form.
183
+ """
184
+ if isinstance(question, str):
185
+ typer.echo(f"{n}. {question}")
186
+ return
187
+ # Copy so popping 'question' doesn't mutate the caller's dict.
188
+ question = dict(question)
189
+ text = question.pop("question", "")
190
+ typer.echo(f"{n}. question: {text}")
191
+ for k, v in question.items():
192
+ if isinstance(v, dict):
193
+ typer.echo(f" {k}:")
194
+ for k1, v1 in v.items():
195
+ typer.echo(f" {k1}: {v1}")
196
+ elif isinstance(v, list):
197
+ typer.echo(f" {k}:")
198
+ for item in v:
199
+ if isinstance(item, dict):
200
+ for n1, (k1, v1) in enumerate(item.items()):
201
+ if n1 == 0:
202
+ typer.echo(f" - {k1}: {v1}")
203
+ else:
204
+ typer.echo(f" {k1}: {v1}")
205
+ else:
206
+ typer.echo(f" - {item}")
207
+ else:
208
+ typer.echo(f" {k}: {v}")
209
+
210
+
177
211
  @list_app.command(name="questions")
178
212
  def list_questions(
179
213
  json_output: Annotated[
@@ -186,7 +220,7 @@ def list_questions(
186
220
  typer.echo(json.dumps(questions))
187
221
  return
188
222
  for n, question in enumerate(questions, start=1):
189
- typer.echo(f"{n}. {question}")
223
+ _echo_question(n, question)
190
224
 
191
225
 
192
226
  @list_app.command(name="publications|pubs")
@@ -786,6 +786,8 @@ def add(
786
786
  elif to == "git":
787
787
  subprocess.call(["git", "add"] + paths)
788
788
  elif to == "dvc":
789
+ for path in paths:
790
+ calkit.git.ensure_dvc_pointer_is_not_ignored(repo, path)
789
791
  calkit.dvc.run_dvc_command(["add"] + paths)
790
792
  elif to == "dvc-zip":
791
793
  for path in paths:
@@ -899,6 +901,7 @@ def add(
899
901
  f"Adding {path} to DVC since it's already tracked "
900
902
  "with DVC"
901
903
  )
904
+ calkit.git.ensure_dvc_pointer_is_not_ignored(repo, path)
902
905
  calkit.dvc.run_dvc_command(["add", path])
903
906
  elif posix_path in pipeline_output_storage:
904
907
  # Respect storage explicitly set in the pipeline definition
@@ -943,6 +946,7 @@ def add(
943
946
  typer.echo(f"Would add {path} to DVC (per extension)")
944
947
  else:
945
948
  typer.echo(f"Adding {path} to DVC per its extension")
949
+ calkit.git.ensure_dvc_pointer_is_not_ignored(repo, path)
946
950
  calkit.dvc.run_dvc_command(["add", path])
947
951
  elif calkit.dvc.zip.is_zip_candidate(path):
948
952
  if dry_run:
@@ -963,6 +967,7 @@ def add(
963
967
  typer.echo(
964
968
  f"Adding {path} to DVC since it's greater than 1 MB"
965
969
  )
970
+ calkit.git.ensure_dvc_pointer_is_not_ignored(repo, path)
966
971
  calkit.dvc.run_dvc_command(["add", path])
967
972
  else:
968
973
  if dry_run:
@@ -3218,6 +3223,14 @@ def call_dvc(
3218
3223
  and DVC is not installed.
3219
3224
  """
3220
3225
  result = calkit.dvc.run_dvc_command(sys.argv[2:])
3226
+ if result != 0 and len(sys.argv) > 2 and sys.argv[2] == "add":
3227
+ typer.secho(
3228
+ "Hint: If DVC failed because a .dvc pointer file is git-ignored, "
3229
+ "use `calkit add --to=dvc <path>` instead so Calkit can "
3230
+ "automatically manage the gitignore exception.",
3231
+ fg=typer.colors.YELLOW,
3232
+ err=True,
3233
+ )
3221
3234
  sys.exit(result)
3222
3235
 
3223
3236
 
@@ -3174,7 +3174,7 @@ def new_release(
3174
3174
  detected_kind, artifact = calkit.releases.find_artifact(path, ck_info)
3175
3175
  if detected_kind is None:
3176
3176
  detected_kind = calkit.detect.detect_artifact_kind(
3177
- pathlib.Path(path).as_posix()
3177
+ pathlib.Path(path).as_posix(),
3178
3178
  )
3179
3179
  if release_kind is None:
3180
3180
  if detected_kind is None:
@@ -3258,16 +3258,12 @@ def new_release(
3258
3258
  # stored as-is (renamed); folders and whole-project releases are
3259
3259
  # zipped.
3260
3260
  project_name = calkit.detect_project_name(prepend_owner=False)
3261
- if path == ".":
3261
+ if path == "." or os.path.isdir(path):
3262
3262
  stored_filename = f"{project_name}-{name}.zip"
3263
3263
  is_zip = True
3264
- elif os.path.isdir(path):
3265
- folder_name = os.path.basename(os.path.normpath(path))
3266
- stored_filename = f"{project_name}-{folder_name}-{name}.zip"
3267
- is_zip = True
3268
3264
  elif os.path.isfile(path):
3269
- stem, ext = os.path.splitext(os.path.basename(path))
3270
- stored_filename = f"{project_name}-{stem}-{name}{ext}"
3265
+ _, ext = os.path.splitext(os.path.basename(path))
3266
+ stored_filename = f"{project_name}-{name}{ext}"
3271
3267
  is_zip = False
3272
3268
  else:
3273
3269
  raise_error(f"Release path '{path}' does not exist")
@@ -437,7 +437,12 @@ def sync(
437
437
  raise_error("No Overleaf sync info found")
438
438
  overleaf_sync_dirs = list(overleaf_info.keys())
439
439
  if paths is not None:
440
- paths = [os.path.dirname(p) if os.path.isfile(p) else p for p in paths]
440
+ paths = [
441
+ (os.path.dirname(p) if os.path.isfile(p) else p)
442
+ .strip()
443
+ .rstrip("/\\")
444
+ for p in paths
445
+ ]
441
446
  for path in paths:
442
447
  if path not in overleaf_sync_dirs:
443
448
  raise_error(f"Path '{path}' is not synced with Overleaf")
@@ -611,7 +616,12 @@ def get_status(
611
616
  raise_error("No Overleaf sync info found")
612
617
  overleaf_sync_dirs = list(overleaf_info.keys())
613
618
  if paths is not None:
614
- paths = [os.path.dirname(p) if os.path.isfile(p) else p for p in paths]
619
+ paths = [
620
+ (os.path.dirname(p) if os.path.isfile(p) else p)
621
+ .strip()
622
+ .rstrip("/\\")
623
+ for p in paths
624
+ ]
615
625
  for path in paths:
616
626
  if path not in overleaf_sync_dirs:
617
627
  raise_error(f"Path '{path}' is not synced with Overleaf")
@@ -13,6 +13,8 @@ import sys
13
13
  from pathlib import Path
14
14
  from typing import Literal
15
15
 
16
+ import pathspec
17
+
16
18
  NotebookLanguage = Literal["python", "julia", "r"]
17
19
 
18
20
 
@@ -1549,6 +1551,20 @@ def _is_stdlib_module(module_name: str) -> bool:
1549
1551
  return base_module in common_stdlib
1550
1552
 
1551
1553
 
1554
+ _PYTHON_IMPORT_TO_DISTRIBUTION = {
1555
+ "sklearn": "scikit-learn",
1556
+ "skimage": "scikit-image",
1557
+ "cv2": "opencv-python",
1558
+ "PIL": "Pillow",
1559
+ "bs4": "beautifulsoup4",
1560
+ "yaml": "PyYAML",
1561
+ "dateutil": "python-dateutil",
1562
+ "dotenv": "python-dotenv",
1563
+ "serial": "pyserial",
1564
+ "git": "GitPython",
1565
+ }
1566
+
1567
+
1552
1568
  def detect_python_dependencies(
1553
1569
  script_path: str | None = None,
1554
1570
  code: str | None = None,
@@ -1614,13 +1630,21 @@ def detect_python_dependencies(
1614
1630
  if not _is_stdlib_module(module_name):
1615
1631
  # Get the top-level package name
1616
1632
  top_level = module_name.split(".")[0]
1617
- dependencies.add(top_level)
1633
+ dependencies.add(
1634
+ _PYTHON_IMPORT_TO_DISTRIBUTION.get(
1635
+ top_level, top_level
1636
+ )
1637
+ )
1618
1638
  elif isinstance(node, ast.ImportFrom):
1619
1639
  if node.module and node.level == 0:
1620
1640
  module_name = node.module
1621
1641
  if not _is_stdlib_module(module_name):
1622
1642
  top_level = module_name.split(".")[0]
1623
- dependencies.add(top_level)
1643
+ dependencies.add(
1644
+ _PYTHON_IMPORT_TO_DISTRIBUTION.get(
1645
+ top_level, top_level
1646
+ )
1647
+ )
1624
1648
  return sorted(list(dependencies))
1625
1649
 
1626
1650
 
@@ -1927,6 +1951,21 @@ PRESENTATION_NAMES = {
1927
1951
  "talk.pdf",
1928
1952
  }
1929
1953
 
1954
+ DETECTION_IGNORE_FPATH = os.path.join(".calkit", "ignore")
1955
+
1956
+
1957
+ def load_detection_ignore(wdir: str | None = None) -> pathspec.PathSpec | None:
1958
+ """Load ``.calkit/ignore`` artifact detection exclusions."""
1959
+ import pathspec
1960
+
1961
+ fpath = DETECTION_IGNORE_FPATH
1962
+ if wdir is not None:
1963
+ fpath = os.path.join(wdir, fpath)
1964
+ if not os.path.isfile(fpath):
1965
+ return None
1966
+ with open(fpath, encoding="utf-8") as f:
1967
+ return pathspec.PathSpec.from_lines("gitwildmatch", f)
1968
+
1930
1969
 
1931
1970
  def _ancestor_dir_names(rel_path: str) -> set[str]:
1932
1971
  """Lower-cased names of the ancestor directories of a "/"-separated path."""
@@ -1938,8 +1977,12 @@ def _path_ext(rel_path: str) -> str:
1938
1977
  return ("." + name.rsplit(".", 1)[-1].lower()) if "." in name else ""
1939
1978
 
1940
1979
 
1941
- def is_figure_path(rel_path: str) -> bool:
1980
+ def is_figure_path(
1981
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
1982
+ ) -> bool:
1942
1983
  """Whether a repo-relative path looks like an auto-detectable figure."""
1984
+ if ignore is not None and ignore.match_file(rel_path):
1985
+ return False
1943
1986
  ancestors = _ancestor_dir_names(rel_path)
1944
1987
  ext = _path_ext(rel_path)
1945
1988
  if ext in FIGURE_EXTENSIONS and ancestors & FIGURE_DIRS:
@@ -1951,37 +1994,49 @@ def is_figure_path(rel_path: str) -> bool:
1951
1994
  return False
1952
1995
 
1953
1996
 
1954
- def is_dataset_path(rel_path: str) -> bool:
1997
+ def is_dataset_path(
1998
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
1999
+ ) -> bool:
1955
2000
  """Whether a repo-relative path looks like an auto-detectable dataset."""
2001
+ if ignore is not None and ignore.match_file(rel_path):
2002
+ return False
1956
2003
  return (
1957
2004
  _path_ext(rel_path) in DATASET_EXTENSIONS
1958
2005
  and bool(_ancestor_dir_names(rel_path) & DATA_DIRS)
1959
- and not is_figure_path(rel_path)
2006
+ and not is_figure_path(rel_path, ignore=ignore)
1960
2007
  )
1961
2008
 
1962
2009
 
1963
- def is_result_path(rel_path: str) -> bool:
2010
+ def is_result_path(
2011
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
2012
+ ) -> bool:
1964
2013
  """Whether a repo-relative path looks like an auto-detectable result.
1965
2014
 
1966
2015
  A result is a data-like file (JSON, CSV, etc.) under a ``results``-style
1967
2016
  directory. Anything already detected as a figure or dataset is excluded.
1968
2017
  """
2018
+ if ignore is not None and ignore.match_file(rel_path):
2019
+ return False
1969
2020
  return (
1970
2021
  _path_ext(rel_path) in RESULT_EXTENSIONS
1971
2022
  and bool(_ancestor_dir_names(rel_path) & RESULT_DIRS)
1972
- and not is_figure_path(rel_path)
1973
- and not is_dataset_path(rel_path)
2023
+ and not is_figure_path(rel_path, ignore=ignore)
2024
+ and not is_dataset_path(rel_path, ignore=ignore)
1974
2025
  )
1975
2026
 
1976
2027
 
1977
- def is_presentation_path(rel_path: str) -> bool:
2028
+ def is_presentation_path(
2029
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
2030
+ ) -> bool:
1978
2031
  """Whether a repo-relative path looks like an auto-detectable presentation.
1979
2032
 
1980
2033
  Either a slide-deck file (PDF/PPTX/KEY) under a ``slides``/``presentations``
1981
2034
  directory, or a file with a presentation-like name (e.g. ``slides.pdf``,
1982
2035
  ``presentation.pdf``) anywhere. Figures are excluded.
1983
2036
  """
1984
- if is_figure_path(rel_path):
2037
+ if ignore is not None and ignore.match_file(rel_path):
2038
+ return False
2039
+ if is_figure_path(rel_path, ignore=ignore):
1985
2040
  return False
1986
2041
  name = rel_path.rsplit("/", 1)[-1].lower()
1987
2042
  if name in PRESENTATION_NAMES:
@@ -2018,7 +2073,9 @@ PUBLICATION_NAMES = {
2018
2073
  }
2019
2074
 
2020
2075
 
2021
- def is_publication_path(rel_path: str) -> bool:
2076
+ def is_publication_path(
2077
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
2078
+ ) -> bool:
2022
2079
  """Whether a repo-relative path looks like an auto-detectable publication.
2023
2080
 
2024
2081
  Either a document (PDF/TeX/DOCX) under a ``paper``/``publication``/
@@ -2026,7 +2083,11 @@ def is_publication_path(rel_path: str) -> bool:
2026
2083
  ``manuscript.pdf``, ``main.tex``) anywhere. Figures and presentations are
2027
2084
  excluded.
2028
2085
  """
2029
- if is_figure_path(rel_path) or is_presentation_path(rel_path):
2086
+ if ignore is not None and ignore.match_file(rel_path):
2087
+ return False
2088
+ if is_figure_path(rel_path, ignore=ignore) or is_presentation_path(
2089
+ rel_path, ignore=ignore
2090
+ ):
2030
2091
  return False
2031
2092
  name = rel_path.rsplit("/", 1)[-1].lower()
2032
2093
  if name in PUBLICATION_NAMES:
@@ -2036,19 +2097,21 @@ def is_publication_path(rel_path: str) -> bool:
2036
2097
  )
2037
2098
 
2038
2099
 
2039
- def detect_artifact_kind(rel_path: str) -> str | None:
2100
+ def detect_artifact_kind(
2101
+ rel_path: str, *, ignore: pathspec.PathSpec | None = None
2102
+ ) -> str | None:
2040
2103
  """Infer an artifact's release kind from its repo-relative path.
2041
2104
 
2042
2105
  Returns one of ``"figure"``, ``"presentation"``, ``"publication"``, or
2043
2106
  ``"dataset"``, or ``None`` if the path doesn't look like any of these.
2044
2107
  """
2045
- if is_figure_path(rel_path):
2108
+ if is_figure_path(rel_path, ignore=ignore):
2046
2109
  return "figure"
2047
- if is_presentation_path(rel_path):
2110
+ if is_presentation_path(rel_path, ignore=ignore):
2048
2111
  return "presentation"
2049
- if is_publication_path(rel_path):
2112
+ if is_publication_path(rel_path, ignore=ignore):
2050
2113
  return "publication"
2051
- if is_dataset_path(rel_path):
2114
+ if is_dataset_path(rel_path, ignore=ignore):
2052
2115
  return "dataset"
2053
2116
  return None
2054
2117
 
@@ -2090,6 +2153,7 @@ def _collapse_dataset_folders(paths: list[str]) -> list[str]:
2090
2153
  def detect_figures(
2091
2154
  candidate_paths: list[str],
2092
2155
  reserved_paths: list[str] | tuple[str, ...] = (),
2156
+ ignore: pathspec.PathSpec | None = None,
2093
2157
  ) -> list[str]:
2094
2158
  """Auto-detected figure paths among ``candidate_paths``.
2095
2159
 
@@ -2103,7 +2167,7 @@ def detect_figures(
2103
2167
  for p in candidate_paths
2104
2168
  if not _is_hidden_path(p)
2105
2169
  and not _is_under_any_dir(p, reserved)
2106
- and is_figure_path(p)
2170
+ and is_figure_path(p, ignore=ignore)
2107
2171
  }
2108
2172
  )
2109
2173
 
@@ -2112,6 +2176,7 @@ def detect_datasets(
2112
2176
  candidate_paths: list[str],
2113
2177
  reserved_paths: list[str] | tuple[str, ...] = (),
2114
2178
  figure_paths: list[str] | tuple[str, ...] = (),
2179
+ ignore: pathspec.PathSpec | None = None,
2115
2180
  ) -> list[str]:
2116
2181
  """Auto-detected dataset paths among ``candidate_paths``.
2117
2182
 
@@ -2126,7 +2191,7 @@ def detect_datasets(
2126
2191
  if not _is_hidden_path(p)
2127
2192
  and not _is_under_any_dir(p, reserved)
2128
2193
  and p not in figset
2129
- and is_dataset_path(p)
2194
+ and is_dataset_path(p, ignore=ignore)
2130
2195
  }
2131
2196
  return _collapse_dataset_folders(sorted(files))
2132
2197
 
@@ -2134,6 +2199,7 @@ def detect_datasets(
2134
2199
  def detect_results(
2135
2200
  candidate_paths: list[str],
2136
2201
  reserved_paths: list[str] | tuple[str, ...] = (),
2202
+ ignore: pathspec.PathSpec | None = None,
2137
2203
  ) -> list[str]:
2138
2204
  """Auto-detected result paths among ``candidate_paths``.
2139
2205
 
@@ -2147,7 +2213,7 @@ def detect_results(
2147
2213
  for p in candidate_paths
2148
2214
  if not _is_hidden_path(p)
2149
2215
  and not _is_under_any_dir(p, reserved)
2150
- and is_result_path(p)
2216
+ and is_result_path(p, ignore=ignore)
2151
2217
  }
2152
2218
  )
2153
2219
 
@@ -2155,6 +2221,7 @@ def detect_results(
2155
2221
  def detect_presentations(
2156
2222
  candidate_paths: list[str],
2157
2223
  reserved_paths: list[str] | tuple[str, ...] = (),
2224
+ ignore: pathspec.PathSpec | None = None,
2158
2225
  ) -> list[str]:
2159
2226
  """Auto-detected presentation paths among ``candidate_paths``."""
2160
2227
  reserved = list(reserved_paths)
@@ -2164,7 +2231,7 @@ def detect_presentations(
2164
2231
  for p in candidate_paths
2165
2232
  if not _is_hidden_path(p)
2166
2233
  and not _is_under_any_dir(p, reserved)
2167
- and is_presentation_path(p)
2234
+ and is_presentation_path(p, ignore=ignore)
2168
2235
  }
2169
2236
  )
2170
2237
 
@@ -2254,18 +2321,28 @@ def detect_project_artifacts(
2254
2321
 
2255
2322
  if ck_info is None:
2256
2323
  ck_info = calkit.load_calkit_info(wdir=wdir)
2324
+ ignore = load_detection_ignore(wdir=wdir)
2257
2325
  reserved = _reserved_artifact_paths(wdir=wdir, ck_info=ck_info)
2258
2326
  candidates = list(
2259
2327
  dict.fromkeys(
2260
2328
  [*list_repo_files(wdir=wdir), *list_dvc_tracked_files(wdir=wdir)]
2261
2329
  )
2262
2330
  )
2263
- figures = detect_figures(candidates, reserved_paths=reserved)
2331
+ figures = detect_figures(
2332
+ candidates, reserved_paths=reserved, ignore=ignore
2333
+ )
2264
2334
  datasets = detect_datasets(
2265
- candidates, reserved_paths=reserved, figure_paths=figures
2335
+ candidates,
2336
+ reserved_paths=reserved,
2337
+ figure_paths=figures,
2338
+ ignore=ignore,
2339
+ )
2340
+ results = detect_results(
2341
+ candidates, reserved_paths=reserved, ignore=ignore
2342
+ )
2343
+ presentations = detect_presentations(
2344
+ candidates, reserved_paths=reserved, ignore=ignore
2266
2345
  )
2267
- results = detect_results(candidates, reserved_paths=reserved)
2268
- presentations = detect_presentations(candidates, reserved_paths=reserved)
2269
2346
  return {
2270
2347
  "figures": figures,
2271
2348
  "datasets": datasets,
@@ -337,3 +337,32 @@ def ensure_path_is_not_ignored(
337
337
  if target_repo.ignored(target_path) and _depth < 10:
338
338
  ensure_path_is_not_ignored(target_repo, target_path, _depth + 1)
339
339
  return True
340
+
341
+
342
+ def ensure_dvc_pointer_is_not_ignored(repo, path: str) -> None:
343
+ """Ensure the .dvc pointer for ``path`` will not be Git-ignored.
344
+
345
+ A broad pattern in a ``.gitignore`` (e.g. ``*.pdf*``) can also match the
346
+ ``<path>.dvc`` pointer DVC commits to Git, causing ``dvc add`` to fail with
347
+ "bad DVC file name ... is git-ignored". This appends a ``!*.dvc`` negation
348
+ to the ``.gitignore`` in the pointer's own directory (which wins under Git
349
+ precedence) so pointers stay tracked. Idempotent.
350
+ """
351
+ path = path.replace("\\", "/").rstrip("/")
352
+ pointer = path + ".dvc"
353
+ if pointer not in repo.ignored(pointer):
354
+ return
355
+ pointer_dir = os.path.dirname(pointer)
356
+ gitignore_path = os.path.join(repo.working_dir, pointer_dir, ".gitignore")
357
+ exception = "!*.dvc"
358
+ existing_lines = []
359
+ if os.path.isfile(gitignore_path):
360
+ with open(gitignore_path, "r", encoding="utf-8") as f:
361
+ existing_lines = f.read().splitlines()
362
+ if exception in existing_lines:
363
+ return # Already present
364
+ os.makedirs(os.path.dirname(gitignore_path), exist_ok=True)
365
+ with open(gitignore_path, "a", encoding="utf-8") as f:
366
+ if existing_lines and existing_lines[-1] != "":
367
+ f.write("\n")
368
+ f.write(exception + "\n")
@@ -351,13 +351,23 @@ class ResultsEvidence(BaseModel):
351
351
  explanation: str | None = None
352
352
 
353
353
 
354
+ class PublicationEvidence(BaseModel):
355
+ """Evidence in the form of a publication."""
356
+
357
+ kind: Literal["publication"] = "publication"
358
+ path: str
359
+ explanation: str | None = None
360
+
361
+
354
362
  class Question(BaseModel):
355
363
  """A question the project hopes to answer."""
356
364
 
357
365
  question: str
358
366
  hypothesis: str | None = None
359
367
  answer: str | None = None
360
- evidence: list[FigureEvidence | ResultsEvidence] | None = None
368
+ evidence: (
369
+ list[FigureEvidence | ResultsEvidence | PublicationEvidence] | None
370
+ ) = None
361
371
 
362
372
 
363
373
  class Dependency(BaseModel):
@@ -1836,3 +1836,70 @@ def test_run_downstream_does_not_submit_unrelated_sweep(tmp_dir):
1836
1836
  assert os.path.exists("other.txt")
1837
1837
  assert not os.path.exists("out-1.txt")
1838
1838
  assert not os.path.exists("out-2.txt")
1839
+
1840
+
1841
+ def test_add_dvc_pointer_unignored(tmp_dir):
1842
+ """Test that calkit add --to=dvc ensures the .dvc pointer is unignored."""
1843
+ subprocess.check_call(["git", "init"])
1844
+ subprocess.check_call(["calkit", "init"])
1845
+ os.makedirs("pubs/defense/ppt")
1846
+ with open("pubs/defense/ppt/.gitignore", "w") as f:
1847
+ f.write("*.pdf*\n")
1848
+ pdf_path = "pubs/defense/ppt/McCabe.pdf"
1849
+ with open(pdf_path, "w") as f:
1850
+ f.write("dummy pdf content")
1851
+ subprocess.check_call(["calkit", "add", "--to=dvc", pdf_path])
1852
+ # Assert pointer exists
1853
+ dvc_file = f"{pdf_path}.dvc"
1854
+ assert os.path.exists(dvc_file)
1855
+ # Assert pointer is NOT ignored (should return 1)
1856
+ res = subprocess.run(["git", "check-ignore", "-q", dvc_file])
1857
+ assert res.returncode == 1
1858
+ # Assert data file IS still ignored
1859
+ res_data = subprocess.run(["git", "check-ignore", "-q", pdf_path])
1860
+ assert res_data.returncode == 0
1861
+ # Assert it was staged
1862
+ staged = calkit.git.get_staged_files()
1863
+ assert dvc_file in staged
1864
+
1865
+
1866
+ def test_add_dvc_pointer_unignored_idempotent(tmp_dir):
1867
+ """Test that the unignore logic does not duplicate lines."""
1868
+ subprocess.check_call(["git", "init"])
1869
+ subprocess.check_call(["calkit", "init"])
1870
+ os.makedirs("data")
1871
+ with open("data/.gitignore", "w") as f:
1872
+ f.write("*.pdf*\n")
1873
+ pdf_path = "data/doc.pdf"
1874
+ with open(pdf_path, "w") as f:
1875
+ f.write("doc")
1876
+ subprocess.check_call(["calkit", "add", "--to=dvc", pdf_path])
1877
+ with open("data/.gitignore") as f:
1878
+ content1 = f.read()
1879
+ # Second add should not duplicate
1880
+ subprocess.check_call(["calkit", "add", "--to=dvc", pdf_path])
1881
+ with open("data/.gitignore") as f:
1882
+ content2 = f.read()
1883
+ assert content1 == content2
1884
+ assert content1.count("!*.dvc") == 1
1885
+
1886
+
1887
+ def test_call_dvc_passthrough_hint(tmp_dir):
1888
+ """Test that calkit dvc add prints a hint if .dvc is ignored."""
1889
+ subprocess.check_call(["git", "init"])
1890
+ subprocess.check_call(["calkit", "init"])
1891
+ os.makedirs("data")
1892
+ with open("data/.gitignore", "w") as f:
1893
+ f.write("*.pdf*\n")
1894
+ pdf_path = "data/doc.pdf"
1895
+ with open(pdf_path, "w") as f:
1896
+ f.write("doc")
1897
+ # Run the passthrough dvc add which will fail because it's ignored
1898
+ res = subprocess.run(
1899
+ ["calkit", "dvc", "add", pdf_path], capture_output=True, text=True
1900
+ )
1901
+ assert res.returncode != 0
1902
+ assert (
1903
+ "Hint: If DVC failed because a .dvc pointer file is git-ignored"
1904
+ in res.stderr
1905
+ )