A community-driven, harness-agnostic collection of AI assistant configurations for academic data science work — skills, agents, commands, hooks, MCP configs, and planning templates. The content is harness-neutral Markdown; bin/install.sh installs to Claude Code and OpenCode today, and Cursor, GitHub Copilot, Windsurf and Gemini CLI are the harnesses the format is designed to reach next (see Install).
Status: both planes are built — 37 planner skills over 21 plugins / 59 skills / 8 agents, specified by 22 specs in
openspec/specs/. Every capability now has a toolbox. What that does not mean: most paths have never been run against their real tool — deposits, document builds, fetches and bundle emission are each gated, the gates are tested, and what sits behind them is not. The exception among deposits is Zenodo, whose deposit, relate and new-version path has run against the Zenodo sandbox (production Zenodo, OSF and DataCite have not). Read that narrowly: it is a claim about deposits, document builds, fetches and bundle emission.dataladis a hard precondition of the e2e suite and is exercised against the real tool unconditionally, and six other capabilities have real-tool paths behind gates. What is distinctive aboutcontainersis that its whole Docker → OCI →.sifpath was run end to end here — and, ironically, that it is the one real path CI cannot run, because apptainer is deliberately not installed there. The evaluation protocol is specified but unrun. No number in this repository comes from a measurement. Why this exists states what is built, what is specified, and what is a gap.
Governance should be a by-product of doing the work. Assistants now execute analyses faster than the surrounding record can be maintained by hand, and anything that requires a separate act of discipline gets skipped under deadline. So the harness routes every computation and every administrative change through one provenance chain — the record is produced by doing the work, not by remembering to. Why this exists makes the full argument: the problem, who the work is for, what it claims, how you would know, and what is not built.
Most AI coding assistant configurations are designed for software products: ship a package, cut a release, deploy a service. Academic data science has a different end goal — publish a research product. But a research product is no longer just a static PDF or a frozen dataset. This project treats it as a living research compendium:
- a provenanced dataset (versioned, citable, DOI-tagged), plus
- a re-executable article that regenerates its own figures and results (NeuroLibre-style), plus
- an agent-callable method bundle that exposes the work's methods as tools a future researcher's AI assistant can invoke on new data (Paper2Agent-style), plus
- a self-hostable deployment of the dataset and its services on data-sovereign infrastructure (Lab-in-a-Box-style),
all built from a single DataLad provenance chain and cross-linked by DOI. The goal is research that the next person — or the next agent — can build upon rapidly, not just read.
This project generalizes the best patterns from software development tooling for academic research workflows, with four priorities:
- STAMPED by default — every research object is built toward the STAMPED principles (Self-containment, Tracking, Actionability, Modularity, Portability, Ephemerality, Distributability). Tracking runs through DataLad, so the full chain from raw data to published result — and every administrative change — is recorded automatically.
- External standards as first-class citizens — STAMPED, BIDS, Neurobagel, SNOMED, OSF, Zenodo, NeuroLibre, Lab-in-a-Box, ORCID, CRediT, and reporting guidelines are integrated into the normal workflow, not bolted on at the end
- Research products are living — the default export re-executes (NeuroLibre), is agent-callable (Paper2Agent / MCP), and is self-hostable (Lab-in-a-Box), not a one-off artifact
- Administration is first-class — funding, ethics, data-management plans, deadlines, people, and credit are tracked alongside the science, with the same provenance discipline. This is what the by-product commitment above delivers: administration stops being the part reconstructed after the fact
Two planes of configuration. The content separates cleanly into a capability plane (thin wrappers over the technical tools — DataLad, Nipoppy, BIDS, containers, publishing, annotation) and a workflow plane (tool-agnostic research process that orchestrates those capabilities). DataLad is the exception that proves the rule: it is the provenance substrate, so the workflow plane uses it natively, the way code uses git, and records what each step did in its commit. This separation is STAMPED Modularity applied to the harness itself — and it is what makes the pieces recombine cleanly (see Architecture).
Target harnesses: Claude Code and OpenCode (installable today via bin/install.sh); Cursor, GitHub Copilot, Windsurf and Gemini CLI (designed for, not yet exercised — the content layer is harness-neutral, so a manual copy works anywhere)
Target workflows: data analysis, experiment design, literature review, reproducibility, project governance & compliance, administrative tracking & reporting, dissemination & living publications, long-term planning
The lifecycle is a scientific pipeline (stages 0–7) running inside a persistent administrative
track. DataLad is the connective tissue — every computation goes through datalad run /
datalad containers-run, and every administrative change is datalad save-d, so neither the
analysis chain nor the administrative record is ever broken. Each stage is driven by a workflow
plugin that calls down into one or more capability plugins (shown in parentheses).
| Stage | What happens | Workflow plugin (→ capabilities) |
|---|---|---|
| 0. Propose & Govern | Funding metadata, Data Management Plan, IRB/ethics, (optional) pre-registration, project-ledger init | govern |
| 1. Initialize | YODA dataset + BIDS layout scaffolded; environment/container; (optional) self-hosted lab infra | project (→ datalad, bids, containers) |
| 2. Curate | Raw → BIDS conversion (optionally via Nipoppy); annotate variables with Neurobagel / SNOMED | curate (→ nipoppy, bids, annotate) |
| 3. Analyze | Run comparisons and preprocessing pipelines via datalad run / datalad containers-run |
analyze, process (→ datalad, nipoppy, containers) |
| 4. QC / Review | BIDS validator; data quality checks; STAMPED / reproducibility audit | govern (stamped-assess), curate (→ bids), analyze |
| 5. Export | Bundle outputs; push dataset version to OSF / Zenodo | disseminate (→ archive) |
| 6. Publish | Update dataset_description.json; mint DOI; push Neurobagel graph |
disseminate (→ archive, datalad), curate (→ annotate) |
| 7. Disseminate & Report | Manuscript + living compendium (executable article + agent bundle + Lab-in-a-Box); reporting-guideline compliance; DOI cross-linking; progress/final reports | disseminate (→ compendium, liab, archive), project |
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ Manage & Comply lane (cross-cutting, runs across ALL stages) │
│ project ledger · obligations & deadlines · decision log · people/credit │
│ · status & funder reports · compliance audits · checkpointing (every turn) │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
▲ ▲ ▲ ▲ ▲ ▲ ▲
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ 0 Govern │→│ 1 Init │→│ 2 Curate │→│ 3 Analyze │→│ 4 QC │→│ 5-6 Pub │→│ 7 Dissem- │
│ │ │ │ │ │ │ │ │ │ │ + DOI │ │ inate │
└───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘
The Manage & Comply lane is the key conceptual addition: administration is not a single stage, it is a continuous track the whole pipeline runs inside. It is served by the tracking skills in the project workflow plugin and the compliance skills in govern, and it is backed by a single versioned Project Ledger.
Checkpointing lives in the lane, not on the spine. There used to be a Stage 4 — Checkpoint
here. It is gone, because a step that happens continuously is not a phase of a project: datalad-cli
ships a Stop hook that raises any unsaved work at the end of a turn, and the ledger has always recorded
{ op: checkpoint, stage: analyze } — an action inside a stage. The analyze/checkpoint skill
remains, for when the state is worth describing rather than merely saving.
The stage diagram above is a typical order, not a rigid pipeline. Real papers are a series of small comparisons that tell a story, usually with supplementary figures, and rarely developed in a perfectly linear order unless strictly pre-registered. New questions arrive mid-project — a reviewer's challenge, a conflicting result, a follow-up worth checking.
So rather than a fixed, up-front "analysis plan" stage, the harness treats an analysis as a lightweight, addable unit — a comparison — that can be introduced at any point and grouped into a product (a paper, a dataset release, a report). A comparison is one small record:
comparison: { id, what, why, inputs, expected_outputs,
rigor: exploratory | confirmatory, status, product?, prereg_id?, branch }
realized as a DataLad branch + datalad run off the shared dataset, and groupable into a product. The same record spans a rigor spectrum with two anchoring modes:
- Quick query / plot (left end). Spec is created and executed immediately — "show me X vs Y in this cohort." It lives only as a DataLad branch/run and has zero ledger footprint (STAMPED Ephemerality): explore freely, keep it only if it tells the story. It touches
project.yamlfor the first time only if promoted into a product. The bar to add one is a one-liner — less is more. - Pre-registered comparison (right end). The spec is frozen and registered before execution (
govern/preregister→ OSF Registrations / ClinicalTrials.gov / PROSPERO), and recorded in the ledger as an outstanding obligation ("a comparison that must be completed") in the Manage & Comply lane. It is executed later viaanalyze/run-comparison, and the result is checked against the registered spec — any deviation is reportable.
Both are the same schema on one spectrum; the confirmatory mode simply adds (1) freeze-the-spec-first and (2) a ledger obligation. This is exactly the STAMPED specification-centric research object view: the durable object is the spec (Self-contained, Tracked, Actionable, Distributable), while the run is Ephemeral. A collection of pre-registered comparisons is therefore just a to-do list of confirmatory obligations; a bag of quick queries is a set of branches you prune down to the ones worth publishing.
- Reuse over time. Because kept comparisons are annotated and provenanced, one from an earlier project can be picked up, re-run, and extended later — flexibility for new explorations is preserved without re-architecting the project.
The analyze/propose-comparison skill picks the rigor mode; analyze/manage-product groups the kept results; the "comparisons still to complete" view is simply the confirmatory obligations in the ledger.
The design uses two orthogonal axes. Understand them separately.
| Plane | What lives here | Rule |
|---|---|---|
| Capability (technical) | Thin, tool-scoped wrappers over one external tool each (Nipoppy, BIDS, containers, publishing, annotation), plus the DataLad toolbox the main thread reads natively. Mechanical, reusable STAMPED primitives. | A capability skill wraps a tool; it holds no research-process logic. A doer returns a command or the files it wrote; it never commits. |
| Workflow (conceptual) | Tool-agnostic research process, written in research vocabulary (govern, initialize, curate, analyze, disseminate). | A workflow skill runs DataLad itself, as code runs git: datalad save / datalad run, with DSH-* lines recording the step. Every other tool goes through its doer, asked in words (what, with which parameters, what to return), never as that tool's command line; the lint enforces this. |
This split is STAMPED Modularity (separation of concerns) + Actionability (the workflow is the executable spec that invokes actionable tool primitives). It is what the user's "keep the technical tools separate from the conceptual workflows" requirement buys: capabilities recombine under different workflows, and a workflow can swap one capability for another (e.g. Zenodo for OSF) without rewriting the process.
Each layer is independently useful:
| Layer | Format | Who needs it |
|---|---|---|
| 1. Content (source of truth) | Universal SKILL.md + .claude-plugin/plugin.json |
Everyone — contributors only write Markdown |
| 2. Installer (current convenience) | bin/install.sh |
Users who want automated OpenCode / Claude Code file installs without extra dependencies |
| 3. Package CLI (planned) | Python CLI (ds-harness) |
Users who want validation, update/remove state, and broader multi-harness translation |
| 4. Manual fallback | Per-harness docs + direct copies | Users in locked-down environments |
The content layer is plain Markdown + YAML. The shell installer is just a copier/translator for the supported on-disk layouts — you can clone the repo and manually copy files to any harness without it. The planned Python package adds validation and richer lifecycle commands, but is not required for install.
The target community (academic data scientists) already uses pip/uv. The format itself has zero Python dependency — Python is only needed to run the CLI installer.
All skills are authored once in a universal SKILL.md with a superset YAML frontmatter. The installer translates this into harness-specific output at install time — no duplication per harness. Capability and workflow skills share the format; they differ only in the plane field and (for capabilities) a stamped: tag.
---
name: plan-analysis
description: >
Guide statistical test selection for research datasets with QC checks.
Triggers when user asks to "plan analysis", "choose a statistical test",
or "what test should I use for this dataset".
plane: workflow # workflow | capability
when:
always: false
globs: ["*.R", "*.py", "*.ipynb"]
category: analyze
tools: [Read, Grep, Bash]
version: "0.1.0"
harnesses: [all]
---A capability skill additionally declares the STAMPED letters it serves:
---
name: datalad-run
plane: capability
stamped: [T, A] # this primitive advances Tracking + Actionability
category: datalad
...
---An agent may pin the model it runs on with an optional model:. Without it, the agent runs on the
harness default. The value must be one of the bare aliases haiku, sonnet, opus or fable, and
the lint checks it. A provider-prefixed value such as anthropic/claude-haiku-4-5 is rejected,
because producing target-specific forms is the installer's job. Only read-only agents (bids-doer,
coordinator) may pin a model. A doer that can change a dataset or publish runs on the session
default.
---
name: bids-doer
tools: Read, Bash, Grep, Glob
model: haiku # read-only: validates and reports
---Harness translation map:
| Universal field | Claude Code | Cursor (.mdc) | Copilot (.instructions.md) | Windsurf |
|---|---|---|---|---|
name |
name: |
frontmatter description: |
frontmatter | filename |
description |
description: |
description: |
filename | filename |
when.globs |
(auto-load in project) | globs: |
applyTo: |
triggers: |
when.always |
(user-invocable) | alwaysApply: |
always-loaded | global rules |
tools |
allowed-tools: |
(ignored) | (ignored) | (ignored) |
plane, stamped |
(metadata, validated) | (metadata) | (metadata) | (metadata) |
model (agents) |
model: as written |
— | — | — |
For OpenCode, the installer maps model: to anthropic/claude-haiku-4-5, anthropic/claude-sonnet-5,
anthropic/claude-opus-5 or anthropic/claude-fable-5-1. These resolve only when OpenCode has the
Anthropic provider configured. A value with no mapping is stripped, so the agent inherits the invoking
agent's model rather than failing to load.
21 plugins, split across the two planes: 15 capability plugins wrap the technical tools, and 6 workflow plugins encode the research process and call down into them.
Tables below separate what is built from what is planned. Planned entries are kept because
they carry design intent, but nothing in them exists on disk. The authoritative list of what is
built is openspec/specs/; openspec/changes/ holds what has
been decided next and is currently empty. Within the per-plugin skill lists, an unbuilt skill is
marked (planned).
A capability plugin is either a doer (a subagent owning tool mechanics) or a toolbox (a
*-cli plugin of skills per command, which the doer reads as reference material). DataLad is the
exception: it has a toolbox and no doer, because it is native to the main thread. Planners run
datalad directly, as they would git, and record each step in DSH-* commit lines.
Built:
| Plugin | Kind | Wraps | Contents | STAMPED |
|---|---|---|---|---|
datalad-cli |
toolbox | DataLad CLI | 1 skill (datalad, 19 verb references), rules, hooks, dsh-log |
T, S, M |
nipoppy |
doer | Nipoppy | nipoppy-doer |
S, T, M, A |
nipoppy-cli |
toolbox | Nipoppy CLI | 4 skills, one per command class | S, T, M, A |
bids |
doer | bids-validator | bids-doer |
S, M |
bids-cli |
toolbox | @bids/validator / legacy bids-validator |
1 skill + offline validator-presence check | S, M |
containers |
doer | Docker / Podman / Apptainer | containers-doer |
P, E |
containers-cli |
toolbox | Docker, Podman, Apptainer | 3 skills, one per job (author / build / convert), + offline runtime check | P, E |
archive |
doer | OSF / Zenodo / DataCite | archive-doer |
D |
archive-cli |
toolbox | OSF / Zenodo / DataCite APIs | 3 skills, one per backend, + offline readiness check | D |
annotate |
doer | Neurobagel / SNOMED / ReproSchema / NIDM | annotate-doer |
M, A |
annotate-cli |
toolbox | bagel-cli / pynidm / reproschema / SNOMED source | 4 skills, one per backend, + offline per-backend check | M, A |
compendium |
doer | MyST / Jupyter Book / repo2data / MCP bundles | compendium-doer |
A, P, E |
compendium-cli |
toolbox | mystmd, jupyter-book, repo2data | 4 skills, one per build job, + offline tool check | A, P, E |
liab |
doer | pyinfra, Forgejo | liab-doer — plans by default |
D |
liab-cli |
toolbox | pyinfra, Forgejo | 2 skills + offline tool check | D |
Every capability plugin that had an OpenSpec change has now shipped, so there is no planned-plugin
table here any more, and openspec/changes/ is empty. What is still open is not depth inside these
plugins but execution: the paths below the gates, described after the table.
The capability plane is the uneven half of the harness. datalad has 19 toolbox skills; annotate,
nipoppy and compendium have 4 each; archive has 3; liab has 2; bids has 1. containers
has 3, one per job. containers was the last capability without a toolbox; add-containers-toolbox
built one and reframed the capability around the path people actually use — author a Dockerfile from
the project's pinned manifest, build it with Docker or Podman, convert it to a .sif for a cluster.
It holds two pins that look like pedantry and are not: an unpinned manifest and a mutable image tag
both produce containers that run, produce numbers, and do not rebuild.
Unevenness in skill count is no longer the main gap, though. The sharper one is that most of
these paths have never run live. The archive skills were written against the real OSF, Zenodo and
DataCite APIs. Only the Zenodo path has run, against sandbox.zenodo.org: deposit, publish, relate and
new version (last run). Production Zenodo, OSF and
DataCite have taken no deposit through them. The same holds for MyST and Jupyter Book
builds, repo2data fetches, MCP bundle emission, and every annotate backend: each is gated, each
gate is tested, and what sits behind the gate is not. A green test suite here is evidence about the
gates, not about the tools.
Capability plugins are deliberately thin: they hold tool mechanics and the STAMPED primitives, nothing about why or when you run them.
Nipoppy as a cross-stage capability. If a project adopts Nipoppy as its dataset-management framework, it is more than a one-shot BIDS converter: it provides a standard CLI and config files (global_config.json, a manifest) that span Initialize → Curate → Analyze → QC — organizing the dataset, converting raw → BIDS, running preprocessing pipelines (fMRIPrep, QSIPrep, …) through containers, and tracking processing status. A project that declares its expected preprocessing pipelines at setup (see project/new-project) wires them into the Nipoppy config so each runs through the datalad capability's container-run with provenance intact.
| Plugin | Lifecycle stages | Built skills |
|---|---|---|
govern |
0 + Manage & Comply lane | init-ledger, obligations, preregister, dmp, ethics-track, qc-review, stamped-assess |
project |
1, 8 + Manage & Comply lane | new-project, log-decision, people, status-report, track-milestone, env-check, claude-config |
curate |
2, 5, 7 | raw-to-bids, annotate, deidentify, merge-data, gen-data-dict |
analyze |
3–5 | plan-analysis, propose-comparison, scaffold-analysis, run-comparison, plot, checkpoint, manage-product, gen-report |
process |
2–3 | run-pipeline |
disseminate |
6, 7, 8 | draft-manuscript, reporting-checklist, dataset-release, publish, link-outputs, executable-article, agent-bundle, liab-deploy, submission-track |
37 planner skills across the six. The lists below describe each; one, analyze/literature-search, is not yet built.
govern — Stand up and maintain the administrative + compliance backbone (stage 0 and the Manage & Comply lane).
obligations— surface and resolve deadlines, compliance requirements, and confirmatory-comparison commitments recorded in the ledgerpreregister— register the study (OSF Registrations / ClinicalTrials.gov / PROSPERO); record the registration ID into the ledger; used to freeze a confirmatory comparison's specqc-review— review a dataset's quality and completeness before it moves downstreaminit-ledger— createproject.yamlin a dataset that has none: the brownfield entry point for a study already under way. Refuses to overwrite an existing ledger and never backfills its log — the first entry records where the record beginsdmp— author/update a Data Management Plan against the RDA DMP Common Standard (maDMP) or a funder template, and turn each promise it makes into akind: dmpobligation. Never asserts a funder requirement it did not read; reports the sections you still have to fillethics-track— record IRB/IACUC protocol, approval, expiry and amendments as akind: ethicsobligation (expiry indue, protocol inref, amendments append tolog:); flag upcoming renewals. Never computes an expiry from an approval date, and never rules on whether a use is in scopestamped-assess— score a research object against the STAMPED checklist, per dimension against the requirement ids S.1 … D.3, with the evidence for each. No composite score (the framework sets no pass mark) and a dimension it could not check isunassessed, never zero. Subsumes the old compliance-audit + reproducibility-audit idea- References:
references/stamped.md
project — Scaffold a new research project and run the ongoing Manage & Comply lane.
new-project— YODA-structured DataLad dataset (run directly), BIDS layout (viabids), a basic scientific-Python container (viacontainers), a declared list of expected preprocessing pipelines wired into the Nipoppy config, CLAUDE.md, the project ledger — and, optionally, self-hosted Lab-in-a-Box infrastructure (Forgejo git host, HedgeDoc notes, dumpthings metadata) so the project lives on data-sovereign infra from day onelog-decision— write a decision record todocs/decisions/<date>-<slug>.md(decision, rationale, alternatives, scope), thendatalad saveitstatus-report— generate a progress / funder-RPPR-style summary from the ledger state + thedsh-logcommit history; read-only unless asked to keep a copypeople— manage collaborators / ORCID / CRediT contributor roles in the ledgerenv-check— report the project's tool dependencies as two separate findings: declared but absent (a setup step) and present but undeclared (a Portability defect,P.1). Runs each capability's own gate script rather than re-implementing it; never installs anythingclaude-config— generate CLAUDE.md, settings and MCP stubs from facts verified in the project. Writes no instruction it could not source, and never puts a credential in a committed stubtrack-milestone— add/update deadlines in the ledger askind: milestoneobligations, so "what's due" has one answer. Never invents a date and never moves one silently — a slip is loggedcoordinator— a read-only orientation agent, not a skill: it reports where a project stands so a fresh session can get oriented without reconstructing state by hand
curate — Get raw data into a standardized, annotated form.
raw-to-bids— convert raw acquisitions into BIDS (via thenipoppydoer), then validate (via thebidsdoer)annotate— enrich metadata so the dataset is self-describing:dataset_description.json, aparticipants.jsondata dictionary, BIDS sidecars, and optionally controlled terms via theannotatedoer (Neurobagel today; NIDM, ReproSchema and SNOMED report unavailable)deidentify— remove PHI as a recorded, provenanced step rather than an untracked fixup: onedatalad runper category, with what was deliberately kept and a required residual-risk statement in the ledger. Selecting and running the tools stays 🔧 do-it-yourselfmerge-data— combine tabular phenotypic/clinical sources as a provenanced run, with the join key supplied rather than inferred and the row/column arithmetic reported. A wrong join does not fail, it produces a tablegen-data-dict— generate aparticipants.jsondata dictionary: the skeleton from the data, the meanings from the user or a codebook. An undescribed column gets no entry and appears in the report rather than a guessedDescription
process — Run established preprocessing pipelines under provenance.
run-pipeline— execute a preprocessing pipeline (fMRIPrep, QSIPrep, …) through thenipoppydoer and run its command underdatalad run, so the run is containerized and recorded
analyze — The comparison/product engine (calls datalad, containers).
propose-comparison— create a comparison record; pick the rigor mode (quick query vs pre-registered)run-comparison— execute a comparison underdatalad runordatalad containers-runon its own branch; check confirmatory results against the registered speccheckpoint— take a described, clean snapshot of the dataset statemanage-product— group kept comparisons into a productplan-analysis— recommend a statistical approach from the design and the data's shape, and list the assumptions it rests on as unchecked; reports no p-value, effect size or power figure, because a number that arrives before the analysis will be quoted as if it came from onescaffold-analysis— emit a runnable, provenance-wrapped script stub for the chosen approach: inputs, outputs and run command, with aNotImplementedErrorwhere the model goes. It writes no analysis logic, and the placeholder fails loudly rather than returning a plausible numberplot— consistent exploratory and publication figures, written as a script and run throughrun-comparisonso each figure carries the analysis's provenance; every value shown comes from a produced output file, and nothing is mocked upgen-report— assemble results tables, QC metrics, figures and their commits into an internal report, with a required gaps section: assumptions never checked, runs that failed, outputs produced by hand. A missing value readsnot reported, never a plausible oneliterature-search(planned) — (scope deliberately thin; pending participant feedback) a lightweight BibTeX-collection helper (PubMed / Semantic Scholar), not a synthesis engine; the clearer value is connecting to meta-analytic tooling (NeuroSynth Compose / NiMARE)
disseminate — Turn the finished, provenanced work into publications and living products.
Classic outputs:
draft-manuscript— IMRaD scaffold; auto-fill Methods / Data-availability / provenance fromdatalad log+ the ledgerreporting-checklist— apply the right EQUATOR guideline (CONSORT / STROBE / PRISMA / ARRIVE) or, with the neuro pack, COBIDASdataset-release— bumpdataset_description.json, write a BIDSCHANGESentry,dataladgit tag, optional Zenodo DOI (via thearchivedoer)publish— push a released product to an archive and record the identifier it returnssubmission-track— record venue, date, status and decision as an append-onlysubmissions[]history on the product, so a resubmission does not erase the first venue's outcome. Never records an outcome that has not happened
Living research compendium:
executable-article— scaffold a NeuroLibre-style reproducible preprint: MySTmyst.yml+ Jupyter Book content, abinder/environment from the DataLad container digest, and arepo2datafile pointing at the OSF/DataLad-published dataset; wire figures to regenerate from the provenanced pipeline. Delegates to thecompendiumdoer, which invokes MyST, resolves each figure's output to the run that produced it, and builds in the project's container — reporting an untraceable figure asunprovenancedand a host build as unpinned.jupyter-book,repo2dataand the MCP scaffold all shipped inadd-compendium-capability.agent-bundle— Paper2Agent-style: synthesize an MCP server + parameterized tools from the project's scripts + data dictionary, emitted as the harness's own universalSKILL.md+plugin.json+ MCP config, with result-reproduction tests. This dogfoods the project's own content format. Delegates to thecompendiumdoer, whosemcp-scaffoldskill emits the bundle and then checks it with this repository's owntests/lint-plugins.py— the step that separates “emitted in the harness format” from “emitted in something that resembles it”. What it does not do is start the MCP server. Seeadd-compendium-capability.liab-deploy— Lab-in-a-Box-style: scaffold aliab-deployments(pyinfra) config that stands up self-hosted Forgejo + git-annex data serving and publishes the provenanced DataLad dataset via git-annex remotes — a data-sovereign distribution channel alongside the cloud-hosted article and agent bundle. Delegates to theliabdoer, which plans by default and applies only on an explicit instruction naming the target host, and which reportsappliedrather thanworkinguntil adatalad getretrieves annexed content from the self-hosted remote. Standing the instance up stays in the declarative deployment; creating the repository on a running instance is theforgejotoolbox skill, which refuses to guess whether it should be private.link-outputs— cross-link dataset / code / paper / preprint / pre-registration / executable-article / agent-bundle / Lab-in-a-Box DOIs & URLs using DataCiteRelatedIdentifierrelation types; write back to the ledgerproducts:anddataset_description.json- References:
references/equator-guidelines.md,references/datacite-relations.md
The administrative source of truth is a single machine-actionable file at the dataset root, sibling to dataset_description.json, and datalad save-d like any other artifact — so administrative metadata gets provenance by default too: every IRB amendment, DMP revision, milestone change, confirmatory-comparison obligation, or new DOI is a tracked commit. Every workflow skill reads/writes it; it auto-fills reports, drives the obligations/reminder surface, and powers STAMPED assessment. A human-readable PROJECT.md is generated from it on demand and never hand-edited.
# project.yaml — the project ledger, as schemas/project.schema.json accepts it today.
# This block is examples/project.yaml; `python3 schemas/validate-ledger.py examples/project.yaml` passes.
project:
name: demo-study
description: "Effect of X on outcome Y in cohort Z"
created: 2026-07-10T14:30:00Z
dataset_root: .
stack: python
products:
# Named deliverables grouped from kept comparisons (analyze/manage-product), released and
# cross-linked by disseminate/*. Empty at new-project; populated as the story takes shape.
- id: main-paper
kind: paper
title: "X reduces Y in cohort Z"
status: in-progress
comparisons: [cmp/group-diff-y]
outputs: [derivatives/cmp-group-diff-y/]
dois: []
relations:
- { relation: IsSupplementedBy, target: data-release }
- id: data-release
kind: dataset
title: "Cohort Z curated BIDS dataset"
status: planned
comparisons: []
outputs: [.]
dois: []
relations: []
obligations:
# Manage & Comply commitments (govern/*). Resolved by flipping status, never by deletion.
- id: prereg-h1
kind: preregistration
description: "Primary hypothesis (group difference in Y) frozen before data lock"
due: 2026-09-01
status: pending
ref: https://osf.io/xxxxx
contributors:
# People + CRediT credit (project/people); mirrored to dataset_description.json Authors.
- name: Ada Researcher
orcid: https://orcid.org/0000-0002-1825-0097
affiliation_ror: https://ror.org/00xxxxx
roles: [Conceptualization, Formal analysis, Writing – original draft]
# There is no activity log here. What happened, when and why is recorded in the commit that did
# it, as DSH-* lines: DSH-Op: propose-comparison / DSH-Stage: analyze / DSH-Product: main-paper
# Read it back with plugins/datalad-cli/scripts/dsh-log.sh (--legacy adds an older ledger's log:).
- { ts: 2026-07-10T15:40:00Z, op: run-comparison, stage: analyze,
note: "container-run stats.py; recorded commit a1b2c3d; outputs derivatives/cmp-group-diff-y/",
branch: cmp/group-diff-y }
- { ts: 2026-07-10T16:00:00Z, op: checkpoint, stage: analyze,
note: "datalad save end-of-session; decision log updated", branch: cmp/group-diff-y }
- { ts: 2026-07-10T16:30:00Z, op: manage-product, stage: analyze,
note: "grouped cmp/group-diff-y into product main-paper", branch: main }The schema is strict and small on purpose. additionalProperties: false everywhere, so a typo
is an error rather than a silently ignored key, and it accepts exactly five top-level sections:
project, products, obligations, contributors, log. The richer shape this README used to
show — study, funding, ethics, agreements, dmp, registration, people, milestones,
comparisons, infrastructure — is design intent, not the current schema; a ledger written that
way fails validation. The schema grows one section at a time, in the same change that introduces the
skill which writes to it.
The ledger ships with a JSON Schema (schemas/project.schema.json) so editors and CI can validate it; schemas/validate-ledger.py is the checker. Only project and log are required, the rest are additive — a project that only needs milestones and people can ignore the rest. Confirmatory comparisons are the only ones that enter the ledger (mirrored into obligations: as work "to be completed"); exploratory quick queries stay branch-only until promoted into a product.
Stage 7 produces a living research compendium: four coupled artifacts, all generated from the same DataLad provenance chain and cross-linked by DOI in the ledger.
| Artifact | What it is | How it's built | External tooling |
|---|---|---|---|
| Provenanced dataset | Versioned, citable data + analysis record | DataLad + BIDS, pushed via publish |
OSF / Zenodo |
| Executable article | A reproducible preprint that re-runs its own figures/results | disseminate/executable-article — MyST/Jupyter Book + binder/ env (from the DataLad container digest) + repo2data pointing at the published dataset |
NeuroLibre (MyST, Jupyter Book, BinderHub, repo2data) |
| Agent bundle | An MCP server exposing the work's methods as callable, tested tools | disseminate/agent-bundle — tools synthesized from the project's scripts + data dictionary, emitted as the harness's own SKILL.md + plugin.json + MCP config |
Paper2Agent pattern + Model Context Protocol |
| Self-hosted deployment | A data-sovereign home serving the dataset + lab services | disseminate/liab-deploy — a liab-deployments (pyinfra) config that stands up Forgejo + git-annex data serving and publishes the DataLad dataset via git-annex remotes |
Lab-in-a-Box (pyinfra, Podman, Forgejo, git-annex) |
Why this fits the architecture cleanly:
- NeuroLibre needs exactly what the harness already produces — a public code repo (notebooks / MyST), a data config, and a pinned, BinderHub-recognized environment. The
datalad,bids, andcontainerscapabilities already produce all three;executable-articlejust arranges them into NeuroLibre's expected layout. - Paper2Agent's output is the harness's own format — an MCP server + a manifest of tools. Because this project already authors universal
SKILL.md+ MCP configs and ships adapters for them,agent-bundleemits its output in that same format and reuses the existing adapter layer. The research product becomes installable into the next researcher's harness with zero new tooling. - Lab-in-a-Box gives self-hosted Distributability — instead of relying solely on external platforms,
liab-deployreproducibly stands up the lab's own git host + data-serving infrastructure. The samegit-annex/DataLad substrate the project already uses becomes its persistent, data-sovereign distribution point. It reuses thepublishcapability's annex-remote mechanics. - Cross-linking is provenance, not metadata gardening —
link-outputsrecords the relations (datasetIsSourceOfarticle; articleIsSupplementTopaper; agent bundleIsDerivedFromcode; Lab-in-a-BoxIsVariantFormOfdataset) using the DataCite schema, written back into the ledger anddataset_description.json.
data-science-harness/
├── README.md
├── LICENSE # MIT
├── pyproject.toml / uv.lock # Python toolchain for the check scripts
├── package.json / package-lock.json # Node toolchain — openspec, mystmd
├── environment.yml # conda env for the end-to-end stack
├── .nvmrc
├── bin/
│ └── install.sh # OpenCode / Claude Code file installer
├── .claude-plugin/
│ └── marketplace.json # plugin marketplace manifest
│
├── docs/
│ ├── stamped.md # STAMPED principles distillation
│ ├── end-to-end-workflow.md # full lifecycle walkthrough
│ ├── project-ledger.md # ledger conventions every planner reuses
│ ├── evaluation.md # evaluation protocol (nothing run yet)
│ ├── references/ # motivating literature — references.bib, index, notes
│ └── writing/ # manuscript craft reference bundle
│
├── openspec/
│ ├── specs/<capability>/spec.md # requirements as they stand today
│ └── changes/<change-id>/ # proposed work; archive/ holds merged changes
│
├── paper/ # the manuscript, as a MyST project
│ ├── myst.yml
│ └── sections/
│
├── bench/ # evaluation fixtures — probes, tasks, rubrics
│
├── resources/ # source PDFs (git-ignored)
│
├── schemas/
│ ├── project.schema.json # JSON Schema for the project ledger
│ └── validate-ledger.py # the validator
│
├── examples/
│ └── project.yaml # worked ledger sample (used by skills/hooks/tests)
│
├── templates/
│ └── skill/SKILL.md # universal skill template
│
├── plugins/
│ ├── */.claude-plugin/plugin.json # plugin manifests used by Claude Code and the installer
│ ├── */skills/*/SKILL.md # universal skill definitions
│ ├── */agents/*.md # Claude Code agents / OpenCode subagents
│ ├── */references/ # plugin reference material copied with bundles
│ └── */hooks/ # optional; only datalad-cli ships one
│
├── tests/
│ ├── lint-plugins.py # structural plugin/skill/agent lint
│ ├── lint-plugins-selftest.py # proves the lint still catches injected drift
│ ├── check-bench-fixtures.py # structural check over bench/
│ ├── check-paper.sh # builds paper/, fails on an unresolved citation
│ └── e2e-smoke.sh # DataLad provenance smoke test
│
└── .github/workflows/ci.yml # structure, specs, paper; e2e on dispatch
| Standard / Tool | What it does | Plane · Plugin | Install requirement |
|---|---|---|---|
| STAMPED | Operating principle framework — the properties every research object is built toward | workflow · govern |
reference-only (docs/stamped.md) |
| DataLad | Tracking backbone — records all analysis commands, inputs, outputs | native · datalad-cli |
pip install datalad |
| BIDS | Brain Imaging Data Structure — canonical neuroimaging dataset format | capability · bids |
npm install -g bids-validator |
| Nipoppy | Standardized dataset organization + pipeline running & tracking; spans curate → analyze → QC | capability · nipoppy |
pip install nipoppy |
| Apptainer / Docker | Portable, ephemeral computational environments | capability · containers |
container runtime |
| Neurobagel / bagel-cli | Annotate phenotypic variables with controlled terms; push to graph | capability · annotate |
pip install bagel-cli |
| SNOMED CT | Clinical terminology — normalize variable names to standard codes | capability · annotate |
SNOMED CT API key or local OWL |
| ReproSchema | Standardized representation of behavioral assessments / questionnaires | capability · annotate |
pip install reproschema |
| NeuroSynth Compose / NiMARE (proposed) | Reproducible coordinate-based meta-analysis of a topic | workflow · analyze |
web platform; pip install nimare |
| OSF / osfclient | Push dataset versions, register DOI | capability · publish |
pip install osfclient |
| Zenodo / zenodraft | Zenodo deposit — mint DOI, archive dataset release | capability · publish |
pip install zenodraft |
| Standard / Tool | What it does | Plane · Plugin | Notes |
|---|---|---|---|
| RDA DMP Common Standard (maDMP) | Machine-actionable Data Management Plan format | workflow · govern |
tracked in ledger dmp: |
| OSF Registrations / ClinicalTrials.gov / PROSPERO | Study pre-registration & registered reports | workflow · govern |
recorded in ledger registration:; freezes confirmatory comparisons |
| ORCID | Persistent researcher identifiers | workflow · project |
ledger people[].orcid |
| CRediT (NISO) | Contributor Roles Taxonomy | workflow · project |
ledger people[].roles |
| ROR | Research Organization Registry identifiers | workflow · project |
ledger affiliation_ror |
| Crossref Funder Registry / NIH RePORTER | Funder & grant identifiers, reporting deadlines | workflow · govern |
ledger funding[] |
| EQUATOR (CONSORT/STROBE/PRISMA/ARRIVE) | Reporting guidelines / checklists | workflow · disseminate |
reporting-checklist |
| COBIDAS (neuro pack) | Neuroimaging reporting standards | workflow · disseminate |
optional neuro reference pack |
| NIDM (neuro pack) | Machine-readable neuroimaging annotation & provenance | capability · annotate |
pip install pynidm |
| DataCite Metadata Schema | DOI cross-linking via RelatedIdentifier |
workflow · disseminate |
link-outputs |
| Standard / Tool | What it does | Plane · Plugin | Install requirement |
|---|---|---|---|
| NeuroLibre | Reproducible preprint server — re-executes the article | workflow · disseminate |
submission via GitHub editorial workflow |
| MyST / Jupyter Book | Executable-article authoring format | workflow · disseminate |
pip install mystmd jupyter-book |
| repo2data / BinderHub / repo2docker | Data + environment reproducibility for execution | disseminate + containers |
pip install repo2data |
| Paper2Agent + MCP | Convert the work's methods into an agent-callable MCP server | workflow · disseminate |
uses the harness's own SKILL.md + MCP format |
Lab-in-a-Box (liab-deployments) |
Self-hosted, data-sovereign deployment of the dataset + lab services | workflow · disseminate + project |
pip install liab-deployments; Podman + systemd + Caddy |
Design notes for contributors and hackathon participants. These describe the intended model; the
ds-harnesspackage and therequires:manifests below are not yet built.
Standards vs. dependencies. Most entries in the tables above are reference-only standards (STAMPED principles, BIDS conventions, SNOMED / NIDM / Neurobagel schemas, COBIDAS, EQUATOR, DataCite, maDMP, CRediT, ROR, ORCID, …). These need no installation — they live as Markdown in each plugin's references/ and are baked into skill prompts. Only a smaller set are executable dependencies that must actually be installed — and those live almost entirely in the capability plane.
Per-plugin dependency declaration. Each plugin's .claude-plugin/plugin.json would gain a
requires block, so dependency requirements stay tracked per module. No manifest carries one today:
"requires": {
"system": ["git", "git-annex", "datalad"], // OS / non-language tools
"python": ["bagel-cli", "pynidm", "reproschema"], // pip-installable
"npm": ["bids-validator"], // Node tools
"reference": ["stamped", "bids", "snomed-ct"] // no install — references/ only
}Executable dependencies fall into three tiers:
- Core (always):
git,git-annex,datalad(+ Python/pip-uv for the CLI) — declared once inharness.yaml; the Tracking substrate every project sits on (capability plugindatalad). - Cross-plane (shared by ≥2 plugins): container runtime (
containers+disseminate),nipoppy(nipoppycapability spanning curate → analyze → track, driven bycurate+analyze),osfclient/git-annex remotes (publish, used bydisseminate+liab-deploy),repo2data(containers+disseminate). The main source of inter-plane coupling — always via a capability plugin. - Step-localized (one plugin): e.g.
bids-validator(bids),bagel-cli/pynidm/reproschema(annotate),mystmd/jupyter-book(disseminate),liab-deployments(disseminate/project).
Dependencies span pip, npm/Node, and system packages, so ds-harness detects and advises: it verifies presence/version of every declared dependency, auto-installs only python: tools, and prints guidance for system: / npm: tools.
What ds-harness does beyond copying files. Translating and installing the Markdown/YAML content is the package's primary job; on top of that it adds a thin layer of deterministic support:
- Validation (schema-driven, runs as CI on PRs):
project.yaml, therequiresblocks, plugin-manifest and root-manifest cross-references, and the universalSKILL.mdsuperset frontmatter — including a plane check (workflow skills must not declare tool dependencies; capability skills must declare astamped:tag) and cross-harness translation-loss warnings. - Environment doctor against the declared
requires:. - Ledger read/query:
ds-harness obligations | status | validateprovide deterministic reads that back theobligations-duehook and status reports. Ledger edits stay with the skills/LLM. - Install-state tracking for clean
update/removeand drift detection.
Guiding line: the package owns deterministic, verifiable, harness-agnostic operations; skills/LLM own generative judgment (drafting a DMP, choosing a test, writing a manuscript). Every package operation is an optional convenience — if the CLI is absent, skills fall back to reading/writing the content directly (Design Rule 2). Deliberately out of scope: re-implementing provenance/analysis orchestration — DataLad and git already own those.
Each plugin carries one manifest at plugins/<name>/.claude-plugin/plugin.json. It is the
registration list: it declares the plugin's identity and enumerates the skills and agents that ship
with it. A skill or agent on disk but absent from the manifest will not load, which is a silent
failure — tests/lint-plugins.py checks the relationship in both directions and errors on either
side of the mismatch.
A capability plugin providing a doer:
{
"name": "nipoppy",
"description": "Nipoppy doer (capability plane): the tool subagent that constructs and validates nipoppy commands and hands dataset-mutating ones back to the planner to run under `datalad run`.",
"version": "0.1.0",
"author": { "name": "bcmcpher" },
"license": "MIT",
"keywords": ["nipoppy", "neuroimaging", "pipelines", "doer", "capability"],
"agents": ["./agents/nipoppy-doer.md"]
}A workflow plugin listing its planner skills:
{
"name": "govern",
"description": "Governance planner (workflow plane): the Manage & Comply lane + QC/review. …",
"version": "0.1.0",
"author": { "name": "bcmcpher" },
"license": "MIT",
"keywords": ["data-science", "govern", "preregistration", "obligations", "compliance", "workflow"],
"skills": [
"./skills/preregister",
"./skills/obligations",
"./skills/qc-review"
]
}The manifest does not carry harness metadata. plane, stamped and delegates_to live in each
skill's own frontmatter, not in the manifest, so a skill is self-describing wherever it is installed:
---
name: run-comparison
description: >
Execute a proposed comparison's analysis script with full DataLad provenance, on its branch,
inside the project container. Trigger on "run the comparison", …
plane: workflow
stamped: [A, T, P, E]
delegates_to: [containers]
---That split is deliberate. The manifest is what a harness reads to load files; the frontmatter is what
the harness-agnostic content says about itself. Putting plane in the manifest would mean a skill
copied out of its plugin no longer knows which plane it belongs to.
Every plugin must also appear in the top-level .claude-plugin/marketplace.json, or it is not
installable. The lint checks that too.
A plugin may ship a hooks/ directory — datalad-cli is the only one that currently does — and a
rules/ directory of short, always-loaded rules files.
datalad-cli makes DataLad ambient, the way git is. Its hooks act only inside a DataLad dataset,
and do nothing when datalad is absent. plugins/datalad-cli/README.md
has the full table.
- Session start (
dsh-status.sh): puts the DataLad rules and a status block in context: dataset, branch, dirty counts, subdatasets, siblings ahead or behind as of the last fetch, and the ledger stage and open obligations, naming any overdue ones (and those due withinproject.due_warn_days, when set). It never touches the network. - Before a shell command (
dsh-guard.sh): blocksgit commitandgit pushand names the DataLad command to use. It warns ongit annex add/drop/unlockand on a repeated-m, which DataLad silently drops.DSH_GUARD=0turns it off. - End of turn (
datalad-checkpoint.sh): if unsaved changes remain that it has not already raised, it asks the assistant once to save them with a message stating what and why, or to say why not. It does not commit.DATALAD_AUTOSAVE=1restores the old silent auto-save;DATALAD_AUTOSAVE=0turns the reminder off.
The current installer is bin/install.sh. It copies the existing Claude Code-compatible plugin content into the target harness's native file layout without changing the source plugins.
It takes --harness opencode|claude-code and --scope project|global, plus --target to override the resolved directory and --dry-run to print the copies it would make without touching the filesystem. With no arguments it installs every plugin for OpenCode at project scope — OpenCode is the default target, because Claude Code has its own native claude plugin install path and OpenCode does not.
The installer is deliberately a small file copier and translator, so someone in a locked-down environment can reproduce what it does by hand.
OpenCode project install:
bin/install.sh --harness opencode --scope projectOpenCode global install:
bin/install.sh --harness opencode --scope globalInstall selected plugins only:
bin/install.sh --harness opencode --scope project project analyze datalad-cliPreview an install:
bin/install.sh --harness opencode --scope project --dry-runRemove what an older install left behind — skills, agents and bundles whose plugin is gone from the
source (the retired datalad-doer, the per-verb datalad-* skills). Only what the installer put
there is touched; add --dry-run to list it first:
bin/install.sh --harness opencode --scope project --prune --dry-run
bin/install.sh --harness opencode --scope project --pruneFor OpenCode, the installer writes:
| Scope | Skills | Subagents | Plugin bundles / references |
|---|---|---|---|
| Project | .opencode/skills/<name>/SKILL.md |
.opencode/agents/<name>.md |
.opencode/dsh/plugins/<plugin>/ |
| Global | ~/.config/opencode/skills/<name>/SKILL.md |
~/.config/opencode/agents/<name>.md |
~/.config/opencode/dsh/plugins/<plugin>/ |
Plugins with hooks also get plugins/dsh-<plugin>.js, and plugins with rules get an entry in
opencode.json, in the same directory.
Hooks and rules for OpenCode. A plugin's hooks/hooks.json is written for Claude Code. For
OpenCode, the installer generates plugins/dsh-<plugin>.js in the target. That file reads the
installed hooks.json and runs the same scripts from OpenCode's plugin API:
| Claude Code hook | OpenCode plugin API | Effect |
|---|---|---|
SessionStart |
session.created event + experimental.chat.system.transform |
output is added to the system prompt, once computed per session |
PreToolUse (matcher Bash) |
tool.execute.before on bash |
exit 2 blocks the call with the script's message; a warning is prepended to the tool output |
Stop |
session.idle event |
a block decision's reason is sent to the session with client.session.prompt, once per state |
Any other event or matcher produces an installer warning naming it; nothing is dropped silently.
A plugin's rules/*.md files are added to the target's opencode.json instructions (created if
absent, other keys kept, no duplicates; needs python3 or node). The generated files are never
committed. For Claude Code, claude plugin install registers the hooks; a --harness claude-code
copy install does not, and says so.
OpenCode also discovers Claude-compatible skill paths such as .claude/skills/, but this installer writes to .opencode/ explicitly so OpenCode installs are easy to inspect and remove. Installed skill and agent copies have prompt-visible reference paths rewritten to the installed bundle location; source files under plugins/ are not rewritten.
Claude Code still supports its native plugin install path:
claude plugin install ./plugins/project
claude plugin install ./plugins/analyze
claude plugin install ./plugins/dataladThe shell installer can also copy Claude Code-native skill and agent files for a project or globally:
bin/install.sh --harness claude-code --scope project
bin/install.sh --harness claude-code --scope global project analyze dataladFor Claude Code copy installs, the installer writes .claude/skills/, .claude/agents/, and .claude/dsh/plugins/ for project scope, or the matching ~/.claude/ directories for global scope. This does not remove or alter .claude-plugin/plugin.json, so existing claude plugin install ./plugins/<name> workflows continue to work.
The planned Python CLI is not currently present in this repository. The intended interface is:
# Install all plugins for a specific harness
ds-harness install --harness=claude-code
ds-harness install --harness=opencode
ds-harness install --harness=cursor --scope=project
# Install only the capability plane, or a single plugin
ds-harness install --plane=capability --harness=claude-code
ds-harness install datalad --harness=copilot
# Dry run
ds-harness install --dry-run --harness=windsurf
# List, update, remove
ds-harness list
ds-harness update
ds-harness remove analyze --harness=cursor
# Validate a project ledger against the schema
ds-harness validate ./project.yamlOnce packaged, install the CLI with:
pip install ds-harness
# or
uv tool install ds-harnessNot built. There is no
harness.yamlon disk. What registers the plugin set today is.claude-plugin/marketplace.json, whichbin/install.shreads andtests/lint-plugins.pychecks every plugin against. The sketch below is the intended shape for a root manifest that also records the plane split and the supported harnesses.
name: data-science-harness
description: Community-driven AI assistant configuration for academic data science
version: "0.1.0"
plugins:
capability:
- ./plugins/datalad
- ./plugins/nipoppy
- ./plugins/bids
- ./plugins/containers
- ./plugins/publish
- ./plugins/annotate
workflow:
- ./plugins/govern
- ./plugins/project
- ./plugins/curate
- ./plugins/analyze
- ./plugins/disseminate
harnesses:
supported: [claude-code, cursor, copilot, windsurf, opencode, gemini-cli]Each rule is tagged with the STAMPED letter(s) it serves.
- Spec-first — all content is Markdown + YAML; no Python required to read or contribute. [A]
- Installer is optional —
bin/install.shand per-harness docs let users install without the CLI. [A, D] - Claude Code-native but not Claude-only — Claude Code plugin format is the reference; adapters translate outward. [P]
- One SKILL.md per skill — no duplication per harness; adapters generate harness-specific output at install time. [M, P]
- Capability vs workflow separation — tool mechanics live in capability plugins; research-process logic in workflow plugins. A workflow skill writes DataLad, git and git-annex commands directly, since they are native, and runs the harness's own scripts; for every other tool it asks the doer in words and never quotes that tool's command line. The lint errors on a quoted peripheral command in a workflow skill. [M, A]
- Community contribution = write Markdown — contributors don't touch Python code. [A]
- References stay in
references/— large domain knowledge lives inreferences/dirs, not in SKILL.md bodies. [S] - DataLad is the default run path — the always-loaded DataLad rules, the guard hook that blocks
git commit/git pushin a dataset, and the singledataladskill keep the provenance chain from being accidentally broken. [T, A] - Environments are pinned and containerized — the
containerscapability produces portable, disposable environments rebuilt from spec. [P, E] - Research products first — the default project export is a versioned, citable dataset, not a software package. [D]
- Provenance for administration too — administrative metadata lives in a versioned
project.yamlledger and isdatalad save-d; every ethics amendment, DMP revision, and DOI is a tracked commit. [T] - Obligations are first-class — deadlines, compliance requirements, and pre-registered comparisons are explicit, queryable ledger entries, never implicit. [T]
- Reminders degrade gracefully — a harness-agnostic on-demand
obligationsskill works everywhere; an optional Claude CodeSessionStarthook surfaces due items where hooks exist. [A] - Research products are living — the default export re-executes (NeuroLibre), is agent-callable (Paper2Agent / MCP), and is self-hostable (Lab-in-a-Box), built from the same provenance chain — never a one-off PDF. [D, E]
This project generalizes and re-partitions the Claude Code-specific plugins in my-skills across the two planes:
my-skills plugin |
data-science-harness plugin(s) | Plane | Notes |
|---|---|---|---|
stat-analysis |
analyze (+ curate merge/dict) |
workflow | Add universal frontmatter; add comparison engine |
project-init |
project |
workflow | Data-analysis project type; adds tracking skills + optional LiaB infra |
bids |
bids |
capability | Validation subset only — the doer is read-only and never modifies the dataset; scaffolding and workflow orchestration move to curate |
datalad-cli |
datalad |
capability | Core subset (run, container-run, save, clone, get, push, log, checkpoint) |
nipoppy-cli |
nipoppy |
capability | Full CLI wrapper, split by command class; orchestration moves to curate/analyze |
| — | containers |
capability | New — portability/ephemerality |
| — | disseminate/publish + archive |
workflow + capability | New — the planner skill is disseminate/publish; OSF/Zenodo/DataCite mechanics live in the archive doer |
| — | curate/annotate + annotate |
workflow + capability | New — the planner skill is curate/annotate; the Neurobagel/SNOMED/NIDM/ReproSchema wrappers live in the annotate doer and its four annotate-cli skills (openspec/specs/annotate) |
| — | govern |
workflow | DMP, ethics, pre-registration, STAMPED assessment |
| — | disseminate |
workflow | Manuscript, reporting guidelines, executable article, agent bundle, Lab-in-a-Box |
Using the harness needs nothing installed. bin/install.sh is a shell file copier and the
content is plain Markdown — see Install. The toolchains below are for checking and
building this repository, and each is pinned in a committed manifest, because STAMPED P.2/P.3 ask
that computational environments be explicitly specified and version controlled and it would be odd
to ask that of users and not of ourselves.
| Toolchain | Manifest | Locked | Enables |
|---|---|---|---|
| Python | pyproject.toml |
uv.lock |
the four check scripts |
| Node | package.json |
package-lock.json |
openspec validate, the paper/ build |
| e2e stack | environment.yml |
deliberately not — see the file | tests/e2e-smoke.sh |
# Python checks
uv sync --group dev
uv run python tests/lint-plugins.py --strict # plugin/skill/agent structure
uv run python tests/lint-plugins-selftest.py # meta-test of the lint
uv run python schemas/validate-ledger.py examples/project.yaml
uv run python tests/check-bench-fixtures.py # evaluation fixtures + routing ground truth
# Node CLIs (resolved from node_modules, not globally)
npm ci
npm run spec:validate # openspec validate --all --strict
npm run paper:check # build paper/ and fail on any unresolved citation
# End-to-end provenance smoke test (needs git-annex; apptainer is a system package)
conda env create -f environment.yml && conda activate ds-harness-e2e
bash tests/e2e-smoke.shThe e2e suite's Zenodo block runs against sandbox.zenodo.org only when DSH_ZENODO_SANDBOX_TOKEN is
set; docs/testing/archive-sandbox.md covers the token, what a
pass shows, and the last run.
Each check exits 2 when its own dependency is absent, so it degrades to a skip rather than a
failure. .github/workflows/ci.yml installs from the manifests and
treats a 2 as an environment error, since the lock file should have supplied the dependency.
CI checks structure and specs, not agent behaviour — see Evaluating the harness.
The forward plan no longer lives in this README. It lives in openspec/, where each
proposed change is a validated record rather than a checkbox in a 55 KB file:
openspec/specs/— 22 specs describing what the harness does today, grounded in the checks that already enforce it (tests/lint-plugins.py,tests/e2e-smoke.sh,schemas/project.schema.json).openspec/changes/— what we have decided to do next. It is currently empty. The "deepening the capability plane" roadmap that lived here has shipped: every change it named is archived,add-containers-toolboxclosed the last hole, and every capability now has a toolbox. An emptychanges/means the structure is finished; it does not mean the work is. What is open is execution, and the honest list of it is the Status note at the top of this file.openspec/changes/archive/— changes that shipped. A change leaveschanges/only when its tasks are done and its spec delta has been merged intospecs/, so the open list stays an accurate account of what is not built.
openspec list # proposed changes
openspec list --specs # current specs
openspec show <id> # a change or a spec
openspec validate --all --strictopenspec/README.md covers the conventions — including the fact that OpenSpec
calls a spec folder a "capability", which is not this repository's "capability plane".
Where the harness stands. Both planes are built. The workflow plane is 37 planner skills across
six plugins, each naming concrete ledger fields, DSH-* commit lines, and delegations; the
capability plane is seven doers, each paired 1:1 with a *-cli toolbox, plus the datalad-cli
toolbox the main thread uses natively. Depth across those toolboxes is still uneven by design rather
than by omission — datalad has 19 verb references because a provenance chain has that many
distinct operations, while bids has 1 skill because validation is one operation.
What is not closed is execution. openspec/changes/ is empty because the structure is finished,
not because the work is: most capability paths are gated, the gates are tested, and the tools behind
them have never run here. That is the subject of the next phase, and it is stated honestly at the
top of this file rather than tracked as an open change.
docs/evaluation.md specifies four probes — routing accuracy, provenance
completeness, end-to-end reproducibility, and cost — with declarative fixtures in
bench/. None has been run. The protocol is written before the capabilities it would
measure, deliberately.
The paper describing all of this is drafted in paper/ as a MyST project; its motivating
literature is tracked in docs/references/.
Two licences, split by what the file is rather than by what reads it:
| Covers | Licence | SPDX |
|---|---|---|
Content — plugins/**/*.md, docs/, templates/, openspec/, paper/, bench/, examples/, this README |
Creative Commons Attribution 4.0 International | CC-BY-4.0 |
Code — bin/, tests/, schemas/, the JSON manifests, .github/, the dependency manifests |
MIT | MIT |
See LICENSE, LICENSE-CONTENT.md, and
REUSE.toml for the path-by-path mapping. paper/myst.yml declares the same split
for the manuscript.
A SKILL.md is prose that instructs a model. Licensing it as software because a machine consumes it
would put the boundary in the wrong place — so it is content, and reuse requires attribution.
STAMPED D.3 asks that each module carry an explicit licence with a resolvable identifier; this
is the repository holding itself to the principle it publishes.
Contributions are Markdown-first. To add a new skill:
- Decide the plane: is this tool mechanics (→ a
capabilityplugin) or research process (→ aworkflowplugin)? Tool mechanics wrap a single CLI and declare astamped:tag; process logic runs DataLad itself and asks doers for everything else in words, without quoting another tool's command line. A new toolbox adds its tool's binary toPERIPHERAL_BINARIESintests/lint-plugins.py, so the lint catches planners that quote it. - Pick the right plugin (or propose a new one in an issue)
- Copy
templates/skill/SKILL.md, fill in the universal frontmatter (includingplane) and instruction body - Add the path to the plugin's
.claude-plugin/plugin.json(skills[]oragents[]) — the lint errors if you skip this, because an unregistered skill silently never loads - Open a PR
No Python knowledge required. The adapter layer is maintained by core contributors.