You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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 partial — type 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() string → KeyMergeStrategy() 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.
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).
Context
#230 (PR #249) typed
FileOp.Actionand added an explicitOpKind. It deliberately leftFileOp.MergeStrategyas a plainstring("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 partial —
type MergeStrategy string+ four consts + anIsKeyMerge()method; retype theFileOpfield, the threestring-typed strategy params (render.IsKeyMerge,render.decodeDestObject,cli.decodeDestBytes/readDestFile,cli.readDestConfig) and convert at the twoKeyMergeStrategy()boundaries (render.orphanCleanupOps,cli.purgeAgentDests) — is ≈7 prod + 2 test files, ~40 lines, with theAdapterinterface 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-returningAdapter.KeyMergeStrategy()accessor through explicit conversions at every boundary — worse than either extreme. The full version retypesKeyMergeStrategy() string→KeyMergeStrategy() MergeStrategy, which changes the publishedAdapterinterface (docs/architecture.md§3,docs/components.md) implemented by all ten adapter packages and everygeneric.Specagent. That is its own change and should land as one commit withrender.IsKeyMergebecoming(MergeStrategy).IsKeyMerge().Scope when taken
type MergeStrategy stringwith the four consts andIsKeyMerge(); retypeFileOp.MergeStrategy,NewCleanupOp'sstrategyparam, the three strategy params and tworeadDest*gates listed above.Adapter.KeyMergeStrategy() MergeStrategyacross all adapter packages +generic.docs/architecture.md§3 interface block,docs/components.md, CHANGELOG (Internal).keyOpconstructor line; zero goldens.TestEveryCleanupLiteralUsesNewCleanupOp(internal/adapter/cleanupop_guard_test.go,hasCleanupShape) exempts a whole-file{}write only whenMergeStrategyis 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).