Skip to content

feat: distinguish dry-run copies in JSON and human output#17

Merged
satococoa merged 2 commits intocopilot/discuss-doctor-command-necessityfrom
copilot/add-dry-run-field-and-summary-split
Apr 5, 2026
Merged

feat: distinguish dry-run copies in JSON and human output#17
satococoa merged 2 commits intocopilot/discuss-doctor-command-necessityfrom
copilot/add-dry-run-field-and-summary-split

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 5, 2026

apply --dry-run was incrementing summary.copied and printing copied= even though no files were written, making it ambiguous whether the count reflects actual copies or planned ones.

Changes

engine.go

  • Added DryRun bool to Result (JSON: "dry_run")
  • Split Summary.CopiedCopied / CopyPlanned with omitempty; they are mutually exclusive in JSON output
  • executePrepared increments CopyPlanned when dryRun=true, Copied otherwise

cli.go

  • SUMMARY line uses copy_planned= in dry-run mode, copied= in real mode

README

  • JSON output section now shows both modes with their respective fields
  • --dry-run flag description notes the label difference

Tests

  • jsonResult updated with DryRun and CopyPlanned fields
  • TestApplyDryRunVerboseOutput asserts copy_planned= present, copied= absent
  • New TestApplyDryRunJSON: verifies dry_run=true, copy_planned>0, copied==0, all copy actions status="planned"
  • New TestEngineApplyDryRunCopyPlanned: same contract at engine level

JSON example

// apply --dry-run --json
{
  "dry_run": true,
  "summary": { "matched": 12, "copy_planned": 8, "skipped_same": 3, ... },
  "actions": [{"op": "copy", "path": ".env", "status": "planned"}, ...]
}

// apply --json
{
  "dry_run": false,
  "summary": { "matched": 12, "copied": 8, "skipped_same": 3, ... },
  "actions": [{"op": "copy", "path": ".env", "status": "done"}, ...]
}
Original prompt

Context

The PR branch copilot/discuss-doctor-command-necessity already removed the doctor command. However, the apply command still uses copied in both the JSON summary field and the human-readable SUMMARY line, even during --dry-run mode where no files are actually copied. This is ambiguous.

Required Changes

1. Add DryRun field to Result (JSON: "dry_run")

In internal/engine/engine.go, add a DryRun bool field to the Result struct so the JSON output includes "dry_run": true or "dry_run": false. This field should be set by executePrepared based on the dryRun parameter.

2. Split Summary.Copied into Copied and CopyPlanned

In internal/engine/engine.go:

  • Change the Summary struct to have two fields:
    type Summary struct {
        Matched           int `json:"matched"`
        Copied            int `json:"copied,omitempty"`
        CopyPlanned       int `json:"copy_planned,omitempty"`
        SkippedSame       int `json:"skipped_same"`
        SkippedMissingSrc int `json:"skipped_missing_src"`
        Conflicts         int `json:"conflicts"`
        Errors            int `json:"errors"`
    }
  • Copied and CopyPlanned are mutually exclusive via omitempty: in dry-run mode only CopyPlanned is non-zero; in real execution mode only Copied is non-zero.
  • In executePrepared, where it currently does result.Summary.Copied++, change it to:
    • If dryRun is true: result.Summary.CopyPlanned++
    • If dryRun is false: result.Summary.Copied++

3. Update human-readable SUMMARY line in internal/cli/cli.go

In the runApply function, change the SUMMARY format string to use copy_planned= when dryRun is true and copied= when dryRun is false:

  • When dryRun is true:

    SUMMARY matched=%d copy_planned=%d skipped_same=%d skipped_missing_src=%d conflicts=%d errors=%d
    

    using result.Summary.CopyPlanned

  • When dryRun is false:

    SUMMARY matched=%d copied=%d skipped_same=%d skipped_missing_src=%d conflicts=%d errors=%d
    

    using result.Summary.Copied

4. Update README.md

  • Update the JSON output example to show both modes (dry-run and normal execution).
  • The dry-run JSON example should show "dry_run": true and "copy_planned" instead of "copied".
  • The normal execution JSON example should show "dry_run": false and "copied".
  • Add a note to the --dry-run flag description explaining that in dry-run mode, the summary uses copy_planned instead of copied.

5. Update tests

internal/engine/engine_integration_test.go

  • TestEngineApplyCopiesIgnoredFiles: currently checks res.Summary.Copied != 2 — keep as-is since this is a real apply (not dry-run).
  • TestEngineApplyIncludeValidationAndNoop: checks res.Summary.Copied != 0 — keep as-is.
  • TestEngineApplyUsesSourceIncludeWhenTargetIncludeMissing: checks res.Summary.Copied != 2 — keep as-is.
  • TestEngineApplyNoopWhenSourceIncludeMissingEvenIfTargetHasInclude: checks res.Summary.Copied != 0 — keep as-is.
  • Add a new test for dry-run mode that verifies CopyPlanned is set and Copied is 0, and that DryRun is true in the result.

internal/cli/cli_integration_test.go

  • Update jsonResult struct to include DryRun bool and both Copied and CopyPlanned fields.
  • TestApplyAC1AC2AC6AC7: keep checking res.Summary.Copied (not dry-run).
  • TestApplyAC8MissingIncludeIsNoop: update to check both Copied and CopyPlanned are 0.
  • TestApplyUsesSourceIncludeWhenTargetIncludeMissing: keep checking res.Summary.Copied (not dry-run).
  • TestApplyDryRunVerboseOutput: update to check for copy_planned= instead of copied= in the SUMMARY line.
  • TestApplyNoopWhenSourceIncludeMissingEvenIfTargetHasInclude: update if it checks Copied.
  • Add a new test TestApplyDryRunJSON that runs apply --dry-run --json and verifies:
    • res.DryRun == true
    • res.Summary.CopyPlanned > 0
    • res.Summary.Copied == 0
    • All actions have status: "planned" (for copy ops)

internal/cli/cli_unit_test.go

  • No changes needed (formatActionLine tests don't involve summary fields).

Important notes

  • Do NOT push to main. Push only to the existing branch copilot/discuss-doctor-command-necessity.
  • Make sure all existing tests continue to pass with the updated logic.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: The README now directs users to apply --dry-run --verbose for diagnostics, but the human summary label is copied= and the JSON field is summary.copied, which both still increment during dry-run (planned copies) even though no files are actually copied. This is user-facing ambiguity; consider clarifying in the docs that copied represents copies performed or planned (dry-run), or adjust the CLI/JSON wording to distinguish copy_planned vs copied when --dry-run is set.

  - use `--dry-run --verbose` when you want diagnost...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

- Add DryRun bool field to Result struct (JSON: "dry_run")
- Split Summary.Copied into Copied (real mode) and CopyPlanned (dry-run mode)
  with omitempty for mutual exclusivity
- Update executePrepared to increment CopyPlanned in dry-run, Copied in real mode
- Update SUMMARY human output to use copy_planned= in dry-run mode
- Update jsonResult struct in CLI integration tests
- Add TestApplyDryRunJSON CLI integration test
- Add TestEngineApplyDryRunCopyPlanned engine integration test
- Update README with separate dry-run and normal JSON output examples

Agent-Logs-Url: https://github.com/satococoa/git-worktreeinclude/sessions/b43b6731-413f-4d2f-ae93-143bd52bfa32

Co-authored-by: satococoa <31448+satococoa@users.noreply.github.com>
Copilot AI changed the title [WIP] Add DryRun field to Result and split Summary fields feat: distinguish dry-run copies in JSON and human output Apr 5, 2026
Copilot AI requested a review from satococoa April 5, 2026 14:04
@satococoa satococoa marked this pull request as ready for review April 5, 2026 14:09
Copilot AI review requested due to automatic review settings April 5, 2026 14:09
@satococoa satococoa merged commit b03c927 into copilot/discuss-doctor-command-necessity Apr 5, 2026
6 checks passed
@satococoa satococoa deleted the copilot/add-dry-run-field-and-summary-split branch April 5, 2026 14:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR removes ambiguity in apply --dry-run output by clearly distinguishing planned copies from performed copies in both JSON output and the human-readable SUMMARY line.

Changes:

  • Added dry_run to the JSON Result payload and populated it from the engine.
  • Split summary.copied into summary.copied vs summary.copy_planned (mutually exclusive via omitempty) and updated engine counting logic accordingly.
  • Updated CLI summary labeling, README examples, and added/updated integration tests to assert the new dry-run contract.

Reviewed changes

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

Show a summary per file
File Description
README.md Documents dry_run and shows separate JSON examples/field naming for dry-run vs normal execution.
internal/engine/engine.go Adds Result.DryRun, splits summary counters (Copied/CopyPlanned), and increments the correct one based on dryRun.
internal/engine/engine_integration_test.go Adds engine-level dry-run test coverage for DryRun, CopyPlanned, and copy action status="planned".
internal/cli/cli.go Prints copy_planned= in dry-run SUMMARY output and copied= otherwise.
internal/cli/cli_integration_test.go Updates JSON decoding struct and adds CLI-level dry-run JSON/verbose assertions for the new fields/labels.

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ebb9ea274

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +29 to +30
Copied int `json:"copied,omitempty"`
CopyPlanned int `json:"copy_planned,omitempty"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve mode-specific summary key when count is zero

omitempty on both Summary.Copied and Summary.CopyPlanned causes both counters to disappear whenever the value is 0 (for example, apply --dry-run --json when all matched files are already identical, or a normal no-op run). That makes dry-run JSON omit copy_planned even though the CLI/docs describe it as the dry-run counter, and it also changes the previous JSON shape where copied was always emitted, which can break consumers that key off field presence.

Useful? React with 👍 / 👎.

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.

3 participants