Skip to content

adapter: type FileOp.MergeStrategy (follow-up to #230) #250

Description

@spxrogers

Context

#230 (PR #249) typed FileOp.Action and added an explicit OpKind. It deliberately left FileOp.MergeStrategy as a plain string ("replace" | "merge-json-keys" | "merge-jsonc-keys" | "merge-toml-keys"). This issue records why, with the measured blast radius, so the follow-up starts from real numbers.

Measured

The cheap partialtype MergeStrategy string + four consts + an IsKeyMerge() method; retype the FileOp field, the three string-typed strategy params (render.IsKeyMerge, render.decodeDestObject, cli.decodeDestBytes/readDestFile, cli.readDestConfig) and convert at the two KeyMergeStrategy() boundaries (render.orphanCleanupOps, cli.purgeAgentDests) — is ≈7 prod + 2 test files, ~40 lines, with the Adapter interface untouched. Every "merge-json-keys" literal compiles as an untyped constant, so the 55 prod / 69 test literals are not in the blast radius.

Why it was deferred

The partial leaves a half-typed API: a typed field fed by the string-returning Adapter.KeyMergeStrategy() accessor through explicit conversions at every boundary — worse than either extreme. The full version retypes KeyMergeStrategy() stringKeyMergeStrategy() MergeStrategy, which changes the published Adapter interface (docs/architecture.md §3, docs/components.md) implemented by all ten adapter packages and every generic.Spec agent. That is its own change and should land as one commit with render.IsKeyMerge becoming (MergeStrategy).IsKeyMerge().

Scope when taken

  • type MergeStrategy string with the four consts and IsKeyMerge(); retype FileOp.MergeStrategy, NewCleanupOp's strategy param, the three strategy params and two readDest* gates listed above.
  • Adapter.KeyMergeStrategy() MergeStrategy across all adapter packages + generic.
  • docs/architecture.md §3 interface block, docs/components.md, CHANGELOG (Internal).
  • The Extract a shared plan→drift-classification walk for status/diff/reconcile/explain #229 characterization harness should need only its keyOp constructor line; zero goldens.
  • The cleanup-shape AST guard must learn the typed constant. TestEveryCleanupLiteralUsesNewCleanupOp (internal/adapter/cleanupop_guard_test.go, hasCleanupShape) exempts a whole-file {} write only when MergeStrategy is the string literal "replace"; once the field is typed, MergeStrategy: adapter.<ReplaceConst> is an identifier and would be flagged as the cleanup shape. Teach the exemption the constant's selector/ident and add a matcher row for it (found in PR refactor(adapter): typed FileOp.Action and an explicit OpCleanup kind (#230) #249's review loop; latent only — no production site sets an explicit replace today).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions