| name | merge-behaviors |
| description | Merge two SPF behaviors into one with cleaned-shape-first discipline. Forces per-side standalone analysis and an explicit complexity-driven direction declaration before any combining happens ā avoids the "relocated mess" failure mode where the merge anchors on the current merged-file shape rather than the cleaned per-side shapes. Use after /refactor-behavior's decomposition check has concluded "merge," or to redo a merge that landed without the discipline. Triggers: "merge behaviors", "merge these behaviors", "combine behaviors", "redo merge", "merge X into Y". |
Merge two SPF Behaviors
A merge is two analyses combined, not one. The canonical failure
mode is starting from the merged file (or from the simpler side) and
extending outward to host the more complex case ā producing leaky
abstractions, conditional branches inside shared helpers, and
relocated-but-not-refactored bodies.
This skill enforces the inverse: per-side cleaned-shape sketches first,
direction declared from a complexity inventory, then combine.
Usage
/merge-behaviors <file-a> <file-b>
Or for a redo case (a merge that already landed without this discipline):
/merge-behaviors <merged-file>
For the redo case, identify the pre-merge inputs from git history (the
commit that performed the merge has both pre-merge files in its parent
tree).
Reference docs
The canonical references ā read them first:
When to use this skill vs. /refactor-behavior
| Situation | Skill |
|---|
| Cleaning up a single behavior file | /refactor-behavior |
Decomposition check (Step 6 of /refactor-behavior) concludes "this should merge with X" | /merge-behaviors |
| A merge already landed without per-side analysis (suspected "relocated mess") | /merge-behaviors |
| Splitting one behavior into two | /refactor-behavior (or open a design discussion first) |
Steps (do these in order; do not skip)
Step 1 ā Identify inputs
State the two pre-merge files (paths or, for a redo case, git ref +
filename pairs). If you're redoing a merge, identify the commit that
performed the merge and pull the pre-merge files from its parent tree.
Confirm with the user before proceeding if the inputs aren't obvious.
Step 2 ā Per-side standalone analysis
For each side independently, run Steps 1ā4 of /refactor-behavior:
- Articulate the purpose (1 sentence). Apply the purpose-verb
diagnostic from
behaviors.md Step 1 ā note whether the verbs are
one-shot, continuous, or "continuous + gated + reset on X."
- List business rules.
- Gap analysis (lightweight ā just enough to know whether real issues
exist).
- Pattern selection.
The per-side analysis is exactly what /refactor-behavior does up to
Step 4; you're running it twice in parallel.
Don't refactor pre-emptively. If a side's gap analysis is empty or
trivial ā the current code already conforms to current conventions ā
record that finding and move on. The next step (cleaned-shape sketch)
takes the current shape, not a projected cleaner one. Only when the
gap analysis turned up real issues do you project a cleaned shape; in
that case, recommend (don't perform) /refactor-behavior on that side
as an optional standalone exercise the user may opt into.
Step 3 ā Cleaned-shape sketch per side
Produce a sketch per side using the cleaned-shape sketch template in
behaviors.md:
- States:
- entry work:
- effects: (continuous reactivity):
- State-exit cleanup:
- Source-reset concerns:
- Private temporal state:
Both sketches must use the same structured format so they're directly
comparable.
If a side already conforms (Step 2 found no issues), the sketch
describes the current shape. If the side needed projecting, the sketch
is the projected post-refactor shape ā and the proposal should note
that landing the standalone refactor first (via /refactor-behavior)
is an option the user can choose.
Step 4 ā Complexity inventory + direction declaration
Apply the complexity inventory
to the two sketches. Count "yes" answers per side. Bias toward "yes"
ā under-counting is the failure mode.
State the direction explicitly:
Building from <constrained side>'s reactor; folding <simpler side>
into <where in the constrained reactor>.
Stop and confirm with the user before proceeding. Direction is the
load-bearing call. The user may override the inventory based on
context the inventory doesn't capture (e.g., "yes the ABR side scores
higher on the inventory but we plan to remove ABR next quarter, so
build around the simpler side"). This is the natural pause point.
Step 5 ā Combined cleaned-shape sketch
Show the merged shape using the same structured format as Step 3. The
combined sketch:
- Inherits states + reactor shape from the constrained side.
- Folds the simpler side's
entry/cleanup work into the host's
entry/cleanup (often as one extra line in entry, often as a
combined cleanup return).
- Carries the constrained side's
effects: unchanged.
- Names where private temporal state lives (typically signals scoped
to the variant's setup, reset in the host's
entry).
If the merged module has per-type specializations (the typical
select*Track-shaped case where some types have effects: and others
don't), show one combined sketch per per-type variant ā they may differ
in which fields are populated.
Step 6 ā Convention checks + decomposition note
Same as /refactor-behavior Steps 5ā6:
- Setup-shape helper signature (
({ state, config }) => cleanup)?
Helpers parameterized by what varies between variants (per-type
config), not by whether-an-optional-thing-is-on-or-off ā see the
leaky-abstraction sniff in reactors.md.
- Pure helpers extracted out of
playback/behaviors/?
- File placement, naming, file-level JSDoc?
- Decomposition note: after the merge, is the writer-count for the
shared slot now 1? Is there any other multi-writer arrangement on
the same slot that this merge does or doesn't address?
Step 7 ā Stop and confirm before code
Propose the changes; do not write code until the user confirms.
Output format
Propose changes in this order. Use markdown headers for each numbered
section. Mirror the per-side structure literally ā two parallel
sub-sections under Step 2 and Step 3, one combined under Step 5.
- Inputs (the two pre-merge files / git refs)
- Per-side standalone analysis ā for each side: purpose, rules,
gap analysis, pattern selection
- Cleaned-shape sketches ā per side, in the template format
- Complexity inventory + direction declaration ā explicit
inventory with counts, then the one-sentence direction statement.
Stop here for user confirmation.
- Combined cleaned-shape sketch (one or several if per-type
variants)
- Proposed changes (numbered list, each citing conventions docs)
- Decomposition note (writer-count, other multi-writer concerns)
Why this order
Two failure modes the order prevents:
- Anchoring on the merged file's current shape rather than per-side
cleaned shapes. Steps 2ā3 force per-side analysis first, before
anything about combination is contemplated.
- Inverting "build from the more constrained side" ā extending the
simpler shape outward to host the more complex one, producing leaky
abstractions and conditional branches inside helpers. Step 4's
explicit direction declaration (with the complexity inventory backing
it) makes the call auditable; the user-confirmation pause makes it
hard to silently invert.
A side benefit: the per-side cleaned-shape sketches are reusable. If
the user later decides to refactor each side standalone first (split
the merge into two PRs), the sketches are the design for those PRs.