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.
- seqforge-2026.7.1/.github/workflows/ci.yml +74 -0
- seqforge-2026.7.1/.github/workflows/docs.yml +41 -0
- seqforge-2026.7.1/.github/workflows/release.yml +123 -0
- seqforge-2026.7.1/.gitignore +27 -0
- seqforge-2026.7.1/.markdownlint-cli2.yaml +39 -0
- seqforge-2026.7.1/.pre-commit-config.yaml +56 -0
- seqforge-2026.7.1/.vscode/settings.json +14 -0
- seqforge-2026.7.1/AGENTS.md +9 -0
- seqforge-2026.7.1/CHANGELOG.md +650 -0
- seqforge-2026.7.1/CLAUDE.md +204 -0
- seqforge-2026.7.1/LICENSE +21 -0
- seqforge-2026.7.1/PKG-INFO +125 -0
- seqforge-2026.7.1/README.md +88 -0
- seqforge-2026.7.1/docs/assets/favicon.png +0 -0
- seqforge-2026.7.1/docs/assets/logo-color.png +0 -0
- seqforge-2026.7.1/docs/assets/logo-readme.png +0 -0
- seqforge-2026.7.1/docs/assets/logo.png +0 -0
- seqforge-2026.7.1/docs/concepts/artifacts.md +96 -0
- seqforge-2026.7.1/docs/concepts/identifying.md +77 -0
- seqforge-2026.7.1/docs/concepts/refusal.md +81 -0
- seqforge-2026.7.1/docs/design.md +931 -0
- seqforge-2026.7.1/docs/getting-started.md +94 -0
- seqforge-2026.7.1/docs/index.md +80 -0
- seqforge-2026.7.1/docs/kb/10x-3p-gex-v2.md +1 -0
- seqforge-2026.7.1/docs/kb/10x-3p-gex-v3.md +1 -0
- seqforge-2026.7.1/docs/kb/bd-rhapsody-wta.md +1 -0
- seqforge-2026.7.1/docs/kb/bulk-rnaseq-pe.md +1 -0
- seqforge-2026.7.1/docs/kb/index.md +43 -0
- seqforge-2026.7.1/docs/kb/splitseq.md +1 -0
- seqforge-2026.7.1/docs/stylesheets/extra.css +26 -0
- seqforge-2026.7.1/docs/tutorials/adding-a-technology.md +81 -0
- seqforge-2026.7.1/docs/tutorials/step-by-step.md +314 -0
- seqforge-2026.7.1/docs/tutorials/with-claude.md +125 -0
- seqforge-2026.7.1/evals/README.md +100 -0
- seqforge-2026.7.1/evals/cases/10x-v2-bytes-only/expected.yaml +8 -0
- seqforge-2026.7.1/evals/cases/10x-v2-bytes-only/inputs/recipe.yaml +7 -0
- seqforge-2026.7.1/evals/cases/10x-v3-bytes-only/expected.yaml +11 -0
- seqforge-2026.7.1/evals/cases/10x-v3-bytes-only/inputs/recipe.yaml +7 -0
- seqforge-2026.7.1/evals/cases/10x-v3-prose/expected.yaml +24 -0
- seqforge-2026.7.1/evals/cases/10x-v3-prose/inputs/recipe.yaml +8 -0
- seqforge-2026.7.1/evals/cases/10x-v3-prose/metadata/methods.txt +18 -0
- seqforge-2026.7.1/evals/cases/PRJNA1027859/expected.yaml +201 -0
- seqforge-2026.7.1/evals/cases/PRJNA1027859/inputs/recipe.yaml +24 -0
- seqforge-2026.7.1/evals/cases/PRJNA1027859/records.json +846 -0
- seqforge-2026.7.1/evals/cases/bulk-metadata-vs-single-cell-reads/expected.yaml +20 -0
- seqforge-2026.7.1/evals/cases/bulk-metadata-vs-single-cell-reads/inputs/recipe.yaml +11 -0
- seqforge-2026.7.1/evals/cases/bulk-pe-bytes-only/expected.yaml +9 -0
- seqforge-2026.7.1/evals/cases/bulk-pe-bytes-only/inputs/recipe.yaml +7 -0
- seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/expected.yaml +33 -0
- seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/inputs/recipe.yaml +7 -0
- seqforge-2026.7.1/evals/cases/chemistry-unstated-trap/metadata/methods.txt +13 -0
- seqforge-2026.7.1/evals/cases/ont-unsupported/expected.yaml +8 -0
- seqforge-2026.7.1/evals/cases/ont-unsupported/inputs/recipe.yaml +8 -0
- seqforge-2026.7.1/evals/cases/single-cell-metadata-vs-bulk-reads/expected.yaml +22 -0
- seqforge-2026.7.1/evals/cases/single-cell-metadata-vs-bulk-reads/inputs/recipe.yaml +12 -0
- seqforge-2026.7.1/evals/cases/splitseq-bytes-only/expected.yaml +11 -0
- seqforge-2026.7.1/evals/cases/splitseq-bytes-only/inputs/recipe.yaml +7 -0
- seqforge-2026.7.1/evals/cases/truncated-gzip/expected.yaml +9 -0
- seqforge-2026.7.1/evals/cases/truncated-gzip/inputs/recipe.yaml +10 -0
- seqforge-2026.7.1/evals/cases/v2-metadata-vs-v3-reads/expected.yaml +17 -0
- seqforge-2026.7.1/evals/cases/v2-metadata-vs-v3-reads/inputs/recipe.yaml +10 -0
- seqforge-2026.7.1/mkdocs.yml +103 -0
- seqforge-2026.7.1/pixi.lock +11692 -0
- seqforge-2026.7.1/pyproject.toml +280 -0
- seqforge-2026.7.1/skills/install.py +98 -0
- seqforge-2026.7.1/skills/seqforge-compose/SKILL.md +83 -0
- seqforge-2026.7.1/skills/seqforge-exam/SKILL.md +40 -0
- seqforge-2026.7.1/skills/seqforge-harvest/SKILL.md +88 -0
- seqforge-2026.7.1/skills/seqforge-io/SKILL.md +76 -0
- seqforge-2026.7.1/skills/seqforge-kb-author/SKILL.md +165 -0
- seqforge-2026.7.1/skills/seqforge-manifest/SKILL.md +138 -0
- seqforge-2026.7.1/skills/seqforge-orchestrate/SKILL.md +138 -0
- seqforge-2026.7.1/skills/seqforge-resolve/SKILL.md +65 -0
- seqforge-2026.7.1/src/seqforge/__init__.py +16 -0
- seqforge-2026.7.1/src/seqforge/cli/__init__.py +38 -0
- seqforge-2026.7.1/src/seqforge/cli/__main__.py +8 -0
- seqforge-2026.7.1/src/seqforge/cli/_common.py +105 -0
- seqforge-2026.7.1/src/seqforge/cli/compose.py +119 -0
- seqforge-2026.7.1/src/seqforge/cli/eval.py +103 -0
- seqforge-2026.7.1/src/seqforge/cli/harvest.py +417 -0
- seqforge-2026.7.1/src/seqforge/cli/hook.py +247 -0
- seqforge-2026.7.1/src/seqforge/cli/io.py +502 -0
- seqforge-2026.7.1/src/seqforge/cli/kb.py +348 -0
- seqforge-2026.7.1/src/seqforge/cli/manifest.py +536 -0
- seqforge-2026.7.1/src/seqforge/cli/probe.py +43 -0
- seqforge-2026.7.1/src/seqforge/cli/processing.py +192 -0
- seqforge-2026.7.1/src/seqforge/cli/project.py +52 -0
- seqforge-2026.7.1/src/seqforge/cli/resolve.py +55 -0
- seqforge-2026.7.1/src/seqforge/cli/root.py +66 -0
- seqforge-2026.7.1/src/seqforge/cli/run.py +463 -0
- seqforge-2026.7.1/src/seqforge/cli/schema.py +41 -0
- seqforge-2026.7.1/src/seqforge/compose/__init__.py +28 -0
- seqforge-2026.7.1/src/seqforge/compose/core.py +515 -0
- seqforge-2026.7.1/src/seqforge/compose/gates.py +113 -0
- seqforge-2026.7.1/src/seqforge/compose/params.py +447 -0
- seqforge-2026.7.1/src/seqforge/e2e.py +1926 -0
- seqforge-2026.7.1/src/seqforge/evals/__init__.py +78 -0
- seqforge-2026.7.1/src/seqforge/evals/case.py +382 -0
- seqforge-2026.7.1/src/seqforge/evals/grade.py +300 -0
- seqforge-2026.7.1/src/seqforge/evals/run.py +420 -0
- seqforge-2026.7.1/src/seqforge/harvest/__init__.py +121 -0
- seqforge-2026.7.1/src/seqforge/harvest/extract.py +319 -0
- seqforge-2026.7.1/src/seqforge/harvest/fields.py +212 -0
- seqforge-2026.7.1/src/seqforge/harvest/normalize.py +537 -0
- seqforge-2026.7.1/src/seqforge/harvest/prep.py +41 -0
- seqforge-2026.7.1/src/seqforge/harvest/providers.py +321 -0
- seqforge-2026.7.1/src/seqforge/harvest/verify.py +251 -0
- seqforge-2026.7.1/src/seqforge/hooks/__init__.py +33 -0
- seqforge-2026.7.1/src/seqforge/hooks/guards.py +214 -0
- seqforge-2026.7.1/src/seqforge/io/__init__.py +61 -0
- seqforge-2026.7.1/src/seqforge/io/archive.py +450 -0
- seqforge-2026.7.1/src/seqforge/io/attributes.py +190 -0
- seqforge-2026.7.1/src/seqforge/io/biosample/attributes.json +6341 -0
- seqforge-2026.7.1/src/seqforge/io/efo/labels.json +55 -0
- seqforge-2026.7.1/src/seqforge/io/efo.py +138 -0
- seqforge-2026.7.1/src/seqforge/io/onlist.py +661 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/3M-february-2018.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/737K-arc-v1.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/737K-august-2016.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls1-384.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls1.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls2-384.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls2.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls3-384.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/bd-rhapsody-cls3.codes.gz +0 -0
- seqforge-2026.7.1/src/seqforge/io/onlists/index.json +74 -0
- seqforge-2026.7.1/src/seqforge/io/remote.py +659 -0
- seqforge-2026.7.1/src/seqforge/io/taxonomy.py +194 -0
- seqforge-2026.7.1/src/seqforge/kb/__init__.py +62 -0
- seqforge-2026.7.1/src/seqforge/kb/anchor.py +169 -0
- seqforge-2026.7.1/src/seqforge/kb/generate.py +147 -0
- seqforge-2026.7.1/src/seqforge/kb/loader.py +152 -0
- seqforge-2026.7.1/src/seqforge/kb/roundtrip.py +112 -0
- seqforge-2026.7.1/src/seqforge/kb/schema.py +422 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex/spec.yaml +62 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v2/README.md +41 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v2/spec.yaml +83 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3/README.md +56 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3/spec.yaml +118 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3.1/README.md +1 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/10x-3p-gex-v3.1/spec.yaml +124 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta/README.md +103 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta/spec.yaml +130 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced/spec.yaml +99 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced-v1/spec.yaml +93 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bd-rhapsody-wta-enhanced-v2/spec.yaml +81 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bulk-rnaseq-pe/README.md +35 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/bulk-rnaseq-pe/spec.yaml +97 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/splitseq/README.md +51 -0
- seqforge-2026.7.1/src/seqforge/kb/specs/splitseq/spec.yaml +157 -0
- seqforge-2026.7.1/src/seqforge/manifest/__init__.py +61 -0
- seqforge-2026.7.1/src/seqforge/manifest/fill.py +531 -0
- seqforge-2026.7.1/src/seqforge/manifest/hash.py +77 -0
- seqforge-2026.7.1/src/seqforge/manifest/instruct.py +114 -0
- seqforge-2026.7.1/src/seqforge/manifest/policy.py +409 -0
- seqforge-2026.7.1/src/seqforge/manifest/validate.py +274 -0
- seqforge-2026.7.1/src/seqforge/models/__init__.py +268 -0
- seqforge-2026.7.1/src/seqforge/models/assertion.py +68 -0
- seqforge-2026.7.1/src/seqforge/models/base.py +100 -0
- seqforge-2026.7.1/src/seqforge/models/blocker.py +71 -0
- seqforge-2026.7.1/src/seqforge/models/conflict.py +47 -0
- seqforge-2026.7.1/src/seqforge/models/dataset.py +320 -0
- seqforge-2026.7.1/src/seqforge/models/evidenced.py +54 -0
- seqforge-2026.7.1/src/seqforge/models/observation.py +157 -0
- seqforge-2026.7.1/src/seqforge/models/processing.py +231 -0
- seqforge-2026.7.1/src/seqforge/models/records.py +145 -0
- seqforge-2026.7.1/src/seqforge/models/resolve.py +216 -0
- seqforge-2026.7.1/src/seqforge/probe/__init__.py +46 -0
- seqforge-2026.7.1/src/seqforge/probe/core.py +232 -0
- seqforge-2026.7.1/src/seqforge/probe/signals.py +250 -0
- seqforge-2026.7.1/src/seqforge/probe/streaming.py +118 -0
- seqforge-2026.7.1/src/seqforge/project.py +177 -0
- seqforge-2026.7.1/src/seqforge/py.typed +0 -0
- seqforge-2026.7.1/src/seqforge/resolve/__init__.py +98 -0
- seqforge-2026.7.1/src/seqforge/resolve/assign.py +204 -0
- seqforge-2026.7.1/src/seqforge/resolve/cache.py +119 -0
- seqforge-2026.7.1/src/seqforge/resolve/confuse.py +215 -0
- seqforge-2026.7.1/src/seqforge/resolve/engine.py +646 -0
- seqforge-2026.7.1/src/seqforge/resolve/escalate.py +668 -0
- seqforge-2026.7.1/src/seqforge/resolve/evaluators.py +306 -0
- seqforge-2026.7.1/src/seqforge/resolve/geometry.py +89 -0
- seqforge-2026.7.1/src/seqforge/resolve/group.py +85 -0
- seqforge-2026.7.1/src/seqforge/resolve/records.py +550 -0
- seqforge-2026.7.1/src/seqforge/resolve/scoring.py +373 -0
- seqforge-2026.7.1/src/seqforge/resolve/window.py +206 -0
- seqforge-2026.7.1/src/seqforge/workflows/__init__.py +234 -0
- seqforge-2026.7.1/src/seqforge/workflows/cram.py +117 -0
- seqforge-2026.7.1/src/seqforge/workflows/h5ad.py +368 -0
- seqforge-2026.7.1/src/seqforge/workflows/map/star.smk +101 -0
- seqforge-2026.7.1/src/seqforge/workflows/map/starsolo.smk +360 -0
- seqforge-2026.7.1/src/seqforge/workflows/qc.py +157 -0
- seqforge-2026.7.1/src/seqforge/workspace.py +125 -0
- seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.bioproject.xml +52 -0
- seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.biosample.xml +2 -0
- seqforge-2026.7.1/tests/fixtures/archive/PRJNA1027859.sra.xml +3 -0
- seqforge-2026.7.1/tests/test_archive.py +188 -0
- seqforge-2026.7.1/tests/test_cli.py +885 -0
- seqforge-2026.7.1/tests/test_compile.py +1947 -0
- seqforge-2026.7.1/tests/test_cram.py +116 -0
- seqforge-2026.7.1/tests/test_e2e.py +797 -0
- seqforge-2026.7.1/tests/test_evals.py +803 -0
- seqforge-2026.7.1/tests/test_extract.py +482 -0
- seqforge-2026.7.1/tests/test_geometry.py +95 -0
- seqforge-2026.7.1/tests/test_h5ad.py +259 -0
- seqforge-2026.7.1/tests/test_harvest.py +648 -0
- seqforge-2026.7.1/tests/test_hooks.py +297 -0
- seqforge-2026.7.1/tests/test_index_reads.py +432 -0
- seqforge-2026.7.1/tests/test_io.py +446 -0
- seqforge-2026.7.1/tests/test_kb.py +765 -0
- seqforge-2026.7.1/tests/test_models.py +386 -0
- seqforge-2026.7.1/tests/test_negatives.py +347 -0
- seqforge-2026.7.1/tests/test_over_length.py +418 -0
- seqforge-2026.7.1/tests/test_partition.py +312 -0
- seqforge-2026.7.1/tests/test_probe.py +244 -0
- seqforge-2026.7.1/tests/test_qc.py +100 -0
- seqforge-2026.7.1/tests/test_records.py +665 -0
- seqforge-2026.7.1/tests/test_remote.py +613 -0
- seqforge-2026.7.1/tests/test_resolve.py +760 -0
- seqforge-2026.7.1/tests/test_skills.py +360 -0
- 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.
|