Skip to content

feat: show a compact sync plan and add a --diff flag - #937

Merged
ctawiah merged 3 commits into
mainfrom
ctawiah/compact-sync-output-1e8a
Oct 22, 2026
Merged

ctawiah merged 3 commits into
mainfrom
ctawiah/compact-sync-output-1e8a

Conversation

@ctawiah

@ctawiah ctawiah commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Context

ldcli sync prompts keeps local prompt files in step with the variations of LaunchDarkly AI Configs. Before it writes, it shows a plan: one row for each variation, with the action that sync will take, such as a push or a pull.

The plain-text plan is hard to read today:

  • Each row prints project, AI Config, and variation headings, an Action: line, and a full diff. Rows that are in sync print too.
  • add --dry-run prints every file in full.

This PR gives the plan, the results, and the add preview a compact layout. Later PRs in this stack add tool and skill rows, and they print them with this layout.

What changes

  • The plain-text and Markdown output of the plan, the results, and the add preview use a compact layout. JSON output does not change.
  • A summary line comes first. Conflicts and errors get their own clause. A plan with nothing to do prints No changes. N in sync.
  • Each change gets one row, grouped under Skills, Tools, or Prompts in apply order. Rows that are in sync appear only in the count.
  • The size column counts the added and removed lines in the diff of the row. Placeholder lines such as (does not exist in LaunchDarkly) do not count.
  • On a color terminal, the size column shows +N in green and -N in red. These are the colors of the added and removed lines in a diff.
  • Piped output, -o markdown, -o json, and output with NO_COLOR set have no color. Column widths ignore the color codes, so the columns stay aligned.
  • A new --diff flag prints each full diff. A conflict always shows its diff. sync prompts, watch, add, attach, and link have the flag.
  • A run that is not a dry run prints the plan only when it needs it. That is before the confirmation question, when a conflict or error row stops the run, or when there is nothing to apply. With --yes, only the results print. A dry run always prints the plan.
  • A conflict prints one header line, such as Conflict: variation demo/support/concise changed on both sides., and then its diff.
  • The note about another working copy (another checkout of the repository) is one Note: line on stderr.
  • -o markdown prints one table for each kind. add --dry-run lists the files and their line counts.
  • BlockingError names the kind of the row, for example cannot sync conflicted resource variation demo/support/concise. Existing error substrings still match.

A plan now looks like this:

2 changes: 1 prompt push, 1 prompt pull. 1 in sync.

Prompts
  pull   demo/support/concise   instructions changed in LaunchDarkly   +1 -1
  push   demo/support/default   instructions changed                   +1 -1

And the results of ldcli sync prompts --yes:

Prompts
  pulled   demo/support/concise   instructions changed in LaunchDarkly
  pushed   demo/support/default   instructions changed

2 applied. 1 in sync.

Design notes

  • One routine, diffBlocks in render_diff.go, feeds both the diff renderer and the size counter. So a size always matches what --diff shows.
  • Placeholder lines stay out of the size. If they counted, a create reads +5 -1 although it removes nothing.
  • The words and reasons come from small tables and functions in output.go. writeOutcomeOutput takes the executed plan, so a result row can show the plan reason.
  • This PR removes the unused field conflictGroup.attachment. It always equals "more than one resource".
  • Result rows do not show the new pins of a variation yet, such as pins search v4. A pin is the tool or skill version that a variation uses. That data comes from the apply step, so feat: apply tool and skill rows in a sync #941 adds it.

Review focus

  • Does the plan still print in every case where the user needs it?
  • Does each size match the --diff output of its row?
  • Is the JSON output of the plan and the results unchanged?
  • Do scripts that match BlockingError text still match?

Verification

  • go test ./...

  • golangci-lint run (v2.9.0)

  • TestPlanAndOutcomeJSONIsStable compares the plan and outcome JSON with a saved copy, so a later change cannot change the JSON by accident.

  • TestWritePlanReviewDiffMatchesRowSize compares each size with the --diff output.

  • I built ldcli and ran it against a local stand-in for the LaunchDarkly API. The runs covered add --dry-run with and without --diff, add, a dry run, --yes, and -o markdown. They also covered a conflict with and without --conflict local, an error row, and a stale working copy.

  • TestDiffSizeTextColorsEachPart, TestWritePlanReviewColorsSizesOnATerminal, and TestWritePlanSizesHaveNoColorOffATerminal cover colored and plain sizes. The second test removes the color codes and compares the result with the plain plan.

  • I ran a dry run in a 120-column pseudo-terminal. +1 was green and -1 was red. With NO_COLOR=1, with -o markdown, and through a pipe, the plan had no color.

Related changes

  1. This PR: show a compact sync plan and add a --diff flag
  2. feat: add target flags to sync prompts and read sync flags only from the command line #938: add target flags, and read sync flags only from the command line
  3. feat: plan standalone tool and skill rows by sync scope #939: plan tool and skill rows by sync scope
  4. feat: create skills during sync, and read again after a 409 on create #940: create skills during sync, and read again after a 409 on create
  5. feat: apply tool and skill rows in a sync #941: apply tool and skill rows in a sync

Note

Overview
Reworks plain-text and Markdown output for ldcli sync prompts so plans and apply results are scannable: a one-line summary, then aligned rows grouped under Tools / Prompts (action, resource, reason, optional Size +N -N from the same diff logic as rendering). In-sync resources only appear in the count.

Adds --diff on sync, watch, add, and apply-style commands to print full diffs (conflicts always show a diff). add --dry-run lists paths with line counts; with --diff, file bodies follow. Plan printing is lazy: shown before confirm, on blocking errors, when nothing applies, or on dry-run; --yes skips the plan and prints compact results only. Stale-working-copy hints move to stderr Note: lines; BlockingError aggregates all blockers and uses ResourceID.Label().

JSON plan/outcome shape is unchanged (covered by stability tests). Terminal size colors respect NO_COLOR and non-TTY output.

Reviewed by Cursor Bugbot for commit a576171. Bugbot is set up for automated code reviews on this repo. Configure here.

The plain-text and Markdown output of sync prompts now starts with a
summary line and shows one row for each change, grouped by resource
kind, with the size of its diff. Full diffs show only for a conflict or
with the new --diff flag. A run with --yes prints only the results.
add --dry-run lists the files that it creates, and --diff adds their
content. JSON output does not change.
On a color terminal, the plan shows +N in green and -N in red. These
are the colors of the added and removed lines in the diff. Piped
output, Markdown, JSON, and output with NO_COLOR set have no color.
Column widths ignore the color codes.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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 fb46607. Configure here.

Comment thread internal/sync/prompt/output.go
A plan row shows only the first clause of an error. The error after
the plan named only the first conflict or error row, so the full
message of a later row did not print. For example, the hint "set
upsert: true to create it" was lost. BlockingError now returns one
line for each blocking row, in plan order. The first line does not
change.
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