Transcript Fixer
Use a two-phase loop:
- Stage 1 applies deterministic, already-known corrections.
- Native AI Correction reads the complete transcript, fixes one-off errors, verifies uncertain entities, and compounds reusable fixes.
Native AI Correction is the default. Stage 1 alone is incomplete. Stage 3 API exists only for automation that has no Claude/Codex agent available.
Operating contract
- Finish Stage 1 → Native AI Correction → compound confirmed recurring fixes. Do not report a transcript clean after Stage 1 alone.
- Skip Native AI only when the human explicitly limits this run to the dictionary pass or a dated artifact proves Native AI already ran on this exact transcript.
- In Claude Code or Codex, do not run Stage 3. Use Stage 1 plus the native workflow.
- Never rewrite speech for fluency. A correction must explain a plausible ASR error and preserve who said what.
- Never infer or reassign speaker identities. Preserve speaker-label lines; human-confirmed labels and user verdicts are authoritative.
- Before correcting any person name, directly read both the configured global people roster and the owning project's explicit identity roster or alias ledger. Stage 1 auto-loads only global
ASR 变体 entries; it does not load project rosters or expose suppressed, disabled, and unlisted entries. If an expected source is missing or the sources conflict, leave the name unchanged and enqueue or ask once. Never use occurrence frequency as identity evidence. Read references/dictionary_identity_and_context.md before settling the name.
- Resolve doubts from available evidence before escalating. Audio download is one evidence channel, not a prerequisite for Native correction. When it is unavailable, follow evidence selection and escalation; do not require the user to change download permissions or treat every pending row as a question only they can answer.
- Leave genuinely unresolved text unchanged and enqueue it. A visible garble is safer than a fluent wrong guess; a pending row records uncertainty, not an automatic human handoff.
- Treat an unfamiliar token as unknown, not as an error. Exhaust the local evidence ladder first. For a load-bearing token that remains unresolved, use the clip-level cross-recognizer rung only when source audio and a permitted second engine are already available; otherwise continue with the available evidence under the escalation policy above. Agreement from a genuinely different recognizer family strongly corroborates the sound, but never chooses between homophonic spellings or overrides the person-name gate. Read native workflow step 4, rung 7 before using it. A backlog of exhausted pendings is the batch form of this rung: when a native pass leaves a queue of locally-unresolvable rows, do not hand the queue to the user wholesale — adjudicate it with
verify_queue_audio.py (one transcript, one source audio, one second engine, both windows per row); the adjudication matrix and timestamp-mapping pitfalls live in advanced_correction_evidence.md § Batch pending adjudication. Only rows whose two windows disagree or stay silent survive to the human.
- Treat a single-line
asr_note value as correction provenance: it intentionally cites old forms and is excluded from matching. Multi-line YAML ledger values are not masked; keywords, titles, other ASR-derived metadata, and body text remain in correction scope.
- Read references/native_ai_full_workflow.md in full before performing a native pass. Read the task-specific references named below before their corresponding action.
Run context
Run every entrypoint through uv run; entrypoints that need third-party Python packages declare them with PEP 723, while stdlib/internal-only utilities may omit the metadata block. Execute commands from the skill directory printed when this skill was invoked, or prefix every script path with that directory. Do not rely on $CLAUDE_SKILL_DIR; it is not available in every harness.
If the bundle location is genuinely unknown, use the installation-resolution procedure in references/installation_setup.md. Do not select the first result from a broad find: caches, backups, and old versions can coexist.
Quick start
# Initialize once
uv run scripts/fix_transcription.py --init
# Stage 1 for one project domain. --apply-domain trusts that explicitly
# selected, human-curated project domain at every risk level.
uv run scripts/fix_transcription.py \
--input meeting.md --stage 1 \
--domain myproject --apply-domain --json
# Several sibling domains may be loaded as one union.
uv run scripts/fix_transcription.py \
--input meeting.md --stage 1 \
--domain myproject,myproject-alt --apply-domain --json
# Preview without writing the Stage 1 output.
uv run scripts/fix_transcription.py \
--input meeting.md --stage 1 --domain myproject --dry-run
# Scan all documented context traps after the native read-through.
uv run scripts/fix_transcription.py --scan-traps \
--context-file ~/.transcript-fixer/contexts/myproject.md \
--input meeting.md
Safe mode is the Stage 1 default: low-risk rules apply; medium/high-risk matches defer to *_needs_review.md and the persistent review queue. Applied: 0 is a valid result, not proof that the transcript is clean.
The Stage 1 JSON contract is:
{
"applied": 0,
"deferred": 0,
"output_path": null,
"needs_review_path": null,
"input_unchanged": true,
"review_enqueued": 0,
"stage1_only_incomplete": true,
"stage2_total_chunks": 0,
"stage2_failed_chunks": 0,
"stage2_degraded": false,
"boundary_refused": 0
}
Read every field in the result. boundary_refused counts dictionary matches the word-boundary check refused this run — neither applied nor deferred, so a caller comparing runs can see why a deferral disappeared; --apply-all switches that check off. stage1_only_incomplete is additive to the original caller contract and must remain true for a Stage 1 script run; only the caller can close it by running Native AI, or by explicitly choosing the agent-less Stage 2/3 route. The stage2_* telemetry fields are always present: Stage 1 reports 0, 0, and false; Stage 2/3 replace them with the actual API outcome. Do not infer no-op or success from whether a sidecar exists.
For a native end-to-end example, read references/example_session_dji_minutes.md.
Choose the route
| Route |
Use when |
Required reading |
| Fast native |
Short/plain transcript, known speakers, low stakes |
This file + native_ai_full_workflow.md |
| Full native |
Domain-heavy, unfamiliar entities, 3+ speakers, long or decision-bearing transcript |
native_ai_full_workflow.md, plus queue and evidence references below |
| Caller integration |
Another skill or ingest pipeline invokes Stage 1 |
Cross-skill caller contract below |
| Review queue/dashboard |
Any item is uncertain or needs audio |
review_queue_dashboard.md |
| Agent-less API |
CI/batch automation with no agent available |
glm_api_setup.md and workflow_guide.md |
| Multi-file batch |
Several related transcripts; especially 10+ files |
advanced_correction_evidence.md |
Use vocabulary and stakes as the primary tier signals; use length only as a tiebreaker. A five-minute medical interview can require the full tier, while a long plain two-person memo can use the fast tier.
Native correction checklist
Give the file its final name before Stage 1. Queue anchors store absolute paths. Use a human-readable project filename before any deferral can enqueue. When the input arrives as inline text with no file yet — a slash-command argument, a pasted block — write it to a file before anything else; --input and the queue anchors both need a path, and a scratch location is fine when nothing downstream will archive it. No --domain given and none obvious from context? Omitting the flag already defaults to searching every domain (--domain's own default), so don't block on picking one — run Stage 1 bare and let safe mode gate what auto-applies. If a specific candidate still needs resolving, one step of the ladder is cheap enough to keep even at fast tier though the rest of it isn't: native_ai_full_workflow.md step 4's rung 1, a single cross-domain lookup — --lookup "<term>" prints every existing claim on the term (dictionary rules active or disabled, as FROM or TO; context rules; roster variants; queue rows) — not the full verification ladder the tier table tells you to skip, just that one query.
Recover the raw baseline before reading a pre-corrected transcript. If an ingest pipeline or previous API pass already touched the text, diff against the raw source first. Judge upstream edits as edits, not as ground truth.
Load project priors and read the complete transcript. Read ~/.transcript-fixer/contexts/<domain>.md when present, then read the whole file before deciding early ambiguities.
Run Stage 1 and inspect the real result. Prefer explicit project domains plus --apply-domain --json. Read deferred and review_enqueued; never silently discard the sidecar or queue gap.
Diff Stage 1 against raw/original. If a rule changed correct speech, work from the original, retire the stored pair with --report-false-positive "<from>" "<to>" --domain <domain>, and verify it no longer fires.
Triage every candidate.
- Confident: the sound change is plausible and context or an authoritative local source settles it.
- Needs verification: a person, company, product, model, ticker, place, number, or other load-bearing term without a source.
- Uncertain: evidence does not settle it; leave the original and enqueue.
- Multi-channel entity fork: when independent transcripts disagree on a person name or other proper noun and no local authority settles it, collect the unresolved forks and ask the human once. Do not guess, and do not treat a majority vote as identity evidence.
Apply the smallest edit that explains the sound. Do not add words the speaker did not say. Correct ASR-derived metadata too, while leaving asr_note intact. A Stage 1 deferral you now agree with is closed through its queue row, not re-applied with sed: --resolve-review <id> --decision accepted (ids from --list-review --review-file "<absolute-canonical-file>" --json) performs the edit, or records a fix you already applied by hand without writing. Either way the row ends accepted; kept_original asserts the transcript was right as spoken and is never the exit for a fix you applied.
Run a second pass.
- Every tier: run
--scan-traps and inspect both hits and unparsed.
- Full tier: use fresh-context review. For a single unsplit review, assign one corrected file and require a compact residual table or explicit
no new residuals; an empty/truncated response is a failed review.
- For a split, multi-file, or resumed Full review, follow native_review_packets.md for packet assignment, JSON results, validation, and recovery.
- High-stakes multi-recording: a sampled clip settles only that anchored item. If the user asked for a higher-quality or complete transcript and the baseline audio is available, load
/daymade-audio:asr-transcribe-to-text and run its full-file transcription path across the complete clearest/canonical recording before claiming whole-transcript coverage; otherwise report sampled cross-check only — incomplete. Prefer a recognizer different from the producer of the canonical body. If only the same recognizer is available, the run proves complete-source coverage but is not independent cross-recognizer corroboration; state that boundary.
Enqueue every unresolved item; escalate selectively. First apply evidence selection and escalation, then follow Review queue safety below and review_queue_dashboard.md. Open only this file when human review is needed. Detection and enqueueing are not correction: for a higher-quality/final claim, every queue row anchored to this exact file must leave pending. For human review, start the dashboard with uv run scripts/review-dashboard/server.py --file "<absolute-canonical-file>"; add --item <id> to land on one fork. If a human is unavailable, keep the artifact explicitly labeled draft / unresolved — incomplete and enumerate the rows; do not ship the raw suspect text under a completed quality claim.
Read back the human state, then finalize. When the human says they marked the dashboard, do not rerun ASR or ask the same questions again. First run uv run scripts/fix_transcription.py --list-review --review-file "<absolute-canonical-file>" --review-status all --json, apply any resulting file state, and require stats.pending_total == 0 for that exact path; zero pending rows is required before the high-quality/final claim. Then diff the file actually edited, run numeric consistency when numbers matter, rerun plain Stage 1, re-grep known corrections, and confirm every change traces to a triage decision. Global queue counts cannot close or reopen this file's quality claim. Last, run --close-sidecars --input "<absolute-canonical-file>": it re-reads every *_changes.md/*_needs_review.md entry against the file and the queue, refuses while an entry still reads as the original without a verdict or any row is pending, and removes the sidecars only when everything is closed (see Finalization).
Compound the learning in the same turn. Route each stable pattern to its correct home; do not leave confirmed fixes only in chat. Native-pass edits never reach Stage 1's correction history, so harvest them mechanically right after the final diff:
# Diff raw vs corrected into parseable trap candidates (review artifact —
# you adjudicate the printed list; --write auto-appends only the recurring
# (≥2x) non-bare candidates; --write-all also appends the one-off set)
uv run scripts/harvest_corrections.py raw.md corrected.md \
--context-file ~/.transcript-fixer/contexts/<domain>.md
Every emitted bullet is round-trip verified through the real trap parser before printing, and pairs already documented in the context file are skipped. A pair the bullet grammar cannot carry — a side with no lexical content or a * in it, such as a vendor's *** redaction mask diffed against the real word — is dropped at the noise filter, and any bullet that still fails to parse is reported on stderr and excluded rather than aborting the run. High-frequency candidates are strong traps; single-occurrence ones need a human judgment — that is why --write leaves them out by default — and ⚠️ 裸形 candidates are never auto-written. This replaces hand-writing trap bullets from memory.
Propagate entity fixes deliberately.
First finish the same-file sweep: inspect harvest_corrections.py --json
entries with nonzero remaining, then check the observed spelling family of
each confirmed entity or technical identifier across body and ASR-derived
metadata. Use the authoritative spelling and conventional filename casing;
a user's informal dictation is not a request to preserve a typo. Preserve real
alternate referents, aliases, and generic-character hits. Do not turn a
same-file sweep into an unreviewed batch replacement or repeat settled questions. Then search
only the owning project’s derived notes/summaries and review every hit; exclude
raw ASR and correction sidecars because they preserve the evidence trail.
The detailed provenance bar, local-first entity ladder, second-pass prompt, queue payload, and finalization rules are in references/native_ai_full_workflow.md.
Cross-skill caller contract
A caller pipeline must:
- Run Stage 1 with the explicitly configured project domain(s),
--apply-domain, and --json. If deferred > review_enqueued, persist the review sidecar outside any temporary directory or surface the gap as failure.
- Run Native AI with this skill loaded, or report
Stage 1 only — incomplete. Agent-less automation may use Stage 3 instead.
Canonical call:
uv run scripts/fix_transcription.py \
--input "$staged" --stage 1 \
--domain "$domains" --apply-domain --json
A caller that wires only the script path never loads this contract. Script-path integration alone is therefore a Stage 1 prefilter, not transcript correction.
Keep project domains warm: every confirmed recurring correction from the native pass must be added back to the correct project domain, roster, or context file.
Dictionary and identity safety
Read references/false_positive_guide.md and references/dictionary_identity_and_context.md before adding a rule.
| Pattern |
Destination |
| Stable non-word or unique garble → canonical term |
--add ... --domain <project> |
| Important recurring person and observed ASR variants |
People roster |
| Correction right only inside a specific recurring phrase |
--add-context-rule PATTERN REPLACEMENT --domain <project> (regex, domain-scoped; omit --domain for global) |
| Common/real word wrong only under a cue |
Domain context trap, never a bare rule (a bare number or a single surname + 老师/总 is refused at roster load and by --add / --import, --force included) |
| Real name → different real name |
Domain context + human/audio verification, never a bare rule |
| Confirmed-correct entity repeatedly reopened |
Confirmed-correct context record |
| One-off sentence-local wording |
Edit only; do not add |
Stage 1's match-time layer (the third of the three layers in references/false_positive_guide.md: add time, apply time, match time) also refuses a dictionary match on its own at three checks before risk scoring: the superset check (the corrected form is already in place), the common-word boundary check for short rules, and the word-boundary check. That last one asks, by script, whether the match is a fragment of real words: an ASCII match with an ASCII letter directly beside it is inside a longer word (Cloud in iCloud; digits do not count, so cloud3 still corrects); a CJK match is refused only when every segment of a dictionary-only jieba cut that overlaps it is a multi-character word and one of them crosses the match boundary (新一 in 更新|一下, 问题记 in 问题|记录, 同龄 in 同龄人) — one single-character segment under the match (巨|神智|能, 叫|新|一下|单) means an unknown fragment and the match proceeds. Refusals are counted as Refused at word boundaries and in the JSON boundary_refused, listed in the Stage 1 summary, and never deferred. A context rule (--add-context-rule) skips this check but still goes through risk scoring, so in safe mode its match is deferred to the review queue rather than applied — accept it there, or run --apply-all, which switches the check off and applies every match; --apply-domain keeps the check. The layers are listed in references/false_positive_guide.md.
A context trap is a cue, not permission to replace blindly. Two annotation classes in a domain context file are machine-readable vetoes that Stage 1 enforces (when the domain is named via --domain — a whole-library run has no owner to veto with): a trap marked 禁裸词/禁入词典 demotes any dictionary rule with the same FROM to review, and a confirmed-correct (勿修) record demotes any rule whose FROM is that token — demotion beats --apply-domain trust-flattening, so a real-word rule (the 绿点→绿电 class: right in business context, wrong in UI context) can stay in the dictionary without firing blindly. --apply-all remains the operator's explicit override. Without the veto the only escape was --report-false-positive, which disables the rule in the contexts where it is right too. --scan-traps supports canonical → and legacy ≈ mappings with the same directional contract: left is observed ASR, right is intended text. Wrap an exact FROM phrase containing spaces in backticks:
- **`CC 思维链`/`CC 思维连` → 目标术语** — only under the domain's documented cue
This demonstrates an exact ASR phrase candidate, not a person-name candidate. The domain context remains the authority for the real target and cue; the scanner only locates the literal FROM forms.
Before adding any real-word-shaped rule, measure the project corpus:
uv run scripts/fix_transcription.py \
--probe "candidate" --corpus /path/to/project-transcripts/
uv run scripts/fix_transcription.py \
--add "candidate" "canonical" --domain myproject \
--check-corpus --corpus /path/to/project-transcripts/
User verdicts settle the occurrence immediately, but they do not make a replacement reusable. Fix the file first, then route the result through the table above: only a stable recurring pattern goes to the dictionary/roster/context; a rare sentence-local mishearing stays file-only. When the user confirms that two legitimate names or nicknames identify the same person, preserve whichever form was actually spoken and store the identity relationship as context, not as a replacement rule.
Review queue safety
Read references/review_queue_dashboard.md before enqueueing or resolving.
Minimum item:
[
{
"file": "/absolute/path/to/transcript.md",
"line": 142,
"original": "<suspect-token-only>",
"suggested": "<best-candidate>",
"kind": "entity",
"context": "<verbatim sentence, or unique clause/span for same-line repeats>",
"evidence": "<what was checked>"
}
]
Safety rules:
file is mandatory for this workflow. Without it, acceptance can record a verdict without editing the transcript.
original is only the suspect token/span; never put the whole sentence there.
context is copied verbatim; line is the key, not line_hint.
suggested is the key, not suggestion. Use actions, not action_pack.
- Resolve one occurrence at a time; sweep sibling entity occurrences only after the whole batch is resolved.
- A
pending row is a blocking state for a high-quality/final transcript, not proof that the issue was handled. Queue detection without a human/evidence verdict leaves the artifact incomplete.
- Read
resolved_text after an override; the listing can still display the rejected suggestion.
- A single-line
asr_note ledger is masked on the accept path too, so resolving an item never edits the provenance line that cites the old form. A fix you applied by hand before resolving is still closed with --decision accepted (or overridden for its override text): when the context recorded at enqueue reappears anywhere in the file with the suggestion in the slot the original occupied, the verdict is recorded without writing (already in place at the anchor — recorded without writing). Line drift does not matter, and neither does the original surviving in other utterances far from the hint, or inside the suggestion itself (阿里→阿里云). It fails closed with ReAnchorNeeded, and the message says which, when the original still sits within the resolve window of the hint outside the suggestion (the anchored utterance, or a look-alike beside it, is still garbled — settle that line on its own row or by hand, then resolve; do not --reanchor-review this row onto it), when the recorded neighbourhood also appears with a third form in the slot (anywhere in the file at the width that matched, or near the hint at any width down to two characters a side), or when the edit touched the characters next to the slot. What it cannot see: an anchored utterance deleted or rewritten past both neighbours while an identical one elsewhere reads corrected — that records accepted with nothing written, and a garble still in the file is re-deferred by the next Stage 1 run. --reanchor-review <id> repairs a row whose original moved or drifted and refuses one whose context already reads corrected. A row the queue can neither re-anchor nor recognise closes with --decision skipped --note <what happened>. kept_original asserts the transcript keeps the original form — never the exit for a fix you applied.
- If the file moved or drifted, run
--reanchor-review. Add --reanchor-root or --reanchor-to when requested. Do not hand-edit around a pending item.
- Promote every
decision_note by meaning; storing a note does not change the dictionary, roster, context, or false-positive state.
Core commands:
uv run scripts/fix_transcription.py --enqueue-review items.json
uv run scripts/fix_transcription.py \
--list-review --review-file "<absolute-canonical-file>" \
--review-status all --json
uv run scripts/fix_transcription.py --show-review <id> --json
uv run scripts/fix_transcription.py --reanchor-review <id>
uv run scripts/fix_transcription.py \
--resolve-review <id> --decision accepted --by reviewer
Numbers, artifacts, and batches
Read references/advanced_correction_evidence.md when any of these conditions holds:
- A number, bound, price, share, deadline, or magnitude drives a decision.
- Two recordings exist for one meeting.
- A load-bearing name or term survived the local ladder unresolved, source audio is available, and the current authorization already permits a second recognizer.
- A whiteboard, slide, or photographed written artifact can independently settle a name/term.
- Several related files should share one correction list.
- A 10+ file batch is being delegated.
Numeric-slot scan:
uv run scripts/scan_numeric_consistency.py transcript.md --domain myproject
Its output is candidates, never automatic edits. For a load-bearing number, follow evidence selection and escalation. When original audio is accessible, use the review dashboard to decide by ear. Otherwise retain competing readings unless other evidence settles them, and continue the remaining items; do not require export permission.
For delegated batches, every agent owns one file, cannot cross-file replace, and returns a residual list. Afterward compare git diff --name-only with the explicit file list and inspect every unexpected file under the repository's worktree-safety rules.
Finalization
- Native mode edits the original file directly. Rerun plain
--stage 1 to confirm; a clean no-op writes no Stage 1 sidecar.
- When a newer
*_stage1.md exists and the original was not edited after it, a plain Stage 1 rerun atomically promotes it and removes disposable sidecars. It retains *_changes.md and *_needs_review.md: those are review evidence, and --close-sidecars is the command that decides they are closed. --apply-all never takes this promotion path.
- Do not use the existence of an output file as the success signal; read JSON/exit status and independently read the final file.
- Preserve raw transcripts,
*_changes.md, and *_needs_review.md as evidence until --close-sidecars --input "<absolute-canonical-file>" reports closed: every entry reads applied in the file (or the original form no longer appears anywhere in the ledger-masked transcript) or is answered by a decided queue row for this exact file — one row per occurrence, matched by nearest line, so a second occurrence with no row of its own stays undecided — or belongs to a FROM→TO rule that has since been disabled as a false positive (disabled: no longer a question), and the file has zero pending rows. It exits 1 (open) naming the undecided entries and pending ids, 2 (blocked) when a *_stage1.md newer than the file still awaits the plain Stage 1 rerun or a report carries entries the parser cannot read (an unreadable report is evidence, not an empty one), and 0 after removing the evidence and stale run outputs. For an entry that still reads as the original and has no row at all, --decide-raw kept_original|skipped --by <who> --note <why> records the verdict through the queue at closure — an audit trail, not a silent deletion. --dry-run shows the verdict without deleting; --json returns it.
*_stage2.md and *_dryrun.md are run-scoped outputs of the API route and the preview, not archive material: promote or discard them in the session that produced them. One newer than the transcript is unpromoted — --close-sidecars retains it, says so, and removes it only with --discard-unpromoted.
- Re-grep a known corrected form in the final file and verify no correction remains only in
asr_note or a sidecar.
- If a queued item was renamed away, repair it with
--reanchor-review rather than resolving it with a false terminal verdict.
Agent-less API route
Only when no Claude/Codex agent can perform Native AI Correction:
export GLM_API_KEY="<api-key>"
uv run scripts/fix_transcript_enhanced.py input.md --output ./corrected
The route writes <stem>_stage2.md beside the input. It is this run's output, not a second transcript: verify it, then promote it onto the transcript or discard it before the session ends. A _stage2.md left beside a transcript that is not it cannot be told apart from reviewed work a month later.
Read references/glm_api_setup.md, references/installation_setup.md, and the explicitly API-oriented portions of references/workflow_guide.md. When a chunk fails after retries, the API route keeps that chunk and its original surrounding separators byte-for-byte and prints a warning; if every chunk fails, the complete output equals the input. For fix_transcription.py --stage 2|3 --json, read the additive stage2_total_chunks, stage2_failed_chunks, and stage2_degraded fields: stage2_degraded: true is not a fully corrected run even though the safely retained artifact is emitted. The enhanced wrapper exits nonzero after writing that retained artifact when any Stage 2 chunk is degraded. Verify the output rather than assuming the warning means a corrected result exists.
The enhanced API wrapper can also add paragraph breaks, reduce repeated filler,
and present corrections for interactive review. Those are API-wrapper features;
they do not authorize Native AI to rewrite wording for fluency.
Utility commands
# Extract likely errors without editing
uv run scripts/fix_transcription.py --extract-uncertain \
--input meeting.md --output ./review
# Import curated preset rules
uv run scripts/fix_transcription.py --load-presets tech
# Repair timestamps
uv run scripts/fix_transcript_timestamps.py meeting.txt --in-place
# Split and rebase sections
uv run scripts/split_transcript_sections.py meeting.txt \
--first-section-name "intro" \
--section "main::<verbatim marker>" \
--rebase-to-zero
# Word-level review diff
uv run scripts/generate_word_diff.py original.md corrected.md output.html
# Harvest native-pass edits into context-trap candidates
uv run scripts/harvest_corrections.py raw.md corrected.md \
--context-file ~/.transcript-fixer/contexts/myproject.md --write
# Batch-adjudicate a transcript's pending queue rows via clip-level
# cross-recognition (one source audio, one second engine, both windows)
uv run scripts/verify_queue_audio.py \
--transcript /abs/meeting.md --audio /abs/source.wav \
--speed 1.0 --engine-script /abs/stepfun-asr/scripts/asr_transcribe.py
# Multi-format Stage 1/API comparison report
uv run scripts/generate_diff_report.py \
original.md original_stage1.md original_stage2.md \
--output ./diff_reports
# Every existing claim on a term: dictionary (active/disabled), context rules, roster, queue
uv run scripts/fix_transcription.py --lookup "候选词"
# Decide whether one finished transcript's review sidecars are closed, then remove them
uv run scripts/fix_transcription.py --close-sidecars \
--input "/absolute/meeting.md" --dry-run
# Setup health
uv run scripts/fix_transcription.py --validate
Read references/script_parameters.md before using less-common flags. Read references/database_schema.md before custom SQL; correction columns are from_text and to_text.
Reference map
All references are one level from this file.
| Need |
Read |
| Full native correction sequence |
native_ai_full_workflow.md |
| Split/batch cold-review packets, result validation, and interruption recovery |
native_review_packets.md |
| Dictionary, people roster, domain contexts |
dictionary_identity_and_context.md |
| False-positive policy |
false_positive_guide.md |
| Queue, dashboard, audio, re-anchor |
review_queue_dashboard.md |
| Numbers, photos, multi-recording, clip cross-check, batches, batch pending-queue audio adjudication |
advanced_correction_evidence.md |
| Context-file grammar/template |
domain_context_guide.md |
| CLI flags and review-item schema |
script_parameters.md |
| Database schema and queries |
database_schema.md, sql_queries.md |
| Short command lookup |
quick_reference.md, dictionary_guide.md |
| Learning loop |
iteration_workflow.md |
| Native examples |
example_session_dji_minutes.md |
| Agent-less API example/config |
example_session.md, glm_api_setup.md, installation_setup.md |
| Architecture and formats |
architecture.md, file_formats.md |
| Operational guidance |
best_practices.md, troubleshooting.md, team_collaboration.md, workflow_guide.md |
Bundled scripts are executed, not loaded into context. The primary entry points are fix_transcription.py, scan_numeric_consistency.py, fetch_minute_audio.py, review-dashboard/server.py, and the diff/timestamp/splitting utilities listed above.
Handoff
After correction, hand off to /daymade-audio:meeting-minutes-taker only when the user wants a structured summary. Do not create meeting minutes automatically: transcript correction and summarization are separate scopes.
1---2name: transcript-fixer3description: Corrects speech-to-text transcription errors with dictionary rules and Claude's built-in AI (no external API key required); Native AI Correction is the default, Stage 1 alone is incomplete, and Stage 3 API is only for automation without Claude Code. Builds personalized correction databases, loads person-name ASR variants from the configured global people roster, and reads per-domain contexts for homophones. Before correcting a person name, the agent must consult both the global roster and the owning project's identity roster; project rosters are not auto-loaded, and occurrence frequency is never identity evidence. Use for ASR/STT output with recognition errors, homophones, garbled technical terms, person-name errors, or mixed Chinese/English, and for cleaning meeting notes, lecture transcripts, interviews, or any speech-recognition text—even when the user only says “fix this transcript,” “clean up these meeting notes,” or mentions a garbled name.4---56# Transcript Fixer78Use a two-phase loop:9101. Stage 1 applies deterministic, already-known corrections.112. Native AI Correction reads the complete transcript, fixes one-off errors, verifies uncertain entities, and compounds reusable fixes.1213**Native AI Correction is the default. Stage 1 alone is incomplete.** Stage 3 API exists only for automation that has no Claude/Codex agent available.1415## Operating contract1617- Finish Stage 1 → Native AI Correction → compound confirmed recurring fixes. Do not report a transcript clean after Stage 1 alone.18- Skip Native AI only when the human explicitly limits this run to the dictionary pass or a dated artifact proves Native AI already ran on this exact transcript.19- In Claude Code or Codex, do not run Stage 3. Use Stage 1 plus the native workflow.20- Never rewrite speech for fluency. A correction must explain a plausible ASR error and preserve who said what.21- Never infer or reassign speaker identities. Preserve speaker-label lines; human-confirmed labels and user verdicts are authoritative.22- Before correcting any person name, directly read both the configured global people roster and the owning project's explicit identity roster or alias ledger. Stage 1 auto-loads only global `ASR 变体` entries; it does not load project rosters or expose suppressed, disabled, and unlisted entries. If an expected source is missing or the sources conflict, leave the name unchanged and enqueue or ask once. Never use occurrence frequency as identity evidence. Read [references/dictionary_identity_and_context.md](references/dictionary_identity_and_context.md) before settling the name.23- Resolve doubts from available evidence before escalating. Audio download is one evidence channel, not a prerequisite for Native correction. When it is unavailable, follow [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation); do not require the user to change download permissions or treat every pending row as a question only they can answer.24- Leave genuinely unresolved text unchanged and enqueue it. A visible garble is safer than a fluent wrong guess; a pending row records uncertainty, not an automatic human handoff.25- Treat an unfamiliar token as unknown, not as an error. Exhaust the local evidence ladder first. For a load-bearing token that remains unresolved, use the clip-level cross-recognizer rung only when source audio and a permitted second engine are already available; otherwise continue with the available evidence under the escalation policy above. Agreement from a genuinely different recognizer family strongly corroborates the sound, but never chooses between homophonic spellings or overrides the person-name gate. Read native workflow step 4, rung 7 before using it. **A backlog of exhausted pendings is the batch form of this rung**: when a native pass leaves a queue of locally-unresolvable rows, do not hand the queue to the user wholesale — adjudicate it with `verify_queue_audio.py` (one transcript, one source audio, one second engine, both windows per row); the adjudication matrix and timestamp-mapping pitfalls live in `advanced_correction_evidence.md` § Batch pending adjudication. Only rows whose two windows disagree or stay silent survive to the human.26- Treat a single-line `asr_note` value as correction provenance: it intentionally cites old forms and is excluded from matching. Multi-line YAML ledger values are not masked; keywords, titles, other ASR-derived metadata, and body text remain in correction scope.27- Read [references/native_ai_full_workflow.md](references/native_ai_full_workflow.md) in full before performing a native pass. Read the task-specific references named below before their corresponding action.2829## Run context3031Run every entrypoint through `uv run`; entrypoints that need third-party Python packages declare them with PEP 723, while stdlib/internal-only utilities may omit the metadata block. Execute commands from the skill directory printed when this skill was invoked, or prefix every script path with that directory. Do not rely on `$CLAUDE_SKILL_DIR`; it is not available in every harness.3233If the bundle location is genuinely unknown, use the installation-resolution procedure in [references/installation_setup.md](references/installation_setup.md). Do not select the first result from a broad `find`: caches, backups, and old versions can coexist.3435## Quick start3637~~~bash38# Initialize once39uv run scripts/fix_transcription.py --init4041# Stage 1 for one project domain. --apply-domain trusts that explicitly42# selected, human-curated project domain at every risk level.43uv run scripts/fix_transcription.py \44 --input meeting.md --stage 1 \45 --domain myproject --apply-domain --json4647# Several sibling domains may be loaded as one union.48uv run scripts/fix_transcription.py \49 --input meeting.md --stage 1 \50 --domain myproject,myproject-alt --apply-domain --json5152# Preview without writing the Stage 1 output.53uv run scripts/fix_transcription.py \54 --input meeting.md --stage 1 --domain myproject --dry-run5556# Scan all documented context traps after the native read-through.57uv run scripts/fix_transcription.py --scan-traps \58 --context-file ~/.transcript-fixer/contexts/myproject.md \59 --input meeting.md60~~~6162Safe mode is the Stage 1 default: low-risk rules apply; medium/high-risk matches defer to `*_needs_review.md` and the persistent review queue. `Applied: 0` is a valid result, not proof that the transcript is clean.6364The Stage 1 JSON contract is:6566~~~json67{68 "applied": 0,69 "deferred": 0,70 "output_path": null,71 "needs_review_path": null,72 "input_unchanged": true,73 "review_enqueued": 0,74 "stage1_only_incomplete": true,75 "stage2_total_chunks": 0,76 "stage2_failed_chunks": 0,77 "stage2_degraded": false,78 "boundary_refused": 079}80~~~8182Read every field in the result. `boundary_refused` counts dictionary matches the word-boundary check refused this run — neither applied nor deferred, so a caller comparing runs can see why a deferral disappeared; `--apply-all` switches that check off. `stage1_only_incomplete` is additive to the original caller contract and must remain true for a Stage 1 script run; only the caller can close it by running Native AI, or by explicitly choosing the agent-less Stage 2/3 route. The `stage2_*` telemetry fields are always present: Stage 1 reports `0`, `0`, and `false`; Stage 2/3 replace them with the actual API outcome. Do not infer no-op or success from whether a sidecar exists.8384For a native end-to-end example, read [references/example_session_dji_minutes.md](references/example_session_dji_minutes.md).8586## Choose the route8788| Route | Use when | Required reading |89|---|---|---|90| Fast native | Short/plain transcript, known speakers, low stakes | This file + [native_ai_full_workflow.md](references/native_ai_full_workflow.md) |91| Full native | Domain-heavy, unfamiliar entities, 3+ speakers, long or decision-bearing transcript | [native_ai_full_workflow.md](references/native_ai_full_workflow.md), plus queue and evidence references below |92| Caller integration | Another skill or ingest pipeline invokes Stage 1 | `Cross-skill caller contract` below |93| Review queue/dashboard | Any item is uncertain or needs audio | [review_queue_dashboard.md](references/review_queue_dashboard.md) |94| Agent-less API | CI/batch automation with no agent available | [glm_api_setup.md](references/glm_api_setup.md) and [workflow_guide.md](references/workflow_guide.md) |95| Multi-file batch | Several related transcripts; especially 10+ files | [advanced_correction_evidence.md](references/advanced_correction_evidence.md) |9697Use vocabulary and stakes as the primary tier signals; use length only as a tiebreaker. A five-minute medical interview can require the full tier, while a long plain two-person memo can use the fast tier.9899## Native correction checklist1001011. **Give the file its final name before Stage 1.** Queue anchors store absolute paths. Use a human-readable project filename before any deferral can enqueue. When the input arrives as inline text with no file yet — a slash-command argument, a pasted block — write it to a file before anything else; `--input` and the queue anchors both need a path, and a scratch location is fine when nothing downstream will archive it. No `--domain` given and none obvious from context? Omitting the flag already defaults to searching every domain (`--domain`'s own default), so don't block on picking one — run Stage 1 bare and let safe mode gate what auto-applies. If a specific candidate still needs resolving, one step of the ladder is cheap enough to keep even at fast tier though the rest of it isn't: native_ai_full_workflow.md step 4's rung 1, a single cross-domain lookup — `--lookup "<term>"` prints every existing claim on the term (dictionary rules active or disabled, as FROM or TO; context rules; roster variants; queue rows) — not the full verification ladder the tier table tells you to skip, just that one query.1022. **Recover the raw baseline before reading a pre-corrected transcript.** If an ingest pipeline or previous API pass already touched the text, diff against the raw source first. Judge upstream edits as edits, not as ground truth.1033. **Load project priors and read the complete transcript.** Read `~/.transcript-fixer/contexts/<domain>.md` when present, then read the whole file before deciding early ambiguities.1044. **Run Stage 1 and inspect the real result.** Prefer explicit project domains plus `--apply-domain --json`. Read `deferred` and `review_enqueued`; never silently discard the sidecar or queue gap.1055. **Diff Stage 1 against raw/original.** If a rule changed correct speech, work from the original, retire the stored pair with `--report-false-positive "<from>" "<to>" --domain <domain>`, and verify it no longer fires.1066. **Triage every candidate.**107 - Confident: the sound change is plausible and context or an authoritative local source settles it.108 - Needs verification: a person, company, product, model, ticker, place, number, or other load-bearing term without a source.109 - Uncertain: evidence does not settle it; leave the original and enqueue.110 - Multi-channel entity fork: when independent transcripts disagree on a person name or other proper noun and no local authority settles it, collect the unresolved forks and ask the human once. Do not guess, and do not treat a majority vote as identity evidence.1117. **Apply the smallest edit that explains the sound.** Do not add words the speaker did not say. Correct ASR-derived metadata too, while leaving `asr_note` intact. A Stage 1 deferral you now agree with is closed through its queue row, not re-applied with sed: `--resolve-review <id> --decision accepted` (ids from `--list-review --review-file "<absolute-canonical-file>" --json`) performs the edit, or records a fix you already applied by hand without writing. Either way the row ends `accepted`; `kept_original` asserts the transcript was right as spoken and is never the exit for a fix you applied.1128. **Run a second pass.**113 - Every tier: run `--scan-traps` and inspect both hits and `unparsed`.114 - Full tier: use fresh-context review. For a single unsplit review, assign one corrected file and require a compact residual table or explicit `no new residuals`; an empty/truncated response is a failed review.115 - For a split, multi-file, or resumed Full review, follow [native_review_packets.md](references/native_review_packets.md) for packet assignment, JSON results, validation, and recovery.116 - High-stakes multi-recording: a sampled clip settles only that anchored item. If the user asked for a higher-quality or complete transcript and the baseline audio is available, load **`/daymade-audio:asr-transcribe-to-text`** and run its full-file transcription path across the complete clearest/canonical recording before claiming whole-transcript coverage; otherwise report `sampled cross-check only — incomplete`. Prefer a recognizer different from the producer of the canonical body. If only the same recognizer is available, the run proves complete-source coverage but is not independent cross-recognizer corroboration; state that boundary.1179. **Enqueue every unresolved item; escalate selectively.** First apply [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation), then follow `Review queue safety` below and [review_queue_dashboard.md](references/review_queue_dashboard.md). Open only this file when human review is needed. Detection and enqueueing are not correction: for a higher-quality/final claim, every queue row anchored to this exact file must leave `pending`. For human review, start the dashboard with `uv run scripts/review-dashboard/server.py --file "<absolute-canonical-file>"`; add `--item <id>` to land on one fork. If a human is unavailable, keep the artifact explicitly labeled `draft / unresolved — incomplete` and enumerate the rows; do not ship the raw suspect text under a completed quality claim.11810. **Read back the human state, then finalize.** When the human says they marked the dashboard, do not rerun ASR or ask the same questions again. First run `uv run scripts/fix_transcription.py --list-review --review-file "<absolute-canonical-file>" --review-status all --json`, apply any resulting file state, and require `stats.pending_total == 0` for that exact path; zero pending rows is required before the high-quality/final claim. Then diff the file actually edited, run numeric consistency when numbers matter, rerun plain Stage 1, re-grep known corrections, and confirm every change traces to a triage decision. Global queue counts cannot close or reopen this file's quality claim. Last, run `--close-sidecars --input "<absolute-canonical-file>"`: it re-reads every `*_changes.md`/`*_needs_review.md` entry against the file and the queue, refuses while an entry still reads as the original without a verdict or any row is pending, and removes the sidecars only when everything is closed (see `Finalization`).11911. **Compound the learning in the same turn.** Route each stable pattern to its correct home; do not leave confirmed fixes only in chat. Native-pass edits never reach Stage 1's correction history, so harvest them mechanically right after the final diff:120121 ~~~bash122 # Diff raw vs corrected into parseable trap candidates (review artifact —123 # you adjudicate the printed list; --write auto-appends only the recurring124 # (≥2x) non-bare candidates; --write-all also appends the one-off set)125 uv run scripts/harvest_corrections.py raw.md corrected.md \126 --context-file ~/.transcript-fixer/contexts/<domain>.md127 ~~~128129 Every emitted bullet is round-trip verified through the real trap parser before printing, and pairs already documented in the context file are skipped. A pair the bullet grammar cannot carry — a side with no lexical content or a `*` in it, such as a vendor's `***` redaction mask diffed against the real word — is dropped at the noise filter, and any bullet that still fails to parse is reported on stderr and excluded rather than aborting the run. High-frequency candidates are strong traps; single-occurrence ones need a human judgment — that is why `--write` leaves them out by default — and ⚠️ 裸形 candidates are never auto-written. This replaces hand-writing trap bullets from memory.13012. **Propagate entity fixes deliberately.**131132 First finish the same-file sweep: inspect `harvest_corrections.py --json`133 entries with nonzero `remaining`, then check the observed spelling family of134 each confirmed entity or technical identifier across body and ASR-derived135 metadata. Use the authoritative spelling and conventional filename casing;136 a user's informal dictation is not a request to preserve a typo. Preserve real137 alternate referents, aliases, and generic-character hits. Do not turn a138 same-file sweep into an unreviewed batch replacement or repeat settled questions. Then search139 only the owning project’s derived notes/summaries and review every hit; exclude140 raw ASR and correction sidecars because they preserve the evidence trail.141142The detailed provenance bar, local-first entity ladder, second-pass prompt, queue payload, and finalization rules are in [references/native_ai_full_workflow.md](references/native_ai_full_workflow.md).143144## Cross-skill caller contract145146A caller pipeline must:1471481. Run Stage 1 with the explicitly configured project domain(s), `--apply-domain`, and `--json`. If `deferred > review_enqueued`, persist the review sidecar outside any temporary directory or surface the gap as failure.1492. Run Native AI with this skill loaded, or report `Stage 1 only — incomplete`. Agent-less automation may use Stage 3 instead.150151Canonical call:152153~~~bash154uv run scripts/fix_transcription.py \155 --input "$staged" --stage 1 \156 --domain "$domains" --apply-domain --json157~~~158159A caller that wires only the script path never loads this contract. Script-path integration alone is therefore a Stage 1 prefilter, not transcript correction.160161Keep project domains warm: every confirmed recurring correction from the native pass must be added back to the correct project domain, roster, or context file.162163## Dictionary and identity safety164165Read [references/false_positive_guide.md](references/false_positive_guide.md) and [references/dictionary_identity_and_context.md](references/dictionary_identity_and_context.md) before adding a rule.166167| Pattern | Destination |168|---|---|169| Stable non-word or unique garble → canonical term | `--add ... --domain <project>` |170| Important recurring person and observed ASR variants | People roster |171| Correction right only inside a specific recurring phrase | `--add-context-rule PATTERN REPLACEMENT --domain <project>` (regex, domain-scoped; omit `--domain` for global) |172| Common/real word wrong only under a cue | Domain context trap, never a bare rule (a bare number or a single surname + 老师/总 is refused at roster load and by `--add` / `--import`, `--force` included) |173| Real name → different real name | Domain context + human/audio verification, never a bare rule |174| Confirmed-correct entity repeatedly reopened | Confirmed-correct context record |175| One-off sentence-local wording | Edit only; do not add |176177Stage 1's match-time layer (the third of the three layers in [references/false_positive_guide.md](references/false_positive_guide.md): add time, apply time, match time) also refuses a dictionary match on its own at three checks before risk scoring: the superset check (the corrected form is already in place), the common-word boundary check for short rules, and the word-boundary check. That last one asks, by script, whether the match is a fragment of real words: an ASCII match with an ASCII letter directly beside it is inside a longer word (`Cloud` in `iCloud`; digits do not count, so `cloud3` still corrects); a CJK match is refused only when every segment of a dictionary-only jieba cut that overlaps it is a multi-character word and one of them crosses the match boundary (新一 in 更新|一下, 问题记 in 问题|记录, 同龄 in 同龄人) — one single-character segment under the match (巨|神智|能, 叫|新|一下|单) means an unknown fragment and the match proceeds. Refusals are counted as `Refused at word boundaries` and in the JSON `boundary_refused`, listed in the Stage 1 summary, and never deferred. A context rule (`--add-context-rule`) skips this check but still goes through risk scoring, so in safe mode its match is deferred to the review queue rather than applied — accept it there, or run `--apply-all`, which switches the check off and applies every match; `--apply-domain` keeps the check. The layers are listed in [references/false_positive_guide.md](references/false_positive_guide.md).178179A context trap is a cue, not permission to replace blindly. Two annotation classes in a domain context file are **machine-readable vetoes that Stage 1 enforces** (when the domain is named via `--domain` — a whole-library run has no owner to veto with): a trap marked `禁裸词`/`禁入词典` demotes any dictionary rule with the same FROM to review, and a confirmed-correct (勿修) record demotes any rule whose FROM is that token — demotion beats `--apply-domain` trust-flattening, so a real-word rule (the 绿点→绿电 class: right in business context, wrong in UI context) can stay in the dictionary without firing blindly. `--apply-all` remains the operator's explicit override. Without the veto the only escape was `--report-false-positive`, which disables the rule in the contexts where it is right too. `--scan-traps` supports canonical `→` and legacy `≈` mappings with the same directional contract: left is observed ASR, right is intended text. Wrap an exact FROM phrase containing spaces in backticks:180181~~~markdown182- **`CC 思维链`/`CC 思维连` → 目标术语** — only under the domain's documented cue183~~~184185This demonstrates an exact ASR phrase candidate, not a person-name candidate. The domain context remains the authority for the real target and cue; the scanner only locates the literal FROM forms.186187Before adding any real-word-shaped rule, measure the project corpus:188189~~~bash190uv run scripts/fix_transcription.py \191 --probe "candidate" --corpus /path/to/project-transcripts/192193uv run scripts/fix_transcription.py \194 --add "candidate" "canonical" --domain myproject \195 --check-corpus --corpus /path/to/project-transcripts/196~~~197198User verdicts settle the occurrence immediately, but they do not make a replacement reusable. Fix the file first, then route the result through the table above: only a stable recurring pattern goes to the dictionary/roster/context; a rare sentence-local mishearing stays file-only. When the user confirms that two legitimate names or nicknames identify the same person, preserve whichever form was actually spoken and store the identity relationship as context, not as a replacement rule.199200## Review queue safety201202Read [references/review_queue_dashboard.md](references/review_queue_dashboard.md) before enqueueing or resolving.203204Minimum item:205206~~~json207[208 {209 "file": "/absolute/path/to/transcript.md",210 "line": 142,211 "original": "<suspect-token-only>",212 "suggested": "<best-candidate>",213 "kind": "entity",214 "context": "<verbatim sentence, or unique clause/span for same-line repeats>",215 "evidence": "<what was checked>"216 }217]218~~~219220Safety rules:221222- `file` is mandatory for this workflow. Without it, acceptance can record a verdict without editing the transcript.223- `original` is only the suspect token/span; never put the whole sentence there.224- `context` is copied verbatim; `line` is the key, not `line_hint`.225- `suggested` is the key, not `suggestion`. Use `actions`, not `action_pack`.226- Resolve one occurrence at a time; sweep sibling entity occurrences only after the whole batch is resolved.227- A `pending` row is a blocking state for a high-quality/final transcript, not proof that the issue was handled. Queue detection without a human/evidence verdict leaves the artifact incomplete.228- Read `resolved_text` after an override; the listing can still display the rejected suggestion.229- A single-line `asr_note` ledger is masked on the accept path too, so resolving an item never edits the provenance line that cites the old form. A fix you applied by hand before resolving is still closed with `--decision accepted` (or `overridden` for its override text): when the context recorded at enqueue reappears anywhere in the file with the suggestion in the slot the original occupied, the verdict is recorded without writing (`already in place at the anchor — recorded without writing`). Line drift does not matter, and neither does the original surviving in other utterances far from the hint, or inside the suggestion itself (阿里→阿里云). It fails closed with `ReAnchorNeeded`, and the message says which, when the original still sits within the resolve window of the hint outside the suggestion (the anchored utterance, or a look-alike beside it, is still garbled — settle that line on its own row or by hand, then resolve; do not `--reanchor-review` this row onto it), when the recorded neighbourhood also appears with a third form in the slot (anywhere in the file at the width that matched, or near the hint at any width down to two characters a side), or when the edit touched the characters next to the slot. What it cannot see: an anchored utterance deleted or rewritten past both neighbours while an identical one elsewhere reads corrected — that records `accepted` with nothing written, and a garble still in the file is re-deferred by the next Stage 1 run. `--reanchor-review <id>` repairs a row whose original moved or drifted and refuses one whose context already reads corrected. A row the queue can neither re-anchor nor recognise closes with `--decision skipped --note <what happened>`. `kept_original` asserts the transcript keeps the original form — never the exit for a fix you applied.230- If the file moved or drifted, run `--reanchor-review`. Add `--reanchor-root` or `--reanchor-to` when requested. Do not hand-edit around a pending item.231- Promote every `decision_note` by meaning; storing a note does not change the dictionary, roster, context, or false-positive state.232233Core commands:234235~~~bash236uv run scripts/fix_transcription.py --enqueue-review items.json237uv run scripts/fix_transcription.py \238 --list-review --review-file "<absolute-canonical-file>" \239 --review-status all --json240uv run scripts/fix_transcription.py --show-review <id> --json241uv run scripts/fix_transcription.py --reanchor-review <id>242uv run scripts/fix_transcription.py \243 --resolve-review <id> --decision accepted --by reviewer244~~~245246## Numbers, artifacts, and batches247248Read [references/advanced_correction_evidence.md](references/advanced_correction_evidence.md) when any of these conditions holds:249250- A number, bound, price, share, deadline, or magnitude drives a decision.251- Two recordings exist for one meeting.252- A load-bearing name or term survived the local ladder unresolved, source audio is available, and the current authorization already permits a second recognizer.253- A whiteboard, slide, or photographed written artifact can independently settle a name/term.254- Several related files should share one correction list.255- A 10+ file batch is being delegated.256257Numeric-slot scan:258259~~~bash260uv run scripts/scan_numeric_consistency.py transcript.md --domain myproject261~~~262263Its output is candidates, never automatic edits. For a load-bearing number, follow [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation). When original audio is accessible, use the review dashboard to decide by ear. Otherwise retain competing readings unless other evidence settles them, and continue the remaining items; do not require export permission.264265For delegated batches, every agent owns one file, cannot cross-file replace, and returns a residual list. Afterward compare `git diff --name-only` with the explicit file list and inspect every unexpected file under the repository's worktree-safety rules.266267## Finalization268269- Native mode edits the original file directly. Rerun plain `--stage 1` to confirm; a clean no-op writes no Stage 1 sidecar.270- When a newer `*_stage1.md` exists and the original was not edited after it, a plain Stage 1 rerun atomically promotes it and removes disposable sidecars. It retains `*_changes.md` and `*_needs_review.md`: those are review evidence, and `--close-sidecars` is the command that decides they are closed. `--apply-all` never takes this promotion path.271- Do not use the existence of an output file as the success signal; read JSON/exit status and independently read the final file.272- Preserve raw transcripts, `*_changes.md`, and `*_needs_review.md` as evidence until `--close-sidecars --input "<absolute-canonical-file>"` reports `closed`: every entry reads applied in the file (or the original form no longer appears anywhere in the ledger-masked transcript) or is answered by a decided queue row for this exact file — one row per occurrence, matched by nearest line, so a second occurrence with no row of its own stays undecided — or belongs to a FROM→TO rule that has since been disabled as a false positive (`disabled`: no longer a question), and the file has zero pending rows. It exits 1 (`open`) naming the undecided entries and pending ids, 2 (`blocked`) when a `*_stage1.md` newer than the file still awaits the plain Stage 1 rerun or a report carries entries the parser cannot read (an unreadable report is evidence, not an empty one), and 0 after removing the evidence and stale run outputs. For an entry that still reads as the original and has no row at all, `--decide-raw kept_original|skipped --by <who> --note <why>` records the verdict through the queue at closure — an audit trail, not a silent deletion. `--dry-run` shows the verdict without deleting; `--json` returns it.273- `*_stage2.md` and `*_dryrun.md` are run-scoped outputs of the API route and the preview, not archive material: promote or discard them in the session that produced them. One newer than the transcript is unpromoted — `--close-sidecars` retains it, says so, and removes it only with `--discard-unpromoted`.274- Re-grep a known corrected form in the final file and verify no correction remains only in `asr_note` or a sidecar.275- If a queued item was renamed away, repair it with `--reanchor-review` rather than resolving it with a false terminal verdict.276277## Agent-less API route278279Only when no Claude/Codex agent can perform Native AI Correction:280281~~~bash282export GLM_API_KEY="<api-key>"283uv run scripts/fix_transcript_enhanced.py input.md --output ./corrected284~~~285286The route writes `<stem>_stage2.md` beside the input. It is this run's output, not a second transcript: verify it, then promote it onto the transcript or discard it before the session ends. A `_stage2.md` left beside a transcript that is not it cannot be told apart from reviewed work a month later.287288Read [references/glm_api_setup.md](references/glm_api_setup.md), [references/installation_setup.md](references/installation_setup.md), and the explicitly API-oriented portions of [references/workflow_guide.md](references/workflow_guide.md). When a chunk fails after retries, the API route keeps that chunk and its original surrounding separators byte-for-byte and prints a warning; if every chunk fails, the complete output equals the input. For `fix_transcription.py --stage 2|3 --json`, read the additive `stage2_total_chunks`, `stage2_failed_chunks`, and `stage2_degraded` fields: `stage2_degraded: true` is not a fully corrected run even though the safely retained artifact is emitted. The enhanced wrapper exits nonzero after writing that retained artifact when any Stage 2 chunk is degraded. Verify the output rather than assuming the warning means a corrected result exists.289290The enhanced API wrapper can also add paragraph breaks, reduce repeated filler,291and present corrections for interactive review. Those are API-wrapper features;292they do not authorize Native AI to rewrite wording for fluency.293294## Utility commands295296~~~bash297# Extract likely errors without editing298uv run scripts/fix_transcription.py --extract-uncertain \299 --input meeting.md --output ./review300301# Import curated preset rules302uv run scripts/fix_transcription.py --load-presets tech303304# Repair timestamps305uv run scripts/fix_transcript_timestamps.py meeting.txt --in-place306307# Split and rebase sections308uv run scripts/split_transcript_sections.py meeting.txt \309 --first-section-name "intro" \310 --section "main::<verbatim marker>" \311 --rebase-to-zero312313# Word-level review diff314uv run scripts/generate_word_diff.py original.md corrected.md output.html315316# Harvest native-pass edits into context-trap candidates317uv run scripts/harvest_corrections.py raw.md corrected.md \318 --context-file ~/.transcript-fixer/contexts/myproject.md --write319320# Batch-adjudicate a transcript's pending queue rows via clip-level321# cross-recognition (one source audio, one second engine, both windows)322uv run scripts/verify_queue_audio.py \323 --transcript /abs/meeting.md --audio /abs/source.wav \324 --speed 1.0 --engine-script /abs/stepfun-asr/scripts/asr_transcribe.py325326# Multi-format Stage 1/API comparison report327uv run scripts/generate_diff_report.py \328 original.md original_stage1.md original_stage2.md \329 --output ./diff_reports330331# Every existing claim on a term: dictionary (active/disabled), context rules, roster, queue332uv run scripts/fix_transcription.py --lookup "候选词"333334# Decide whether one finished transcript's review sidecars are closed, then remove them335uv run scripts/fix_transcription.py --close-sidecars \336 --input "/absolute/meeting.md" --dry-run337338# Setup health339uv run scripts/fix_transcription.py --validate340~~~341342Read [references/script_parameters.md](references/script_parameters.md) before using less-common flags. Read [references/database_schema.md](references/database_schema.md) before custom SQL; correction columns are `from_text` and `to_text`.343344## Reference map345346All references are one level from this file.347348| Need | Read |349|---|---|350| Full native correction sequence | [native_ai_full_workflow.md](references/native_ai_full_workflow.md) |351| Split/batch cold-review packets, result validation, and interruption recovery | [native_review_packets.md](references/native_review_packets.md) |352| Dictionary, people roster, domain contexts | [dictionary_identity_and_context.md](references/dictionary_identity_and_context.md) |353| False-positive policy | [false_positive_guide.md](references/false_positive_guide.md) |354| Queue, dashboard, audio, re-anchor | [review_queue_dashboard.md](references/review_queue_dashboard.md) |355| Numbers, photos, multi-recording, clip cross-check, batches, batch pending-queue audio adjudication | [advanced_correction_evidence.md](references/advanced_correction_evidence.md) |356| Context-file grammar/template | [domain_context_guide.md](references/domain_context_guide.md) |357| CLI flags and review-item schema | [script_parameters.md](references/script_parameters.md) |358| Database schema and queries | [database_schema.md](references/database_schema.md), [sql_queries.md](references/sql_queries.md) |359| Short command lookup | [quick_reference.md](references/quick_reference.md), [dictionary_guide.md](references/dictionary_guide.md) |360| Learning loop | [iteration_workflow.md](references/iteration_workflow.md) |361| Native examples | [example_session_dji_minutes.md](references/example_session_dji_minutes.md) |362| Agent-less API example/config | [example_session.md](references/example_session.md), [glm_api_setup.md](references/glm_api_setup.md), [installation_setup.md](references/installation_setup.md) |363| Architecture and formats | [architecture.md](references/architecture.md), [file_formats.md](references/file_formats.md) |364| Operational guidance | [best_practices.md](references/best_practices.md), [troubleshooting.md](references/troubleshooting.md), [team_collaboration.md](references/team_collaboration.md), [workflow_guide.md](references/workflow_guide.md) |365366Bundled scripts are executed, not loaded into context. The primary entry points are `fix_transcription.py`, `scan_numeric_consistency.py`, `fetch_minute_audio.py`, `review-dashboard/server.py`, and the diff/timestamp/splitting utilities listed above.367368## Handoff369370After correction, hand off to `/daymade-audio:meeting-minutes-taker` only when the user wants a structured summary. Do not create meeting minutes automatically: transcript correction and summarization are separate scopes.