From ffc07922c7a2fb2babcb4ac4f7641d59e9f38dff Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 25 May 2026 08:34:23 -0500 Subject: [PATCH] Cleanup following the render stage implementation and remove the completed roadmap --- .DS_Store | Bin 6148 -> 0 bytes README.md | 2 +- docs/roadmap/render.md | 206 --------------------------------- internal/stage/analyze.go | 2 +- internal/stage/analyze_test.go | 4 +- internal/stage/normalize.go | 2 +- internal/stage/render.go | 4 +- internal/stage/render_test.go | 4 +- internal/stage/trim.go | 4 +- 9 files changed, 11 insertions(+), 217 deletions(-) delete mode 100644 .DS_Store delete mode 100644 docs/roadmap/render.md diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a84cbf781fdcf83353560b410194ffc23d940d1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&1%~~5T3PNcNG)xAuT;F^y(5DlGY&?Rdrt=GVP%)juaVNRMr|xjt|D5WBWpd zK0zO#(6`7n=gj`t)Yt}tq0le`v)}CQ%&zv6*wFw0^QKV?pbh{WRbk1&W{aqKr5jSR zBUMDE*GS+MVAOQ(C5FoDJUpULgVIo7E{}GOpILn*OKde-)JgGkA zoY(jle<){u9^})^3nrgbuPbE|F6|)v5cg-j`pG+)=0TkHCpsXG`snicW1L2E=E-Rq zWjfY36Wno}Uj6xe-foCD?L|Y(TW1%q8=`&QUMw8`?B(g@RqtyuO6500FT*cS%dy2J zd`DyJ$&x|KzmcQy(s7sHNOn4w0p9(2=iUGQ^>(*!w72hgxZnP_{v=IgI>0@bXZ-7t z$s`7d0b<|)2JEfEYX_`Bp~L_&@HiPz_k%!H=vpic>Z=1AT>>Cha99i4s+LeO!lG-j zFo-iK%!DGEP-aUEX2Nk?SU=ZdVbFvFv&9FqJ2P9MFugmjFSI)_*C4gT05P!6K-qNb zD*wOVUH`8KQI8lP1|AgytkUs29v;c&){VodTq{toQI#ma!r&?d4V{Y7SEb??s9JDb YC8SQx|+6#5a+G*Ckf{8I)V06mLyUH||9 diff --git a/README.md b/README.md index 4e35281..8ae4562 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Narratio is a stage-driven Go orchestrator for turning D&D session audio into polished transcripts and generated artifacts. -It runs a deterministic workflow across `prepare`, `transcribe`, `merge`, `polish`, `normalize`, `trim`, `analyze`, and `publish`, with manifest-driven continuation and restore support. +It runs a deterministic workflow across `prepare`, `transcribe`, `merge`, `polish`, `normalize`, `trim`, `render`, `analyze`, and `publish`, with manifest-driven continuation and restore support. ```bash narratio run 2026-04-04 diff --git a/docs/roadmap/render.md b/docs/roadmap/render.md deleted file mode 100644 index c5efabf..0000000 --- a/docs/roadmap/render.md +++ /dev/null @@ -1,206 +0,0 @@ -# Roadmap: Render Stage - -Status: Completed - -This roadmap defines a post-1.0 feature addition: a new `render` stage that uses `seriatim render` to produce human-readable Markdown versions of the final transcript artifacts. - -Planned behavior belongs only in this roadmap until implementation lands. Current-behavior docs, examples, and command references must be updated only after the code is implemented and tested. - -## Goal - -Add a first-class stage between `trim` and `analyze`: - -1. `prepare` -2. `transcribe` -3. `merge` -4. `polish` -5. `normalize` -6. `trim` -7. `render` -8. `analyze` -9. `publish` -10. `notify` - -The stage renders Markdown versions of: - -- `narratio.transcript.final` -- `narratio.transcript.final_trimmed` - -The stage produces new built-in artifacts: - -| Source ID | Canonical path | Output kind | -| --- | --- | --- | -| `narratio.transcript.final_markdown` | `transcripts/final.md` | `transcript_final_markdown` | -| `narratio.transcript.final_trimmed_markdown` | `transcripts/final.trimmed.md` | `transcript_final_trimmed_markdown` | - -Default publish outputs should include: - -- `narratio.transcript.final_trimmed` -- `narratio.transcript.final_markdown` -- `narratio.transcript.final_trimmed_markdown` - -## Public Contract - -Add `pipeline.render` with strict YAML decoding. - -Fields: - -| Field | Type | Default | Validation | -| --- | --- | --- | --- | -| `enabled` | bool | `true` | optional | -| `format` | string | `markdown` | only `markdown` is supported | -| `title` | string | empty | optional | -| `include_timestamps` | bool | `true` | optional | -| `include_segment_ids` | bool | `false` | optional | -| `include_metadata` | bool | `false` | optional | - -Title behavior: - -- if `pipeline.render.title` is non-empty, pass it as `--title`; -- otherwise, if `session.title` is non-empty, pass `session.title` as `--title`; -- otherwise, omit `--title` and let Seriatim use its default. - -The initial implementation supports only Markdown. Future formats require explicit config validation and artifact naming decisions. - -## Implementation Stages - -### Stage 1: Artifact, Config, and Adapter Contracts - -- Extend the transcript artifact model with the two Markdown built-ins. -- Register Markdown artifacts as text content in the artifact registry so they work in artifact resolution, publish outputs, locks, status, artifacts list, and Scriptorium inputs. -- Add `RenderConfig` under `PipelineConfig` and apply defaults in the config loader/defaulting path. -- Validate `pipeline.render.format` as `markdown` and keep unknown fields rejected by strict YAML decoding. -- Extend the Seriatim adapter interface with `Render(ctx, RenderRequest)`. -- Add subprocess support for `seriatim render` with: - - `--input-file` - - `--output-file` - - `--format markdown` - - optional `--title` - - explicit boolean behavior for timestamps, segment IDs, and metadata. -- Validate render output as non-empty text, not JSON. -- Write Seriatim render stdout/stderr logs and generated invocation config consistently with existing Seriatim stage calls. - -### Stage 2: Render Stage Runtime - -- Add `renderStage` to the stage package. -- Insert `renderStage{}` into `stage.All()` after `trimStage{}` and before `analyzeStage{}`. -- Make `run-stage render ` work through the existing stage selection path. -- If render is disabled, mark the stage succeeded with metadata and no outputs. -- If enabled, resolve inputs manifest-first using existing artifact resolution: - - final transcript from `narratio.transcript.final`; - - final trimmed transcript from `narratio.transcript.final_trimmed`. -- Render run-local Markdown outputs first, then materialize canonical outputs: - - `transcripts/final.md` - - `transcripts/final.trimmed.md` -- Record manifest outputs with the new output kinds and source IDs. -- Record metadata for input paths/provenance, canonical/run-local output paths, format, resolved title, boolean render settings, adapter duration/exit code/binary, and adapter metadata. -- On missing required JSON inputs, fail clearly with guidance to run `normalize` or `trim` as appropriate. - -### Stage 3: Publish, Analyze, Docs, and Examples - -- Update default publish outputs to include both Markdown artifacts in addition to final trimmed JSON. -- Ensure publish output destination derivation works for Markdown built-ins through the shared artifact policy path. -- Ensure Scriptorium input validation accepts Markdown built-ins as ordinary built-in sources. -- Update analyze missing-input guidance so required Markdown built-in inputs point operators to `run-stage render`. -- Update user/operator/internal docs only after implementation: - - `docs/cli.md` - - `docs/config.md` - - `docs/operations.md` - - `docs/internal/README.md` - - `docs/internal/artifacts.md` - - new `docs/internal/stage-render.md` - - `docs/integrations/seriatim.md` -- Update examples only where useful; defaults should work without an explicit `pipeline.render` block. - -## Seriatim Adapter Contract - -Add a narrow render request/result beside the existing merge, normalize, and trim contracts. - -Request fields: - -- binary -- input transcript path -- output Markdown path -- format -- title -- include timestamps -- include segment IDs -- include metadata -- stdout log path -- stderr log path -- generated config path -- timeout - -Result fields: - -- output path -- stdout log path -- stderr log path -- generated config path -- exit code -- duration -- invoked binary -- format -- title -- metadata - -The adapter owns subprocess command construction and validation of the non-empty output file. Stage logic should express intent in Narratio terms and should not construct subprocess arguments directly. - -## Testing Guidance - -Focused tests: - -- `internal/artifactmodel` and `internal/artifacts` - - new Markdown source IDs, canonical paths, output kinds, producer stage, text validation, catalog ordering; - - resolver fallback from canonical Markdown paths; - - publish destination derivation for Markdown built-ins. -- `internal/config` - - render defaults; - - strict decode rejects unknown render fields; - - invalid format fails validation; - - default publish outputs include final trimmed JSON plus both Markdown artifacts. -- `internal/adapters/seriatim` - - render command args; - - title omission vs explicit title; - - boolean flag behavior; - - generated config; - - stdout/stderr logs; - - non-empty output validation; - - failure wrapping. -- `internal/stage` - - render resolves final and final-trimmed inputs from manifest outputs before canonical fallback; - - render writes run-local outputs and materializes canonical Markdown outputs; - - render records manifest outputs, logs, generated configs, and metadata; - - disabled render succeeds without outputs; - - missing final/final-trimmed inputs fail clearly. -- `internal/app` - - full plan order includes `render`; - - `run-stage render ` works; - - force rerunning render marks analyze, publish, and notify stale; - - status and artifacts list include Markdown built-ins; - - publish defaults include Markdown outputs. - -Validation commands: - -- `go test ./internal/artifactmodel ./internal/artifacts -v` -- `go test ./internal/config -v` -- `go test ./internal/adapters/seriatim -v` -- `go test ./internal/stage -run Render -v` -- `go test ./internal/app -run 'Plan|RunStage|Publish|Artifacts|Status' -v` -- `go test ./...` - -## Non-Goals - -- Do not change existing JSON transcript source IDs, canonical paths, or output kinds. -- Do not make Markdown output paths configurable in the first implementation. -- Do not add additional render formats before the format naming and artifact naming contract is defined. -- Do not move Seriatim subprocess details into stage logic. -- Do not document the render stage as implemented outside this roadmap until implementation lands. - -## Assumptions - -- `render` is enabled by default. -- Markdown canonical paths are fixed built-in artifact paths. -- `format: markdown` is the only supported initial format. -- Both Markdown outputs are included in default publish outputs. -- Existing publish layout and current-state commit behavior remain unchanged. diff --git a/internal/stage/analyze.go b/internal/stage/analyze.go index 4725d93..cbe39e7 100644 --- a/internal/stage/analyze.go +++ b/internal/stage/analyze.go @@ -672,7 +672,7 @@ func resolveScriptoriumInput( return "", false, nil, fmt.Errorf("trimmed transcript input is unavailable; run trim stage first") case artifacts.ArtifactTranscriptFinalMarkdown, artifacts.ArtifactTranscriptFinalTrimmedMarkdown: return "", false, nil, fmt.Errorf( - "rendered markdown transcript input is unavailable for source %q; run narratio run-stage --force render %s", + "rendered markdown transcript input is unavailable for source %q; run narratio run-stage render %s --force", descriptor.Source.ID, paths.SessionID, ) diff --git a/internal/stage/analyze_test.go b/internal/stage/analyze_test.go index 99e11cc..008e9e4 100644 --- a/internal/stage/analyze_test.go +++ b/internal/stage/analyze_test.go @@ -1083,7 +1083,7 @@ func TestAnalyzeFailsWhenRenderedMarkdownTranscriptMissing(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "run narratio run-stage --force render") { + if !strings.Contains(err.Error(), "run narratio run-stage render") || !strings.Contains(err.Error(), "--force") { t.Fatalf("error = %q, want render guidance", err.Error()) } } @@ -1101,7 +1101,7 @@ func TestAnalyzeFailsWhenRenderedTrimmedMarkdownTranscriptMissing(t *testing.T) if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "run narratio run-stage --force render") { + if !strings.Contains(err.Error(), "run narratio run-stage render") || !strings.Contains(err.Error(), "--force") { t.Fatalf("error = %q, want render guidance", err.Error()) } } diff --git a/internal/stage/normalize.go b/internal/stage/normalize.go index b29535b..d4ddcc6 100644 --- a/internal/stage/normalize.go +++ b/internal/stage/normalize.go @@ -99,7 +99,7 @@ func (normalizeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) ( stderrPath = filepath.Join(runLayout.LogsDir, "seriatim.normalize.stderr.log") generatedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.normalize.generated.yml") } - timeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout) + timeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout) if err != nil { return nil, fmt.Errorf("normalize: resolve seriatim timeout: %w", err) } diff --git a/internal/stage/render.go b/internal/stage/render.go index d61262b..c2de8af 100644 --- a/internal/stage/render.go +++ b/internal/stage/render.go @@ -131,7 +131,7 @@ func (renderStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St finalTrimmedGeneratedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.render.final_trimmed.generated.yml") } - timeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout) + timeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout) if err != nil { return nil, fmt.Errorf("render: resolve seriatim timeout: %w", err) } @@ -260,7 +260,7 @@ func wrapRenderInputResolveError(err error, sessionID, sourceID, guidanceStage s var notFound *artifacts.SessionArtifactNotFoundError if errors.As(err, ¬Found) { return fmt.Errorf( - "render: required input %q is unavailable; run narratio run-stage --force %s %s", + "render: required input %q is unavailable; run narratio run-stage %s %s --force", sourceID, guidanceStage, sessionID, diff --git a/internal/stage/render_test.go b/internal/stage/render_test.go index 6637796..a2a5d8f 100644 --- a/internal/stage/render_test.go +++ b/internal/stage/render_test.go @@ -103,7 +103,7 @@ func TestRenderStageFailsWhenFinalInputMissing(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "run narratio run-stage --force normalize") { + if !strings.Contains(err.Error(), "run narratio run-stage normalize") || !strings.Contains(err.Error(), "--force") { t.Fatalf("error = %q, want normalize guidance", err.Error()) } } @@ -117,7 +117,7 @@ func TestRenderStageFailsWhenFinalTrimmedInputMissing(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "run narratio run-stage --force trim") { + if !strings.Contains(err.Error(), "run narratio run-stage trim") || !strings.Contains(err.Error(), "--force") { t.Fatalf("error = %q, want trim guidance", err.Error()) } } diff --git a/internal/stage/trim.go b/internal/stage/trim.go index f8f2001..cadfb9e 100644 --- a/internal/stage/trim.go +++ b/internal/stage/trim.go @@ -315,7 +315,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag trimStderrLogPath = filepath.Join(runLayout.LogsDir, "seriatim.trim.stderr.log") trimGeneratedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.trim.generated.yml") } - trimTimeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout) + trimTimeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout) if err != nil { return nil, fmt.Errorf("trim: resolve seriatim timeout: %w", err) } @@ -398,7 +398,7 @@ func copyTranscript(store artifacts.Store, src, dst string) error { return nil } -func resolveTrimSeriatimTimeout(raw string) (time.Duration, error) { +func resolveSeriatimStageTimeout(raw string) (time.Duration, error) { trimmed := strings.TrimSpace(raw) if trimmed == "" { return 0, nil