seqforge 2026.7.1__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 (220) hide show
  1. seqforge-2026.7.1/.github/workflows/ci.yml +74 -0
  2. seqforge-2026.7.1/.github/workflows/docs.yml +41 -0
  3. seqforge-2026.7.1/.github/workflows/release.yml +123 -0
  4. seqforge-2026.7.1/.gitignore +27 -0
  5. seqforge-2026.7.1/.markdownlint-cli2.yaml +39 -0
  6. seqforge-2026.7.1/.pre-commit-config.yaml +56 -0
  7. seqforge-2026.7.1/.vscode/settings.json +14 -0
  8. seqforge-2026.7.1/AGENTS.md +9 -0
  9. seqforge-2026.7.1/CHANGELOG.md +650 -0
  10. seqforge-2026.7.1/CLAUDE.md +204 -0
  11. seqforge-2026.7.1/LICENSE +21 -0
  12. seqforge-2026.7.1/PKG-INFO +125 -0
  13. seqforge-2026.7.1/README.md +88 -0
  14. seqforge-2026.7.1/docs/assets/favicon.png +0 -0
  15. seqforge-2026.7.1/docs/assets/logo-color.png +0 -0
  16. seqforge-2026.7.1/docs/assets/logo-readme.png +0 -0
  17. seqforge-2026.7.1/docs/assets/logo.png +0 -0
  18. seqforge-2026.7.1/docs/concepts/artifacts.md +96 -0
  19. seqforge-2026.7.1/docs/concepts/identifying.md +77 -0
  20. seqforge-2026.7.1/docs/concepts/refusal.md +81 -0
  21. seqforge-2026.7.1/docs/design.md +931 -0
  22. seqforge-2026.7.1/docs/getting-started.md +94 -0
  23. seqforge-2026.7.1/docs/index.md +80 -0
  24. seqforge-2026.7.1/docs/kb/10x-3p-gex-v2.md +1 -0
  25. seqforge-2026.7.1/docs/kb/10x-3p-gex-v3.md +1 -0
  26. seqforge-2026.7.1/docs/kb/bd-rhapsody-wta.md +1 -0
  27. seqforge-2026.7.1/docs/kb/bulk-rnaseq-pe.md +1 -0
  28. seqforge-2026.7.1/docs/kb/index.md +43 -0
  29. seqforge-2026.7.1/docs/kb/splitseq.md +1 -0
  30. seqforge-2026.7.1/docs/stylesheets/extra.css +26 -0
  31. seqforge-2026.7.1/docs/tutorials/adding-a-technology.md +81 -0
  32. seqforge-2026.7.1/docs/tutorials/step-by-step.md +314 -0
  33. seqforge-2026.7.1/docs/tutorials/with-claude.md +125 -0
  34. seqforge-2026.7.1/evals/README.md +100 -0
  35. seqforge-2026.7.1/evals/cases/10x-v2-bytes-only/expected.yaml +8 -0
  36. seqforge-2026.7.1/evals/cases/10x-v2-bytes-only/inputs/recipe.yaml +7 -0
  37. seqforge-2026.7.1/evals/cases/10x-v3-bytes-only/expected.yaml +11 -0
  38. seqforge-2026.7.1/evals/cases/10x-v3-bytes-only/inputs/recipe.yaml +7 -0
  39. seqforge-2026.7.1/evals/cases/10x-v3-prose/expected.yaml +24 -0
  40. seqforge-2026.7.1/evals/cases/10x-v3-prose/inputs/recipe.yaml +8 -0
  41. seqforge-2026.7.1/evals/cases/10x-v3-prose/metadata/methods.txt +18 -0
  42. seqforge-2026.7.1/evals/cases/PRJNA1027859/expected.yaml +201 -0
  43. seqforge-2026.7.1/evals/cases/PRJNA1027859/inputs/recipe.yaml +24 -0
  44. seqforge-2026.7.1/evals/cases/PRJNA1027859/records.json +846 -0
  45. seqforge-2026.7.1/evals/cases/bulk-metadata-vs-single-cell-reads/expected.yaml +20 -0
  46. seqforge-2026.7.1/evals/cases/bulk-metadata-vs-single-cell-reads/inputs/recipe.yaml +11 -0
  47. seqforge-2026.7.1/evals/cases/bulk-pe-bytes-only/expected.yaml +9 -0
  48. seqforge-2026.7.1/evals/cases/bulk-pe-bytes-only/inputs/recipe.yaml +7 -0
  49. seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/expected.yaml +33 -0
  50. seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/inputs/recipe.yaml +7 -0
  51. seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/metadata/methods.txt +13 -0
  52. seqforge-2026.7.1/evals/cases/ont-unsupported/expected.yaml +8 -0
  53. seqforge-2026.7.1/evals/cases/ont-unsupported/inputs/recipe.yaml +8 -0
  54. seqforge-2026.7.1/evals/cases/single-cell-metadata-vs-bulk-reads/expected.yaml +22 -0
  55. seqforge-2026.7.1/evals/cases/single-cell-metadata-vs-bulk-reads/inputs/recipe.yaml +12 -0
  56. seqforge-2026.7.1/evals/cases/splitseq-bytes-only/expected.yaml +11 -0
  57. seqforge-2026.7.1/evals/cases/splitseq-bytes-only/inputs/recipe.yaml +7 -0
  58. seqforge-2026.7.1/evals/cases/truncated-gzip/expected.yaml +9 -0
  59. seqforge-2026.7.1/evals/cases/truncated-gzip/inputs/recipe.yaml +10 -0
  60. seqforge-2026.7.1/evals/cases/v2-metadata-vs-v3-reads/expected.yaml +17 -0
  61. seqforge-2026.7.1/evals/cases/v2-metadata-vs-v3-reads/inputs/recipe.yaml +10 -0
  62. seqforge-2026.7.1/mkdocs.yml +103 -0
  63. seqforge-2026.7.1/pixi.lock +11692 -0
  64. seqforge-2026.7.1/pyproject.toml +280 -0
  65. seqforge-2026.7.1/skills/install.py +98 -0
  66. seqforge-2026.7.1/skills/seqforge-compose/SKILL.md +83 -0
  67. seqforge-2026.7.1/skills/seqforge-exam/SKILL.md +40 -0
  68. seqforge-2026.7.1/skills/seqforge-harvest/SKILL.md +88 -0
  69. seqforge-2026.7.1/skills/seqforge-io/SKILL.md +76 -0
  70. seqforge-2026.7.1/skills/seqforge-kb-author/SKILL.md +165 -0
  71. seqforge-2026.7.1/skills/seqforge-manifest/SKILL.md +138 -0
  72. seqforge-2026.7.1/skills/seqforge-orchestrate/SKILL.md +138 -0
  73. seqforge-2026.7.1/skills/seqforge-resolve/SKILL.md +65 -0
  74. seqforge-2026.7.1/src/seqforge/__init__.py +16 -0
  75. seqforge-2026.7.1/src/seqforge/cli/__init__.py +38 -0
  76. seqforge-2026.7.1/src/seqforge/cli/__main__.py +8 -0
  77. seqforge-2026.7.1/src/seqforge/cli/_common.py +105 -0
  78. seqforge-2026.7.1/src/seqforge/cli/compose.py +119 -0
  79. seqforge-2026.7.1/src/seqforge/cli/eval.py +103 -0
  80. seqforge-2026.7.1/src/seqforge/cli/harvest.py +417 -0
  81. seqforge-2026.7.1/src/seqforge/cli/hook.py +247 -0
  82. seqforge-2026.7.1/src/seqforge/cli/io.py +502 -0
  83. seqforge-2026.7.1/src/seqforge/cli/kb.py +348 -0
  84. seqforge-2026.7.1/src/seqforge/cli/manifest.py +536 -0
  85. seqforge-2026.7.1/src/seqforge/cli/probe.py +43 -0
  86. seqforge-2026.7.1/src/seqforge/cli/processing.py +192 -0
  87. seqforge-2026.7.1/src/seqforge/cli/project.py +52 -0
  88. seqforge-2026.7.1/src/seqforge/cli/resolve.py +55 -0
  89. seqforge-2026.7.1/src/seqforge/cli/root.py +66 -0
  90. seqforge-2026.7.1/src/seqforge/cli/run.py +463 -0
  91. seqforge-2026.7.1/src/seqforge/cli/schema.py +41 -0
  92. seqforge-2026.7.1/src/seqforge/compose/__init__.py +28 -0
  93. seqforge-2026.7.1/src/seqforge/compose/core.py +515 -0
  94. seqforge-2026.7.1/src/seqforge/compose/gates.py +113 -0
  95. seqforge-2026.7.1/src/seqforge/compose/params.py +447 -0
  96. seqforge-2026.7.1/src/seqforge/e2e.py +1926 -0
  97. seqforge-2026.7.1/src/seqforge/evals/__init__.py +78 -0
  98. seqforge-2026.7.1/src/seqforge/evals/case.py +382 -0
  99. seqforge-2026.7.1/src/seqforge/evals/grade.py +300 -0
  100. seqforge-2026.7.1/src/seqforge/evals/run.py +420 -0
  101. seqforge-2026.7.1/src/seqforge/harvest/__init__.py +121 -0
  102. seqforge-2026.7.1/src/seqforge/harvest/extract.py +319 -0
  103. seqforge-2026.7.1/src/seqforge/harvest/fields.py +212 -0
  104. seqforge-2026.7.1/src/seqforge/harvest/normalize.py +537 -0
  105. seqforge-2026.7.1/src/seqforge/harvest/prep.py +41 -0
  106. seqforge-2026.7.1/src/seqforge/harvest/providers.py +321 -0
  107. seqforge-2026.7.1/src/seqforge/harvest/verify.py +251 -0
  108. seqforge-2026.7.1/src/seqforge/hooks/__init__.py +33 -0
  109. seqforge-2026.7.1/src/seqforge/hooks/guards.py +214 -0
  110. seqforge-2026.7.1/src/seqforge/io/__init__.py +61 -0
  111. seqforge-2026.7.1/src/seqforge/io/archive.py +450 -0
  112. seqforge-2026.7.1/src/seqforge/io/attributes.py +190 -0
  113. seqforge-2026.7.1/src/seqforge/io/biosample/attributes.json +6341 -0
  114. seqforge-2026.7.1/src/seqforge/io/efo/labels.json +55 -0
  115. seqforge-2026.7.1/src/seqforge/io/efo.py +138 -0
  116. seqforge-2026.7.1/src/seqforge/io/onlist.py +661 -0
  117. seqforge-2026.7.1/src/seqforge/io/onlists/3M-february-2018.codes.gz +0 -0
  118. seqforge-2026.7.1/src/seqforge/io/onlists/737K-arc-v1.codes.gz +0 -0
  119. seqforge-2026.7.1/src/seqforge/io/onlists/737K-august-2016.codes.gz +0 -0
  120. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls1-384.codes.gz +0 -0
  121. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls1.codes.gz +0 -0
  122. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls2-384.codes.gz +0 -0
  123. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls2.codes.gz +0 -0
  124. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls3-384.codes.gz +0 -0
  125. seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls3.codes.gz +0 -0
  126. seqforge-2026.7.1/src/seqforge/io/onlists/index.json +74 -0
  127. seqforge-2026.7.1/src/seqforge/io/remote.py +659 -0
  128. seqforge-2026.7.1/src/seqforge/io/taxonomy.py +194 -0
  129. seqforge-2026.7.1/src/seqforge/kb/__init__.py +62 -0
  130. seqforge-2026.7.1/src/seqforge/kb/anchor.py +169 -0
  131. seqforge-2026.7.1/src/seqforge/kb/generate.py +147 -0
  132. seqforge-2026.7.1/src/seqforge/kb/loader.py +152 -0
  133. seqforge-2026.7.1/src/seqforge/kb/roundtrip.py +112 -0
  134. seqforge-2026.7.1/src/seqforge/kb/schema.py +422 -0
  135. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex/spec.yaml +62 -0
  136. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v2/README.md +41 -0
  137. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v2/spec.yaml +83 -0
  138. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3/README.md +56 -0
  139. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3/spec.yaml +118 -0
  140. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3.1/README.md +1 -0
  141. seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3.1/spec.yaml +124 -0
  142. seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta/README.md +103 -0
  143. seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta/spec.yaml +130 -0
  144. seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced/spec.yaml +99 -0
  145. seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced-v1/spec.yaml +93 -0
  146. seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced-v2/spec.yaml +81 -0
  147. seqforge-2026.7.1/src/seqforge/kb/specs/bulk-rnaseq-pe/README.md +35 -0
  148. seqforge-2026.7.1/src/seqforge/kb/specs/bulk-rnaseq-pe/spec.yaml +97 -0
  149. seqforge-2026.7.1/src/seqforge/kb/specs/splitseq/README.md +51 -0
  150. seqforge-2026.7.1/src/seqforge/kb/specs/splitseq/spec.yaml +157 -0
  151. seqforge-2026.7.1/src/seqforge/manifest/__init__.py +61 -0
  152. seqforge-2026.7.1/src/seqforge/manifest/fill.py +531 -0
  153. seqforge-2026.7.1/src/seqforge/manifest/hash.py +77 -0
  154. seqforge-2026.7.1/src/seqforge/manifest/instruct.py +114 -0
  155. seqforge-2026.7.1/src/seqforge/manifest/policy.py +409 -0
  156. seqforge-2026.7.1/src/seqforge/manifest/validate.py +274 -0
  157. seqforge-2026.7.1/src/seqforge/models/__init__.py +268 -0
  158. seqforge-2026.7.1/src/seqforge/models/assertion.py +68 -0
  159. seqforge-2026.7.1/src/seqforge/models/base.py +100 -0
  160. seqforge-2026.7.1/src/seqforge/models/blocker.py +71 -0
  161. seqforge-2026.7.1/src/seqforge/models/conflict.py +47 -0
  162. seqforge-2026.7.1/src/seqforge/models/dataset.py +320 -0
  163. seqforge-2026.7.1/src/seqforge/models/evidenced.py +54 -0
  164. seqforge-2026.7.1/src/seqforge/models/observation.py +157 -0
  165. seqforge-2026.7.1/src/seqforge/models/processing.py +231 -0
  166. seqforge-2026.7.1/src/seqforge/models/records.py +145 -0
  167. seqforge-2026.7.1/src/seqforge/models/resolve.py +216 -0
  168. seqforge-2026.7.1/src/seqforge/probe/__init__.py +46 -0
  169. seqforge-2026.7.1/src/seqforge/probe/core.py +232 -0
  170. seqforge-2026.7.1/src/seqforge/probe/signals.py +250 -0
  171. seqforge-2026.7.1/src/seqforge/probe/streaming.py +118 -0
  172. seqforge-2026.7.1/src/seqforge/project.py +177 -0
  173. seqforge-2026.7.1/src/seqforge/py.typed +0 -0
  174. seqforge-2026.7.1/src/seqforge/resolve/__init__.py +98 -0
  175. seqforge-2026.7.1/src/seqforge/resolve/assign.py +204 -0
  176. seqforge-2026.7.1/src/seqforge/resolve/cache.py +119 -0
  177. seqforge-2026.7.1/src/seqforge/resolve/confuse.py +215 -0
  178. seqforge-2026.7.1/src/seqforge/resolve/engine.py +646 -0
  179. seqforge-2026.7.1/src/seqforge/resolve/escalate.py +668 -0
  180. seqforge-2026.7.1/src/seqforge/resolve/evaluators.py +306 -0
  181. seqforge-2026.7.1/src/seqforge/resolve/geometry.py +89 -0
  182. seqforge-2026.7.1/src/seqforge/resolve/group.py +85 -0
  183. seqforge-2026.7.1/src/seqforge/resolve/records.py +550 -0
  184. seqforge-2026.7.1/src/seqforge/resolve/scoring.py +373 -0
  185. seqforge-2026.7.1/src/seqforge/resolve/window.py +206 -0
  186. seqforge-2026.7.1/src/seqforge/workflows/__init__.py +234 -0
  187. seqforge-2026.7.1/src/seqforge/workflows/cram.py +117 -0
  188. seqforge-2026.7.1/src/seqforge/workflows/h5ad.py +368 -0
  189. seqforge-2026.7.1/src/seqforge/workflows/map/star.smk +101 -0
  190. seqforge-2026.7.1/src/seqforge/workflows/map/starsolo.smk +360 -0
  191. seqforge-2026.7.1/src/seqforge/workflows/qc.py +157 -0
  192. seqforge-2026.7.1/src/seqforge/workspace.py +125 -0
  193. seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.bioproject.xml +52 -0
  194. seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.biosample.xml +2 -0
  195. seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.sra.xml +3 -0
  196. seqforge-2026.7.1/tests/test_archive.py +188 -0
  197. seqforge-2026.7.1/tests/test_cli.py +885 -0
  198. seqforge-2026.7.1/tests/test_compile.py +1947 -0
  199. seqforge-2026.7.1/tests/test_cram.py +116 -0
  200. seqforge-2026.7.1/tests/test_e2e.py +797 -0
  201. seqforge-2026.7.1/tests/test_evals.py +803 -0
  202. seqforge-2026.7.1/tests/test_extract.py +482 -0
  203. seqforge-2026.7.1/tests/test_geometry.py +95 -0
  204. seqforge-2026.7.1/tests/test_h5ad.py +259 -0
  205. seqforge-2026.7.1/tests/test_harvest.py +648 -0
  206. seqforge-2026.7.1/tests/test_hooks.py +297 -0
  207. seqforge-2026.7.1/tests/test_index_reads.py +432 -0
  208. seqforge-2026.7.1/tests/test_io.py +446 -0
  209. seqforge-2026.7.1/tests/test_kb.py +765 -0
  210. seqforge-2026.7.1/tests/test_models.py +386 -0
  211. seqforge-2026.7.1/tests/test_negatives.py +347 -0
  212. seqforge-2026.7.1/tests/test_over_length.py +418 -0
  213. seqforge-2026.7.1/tests/test_partition.py +312 -0
  214. seqforge-2026.7.1/tests/test_probe.py +244 -0
  215. seqforge-2026.7.1/tests/test_qc.py +100 -0
  216. seqforge-2026.7.1/tests/test_records.py +665 -0
  217. seqforge-2026.7.1/tests/test_remote.py +613 -0
  218. seqforge-2026.7.1/tests/test_resolve.py +760 -0
  219. seqforge-2026.7.1/tests/test_skills.py +360 -0
  220. seqforge-2026.7.1/tests/test_taxonomy.py +120 -0
@@ -0,0 +1,74 @@
1
+ name: CI
2
+
3
+ # THE mechanism. Most of seqforge's non-negotiable rules are enforced by tests (the parse/count line,
4
+ # the KB round-trip over every spec, the rung-0-2 separability guard, the read/byte budget), and
5
+ # nothing else runs them automatically: pre-commit deliberately carries only the fast hooks, so a red
6
+ # commit can exist on a branch. This is where it is caught -- and on a machine that is not yours.
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ pull_request:
12
+
13
+ concurrency:
14
+ group: ci-${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ lint:
22
+ name: lint + typecheck
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v5
26
+ - uses: prefix-dev/setup-pixi@v0.10.0
27
+ with:
28
+ environments: default
29
+ locked: true
30
+ - name: ruff (lint)
31
+ run: pixi run -e default lint
32
+ - name: ruff format check
33
+ run: pixi run -e default fmt-check
34
+ - name: mypy --strict
35
+ run: pixi run -e default typecheck
36
+
37
+ # markdownlint is a Node tool, so it runs on its own runner rather than through pixi. It lints the
38
+ # human-facing docs (the site pages, the KB assay READMEs that render into them, and the top-level
39
+ # README) per `.markdownlint-cli2.yaml`; running with no file args makes cli2 read the globs/ignores
40
+ # from that config. Pinned so a new rule cannot turn CI red without a deliberate bump.
41
+ markdown:
42
+ name: markdownlint
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v5
46
+ - uses: actions/setup-node@v4
47
+ with:
48
+ node-version: "22"
49
+ - name: markdownlint (docs + README)
50
+ run: npx --yes markdownlint-cli2@0.23.1
51
+
52
+ test:
53
+ name: test
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v5
57
+ - uses: prefix-dev/setup-pixi@v0.10.0
58
+ with:
59
+ environments: test
60
+ locked: true
61
+ - name: pytest
62
+ run: pixi run -e test test
63
+
64
+ build:
65
+ name: build wheel + sdist
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v5
69
+ - uses: prefix-dev/setup-pixi@v0.10.0
70
+ with:
71
+ environments: default
72
+ locked: true
73
+ - name: build
74
+ run: pixi run -e default build
@@ -0,0 +1,41 @@
1
+ name: Docs
2
+
3
+ # Builds the MkDocs site and publishes it to the `gh-pages` branch on every push to main.
4
+ # Mirrors liulab-runtime's mechanism (a gh-pages branch), not liulab-genome's (a Pages artifact):
5
+ # the two siblings differ, and CLAUDE.md's own wording says gh-pages.
6
+ #
7
+ # Setup (one-off, by a maintainer):
8
+ # 1. Let this workflow run once so it creates the `gh-pages` branch.
9
+ # 2. Settings -> Pages -> Source: "Deploy from a branch".
10
+ # 3. Branch: `gh-pages`, folder: `/ (root)`. Save.
11
+
12
+ on:
13
+ push:
14
+ branches: [main]
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: write
19
+
20
+ concurrency:
21
+ group: gh-pages
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ deploy:
26
+ name: Build & deploy to gh-pages
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v5
30
+ with:
31
+ fetch-depth: 0
32
+ - uses: prefix-dev/setup-pixi@v0.10.0
33
+ with:
34
+ environments: docs
35
+ locked: true
36
+ - name: Configure git identity
37
+ run: |
38
+ git config user.name "github-actions[bot]"
39
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
40
+ - name: mkdocs gh-deploy
41
+ run: pixi run -e docs mkdocs gh-deploy --force --strict
@@ -0,0 +1,123 @@
1
+ name: Release
2
+
3
+ # Publish seqforge to PyPI. The wheel + sdist are built ONCE, then the same artifacts are uploaded via
4
+ # PyPI Trusted Publishing (OIDC) — no long-lived API token is stored in the repo.
5
+ #
6
+ # How a release happens:
7
+ # 1. Bump `version` in pyproject.toml and add the matching CHANGELOG.md entry (CalVer YYYY.M.PATCH).
8
+ # 2. Tag it `v<version>` (e.g. v2026.7.0) and create a GitHub Release from that tag.
9
+ # 3. Publishing the Release fires this workflow -> build -> publish to PyPI.
10
+ # `workflow_dispatch` is the manual door: pick `testpypi` to dry-run the upload to TestPyPI, or `pypi`
11
+ # to publish without cutting a GitHub Release.
12
+ #
13
+ # ONE-TIME SETUP by a maintainer (Trusted Publishing needs no secrets, but does need registration):
14
+ # - PyPI: https://pypi.org/manage/account/publishing/ -> add a "pending publisher":
15
+ # project `seqforge`, owner `liuhlab`, repo `seqforge`,
16
+ # workflow `release.yml`, environment `pypi`.
17
+ # - TestPyPI: https://test.pypi.org/manage/account/publishing/ -> same, environment `testpypi`.
18
+ # - Settings -> Environments: create `pypi` and `testpypi` (optionally add required reviewers on
19
+ # `pypi` so a human approves every real publish).
20
+ # The `environment:` on each publish job must match the name registered above, or PyPI rejects the
21
+ # OIDC token.
22
+
23
+ on:
24
+ release:
25
+ types: [published]
26
+ workflow_dispatch:
27
+ inputs:
28
+ target:
29
+ description: "Index to publish to"
30
+ type: choice
31
+ options: [testpypi, pypi]
32
+ default: testpypi
33
+
34
+ # Least privilege by default; the publish jobs opt into `id-token: write` for OIDC themselves.
35
+ permissions:
36
+ contents: read
37
+
38
+ concurrency:
39
+ group: release-${{ github.ref }}
40
+ cancel-in-progress: false
41
+
42
+ jobs:
43
+ build:
44
+ name: build wheel + sdist
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v5
48
+
49
+ # A plain Python is enough: `python -m build` runs hatchling in an isolated build env and does
50
+ # NOT install seqforge's runtime dependencies — so this job never has to reach the lab git
51
+ # packages, and the build stays fast and self-contained.
52
+ - uses: actions/setup-python@v5
53
+ with:
54
+ python-version: "3.12"
55
+
56
+ - name: Install build tooling
57
+ run: python -m pip install --upgrade build twine
58
+
59
+ # On a Release, the git tag is the source of truth for the version a human intended to ship.
60
+ # Refuse to build if it disagrees with pyproject's `version` — that mismatch is how you publish
61
+ # 2026.7.0 under a `v2026.7.1` tag and never notice. `v` prefix optional. Skipped on manual runs.
62
+ - name: Check tag matches pyproject version
63
+ if: github.event_name == 'release'
64
+ run: |
65
+ pkg_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
66
+ tag="${GITHUB_REF_NAME#v}"
67
+ echo "pyproject version: $pkg_version"
68
+ echo "release tag: $GITHUB_REF_NAME (normalized: $tag)"
69
+ if [ "$pkg_version" != "$tag" ]; then
70
+ echo "::error::Release tag '$GITHUB_REF_NAME' does not match pyproject version '$pkg_version'. Bump pyproject.toml or retag."
71
+ exit 1
72
+ fi
73
+
74
+ - name: Build
75
+ run: python -m build
76
+
77
+ # `twine check` validates the rendered metadata (long-description, classifiers) the way PyPI
78
+ # will, and — because we stripped the git deps from [project].dependencies — this is also where a
79
+ # regressed `git+https` direct reference would surface before the upload step.
80
+ - name: Check distributions
81
+ run: twine check dist/*
82
+
83
+ - uses: actions/upload-artifact@v4
84
+ with:
85
+ name: dist
86
+ path: dist/
87
+ if-no-files-found: error
88
+
89
+ publish-testpypi:
90
+ name: publish to TestPyPI
91
+ # Manual dry-run only. Real releases go straight to PyPI via publish-pypi.
92
+ if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
93
+ needs: build
94
+ runs-on: ubuntu-latest
95
+ environment: testpypi
96
+ permissions:
97
+ id-token: write # OIDC token for Trusted Publishing
98
+ steps:
99
+ - uses: actions/download-artifact@v4
100
+ with:
101
+ name: dist
102
+ path: dist/
103
+ - name: Publish to TestPyPI
104
+ uses: pypa/gh-action-pypi-publish@release/v1
105
+ with:
106
+ repository-url: https://test.pypi.org/legacy/
107
+
108
+ publish-pypi:
109
+ name: publish to PyPI
110
+ # A published GitHub Release, or a manual run that explicitly targets pypi.
111
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
112
+ needs: build
113
+ runs-on: ubuntu-latest
114
+ environment: pypi
115
+ permissions:
116
+ id-token: write # OIDC token for Trusted Publishing
117
+ steps:
118
+ - uses: actions/download-artifact@v4
119
+ with:
120
+ name: dist
121
+ path: dist/
122
+ - name: Publish to PyPI
123
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ # pixi
2
+ .pixi/
3
+ *.egg-info/
4
+ # python
5
+ __pycache__/
6
+ *.py[cod]
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .pytest_cache/
10
+ .hypothesis/
11
+ build/
12
+ dist/
13
+ # seqforge working state (resumable, content-addressed — never committed).
14
+ #
15
+ # ANCHORED, and it has to be. An unanchored `seqforge/` matches a directory of that name at ANY
16
+ # depth, which in this repo is `src/seqforge/` — git would ignore our own source tree, silently.
17
+ # The leading slash pins it to the repo root, which is where a `-C .` run puts it.
18
+ /seqforge/
19
+ .seqforge/
20
+ # docs build
21
+ site/
22
+ # editor / OS
23
+ .DS_Store
24
+ .vscode/settings.local.json
25
+
26
+ # Slurm job logs (lab convention: `mkdir -p sbatch` in the submit dir)
27
+ sbatch/
@@ -0,0 +1,39 @@
1
+ # markdownlint for the human-facing docs (and the published README).
2
+ #
3
+ # The whole default rule set runs (`default: true`) — this is markdownlint applied in general, not a
4
+ # hand-picked subset. The one tuning is line length: prose here is hand-wrapped at 100 (the same width
5
+ # ruff enforces for Python), so MD013 is set to 100 rather than its default 80, and exempted inside
6
+ # code blocks and tables where wrapping would break meaning. Long bare URLs are exempt automatically.
7
+ #
8
+ # Scope is deliberate: the docs site, the KB assay READMEs that render into it, and the top-level
9
+ # README — the prose a person reads. It does NOT lint the agent-facing files (CLAUDE.md, AGENTS.md,
10
+ # docs/design.md), whose format serves a different reader.
11
+ #
12
+ # Runs in CI (.github/workflows/ci.yml, the `markdown` job) and locally as a pre-commit hook.
13
+
14
+ config:
15
+ default: true
16
+ # Line length is NOT gated, matching the codebase's own policy: ruff sets line-length 100 but
17
+ # ignores E501 (see CLAUDE.md), so Python lines may run over and prose follows the same rule —
18
+ # hand-wrapped near 100, never failed for a trailing word or an unbreakable URL. Every other
19
+ # markdownlint rule (headings, lists, fenced-code language, table style, inline-HTML, …) stays on.
20
+ MD013: false
21
+
22
+ globs:
23
+ - "docs/**/*.md"
24
+ - "src/seqforge/kb/specs/**/README.md"
25
+ - "README.md"
26
+
27
+ ignores:
28
+ # Agent-facing internal design doc — excluded from the site, and not written to docs-page rules.
29
+ - "docs/design.md"
30
+ # The KB assay pages are one-line snippet-include wrappers (they pull in the canonical README next
31
+ # to each spec.yaml); there is no prose of their own to lint. The README they include IS linted,
32
+ # directly, via the specs glob above.
33
+ - "docs/kb/10x-3p-gex-v2.md"
34
+ - "docs/kb/10x-3p-gex-v3.md"
35
+ - "docs/kb/bd-rhapsody-wta.md"
36
+ - "docs/kb/splitseq.md"
37
+ - "docs/kb/bulk-rnaseq-pe.md"
38
+ # The v3.1 README is a symlink to the v3 README — linting the target once is enough.
39
+ - "src/seqforge/kb/specs/10x-3p-gex-v3.1/README.md"
@@ -0,0 +1,56 @@
1
+ # Pre-commit hooks for seqforge.
2
+ #
3
+ # Install once per clone: pixi run -- pre-commit install
4
+ # Run on all files: pixi run -- pre-commit run --all-files
5
+ #
6
+ # Ruff is installed by pre-commit in its own ephemeral env (pinned below); mypy runs through pixi so
7
+ # it sees the project's resolved env — the same shape liulab-genome uses.
8
+ #
9
+ # WHY pytest is NOT here: most of this repo's rules are enforced by tests, so the suite is the
10
+ # mechanism — but a mechanism that runs on every commit taxes every commit, including the ones that
11
+ # touch only prose. The suite runs in CI (`.github/workflows/ci.yml`) on push and PR; run it locally
12
+ # with `pixi run check` when you have changed behaviour. The tradeoff is explicit: a broken commit can
13
+ # now land on a branch and is caught at push instead of at commit.
14
+
15
+ repos:
16
+ - repo: https://github.com/pre-commit/pre-commit-hooks
17
+ rev: v5.0.0
18
+ hooks:
19
+ - id: trailing-whitespace
20
+ - id: end-of-file-fixer
21
+ - id: check-yaml
22
+ - id: check-toml
23
+ - id: check-merge-conflict
24
+ - id: check-added-large-files
25
+ args: [--maxkb=500]
26
+
27
+ - repo: https://github.com/astral-sh/ruff-pre-commit
28
+ rev: v0.15.16
29
+ hooks:
30
+ - id: ruff-check
31
+ args: [--fix]
32
+ - id: ruff-format
33
+
34
+ - repo: https://github.com/shellcheck-py/shellcheck-py
35
+ rev: v0.10.0.1
36
+ hooks:
37
+ - id: shellcheck
38
+
39
+ # markdownlint over the human-facing docs. A fast hook, so it belongs here. pass_filenames: false
40
+ # makes it lint the scope declared in `.markdownlint-cli2.yaml` (globs + ignores) rather than only
41
+ # the changed files, so local and CI (the `markdown` job) check exactly the same set.
42
+ - repo: https://github.com/DavidAnson/markdownlint-cli2
43
+ rev: v0.23.1
44
+ hooks:
45
+ - id: markdownlint-cli2
46
+ pass_filenames: false
47
+
48
+ - repo: local
49
+ hooks:
50
+ - id: mypy
51
+ name: mypy --strict (via pixi)
52
+ entry: pixi run --no-progress typecheck
53
+ language: system
54
+ types: [python]
55
+ pass_filenames: false
56
+ require_serial: true
@@ -0,0 +1,14 @@
1
+ {
2
+ "python.defaultInterpreterPath": "${workspaceFolder}/.pixi/envs/default/bin/python",
3
+ "python.terminal.activateEnvironment": false,
4
+ "python.analysis.extraPaths": ["${workspaceFolder}/src"],
5
+ "python.testing.pytestEnabled": true,
6
+ "python.testing.pytestArgs": ["tests"],
7
+ "search.exclude": {
8
+ "**/.pixi": true
9
+ },
10
+ "files.watcherExclude": {
11
+ "**/.pixi/**": true
12
+ },
13
+ "python-envs.defaultEnvManager": "ms-python.python:system"
14
+ }
@@ -0,0 +1,9 @@
1
+ # AGENTS.md
2
+
3
+ This repository's agent guidance lives in [`CLAUDE.md`](CLAUDE.md) — read it in full. It is
4
+ tool-agnostic (the only Claude Code-specific parts are the hook names `PreToolUse` / `PostToolUse` /
5
+ `Stop`; the rules R1–R15 and conventions apply to any coding agent).
6
+
7
+ Kept as a single canonical document on purpose: do **not** generate this file by find-replacing
8
+ "Claude"→"Codex" across `CLAUDE.md` — that corrupts real paths like `.seqforge/` and would misstate
9
+ the rules. Point here instead.