- Prioritize non-verbosity and information density.
- Prioritize elegance and simplicity: concise idiomatic Julia, simple data expressions over stateful loops, no abstract fields, no speculative abstractions.
- Type function arguments only when dispatch needs them.
- Do not use
isa/typeoftype checks for behavior that can be expressed with dispatch or small trait functions. - Use
!only for functions that mutate an argument. Internal mutating helpers should returnnothing; value-returning helpers should not use!. - Use low-level constructors like
new{typeof(...)}only in the minimal inner-constructor boundary that actually needs them. - Add helper functions only when reused or semantically justified.
- Reuse existing APIs before adding private wrappers.
- Add docstrings only for public-facing functions.
- Prefer domain names over implementation-mechanic names and explanatory comments. Add comments only when they add information.
- Avoid magic numbers. Name domain constants and test fixtures when the value carries meaning.
- Make semantic phases visible in code structure.
- Be terse. Assume expert user.
- Prefer commands/diffs over long explanations.
- Keep diffs tight. Do not reformat or clean up unrelated code.
- Reduce lines by simplifying design, not by compressing code.
- If the user gives an existing branch/worktree, use it after verifying it is the intended one.
- If starting feature/PR work yourself, create a unique branch from latest
origin/masterin a separate worktree; keep the main checkout onmaster. - If the active checkout is detached, create a unique local branch there before editing.
- Fresh worktree pattern:
git fetch origin && git worktree prune && git worktree add -b codex/<task> ../KomaMRI.jl-<task> origin/master. - If a branch/path exists, choose a new name. Do not force-remove unless asked.
- Never push unless the latest user message explicitly asks. Never push to
master. - When creating pull requests, open them ready for review unless explicitly asked for a draft PR.
- Do not prefix PR titles with
[codex]or other author/tool tags unless explicitly requested. - No destructive Git commands without explicit approval.
- Keep generated scratch artifacts under
.tmp/; never stage or push.tmp/contents.
- Monorepo packages: root
KomaMRI, plusKomaMRIBase,KomaMRICore,KomaMRIFiles,KomaMRIPlots. KomaMRICore,KomaMRIFiles, andKomaMRIPlotsdepend onKomaMRIBase; the root package depends on those three.- Each package has its own
Project.toml; tests use*/test/Project.toml; docs usedocs/Project.toml. - Root
[workspace]includestest,docs,benchmarks, and all subpackages. - Do not edit
Manifest.tomldirectly or commit manifest churn unless explicitly asked.
- Use the relevant project:
julia --project=<path>andPkg.activate(...); never use the global env by accident. - First action for Julia work: use or start the persistent Julia REPL; do not run Julia through
exec_commandunless isolation is technically required and stated first. - Use one persistent Julia REPL/session started with
--threads=autoand Revise for all Julia work, including diagnostics, plotting, scratch scripts, examples, and tests. Do not runjulia -e,julia script.jl, or package tests in fresh shell processes while a threaded REPL is available; send commands to the session. If code is too large to paste safely, write it under.tmp/and runinclude("...")from the existing REPL. Use a fresh process only when isolation is technically required or the user asks. Restart only if absent/crashed, incorrectly threaded, or the user asks. Keep it open. - On Julia 1.12+, Revise can handle struct redefinitions; do not restart only because a struct changed.
- Prefer workspace setup: activate root or the child project and
Pkg.instantiate(). Use explicitPkg.develop(path=...)only to reproduce CI or older Julia 1.10 wiring. - Change dependencies/compat with Pkg APIs, not casual
Project.tomledits. - Prefer command-line arguments for script options. Use environment variables only for actual environment/CI/backend semantics such as CPU/GPU/CUDA/Metal selection.
- For performance work: profile first, benchmark with
BenchmarkTools.@benchmarkand interpolated inputs, then optimize the actual hotspot. Never report one-off@elapsed/wall-clock timings as benchmark results. Warm the code first; for GPU benchmarks, synchronize the backend (for exampleCUDA.synchronize(),Metal.synchronize()) inside the measured function or immediately after the kernel work. - Never write raw
seq += ...in examples or generated code; use@addblock. - Never call
build_*only to extract events or duration and then rebuild the same block. Usemake_*for custom blocks, copy a built block when preserving its block semantics, or append the built sequence/block directly.
- Use
uvfor reproducible Python environments. Do not use barepip.
- In the persistent Julia session, run tests from the correct project with
Pkg.test(...); use the narrowest package first. - Usual package tests:
Pkg.test("KomaMRIBase"),Pkg.test("KomaMRICore"),Pkg.test("KomaMRIFiles"),Pkg.test("KomaMRIPlots"), rootPkg.test(). - For API, event-semantics, or compat changes, test downstream dependents before pushing;
KomaMRIBasechanges usually requireKomaMRICore,KomaMRIFiles,KomaMRIPlots, and root/docs checks as relevant. - Root and
KomaMRIPlotstests may needxvfb-runon headless Linux. KomaMRICorebackend tests usetest_args(CPU,CUDA,AMDGPU,Metal,oneAPI) or test preferences; do not commit backend GPU deps to shared test projects.- Use
@testitemtags::base,:files,:plots,:koma, and for core:coreplus exactly one of:motionor:nomotion. - If testing a PR implementation, add it with Pkg
url/rev; do not hand-copy files. - Use representative semantic variable names or formulas for expected values; avoid unexplained magic numbers in assertions.
- Do not introduce or loosen numerical tolerances merely to make a comparison pass. Use nonzero tolerances only when comparing different numeric precisions, reading values from a precision-truncating file format, or following an explicit numerical error model; document the source and expected scale of the error next to the assertion. Every tolerance change must be justified.
- For groups of semantically similar tests, add one compact comment above the group when intent is not obvious. Do not comment every assertion.
- Do not add tests that merely check a function equals its own definition or reimplement the same logic in the test. Test behavioral contracts, regressions, edge cases, and cross-implementation parity; do not add random assertions only to increase coverage.
- For all tests, prioritize physical, MRI, and behavioral correctness: where applicable, compare against an analytical result or an independent high-accuracy ODE solution, test conserved quantities or known invariants, and use cross-method/backend parity only when one path is a trusted reference.
- Every new test must have an explicit semantic purpose. Do not add
isa, output-type, allocation-shape, or “runs without error” assertions unless that exact API contract or regression is the subject of the test. - Every group of semantically related assertions must be preceded by one compact comment stating the physical, numerical, or behavioral invariant being tested. Do not narrate implementation steps or comment individual assertions.
- Prefer fewer strong tests over duplicated CPU/GPU or per-method copies. Use the existing backend loader so one invariant runs on the selected backend.
- Use the
docsenvironment. - Edit source docs,
lit-*.jl, andpluto-*.jl; do not edit generated tutorial artifacts. - In the persistent Julia session activated for
docs, build withinclude("docs/make.jl"); doctest withusing Documenter: doctest; using KomaMRI; doctest(KomaMRI). - With this repo's
MarkdownVitepressdefaults,docs/make.jlruns the full VitePress build; the final local site isdocs/build/1. Preview final docs by serving that directory, as DocumenterVitepress recommends. Use one server, reuse it when possible, and stop it when done. - The npm scripts in
docs/package.jsonrun VitePress ondocs/build/.documenter, which is the generated VitePress source. Usenpm run docs:dev,npm run docs:build, ornpm run docs:previewonly when intentionally using the VitePress workflow; do not treat.documenteras the final static site and do not servedocs/build/1through VitePress. - If VitePress components such as tabs are involved, verify the final
docs/build/1page is interactive and the referenced/assets/app.*.jsreturns 200. - For Literate docs, hide implementation-only setup and plotting plumbing with
#hide; show the result the reader should learn from. Avoid leaving a bare final variable such aspvisible when it only exists to render a plot. - Do not add special cases to Literate docs generation for individual pages or filenames. Use the standard
lit-*source and ignoredgen-*generated outputs; do not preserve legacy URLs by changing generator behavior. - Prefer examples that generate their figures from source data/code. Keep complex plotting code hidden unless the plotting code itself is the lesson.
- For interactive docs figures, prefer KomaMRI/KomaMRIPlots APIs and rendered interactive HTML. Do not replace them with static assets or low-level Plotly trace construction unless the user asks or there is no higher-level API.
- Preserve the paper links listed in
docs/paper-links.toml: when changing one of their destinations, update its mapping and verify the generated redirect indocs/build/1. - Keep
DocumenterVitepress.deploydocs(...)indocs/make.jl. - Do not commit files generated by
docs/make.jl, Literate, or PlutoSliderServer unless explicitly asked.
- PRs target
master, stay scoped, and include what changed, why, and what was tested. - Add trigger labels at PR creation when the first CI run matters:
documentationorrun-gpu-ci. Adding them after PR creation is too late for the first run. - Do not use the
pre-releaselabel unless the user explicitly asks to test Julia pre-release versions. - Register package versions only after the release PR is merged and
origin/mastercontains the target package/version. Do not trigger JuliaRegistrator from an open PR. - For subpackage registration, comment on the merged
origin/mastercommit, not the PR:@JuliaRegistrator register subdir=<PackageName>. PR comments do not trigger Registrator in this repo. - Registration comes before tagging/releasing: wait for the corresponding
JuliaRegistries/GeneralPR to merge. - Only the root package release is automatic through JuliaTagBot after General registration merges.
- Subpackage releases are manual after their General registration PRs merge: create annotated tags on current
origin/master, then push the tags and create GitHub releases:git fetch origin master --tags,git tag -a <tag> -F notes.md origin/master,git push origin <tag>,gh release create <tag> --title <tag> --notes-file notes.md. - Registrator notes must mention breaking status: include
## Breaking changesor stateNo breaking changes.
- Contributor workflow:
docs/src/how-to/5-contribute-to-koma.md - Pulseq MATLAB to Koma translation:
docs/src/how-to/pulseq-matlab-to-koma.md - CI truth:
.github/workflows/CI.ymland.github/workflows/CIPreRelease.yml