Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "celltypepilot",
"version": "0.3.0",
"version": "0.3.1",
"description": "Single-cell annotation review plugin — governed context, independent state evidence, conservative abstention, and critic checks.",
"author": {
"name": "HERRY423"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "celltypepilot",
"version": "0.3.0",
"version": "0.3.1",
"description": "Single-cell annotation review plugin — governed context, independent state evidence, conservative abstention, and critic checks.",
"author": {
"name": "HERRY423",
Expand Down
16 changes: 6 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,15 @@ jobs:
--tag "$GITHUB_REF_NAME" \
--output-dir release-assets
python -m twine check dist/*
(
cd dist
sha256sum *
) > ../release-assets/SHA256SUMS
(
cd release-assets
sha256sum celltypepilot-plugin-*.zip
) >> SHA256SUMS
python scripts/build_release_checksums.py \
--dist-dir dist \
--plugin-dir release-assets \
--output release-assets/SHA256SUMS

- name: Run release verification suite
run: |
ruff check src/ tests/ scripts/build_plugin_bundle.py
ruff format --check src/ tests/ scripts/build_plugin_bundle.py
ruff check src/ tests/ scripts/build_plugin_bundle.py scripts/build_release_checksums.py
ruff format --check src/ tests/ scripts/build_plugin_bundle.py scripts/build_release_checksums.py
pytest --cov=celltypepilot --cov-report=term-missing

- name: Smoke-test installed wheel and plugin bundle
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to CellTypePilot are documented here. The project follows
[Semantic Versioning](https://semver.org/). Release claims remain bounded by the
validation scope recorded in the repository and generated manifests.

## [0.3.1] - 2026-08-10

### Fixed

- Generate release checksums with a tested, cross-platform Python builder instead of shell
redirections whose paths depended on the parent shell working directory.
- Fail closed unless the current version has exactly one wheel, one source distribution, and one
Agent plugin bundle before `SHA256SUMS` is written.

### Release note

- The immutable `v0.3.0` tag did not create a GitHub Release or publish a PyPI distribution because
its verification job stopped before either publication stage.
- This patch changes release infrastructure and version metadata only; it adds no biological
validation or annotation-accuracy claim.

## [0.3.0] - 2026-08-10

### Added
Expand All @@ -30,4 +46,5 @@ validation scope recorded in the repository and generated manifests.
- It does not establish biological superiority over CellTypist, SingleR, Azimuth, popV, or expert review.
- A qualified human remains responsible for final annotations and biological claims.

[0.3.1]: https://github.com/HERRY423/CellTypePilot/releases/tag/v0.3.1
[0.3.0]: https://github.com/HERRY423/CellTypePilot/releases/tag/v0.3.0
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ coding workspace you already use. A qualified human owns the final biological de

## Current validation boundary

| Available in v0.3.0 | Not yet claimed |
| Available in v0.3.1 | Not yet claimed |
|---|---|
| Direction-, log2FC-, FDR-, and expression-fraction-gated DE evidence | Biological superiority over CellTypist, SingleR, Azimuth, or popV |
| Complete expected-marker denominators, with missing genes separated from present-but-silent genes | A completed public multi-study/donor benchmark |
Expand Down Expand Up @@ -80,7 +80,7 @@ output/
├── transitional_states.csv # Clusters flagged as differentiation intermediates
├── disagreements.csv # Marker vs reference disagreement analysis
├── report_draft.html # Self-contained HTML report with all figures embedded
├── methodology_draft.txt # "We annotated N clusters using CellTypePilot v0.3.0..."
├── methodology_draft.txt # "We annotated N clusters using CellTypePilot v0.3.1..."
├── manifest.json # Provenance: versions, params, data hash, output hashes
└── figures/
├── umap_cluster.png # UMAP by cluster (colorblind-friendly Wong palette)
Expand Down Expand Up @@ -321,9 +321,9 @@ reference, evidence, and provenance gates apply as the CLI.
```
CellTypePilot/
├── .claude-plugin/
│ └── plugin.json ← Claude Code plugin manifest (v0.3.0)
│ └── plugin.json ← Claude Code plugin manifest (v0.3.1)
├── .codex-plugin/
│ └── plugin.json ← Codex plugin manifest (v0.3.0, with interface block)
│ └── plugin.json ← Codex plugin manifest (v0.3.1, with interface block)
├── skills/
│ └── celltypepilot/
│ ├── SKILL.md ← Shared skill instructions (4-stage workflow)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "celltypepilot"
version = "0.3.0"
version = "0.3.1"
description = "Deterministic single-cell annotation backend for Agent plugins"
readme = "README.md"
license = "MIT"
Expand Down
83 changes: 83 additions & 0 deletions scripts/build_release_checksums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Build a deterministic SHA256SUMS file for CellTypePilot release artifacts."""

from __future__ import annotations

import argparse
import hashlib
import re
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
VERSION_PATTERN = re.compile(r'^version = "([^"]+)"$', re.MULTILINE)


def _read_version(root: Path = REPO_ROOT) -> str:
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
match = VERSION_PATTERN.search(pyproject)
if not match:
raise ValueError("project version not found in pyproject.toml")
return match.group(1)


def _require_single(directory: Path, pattern: str, label: str) -> Path:
matches = sorted(path for path in directory.glob(pattern) if path.is_file())
if len(matches) != 1:
raise ValueError(
f"expected exactly one {label} matching {directory / pattern}, found {len(matches)}"
)
return matches[0]


def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()


def build_checksums(
dist_dir: Path,
plugin_dir: Path,
output_path: Path,
*,
root: Path = REPO_ROOT,
) -> Path:
"""Hash the one wheel, sdist, and plugin bundle for the current project version."""
version = _read_version(root)
artifacts = [
_require_single(dist_dir, f"celltypepilot-{version}-*.whl", "wheel"),
_require_single(dist_dir, f"celltypepilot-{version}.tar.gz", "source distribution"),
_require_single(
plugin_dir,
f"celltypepilot-plugin-{version}.zip",
"Agent plugin bundle",
),
]

names = [artifact.name for artifact in artifacts]
if len(names) != len(set(names)):
raise ValueError("release artifact basenames must be unique")

output_path.parent.mkdir(parents=True, exist_ok=True)
lines = [f"{_sha256(artifact)} {artifact.name}\n" for artifact in sorted(artifacts)]
with output_path.open("w", encoding="utf-8", newline="\n") as handle:
handle.writelines(lines)
return output_path


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--dist-dir", type=Path, default=Path("dist"))
parser.add_argument("--plugin-dir", type=Path, default=Path("release-assets"))
parser.add_argument(
"--output",
type=Path,
default=Path("release-assets/SHA256SUMS"),
)
args = parser.parse_args()
print(build_checksums(args.dist_dir, args.plugin_dir, args.output))


if __name__ == "__main__":
main()
4 changes: 2 additions & 2 deletions skills/celltypepilot/reference/outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ does not contribute marker evidence.

```json
{
"celltypepilot_version": "0.3.0",
"celltypepilot_version": "0.3.1",
"mkg_version": "mkg-2026.08.1",
"timestamp": "2026-08-06T12:00:00+00:00",
"input": {
Expand Down Expand Up @@ -126,7 +126,7 @@ Self-contained HTML report with embedded CSS. Sections:

A plain-text paragraph suitable for adaptation into a paper's Methods section. Example:

> Cell type annotation was performed using CellTypePilot (v0.3.0), an evidence-driven
> Cell type annotation was performed using CellTypePilot (v0.3.1), an evidence-driven
> annotation pipeline with built-in critic review. Marker gene evidence was sourced from
> the CellTypePilot Marker Knowledge Graph (MKG mkg-2026.08.1), a curated atlas integrating
> PanglaoDB, CellMarker, and Cell Ontology resources. For each of the N clusters identified
Expand Down
2 changes: 1 addition & 1 deletion src/celltypepilot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from importlib import import_module

__version__ = "0.3.0"
__version__ = "0.3.1"
MKG_VERSION = "mkg-2026.08.1" # Marker Knowledge Graph version

__all__ = [
Expand Down
16 changes: 9 additions & 7 deletions src/celltypepilot/literature.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import urllib.request
from dataclasses import dataclass, field

from . import __version__

USER_AGENT = f"CellTypePilot/{__version__} (https://github.com/HERRY423/CellTypePilot)"

# ──────────────────────────────────────────────
# Data classes
# ──────────────────────────────────────────────
Expand Down Expand Up @@ -102,7 +106,7 @@ def search_pubmed(
}
search_url = f"{PUBMED_BASE}/esearch.fcgi?{urllib.parse.urlencode(search_params)}"

req = urllib.request.Request(search_url, headers={"User-Agent": "CellTypePilot/0.3.0"})
req = urllib.request.Request(search_url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=10) as resp:
search_data = json.loads(resp.read().decode("utf-8"))

Expand All @@ -119,7 +123,7 @@ def search_pubmed(
}
fetch_url = f"{PUBMED_BASE}/efetch.fcgi?{urllib.parse.urlencode(fetch_params)}"

req = urllib.request.Request(fetch_url, headers={"User-Agent": "CellTypePilot/0.3.0"})
req = urllib.request.Request(fetch_url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=10) as resp:
# efetch returns XML, but we can parse basic info from esummary
pass
Expand All @@ -133,7 +137,7 @@ def search_pubmed(
}
summary_url = f"{PUBMED_BASE}/esummary.fcgi?{urllib.parse.urlencode(summary_params)}"

req = urllib.request.Request(summary_url, headers={"User-Agent": "CellTypePilot/0.3.0"})
req = urllib.request.Request(summary_url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=10) as resp:
summary_data = json.loads(resp.read().decode("utf-8"))

Expand Down Expand Up @@ -200,9 +204,7 @@ def search_biorxiv(

req = urllib.request.Request(
url,
headers={
"User-Agent": "CellTypePilot/0.3.0 (https://github.com/HERRY423/CellTypePilot)"
},
headers={"User-Agent": USER_AGENT},
)
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
Expand Down Expand Up @@ -454,7 +456,7 @@ def check_mcp_availability() -> dict:
test_query = "test"
params = {"db": "pubmed", "term": test_query, "retmax": "1", "retmode": "json"}
url = f"{PUBMED_BASE}/esearch.fcgi?{urllib.parse.urlencode(params)}"
req = urllib.request.Request(url, headers={"User-Agent": "CellTypePilot/0.3.0"})
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode("utf-8"))
if "esearchresult" in data:
Expand Down
4 changes: 3 additions & 1 deletion src/celltypepilot/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@
from datetime import datetime, timezone
from pathlib import Path

from . import __version__

ONTOLOGY_ENV_VAR = "CELLTYPEPILOT_ONTOLOGY_DIR"
ONTOLOGY_FILENAME = "cl.obo"
METADATA_FILENAME = "ontology_meta.json"
CL_OBO_URL = "http://purl.obolibrary.org/obo/cl.obo"
USER_AGENT = "CellTypePilot/0.3.0 (https://github.com/HERRY423/CellTypePilot)"
USER_AGENT = f"CellTypePilot/{__version__} (https://github.com/HERRY423/CellTypePilot)"


class OntologyError(ValueError):
Expand Down
Loading