package stage import ( "context" "fmt" "path/filepath" "gitea.maximumdirect.net/eric/narratio/internal/adapters/analyzer" "gitea.maximumdirect.net/eric/narratio/internal/adapters/notify" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "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 "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{}, transcribeStage{}, placeholderStage{name: "normalize"}, mergeStage{}, polishStage{}, 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) }