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:
- real `normalize` behavior
- real `trim` behavior
- real `archive` behavior
- real `notify` behavior
- additional analyze artifacts beyond `session_recap`

View File

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

View File

@@ -32,7 +32,7 @@ func TestExecuteValidCommands(t *testing.T) {
wantOut string
}{
{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: "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="},
@@ -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) {
workspaceRoot := t.TempDir()
var serverCalls int

View File

@@ -27,7 +27,7 @@ func TestPlanCreatesAndReusesWorkdir(t *testing.T) {
if !strings.Contains(got, "narratio plan: workdir prepared at") {
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") {
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, "audio"),
filepath.Join(sessionWorkdir, "transcripts", "raw"),
filepath.Join(sessionWorkdir, "transcripts", "normalized"),
filepath.Join(sessionWorkdir, "transcripts", "trimmed"),
filepath.Join(sessionWorkdir, "artifacts"),
filepath.Join(sessionWorkdir, "config"),
filepath.Join(sessionWorkdir, "logs"),
@@ -81,8 +81,8 @@ func TestPlanShowsRunAndSkipFromManifest(t *testing.T) {
if !strings.Contains(got, "prepare: skip") || !strings.Contains(got, "transcribe: skip") {
t.Fatalf("output = %q, want prepare/transcribe skipped", got)
}
if !strings.Contains(got, "normalize: run") {
t.Fatalf("output = %q, want normalize run", got)
if !strings.Contains(got, "trim: run") {
t.Fatalf("output = %q, want trim run", got)
}
if !strings.Contains(got, "totals: run=6 skip=2") {
t.Fatalf("output = %q, want totals run=6 skip=2", got)

View File

@@ -4,7 +4,7 @@ import "testing"
func TestBuildFullPlanOrder(t *testing.T) {
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) {
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 {
t.Fatalf("load manifest: %v", err)
}
if loaded.Stages["normalize"] == nil || loaded.Stages["normalize"].Status != manifest.StatusSucceeded {
t.Fatalf("normalize stage = %#v, want succeeded", loaded.Stages["normalize"])
if loaded.Stages["trim"] == nil || loaded.Stages["trim"].Status != manifest.StatusSucceeded {
t.Fatalf("trim stage = %#v, want succeeded", loaded.Stages["trim"])
}
}
@@ -56,7 +56,7 @@ func TestResumeNoRemainingStages(t *testing.T) {
store := &manifest.LocalStore{}
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)
}
if err := store.Save(context.Background(), manifestPath, m); err != nil {
@@ -85,7 +85,7 @@ func TestResumeForceRerunsSucceeded(t *testing.T) {
store := &manifest.LocalStore{}
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)
}
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())
}
}
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)
}
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]
if sr == nil {
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 {
t.Fatalf("transcribe status = %#v, want failed", got)
}
if got := m.Stages["normalize"]; got != nil {
t.Fatalf("normalize should not run, got %#v", got)
if got := m.Stages["merge"]; got != nil {
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.TranscriptsDir,
paths.TranscriptsRawDir,
paths.TranscriptsNormalizedDir,
paths.TranscriptsTrimmedDir,
paths.ArtifactsDir,
paths.ConfigDir,
paths.LogsDir,

View File

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

View File

@@ -4,18 +4,18 @@ import "path/filepath"
// SessionPaths contains canonical local paths for one session work directory.
type SessionPaths struct {
WorkspaceRoot string
Root string
InputsDir string
AudioDir string
TranscriptsDir string
TranscriptsRawDir string
TranscriptsNormalizedDir string
ArtifactsDir string
ConfigDir string
LogsDir string
ManifestPath string
LockPath string
WorkspaceRoot string
Root string
InputsDir string
AudioDir string
TranscriptsDir string
TranscriptsRawDir string
TranscriptsTrimmedDir string
ArtifactsDir string
ConfigDir string
LogsDir string
ManifestPath string
LockPath string
}
// SessionWorkDir returns the work directory for one session.
@@ -27,17 +27,17 @@ func buildSessionPaths(workspaceRoot, sessionID string) SessionPaths {
root := SessionWorkDir(workspaceRoot, sessionID)
transcripts := filepath.Join(root, "transcripts")
return SessionPaths{
WorkspaceRoot: workspaceRoot,
Root: root,
InputsDir: filepath.Join(root, "inputs"),
AudioDir: filepath.Join(root, "audio"),
TranscriptsDir: transcripts,
TranscriptsRawDir: filepath.Join(transcripts, "raw"),
TranscriptsNormalizedDir: filepath.Join(transcripts, "normalized"),
ArtifactsDir: filepath.Join(root, "artifacts"),
ConfigDir: filepath.Join(root, "config"),
LogsDir: filepath.Join(root, "logs"),
ManifestPath: filepath.Join(root, "manifest.json"),
LockPath: filepath.Join(root, ".lock"),
WorkspaceRoot: workspaceRoot,
Root: root,
InputsDir: filepath.Join(root, "inputs"),
AudioDir: filepath.Join(root, "audio"),
TranscriptsDir: transcripts,
TranscriptsRawDir: filepath.Join(transcripts, "raw"),
TranscriptsTrimmedDir: filepath.Join(transcripts, "trimmed"),
ArtifactsDir: filepath.Join(root, "artifacts"),
ConfigDir: filepath.Join(root, "config"),
LogsDir: filepath.Join(root, "logs"),
ManifestPath: filepath.Join(root, "manifest.json"),
LockPath: filepath.Join(root, ".lock"),
}
}

View File

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