@miadi/transcription
v0.3.1
Published
Read a capture's transcriptions by form and intent: label-grammar parsing, prose segmentation, and the semantic-correction advisory that warns and never rewrites. Reads both shapes — the episode-capture folder the recorder writes today and the legacy gmte
Readme
@miadi/transcription
Reads a composition's transcriptions by form and intent, so a decomposer can route each one to the treatment it actually calls for.
This is the transcription aspect of the composition-recorder suite, and the
opportunistic partner of the transcription-decomposer. It carries the RISE
specs it implements in rispecs/.
It reads. It never writes. A composition belongs to the musician who recorded it. Nothing here repairs, normalizes, or rewrites a manifest, a transcription, or a take.
Install
npm i @miadi/transcription@alphaOne runtime dependency — @miadi/episodic-memory-schema, which is itself dependency-free. Node built-ins otherwise, and it installs on Termux.
The surface
Two shapes carry transcriptions, and both read into the same TranscriptionReading.
An episode capture — what the recorder writes today:
import { readCaptureTranscriptions } from "@miadi/transcription"
// captures/260825114010/ — capture.json, transcription.json, and the .txt outputs
for (const reading of await readCaptureTranscriptions(captureDir)) {
reading.format.format
reading.route
}A legacy composition — the five frozen gmtermux trees:
import { readCompositionTexts, readTranscriptionEntry } from "@miadi/transcription"
const manifest = JSON.parse(await readFile("composition.json", "utf8"))
for (const reading of readCompositionTexts(manifest)) {
reading.format.format // monologue | request | plan | journal | dialogue | list | document | note | empty
reading.intent.genre // parsed from the hand-typed label's own grammar
reading.segments // addressable units, each with the move it makes
reading.corrections // the advisory — warns, never applies
reading.route // { approach, reason, suggestedStrategy }
}CLI
miadi-transcription read <composition-dir> # form, intent, segments, route per entry
miadi-transcription segments <composition-dir> # the segments themselves
miadi-transcription advise <composition-dir> # only the correction advisoryWhat it does, and why it does it that way
Segmentation is prose-driven, because the data is one line
In the real corpus a texts[].content is a single unbroken line — the
longest is 20,574 characters with no newline at all. There are no paragraphs to
split on, no timestamps, and no speaker turns. Segmentation therefore reads the
speech itself: sentence boundaries (guarding French abbreviations and decimals),
discourse markers that open a new movement (alors, donc, bref, so,
anyway), a size ceiling, and — most importantly — a shift in what the
speaker is doing.
That last seam is the one that matters. An ask followed by a stretch of deciding is otherwise one span whose dominant move is "decide", and the ask — the most decomposable thing in the whole transcription — stops being separately addressable.
Fidelity is a hard guarantee: segments.map(s => s.text).join("") reproduces
the source exactly, and every charStart/charEnd indexes the original string.
Verified across all 1064 segments the corpus produces.
The label grammar is mined, not invented
texts[].label is free text, and across the corpus it carries a consistent
unwritten grammar that lived only in its author's head:
RequestToJerryFromWilliam → genre=request to=Jerry from=William
InitByWilliamReadyForJerry → genre=handoff by=William readyFor=Jerry
Rel2ep099ep075ep103 → relatesTo=[ep099, ep075, ep103]
MultiHasDesiredProcesses2filmInEp103 → multi=true relatesTo=[ep103]
Cycle6to7…Question2Jerry-da8002… → cycles=[6-7] to=JerryEvery genre keyword and token pattern was derived from the 86 labels actually present in the corpus. ~30% of those labels are empty, so the contract is absolute: absence means infer, never invalid.
The advisory warns and never rewrites
Per transcription.spec.md, recurring mishearings are resolved by warning.
There is deliberately no applyCorrections export, and there never should be.
Every table entry was verified against the corpus before being encoded, and two
plausible-looking candidates were rejected on inspection: Ermes (11 hits —
all the French "en termes de") and Media (Plex, spelled correctly). A table
that flags correct speech is worse than no table.
| Heard | Intended | Source |
|---|---|---|
| TallyScale · TaylorSkills · Tailscal | Tailscale | corpus |
| Niro | Nyro | corpus |
| Thermux | Termux | corpus |
| Kauaia | coaia | corpus |
| Orion | Aureon | corpus (low confidence — read the passage) |
| medicine roll | medicine wheel | spec |
Additive and opportunistic
texts[].format, texts[].corrections, and texts[].intent are optional fields
a composition may or may not carry. A present field is authoritative and is
never second-guessed; an absent one is inferred. No manifest version bump,
and a reader that ignores these fields is unaffected.
Routing
| Form | Approach | miaco --strategy |
|---|---|---|
| request · handoff · plan · list · document · note | direct | standard |
| monologue · journal | layered | iterative-refinement |
| dialogue · two named voices | multi-voice | adversarial-consensus |
suggestedStrategy is a hint, not a coupling — this package knows nothing
about miaco, and a consumer is free to map approach itself. A directed act
(RequestToJerryFromWilliam) names two people because it has a sender and a
receiver; that is direction, not turn-taking, so it routes direct.
What this package does not do
It does not produce words from audio. That is @miadi/capture-service —
the Groq contract, the credential, the device. This package reads what that
produced.
The seam was not always there. transcribeTake() lived here until 0.2.0,
extracted from the recorder monolith's transcribeAudio on 2026-07-24 — three
weeks before @miadi/capture-service extracted the same contract from the same
monolith, because this package was sitting unmerged on a branch and could not be
found. Two packages then declared a type named TranscriptionSidecar. The
producing half is gone from here; capture-service owns it alone.
What remains is the reading half, which nothing else owns:
| Concern | Package |
| --- | --- |
| producing words from audio | @miadi/capture-service |
| reading words for decomposition | @miadi/transcription (this) |
parseSidecar understands what all three writers emit — the recorder's
{ french, english }, capture-service's { language, transcription,
translation }, and the forward-looking { spoken, spokenLang, english }. It
had to: capture-service writes the french key only when the language is
French, so a Spanish take read through the ancestor keys alone came back with
no words at all.
Tests
npm test