Open-source design-controlled development infrastructure for medical device and SaMD teams — includes dhfkit
Project description
MedHarness
AI harness and DHF tooling for medical device software teams.
MedHarness structures how AI agents interact with a Design History File under IEC 62304 / FDA-regulated software projects. It pre-computes DHF context before an agent runs, enforces approval gates the agent must pass through, and commits decisions back into the DHF — so the engineer controls the feedback loop, not the agent.
It combines two packages:
medharness— CLI harness, CI gates, CR workflows, project scaffolding (init)dhfkit— standalone DHF engine for items, traceability, document generation, schema validation
Install
pip install medharness[full]
[full] pulls in optional extras: ai (Gemini-based AI review) and docs (PDF export via WeasyPrint).
Omit for a minimal install — the DHF engine (dhfkit) is always included.
Verify:
medharness --help
dhfkit --help
From source (development):
git clone https://github.com/itercharles/MedHarness
cd MedHarness
pip install -e ".[dev]"
pytest dhfkit/tests/ tests/
Quick Start
medharness init is zero-prompt — it scaffolds a single-repo project in the
current directory. The project name is derived from the directory name.
mkdir my-medical-device && cd my-medical-device
python -m venv .venv && source .venv/bin/activate
pip install medharness
medharness init
After init completes, here's what exists on disk:
my-medical-device/ # single repo — DHF + source together
├── DHF/
│ ├── config/
│ │ ├── global.yaml # project name, lifecycle states
│ │ └── doc_types/ # one YAML per type (SYS, CRS, SRS, SWDD, CR, …)
│ ├── items/ # one YAML file per requirement / risk / CR
│ │ ├── 01_crs/ # Customer Requirements (CRS-NNN.yaml)
│ │ ├── 02_sys/ # System Requirements (SYS-NNN.yaml)
│ │ ├── 03_srs/ # Software Requirements (SRS-NNN.yaml)
│ │ ├── 06_cr/ # Change Requests (CR-NNN.yaml)
│ │ └── ... # Use Cases, SOUP, Risk, Defects, etc.
│ ├── test-results/
│ ├── documents/
│ │ ├── specs/ # Jinja2 spec templates (.j2)
│ │ └── plans/ # development_plan.md, verification_plan.md, …
│ └── README.md
├── .github/
│ ├── workflows/
│ │ ├── engineering-control.yml # main CI: CR validation + coverage gate + evidence
│ │ ├── cr-analyze.yml # CR analysis from issues
│ │ ├── cr-develop.yml # CR development with AI
│ │ ├── cr-spec-iterate.yml # iterate on spec with review feedback
│ │ ├── cr-transition.yml # transition CR state in DHF
│ │ ├── cr-complete.yml # auto-close CR on PR merge
│ │ └── review-pr.yml # AI-assisted PR review
│ └── prompts/ # AI prompt templates
├── .claude/skills/ # Claude Code skills
├── tests/ # product test suite
├── CLAUDE.md # agent entrypoint
├── .gitignore
└── README.md # project README
The scaffolded items are starter samples — replace them with your project's real requirements, architecture, and plans before using this for a regulated product.
Initialize git and push:
git init && git add -A
git commit -m "feat: initialize My Medical Device with MedHarness"
git remote add origin https://github.com/<org>/my-medical-device
git push -u origin main
How a Change Request flows
Every non-trivial change starts as a Change Request (CR) in the DHF. CRs move through AI-assisted stages:
Issue → cr-analyze → cr-spec-iterate → cr-develop → cr-transition → cr-complete
| Stage | Trigger | What MedHarness does |
|---|---|---|
| cr-analyze | Issue labeled CR |
Pre-computes DHF context, runs Claude to write a technical spec, commits the spec to DHF/documents/cr-specs/ |
| cr-spec-iterate | Review feedback on spec | Resumes Claude session with feedback, updates spec, pushes revisions |
| cr-develop | Spec approved | Injects $DHF_CONTEXT, runs Claude to implement code, opens a PR |
| cr-transition | PR events | Transitions the CR to in_review/approved in the DHF |
| cr-complete | PR merged | Transitions the CR to complete, generates closing evidence |
At each stage MedHarness:
- Pre-computes context —
medharness dhf context for-stage <stage>returns focused JSON - Injects into agent environment —
$DHF_CONTEXTis available to Claude - Captures decisions back —
medharness dhf item transition --commit --push - Stores session IDs —
medharness ci claude-session put/getfor iterative review loops
The workflow YAML files for each stage are scaffolded by medharness init into
.github/workflows/.
Test Coverage Gate
The CI gate (medharness ci test-coverage) enforces that every verifiable requirement
has at least one passing test linked to it.
JUnit XML contract
Tests must emit JUnit XML with properties linking to DHF item IDs:
<testcase name="test_TC_SYS_005_001_validates_link_format">
<properties>
<property name="medharness.id" value="TC-SYS-005-001"/>
<property name="medharness.links" value="SYS-005"/>
</properties>
</testcase>
All property names are defined as constants in medharness/contracts.py:
| Property | Purpose |
|---|---|
medharness.id |
Test case identifier (e.g. TC-SYS-005-001) |
medharness.links |
Comma-separated DHF item IDs the test covers |
medharness.title |
Human-readable test title (optional) |
medharness.reviewer |
Reviewer name (optional) |
medharness.review_date |
Review date (optional) |
medharness.review_status |
Review status (optional) |
Python / pytest
Use pytest's record_property in conftest.py:
@pytest.fixture(autouse=True)
def _inject_medharness_metadata(request, record_property):
doc = request.function.__doc__ or ""
tc_id = extract_tc_id_from_name(request.node.name)
links = parse_links(doc) # extract @links:SYS-005 from docstring
if tc_id:
record_property("medharness.id", tc_id)
if links:
record_property("medharness.links", ",".join(links))
TypeScript / Vitest / Playwright
Use custom JUnit reporters that emit <properties> blocks for medharness.links.
Reference implementations are available in the WebTPS repo.
Running the gate locally
# From project root
pytest tests/ -q --junitxml=test-results/results.xml
medharness --dhf DHF ci test-coverage --junit-dir test-results
Expect output like:
[test-coverage] SRS: 12/14 covered
↳ uncovered: SRS-012
↳ uncovered: SRS-008
The command exits non-zero when gaps exist, blocking CI.
CLI Reference
Scaffold
medharness init # zero-prompt single-repo project setup
DHF operations (run with --dhf DHF)
medharness --dhf DHF dhf item list --type SYS
medharness --dhf DHF dhf item get SYS-001
medharness --dhf DHF dhf item create --type SYS --data '{"title": "My req"}'
medharness --dhf DHF dhf item update SYS-001 --data '{"title": "Updated"}'
medharness --dhf DHF dhf item delete SYS-001
medharness --dhf DHF dhf item transitions CR-001
medharness --dhf DHF dhf item transition CR-001 approved --by "Alice"
medharness --dhf DHF dhf validate schema
medharness --dhf DHF dhf validate traceability
medharness --dhf DHF dhf doc list
medharness --dhf DHF dhf doc generate SYS
medharness --dhf DHF dhf doc export SYS # PDF output (requires `[docs]`)
medharness --dhf DHF dhf test list
medharness --dhf DHF dhf config doc-types
CI gates
medharness ci dhf-validate --dhf DHF
medharness ci test-coverage --dhf DHF --junit-dir test-results
medharness ci evidence bundle --dhf DHF --out-dir artifacts
CR workflow commands
medharness cr workflow intake-github-issue-ci # CR intake from issue
medharness cr workflow complete-from-github-pr # CR completion on PR merge
Agent session helpers
medharness ci claude-session put <pr_number> <session_id>
medharness ci claude-session get <pr_number>
Python API
Use DHFClient for high-level operations (recommended for product repo automation):
from medharness.client import DHFClient
client = DHFClient(Path("DHF"))
cr = client.get_item("CR-034")
spec = client.get_cr_context("CR-034") # {"cr": {...}, "spec": "..."}
client.transition_item("CR-034", "in_review", performed_by="alice")
Or use dhfkit standalone (no dependency on medharness):
from dhfkit.local_adapter import LocalDHFAdapter
adapter = LocalDHFAdapter(Path("DHF"))
items = adapter.list_items("SRS")
Repository layout
| Directory | Purpose |
|---|---|
medharness/ |
CLI harness, CI gates, CR workflows, init scaffolding |
dhfkit/ |
DHF engine: items, lifecycle, traceability, document generation |
dhfkit/templates/ |
Starter DHF scaffold — config, specs, plans, sample items, CI workflows |
tests/ |
MedHarness and dhfkit test suites |
docs/ |
Architecture, ADRs, compatibility contracts |
dhfkit has no dependency on medharness — the engine can be used standalone.
Docs
- docs/architecture.md — packages, scaffold model, DHF lifecycle
- docs/compatibility-contracts.md — stable public contracts
- docs/adr/ — architecture decision records
- CHANGELOG.md — version history
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file medharness-0.2.1.tar.gz.
File metadata
- Download URL: medharness-0.2.1.tar.gz
- Upload date:
- Size: 111.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a133753a7171ef914ead6af6ebb2803cf7057f4e2e466f5429b2879b961515d
|
|
| MD5 |
952ce01955bb74af8b6777736cd61e61
|
|
| BLAKE2b-256 |
039b06adca0352726780d6833a1fb9fde1ecc3365eefc738f0494656e429bb5a
|
Provenance
The following attestation bundles were made for medharness-0.2.1.tar.gz:
Publisher:
release.yml on itercharles/MedHarness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medharness-0.2.1.tar.gz -
Subject digest:
5a133753a7171ef914ead6af6ebb2803cf7057f4e2e466f5429b2879b961515d - Sigstore transparency entry: 1446906937
- Sigstore integration time:
-
Permalink:
itercharles/MedHarness@8f1c504047ea97f0018a520f009123337f4e30ee -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/itercharles
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f1c504047ea97f0018a520f009123337f4e30ee -
Trigger Event:
push
-
Statement type:
File details
Details for the file medharness-0.2.1-py3-none-any.whl.
File metadata
- Download URL: medharness-0.2.1-py3-none-any.whl
- Upload date:
- Size: 145.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5247d19855a7146cf55502265813997fba6f1b899b487dc1dd39e827083f80ea
|
|
| MD5 |
643c129e01b17c4f08a884dff49b9db2
|
|
| BLAKE2b-256 |
b7b7f859c2335c70e80f7362a3cb41bbcf543f98ca8d7b499a0c981563399de5
|
Provenance
The following attestation bundles were made for medharness-0.2.1-py3-none-any.whl:
Publisher:
release.yml on itercharles/MedHarness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
medharness-0.2.1-py3-none-any.whl -
Subject digest:
5247d19855a7146cf55502265813997fba6f1b899b487dc1dd39e827083f80ea - Sigstore transparency entry: 1446907033
- Sigstore integration time:
-
Permalink:
itercharles/MedHarness@8f1c504047ea97f0018a520f009123337f4e30ee -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/itercharles
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f1c504047ea97f0018a520f009123337f4e30ee -
Trigger Event:
push
-
Statement type: