Skip to content

feat(figma): drift detection and mapping cache reuse (Phase E — T2F.10)#43

Open
canonical-muhammadbassiony wants to merge 3 commits into
feat/figma-phase-d-clifrom
feat/figma-phase-e-drift
Open

feat(figma): drift detection and mapping cache reuse (Phase E — T2F.10)#43
canonical-muhammadbassiony wants to merge 3 commits into
feat/figma-phase-d-clifrom
feat/figma-phase-e-drift

Conversation

@canonical-muhammadbassiony

Copy link
Copy Markdown
Collaborator

Summary

Implements drift detection for Figma-backed runs. Bauer now skips expensive API calls when the design file has not changed since the last run.

Tasks Implemented

  • T2F.10:
    • GetMeta called first to retrieve file version before any other Figma API calls
    • If version unchanged vs previous run → reuse stored mappings, skip GetNodes/screenshot downloads
    • If version changed → log warning, full re-fetch
    • Resolver.Build hardened with post-process normalization: chunks with Confidence < 0.5, Method == "fallback", or Method == "none" explicitly marked as Status: "unresolved"

Files Changed

  • internal/artifacts/manager.goFigmaVersion field; LoadPreviousMeta, LoadMappings, UpdateRunFigmaVersion methods
  • internal/orchestrator/orchestrator.go — drift detection in generateChunksWithFigma; cache-hit early-return
  • internal/source/mapping/resolver.go — post-process normalization loop

Part of the Bauer v2 stacked PR series (Branch 8 of 12).

T2F.10: LoadPreviousMeta looks up most recent matching run by docID + figmaFileKey
T2F.10: LoadMappings reads mappings.json from a previous run artifact
T2F.10: generateChunksWithFigma checks Figma version before re-fetching
T2F.10: version unchanged => reuse stored mappings (skips GetNodes + screenshot DL)
T2F.10: version changed => warn + full re-fetch
T2F.10: RunMetadata.FigmaVersion stored after each figma fetch
T2F.10: low-confidence/fallback mappings flagged with status: unresolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Figma drift detection so Bauer can skip expensive Figma fetches when the target file version hasn’t changed, and tightens mapping status normalization to avoid “silently promoted” low-confidence mappings.

Changes:

  • Add figma_version to run metadata and index entries; introduce artifact-manager helpers for loading prior run metadata/mappings and updating the stored Figma version.
  • Implement drift detection in generateChunksWithFigma with a cache-hit early return that reuses stored mappings when the Figma version matches.
  • Normalize mapping outputs so low-confidence / fallback / none mappings are explicitly marked Status: "unresolved".

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
internal/source/mapping/resolver.go Adds a post-pass to enforce Status: "unresolved" for low-confidence/fallback/none mappings.
internal/orchestrator/orchestrator.go Adds drift detection via GetMeta and a cache-hit early return that reuses prior mappings.
internal/artifacts/manager.go Persists FigmaVersion and adds helpers to find prior run metadata, load mappings, and patch run metadata.
docs/implementation-log.md Updates implementation log entry to describe Phase E / T2F.10 changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +314 to +331
// Version unchanged: reuse stored mappings and skip re-fetch.
slog.Info("Figma version unchanged; reusing stored mappings",
slog.String("version", currentMeta.Version),
slog.String("prev_run_id", prevRunMeta.RunID),
)
resolvedChunks := o.arts.LoadMappings(prevRunMeta.RunID)
if resolvedChunks != nil {
return engine.RenderChunksFromResolved(
bundle.Document.DocumentTitle,
suggestedURL,
cfg.FigmaURL,
resolvedChunks,
cfg.ChunkSize,
cfg.OutputDir,
)
}
slog.Warn("Previous mappings could not be loaded; proceeding with full re-fetch",
slog.String("prev_run_id", prevRunMeta.RunID))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. The drift check gates on Figma version only — it answers 'has the design changed?' If the Google Doc content changed, the user would simply re-run (the doc is always freshly fetched at the start of Execute). Coupling doc-change detection into the Figma drift path would conflate two independent concerns.

Comment on lines +321 to +328
return engine.RenderChunksFromResolved(
bundle.Document.DocumentTitle,
suggestedURL,
cfg.FigmaURL,
resolvedChunks,
cfg.ChunkSize,
cfg.OutputDir,
)
Comment on lines +301 to +305
// Drift detection: check whether the Figma file version has changed since last run.
currentMeta, err := figmaClient.GetMeta(ctx, figmaRef.FileKey)
if err != nil {
return nil, fmt.Errorf("fetching figma metadata for drift check: %w", err)
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the meta fetched for drift detection could be threaded into FetchFigma to avoid the redundant call. This requires changing FetchFigma's signature and plumbing the response through, so tracking as a follow-up to keep this PR focused on drift detection correctness.

Comment on lines +207 to +221
// LoadPreviousMeta returns the RunMetadata from the most recent completed run
// that matches the given (docID, figmaFileKey) pair, or nil if none exists.
// It reads runs.jsonl in reverse order (last entry = most recent) to find the match.
func (m *Manager) LoadPreviousMeta(docID, figmaFileKey string) *RunMetadata {
indexPath := filepath.Join(m.base, "runs.jsonl")
f, err := os.Open(indexPath)
if err != nil {
return nil
}
defer f.Close()

// Collect matching entries (in file order; last = most recent).
var matched []RunIndexEntry
scanner := bufio.NewScanner(f)
for scanner.Scan() {
Comment on lines +210 to +217
func (m *Manager) LoadPreviousMeta(docID, figmaFileKey string) *RunMetadata {
indexPath := filepath.Join(m.base, "runs.jsonl")
f, err := os.Open(indexPath)
if err != nil {
return nil
}
defer f.Close()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed these need coverage. Will add unit tests for LoadPreviousMeta, LoadMappings, and UpdateRunFigmaVersion in a follow-up.

Comment thread internal/source/mapping/resolver.go Outdated
Comment on lines +35 to +40
// Enforce: low-confidence and fallback/none mappings must never be silently promoted.
for i := range chunks {
m := &chunks[i].Mapping
if m.Confidence < 0.5 || m.Method == "fallback" || m.Method == "none" {
m.Status = "unresolved"
}
Comment thread docs/implementation-log.md Outdated
Comment on lines +256 to +260
**Summary:** Implemented drift detection and mapping cache reuse for Figma-backed runs. `RunMetadata` and `RunIndexEntry` gained a `FigmaVersion` field, and three new methods were added to `artifacts.Manager`: `LoadPreviousMeta` (scans `runs.jsonl` in reverse to find the most recent successful run with a matching DocID and Figma file key), `LoadMappings` (reads `extraction/mappings.json` from a prior run), and `UpdateRunFigmaVersion` (patches the current run's `metadata.json` after a fresh Figma fetch). In `generateChunksWithFigma`, a `GetMeta` call is now made before any other Figma API calls; if the version is unchanged versus the previous run, the stored mappings are reused and `GetNodes`/screenshot downloads are skipped; if changed, a warning is logged and a full re-fetch proceeds. `Resolver.Build` was hardened with a post-process normalization step that explicitly marks any chunk with `Confidence < 0.5`, `Method == "fallback"`, or `Method == "none"` as `Status: "unresolved"`, preventing silent promotion of low-quality mappings.

**Files changed:** _(to be filled by agent)_
**Files changed:**
- `internal/artifacts/manager.go`: Added `FigmaVersion` field to `RunMetadata` and `RunIndexEntry`; added `LoadPreviousMeta`, `LoadMappings`, and `UpdateRunFigmaVersion` methods; added `bufio` import for JSONL scanning.
- `internal/orchestrator/orchestrator.go`: Rewrote `generateChunksWithFigma` to call `GetMeta` first for drift detection, consult `LoadPreviousMeta`/`LoadMappings` for cache reuse, log version changes as warnings, and call `UpdateRunFigmaVersion` after each fresh Figma fetch.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment on lines +319 to +323
resolvedChunks := o.arts.LoadMappings(prevRunMeta.RunID)
if resolvedChunks != nil {
// Persist reused mappings into the current run so it is self-contained.
if runID != "" {
if err := o.arts.WriteMappings(runID, resolvedChunks); err != nil {
Comment on lines +236 to +241
// Match figmaFileKey against the stored FigmaURL.
if entry.FigmaURL == "" {
continue
}
ref, err := figma.ParseLink(entry.FigmaURL)
if err != nil || ref.FileKey != figmaFileKey {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is a known limitation. LoadPreviousMeta currently matches by file key only, so running against a different node within the same file will incorrectly reuse stale mappings. Fixing this properly requires adding a node_id column to RunIndexEntry and updating the JSONL schema. Tracking as a follow-up for multi-node workflow support.

Comment on lines +228 to +235
continue
}
if entry.Status != "success" {
continue
}
if entry.DocID != docID {
continue
}
Comment on lines +35 to +41
// Post-process: low-confidence, fallback, and unmatched ("none") mappings are
// promoted to status "unresolved" so they are never silently treated as healthy.
for i := range chunks {
m := &chunks[i].Mapping
if m.Confidence < 0.5 || m.Method == "fallback" || m.Method == "none" {
m.Status = "unresolved"
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +207 to +209
// LoadPreviousMeta returns the RunMetadata from the most recent completed run
// that matches the given (docID, figmaFileKey) pair, or nil if none exists.
// It scans runs.jsonl forward and returns metadata from the last matching entry.
continue
}
matched = append(matched, entry)
}
Comment on lines +250 to +255
// Iterate backwards to find the most recent run with a non-empty FigmaVersion.
for i := len(matched) - 1; i >= 0; i-- {
metaPath := filepath.Join(m.base, matched[i].RunID, "metadata.json")
data, err := os.ReadFile(metaPath)
if err != nil {
continue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants