package app import ( "context" "flag" "fmt" "io" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/config" ) // Plan validates configuration, prepares the local workdir, and prints stage order. func Plan(_ context.Context, args []string, out io.Writer) error { fs := flag.NewFlagSet("plan", flag.ContinueOnError) fs.SetOutput(io.Discard) var pipelinePath string var sessionPath string var force bool fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml") fs.StringVar(&sessionPath, "session", "", "path to session.yml") fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)") if err := fs.Parse(args); err != nil { return fmt.Errorf("plan: invalid flags: %w", err) } if fs.NArg() != 0 { return fmt.Errorf("plan: unexpected positional arguments") } if pipelinePath == "" || sessionPath == "" { return fmt.Errorf("plan: --config and --session are required") } cfg, err := config.Load(pipelinePath, sessionPath) if err != nil { return fmt.Errorf("plan: %w", err) } if err := config.Validate(cfg); err != nil { return fmt.Errorf("plan: %w", err) } store := artifacts.NewLocalStore(cfg.Pipeline.Workspace.Root) paths, err := store.EnsureLayout(cfg.Session.SessionID) if err != nil { return fmt.Errorf("plan: prepare workdir: %w", err) } _ = force // TODO: use --force in future skip/stale planning behavior. stages := BuildFullPlan() if _, err := fmt.Fprintf(out, "narratio plan: workdir prepared at %s\n", paths.Root); err != nil { return err } for _, s := range stages { if _, err := fmt.Fprintln(out, s.Name()); err != nil { return err } } return nil }