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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/release/Package.swift.template
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// swift-tools-version: 5.9

// TEMPLATE for the machine-generated release manifest -- the Package.swift that
// exists only at a tagged release commit ("B" in docs/ai/design/release-process.md).
// exists only at a tagged release commit, called "commit B".
// The _release-target-swift workflow interpolates the three MX_RELEASE
// placeholders (version, url, checksum) and the release-build prep job commits
// the result as B; commit R then restores the source manifest, so every tip of
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/_release-target-swift.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The Swift release target (docs/ai/design/release-process.md §8-9): builds the
# The Swift release target builds the
# Mx XCFramework from commit A and emits the two artifacts the release system
# consumes -- the publishable archive and the metadata the prep job commits as B.
# Called only from release-build.yml (workflow_call); never triggered directly.
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
set -euxo pipefail
# For a zip, swift package compute-checksum IS the file's SHA-256;
# assert that equivalence here so release-publish can re-verify with
# plain sha256sum (design §5, check 6).
# plain sha256sum.
CHECKSUM=$(swift package compute-checksum "out/dist/$ARCHIVE")
SHA256=$(shasum -a 256 "out/dist/$ARCHIVE" | cut -d' ' -f1)
if [ "$CHECKSUM" != "$SHA256" ]; then
Expand Down Expand Up @@ -158,7 +158,7 @@ jobs:
cp out/metadata/tree/Package.swift "$RUNNER_TEMP/manifest-check/"
(cd "$RUNNER_TEMP/manifest-check" && swift package dump-package > /dev/null)

# The target's contract fragment (design §9): identity, artifact,
# The target's contract fragment: identity, artifact,
# checksum, platforms, and the paths this target may occupy in B. A
# copy ships in both artifacts.
jq -n \
Expand All @@ -179,7 +179,7 @@ jobs:
> out/metadata/fragment.json
cp out/metadata/fragment.json out/dist/fragment.json

# The publishable artifact (design §6): archive + .sha256 + fragment.
# The publishable artifact: archive + .sha256 + fragment.
# retention-days is the documented 90-day contract: the tag must be pushed
# while this artifact still exists.
- name: Upload release artifact
Expand All @@ -190,7 +190,7 @@ jobs:
retention-days: 90
if-no-files-found: error

# The prep-job channel (design §9, outputs b and c): the exact tree files
# The prep-job channel: the exact tree files
# for commit B plus the contract fragment.
- name: Upload metadata artifact
uses: actions/upload-artifact@v4
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,7 @@ jobs:

# Proves mx compiles as a Swift package under AppleClang/SPM on every PR --
# the path komp takes when it depends on a sibling mx via `.package(path:)`.
# Runs no test suites; the binary xcframework ships from release-build.yml
# (docs/ai/design/release-process.md), not from CI.
# Runs no test suites; the binary xcframework ships from release-build.yml, not from CI.
#
# SwiftPM has no compiler-launcher hook, so incremental builds come from caching
# its .build tree: llbuild tracks inputs by content signature, so a restored
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Phase 1 of the release system (docs/ai/design/release-process.md): build a
# Phase 1 of the release system: build a
# chosen commit A for every release target, then open the release PR whose first
# commit, B, is the tag target. Nothing here publishes; phase 2 is
# release-publish.yml, triggered by the tag push after the PR merges.
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
[[ "$VERSION" =~ $SEMVER ]] \
|| { echo "::error::version must be MAJOR.MINOR.PATCH, optionally followed by a pre-release suffix such as -rc1, with no leading v, got '$VERSION'"; exit 1; }

# The target fan-out (design §9): one reusable workflow per release target.
# The target fan-out: one reusable workflow per release target.
# Adding a target = add a job like this one, and extend the hardcoded path
# ceiling in release-publish.yml with its declared metadata paths.
swift:
Expand Down Expand Up @@ -95,7 +95,7 @@ jobs:
shopt -s nullglob

if ! git merge-base --is-ancestor "$SHA" origin/main; then
echo "::warning::$SHA is not an ancestor of main yet; release-publish will refuse the tag until it is (design §5, check 3)."
echo "::warning::$SHA is not an ancestor of main yet; release-publish will refuse the tag until it is."
fi

FRAGMENTS=("$RUNNER_TEMP"/release-metadata/*/fragment.json)
Expand All @@ -115,7 +115,7 @@ jobs:
if [ -d "$d/tree" ]; then cp -R "$d/tree/." .; fi
done

# release-manifest.json (design §5): version, base SHA A, this run's
# release-manifest.json: version, base SHA A, this run's
# ID (the run pins itself into B -- dispatch inputs are not queryable
# later), every target's fragment, and the union of paths B may touch.
jq -s \
Expand Down Expand Up @@ -170,8 +170,7 @@ jobs:
set -euo pipefail
cat > "$RUNNER_TEMP/body.md" <<'BODY'
Machine-generated release PR for **v__VERSION__**, base commit A = `__A__`,
opened by [release-build run __RUN_ID__](__RUN_URL__). Process:
`docs/ai/design/release-process.md`.
opened by [release-build run __RUN_ID__](__RUN_URL__).

Two machine-written commits:

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Phase 2 of the release system (docs/ai/design/release-process.md §5): a v* tag
# Phase 2 of the release system: a v* tag
# push re-verifies commit B's provenance record against primary sources and
# publishes the already-built artifacts. Publishing never rebuilds anything: the
# bytes attached to the Release are the bytes release-build validated, fetched
# from that run by the run ID pinned inside B. Any check failing means no
# release, loudly (design §11) -- recovery is always: delete the tag, fix the
# release, loudly -- recovery is always: delete the tag, fix the
# cause, re-push.
#
# GitHub runs this file as it exists at the tagged commit, so only commits whose
# trees already carry release-publish.yml are releasable (design §4).
# trees already carry release-publish.yml are releasable.
name: release-publish

on:
Expand Down Expand Up @@ -37,7 +37,7 @@ jobs:
run: |
set -euo pipefail
fail() {
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing (docs/ai/design/release-process.md, section 11)."
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing."
exit 1
}
B=$(git rev-parse "refs/tags/$TAG^{commit}")
Expand Down Expand Up @@ -109,7 +109,7 @@ jobs:
run: |
set -euo pipefail
fail() {
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing (docs/ai/design/release-process.md, section 11)."
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing."
exit 1
}
# The API path scopes the lookup to this repository, so a run ID from
Expand All @@ -127,7 +127,7 @@ jobs:
run: |
set -euo pipefail
fail() {
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing (docs/ai/design/release-process.md, section 11)."
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing."
exit 1
}
: > assets.txt
Expand Down Expand Up @@ -172,7 +172,7 @@ jobs:
run: |
set -euo pipefail
fail() {
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing (docs/ai/design/release-process.md, section 11)."
echo "::error::release-publish: $*. Nothing was published. Recover by deleting the tag (git push origin :refs/tags/$TAG), fixing the cause, and re-pushing."
exit 1
}
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
Expand Down
114 changes: 55 additions & 59 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,36 @@ You should, however, automatically fix stale code examples.

```
mx/
AGENTS.md <- you are here
.claude/skills/ <- agent skills; every skill name starts with mx-
Makefile <- top-level build driver
cmake/ <- CMake helpers: GitSha.cmake, and the backend-pugixml consumer project
that `make backend-pugixml-test` builds (mx against a stock pugixml)
scripts/ <- maintenance scripts (vendor_pugixml.py refreshes src/private/mxpugi)
Dockerfile <- mx-sdk image with toolchains and dev tools
CMakeLists.txt <- C++ project
Package.swift <- Swift Package Manager wrapper (built by the swift CI job)
data/ <- MusicXML test corpus (large, see data/README.md)
docs/ai/design/ <- design docs
docs/musicxml-4.0-ed15c23.xsd <- the current musicxml.xsd schema
src/ <- C++ sourcecode root
src/include/mx/api/ <- the public mx::api headers; mx's public interface
src/private/ <- C++ source
mxpugi/ <- vendored pugixml 1.16 under the private mxpugi namespace (generated by
scripts/vendor_pugixml.py; XML parser and raw DOM)
mx/xml/ <- the XML seam: mx::xml::{Document,Node,Attribute} value handles, the
XmlBackend concept, and the pugixml adapter (mx/xml/pugixml/); the only
code under mx/ that includes mxpugi (docs/ai/design/xml-backend.md)
mx/core/ <- mx::core's hand-written runtime (outside of mx/core/generated)
mx/core/generated/ <- MusicXML XSD C++ typed model (generated, see gen/cpp)
mx/api/ <- mx::api implementation
mx/impl/ <- the api to core translation layer
mx/utility/ <- helpers
mx/examples/ <- api example programs
mxtest/core/ <- mx::core unit tests (small)
mxtest/corert/ <- C++ core roundtrip test (deserializes and serializes the test corpus)
mxtest/import/ <- normalization support (Normalize.cpp/h + DecimalFields.h) shared by corert and api tests
mxtest/api/ <- tests for the mx::api layer
mxtest/impl/ <- tests for the mx::impl layer
mxtest/file/ <- file-walking test infrastructure for the mx::api tests (MxFile, MxFileTest, etc.)
mxtest/control/ <- compile-control flags (CompileControl.h: enables/disables test suite compilation)
cpul/ <- vendored Catch2 test runner
gen/ <- code generator system (see gen/README.md)
cpp/ <- C++ target (the product): config.toml + templates
schema/ <- JSON Schema target
test/go/ <- A toy Go implementation of MusicXML XSD for gen validation
test/c/ <- A toy C implementation of MusicXML XSD for gen validation
audit/ <- MusicXML feature-audit tool (see audit/README.md); `make audit`
.claude/skills/ <- agent skills; every skill name starts with mx-
Makefile <- top-level build driver
cmake/ <- CMake helpers and the backend-pugixml consumer project
scripts/ <- maintenance scripts (vendor_pugixml.py refreshes mxpugi)
Dockerfile <- mx-sdk image with toolchains and dev tools
deslag.toml <- prose lint config; `make fmt-check` runs deslag
data/ <- MusicXML test corpus (large, see data/README.md)
docs/ai/design/ <- design docs
docs/musicxml-4.0-ed15c23.xsd <- the current musicxml.xsd schema
src/include/mx/api/ <- the public mx::api headers; mx's public interface
src/private/mxpugi/ <- vendored pugixml 1.16 in the mxpugi namespace (generated)
src/private/mx/xml/ <- the XML seam; the only mx/ code that includes mxpugi
src/private/mx/core/ <- mx::core's hand-written runtime
src/private/mx/core/generated/ <- MusicXML XSD C++ typed model (generated, see gen/cpp)
src/private/mx/api/ <- mx::api implementation
src/private/mx/impl/ <- the api to core translation layer
src/private/mx/utility/ <- helpers
src/private/mx/examples/ <- api example programs
src/private/mxtest/core/ <- mx::core unit tests (small)
src/private/mxtest/corert/ <- core roundtrip test over the test corpus
src/private/mxtest/import/ <- normalization shared by corert and api tests
src/private/mxtest/api/ <- tests for the mx::api layer
src/private/mxtest/impl/ <- tests for the mx::impl layer
src/private/mxtest/file/ <- file-walking infrastructure for the mx::api tests
src/private/cpul/ <- vendored Catch2 test runner
gen/ <- code generator system (see gen/README.md)
gen/cpp/ <- C++ target (the product): config.toml + templates
gen/schema/ <- JSON Schema target
gen/test/ <- toy Go and C targets that validate the generator
audit/ <- MusicXML feature-audit tool (see audit/README.md)
```

## Feature audit (`audit/`)
Expand Down Expand Up @@ -113,8 +103,8 @@ instead:
- In code generator templates (`gen/cpp/templates/`): use `{{ident}}` to make names unique, e.g.
`k{{ident}}Wire`, `isNameChar{{ident}}`.

Unity builds can be tested with CMake's built-in support — no changes to `CMakeLists.txt` are needed
by contributors. To verify, configure with:
Unity builds can be tested with CMake's built-in support; contributors do not need to change
`CMakeLists.txt`. To verify, configure with:

```
cmake -S . -B build/unity -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=0
Expand All @@ -131,12 +121,12 @@ than the strongly-typed `mx::core` model. No core type may appear in a public ap
- New fields that can be absent: use `std::optional<T>` (values and sub-structs alike). Precedent:
`NoteData::tieLetRing`, `PartData::transposition`, `MeasureData::partSymbol`.
- Exception, enums: an absent-able enum gets an `unspecified` enumerator as its first value and
default — never `std::optional<SomeEnum>`. This includes the ternary
default, never `std::optional<SomeEnum>`. This includes the ternary
`Bool { unspecified, yes, no }` in `ApiCommon.h`; use it, never `std::optional<bool>`.
- Do not mass-migrate existing `bool is...Specified` / `has...` flags or `-1` sentinels; they are
legacy (issue #249). Migrations are separate, deliberate breaking changes.
- Every new field needs a default value and a matching `MXAPI_EQUALS_MEMBER(field)` line
(`MXAPI_DOUBLES_EQUALS_MEMBER` for doubles) in the type's equality block — a missed line silently
(`MXAPI_DOUBLES_EQUALS_MEMBER` for doubles) in the type's equality block. A missed line silently
drops the field from equality and round-trip checks.

### Design doctrine
Expand All @@ -147,21 +137,27 @@ no exceptions, `Result` quarantined), choice types, and comment rules.

## Quality gates

Run `make fmt` to format. `make fmt-check` is the clang-format gate **only** — it builds and tests
nothing. Run `make core-roundtrip-test` for corert (especially `mx/core` work) before pushing core
work. `make api-test` runs the api/impl suite + examples; `make api-roundtrip` and the gen/audit
gates run separately. `make test-all` runs every C++ suite at once (core roundtrip + unit + api-test
\+ api-roundtrip) — the deep gate CI runs on Linux (see `make help` / CI). Adding/removing a `data/`
file: bump the pinned count in `CoreRoundtripTest.cpp`, run `make audit` (regenerates `corpus.xml` +
`*.features.xml`), confirm round-trip via `make core-roundtrip-test`. `ApiLoadSmokeTest` proves a
file imports without crashing, not that the data is correct; the read→write→read gate
(`make api-roundtrip` / `roundtrip-baseline.txt`) is the correctness check — pin a fixture there to
defend a feature. `make wasm-test` builds `mx::api` for Emscripten and runs the examples under Node
— CI's check that mx still compiles and works client-side in a browser (a consumer, denigma, depends
on this; issue #386). `make backend-pugixml-test` builds mx against a stock pugixml
(`MX_XML_BACKEND=pugixml`, see the README's "Bringing your own XML library") and runs the core
suites — CI's check that the XML backend option works; `make symbol-check` only applies to the
default vendored build.
Run `make fmt` to format. `make fmt-check` is the formatting and prose gate: it runs clang-format
and `deslag check` (which reads `deslag.toml`), and it does not build or run tests. Fix what deslag
reports by its advice; only the owner changes its limits or adds an `allow` entry.

Run `make core-roundtrip-test` for corert (especially `mx/core` work) before pushing core work.
`make api-test` runs the api/impl suite + examples; `make api-roundtrip` and the gen/audit gates run
separately. `make test-all` runs every C++ suite at once (core roundtrip + unit + api-test +
api-roundtrip), the deep gate CI runs on Linux (see `make help` / CI).

Adding/removing a `data/` file: bump the pinned count in `CoreRoundtripTest.cpp`, run `make audit`
(regenerates `corpus.xml` + `*.features.xml`), confirm round-trip via `make core-roundtrip-test`.

`ApiLoadSmokeTest` proves a file imports without crashing, not that the data is correct. The
read->write->read gate (`make api-roundtrip` / `roundtrip-baseline.txt`) is the correctness check;
pin a fixture there to defend a feature.

`make wasm-test` builds `mx::api` for Emscripten and runs the examples under Node: CI's check that mx
still compiles and works client-side in a browser (a consumer, denigma, depends on this; issue
#386). `make backend-pugixml-test` builds mx against a stock pugixml (`MX_XML_BACKEND=pugixml`, see
the README's "Bringing your own XML library") and runs the core suites: CI's check that the XML
backend option works. `make symbol-check` only applies to the default vendored build.

Look at what will run in CI `.github/workflows/ci.yaml` and anticipate issues there when coding
locally. Code coverage is not part of the normal CI run; trigger it on demand with a `/coverage`
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ project(mx CXX)
# + the mx::xml seam over the XML library (src/private/mx/xml, backed by mx's
# private copy of pugixml, mxpugi, unless MX_XML_BACKEND says otherwise), plus
# the corert/unit test binaries.
# See AGENTS.md, docs/ai/design/mx-core-plan.md and docs/ai/design/xml-backend.md.
# See AGENTS.md and docs/ai/design/xml-backend.md.

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -108,7 +108,7 @@ set(PRIVATE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/private")
set(PUBLIC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/include")

# mxpugi: the XML library behind mx::core's XML seam in the default
# (vendored) mode (plan §2.1 chose pugixml). It is pugixml, vendored at
# (vendored) mode (mx::core uses pugixml directly). It is pugixml, vendored at
# src/private/mxpugi by scripts/vendor_pugixml.py with its namespace renamed
# from `pugi` to `mxpugi` (stock pugiconfig). The private copy exists so that
# mx does not define any `pugi::` symbol: a consumer that links its own
Expand Down
Loading
Loading