Changelog#
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Releases up to and including 0.1.1 shipped under the project’s former name,
materials-cli. Entries below that line name it as it was; they are a record of what happened, not of what the package is called now.
[Unreleased]#
Added#
A documentation site under
docs/(Sphinx + MyST + pydata-sphinx-theme): 61 pages organised as Diátaxis quadrants — getting started, six tutorials, ten how-to guides, seven explanation pages, eight architecture pages, areference/section (the CLI generated from the real Typer app, every config key, every material state), a 15-page API reference generated from docstrings, and five architecture diagrams rendered from committed scripts in matching light and dark themes. Every worked example is either captured from a real run or labelled as drawn from a recorded one. Not hosted yet — the README gives the four commands that build it locally. The doc toolchain lives in a PEP 735 dependency group, so it can never leak into a user’s install.NumPy-style docstrings across every public module, class and function, with 30 runnable doctests — which is what the API reference is generated from.
Changelog, licence and citation pages that include
CHANGELOG.md,LICENSEandCITATION.cffrather than restating them, so the site cannot carry a second copy that drifts from the files that ship.
Changed#
CI gained a documentation job: it builds the site with warnings as errors on every pull request, and checks that the generated CLI reference is not empty — the one failure mode a warnings-as-errors build cannot see.
The version is now guarded across all three places it is written (
pyproject.toml,matmelt.__version__,CITATION.cff); a release that updates one and not the others fails the test suite instead of shipping a tool that misreports its own version.
[0.2.0] - 2026-08-07#
Changed — BREAKING: the project is now MatMelt#
materials-cli is renamed to MatMelt. Same tool, same architecture, same data;
new name on every user-facing surface.
Before |
After |
|
|---|---|---|
PyPI distribution |
|
|
Command |
|
|
Import package |
|
|
Default config file |
|
|
Installer root |
|
|
Installer env vars |
|
|
Install-spec file |
|
|
Default remote job root |
|
|
Integration-test switches |
|
|
Migration. pip install --upgrade materials-cli does not cross over — the two
are separate PyPI projects and 0.1.1 stays the last materials-cli release.
pip install matmelt # or: curl -fsSL .../install.sh | bash
pip uninstall materials-cli # and, for an install.sh copy:
rm -rf ~/.local/share/materials-cli ~/.local/bin/materials-cli
Your existing runs keep working, untouched:
The state database (
state.sqlite), every per-material directory, and everyrecord.jsonare unchanged — no migration, no re-run.The OPTIMADE custom-field namespace stays
_mcli(and_mcli_*in exported CIFs) deliberately: renaming it would break every record already produced and every downstream consumer of the format.A
materials-cli.yamlleft in place is still read: when nomatmelt.yamlexists beside it, commands fall back to it and print a deprecation note on stderr (so--jsonstdout stays parseable). An explicit--configis always honoured verbatim. Rename the file at your convenience.
Added#
matmelt.commands.resolve_config_path()— the single place that decides which config file a command reads, with the legacy-filename fallback above.ui.console.render_legacy_config_note()and anote_console(stderr) for advisory messages that must not pollute--jsonoutput.
Notes#
_mcli-prefixed identifiers, themcli-<id>remote job-directory prefix, and the./materials-work/./materials-jobsscaffold defaults are unchanged — they name data and workspaces, not the tool.
[0.1.1] - 2026-07-31#
Fixed#
Package authorship now renders on PyPI. PEP 621 routes
authors/maintainersentries that carry an email into theAuthor-email/Maintainer-emailmetadata fields and those without one intoAuthor/Maintainer; PyPI’s project page shows the latter, falling back toAuthor-emailonly whenAuthoris empty. In 0.1.0 AMUZUGA Komi Djidoula Wisdom Sitou was the only entry with an address, so that was the one name the page did not display. Every author now appears in the rendered field while the contact address is retained. Metadata only — no code change.Releases can reach PyPI at all.
publish.yml’s job-levelpermissions:block listed onlyid-token: write, and a job-level block replaces the workflow-level one rather than merging with it, socontents: readwas dropped andactions/checkoutcould not read this private repository — reported as a misleading404 Repository not foundagainst a URL that plainly exists. Both permissions are now declared on the job.
[0.1.0] - 2026-07-29#
First public release: the full five-stage funnel (compose → structure → screen →
optional calculate → extract), local and SSH/SLURM execution, durable resumable runs,
and the init / doctor / run / status / monitor / retry / results / cif /
update command surface.
Added#
Repository presentation for a first release: a rewritten, deliberately light
README.md(funnel diagram, install, quickstart, config shape, command table — the full reference stays inguide.md), aCONTRIBUTING.mdcarrying the dev setup and the release checklist that used to sit in the README, aCITATION.cff(GitHub Cite this repository), and a.mailmapmapping the pre-2026-07-29 commits’ malformed author address onto the correct one.materials-cli cif— export the DFTB+ relaxed structures of materials that passed thecalculatestage as CIF files (<work_dir>/cifs/<id>.cif, or--out-dir;--materials/--materials-filenarrow the selection,--jsonreports machine-readably). The geometry is the one DFTB+ relaxed to (geom.out.gen), not the MACE-relaxed cell it started from:calc_result.jsononly ever recorded each attempt’s input parameters. Files are P1 with the WorkChain/CalcJob pks, energy, band gap, Fermi level and max force as_mcli_*provenance items. Materials that did not converge (or never reached calculate) are skipped and counted, never failed; a geometry that cannot be fetched fails only its own material. The state database is opened read-only.calc_result.jsonnow carriesfinal_geometry(gen+source_calc_pk), captured best-effort by the calculate runner from the last attempt’s retrieved folder — so exporting a structure calculated from now on needs no AiiDA lookup at all. Results written before this fall back to fetching the geometry from the user’s AiiDA repository (cif_runner.py, run under the calculate stage’s own interpreter, one batched call per export — the package still never imports aiida).Structure stage 1D / 2D / 3D dimensionality (
stages.structure.options.dimensions, default[3D]): each requested dimensionality is swept over its full symmetry family — 3D = 230 space groups, 2D = 80 layer groups, 1D = 75 rod groups — via the runner’s new--dimsflag (replaces--space-groupsas the selector;--space-groupsremains an optional per-dimension debug range limiter). Extract carries the newdimensionfield in each record’s_mcli.provenance.
Changed#
Structure child ids are now
<Formula>_<DIM>-<SG>(e.g.Cl1Na1_3D-225) — the dimensionality tag and space-group number joined with-, keeping the last underscore as the formula boundary and symbols (with path separators) out of ids. Each child’sstructure.jsongains adimensionfield.Project skeleton: src-layout package, Typer CLI entry point (
materials-cli) with--versionand honest stub commands (init,run,status,results,doctor), pyproject (hatchling;[compute]/[dev]extras), smoke tests, project docs.Core contracts and backends (Phases 1–4):
JobSpec/Handle/JobResultvalue objects, six-methodComputeBackendABC + registry, restart-safelocalbackend,ssh_execbackend with declarative rsync/SSH staging (remotes never receive package code), SQLite state store + single-threaded orchestrator (fan-out, retry budgets, in-flight job ledger, kill/resume), pydantic-v2 YAML config withenv:NAMEresolution.The four v1 stages (compose/structure/screen/extract), each with a self-contained runner script; dependency preflight, seed-enumeration report, and MACE-model status/consented-download machinery.
Full command UX (5.1):
initscaffolds a startermaterials-cli.yamland checks the environment;runpreflight-gates the active stages, obtains disclosed model-download consent (--yes), seeds, and drains with per-pass progress (Ctrl-C-safe resume,--max-passes);statusandresults(--export) read the run’s state store;doctordiagnoses backends and remote-aware stage readiness. All commands share--config/-c,--json, and the 0/1/2 exit-code convention.Distribution (5.2):
install.sh(curl|bash-safe, no sudo — Python ≥3.10 check with actionable failures, dedicated venv under~/.local/share/materials-cli, launcher symlink in~/.local/bin,MATERIALS_CLI_SPEC/_HOME/_BIN/_PYTHONoverrides, idempotent re-run = repair/upgrade) and thematerials-cli updatecommand, which upgrades in place from the spec the installer recorded and refuses cleanly on dev/pip installs.Own repository + release automation (5.3): the project moved to github.com/ShellRuner/materials-cli (CI live and green);
publish.ymlpublishes to PyPI onvX.Y.Ztags via Trusted Publishing, gated on the full check suite and a tag-matches-package-version check.Unary (single-element) compositions: a size-1 seed emits the elemental compositions
X1..Xndirectly (allotrope stoichiometries such as C60; SMACT’s charge balance cannot apply to one element and is never imported on that path), capped by the newmax_unary_atomscompose option (default: followsthreshold). Thethresholdandmax_unary_atomsoptions are now documented in theinitscaffold and README.Selective runs:
run --materials id1,id2/--materials-file FILErestricts any run — full or--only— to the named materials and their descendants (fan-out children stay eligible by lineage, across reruns);status --state <state>lists the material ids (and parents) at any state as the picking list.materials-cli retry: user-driven recovery of terminally-FAILEDmaterials without hand-editing the state DB. Resets each selected material back to the live state it was claimable from before it failed (the output state of its last completed stage), so the nextrunre-runs the stage it died on; the completions timeline is preserved and no item-dir files are touched. Selection defaults to all FAILED and narrows with--stage <name>and/or--materials/--materials-file; a preview grouped by stage (count + distinct-reason count + an example) precedes a confirmation prompt (--yesskips it,--dry-runpreviews only,--jsonemits the plan/result and applies only with--yes). Backed by the newStateStore.reset(id, target_state)primitive (FAILED-only, atomic, terminal-target-guarded).materials-cli monitor: a live, read-only terminal dashboard over a running (or resting) funnel — three stacked Rich panels: run overview (elapsed, done %, in-flight, failed, rolling throughput + naive ETA), stage pipeline (per-stage ready/running/ done/failed, speed, progress bar, backend with cached health), and recent failures (newest first, with reasons and failed-at times). Refreshes every--intervalseconds (default 2) viarich.live;--onceprints a single snapshot for scripting/logging;--track ID1,ID2/--track-file FILEswap the failures panel for a tracked-materials detail (state, time-in-state, in-flight job, failure reason; unknown ids shown as “not found”). The dashboard opens the state DB with the newStateStore(read_only=True)observer mode (PRAGMA query_only— the engine itself rejects writes, so watching a live run can never corrupt it); backend health probes are cached for 30 s, never per tick. Zero new dependencies (Rich only). Supporting state-layer additions, all migration-free:MaterialRecord.created_at/updated_at,completion_counts_since,recent_failures,earliest_created_at, and an idempotentstage_completions(completed_at)index existing databases gain on their next read-write open.materials-cli monitorexpanded to a five-panel dashboard with per-material live detail and richer visuals:Running now — one row per material on a live job (longest-running first, capped with a “+N more” line): stage, backend, elapsed, attempt number, and live CPU and GPU usage meters. Usage is sampled per job process-subtree — the
localbackend reads/proctick deltas (true instantaneous CPU%; a multi-worker job legitimately reads >100%) andnvidia-smi pmonfor GPU;ssh_execanswers from one batched remoteps/nvidia-smiround-trip. Sampling is best-effort telemetry that never raises: an un-probed remote, a SLURM-scheduled job, a machine withoutnvidia-smi, or a gone PID all render a dash, never a fabricated 0. Local sampling runs every tick; remote sampling is throttled to a 10 s TTL (the health-probe discipline).Recent completions — the success mirror of recent failures: newest DONE materials with how long each took (created → finished) and the finish time.
Run overview now leads with a proportional donut gauge (a Unicode ring that fills clockwise with done-%) and a stacked state-distribution bar segmenting every lifecycle state in a funnel gradient (warming to green at DONE; yellow EXPANDED, red FAILED), with a color-swatched legend of exact counts and percentages. Zero new dependencies (Rich only). Supporting additions:
StateStore.recent_successes, a per-jobUsageSampler(CPU + GPU via a duck-typed backend hook), andRunningMaterial/recent_successesonMonitorSnapshot.
Package attribution:
authors= Quantum ARISE, AMUZUGA Komi Djidoula Wisdom Sitou, LABBAH Elie;maintainers= AMUZUGA Komi Djidoula Wisdom Sitou, LABBAH Elie. The same three are credited inCITATION.cff, both people affiliated to Quantum ARISE.A full CI/CD pipeline:
ci.ymlnow runs lint → security → tests → build on every push and pull request, and isworkflow_call-able sopublish.ymlruns the identical gates on a tagged commit instead of a second copy that can drift. The new security job isbanditSAST oversrc/(configured in[tool.bandit], with each suppression justified per-line — no global skips) pluspip-auditover the declared dependencies. The new build job builds sdist + wheel, runstwine check, fails if the sdist contains private files, installs the wheel into a clean venv and runs the CLI from outside the source tree, then uploads the artifacts.
Fixed#
The top-level
--helptagline printedscreen → → extract:[calculate]in a Typer help string is parsed by Rich as a markup tag and silently swallowed. Regression-tested.Green CI. Three
--helptests asserted on Typer’s rendered output with a plain substring check. Rich treats GitHub Actions as a colour-capable terminal, so the help highlighter emitted--onlyas two separately-styled ANSI spans and the substring did not exist — the tests failed only in CI. Assertions now go through a sharedstrip_ansitest helper. Separately,ruff0.16 introducedRUF036and turned three untouchedX | None | intreturn annotations red; they are reordered, andruff/mypy/pytestare now pinned to a minor series so an upstream release can no longer break an untouchedmain.The sdist no longer ships the working tree. Publishing would have put the maintainer’s own
materials-cli.yaml(a real host and username), the internal notes (CLAUDE.md,TASKS.md,SESSION_NOTES.md,debug.md,mock_test.md,proposition/) and the README screenshots on PyPI.[tool.hatch.build.targets.sdist]is now an explicit allow-list (289 KB vs 692 KB), andpublish.ymlprints the sdist contents and runstwine checkbefore the irrevocable upload.Every published URL pointed at the old
ShellRuner/materials-clilocation and was dead:pyproject.tomlproject URLs, the installer’s documentedcurlline,guide.md’s install section andpublish.yml’s pending-publisher note now nameQuantum-ARISE-Acad/qa-materials-cli. Package author metadata and theLICENSEcopyright line name the maintainer instead of a placeholder.FakeBackend(test rail) fabricated every declared output as a file, so after fan-out stages began declaring theirchildren/directory as an output, a scripted fan-out job raisedNotADirectoryErrorand the orchestrator paused the stage every pass — hanging the CLI end-to-end tests in a silent infinite drain loop. Fabrication now writes scripted content first and treats directory-valued declared outputs as directories (recording them when present) instead of clobbering them into files.Fan-out stages (
compose,structure) now work on a remotessh_execbackend in staged mode. Their runner writes achildren/<id>/subtree (each child’s seed files, e.g.structure.cif) alongsidechildren.json, but only the manifest was declared as an output — so on a remote the subtree was never staged back and every material faulted at settle time (children/<id>/ is missing). The staging transport now carries directory outputs (ssh_exec._stage_outpulls each declared output basename-preserving into its parent dir; rsync-arecurses, so a file or a whole tree comes back identically;_collect_localuses.exists()), and the fan-out stages declarechildren/as a staged artifact.produces/validate_outputsstay onchildren.jsonalone, so the zero-children case remains valid.localwas unaffected (it runs in-place).The screen stage’s
model_pathcheck is now location-aware: when screen is wired to a remote backend, the model file is stat-ed on that remote (where screen actually runs) instead of the local disk.doctorno longer reports a valid remote model as “no such file”, andrunno longer refuses to start over a configured remote model it could not see locally. Mechanism: anssh_execcheck_remote_filehook (analogue of the existingcheck_remote_import), consumed bypreflight.mace_model_status; thelocalbackend keeps the localstat.
Changed#
run.combination_sizesomitted (or explicitly[]) now defaults to all sizes1..len(elements)instead of[2], and size1is legal in explicit lists (previously the floor was 2 and[]was a config error).A mid-funnel
pipeline.stop_afteris now a pause, not retirement: materials that complete the stop stage rest at its output state (STRUCTURED,SCREENED, …) — live and claimable, so raising or removingstop_after(or a laterrun --only <stage>) continues them on the same store. Finalization to terminalDONEhappens only at the configured last stage, where resting and finishing coincide (so the defaultstop_after: extractscaffold behaves exactly as before). Previously a mid-funnel stop finalized everything toDONE, which no later run could ever resume. Such runs now drain on a zero-activity pass and report where their materials rest.Runner scripts are no longer staged into material directories.
JobSpecgainedscratch_inputs— the subset ofinput_artifactsthat are a job’s own implementation (its self-contained runner) rather than the item’s science data. Thelocalbackend stages those into its private per-job dir (not the material dir) and points the command at that path, keepingcwdthe material dir;ssh_execstages them into the remote job dir (staged mode) or pushes only the runner into a per-job remote dir undershared_filesystem(closing a latent gap where the runner was referenced but never placed). Material directories now hold only science artifacts — a stage’s inputs and its declared outputs — at every point in a run, not just after release. Supersedes the earlier release-time scrub.