package stage import ( "context" "fmt" "path/filepath" "gitea.maximumdirect.net/eric/narratio/internal/adapters/analyzer" "gitea.maximumdirect.net/eric/narratio/internal/adapters/audita" "gitea.maximumdirect.net/eric/narratio/internal/adapters/notify" "gitea.maximumdirect.net/eric/narratio/internal/adapters/seriatim" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "gitea.maximumdirect.net/eric/narratio/internal/adapters/whisperx" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) const placeholderMessage = "placeholder stage: no real work executed" type placeholderStage struct { name string } func (s placeholderStage) Name() string { return s.name } func (s placeholderStage) Declares() IODecl { return IODecl{ Inputs: []artifacts.Ref{{Kind: "artifact", Category: "input", RelativePath: s.name + ".input.placeholder"}}, Outputs: []artifacts.Ref{{Kind: "artifact", Category: "output", RelativePath: s.name + ".output.placeholder"}}, } } func (s placeholderStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*StageResult, error) { result := &StageResult{ Metadata: map[string]any{ "placeholder": true, "stage": s.name, "message": placeholderMessage, }, } if env == nil || env.Config == nil || env.ArtifactStore == nil { return result, nil } sessionID := m.SessionID if sessionID == "" && env.Config.Session != nil { sessionID = env.Config.Session.SessionID } if sessionID == "" { return result, nil } paths := env.ArtifactStore.SessionPaths(sessionID) switch s.name { case "transcribe": if env.WhisperX != nil { req := whisperx.TranscribeRequest{ SpeakerID: "placeholder-speaker", AudioPath: filepath.Join(paths.AudioDir, "placeholder-speaker.flac"), OutputRawTranscriptPath: filepath.Join(paths.TranscriptsRawDir, "placeholder-speaker.json"), } resp, err := env.WhisperX.Transcribe(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder transcribe adapter call failed: %w", err) } result.Outputs = append(result.Outputs, artifacts.Ref{ Kind: "transcript_raw", Category: "transcripts", SessionID: sessionID, AbsolutePath: resp.OutputRawTranscriptPath, }) } case "merge": if env.Seriatim != nil { req := seriatim.MergeRequest{ GeneratedConfigPath: filepath.Join(paths.ConfigDir, "seriatim.generated.yml"), InputTranscriptPaths: []string{filepath.Join(paths.TranscriptsNormalizedDir, "placeholder-speaker.json")}, OutputMergedTranscriptPath: filepath.Join(paths.TranscriptsDir, "merged.json"), StdoutLogPath: filepath.Join(paths.LogsDir, "seriatim.stdout.log"), StderrLogPath: filepath.Join(paths.LogsDir, "seriatim.stderr.log"), } resp, err := env.Seriatim.Run(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder merge adapter call failed: %w", err) } result.Outputs = append(result.Outputs, artifacts.Ref{Kind: "transcript_merged", Category: "transcripts", SessionID: sessionID, AbsolutePath: resp.MergedTranscriptPath}) result.Logs = append(result.Logs, req.StdoutLogPath, req.StderrLogPath) result.GeneratedConfigs = append(result.GeneratedConfigs, req.GeneratedConfigPath) } case "polish": if env.Audita != nil { req := audita.PolishRequest{ GeneratedConfigPath: filepath.Join(paths.ConfigDir, "audita.generated.yml"), MergedTranscriptPath: filepath.Join(paths.TranscriptsDir, "merged.json"), OutputProcessedPath: filepath.Join(paths.TranscriptsDir, "processed.json"), StdoutLogPath: filepath.Join(paths.LogsDir, "audita.stdout.log"), StderrLogPath: filepath.Join(paths.LogsDir, "audita.stderr.log"), } resp, err := env.Audita.Run(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder polish adapter call failed: %w", err) } result.Outputs = append(result.Outputs, artifacts.Ref{Kind: "transcript_processed", Category: "transcripts", SessionID: sessionID, AbsolutePath: resp.ProcessedTranscriptPath}) result.Logs = append(result.Logs, req.StdoutLogPath, req.StderrLogPath) result.GeneratedConfigs = append(result.GeneratedConfigs, req.GeneratedConfigPath) } case "analyze": if env.Analyzer != nil { req := analyzer.AnalyzeRequest{ ArtifactType: "session-log", ProcessedTranscriptPath: filepath.Join(paths.TranscriptsDir, "processed.json"), ContextReferences: []string{"previous-session"}, OutputPath: filepath.Join(paths.ArtifactsDir, "session-log.md"), GeneratedConfigPath: filepath.Join(paths.ConfigDir, "analyzer.session-log.generated.yml"), StdoutLogPath: filepath.Join(paths.LogsDir, "analyzer.session-log.stdout.log"), StderrLogPath: filepath.Join(paths.LogsDir, "analyzer.session-log.stderr.log"), } resp, err := env.Analyzer.Run(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder analyze adapter call failed: %w", err) } result.Outputs = append(result.Outputs, artifacts.Ref{Kind: "artifact", Category: "artifacts", SessionID: sessionID, AbsolutePath: resp.ArtifactPath}) result.Logs = append(result.Logs, req.StdoutLogPath, req.StderrLogPath) result.GeneratedConfigs = append(result.GeneratedConfigs, req.GeneratedConfigPath) } case "archive": if env.Storage != nil { req := storage.ArchiveRequest{ SessionID: sessionID, ManifestPath: paths.ManifestPath, Items: []storage.ArchiveItem{{ Kind: "artifact", LocalPath: filepath.Join(paths.ArtifactsDir, "session-log.md"), RemoteKey: "sessions/" + sessionID + "/artifacts/session-log.md", }}, } _, err := env.Storage.Archive(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder archive adapter call failed: %w", err) } } case "notify": if env.Notifier != nil { req := notify.SendRequest{ Subject: "narratio placeholder run complete", Body: "placeholder stage execution finished", Metadata: map[string]string{ "session_id": sessionID, }, } _, err := env.Notifier.Send(ctx, req) if err != nil { return nil, fmt.Errorf("placeholder notify adapter call failed: %w", err) } } } return result, nil } // All returns the canonical ordered stage list for full pipeline execution. func All() []Stage { return []Stage{ prepareStage{}, placeholderStage{name: "transcribe"}, placeholderStage{name: "normalize"}, placeholderStage{name: "merge"}, placeholderStage{name: "polish"}, placeholderStage{name: "analyze"}, placeholderStage{name: "archive"}, placeholderStage{name: "notify"}, } } // Select returns one stage by exact name from the canonical stage set. func Select(name string) (Stage, error) { for _, s := range All() { if s.Name() == name { return s, nil } } return nil, fmt.Errorf("unknown stage %q", name) }