Replace normalize stage with trim scaffold

This commit is contained in:
2026-05-08 16:51:01 +00:00
parent 4d646f161a
commit 38dae8b584
11 changed files with 88 additions and 45 deletions

View File

@@ -17,7 +17,7 @@ Implemented now:
Not implemented yet: Not implemented yet:
- real `normalize` behavior - real `trim` behavior
- real `archive` behavior - real `archive` behavior
- real `notify` behavior - real `notify` behavior
- additional analyze artifacts beyond `session_recap` - additional analyze artifacts beyond `session_recap`

View File

@@ -23,7 +23,7 @@ Implemented:
Still placeholder/future: Still placeholder/future:
- `normalize` stage behavior - `trim` stage behavior
- `archive` stage behavior - `archive` stage behavior
- `notify` stage behavior - `notify` stage behavior
- additional Scriptorium artifact types beyond `session_recap` - additional Scriptorium artifact types beyond `session_recap`
@@ -36,9 +36,9 @@ Canonical stage order:
1. `prepare` 1. `prepare`
2. `transcribe` 2. `transcribe`
3. `normalize` 3. `merge`
4. `merge` 4. `polish`
5. `polish` 5. `trim`
6. `analyze` 6. `analyze`
7. `archive` 7. `archive`
8. `notify` 8. `notify`

View File

@@ -32,7 +32,7 @@ func TestExecuteValidCommands(t *testing.T) {
wantOut string wantOut string
}{ }{
{name: "run", args: []string{"run", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio run: session 2026-05-03; executed=8 skipped=0; manifest="}, {name: "run", args: []string{"run", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio run: session 2026-05-03; executed=8 skipped=0; manifest="},
{name: "plan", args: []string{"plan", "--config", pipelinePath, "--session", sessionPath}, wantOut: "prepare: skip\ntranscribe: skip\nnormalize: skip\nmerge: skip\npolish: skip\nanalyze: skip\narchive: skip\nnotify: skip"}, {name: "plan", args: []string{"plan", "--config", pipelinePath, "--session", sessionPath}, wantOut: "prepare: skip\ntranscribe: skip\nmerge: skip\npolish: skip\ntrim: skip\nanalyze: skip\narchive: skip\nnotify: skip"},
{name: "status", args: []string{"status", "--manifest", manifestPath}, wantOut: "session_id: 2026-05-03"}, {name: "status", args: []string{"status", "--manifest", manifestPath}, wantOut: "session_id: 2026-05-03"},
{name: "resume", args: []string{"resume", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio resume: session 2026-05-03 has no remaining stages"}, {name: "resume", args: []string{"resume", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio resume: session 2026-05-03 has no remaining stages"},
{name: "run-stage", args: []string{"run-stage", "--config", pipelinePath, "--session", sessionPath, "polish"}, wantOut: "narratio run-stage: stage=polish executed=0 skipped=1 force=false; manifest="}, {name: "run-stage", args: []string{"run-stage", "--config", pipelinePath, "--session", sessionPath, "polish"}, wantOut: "narratio run-stage: stage=polish executed=0 skipped=1 force=false; manifest="},
@@ -106,6 +106,22 @@ func TestExecuteRunStageUnknownFails(t *testing.T) {
} }
} }
func TestExecuteRunStageNormalizeIsRejected(t *testing.T) {
workspaceRoot := t.TempDir()
pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot, "https://example.com/transcribe")
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute([]string{"run-stage", "--config", pipelinePath, "--session", sessionPath, "normalize"}, &stdout, &stderr)
if code == 0 {
t.Fatal("exit code = 0, want non-zero")
}
if !strings.Contains(stderr.String(), "unknown stage") {
t.Fatalf("stderr = %q, want unknown stage error", stderr.String())
}
}
func TestExecuteRunStageTranscribeUsesConfiguredWhisperXServer(t *testing.T) { func TestExecuteRunStageTranscribeUsesConfiguredWhisperXServer(t *testing.T) {
workspaceRoot := t.TempDir() workspaceRoot := t.TempDir()
var serverCalls int var serverCalls int

View File

@@ -27,7 +27,7 @@ func TestPlanCreatesAndReusesWorkdir(t *testing.T) {
if !strings.Contains(got, "narratio plan: workdir prepared at") { if !strings.Contains(got, "narratio plan: workdir prepared at") {
t.Fatalf("first output = %q, want workdir prepared", got) t.Fatalf("first output = %q, want workdir prepared", got)
} }
for _, name := range []string{"prepare", "transcribe", "normalize", "merge", "polish", "analyze", "archive", "notify"} { for _, name := range []string{"prepare", "transcribe", "merge", "polish", "trim", "analyze", "archive", "notify"} {
if !strings.Contains(got, name+": run") { if !strings.Contains(got, name+": run") {
t.Fatalf("first output = %q, missing stage %q", got, name) t.Fatalf("first output = %q, missing stage %q", got, name)
} }
@@ -42,7 +42,7 @@ func TestPlanCreatesAndReusesWorkdir(t *testing.T) {
filepath.Join(sessionWorkdir, "inputs"), filepath.Join(sessionWorkdir, "inputs"),
filepath.Join(sessionWorkdir, "audio"), filepath.Join(sessionWorkdir, "audio"),
filepath.Join(sessionWorkdir, "transcripts", "raw"), filepath.Join(sessionWorkdir, "transcripts", "raw"),
filepath.Join(sessionWorkdir, "transcripts", "normalized"), filepath.Join(sessionWorkdir, "transcripts", "trimmed"),
filepath.Join(sessionWorkdir, "artifacts"), filepath.Join(sessionWorkdir, "artifacts"),
filepath.Join(sessionWorkdir, "config"), filepath.Join(sessionWorkdir, "config"),
filepath.Join(sessionWorkdir, "logs"), filepath.Join(sessionWorkdir, "logs"),
@@ -81,8 +81,8 @@ func TestPlanShowsRunAndSkipFromManifest(t *testing.T) {
if !strings.Contains(got, "prepare: skip") || !strings.Contains(got, "transcribe: skip") { if !strings.Contains(got, "prepare: skip") || !strings.Contains(got, "transcribe: skip") {
t.Fatalf("output = %q, want prepare/transcribe skipped", got) t.Fatalf("output = %q, want prepare/transcribe skipped", got)
} }
if !strings.Contains(got, "normalize: run") { if !strings.Contains(got, "trim: run") {
t.Fatalf("output = %q, want normalize run", got) t.Fatalf("output = %q, want trim run", got)
} }
if !strings.Contains(got, "totals: run=6 skip=2") { if !strings.Contains(got, "totals: run=6 skip=2") {
t.Fatalf("output = %q, want totals run=6 skip=2", got) t.Fatalf("output = %q, want totals run=6 skip=2", got)

View File

@@ -4,7 +4,7 @@ import "testing"
func TestBuildFullPlanOrder(t *testing.T) { func TestBuildFullPlanOrder(t *testing.T) {
got := BuildFullPlan() got := BuildFullPlan()
want := []string{"prepare", "transcribe", "normalize", "merge", "polish", "analyze", "archive", "notify"} want := []string{"prepare", "transcribe", "merge", "polish", "trim", "analyze", "archive", "notify"}
if len(got) != len(want) { if len(got) != len(want) {
t.Fatalf("len(plan) = %d, want %d", len(got), len(want)) t.Fatalf("len(plan) = %d, want %d", len(got), len(want))
} }

View File

@@ -44,8 +44,8 @@ func TestResumeStartsAfterCompletedStages(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("load manifest: %v", err) t.Fatalf("load manifest: %v", err)
} }
if loaded.Stages["normalize"] == nil || loaded.Stages["normalize"].Status != manifest.StatusSucceeded { if loaded.Stages["trim"] == nil || loaded.Stages["trim"].Status != manifest.StatusSucceeded {
t.Fatalf("normalize stage = %#v, want succeeded", loaded.Stages["normalize"]) t.Fatalf("trim stage = %#v, want succeeded", loaded.Stages["trim"])
} }
} }
@@ -56,7 +56,7 @@ func TestResumeNoRemainingStages(t *testing.T) {
store := &manifest.LocalStore{} store := &manifest.LocalStore{}
m := manifest.New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC)) m := manifest.New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
for _, name := range []string{"prepare", "transcribe", "normalize", "merge", "polish", "analyze", "archive", "notify"} { for _, name := range []string{"prepare", "transcribe", "merge", "polish", "trim", "analyze", "archive", "notify"} {
m.MarkStageSucceeded(name, time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil) m.MarkStageSucceeded(name, time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
} }
if err := store.Save(context.Background(), manifestPath, m); err != nil { if err := store.Save(context.Background(), manifestPath, m); err != nil {
@@ -85,7 +85,7 @@ func TestResumeForceRerunsSucceeded(t *testing.T) {
store := &manifest.LocalStore{} store := &manifest.LocalStore{}
m := manifest.New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC)) m := manifest.New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
for _, name := range []string{"prepare", "transcribe", "normalize", "merge", "polish", "analyze", "archive", "notify"} { for _, name := range []string{"prepare", "transcribe", "merge", "polish", "trim", "analyze", "archive", "notify"} {
m.MarkStageSucceeded(name, time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil) m.MarkStageSucceeded(name, time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
} }
if err := store.Save(context.Background(), manifestPath, m); err != nil { if err := store.Save(context.Background(), manifestPath, m); err != nil {
@@ -165,3 +165,30 @@ func TestRunStageSkipAndForce(t *testing.T) {
t.Fatalf("output = %q, want force rerun", out.String()) t.Fatalf("output = %q, want force rerun", out.String())
} }
} }
func TestRunStageTrimPlaceholderExecutes(t *testing.T) {
workspaceRoot := t.TempDir()
pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
manifestPath := filepath.Join(workspaceRoot, "work", "2026-05-03", "manifest.json")
var out bytes.Buffer
err := RunStage(context.Background(), []string{"--config", pipelinePath, "--session", sessionPath, "trim"}, &out)
if err != nil {
t.Fatalf("RunStage(trim) error = %v", err)
}
if !strings.Contains(out.String(), "stage=trim executed=1 skipped=0") {
t.Fatalf("output = %q, want stage=trim executed", out.String())
}
store := &manifest.LocalStore{}
m, err := store.Load(context.Background(), manifestPath)
if err != nil {
t.Fatalf("load manifest: %v", err)
}
if m.Stages["trim"] == nil || m.Stages["trim"].Status != manifest.StatusSucceeded {
t.Fatalf("trim stage = %#v, want succeeded", m.Stages["trim"])
}
if m.Stages["trim"].Metadata == nil || m.Stages["trim"].Metadata["placeholder"] != true {
t.Fatalf("trim stage metadata = %#v, want placeholder=true", m.Stages["trim"].Metadata)
}
}

View File

@@ -61,7 +61,7 @@ func TestExecuteStagesPlaceholderSuccessUpdatesManifest(t *testing.T) {
t.Fatalf("Load manifest error = %v", err) t.Fatalf("Load manifest error = %v", err)
} }
for _, name := range []string{"prepare", "transcribe", "normalize", "merge", "polish", "analyze", "archive", "notify"} { for _, name := range []string{"prepare", "transcribe", "merge", "polish", "trim", "analyze", "archive", "notify"} {
sr := m.Stages[name] sr := m.Stages[name]
if sr == nil { if sr == nil {
t.Fatalf("missing stage record %q", name) t.Fatalf("missing stage record %q", name)
@@ -238,8 +238,8 @@ func TestExecuteStagesFailureUpdatesManifest(t *testing.T) {
if got := m.Stages["transcribe"]; got == nil || got.Status != manifest.StatusFailed { if got := m.Stages["transcribe"]; got == nil || got.Status != manifest.StatusFailed {
t.Fatalf("transcribe status = %#v, want failed", got) t.Fatalf("transcribe status = %#v, want failed", got)
} }
if got := m.Stages["normalize"]; got != nil { if got := m.Stages["merge"]; got != nil {
t.Fatalf("normalize should not run, got %#v", got) t.Fatalf("merge should not run, got %#v", got)
} }
} }

View File

@@ -51,7 +51,7 @@ func (s *LocalStore) EnsureLayout(sessionID string) (SessionPaths, error) {
paths.AudioDir, paths.AudioDir,
paths.TranscriptsDir, paths.TranscriptsDir,
paths.TranscriptsRawDir, paths.TranscriptsRawDir,
paths.TranscriptsNormalizedDir, paths.TranscriptsTrimmedDir,
paths.ArtifactsDir, paths.ArtifactsDir,
paths.ConfigDir, paths.ConfigDir,
paths.LogsDir, paths.LogsDir,

View File

@@ -20,7 +20,7 @@ func TestEnsureLayoutCreatesExpectedDirectories(t *testing.T) {
checkDirExists(t, paths.AudioDir) checkDirExists(t, paths.AudioDir)
checkDirExists(t, paths.TranscriptsDir) checkDirExists(t, paths.TranscriptsDir)
checkDirExists(t, paths.TranscriptsRawDir) checkDirExists(t, paths.TranscriptsRawDir)
checkDirExists(t, paths.TranscriptsNormalizedDir) checkDirExists(t, paths.TranscriptsTrimmedDir)
checkDirExists(t, paths.ArtifactsDir) checkDirExists(t, paths.ArtifactsDir)
checkDirExists(t, paths.ConfigDir) checkDirExists(t, paths.ConfigDir)
checkDirExists(t, paths.LogsDir) checkDirExists(t, paths.LogsDir)

View File

@@ -10,7 +10,7 @@ type SessionPaths struct {
AudioDir string AudioDir string
TranscriptsDir string TranscriptsDir string
TranscriptsRawDir string TranscriptsRawDir string
TranscriptsNormalizedDir string TranscriptsTrimmedDir string
ArtifactsDir string ArtifactsDir string
ConfigDir string ConfigDir string
LogsDir string LogsDir string
@@ -33,7 +33,7 @@ func buildSessionPaths(workspaceRoot, sessionID string) SessionPaths {
AudioDir: filepath.Join(root, "audio"), AudioDir: filepath.Join(root, "audio"),
TranscriptsDir: transcripts, TranscriptsDir: transcripts,
TranscriptsRawDir: filepath.Join(transcripts, "raw"), TranscriptsRawDir: filepath.Join(transcripts, "raw"),
TranscriptsNormalizedDir: filepath.Join(transcripts, "normalized"), TranscriptsTrimmedDir: filepath.Join(transcripts, "trimmed"),
ArtifactsDir: filepath.Join(root, "artifacts"), ArtifactsDir: filepath.Join(root, "artifacts"),
ConfigDir: filepath.Join(root, "config"), ConfigDir: filepath.Join(root, "config"),
LogsDir: filepath.Join(root, "logs"), LogsDir: filepath.Join(root, "logs"),

View File

@@ -90,9 +90,9 @@ func All() []Stage {
return []Stage{ return []Stage{
prepareStage{}, prepareStage{},
transcribeStage{}, transcribeStage{},
placeholderStage{name: "normalize"},
mergeStage{}, mergeStage{},
polishStage{}, polishStage{},
placeholderStage{name: "trim"},
analyzeStage{}, analyzeStage{},
placeholderStage{name: "archive"}, placeholderStage{name: "archive"},
placeholderStage{name: "notify"}, placeholderStage{name: "notify"},