Repository navigation
Conversation
A variation row writes the content of each tool and skill that it uses. The guard for tool and skill changes skips one that the overwritten variation does not use yet. So in a sync of prompts, two overwrites happened: - A push of a variation that newly attaches a tool published the old local copy over a change in LaunchDarkly. - A pull of a variation that newly references a tool wrote the old LaunchDarkly copy over a local edit. The plan now has a row for such a tool or skill when it exists on both sides with different content. The row decides the content, as in a sync of every kind: it pulls, pushes, or is a conflict. The variation row then uses that content and pins its version. A tool or skill with equal sides gets no row, so other plans do not change.
6bf458a to
68a0951
Compare
| // writtenSides returns the variation that a variation row writes, and the | ||
| // variation that the write replaces. A push writes the local variation to | ||
| // LaunchDarkly, and a pull writes the LaunchDarkly variation to the local | ||
| // file. Both are nil for a row that writes no variation. |
There was a problem hiding this comment.
this is a bit of a confusing comment. either remove are make more succinct imo
A conflicted variation planned a row for each tool and skill that one of its sides newly used. The conflict choice of the variation then also covered that row. So a choice of LaunchDarkly, whose version does not use the tool, pulled the tool over a local edit. A conflict writes no side until the user chooses one. So only a push or a pull of a variation now plans these rows. A conflict plans no extra rows. The comment of writtenSides is shorter.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0a8d6d0. Configure here.
…ly uses A conflicted variation planned no row for a tool or skill that one of its sides newly used. So a choice of local published the local copy over a LaunchDarkly change of the tool, and a tool that changed on both sides never became part of the conflict. Each side of a conflict now counts. A row that only conflicts need joins the conflict when the tool or skill changed on both sides. Otherwise it keeps its own action, and stays in the plan only when the chosen side uses the tool or skill. So a choice of local pulls the LaunchDarkly change first, and a choice of LaunchDarkly still leaves a tool that it does not use as it is.
| // A push of a variation that newly attaches a tracked tool must not publish | ||
| // the local copy over a change in LaunchDarkly. The plan pulls the tool, and | ||
| // the variation pins the pulled version. | ||
| func TestPromptPushOfANewToolReferenceKeepsTheLaunchDarklyChange(t *testing.T) { |
There was a problem hiding this comment.
no change needed - calling out the push with the dry run flag doesn't change anything might be helpful for the test name/descriptive comment. It immediately gives a good understanding of the test. same with the pull using the flag.
There was a problem hiding this comment.
Good idea, done in cf6adb5. Both comments now say the dry run plans the tool row and changes nothing, and the tests check that right after the dry run.
The push and pull tests of a new tool reference start with a dry run. Their comments now say so, and each test checks after the dry run that it sent only reads and left the tool file as it was.

Context
A sync plans one row for each variation. In the
tools,skills, andallscopes, it also plans one row for each tool and skill. A variation row also writes the content of each tool and skill that the variation uses. A push publishes the local copy, and a pull writes the LaunchDarkly copy to the local file.checkAttachmentBaselines(#939) stops a variation row from overwriting a tool that changed on the other side. But it skips a tool that the overwritten side of the variation does not use yet. So in async promptsplan, which has no tool rows, two overwrites happen onmain:#941 already prevents both overwrites when the plan has a row for the tool (
attachmentResolver.settledRow). This PR plans that row.What changes
planReferencedAttachmentscollects each tool and skill that a variation row writes to a side that does not use it yet. Only a push or a pull counts. A conflict writes no side until the user chooses one, and its choice covers only the variation, so a conflict gets no extra row.planAttachmentRows, and keeps only the rows where both sides exist and differ. Only then can the variation row overwrite something.settledRow, andgroupConflictsdo the rest. When both sides of the tool changed, the tool row is a conflict, and the variations that share the tool get one choice.The push case against a stand-in for the LaunchDarkly API. The
searchtool changed in LaunchDarkly, andattachadds it to a second variation:Design notes
planAttachmentRows,planAttachment, the sharedattachmentCache,settledRow, andgroupConflicts. There is no second guard.checkAttachmentBaselinesnow uses the newwrittenSides(), the same helper that finds the new references. Both read the direction of a row in one place.--config-keysync is safe too, and it reads only tools of the projects that it already reads.Review focus
attach?Verification
go test ./...andgolangci-lint run(v2.9.0) pass.sync promptsthrough the command. They fail onmainand pass here:TestPromptPushOfANewToolReferenceKeepsTheLaunchDarklyChangeTestPromptPullOfANewToolReferenceKeepsTheLocalEdit, whose next dry run plans only the repin of the variationTestPromptPullOfANewSkillReferenceKeepsTheLocalEditTestPromptNewToolReferenceWithTwoChangesIsAConflictTestPromptNewToolReferenceWithEqualSidesPlansNoToolRowchecks that a safe plan does not change.TestPromptConflictChoiceDoesNotPullAToolThatOnlyTheLocalVersionUseschecks that a choice of LaunchDarkly for a conflicted variation does not pull a tool that only the local version newly uses. It fails without0a8d6d07.ldclifrommainand from this branch, and ran both flows against a stand-in for the LaunchDarkly API.mainoverwrites in both flows. This branch keeps both changes.Related changes
This stack builds on #937 to #941, which are merged.
ctawiah/fix-prompt-tool-diff-5b1c)ctawiah/plan-referenced-attachments-1e8a)ctawiah/keep-unused-attachments-1e8a)ctawiah/add-attachments-1e8a)ctawiah/route-scopes-1e8a)ctawiah/scope-commands-1e8a)ctawiah/create-attachments-1e8a)ctawiah/create-attachment-flags-1e8a)ctawiah/share-variation-selection-1e8a)ctawiah/create-variations-1e8a)ctawiah/create-variation-flags-1e8a)Note
Overview
Fixes sync prompts so attaching a tool or skill to a variation no longer silently overwrites the other side’s attachment content.
When a variation push/pull newly references a tracked tool or skill that differs locally and in LaunchDarkly, planning now adds an explicit tool/skill row (via
planReferencedAttachments) so content is reconciled before the variation pins a version. Equal content or missing server copies still get no extra row.Conflict handling is extended:
conflictUsestracks tools/skills only needed for one side of a conflicted variation;applyConflictResolutionsdrops or keeps those rows based on the user’s choice, and conflict grouping no longer absorbs them incorrectly.Broad acceptance tests cover push/pull, skills, dual-side edits, conflict flags, and repin-only follow-up syncs.
Reviewed by Cursor Bugbot for commit cf6adb5. Bugbot is set up for automated code reviews on this repo. Configure here.