hivecode-mcp 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/HIVE_RULES.md +98 -98
  2. package/LICENSE +21 -21
  3. package/README.md +421 -421
  4. package/core.js +91 -91
  5. package/docs/ALWAYS_ON.md +310 -310
  6. package/docs/GITHUB_WORKFLOW.md +394 -394
  7. package/docs/HIVE_RULES.md +41 -41
  8. package/docs/MCP.md +157 -157
  9. package/docs/QUICKSTART.md +266 -266
  10. package/docs/SELF_HOSTING.md +256 -256
  11. package/docs/SLACK_SETUP.md +474 -450
  12. package/docs/TROUBLESHOOTING.md +105 -105
  13. package/hive-activity.js +336 -336
  14. package/hive-authors.js +46 -44
  15. package/hive-await.js +230 -230
  16. package/hive-branch.js +608 -607
  17. package/hive-busy.js +88 -88
  18. package/hive-coord.js +110 -110
  19. package/hive-gate.js +135 -135
  20. package/hive-github.js +2004 -1944
  21. package/hive-hook-install.js +179 -179
  22. package/hive-instructions.js +74 -74
  23. package/hive-land.js +695 -691
  24. package/hive-listen.js +81 -81
  25. package/hive-mailbox.js +520 -520
  26. package/hive-mcp-http.js +591 -580
  27. package/hive-mcp.js +2156 -2093
  28. package/hive-mux-proto.js +58 -58
  29. package/hive-mux.js +351 -350
  30. package/hive-notify.js +234 -233
  31. package/hive-obligations.js +247 -247
  32. package/hive-poll-proto.js +50 -50
  33. package/hive-pr-staleness.js +189 -189
  34. package/hive-pr.js +600 -512
  35. package/hive-privacy.js +327 -327
  36. package/hive-receipts.js +233 -233
  37. package/hive-relay-health.js +234 -230
  38. package/hive-repos.js +430 -430
  39. package/hive-room-key.js +210 -209
  40. package/hive-room-mode.js +1668 -1668
  41. package/hive-room-watcher.js +562 -561
  42. package/hive-secrets.js +89 -89
  43. package/hive-session.js +114 -114
  44. package/hive-setup.js +560 -558
  45. package/hive-slack-agent.js +408 -396
  46. package/hive-slack.js +2384 -1809
  47. package/hive-stdio.js +172 -172
  48. package/hive-stop-hook.js +293 -293
  49. package/hive-throttle.js +118 -118
  50. package/hive-time.js +71 -71
  51. package/hive-transport.js +578 -353
  52. package/hive-update.js +58 -58
  53. package/hive-version.js +110 -110
  54. package/hive-webhook.js +55 -54
  55. package/icr-merge.js +58 -58
  56. package/icr.js +851 -851
  57. package/lang-brace.js +305 -305
  58. package/lang-js.js +651 -651
  59. package/lang-json.js +211 -211
  60. package/lang-python.js +588 -588
  61. package/lang-ruby.js +230 -230
  62. package/lang-toml.js +136 -136
  63. package/lang-yaml.js +183 -183
  64. package/limits.js +413 -413
  65. package/package.json +192 -192
  66. package/reconcile.cjs +621 -614
  67. package/slack-app-manifest-agent.json +61 -61
  68. package/slack-app-manifest-inbound.json +36 -36
  69. package/slack-app-manifest.json +27 -27
  70. package/substrate.js +252 -252
  71. package/sync.js +5189 -4994
  72. package/token.js +201 -201
package/lang-brace.js CHANGED
@@ -1,305 +1,305 @@
1
- // Brace-family language provider for ICR — a dependency-free, language-agnostic
2
- // structural splitter for C-style languages (TypeScript, Go, Rust, Java, C/C++,
3
- // C#, Swift, Kotlin, Scala, PHP, Dart, …). It implements the ICR provider contract
4
- // { id, exts, parses, units, declaredNames, splitUnit, parsesUnit } so the SAME
5
- // merge engine in icr.js merges these languages by structure — different top-level
6
- // declarations merge cleanly, the same declaration edited two ways is a named
7
- // conflict, and edits to different members inside one class/function merge via the
8
- // engine's finer-grained inner merge (splitUnit).
9
- //
10
- // It is a heuristic scanner, not a full parser: it tracks strings, line/block
11
- // comments, and bracket depth to find top-level declarations and their byte ranges.
12
- // This gives real structural merge and an approximate parses() (balanced delimiters)
13
- // — honest and useful today; a tree-sitter provider can later replace it behind the
14
- // same interface with zero engine changes.
15
-
16
- const LINE = ['//']
17
- const OPEN = { '{': '}', '(': ')', '[': ']' }
18
- const CLOSE = { '}': '{', ')': '(', ']': '[' }
19
-
20
- // Walk `src` once. `at(i)` returns the index just past a string or comment starting
21
- // at i, or -1 if none starts there — so callers can skip over them uniformly.
22
- function scannerAt(src, strings) {
23
- const n = src.length
24
- return function at(i) {
25
- if (src[i] === '/' && src[i + 1] === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; return Math.min(n, j + 2) }
26
- for (const lc of LINE) if (src.startsWith(lc, i)) { let j = i + lc.length; while (j < n && src[j] !== '\n') j++; return j }
27
- if (strings.includes(src[i])) { const q = src[i]; let j = i + 1; while (j < n) { if (src[j] === '\\') { j += 2; continue } if (src[j] === q) { j++; break } j++ } return j }
28
- return -1
29
- }
30
- }
31
-
32
- const isWS = (c) => c === ' ' || c === '\t' || c === '\r' || c === '\n'
33
-
34
- // Tokens that CONTINUE a declaration onto the next line and can never begin a new top-level
35
- // one. Used by topUnits to avoid cutting a declaration in half at a newline.
36
- const CONT = /^(?:__attribute__|__asm__|__asm|__declspec|noexcept|throw\s*\(|->)/
37
-
38
- // Extract top-level units [{start,end}] from src[from..to]. A unit is a maximal
39
- // declaration/statement at bracket depth 0: either a block (signature + balanced
40
- // {…}) or a statement ending at ';' or end-of-line.
41
- function topUnits(src, strings, from = 0, to = src.length) {
42
- const at = scannerAt(src, strings)
43
- const units = []
44
- let i = from
45
- while (i < to) {
46
- while (i < to && isWS(src[i])) i++
47
- if (i >= to) break
48
-
49
- const start = i
50
- let depth = 0, sawBrace = false
51
- while (i < to) {
52
- const s = at(i)
53
- if (s > i) { i = s; continue } // skip string / comment
54
- const c = src[i]
55
- if (OPEN[c]) { if (c === '{') sawBrace = true; depth++; i++; continue }
56
- if (CLOSE[c]) { depth--; i++; if (depth <= 0 && sawBrace) { while (i < to && (src[i] === ' ' || src[i] === '\t')) i++; if (src[i] === ';') i++; break } continue }
57
- if (depth === 0) {
58
- if (c === ';') { i++; break }
59
- if (c === '\n' && !sawBrace) {
60
- // K&R / Allman / kernel style: the body's opening brace sits on the NEXT line, so
61
- // this newline is INSIDE a declaration, not the end of one. Breaking here split
62
- // every such function into two units — `stmt:<signature>` and `stmt:{` — with
63
- // POSITIONAL keys, so any insertion elsewhere shifted the indices and re-paired
64
- // body #N with signature #N+1. That silently produced code whose function bodies
65
- // belonged to other functions, while braces stayed balanced so the parse gate
66
- // passed. Measured at ~34% of real C merges before this fix.
67
- // Look ahead past whitespace and comments: if a block follows, it belongs to us.
68
- let k = i + 1
69
- for (;;) {
70
- while (k < to && isWS(src[k])) k++
71
- const s2 = at(k)
72
- if (s2 > k && (src[k] === '/' || src[k] === '#')) { k = s2; continue } // comment
73
- break
74
- }
75
- if (k < to && src[k] === '{') { i++; continue } // keep scanning; the block joins this unit
76
- // A declaration can also continue onto the next line WITHOUT a brace, e.g.
77
- // void addReplyErrorFormat(client *c, const char *fmt, ...)
78
- // __attribute__((format(printf, 2, 3)));
79
- // Splitting there produced an orphan `__attribute__…;` unit whose text is IDENTICAL
80
- // across many prototypes in a header, so the keys became positional (#2, #3, …) and
81
- // shifted exactly like the signature/body case — orphaning attributes onto the wrong
82
- // prototypes. Only tokens that can never BEGIN a new top-level declaration are listed
83
- // here: `const`/`override`/`final` are deliberately excluded because in TS/Rust they
84
- // legitimately start the next statement (the C++ uses of those end in `{`, which the
85
- // brace lookahead above already handles).
86
- if (k < to && CONT.test(src.slice(k, k + 16))) { i++; continue }
87
- break
88
- }
89
- }
90
- i++
91
- }
92
- units.push({ start, end: i })
93
- }
94
- return units
95
- }
96
-
97
- const DECL = /\b(function|func|fn|def|fun|class|interface|struct|enum|trait|impl|type|namespace|module|package|object|record|protocol|actor|extension)\s+([A-Za-z_$][\w$]*)/
98
- const VARD = /\b(const|let|var|val|static|final)\s+([A-Za-z_$][\w$]*)/
99
- // Method/function signature, including TYPE-FIRST forms (`int a()`, `public static
100
- // void main(String[] a)`, `std::string name()`): optional decorators + any number of
101
- // modifiers + an optional return-type token, then NAME(args){. The captured name is
102
- // the identifier directly before `(`.
103
- // The return-type group ends with `[\s*&]+` rather than `\s+` so a pointer return type whose
104
- // star is attached to the NAME still parses: `struct sk_buff *alloc_skb(int n)` must key as
105
- // `alloc_skb`, not fall through to DECL and key as the return type `sk_buff`.
106
- // The `Class::` group makes C++ out-of-line member definitions keyable — `int Widget::render(…)`
107
- // keys as `render`. Without it every C++ method definition fell through to an unkeyed `stmt:`.
108
- const METH = /^(?:@[\w.()]+\s+)*(?:(?:public|private|protected|internal|static|async|final|override|open|pub|suspend|inline|virtual|export|default|abstract|sealed|unsafe|extern|synchronized|native|constexpr)\s+)*(?:(?:function|fn|func|fun)\s+)?(?:[A-Za-z_$][\w$<>\[\].,:&*\s]*?[\s*&]+)?(?:[A-Za-z_$][\w$]*\s*::\s*)*([A-Za-z_$][\w$]*)\s*(?:<[^>]*>)?\s*\([^;{]*\)(?:\s*(?:const|noexcept|override|final|volatile|mutable|&&?|throw\s*\([^){]*\)))*\s*(?:->[^{]+|:[^={]+)?\s*\{/
109
- // control-flow headers also look like `name (…) {` — never key them as methods
110
- const CTRL = new Set(['if', 'for', 'while', 'switch', 'catch', 'do', 'else', 'try', 'return', 'new', 'throw', 'match', 'select', 'when', 'until', 'lock', 'using', 'foreach', 'with'])
111
-
112
- function keyOf(text) {
113
- const head = text.replace(/^\s+/, '')
114
- // DECL and METH can both match one declaration, and which is RIGHT depends on whether they
115
- // agree on the name:
116
- // `function a() {` -> both say "a" -> keep DECL's richer `function:a`
117
- // `struct sk_buff *alloc_skb(` -> DECL says "sk_buff",
118
- // METH says "alloc_skb" -> DECL matched the RETURN TYPE,
119
- // so METH wins and the key names the function.
120
- // Preferring DECL unconditionally (the original order) meant every pointer-returning C
121
- // function was keyed by its return type, feeding the rename/dangling logic wrong names.
122
- const meth = head.match(METH)
123
- const decl = head.match(DECL)
124
- if (meth && !CTRL.has(meth[1])) {
125
- if (decl && decl[2] === meth[1]) return decl[1] + ':' + decl[2]
126
- return 'method:' + meth[1]
127
- }
128
- if (decl) return decl[1] + ':' + decl[2]
129
- const m = head.match(VARD); if (m) return 'var:' + m[2]
130
- return 'stmt:' + head.split('\n')[0].trim().replace(/\s+/g, ' ')
131
- }
132
-
133
- // De-duplicate keys deterministically (repeated statements / same-named units) so the
134
- // engine's keyed merge never silently drops one. Order is stable → convergent.
135
- function keyedUnits(raw, src) {
136
- const seen = new Map(), out = []
137
- for (const u of raw) {
138
- const text = src.slice(u.start, u.end)
139
- let k = keyOf(text)
140
- const c = (seen.get(k) || 0) + 1; seen.set(k, c)
141
- if (c > 1) k += '#' + c
142
- out.push({ key: k, text, start: u.start, end: u.end })
143
- }
144
- return out
145
- }
146
-
147
- // Generic lexer for the token-level inner merge + the intent layer. Tokens are
148
- // identifiers, numbers, whole strings, and single operator chars; comments live in
149
- // the gaps (same contract as the JS provider: a comment-only edit is a no-op here).
150
- // Returns [{start,end,k}] — k is the raw text, so any content change is a token change.
151
- function lexTokens(src, strings) {
152
- const at = scannerAt(src, strings)
153
- const out = []
154
- const n = src.length
155
- let i = 0
156
- while (i < n) {
157
- if (isWS(src[i])) { i++; continue }
158
- const j = at(i)
159
- if (j > i) {
160
- if (src[i] === '/' || src[i] === '#') { i = j; continue } // comment → gap
161
- out.push({ start: i, end: j, k: src.slice(i, j) }); i = j; continue // string → one token
162
- }
163
- const c = src[i]
164
- if (/[A-Za-z_$]/.test(c)) { let e = i + 1; while (e < n && /[\w$]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
165
- if (/[0-9]/.test(c)) { let e = i + 1; while (e < n && /[\w.]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
166
- out.push({ start: i, end: i + 1, k: c }); i++
167
- }
168
- return out
169
- }
170
-
171
- // Is this token a REFERENCE-position identifier (not property access `.name`)?
172
- function isRefToken(src, t) {
173
- if (!/^[A-Za-z_$][\w$]*$/.test(t.k)) return false
174
- let p = t.start - 1
175
- while (p >= 0 && isWS(src[p])) p--
176
- return !(p >= 0 && src[p] === '.')
177
- }
178
-
179
- // Approximate parse check: delimiters balanced and no unterminated string/comment,
180
- // ignoring the contents of strings and comments.
181
- function balanced(src, strings) {
182
- const at = scannerAt(src, strings)
183
- const stack = []
184
- let cpp = 0 // #if/#ifdef/#ifndef nesting depth
185
- let lineStart = true // only whitespace seen so far on this line
186
- let i = 0
187
- while (i < src.length) {
188
- const s = at(i)
189
- if (s > i) { if (s > src.length) return false; i = s; lineStart = false; continue }
190
- const c = src[i]
191
- if (OPEN[c]) stack.push(c)
192
- else if (CLOSE[c]) { if (stack.pop() !== CLOSE[c]) return false }
193
- // PREPROCESSOR NESTING. Brace balance alone cannot see a dropped `#endif`: the merged
194
- // text stays perfectly brace-balanced while the conditional structure is broken, so the
195
- // gate passed and invalid C shipped as clean. Directives are matched only at the start
196
- // of a line, so `#[derive]`, `#region` and `#` inside strings/comments are unaffected
197
- // (strings and comments are skipped by the scanner above).
198
- else if (c === '#' && lineStart) {
199
- const rest = src.slice(i + 1, i + 12).replace(/^\s+/, '')
200
- if (/^(ifdef|ifndef|if)\b/.test(rest)) cpp++
201
- else if (/^endif\b/.test(rest)) { cpp--; if (cpp < 0) return false }
202
- }
203
- if (c === '\n') lineStart = true
204
- else if (c !== ' ' && c !== '\t' && c !== '\r') lineStart = false
205
- i++
206
- }
207
- return stack.length === 0 && cpp === 0
208
- }
209
-
210
- export function makeBraceLanguage({ id, exts, strings = ['"', "'", '`'] }) {
211
- const provider = {
212
- id,
213
- exts,
214
- parses: (src) => balanced(String(src == null ? '' : src), strings),
215
- units: (src) => keyedUnits(topUnits(String(src), strings), String(src)),
216
- declaredNames: (src) => {
217
- const names = new Set()
218
- for (const u of keyedUnits(topUnits(String(src), strings), String(src))) {
219
- const m = u.key.match(/^(?:[a-z]+):([A-Za-z_$][\w$]*)/)
220
- if (m && !u.key.startsWith('stmt:')) names.add(m[1])
221
- }
222
- return names
223
- },
224
- parsesUnit: (src) => balanced(String(src), strings),
225
- // --- intent layer (heuristic but honest): dangling refs, rename, token merge ---
226
- // Every identifier used in reference position (skips `.property` access). Approximate
227
- // and OVER-inclusive — which only ever makes the dangling check MORE cautious: a
228
- // deleted-but-still-referenced name is surfaced as a conflict, never shipped broken.
229
- usedIdentifiers: (src) => {
230
- src = String(src)
231
- const used = new Set()
232
- for (const t of lexTokens(src, strings)) if (isRefToken(src, t)) used.add(t.k)
233
- return used
234
- },
235
- // The declaration's text with its own name excluded, so two declarations that differ
236
- // only in their name compare equal — that's how the engine spots a rename.
237
- declBody: (src, name) => {
238
- src = String(src)
239
- for (const u of keyedUnits(topUnits(src, strings), src)) {
240
- const m = u.key.match(/^([a-z]+):(.+?)(?:#\d+)?$/)
241
- if (!m || m[1] === 'stmt' || m[2] !== name) continue
242
- return u.text.replace(name, '').trimEnd() // trailing whitespace must not defeat the rename match
243
- }
244
- return null
245
- },
246
- // Rewrite reference-position uses of oldName (never `.oldName` property access).
247
- // Back-to-front so offsets stay valid. The engine only accepts the rewrite if the
248
- // result still passes parses(), so a bad rewrite can't ship.
249
- renameRefs: (src, oldName, newName) => {
250
- src = String(src)
251
- const spots = []
252
- for (const t of lexTokens(src, strings)) if (t.k === oldName && isRefToken(src, t)) spots.push(t)
253
- let out = src
254
- for (const t of spots.reverse()) out = out.slice(0, t.start) + newName + out.slice(t.end)
255
- return out
256
- },
257
- // Finest granularity: token stream for the engine's token-level inner merge.
258
- tokenize: (src) => lexTokens(String(src), strings),
259
- // Finer granularity: descend one level into a block declaration so two agents
260
- // editing DIFFERENT members of the same class/function merge instead of clashing.
261
- splitUnit: (text) => {
262
- const src = String(text)
263
- // Only descend into NAMED containers (class/struct/impl/…), whose members are
264
- // keyed by name — so editing different members merges. A function/method body is
265
- // just statements (no stable keys); descending there would falsely merge two
266
- // edits to the same code, so we DON'T — the whole unit becomes a clean conflict.
267
- if (!/\b(class|interface|struct|enum|trait|impl|namespace|module|object|record|extension|protocol|actor)\b/.test(src.split('{')[0]))
268
- return null
269
- const at = scannerAt(src, strings)
270
- let i = 0, open = -1
271
- while (i < src.length) { const s = at(i); if (s > i) { i = s; continue } if (src[i] === '{') { open = i; break } i++ }
272
- if (open < 0) return null
273
- // find matching close
274
- let depth = 0, j = open, close = -1
275
- while (j < src.length) { const s = at(j); if (s > j) { j = s; continue } const c = src[j]; if (c === '{') depth++; else if (c === '}') { depth--; if (depth === 0) { close = j; break } } j++ }
276
- if (close < 0) return null
277
- const sig = src.slice(0, open)
278
- const body = src.slice(open + 1, close)
279
- const inner = keyedUnits(topUnits(body, strings), body)
280
- // inner ranges are already in `body` coordinates (topUnits scanned body). spliceable:
281
- // the engine rebuilds the body by splicing merged members back into it, so the class's
282
- // original indentation and any comments between members survive (no brace-glue reflow).
283
- return { sig, open: '{', close: src.slice(close), body, units: inner, join: '\n', spliceable: true }
284
- },
285
- }
286
- return provider
287
- }
288
-
289
- // Ready-made providers covering the common C-family languages. One provider can claim
290
- // many extensions; comment/string syntax defaults fit the whole family (edge cases like
291
- // Rust raw strings / lifetimes are tolerated — worst case a unit is merged coarsely).
292
- export const braceLanguages = [
293
- makeBraceLanguage({ id: 'ts', exts: ['.ts', '.tsx', '.mts', '.cts'] }),
294
- makeBraceLanguage({ id: 'jsx', exts: ['.jsx'] }),
295
- makeBraceLanguage({ id: 'go', exts: ['.go'], strings: ['"', '`'] }),
296
- makeBraceLanguage({ id: 'rust', exts: ['.rs'] }),
297
- makeBraceLanguage({ id: 'java', exts: ['.java'] }),
298
- makeBraceLanguage({ id: 'c', exts: ['.c', '.h', '.cpp', '.hpp', '.cc', '.hh', '.cxx'] }),
299
- makeBraceLanguage({ id: 'csharp', exts: ['.cs'] }),
300
- makeBraceLanguage({ id: 'swift', exts: ['.swift'] }),
301
- makeBraceLanguage({ id: 'kotlin', exts: ['.kt', '.kts'] }),
302
- makeBraceLanguage({ id: 'scala', exts: ['.scala'] }),
303
- makeBraceLanguage({ id: 'php', exts: ['.php'] }),
304
- makeBraceLanguage({ id: 'dart', exts: ['.dart'] }),
305
- ]
1
+ // Brace-family language provider for ICR — a dependency-free, language-agnostic
2
+ // structural splitter for C-style languages (TypeScript, Go, Rust, Java, C/C++,
3
+ // C#, Swift, Kotlin, Scala, PHP, Dart, …). It implements the ICR provider contract
4
+ // { id, exts, parses, units, declaredNames, splitUnit, parsesUnit } so the SAME
5
+ // merge engine in icr.js merges these languages by structure — different top-level
6
+ // declarations merge cleanly, the same declaration edited two ways is a named
7
+ // conflict, and edits to different members inside one class/function merge via the
8
+ // engine's finer-grained inner merge (splitUnit).
9
+ //
10
+ // It is a heuristic scanner, not a full parser: it tracks strings, line/block
11
+ // comments, and bracket depth to find top-level declarations and their byte ranges.
12
+ // This gives real structural merge and an approximate parses() (balanced delimiters)
13
+ // — honest and useful today; a tree-sitter provider can later replace it behind the
14
+ // same interface with zero engine changes.
15
+
16
+ const LINE = ['//']
17
+ const OPEN = { '{': '}', '(': ')', '[': ']' }
18
+ const CLOSE = { '}': '{', ')': '(', ']': '[' }
19
+
20
+ // Walk `src` once. `at(i)` returns the index just past a string or comment starting
21
+ // at i, or -1 if none starts there — so callers can skip over them uniformly.
22
+ function scannerAt(src, strings) {
23
+ const n = src.length
24
+ return function at(i) {
25
+ if (src[i] === '/' && src[i + 1] === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; return Math.min(n, j + 2) }
26
+ for (const lc of LINE) if (src.startsWith(lc, i)) { let j = i + lc.length; while (j < n && src[j] !== '\n') j++; return j }
27
+ if (strings.includes(src[i])) { const q = src[i]; let j = i + 1; while (j < n) { if (src[j] === '\\') { j += 2; continue } if (src[j] === q) { j++; break } j++ } return j }
28
+ return -1
29
+ }
30
+ }
31
+
32
+ const isWS = (c) => c === ' ' || c === '\t' || c === '\r' || c === '\n'
33
+
34
+ // Tokens that CONTINUE a declaration onto the next line and can never begin a new top-level
35
+ // one. Used by topUnits to avoid cutting a declaration in half at a newline.
36
+ const CONT = /^(?:__attribute__|__asm__|__asm|__declspec|noexcept|throw\s*\(|->)/
37
+
38
+ // Extract top-level units [{start,end}] from src[from..to]. A unit is a maximal
39
+ // declaration/statement at bracket depth 0: either a block (signature + balanced
40
+ // {…}) or a statement ending at ';' or end-of-line.
41
+ function topUnits(src, strings, from = 0, to = src.length) {
42
+ const at = scannerAt(src, strings)
43
+ const units = []
44
+ let i = from
45
+ while (i < to) {
46
+ while (i < to && isWS(src[i])) i++
47
+ if (i >= to) break
48
+
49
+ const start = i
50
+ let depth = 0, sawBrace = false
51
+ while (i < to) {
52
+ const s = at(i)
53
+ if (s > i) { i = s; continue } // skip string / comment
54
+ const c = src[i]
55
+ if (OPEN[c]) { if (c === '{') sawBrace = true; depth++; i++; continue }
56
+ if (CLOSE[c]) { depth--; i++; if (depth <= 0 && sawBrace) { while (i < to && (src[i] === ' ' || src[i] === '\t')) i++; if (src[i] === ';') i++; break } continue }
57
+ if (depth === 0) {
58
+ if (c === ';') { i++; break }
59
+ if (c === '\n' && !sawBrace) {
60
+ // K&R / Allman / kernel style: the body's opening brace sits on the NEXT line, so
61
+ // this newline is INSIDE a declaration, not the end of one. Breaking here split
62
+ // every such function into two units — `stmt:<signature>` and `stmt:{` — with
63
+ // POSITIONAL keys, so any insertion elsewhere shifted the indices and re-paired
64
+ // body #N with signature #N+1. That silently produced code whose function bodies
65
+ // belonged to other functions, while braces stayed balanced so the parse gate
66
+ // passed. Measured at ~34% of real C merges before this fix.
67
+ // Look ahead past whitespace and comments: if a block follows, it belongs to us.
68
+ let k = i + 1
69
+ for (;;) {
70
+ while (k < to && isWS(src[k])) k++
71
+ const s2 = at(k)
72
+ if (s2 > k && (src[k] === '/' || src[k] === '#')) { k = s2; continue } // comment
73
+ break
74
+ }
75
+ if (k < to && src[k] === '{') { i++; continue } // keep scanning; the block joins this unit
76
+ // A declaration can also continue onto the next line WITHOUT a brace, e.g.
77
+ // void addReplyErrorFormat(client *c, const char *fmt, ...)
78
+ // __attribute__((format(printf, 2, 3)));
79
+ // Splitting there produced an orphan `__attribute__…;` unit whose text is IDENTICAL
80
+ // across many prototypes in a header, so the keys became positional (#2, #3, …) and
81
+ // shifted exactly like the signature/body case — orphaning attributes onto the wrong
82
+ // prototypes. Only tokens that can never BEGIN a new top-level declaration are listed
83
+ // here: `const`/`override`/`final` are deliberately excluded because in TS/Rust they
84
+ // legitimately start the next statement (the C++ uses of those end in `{`, which the
85
+ // brace lookahead above already handles).
86
+ if (k < to && CONT.test(src.slice(k, k + 16))) { i++; continue }
87
+ break
88
+ }
89
+ }
90
+ i++
91
+ }
92
+ units.push({ start, end: i })
93
+ }
94
+ return units
95
+ }
96
+
97
+ const DECL = /\b(function|func|fn|def|fun|class|interface|struct|enum|trait|impl|type|namespace|module|package|object|record|protocol|actor|extension)\s+([A-Za-z_$][\w$]*)/
98
+ const VARD = /\b(const|let|var|val|static|final)\s+([A-Za-z_$][\w$]*)/
99
+ // Method/function signature, including TYPE-FIRST forms (`int a()`, `public static
100
+ // void main(String[] a)`, `std::string name()`): optional decorators + any number of
101
+ // modifiers + an optional return-type token, then NAME(args){. The captured name is
102
+ // the identifier directly before `(`.
103
+ // The return-type group ends with `[\s*&]+` rather than `\s+` so a pointer return type whose
104
+ // star is attached to the NAME still parses: `struct sk_buff *alloc_skb(int n)` must key as
105
+ // `alloc_skb`, not fall through to DECL and key as the return type `sk_buff`.
106
+ // The `Class::` group makes C++ out-of-line member definitions keyable — `int Widget::render(…)`
107
+ // keys as `render`. Without it every C++ method definition fell through to an unkeyed `stmt:`.
108
+ const METH = /^(?:@[\w.()]+\s+)*(?:(?:public|private|protected|internal|static|async|final|override|open|pub|suspend|inline|virtual|export|default|abstract|sealed|unsafe|extern|synchronized|native|constexpr)\s+)*(?:(?:function|fn|func|fun)\s+)?(?:[A-Za-z_$][\w$<>\[\].,:&*\s]*?[\s*&]+)?(?:[A-Za-z_$][\w$]*\s*::\s*)*([A-Za-z_$][\w$]*)\s*(?:<[^>]*>)?\s*\([^;{]*\)(?:\s*(?:const|noexcept|override|final|volatile|mutable|&&?|throw\s*\([^){]*\)))*\s*(?:->[^{]+|:[^={]+)?\s*\{/
109
+ // control-flow headers also look like `name (…) {` — never key them as methods
110
+ const CTRL = new Set(['if', 'for', 'while', 'switch', 'catch', 'do', 'else', 'try', 'return', 'new', 'throw', 'match', 'select', 'when', 'until', 'lock', 'using', 'foreach', 'with'])
111
+
112
+ function keyOf(text) {
113
+ const head = text.replace(/^\s+/, '')
114
+ // DECL and METH can both match one declaration, and which is RIGHT depends on whether they
115
+ // agree on the name:
116
+ // `function a() {` -> both say "a" -> keep DECL's richer `function:a`
117
+ // `struct sk_buff *alloc_skb(` -> DECL says "sk_buff",
118
+ // METH says "alloc_skb" -> DECL matched the RETURN TYPE,
119
+ // so METH wins and the key names the function.
120
+ // Preferring DECL unconditionally (the original order) meant every pointer-returning C
121
+ // function was keyed by its return type, feeding the rename/dangling logic wrong names.
122
+ const meth = head.match(METH)
123
+ const decl = head.match(DECL)
124
+ if (meth && !CTRL.has(meth[1])) {
125
+ if (decl && decl[2] === meth[1]) return decl[1] + ':' + decl[2]
126
+ return 'method:' + meth[1]
127
+ }
128
+ if (decl) return decl[1] + ':' + decl[2]
129
+ const m = head.match(VARD); if (m) return 'var:' + m[2]
130
+ return 'stmt:' + head.split('\n')[0].trim().replace(/\s+/g, ' ')
131
+ }
132
+
133
+ // De-duplicate keys deterministically (repeated statements / same-named units) so the
134
+ // engine's keyed merge never silently drops one. Order is stable → convergent.
135
+ function keyedUnits(raw, src) {
136
+ const seen = new Map(), out = []
137
+ for (const u of raw) {
138
+ const text = src.slice(u.start, u.end)
139
+ let k = keyOf(text)
140
+ const c = (seen.get(k) || 0) + 1; seen.set(k, c)
141
+ if (c > 1) k += '#' + c
142
+ out.push({ key: k, text, start: u.start, end: u.end })
143
+ }
144
+ return out
145
+ }
146
+
147
+ // Generic lexer for the token-level inner merge + the intent layer. Tokens are
148
+ // identifiers, numbers, whole strings, and single operator chars; comments live in
149
+ // the gaps (same contract as the JS provider: a comment-only edit is a no-op here).
150
+ // Returns [{start,end,k}] — k is the raw text, so any content change is a token change.
151
+ function lexTokens(src, strings) {
152
+ const at = scannerAt(src, strings)
153
+ const out = []
154
+ const n = src.length
155
+ let i = 0
156
+ while (i < n) {
157
+ if (isWS(src[i])) { i++; continue }
158
+ const j = at(i)
159
+ if (j > i) {
160
+ if (src[i] === '/' || src[i] === '#') { i = j; continue } // comment → gap
161
+ out.push({ start: i, end: j, k: src.slice(i, j) }); i = j; continue // string → one token
162
+ }
163
+ const c = src[i]
164
+ if (/[A-Za-z_$]/.test(c)) { let e = i + 1; while (e < n && /[\w$]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
165
+ if (/[0-9]/.test(c)) { let e = i + 1; while (e < n && /[\w.]/.test(src[e])) e++; out.push({ start: i, end: e, k: src.slice(i, e) }); i = e; continue }
166
+ out.push({ start: i, end: i + 1, k: c }); i++
167
+ }
168
+ return out
169
+ }
170
+
171
+ // Is this token a REFERENCE-position identifier (not property access `.name`)?
172
+ function isRefToken(src, t) {
173
+ if (!/^[A-Za-z_$][\w$]*$/.test(t.k)) return false
174
+ let p = t.start - 1
175
+ while (p >= 0 && isWS(src[p])) p--
176
+ return !(p >= 0 && src[p] === '.')
177
+ }
178
+
179
+ // Approximate parse check: delimiters balanced and no unterminated string/comment,
180
+ // ignoring the contents of strings and comments.
181
+ function balanced(src, strings) {
182
+ const at = scannerAt(src, strings)
183
+ const stack = []
184
+ let cpp = 0 // #if/#ifdef/#ifndef nesting depth
185
+ let lineStart = true // only whitespace seen so far on this line
186
+ let i = 0
187
+ while (i < src.length) {
188
+ const s = at(i)
189
+ if (s > i) { if (s > src.length) return false; i = s; lineStart = false; continue }
190
+ const c = src[i]
191
+ if (OPEN[c]) stack.push(c)
192
+ else if (CLOSE[c]) { if (stack.pop() !== CLOSE[c]) return false }
193
+ // PREPROCESSOR NESTING. Brace balance alone cannot see a dropped `#endif`: the merged
194
+ // text stays perfectly brace-balanced while the conditional structure is broken, so the
195
+ // gate passed and invalid C shipped as clean. Directives are matched only at the start
196
+ // of a line, so `#[derive]`, `#region` and `#` inside strings/comments are unaffected
197
+ // (strings and comments are skipped by the scanner above).
198
+ else if (c === '#' && lineStart) {
199
+ const rest = src.slice(i + 1, i + 12).replace(/^\s+/, '')
200
+ if (/^(ifdef|ifndef|if)\b/.test(rest)) cpp++
201
+ else if (/^endif\b/.test(rest)) { cpp--; if (cpp < 0) return false }
202
+ }
203
+ if (c === '\n') lineStart = true
204
+ else if (c !== ' ' && c !== '\t' && c !== '\r') lineStart = false
205
+ i++
206
+ }
207
+ return stack.length === 0 && cpp === 0
208
+ }
209
+
210
+ export function makeBraceLanguage({ id, exts, strings = ['"', "'", '`'] }) {
211
+ const provider = {
212
+ id,
213
+ exts,
214
+ parses: (src) => balanced(String(src == null ? '' : src), strings),
215
+ units: (src) => keyedUnits(topUnits(String(src), strings), String(src)),
216
+ declaredNames: (src) => {
217
+ const names = new Set()
218
+ for (const u of keyedUnits(topUnits(String(src), strings), String(src))) {
219
+ const m = u.key.match(/^(?:[a-z]+):([A-Za-z_$][\w$]*)/)
220
+ if (m && !u.key.startsWith('stmt:')) names.add(m[1])
221
+ }
222
+ return names
223
+ },
224
+ parsesUnit: (src) => balanced(String(src), strings),
225
+ // --- intent layer (heuristic but honest): dangling refs, rename, token merge ---
226
+ // Every identifier used in reference position (skips `.property` access). Approximate
227
+ // and OVER-inclusive — which only ever makes the dangling check MORE cautious: a
228
+ // deleted-but-still-referenced name is surfaced as a conflict, never shipped broken.
229
+ usedIdentifiers: (src) => {
230
+ src = String(src)
231
+ const used = new Set()
232
+ for (const t of lexTokens(src, strings)) if (isRefToken(src, t)) used.add(t.k)
233
+ return used
234
+ },
235
+ // The declaration's text with its own name excluded, so two declarations that differ
236
+ // only in their name compare equal — that's how the engine spots a rename.
237
+ declBody: (src, name) => {
238
+ src = String(src)
239
+ for (const u of keyedUnits(topUnits(src, strings), src)) {
240
+ const m = u.key.match(/^([a-z]+):(.+?)(?:#\d+)?$/)
241
+ if (!m || m[1] === 'stmt' || m[2] !== name) continue
242
+ return u.text.replace(name, '').trimEnd() // trailing whitespace must not defeat the rename match
243
+ }
244
+ return null
245
+ },
246
+ // Rewrite reference-position uses of oldName (never `.oldName` property access).
247
+ // Back-to-front so offsets stay valid. The engine only accepts the rewrite if the
248
+ // result still passes parses(), so a bad rewrite can't ship.
249
+ renameRefs: (src, oldName, newName) => {
250
+ src = String(src)
251
+ const spots = []
252
+ for (const t of lexTokens(src, strings)) if (t.k === oldName && isRefToken(src, t)) spots.push(t)
253
+ let out = src
254
+ for (const t of spots.reverse()) out = out.slice(0, t.start) + newName + out.slice(t.end)
255
+ return out
256
+ },
257
+ // Finest granularity: token stream for the engine's token-level inner merge.
258
+ tokenize: (src) => lexTokens(String(src), strings),
259
+ // Finer granularity: descend one level into a block declaration so two agents
260
+ // editing DIFFERENT members of the same class/function merge instead of clashing.
261
+ splitUnit: (text) => {
262
+ const src = String(text)
263
+ // Only descend into NAMED containers (class/struct/impl/…), whose members are
264
+ // keyed by name — so editing different members merges. A function/method body is
265
+ // just statements (no stable keys); descending there would falsely merge two
266
+ // edits to the same code, so we DON'T — the whole unit becomes a clean conflict.
267
+ if (!/\b(class|interface|struct|enum|trait|impl|namespace|module|object|record|extension|protocol|actor)\b/.test(src.split('{')[0]))
268
+ return null
269
+ const at = scannerAt(src, strings)
270
+ let i = 0, open = -1
271
+ while (i < src.length) { const s = at(i); if (s > i) { i = s; continue } if (src[i] === '{') { open = i; break } i++ }
272
+ if (open < 0) return null
273
+ // find matching close
274
+ let depth = 0, j = open, close = -1
275
+ while (j < src.length) { const s = at(j); if (s > j) { j = s; continue } const c = src[j]; if (c === '{') depth++; else if (c === '}') { depth--; if (depth === 0) { close = j; break } } j++ }
276
+ if (close < 0) return null
277
+ const sig = src.slice(0, open)
278
+ const body = src.slice(open + 1, close)
279
+ const inner = keyedUnits(topUnits(body, strings), body)
280
+ // inner ranges are already in `body` coordinates (topUnits scanned body). spliceable:
281
+ // the engine rebuilds the body by splicing merged members back into it, so the class's
282
+ // original indentation and any comments between members survive (no brace-glue reflow).
283
+ return { sig, open: '{', close: src.slice(close), body, units: inner, join: '\n', spliceable: true }
284
+ },
285
+ }
286
+ return provider
287
+ }
288
+
289
+ // Ready-made providers covering the common C-family languages. One provider can claim
290
+ // many extensions; comment/string syntax defaults fit the whole family (edge cases like
291
+ // Rust raw strings / lifetimes are tolerated — worst case a unit is merged coarsely).
292
+ export const braceLanguages = [
293
+ makeBraceLanguage({ id: 'ts', exts: ['.ts', '.tsx', '.mts', '.cts'] }),
294
+ makeBraceLanguage({ id: 'jsx', exts: ['.jsx'] }),
295
+ makeBraceLanguage({ id: 'go', exts: ['.go'], strings: ['"', '`'] }),
296
+ makeBraceLanguage({ id: 'rust', exts: ['.rs'] }),
297
+ makeBraceLanguage({ id: 'java', exts: ['.java'] }),
298
+ makeBraceLanguage({ id: 'c', exts: ['.c', '.h', '.cpp', '.hpp', '.cc', '.hh', '.cxx'] }),
299
+ makeBraceLanguage({ id: 'csharp', exts: ['.cs'] }),
300
+ makeBraceLanguage({ id: 'swift', exts: ['.swift'] }),
301
+ makeBraceLanguage({ id: 'kotlin', exts: ['.kt', '.kts'] }),
302
+ makeBraceLanguage({ id: 'scala', exts: ['.scala'] }),
303
+ makeBraceLanguage({ id: 'php', exts: ['.php'] }),
304
+ makeBraceLanguage({ id: 'dart', exts: ['.dart'] }),
305
+ ]