From 6c9922eeb7c8ede8b5487de3177e3581936a03bc Mon Sep 17 00:00:00 2001 From: Bauer Agent Date: Wed, 20 May 2026 13:50:08 +0300 Subject: [PATCH 1/3] feat(figma-phase-d): thread figma through CLI and orchestrator, MCP guidance in prompts T2F.8: cmd/bauer --figma-url flag wired through CLIFlags and Config T2F.8: orchestrator.Execute forks on cfg.FigmaURL for figma-aware prompt generation T2F.8: generateChunksWithFigma fetches design, runs mapping.Resolver, writes artifacts T2F.9: figmaChunkContext.FigmaURL field enables MCP guidance block in template T2F.9: figma-context.md optional MCP section renders when FigmaURL is set T2F.9: Engine.RenderChunksFromResolved writes figma-aware prompt files to disk --- cmd/bauer/main.go | 9 +- docs/implementation-log.md | 8 +- internal/orchestrator/orchestrator.go | 95 ++++++++++++++++++++-- internal/prompt/engine.go | 43 ++++++++++ internal/prompt/templates/figma-context.md | 17 ++++ 5 files changed, 158 insertions(+), 14 deletions(-) diff --git a/cmd/bauer/main.go b/cmd/bauer/main.go index 46d83fa..58fac22 100644 --- a/cmd/bauer/main.go +++ b/cmd/bauer/main.go @@ -31,10 +31,7 @@ func main() { openIssue := fs.Bool("open-issue", false, "Generate a plan and open a GitHub issue without applying changes (mutually exclusive with --open-pr)") branchPrefix := fs.String("branch-prefix", "", "Prefix for created branches (default: bauer)") githubRepo := fs.String("github-repo", "", "GitHub repository in owner/repo format (required for --open-pr and --open-issue)") - - fs.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage:\n\n") - fmt.Fprintf(os.Stderr, "\t%s --doc-id [--credentials ] [flags]\n\n", os.Args[0]) + figmaURL := fs.String("figma-url", "", "Figma file or design URL for design reference (requires BAUER_FIGMA_TOKEN)") fmt.Fprintf(os.Stderr, "Flags:\n\n") fs.PrintDefaults() fmt.Fprintf(os.Stderr, "\nEnvironment variables:\n\n") @@ -47,8 +44,7 @@ func main() { fmt.Fprintf(os.Stderr, "\tBAUER_ARTIFACTS_DIR Override for --artifacts-dir\n") fmt.Fprintf(os.Stderr, "\tBAUER_BRANCH_PREFIX Override for --branch-prefix\n") fmt.Fprintf(os.Stderr, "\tBAUER_GITHUB_REPO Override for --github-repo\n") - fmt.Fprintf(os.Stderr, "\n") - } + fmt.Fprintf(os.Stderr, "\tBAUER_FIGMA_TOKEN Figma API token (required when --figma-url is supplied)\n") if err := fs.Parse(os.Args[1:]); err != nil { os.Exit(1) @@ -71,6 +67,7 @@ func main() { ArtifactsDir: *artifactsDir, BranchPrefix: *branchPrefix, GitHubRepo: *githubRepo, + FigmaURL: *figmaURL, } fs.Visit(func(f *flag.Flag) { switch f.Name { diff --git a/docs/implementation-log.md b/docs/implementation-log.md index 264cd43..f4a4220 100644 --- a/docs/implementation-log.md +++ b/docs/implementation-log.md @@ -237,9 +237,13 @@ _Parent: `feat/figma-phase-c-mapping`_ **Tasks:** T2F.8, T2F.9 -**Summary:** _(to be filled by agent)_ +**Summary:** Threaded Figma through the CLI and orchestrator (T2F.8) and added an optional MCP guidance block to prompts (T2F.9). Added `--figma-url` flag to `cmd/bauer/main.go`, wired into `CLIFlags.FigmaURL`. `orchestrator.Execute` now forks on `cfg.FigmaURL != ""`: the figma-aware path calls the new `generateChunksWithFigma()` method which fetches design data via `sources.FetchFigma`, runs `mapping.Resolver.Build`, persists figma artifacts (extraction, comments, mappings), and generates prompts via `engine.RenderChunksFromResolved`. For T2F.9: added `FigmaURL string` field (with `json:"-"`) to `figmaChunkContext` in `engine.go`; `RenderChunk` now sets `ctx.FigmaURL = data.FigmaURL` before template execution; added an optional MCP guidance block to `internal/prompt/templates/figma-context.md` that renders only when `{{if .FigmaURL}}`; added `Engine.RenderChunksFromResolved()` which generates figma-aware prompt files using `GenerateChunksFromResolved` + `RenderChunk`. `BAUER_FIGMA_TOKEN` env var usage mentioned in `--help` output. -**Files changed:** _(to be filled by agent)_ +**Files changed:** +- `cmd/bauer/main.go` — added `--figma-url` flag; `CLIFlags.FigmaURL` wired through `FlagsSource`; `BAUER_FIGMA_TOKEN` env var note in help text +- `internal/orchestrator/orchestrator.go` — `Execute` forks on `cfg.FigmaURL != ""`; new `generateChunksWithFigma()` method: calls `figma.ParseLink`, `figma.NewClient`, `sources.FetchFigma`, `mapping.Resolver.Build`, `arts.WriteFigmaExtraction`/`WriteFigmaComments`/`WriteMappings`, `engine.RenderChunksFromResolved`; log line uses `design.Anchors` (not `.Nodes`) +- `internal/prompt/engine.go` — `figmaChunkContext` gains `FigmaURL string` (json:"-"); `RenderChunk` sets `ctx.FigmaURL = data.FigmaURL`; new `RenderChunksFromResolved()` method that calls `GenerateChunksFromResolved` + `RenderChunk` and writes prompt files to disk +- `internal/prompt/templates/figma-context.md` — optional `{{if .FigmaURL}}` MCP guidance block added at end of template --- diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 58c163a..726f2d6 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -4,8 +4,10 @@ import ( "bauer/internal/agent" "bauer/internal/artifacts" "bauer/internal/config" + "bauer/internal/figma" "bauer/internal/prompt" "bauer/internal/source" + "bauer/internal/source/mapping" "context" "fmt" "log/slog" @@ -137,11 +139,18 @@ func (o *DefaultOrchestrator) Execute(ctx context.Context, cfg *config.Config) ( slog.Int("total_locations", totalLocations), slog.Int("chunk_size", cfg.ChunkSize), ) - chunks, err := engine.GenerateAllChunks( - bundle.Document, - cfg.ChunkSize, - cfg.OutputDir, - ) + + // When a Figma URL is configured, fetch design data and use figma-aware prompt generation. + var chunks []prompt.ChunkResult + if cfg.FigmaURL != "" { + chunks, err = o.generateChunksWithFigma(ctx, cfg, bundle, engine, runID) + } else { + chunks, err = engine.GenerateAllChunks( + bundle.Document, + cfg.ChunkSize, + cfg.OutputDir, + ) + } if err != nil { slog.Error("Failed to generate prompts", slog.String("error", err.Error())) completeRun("failed", 0) @@ -273,8 +282,82 @@ func (o *DefaultOrchestrator) Execute(ctx context.Context, cfg *config.Config) ( } // executeAgentChunks executes each chunk via the agent and returns outputs. -func executeAgentChunks( +// generateChunksWithFigma fetches Figma design data and produces figma-aware prompt files. +// It is called by Execute when cfg.FigmaURL is non-empty. +func (o *DefaultOrchestrator) generateChunksWithFigma( ctx context.Context, + cfg *config.Config, + bundle *source.SourceBundle, + engine *prompt.Engine, + runID string, +) ([]prompt.ChunkResult, error) { + figmaRef, err := figma.ParseLink(cfg.FigmaURL) + if err != nil { + return nil, fmt.Errorf("invalid figma URL %q: %w", cfg.FigmaURL, err) + } + + figmaClient := figma.NewClient(cfg.FigmaToken) + + // Determine screenshot directory (inside the artifact run when available). + screenshotDir := "" + if runID != "" { + screenshotDir, err = o.arts.EnsureScreenshotsDir(runID) + if err != nil { + slog.Warn("Failed to create screenshots dir, proceeding without screenshots", + slog.String("error", err.Error())) + screenshotDir = "" + } + } + + slog.Info("Fetching Figma design data", slog.String("figma_url", cfg.FigmaURL)) + design, err := o.sources.FetchFigma(ctx, figmaClient, figmaRef, screenshotDir) + if err != nil { + return nil, fmt.Errorf("fetching figma design: %w", err) + } + slog.Info("Figma design fetched", + slog.Int("anchors", len(design.Anchors)), + slog.Int("comments", len(design.Comments)), + ) + + // Persist figma artifacts. + if runID != "" { + if werr := o.arts.WriteFigmaExtraction(runID, design); werr != nil { + slog.Warn("Failed to write figma extraction artifact", slog.String("error", werr.Error())) + } + if werr := o.arts.WriteFigmaComments(runID, design.Comments); werr != nil { + slog.Warn("Failed to write figma comments artifact", slog.String("error", werr.Error())) + } + } + + // Build resolved chunks (design-aware mapping). + resolver := &mapping.Resolver{} + resolvedChunks := resolver.Build(bundle.Document.GroupedSuggestions, design) + slog.Info("Mapping resolved", slog.Int("resolved_chunks", len(resolvedChunks))) + + if runID != "" { + if werr := o.arts.WriteMappings(runID, resolvedChunks); werr != nil { + slog.Warn("Failed to write mappings artifact", slog.String("error", werr.Error())) + } + } + + // Generate figma-aware prompt files. + suggestedURL := "" + if bundle.Document.Metadata != nil { + suggestedURL = bundle.Document.Metadata.SuggestedUrl + } + + return engine.RenderChunksFromResolved( + bundle.Document.DocumentTitle, + suggestedURL, + cfg.FigmaURL, + resolvedChunks, + cfg.ChunkSize, + cfg.OutputDir, + ) +} + +// executeAgentChunks executes each chunk via the agent and returns outputs. +func executeAgentChunks( ctx context.Context, chunks []prompt.ChunkResult, cfg *config.Config, a agent.Agent, diff --git a/internal/prompt/engine.go b/internal/prompt/engine.go index 45e02f6..53dba84 100644 --- a/internal/prompt/engine.go +++ b/internal/prompt/engine.go @@ -147,6 +147,7 @@ func (e *Engine) RenderChunk(data PromptData) (string, error) { if err := json.Unmarshal([]byte(data.FigmaContextJSON), &ctx); err != nil { return "", fmt.Errorf("parsing figma context JSON: %w", err) } + ctx.FigmaURL = data.FigmaURL tmpl, err := template.New("figma-context").Parse(figmaContextTemplate) if err != nil { return "", fmt.Errorf("parsing figma context template: %w", err) @@ -230,10 +231,13 @@ func (e *Engine) GenerateAllChunks( } // figmaChunkContext is the data structure serialized into FigmaContextJSON. +// FigmaURL is NOT serialized; it is set at render time from PromptData.FigmaURL +// to power the optional MCP guidance block in the figma-context template. type figmaChunkContext struct { Anchors []mapping.DesignAnchorRef `json:"anchors,omitempty"` Screenshots []string `json:"screenshots,omitempty"` Comments []mapping.DesignCommentRef `json:"comments,omitempty"` + FigmaURL string `json:"-"` } // GenerateChunksFromResolved creates one PromptData per batch of resolved chunks. @@ -338,3 +342,42 @@ func indexOf(s, substr string) int { } return -1 } +// RenderChunksFromResolved generates figma-aware prompt files from pre-resolved chunks. +// It is used when --figma-url is supplied so that Figma design context is embedded in +// each prompt. outputDir is created if it does not exist. +// The returned ChunkResults contain Filenames suitable for agent execution. +func (e *Engine) RenderChunksFromResolved( + docTitle, suggestedURL, figmaURL string, + chunks []mapping.ResolvedChunk, + chunkSize int, + outputDir string, +) ([]ChunkResult, error) { + if err := os.MkdirAll(outputDir, 0755); err != nil { + return nil, fmt.Errorf("creating output directory %q: %w", outputDir, err) + } + + promptDatas, err := e.GenerateChunksFromResolved(docTitle, suggestedURL, figmaURL, chunks, chunkSize) + if err != nil { + return nil, err + } + + results := make([]ChunkResult, len(promptDatas)) + for i, pd := range promptDatas { + content, err := e.RenderChunk(pd) + if err != nil { + return nil, fmt.Errorf("rendering chunk %d: %w", i+1, err) + } + fname := fmt.Sprintf("chunk-%d-of-%d.md", pd.ChunkNumber, pd.TotalChunks) + fpath := filepath.Join(outputDir, fname) + if err := os.WriteFile(fpath, []byte(content), 0644); err != nil { + return nil, fmt.Errorf("writing chunk %d to file: %w", i+1, err) + } + results[i] = ChunkResult{ + ChunkNumber: pd.ChunkNumber, + Content: content, + Filename: fpath, + LocationCount: pd.LocationCount, + } + } + return results, nil +} \ No newline at end of file diff --git a/internal/prompt/templates/figma-context.md b/internal/prompt/templates/figma-context.md index 737dea7..1175efb 100644 --- a/internal/prompt/templates/figma-context.md +++ b/internal/prompt/templates/figma-context.md @@ -30,3 +30,20 @@ The Google Doc is the canonical intent source. Designer comments are requirement - Verify your implementation matches the visual design for the suggestion locations in this chunk. - Do not invent new UI components if the design shows an existing one. - If the design shows a spacing or typography token, check whether an equivalent exists in the codebase. +{{if .FigmaURL}} +### If you have access to Figma MCP tools (optional) + +If your runtime supports the Figma MCP server (e.g. VS Code Copilot Chat, Cursor, or Claude Code), +you may fetch live data directly from the design file to supplement the stored context above: + +`{{.FigmaURL}}` + +If you choose to use MCP tools: +- Treat Bauer's stored artifacts (screenshots, design node references, and designer comments above) + as the **ground truth** for this run. +- If the live MCP view conflicts with stored artifacts, **surface the conflict explicitly** rather + than silently preferring one over the other. +- Do not rely on MCP tools alone — the stored artifacts are the authoritative reference. + +If you do not have access to Figma MCP tools, ignore this section entirely. +{{end}} From 2a47d958ea1c4c1c049b2aa86e4df2262435d9d9 Mon Sep 17 00:00:00 2001 From: Bauer Agent Date: Tue, 26 May 2026 11:19:45 +0300 Subject: [PATCH 2/3] fix: address PR review comments (usage func, screenshot guard, formatting) --- cmd/app/main.go | 1 - cmd/app/v1/api.go | 3 +- cmd/bauer/main.go | 8 ++- internal/config/manager.go | 74 +++++++++++++++++++-------- internal/github/pr.go | 4 +- internal/orchestrator/orchestrator.go | 7 ++- internal/prompt/engine.go | 67 ++++++++++++------------ internal/source/mapping/types.go | 6 +-- 8 files changed, 104 insertions(+), 66 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index b8cf8bd..003f169 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -71,4 +71,3 @@ func main() { os.Exit(1) } } - diff --git a/cmd/app/v1/api.go b/cmd/app/v1/api.go index 6f1f725..cd8a1e2 100644 --- a/cmd/app/v1/api.go +++ b/cmd/app/v1/api.go @@ -83,7 +83,6 @@ func executeJob(requestID string, cfg config.Config, rc types.RouteConfig) { ) } - func GetHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -91,4 +90,4 @@ func GetHealth(w http.ResponseWriter, r *http.Request) { if err != nil { slog.Error("error writing response", "error", err.Error()) } -} \ No newline at end of file +} diff --git a/cmd/bauer/main.go b/cmd/bauer/main.go index 58fac22..a9f117e 100644 --- a/cmd/bauer/main.go +++ b/cmd/bauer/main.go @@ -31,7 +31,10 @@ func main() { openIssue := fs.Bool("open-issue", false, "Generate a plan and open a GitHub issue without applying changes (mutually exclusive with --open-pr)") branchPrefix := fs.String("branch-prefix", "", "Prefix for created branches (default: bauer)") githubRepo := fs.String("github-repo", "", "GitHub repository in owner/repo format (required for --open-pr and --open-issue)") - figmaURL := fs.String("figma-url", "", "Figma file or design URL for design reference (requires BAUER_FIGMA_TOKEN)") + figmaURL := fs.String("figma-url", "", "Figma file or design URL for design reference (requires BAUER_FIGMA_TOKEN)") + + fs.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [flags]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Flags:\n\n") fs.PrintDefaults() fmt.Fprintf(os.Stderr, "\nEnvironment variables:\n\n") @@ -44,7 +47,8 @@ func main() { fmt.Fprintf(os.Stderr, "\tBAUER_ARTIFACTS_DIR Override for --artifacts-dir\n") fmt.Fprintf(os.Stderr, "\tBAUER_BRANCH_PREFIX Override for --branch-prefix\n") fmt.Fprintf(os.Stderr, "\tBAUER_GITHUB_REPO Override for --github-repo\n") - fmt.Fprintf(os.Stderr, "\tBAUER_FIGMA_TOKEN Figma API token (required when --figma-url is supplied)\n") + fmt.Fprintf(os.Stderr, "\tBAUER_FIGMA_TOKEN Figma API token (required when --figma-url is supplied)\n") + } if err := fs.Parse(os.Args[1:]); err != nil { os.Exit(1) diff --git a/internal/config/manager.go b/internal/config/manager.go index e101922..2961c0e 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -38,22 +38,54 @@ func (r *Resolver) Resolve() (*Config, error) { } func mergeConfig(base, override *Config) { - if override.DocID != "" { base.DocID = override.DocID } - if override.CredentialsPath != "" { base.CredentialsPath = override.CredentialsPath } - if override.Model != "" { base.Model = override.Model } - if override.SummaryModel != "" { base.SummaryModel = override.SummaryModel } - if override.ArtifactsDir != "" { base.ArtifactsDir = override.ArtifactsDir } - if override.BranchPrefix != "" { base.BranchPrefix = override.BranchPrefix } - if override.ChunkSize != 0 { base.ChunkSize = override.ChunkSize } - if override.GitHubRepo != "" { base.GitHubRepo = override.GitHubRepo } - if override.FigmaURL != "" { base.FigmaURL = override.FigmaURL } - if override.FigmaToken != "" { base.FigmaToken = override.FigmaToken } - if override.OutputDir != "" { base.OutputDir = override.OutputDir } - if override.TargetRepo != "" { base.TargetRepo = override.TargetRepo } - if override.PageRefresh != nil { base.PageRefresh = override.PageRefresh } - if override.DryRun != nil { base.DryRun = override.DryRun } - if override.OpenPR != nil { base.OpenPR = override.OpenPR } - if override.OpenIssue != nil { base.OpenIssue = override.OpenIssue } + if override.DocID != "" { + base.DocID = override.DocID + } + if override.CredentialsPath != "" { + base.CredentialsPath = override.CredentialsPath + } + if override.Model != "" { + base.Model = override.Model + } + if override.SummaryModel != "" { + base.SummaryModel = override.SummaryModel + } + if override.ArtifactsDir != "" { + base.ArtifactsDir = override.ArtifactsDir + } + if override.BranchPrefix != "" { + base.BranchPrefix = override.BranchPrefix + } + if override.ChunkSize != 0 { + base.ChunkSize = override.ChunkSize + } + if override.GitHubRepo != "" { + base.GitHubRepo = override.GitHubRepo + } + if override.FigmaURL != "" { + base.FigmaURL = override.FigmaURL + } + if override.FigmaToken != "" { + base.FigmaToken = override.FigmaToken + } + if override.OutputDir != "" { + base.OutputDir = override.OutputDir + } + if override.TargetRepo != "" { + base.TargetRepo = override.TargetRepo + } + if override.PageRefresh != nil { + base.PageRefresh = override.PageRefresh + } + if override.DryRun != nil { + base.DryRun = override.DryRun + } + if override.OpenPR != nil { + base.OpenPR = override.OpenPR + } + if override.OpenIssue != nil { + base.OpenIssue = override.OpenIssue + } } // EnvVarSource reads BAUER_* env vars. @@ -69,14 +101,14 @@ func (e *EnvVarSource) Load() (*Config, error) { } else if v := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); v != "" { cfg.CredentialsPath = v } - cfg.DocID = os.Getenv("BAUER_DOC_ID") - cfg.Model = os.Getenv("BAUER_MODEL") + cfg.DocID = os.Getenv("BAUER_DOC_ID") + cfg.Model = os.Getenv("BAUER_MODEL") cfg.SummaryModel = os.Getenv("BAUER_SUMMARY_MODEL") cfg.ArtifactsDir = os.Getenv("BAUER_ARTIFACTS_DIR") cfg.BranchPrefix = os.Getenv("BAUER_BRANCH_PREFIX") - cfg.GitHubRepo = os.Getenv("BAUER_GITHUB_REPO") - cfg.FigmaURL = os.Getenv("BAUER_FIGMA_URL") - cfg.FigmaToken = os.Getenv("BAUER_FIGMA_TOKEN") + cfg.GitHubRepo = os.Getenv("BAUER_GITHUB_REPO") + cfg.FigmaURL = os.Getenv("BAUER_FIGMA_URL") + cfg.FigmaToken = os.Getenv("BAUER_FIGMA_TOKEN") if cfg.FigmaToken == "" { cfg.FigmaToken = os.Getenv("FIGMA_TOKEN") } diff --git a/internal/github/pr.go b/internal/github/pr.go index e5813a8..5e5fcff 100644 --- a/internal/github/pr.go +++ b/internal/github/pr.go @@ -65,7 +65,7 @@ func CreatePR(owner, repo string, opts CreatePROptions) (string, error) { } cmd := exec.Command("gh", args...) - + // Log token availability for debugging logger := slog.Default() ghToken := os.Getenv("GH_TOKEN") @@ -77,7 +77,7 @@ func CreatePR(owner, repo string, opts CreatePROptions) (string, error) { } else { logger.Debug("GH_TOKEN is set for PR creation", "token_prefix", ghToken[:10]) } - + output, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("failed to create PR: %w, output: %s", err, output) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 726f2d6..0679c85 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -281,7 +281,6 @@ func (o *DefaultOrchestrator) Execute(ctx context.Context, cfg *config.Config) ( }, nil } -// executeAgentChunks executes each chunk via the agent and returns outputs. // generateChunksWithFigma fetches Figma design data and produces figma-aware prompt files. // It is called by Execute when cfg.FigmaURL is non-empty. func (o *DefaultOrchestrator) generateChunksWithFigma( @@ -309,6 +308,10 @@ func (o *DefaultOrchestrator) generateChunksWithFigma( } } + if screenshotDir == "" { + slog.Warn("Screenshot directory unavailable, skipping screenshot download") + } + slog.Info("Fetching Figma design data", slog.String("figma_url", cfg.FigmaURL)) design, err := o.sources.FetchFigma(ctx, figmaClient, figmaRef, screenshotDir) if err != nil { @@ -357,7 +360,7 @@ func (o *DefaultOrchestrator) generateChunksWithFigma( } // executeAgentChunks executes each chunk via the agent and returns outputs. -func executeAgentChunks( ctx context.Context, +func executeAgentChunks(ctx context.Context, chunks []prompt.ChunkResult, cfg *config.Config, a agent.Agent, diff --git a/internal/prompt/engine.go b/internal/prompt/engine.go index 53dba84..2411246 100644 --- a/internal/prompt/engine.go +++ b/internal/prompt/engine.go @@ -342,42 +342,43 @@ func indexOf(s, substr string) int { } return -1 } + // RenderChunksFromResolved generates figma-aware prompt files from pre-resolved chunks. // It is used when --figma-url is supplied so that Figma design context is embedded in // each prompt. outputDir is created if it does not exist. // The returned ChunkResults contain Filenames suitable for agent execution. func (e *Engine) RenderChunksFromResolved( - docTitle, suggestedURL, figmaURL string, - chunks []mapping.ResolvedChunk, - chunkSize int, - outputDir string, + docTitle, suggestedURL, figmaURL string, + chunks []mapping.ResolvedChunk, + chunkSize int, + outputDir string, ) ([]ChunkResult, error) { - if err := os.MkdirAll(outputDir, 0755); err != nil { - return nil, fmt.Errorf("creating output directory %q: %w", outputDir, err) - } - - promptDatas, err := e.GenerateChunksFromResolved(docTitle, suggestedURL, figmaURL, chunks, chunkSize) - if err != nil { - return nil, err - } - - results := make([]ChunkResult, len(promptDatas)) - for i, pd := range promptDatas { - content, err := e.RenderChunk(pd) - if err != nil { - return nil, fmt.Errorf("rendering chunk %d: %w", i+1, err) - } - fname := fmt.Sprintf("chunk-%d-of-%d.md", pd.ChunkNumber, pd.TotalChunks) - fpath := filepath.Join(outputDir, fname) - if err := os.WriteFile(fpath, []byte(content), 0644); err != nil { - return nil, fmt.Errorf("writing chunk %d to file: %w", i+1, err) - } - results[i] = ChunkResult{ - ChunkNumber: pd.ChunkNumber, - Content: content, - Filename: fpath, - LocationCount: pd.LocationCount, - } - } - return results, nil -} \ No newline at end of file + if err := os.MkdirAll(outputDir, 0755); err != nil { + return nil, fmt.Errorf("creating output directory %q: %w", outputDir, err) + } + + promptDatas, err := e.GenerateChunksFromResolved(docTitle, suggestedURL, figmaURL, chunks, chunkSize) + if err != nil { + return nil, err + } + + results := make([]ChunkResult, len(promptDatas)) + for i, pd := range promptDatas { + content, err := e.RenderChunk(pd) + if err != nil { + return nil, fmt.Errorf("rendering chunk %d: %w", i+1, err) + } + fname := fmt.Sprintf("chunk-%d-of-%d.md", pd.ChunkNumber, pd.TotalChunks) + fpath := filepath.Join(outputDir, fname) + if err := os.WriteFile(fpath, []byte(content), 0644); err != nil { + return nil, fmt.Errorf("writing chunk %d to file: %w", i+1, err) + } + results[i] = ChunkResult{ + ChunkNumber: pd.ChunkNumber, + Content: content, + Filename: fpath, + LocationCount: pd.LocationCount, + } + } + return results, nil +} diff --git a/internal/source/mapping/types.go b/internal/source/mapping/types.go index c378e97..a135d38 100644 --- a/internal/source/mapping/types.go +++ b/internal/source/mapping/types.go @@ -8,9 +8,9 @@ import "bauer/internal/gdocs" type ResolvedChunk struct { Locations []gdocs.LocationGroupedSuggestions `json:"locations"` DesignAnchors []DesignAnchorRef `json:"design_anchors,omitempty"` - ScreenshotPaths []string `json:"screenshot_paths,omitempty"` - Comments []DesignCommentRef `json:"comments,omitempty"` - Mapping MappingMetadata `json:"mapping"` + ScreenshotPaths []string `json:"screenshot_paths,omitempty"` + Comments []DesignCommentRef `json:"comments,omitempty"` + Mapping MappingMetadata `json:"mapping"` } // DesignAnchorRef is a lightweight reference to a matched Figma node. From d3b305699e0117475660c313b835eb6281dd01ec Mon Sep 17 00:00:00 2001 From: Bauer Agent Date: Tue, 26 May 2026 12:10:34 +0300 Subject: [PATCH 3/3] fix: address second-round PR review comments --- cmd/bauer/main.go | 4 +++- internal/source/manager.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/bauer/main.go b/cmd/bauer/main.go index a9f117e..c03aeb1 100644 --- a/cmd/bauer/main.go +++ b/cmd/bauer/main.go @@ -31,7 +31,7 @@ func main() { openIssue := fs.Bool("open-issue", false, "Generate a plan and open a GitHub issue without applying changes (mutually exclusive with --open-pr)") branchPrefix := fs.String("branch-prefix", "", "Prefix for created branches (default: bauer)") githubRepo := fs.String("github-repo", "", "GitHub repository in owner/repo format (required for --open-pr and --open-issue)") - figmaURL := fs.String("figma-url", "", "Figma file or design URL for design reference (requires BAUER_FIGMA_TOKEN)") + figmaURL := fs.String("figma-url", "", "Figma file or design URL for design reference\n\t(requires BAUER_FIGMA_TOKEN or FIGMA_TOKEN)") fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [flags]\n\n", os.Args[0]) @@ -47,7 +47,9 @@ func main() { fmt.Fprintf(os.Stderr, "\tBAUER_ARTIFACTS_DIR Override for --artifacts-dir\n") fmt.Fprintf(os.Stderr, "\tBAUER_BRANCH_PREFIX Override for --branch-prefix\n") fmt.Fprintf(os.Stderr, "\tBAUER_GITHUB_REPO Override for --github-repo\n") + fmt.Fprintf(os.Stderr, "\tBAUER_FIGMA_URL Override for --figma-url\n") fmt.Fprintf(os.Stderr, "\tBAUER_FIGMA_TOKEN Figma API token (required when --figma-url is supplied)\n") + fmt.Fprintf(os.Stderr, "\tFIGMA_TOKEN Fallback for BAUER_FIGMA_TOKEN\n") } if err := fs.Parse(os.Args[1:]); err != nil { diff --git a/internal/source/manager.go b/internal/source/manager.go index 5e9a005..d255275 100644 --- a/internal/source/manager.go +++ b/internal/source/manager.go @@ -64,7 +64,7 @@ func (m *Manager) FetchFigma(ctx context.Context, client *figma.Client, ref *fig // Request screenshots for the specified node(s) screenshotPaths := map[string]string{} - if len(nodeIDs) > 0 { + if len(nodeIDs) > 0 && screenshotDir != "" { imageURLs, err := client.GetImages(ctx, ref.FileKey, nodeIDs) if err != nil { // Non-fatal: log and continue without screenshots