cgcardona / muse public
muse-attributes.md markdown
345 lines 10.7 KB
12559ad7 feat: supercharge .museattributes — base/union strategies, priority, co… Gabriel Cardona <cgcardona@gmail.com> 1d ago
1 # `.museattributes` Reference
2
3 > **Format:** TOML · **Location:** repository root (next to `.muse/`)
4 > **Loaded by:** `muse merge`, `muse cherry-pick`, `muse attributes`
5 > **Updated:** v0.1.2 — added `base` strategy, `comment` and `priority` fields,
6 > priority-based rule ordering, and full code-domain support.
7
8 `.museattributes` declares per-path, per-dimension merge strategy overrides for
9 a Muse repository. It uses TOML syntax for consistency with `.muse/config.toml`
10 and to allow richer structure (comments, typed values, named sections).
11
12 The file is domain-agnostic — the same format works for MIDI, code, genomics,
13 3D design, scientific simulation, or any future domain.
14
15 ---
16
17 ## File Structure
18
19 ```toml
20 # .museattributes
21 # Merge strategy overrides for this repository.
22 # Documentation: docs/reference/muse-attributes.md
23
24 [meta]
25 domain = "midi" # optional — validated against .muse/repo.json "domain"
26
27 [[rules]]
28 path = "drums/*" # fnmatch glob
29 dimension = "*" # domain axis, or "*" for any
30 strategy = "ours" # resolution strategy
31 comment = "Drums are always authored on this branch."
32 priority = 20 # higher = evaluated first
33
34 [[rules]]
35 path = "keys/*"
36 dimension = "pitch_bend"
37 strategy = "theirs"
38 comment = "Remote always has the better pitch-bend automation."
39 priority = 15
40
41 [[rules]]
42 path = "*"
43 dimension = "*"
44 strategy = "auto"
45 ```
46
47 ---
48
49 ## Sections
50
51 ### `[meta]` (optional)
52
53 | Key | Type | Description |
54 |-----|------|-------------|
55 | `domain` | string | The domain this file targets. When present, validated against `.muse/repo.json "domain"`. A mismatch logs a warning but does not abort. |
56
57 `[meta]` has no effect on merge resolution. It provides a machine-readable
58 declaration of the intended domain, enabling tooling to warn when rules may be
59 targeting the wrong plugin.
60
61 ---
62
63 ### `[[rules]]` (array)
64
65 Each `[[rules]]` entry is a single merge strategy rule.
66
67 | Field | Type | Required | Default | Description |
68 |-------|------|----------|---------|-------------|
69 | `path` | string | **yes** | — | `fnmatch` glob matched against workspace-relative POSIX paths (e.g. `"drums/*"`, `"src/**/*.py"`). |
70 | `dimension` | string | **yes** | — | Domain axis name (e.g. `"pitch_bend"`, `"symbols"`) or `"*"` to match any dimension. |
71 | `strategy` | string | **yes** | — | One of the six strategies (see below). |
72 | `comment` | string | no | `""` | Free-form documentation explaining *why* the rule exists. Ignored at runtime. |
73 | `priority` | integer | no | `0` | Ordering weight. Higher-priority rules are evaluated before lower-priority ones, regardless of their position in the file. Ties preserve declaration order. |
74
75 ---
76
77 ## Strategies
78
79 | Strategy | Behaviour |
80 |----------|-----------|
81 | `auto` | **Default.** Defer to the three-way merge engine. Unmatched paths always use this. |
82 | `ours` | Take the current-branch (left) version. Remove the path from the conflict list. |
83 | `theirs` | Take the incoming-branch (right) version. Remove the path from the conflict list. |
84 | `union` | Include **all** additions from both sides. Deletions are honoured only when both sides agree. Best for independent element sets (MIDI notes, symbol additions, import sets, genomic mutations). Falls back to `ours` for binary blobs where full unification is impossible. |
85 | `base` | Revert to the **common merge-base version** — discard changes from *both* branches. Use this for generated files, lock files, or any path that must stay at a known-good state during a merge. |
86 | `manual` | Force the path into the conflict list for human review, even when the engine would auto-resolve it. |
87
88 ---
89
90 ## Rule Evaluation Order
91
92 Rules are sorted by `priority` (descending) then by declaration order (ascending)
93 before evaluation. The **first matching rule wins**.
94
95 ```
96 Rule evaluation order = sort by -priority, then by file position
97 ```
98
99 This means:
100
101 - A `priority = 100` rule declared *anywhere* in the file always beats a
102 `priority = 0` catch-all, no matter where either appears.
103 - Rules with equal `priority` preserve the order they were written in.
104 - When no rule matches, the strategy falls back to `"auto"`.
105
106 **Recommended pattern:** assign high `priority` values to narrow, safety-critical
107 rules (secrets, generated files, master tracks); assign `priority = 0` to your
108 broad catch-all rule.
109
110 ---
111
112 ## Matching Rules
113
114 - **Path matching** uses Python's `fnmatch.fnmatch()`. Patterns are matched
115 against workspace-relative POSIX path strings (forward slashes, no leading `/`).
116 `*` matches within a directory segment; `**` matches across segments.
117 - **Dimension matching**: `"*"` in the `dimension` field matches any dimension.
118 A named dimension (e.g. `"pitch_bend"`) matches only that exact name.
119 - **When the caller passes `dimension="*"`**, any rule dimension matches — this is
120 used when issuing a file-level strategy query that does not target a specific axis.
121
122 ---
123
124 ## Domain Integration
125
126 ### MIDI domain
127
128 Rules are applied at two levels:
129
130 1. **File level** — strategy resolved against the full file path and `dimension="*"`.
131 `ours` / `theirs` / `base` / `union` / `manual` all fire before any MIDI-specific
132 processing.
133 2. **Dimension level** — for `.mid` files not resolved at file level,
134 `merge_midi_dimensions` checks the named dimension (e.g. `"notes"`,
135 `"pitch_bend"`) against the rule list. Dimension aliases (e.g. `"tempo"` for
136 `"tempo_map"`) are also matched.
137
138 **MIDI dimensions** (usable in `dimension`):
139 `notes`, `pitch_bend`, `channel_pressure`, `poly_pressure`,
140 `cc_modulation`, `cc_volume`, `cc_pan`, `cc_expression`,
141 `cc_sustain`, `cc_portamento`, `cc_sostenuto`, `cc_soft_pedal`,
142 `cc_reverb`, `cc_chorus`, `cc_other`, `program_change`,
143 `tempo_map`, `time_signatures`, `key_signatures`, `markers`,
144 `track_structure`
145
146 **Aliases**: `aftertouch`, `poly_aftertouch`, `modulation`, `volume`, `pan`,
147 `expression`, `sustain`, `portamento`, `sostenuto`, `soft_pedal`, `reverb`,
148 `chorus`, `automation`, `program`, `tempo`, `time_sig`, `key_sig`
149
150 **Non-independent dimensions** (`tempo_map`, `time_signatures`, `track_structure`):
151 a conflict in any of these blocks the entire file merge, because they are
152 structurally coupled.
153
154 ### Code domain
155
156 Rules are applied at two levels:
157
158 1. **File level** — inside `CodePlugin.merge()`, strategy resolved against each
159 file path and `dimension="*"`. All six strategies are fully implemented.
160 `manual` also fires on one-sided auto-resolved paths (i.e. a path where only
161 one branch changed — `manual` forces it into the conflict list anyway).
162 2. **Symbol level** — inside `CodePlugin.merge_ops()`, symbol-level conflict
163 addresses (`"src/utils.py::calculate_total"`) are checked by extracting the
164 file path and calling `resolve_strategy`. A `path = "src/**/*.py"` /
165 `strategy = "ours"` rule suppresses symbol-level conflicts inside those files,
166 not just file-level manifest conflicts.
167
168 **Code dimensions** (usable in `dimension`):
169 `structure`, `symbols`, `imports`, `variables`, `metadata`
170
171 ---
172
173 ## Examples
174
175 ### MIDI — drums always ours, pitch-bend from remote, union on stems
176
177 ```toml
178 [meta]
179 domain = "midi"
180
181 [[rules]]
182 path = "master.mid"
183 dimension = "*"
184 strategy = "manual"
185 comment = "Master track must always be reviewed by a human."
186 priority = 100
187
188 [[rules]]
189 path = "drums/*"
190 dimension = "*"
191 strategy = "ours"
192 comment = "Drum tracks are always authored on this branch."
193 priority = 20
194
195 [[rules]]
196 path = "keys/*.mid"
197 dimension = "pitch_bend"
198 strategy = "theirs"
199 comment = "Remote always has better pitch-bend automation."
200 priority = 15
201
202 [[rules]]
203 path = "stems/*"
204 dimension = "notes"
205 strategy = "union"
206 comment = "Combine note additions from both arrangers."
207
208 [[rules]]
209 path = "mixdown.mid"
210 dimension = "*"
211 strategy = "base"
212 comment = "Mixdown is generated — revert to ancestor during merge."
213
214 [[rules]]
215 path = "*"
216 dimension = "*"
217 strategy = "auto"
218 ```
219
220 ### Code — generated files reverted, test additions unioned, core reviewed
221
222 ```toml
223 [meta]
224 domain = "code"
225
226 [[rules]]
227 path = "config/secrets.*"
228 dimension = "*"
229 strategy = "manual"
230 comment = "Secrets require human review — never auto-merge."
231 priority = 100
232
233 [[rules]]
234 path = "src/generated/**"
235 dimension = "*"
236 strategy = "base"
237 comment = "Generated — always revert to ancestor; re-run codegen after merge."
238 priority = 30
239
240 [[rules]]
241 path = "src/core/**"
242 dimension = "*"
243 strategy = "manual"
244 comment = "Core changes need human review on every merge."
245 priority = 25
246
247 [[rules]]
248 path = "tests/**"
249 dimension = "symbols"
250 strategy = "union"
251 comment = "Test additions from both branches are always safe to combine."
252
253 [[rules]]
254 path = "src/**/*.py"
255 dimension = "imports"
256 strategy = "union"
257 comment = "Import sets are independent; accumulate additions from both sides."
258
259 [[rules]]
260 path = "package-lock.json"
261 dimension = "*"
262 strategy = "ours"
263 comment = "Lock file is managed by this branch's CI."
264
265 [[rules]]
266 path = "*"
267 dimension = "*"
268 strategy = "auto"
269 ```
270
271 ### Genomics — reference always ours, mutation sets unioned
272
273 ```toml
274 [meta]
275 domain = "genomics"
276
277 [[rules]]
278 path = "reference/*"
279 dimension = "*"
280 strategy = "ours"
281 comment = "Reference sequence is always maintained on main."
282 priority = 50
283
284 [[rules]]
285 path = "mutations/*"
286 dimension = "*"
287 strategy = "union"
288 comment = "Accumulate mutations from both experimental branches."
289
290 [[rules]]
291 path = "*"
292 dimension = "*"
293 strategy = "auto"
294 ```
295
296 ### Force manual review on all structural changes (any domain)
297
298 ```toml
299 [[rules]]
300 path = "*"
301 dimension = "track_structure"
302 strategy = "manual"
303 priority = 50
304
305 [[rules]]
306 path = "*"
307 dimension = "*"
308 strategy = "auto"
309 ```
310
311 ---
312
313 ## `applied_strategies` in MergeResult
314
315 After a merge, `MergeResult.applied_strategies` is a `dict[str, str]` mapping
316 each path (or symbol address for the code domain) where a `.museattributes` rule
317 fired to the strategy that was applied. The `muse merge` CLI prints this as:
318
319 ```
320 ✔ [ours] drums/kick.mid
321 ✔ [base] mixdown.mid
322 ✔ [union] stems/bass.mid
323 ✔ [manual] master.mid
324 ```
325
326 Paths resolved by the default `"auto"` strategy are not included — only explicit
327 overrides appear in the map.
328
329 ---
330
331 ## Generated Template
332
333 `muse init --domain <name>` writes a fully-commented template to the repository
334 root. The template documents all six strategies, all five rule fields, and
335 includes annotated examples for MIDI, code, and generic repositories.
336
337 ---
338
339 ## Related
340
341 - `.muse/config.toml` — per-repository user, auth, remote, and domain configuration
342 - `.museignore` — snapshot exclusion list (paths excluded from `muse commit`)
343 - `muse attributes` — CLI command to display current rules and `[meta]` domain
344 - `docs/reference/type-contracts.md` — `AttributeRule`, `MuseAttributesFile`, `MergeResult` TypedDict definitions
345 - `docs/reference/code-domain.md` — code domain schema and dimensions