@miadi/composition-to-episode
v0.2.0
Published
Studio compositions and chronicle episodes, related in both directions. Resolves a composition by portable ref (aureon/ava002) under $MIADI_STUDIO_DIR, weaves its provenance into an episode without ever rewriting the composition, and reads the link back —
Readme
@miadi/composition-to-episode
The migration weaver. This package reads a legacy gmtermux composition folder and weaves its provenance into a miadi-chronicle episode:
composition-to-episode weave <dir> --number NThe word "composition" in this package's name refers to the legacy source
format — the gmtermux compositions*/ folder with its lazy
composition.json manifest — not to the musical-composition studio domain.
The studio domain lives at @miadi/musical-composition; the device act of
capturing lives at @miadi/capture. This package is neither: it is the
instrument that carries the old world into the new one.
Transitional, strangler-era
This is migration tooling. It exists because years of gmtermux compositions predate the chronicle and must cross over without being rewritten. It is the instrument that will execute the 74-row migration map ruled in the episode 318 vessel:
/srv/miadi/episodes/miadi-chronicle/2026-08-09-episode-318-a-folder-is-a-composition-the-capture-service-the-code-cannot-yet-hold/MIGRATION-MAP-compositions-to-chronicle-260809.mdWhen the corpus has crossed and the capture family owns new takes end-to-end, this package's work is done. It accretes no studio features and no capture features — a strangler-fig instrument, not a domain home.
The three roles, separated
Three roles were once entangled in @miadi/composition. They are now fully
separate — otherwise, in the human's ruling, they "will always be confused":
| Role | Package |
| --- | --- |
| Studio artifact domain (key, capo, tempo, sections, chords) | @miadi/musical-composition |
| Migration weaver (this package — transitional) | @miadi/composition-to-episode |
| The device act of capturing | @miadi/capture, @miadi/capture-client |
The ownership split
| Concern | Owner |
| --- | --- |
| Creating and narrating episodes | passages |
| Relating and syncing artefacts to episodes | @miadi/inquiry-weave |
| Reading legacy compositions, authoring their provenance | @miadi/composition-to-episode |
| Observing heterogeneous sources without judging them | @miadi/episodic-memory-schema |
The dependency runs one way. This package imports inquiry-weave and the schema; neither may ever import it. There is exactly one episode scaffold algorithm in this repository and it lives in inquiry-weave — every filesystem primitive on the episode side is delegated there.
What it will not do
- Rewrite a composition. Reading is the only operation performed on a composition folder. No repair, no normalization, no schema-version stamping.
- Invent an episode number. A slug reading
ep250-is evidence about a recording, not authorization to claim episode 250. The number is offered as a candidate; a caller must confirm it. - Bump the weave version.
weave: 1is what every existing reader parses. Composition provenance travels inepisode.yaml, never by versioning up. - Claim to be canonical. Everything projected is marked
canonical: false. A machine-derived projection is a proposal to the chronicle, not a ruling. - Project to authored
EpisodicMemory. That schema requires a session id, an opened-at, a charter purpose, and consent decisions. A composition supplies none of them, and inventing them would be authoring disguised as reading.
Classification, not validation
The composition corpus is deliberately heterogeneous — hand-authored folders sit beside editor-written ones and both are legitimate. A boolean valid/invalid would declare half the corpus broken, so compositions are classified:
| Classification | Meaning |
| --- | --- |
| current-ui-ready | Carries every collection the current editor writes |
| compatible-legacy | Readable and whole, predating some of those collections |
| repairable | Readable, with defects a human could resolve |
| unsafe-or-ambiguous | Cannot be read, or its identity cannot be trusted |
A folder with no composition.json is compatible-legacy, not an error — the
directory is the identity and the manifest materializes on the first edit.
Usage
A composition is addressed by what it is, not by where one machine keeps it:
aureon/ava002, a bare slug searched across the shelves, or a path. Refs resolve
under $MIADI_STUDIO_DIR.
composition-to-episode list # the whole studio, woven and unwoven
composition-to-episode list --unwoven --agent aureon # one shelf, only what no episode records
composition-to-episode linked # every recorded link, and whether it resolves here
composition-to-episode linked --episode 2026-08-17-episode-335-…
composition-to-episode observe aureon/ava002 # read a composition, print its observation
composition-to-episode classify aureon/ava002 # classify it without touching it
composition-to-episode project aureon/ava002 # print the episode.yaml overlay it would write
composition-to-episode weave aureon/ava002 --number 336
composition-to-episode weave aureon/ava002 --episode 335 --dry-runlist and linked both take --json, so a person and the chronicle app read
the same answer out of the same code. linked exits 1 when any recorded link
names a place this host cannot reach — see below.
The bin is named composition-to-episode, whole. The bare word composition
belongs to the studio domain's future and is over-broad by ruling
(rispecs/capture-vocabulary.spec.md §3) — this CLI never claims it.
import { readComposition, classifyComposition, weaveComposition } from "@miadi/composition-to-episode"
const source = await readComposition("/path/to/compositions-nyro/ep250-routing")
const assessment = classifyComposition(source)
const result = await weaveComposition({
compositionDir: source.dir,
number: 250,
issue: "miadisabelle/gmtermux#31",
})Environment
| Variable | Meaning |
| --- | --- |
| MIADI_STUDIO_DIR | The studio — agent shelves of compositions. Default /srv/miadi/episodes/miadi-studio |
| MIADI_CHRONICLE_ROOT | Episode vessel root |
| MIADI_INQUIRY_ROOT | Where artefact folders are resolved from |
| MIADI_INQUIRY_REPO | Fallback repo for issue references |
MIADI_STUDIO_DIR is this package's own; the rest are inherited from
@miadi/inquiry-weave, unchanged. Every chain is option, then environment, then
a default literal at the end — never the other way around.
A composition folder anywhere on disk registers as an artefact by absolute path; it never has to be moved under the inquiry root.
Why the studio has a name
Before 0.2.0 a composition was addressed only by absolute path, and the projection recorded that path verbatim. Three episodes in the chronicle therefore carry
source_composition:
path: /data/data/com.termux/files/home/compositions-nyro/ep097-ceremony-agent-skillswhich is the Android device's own storage: true on exactly one machine, and that
machine a phone. The work has since moved — captured on the device, dropped and
committed into <studio>/<agent>/<composition>/ under the score/take custody law.
So source_composition now carries ref and studio_relative beside path, and
the reader resolves the portable form against whatever $MIADI_STUDIO_DIR says on
the host doing the reading. Existing blocks stay readable and are never rewritten;
a link this host cannot follow reports resolvable: false rather than vanishing.
Reading the relation back
import {
compositionsForEpisode, // episode -> the compositions it records
readCompositionLinks, // the whole chronicle, one pass
studioWeaveStatus, // studio -> woven | unwoven
resolveCompositionRef, // "aureon/ava002" -> an absolute dir on this host
listStudio,
} from "@miadi/composition-to-episode"studioWeaveStatus matches by resolved absolute directory first — the only
comparison two spellings of one path cannot fool — and falls back to the portable
ref, so a link written on one host matches a folder read on another.
What gets written
Weaving writes three files, none of them inside the composition's manifest:
<composition>/.weave.yaml durable relation (weave: 1)
<composition>/AGENTS.md back-link appended
<episode>/inquiry/weave.yaml sync state (weave: 1)
<episode>/episode.yaml source_composition, projection, lineageIn episode.yaml, only source_composition, projection, and
composition_musical are ever replaced. lineage is written only when absent —
a hand-authored lineage always wins. Every other key is left exactly as found.
Requires
@miadi/episodic-memory-schema@^0.3.0 — 0.2.0 swept the songwriting layer into
unmappedFields and labelled .mid and .mp4 clips as audio.
Provenance
This code was born as @miadi/composition, briefly carried the name
@miadi/musical-composition (2026-08-09, same day), and settled here when the
human ruled the three roles fully separate. The confession that named the
entanglement — the founding wound — lives verbatim in the PROVENANCE section of
packages/musical-composition/README.md, the studio domain home, where it was
written.
