feat(figma): thread Figma through CLI and orchestrator (Phase D — T2F.8, T2F.9)#42
Conversation
…uidance 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
There was a problem hiding this comment.
Pull request overview
Threads the optional Figma integration end-to-end so a supplied --figma-url can drive design-aware prompt generation via the orchestrator, and adds an optional MCP guidance section in the Figma prompt template.
Changes:
- Add a
--figma-urlCLI flag and pass it through config into orchestration. - Fork orchestrator prompt generation to a Figma-aware path that fetches/normalizes Figma data, builds mappings, persists artifacts, and renders prompts from resolved chunks.
- Extend prompt rendering/template context to optionally include a Figma MCP guidance block when a Figma URL is present.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/bauer/main.go | Adds --figma-url flag and updates CLI help text / config wiring. |
| internal/orchestrator/orchestrator.go | Adds Figma-aware prompt generation path (generateChunksWithFigma) and switches Execute based on cfg.FigmaURL. |
| internal/prompt/engine.go | Threads FigmaURL into the figma template context and adds RenderChunksFromResolved helper. |
| internal/prompt/templates/figma-context.md | Adds conditional MCP guidance block rendered when .FigmaURL is set. |
| docs/implementation-log.md | Updates implementation log entry for this phase. |
Comments suppressed due to low confidence (1)
internal/orchestrator/orchestrator.go:364
executeAgentChunkshas a formatting issue (func executeAgentChunks( ctx ...) that should be cleaned up; it looks like a gofmt regression and may fail golangci-lint formatting checks. Please run gofmt (or fix the signature formatting) before merging.
// executeAgentChunks executes each chunk via the agent and returns outputs.
func executeAgentChunks( ctx context.Context,
chunks []prompt.ChunkResult,
cfg *config.Config,
a agent.Agent,
) ([]ChunkOutput, time.Duration, error) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -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") | |||
| slog.Warn("Failed to create screenshots dir, proceeding without screenshots", | ||
| slog.String("error", err.Error())) | ||
| screenshotDir = "" | ||
| } | ||
| } |
| @@ -273,8 +282,82 @@ func (o *DefaultOrchestrator) Execute(ctx context.Context, cfg *config.Config) ( | |||
| } | |||
|
|
|||
| // executeAgentChunks executes each chunk via the agent and returns outputs. | |||
| 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 |
| 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 |
There was a problem hiding this comment.
Agreed — RenderChunksFromResolved should have unit test coverage. Tracking for a follow-up; out of scope for this PR.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
cmd/bauer/main.go:42
- The usage banner was changed to
Usage: <cmd> [flags], but--doc-id(orBAUER_DOC_ID) is still required andConfig.Validate()will fail without it. Consider restoring a usage line that clearly indicates the required--doc-id <id>to avoid confusing users.
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")
fmt.Fprintf(os.Stderr, "\tBAUER_DOC_ID Override for --doc-id\n")
fmt.Fprintf(os.Stderr, "\tBAUER_CREDENTIALS_PATH Override for --credentials\n")
| @@ -47,7 +47,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") | |||
| } | |||
| figmaRef, err := figma.ParseLink(cfg.FigmaURL) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("invalid figma URL %q: %w", cfg.FigmaURL, err) | ||
| } |
There was a problem hiding this comment.
This is a known limitation. When a Figma URL has no node-id, FetchFigma still proceeds (fetches file-level metadata and comments) but produces zero anchors. The orchestrator continues without Figma anchor context (a warning is logged). Full whole-file node enumeration is planned but intentionally deferred; the current behavior is graceful degradation, not a failure path.
| // 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 = "" | ||
| } | ||
| } | ||
|
|
||
| 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 { |
| // Append figma context section when design data is present | ||
| if data.FigmaContextJSON != "" { | ||
| var ctx figmaChunkContext | ||
| if err := json.Unmarshal([]byte(data.FigmaContextJSON), &ctx); err != nil { | ||
| return "", fmt.Errorf("parsing figma context JSON: %w", err) | ||
| } | ||
| ctx.FigmaURL = data.FigmaURL |
| @@ -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]) | |||
Summary
Threads Figma integration through the CLI and orchestrator, completing Phase D of spec 002.
Tasks Implemented
--figma-urlflag tocmd/bauer/main.go, wired intoCLIFlags.FigmaURL. The orchestrator forks oncfg.FigmaURL != "": the figma-aware path callsgenerateChunksWithFigma()which fetches design data viasources.FetchFigma, runsmapping.Resolver.Build, persists figma artifacts, and generates prompts viaengine.RenderChunksFromResolved.FigmaURLfield added tofigmaChunkContext(withjson:"-"), andfigma-context.mdhas a{{if .FigmaURL}}conditional section with instructions for AI agents that have MCP access.Files Changed
cmd/bauer/main.go—--figma-urlflag, wired throughFlagsSourceinternal/orchestrator/orchestrator.go—generateChunksWithFigma()method;Executeforks on FigmaURLinternal/prompt/engine.go—FigmaURLin context;RenderChunksFromResolved()methodinternal/prompt/templates/figma-context.md— MCP guidance block ({{if .FigmaURL}})Part of the Bauer v2 stacked PR series (Branch 7 of 12).