hivecode-mcp 0.6.0 → 0.8.0

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.
package/lang-js.js ADDED
@@ -0,0 +1,418 @@
1
+ // lang-js.js — the JavaScript language provider for ICR.
2
+ //
3
+ // ICR's merge engine (icr.js) is language-agnostic: it merges a list of keyed "units"
4
+ // and asks intent questions ("what names are declared / used / what's a function body").
5
+ // Everything that actually knows about a *language* — how to parse it, what a top-level
6
+ // declaration is, how to find references — lives behind a provider like this one.
7
+ //
8
+ // To add a language (Python, Go, Rust…), write another module exposing the same shape
9
+ // and register it with icr.js's registerLanguage(). This JS provider is just the first
10
+ // plugin; it happens to use `acorn` because acorn is a tiny pure-JS parser. A tree-sitter
11
+ // provider would implement the identical interface over tree-sitter grammars.
12
+
13
+ import * as acorn from 'acorn'
14
+
15
+ // Parse permissively: try module syntax, fall back to script (so plain snippets and
16
+ // ESM both work). Throws only when the code genuinely won't parse either way.
17
+ function parse(src) {
18
+ try { return acorn.parse(src, { ecmaVersion: 'latest', sourceType: 'module' }) }
19
+ catch { return acorn.parse(src, { ecmaVersion: 'latest', sourceType: 'script' }) }
20
+ }
21
+
22
+ function parses(src) { try { parse(src); return true } catch { return false } }
23
+
24
+ // A stable key per top-level node — what lets us recognize "the same declaration"
25
+ // across two edits. Names where we have them; position as a last resort.
26
+ function keyOf(node, i) {
27
+ let n = node
28
+ if (n.type === 'ExportNamedDeclaration' && n.declaration) n = n.declaration
29
+ if (n.type === 'ExportDefaultDeclaration') return 'export:default'
30
+ if (n.type === 'FunctionDeclaration' && n.id) return 'fn:' + n.id.name
31
+ if (n.type === 'ClassDeclaration' && n.id) return 'class:' + n.id.name
32
+ if (n.type === 'VariableDeclaration' && n.declarations[0] && n.declarations[0].id && n.declarations[0].id.name)
33
+ return 'var:' + n.declarations[0].id.name
34
+ // Key imports by their SOURCE MODULE, so two agents adding imports from DIFFERENT
35
+ // modules never collide, and two touching the SAME module are merged (specifier union).
36
+ if (n.type === 'ImportDeclaration') return 'import:' + (n.source && n.source.value != null ? n.source.value : i)
37
+ return 'stmt:' + i
38
+ }
39
+
40
+ // Top-level units: ordered { key, text, start, end } for each declaration/statement.
41
+ // start/end are byte offsets in `src` — used by the format-preserving splice so unchanged
42
+ // regions (and the whitespace/comments between units) survive a merge verbatim.
43
+ function units(src) {
44
+ const ast = parse(src)
45
+ return ast.body.map((node, i) => ({ key: keyOf(node, i), text: src.slice(node.start, node.end), start: node.start, end: node.end }))
46
+ }
47
+
48
+ // Bare declared name (foo, Bar, VERSION) of a node, or null for anonymous statements.
49
+ function bareName(node) {
50
+ let n = node
51
+ if (n.type === 'ExportNamedDeclaration' && n.declaration) n = n.declaration
52
+ if (n.type === 'FunctionDeclaration' && n.id) return n.id.name
53
+ if (n.type === 'ClassDeclaration' && n.id) return n.id.name
54
+ if (n.type === 'VariableDeclaration' && n.declarations[0] && n.declarations[0].id && n.declarations[0].id.name) return n.declarations[0].id.name
55
+ return null
56
+ }
57
+
58
+ function declaredNames(src) {
59
+ const s = new Set()
60
+ for (const node of parse(src).body) {
61
+ const nm = bareName(node); if (nm) s.add(nm)
62
+ // Import locals count as declared names too — so if an import is ever dropped and its
63
+ // binding is still used, the dangling-reference check catches it (safety net for the
64
+ // import merge). Keeps the never-emit-broken-code guarantee honest for imports.
65
+ if (node.type === 'ImportDeclaration') for (const sp of node.specifiers) s.add(sp.local.name)
66
+ }
67
+ return s
68
+ }
69
+
70
+ // --- import-aware merging -------------------------------------------------------
71
+ // Parse a single import statement into its parts, or null if it isn't one.
72
+ function parseImport(src) {
73
+ let n
74
+ try { n = parse(src).body[0] } catch { return null }
75
+ if (!n || n.type !== 'ImportDeclaration') return null
76
+ let def = null, ns = null
77
+ const named = []
78
+ for (const sp of n.specifiers) {
79
+ if (sp.type === 'ImportDefaultSpecifier') def = sp.local.name
80
+ else if (sp.type === 'ImportNamespaceSpecifier') ns = sp.local.name
81
+ else named.push({ imported: sp.imported.name != null ? sp.imported.name : sp.imported.value, local: sp.local.name })
82
+ }
83
+ return { source: n.source.value, def, ns, named }
84
+ }
85
+ function renderImport({ source, def, ns, named }) {
86
+ const parts = []
87
+ if (def) parts.push(def)
88
+ if (ns) parts.push('* as ' + ns)
89
+ if (named.length) parts.push('{ ' + named.map((s) => s.imported === s.local ? s.local : s.imported + ' as ' + s.local).join(', ') + ' }')
90
+ if (!parts.length) return "import '" + source + "'"
91
+ return 'import ' + parts.join(', ') + " from '" + source + "'"
92
+ }
93
+ // Both sides changed an import from the SAME module → union their specifiers into one
94
+ // statement (the common "both agents added an import" case). Returns null — meaning
95
+ // "let the normal conflict path handle it" — for anything we can't safely combine
96
+ // (different modules, clashing defaults/namespaces, namespace-mixed-with-named).
97
+ function mergeUnit(baseText, aText, bText) {
98
+ const a = parseImport(aText), b = parseImport(bText)
99
+ if (!a || !b || a.source !== b.source) return null
100
+ let def
101
+ if (a.def && b.def) { if (a.def !== b.def) return null; def = a.def } else def = a.def || b.def
102
+ let ns
103
+ if (a.ns && b.ns) { if (a.ns !== b.ns) return null; ns = a.ns } else ns = a.ns || b.ns
104
+ if (ns && (a.named.length || b.named.length)) return null // `* as X` can't share a statement with named imports
105
+ const seen = new Set(), named = []
106
+ for (const s of [...a.named, ...b.named]) { const k = s.imported + '|' + s.local; if (!seen.has(k)) { seen.add(k); named.push(s) } }
107
+ return renderImport({ source: a.source, def, ns, named })
108
+ }
109
+
110
+ // The BODY of a top-level declaration, by name, with the name itself excluded — so two
111
+ // declarations that differ only in their name compare equal (that's how we spot a rename).
112
+ function declBody(src, name) {
113
+ for (const node of parse(src).body) {
114
+ if (bareName(node) !== name) continue
115
+ let n = node
116
+ if (n.type === 'ExportNamedDeclaration' && n.declaration) n = n.declaration
117
+ if ((n.type === 'FunctionDeclaration' || n.type === 'ClassDeclaration') && n.body)
118
+ return src.slice(n.body.start, n.body.end)
119
+ if (n.type === 'VariableDeclaration' && n.declarations[0] && n.declarations[0].init) {
120
+ const init = n.declarations[0].init
121
+ return src.slice(init.start, init.end)
122
+ }
123
+ return null
124
+ }
125
+ return null
126
+ }
127
+
128
+ // Rewrite every reference to `oldName` as `newName` (skipping property/key positions,
129
+ // which aren't references to the declaration). Edits back-to-front to keep offsets valid.
130
+ function renameRefs(src, oldName, newName) {
131
+ const spots = []
132
+ walk(parse(src), (node, parent, key) => {
133
+ if (node.type !== 'Identifier' || node.name !== oldName) return
134
+ if (parent && parent.type === 'MemberExpression' && key === 'property' && !parent.computed) return
135
+ if (parent && (parent.type === 'Property' || parent.type === 'PropertyDefinition') && key === 'key' && !parent.computed) return
136
+ spots.push([node.start, node.end])
137
+ })
138
+ spots.sort((a, b) => b[0] - a[0])
139
+ let out = src
140
+ for (const [s, e] of spots) out = out.slice(0, s) + newName + out.slice(e)
141
+ return out
142
+ }
143
+
144
+ // Every identifier USED anywhere (skips obj.prop names and non-computed object keys).
145
+ // Approximate — good enough for the intent check; the real version is scope-aware.
146
+ function usedIdentifiers(src) {
147
+ const used = new Set()
148
+ walk(parse(src), (node, parent, key) => {
149
+ if (node.type !== 'Identifier') return
150
+ if (parent && parent.type === 'MemberExpression' && key === 'property' && !parent.computed) return
151
+ if (parent && (parent.type === 'Property' || parent.type === 'PropertyDefinition') && key === 'key' && !parent.computed) return
152
+ used.add(node.name)
153
+ })
154
+ return used
155
+ }
156
+
157
+ function walk(node, visit, parent = null, key = null) {
158
+ if (!node || typeof node.type !== 'string') return
159
+ visit(node, parent, key)
160
+ for (const k of Object.keys(node)) {
161
+ if (k === 'type' || k === 'start' || k === 'end' || k === 'loc') continue
162
+ const v = node[k]
163
+ if (Array.isArray(v)) { for (const c of v) walk(c, visit, node, k) }
164
+ else if (v && typeof v.type === 'string') walk(v, visit, node, k)
165
+ }
166
+ }
167
+
168
+ // --- scope-aware reference analysis ---------------------------------------------
169
+ // The names REFERENCED in `src` that resolve to NOTHING in the file's own scopes —
170
+ // i.e. free/global names, including any use of a top-level declaration. This is the
171
+ // scope-aware upgrade of usedIdentifiers: a use of `x` that resolves to a LOCAL binding
172
+ // (a param, a `const x` in the same function, a catch var, a loop var…) is NOT counted,
173
+ // because it isn't a reference to a top-level declaration. That lets ICR tell a deleted
174
+ // top-level `helper` apart from an unrelated local `helper` that merely shares its name.
175
+ //
176
+ // Honest scope coverage: functions (declaration/expression/arrow), blocks, catch
177
+ // clauses, for/for-in/for-of loop bindings, params with simple destructuring, imports,
178
+ // var-hoisting to the function scope, and let/const/class/function block binding.
179
+ // Approximations (rare, and they only ever make the dangling check MORE cautious):
180
+ // default-value expressions in patterns and `with`/eval are not modeled.
181
+ // Walk the AST and call emit(identifierNode) for every reference that resolves to NO
182
+ // local binding (free / would resolve to module or global scope). Both the dangling-ref
183
+ // check and scope-aware renaming are built on this single traversal.
184
+ function walkFreeRefs(src, emit) { resolveScopes(parse(src), null, emit) }
185
+
186
+ function referencedFreeNames(src) {
187
+ const free = new Set()
188
+ walkFreeRefs(src, (node) => free.add(node.name))
189
+ return free
190
+ }
191
+
192
+ // Rename ONLY the free references to oldName (those that resolve to the top-level/global
193
+ // binding) — never a local variable that merely shares the name. Edits back-to-front.
194
+ function renameFreeRefs(src, oldName, newName) {
195
+ const spots = []
196
+ walkFreeRefs(src, (node) => { if (node.name === oldName) spots.push([node.start, node.end]) })
197
+ spots.sort((a, b) => b[0] - a[0])
198
+ let out = src
199
+ for (const [s, e] of spots) out = out.slice(0, s) + newName + out.slice(e)
200
+ return out
201
+ }
202
+
203
+ const CHILD_SKIP = new Set(['type', 'start', 'end', 'loc', 'range'])
204
+ function eachChild(node, fn) {
205
+ for (const k of Object.keys(node)) {
206
+ if (CHILD_SKIP.has(k)) continue
207
+ const v = node[k]
208
+ if (Array.isArray(v)) { for (const c of v) if (c && typeof c.type === 'string') fn(c) }
209
+ else if (v && typeof v.type === 'string') fn(v)
210
+ }
211
+ }
212
+
213
+ // Names introduced by a binding target (handles identifiers + simple destructuring).
214
+ function bindingNames(node, out) {
215
+ if (!node) return
216
+ switch (node.type) {
217
+ case 'Identifier': out.add(node.name); break
218
+ case 'ObjectPattern': for (const p of node.properties) bindingNames(p.type === 'RestElement' ? p.argument : p.value, out); break
219
+ case 'ArrayPattern': for (const el of node.elements) if (el) bindingNames(el, out); break
220
+ case 'AssignmentPattern': bindingNames(node.left, out); break
221
+ case 'RestElement': bindingNames(node.argument, out); break
222
+ }
223
+ }
224
+
225
+ // A binding target's NAMES are bindings, but its default values (`a = expr`) and computed
226
+ // destructuring keys (`{ [k]: v }`) are REFERENCES that must be resolved against the scope.
227
+ // Without this, a name used only in a default value would be missed by the dangling check.
228
+ function resolveBindingDefaults(node, scope, emit) {
229
+ if (!node) return
230
+ switch (node.type) {
231
+ case 'AssignmentPattern': resolveBindingDefaults(node.left, scope, emit); resolveScopes(node.right, scope, emit); break
232
+ case 'ObjectPattern': for (const p of node.properties) {
233
+ if (p.type === 'RestElement') { resolveBindingDefaults(p.argument, scope, emit); break }
234
+ if (p.computed) resolveScopes(p.key, scope, emit)
235
+ resolveBindingDefaults(p.value, scope, emit)
236
+ } break
237
+ case 'ArrayPattern': for (const el of node.elements) if (el) resolveBindingDefaults(el, scope, emit); break
238
+ case 'RestElement': resolveBindingDefaults(node.argument, scope, emit); break
239
+ // Identifier: a pure binding — nothing to resolve.
240
+ }
241
+ }
242
+
243
+ const makeScope = (parent, fnScope) => { const s = { vars: new Set(), parent }; s.fnScope = fnScope || s; return s }
244
+ const resolves = (scope, name) => { for (let s = scope; s; s = s.parent) if (s.vars.has(name)) return true; return false }
245
+
246
+ // Hoist var declarations + function declarations into the function scope (deep, but not
247
+ // crossing into nested functions, which own their own var scope).
248
+ function hoistFunctionScope(nodes, scope) { for (const n of nodes) collectHoisted(n, scope) }
249
+ function collectHoisted(node, scope) {
250
+ if (!node || typeof node.type !== 'string') return
251
+ if (node.type === 'FunctionDeclaration') { if (node.id) scope.fnScope.vars.add(node.id.name); return }
252
+ if (/Function(Expression)?$|ArrowFunctionExpression/.test(node.type)) return // nested function: stop
253
+ if (node.type === 'VariableDeclaration' && node.kind === 'var')
254
+ for (const d of node.declarations) bindingNames(d.id, scope.fnScope.vars)
255
+ eachChild(node, (c) => collectHoisted(c, scope))
256
+ }
257
+ // Bind let/const/class/function/import names declared directly in a block/program.
258
+ function hoistBlock(nodes, scope) {
259
+ for (const n of nodes) {
260
+ if (n.type === 'VariableDeclaration' && (n.kind === 'let' || n.kind === 'const'))
261
+ for (const d of n.declarations) bindingNames(d.id, scope.vars)
262
+ else if (n.type === 'ClassDeclaration' && n.id) scope.vars.add(n.id.name)
263
+ else if (n.type === 'FunctionDeclaration' && n.id) scope.vars.add(n.id.name)
264
+ else if (n.type === 'ImportDeclaration') for (const sp of n.specifiers) scope.vars.add(sp.local.name)
265
+ }
266
+ }
267
+
268
+ function resolveScopes(node, scope, emit) {
269
+ if (!node || typeof node.type !== 'string') return
270
+ switch (node.type) {
271
+ case 'Program': {
272
+ const s = makeScope(null, null)
273
+ hoistFunctionScope(node.body, s); hoistBlock(node.body, s)
274
+ for (const c of node.body) resolveScopes(c, s, emit)
275
+ return
276
+ }
277
+ case 'FunctionDeclaration': case 'FunctionExpression': case 'ArrowFunctionExpression': {
278
+ const s = makeScope(scope, null)
279
+ for (const p of node.params) bindingNames(p, s.vars)
280
+ for (const p of node.params) resolveBindingDefaults(p, s, emit) // default values are references
281
+ if (node.id && node.type === 'FunctionExpression') s.vars.add(node.id.name) // named fn expr
282
+ if (node.body.type === 'BlockStatement') {
283
+ hoistFunctionScope(node.body.body, s); hoistBlock(node.body.body, s)
284
+ for (const c of node.body.body) resolveScopes(c, s, emit)
285
+ } else resolveScopes(node.body, s, emit) // arrow with expression body
286
+ return
287
+ }
288
+ case 'BlockStatement': {
289
+ const s = makeScope(scope, scope.fnScope); hoistBlock(node.body, s)
290
+ for (const c of node.body) resolveScopes(c, s, emit)
291
+ return
292
+ }
293
+ case 'CatchClause': {
294
+ const s = makeScope(scope, scope.fnScope)
295
+ if (node.param) bindingNames(node.param, s.vars)
296
+ resolveScopes(node.body, s, emit)
297
+ return
298
+ }
299
+ case 'ForStatement': {
300
+ const s = makeScope(scope, scope.fnScope)
301
+ if (node.init && node.init.type === 'VariableDeclaration') hoistBlock([node.init], s)
302
+ for (const k of ['init', 'test', 'update', 'body']) if (node[k]) resolveScopes(node[k], s, emit)
303
+ return
304
+ }
305
+ case 'ForInStatement': case 'ForOfStatement': {
306
+ const s = makeScope(scope, scope.fnScope)
307
+ if (node.left.type === 'VariableDeclaration') hoistBlock([node.left], s)
308
+ else resolveScopes(node.left, s, emit)
309
+ resolveScopes(node.right, s, emit); resolveScopes(node.body, s, emit)
310
+ return
311
+ }
312
+ case 'VariableDeclarator': { resolveBindingDefaults(node.id, scope, emit); if (node.init) resolveScopes(node.init, scope, emit); return } // id is a binding (defaults are refs)
313
+ case 'Identifier': { if (!resolves(scope, node.name)) emit(node); return }
314
+ case 'MemberExpression': { resolveScopes(node.object, scope, emit); if (node.computed) resolveScopes(node.property, scope, emit); return }
315
+ case 'Property': { if (node.computed) resolveScopes(node.key, scope, emit); resolveScopes(node.value, scope, emit); return }
316
+ case 'PropertyDefinition': case 'MethodDefinition': { if (node.computed) resolveScopes(node.key, scope, emit); if (node.value) resolveScopes(node.value, scope, emit); return }
317
+ case 'LabeledStatement': { resolveScopes(node.body, scope, emit); return }
318
+ case 'BreakStatement': case 'ContinueStatement': return // labels aren't value references
319
+ case 'ExportSpecifier': { resolveScopes(node.local, scope, emit); return }
320
+ case 'ImportSpecifier': case 'ImportDefaultSpecifier': case 'ImportNamespaceSpecifier': return // binding positions
321
+ default: eachChild(node, (c) => resolveScopes(c, scope, emit))
322
+ }
323
+ }
324
+
325
+ // A stable key for a class member, so two agents editing DIFFERENT methods of the same
326
+ // class merge, while editing the SAME method conflicts.
327
+ function memberKey(m, i) {
328
+ const kind = m.type === 'MethodDefinition' ? (m.kind === 'constructor' ? 'ctor' : 'method') : 'field'
329
+ if (m.key && m.key.type === 'Identifier' && !m.computed)
330
+ return kind + ':' + (m.static ? 'static.' : '') + m.key.name
331
+ return 'member:' + i
332
+ }
333
+
334
+ // For finer-grained merging: split a single declaration into its signature text + keyed
335
+ // inner units, plus how to reassemble them. Handles FUNCTIONS (body statements, keyed
336
+ // like top-level decls) and CLASSES (members, keyed by method/field name). Returns null
337
+ // for anything else (the engine then treats the declaration as indivisible). Tolerates
338
+ // fragments that don't parse standalone (e.g. a bare `return`).
339
+ const MEMBER_WRAP = 'class __ICR__{'
340
+ // A class member (method) doesn't parse standalone — wrap it in a throwaway class so we
341
+ // can recurse INTO a method body that both agents edited. Offsets are mapped back to src.
342
+ function splitMember(src) {
343
+ let ast
344
+ try { ast = parse(MEMBER_WRAP + src + '}') } catch { return null }
345
+ const cls = ast.body[0]
346
+ if (!cls || cls.type !== 'ClassDeclaration' || !cls.body.body.length) return null
347
+ const m = cls.body.body[0]
348
+ if (!m || m.type !== 'MethodDefinition' || !m.value || !m.value.body || m.value.body.type !== 'BlockStatement') return null
349
+ const off = MEMBER_WRAP.length, body = m.value.body
350
+ const sig = src.slice(0, body.start - off)
351
+ const units = body.body.map((s, i) => ({ key: keyOf(s, i), text: src.slice(s.start - off, s.end - off) }))
352
+ return { sig, units, open: '{\n ', join: '\n ', close: '\n}' }
353
+ }
354
+
355
+ function splitUnit(src) {
356
+ let ast
357
+ try { ast = parse(src) } catch { return splitMember(src) } // maybe a class-member fragment
358
+ let n = ast.body[0]
359
+ if (!n) return null
360
+ if (n.type === 'ExportNamedDeclaration' && n.declaration) n = n.declaration
361
+ if (n.type === 'FunctionDeclaration' && n.body) {
362
+ const sig = src.slice(n.start, n.body.start)
363
+ const units = n.body.body.map((s, i) => ({ key: keyOf(s, i), text: src.slice(s.start, s.end) }))
364
+ return { sig, units, open: '{\n ', join: '\n ', close: '\n}' }
365
+ }
366
+ if (n.type === 'ClassDeclaration' && n.body) {
367
+ const sig = src.slice(n.start, n.body.start)
368
+ const units = n.body.body.map((m, i) => ({ key: memberKey(m, i), text: src.slice(m.start, m.end) }))
369
+ return { sig, units, open: '{\n ', join: '\n\n ', close: '\n}' }
370
+ }
371
+ // `const x = { ... }` / `const x = () => { ... }` / `const x = function () { ... }`
372
+ if (n.type === 'VariableDeclaration' && n.declarations.length === 1 && n.declarations[0].init) {
373
+ const init = n.declarations[0].init
374
+ if (init.type === 'ObjectExpression') {
375
+ const sig = src.slice(n.start, init.start)
376
+ const units = init.properties.map((p, i) => ({ key: propKey(p, i), text: src.slice(p.start, p.end) }))
377
+ return { sig, units, open: '{\n ', join: ',\n ', close: '\n}' }
378
+ }
379
+ if ((init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') && init.body && init.body.type === 'BlockStatement') {
380
+ const sig = src.slice(n.start, init.body.start)
381
+ const units = init.body.body.map((s, i) => ({ key: keyOf(s, i), text: src.slice(s.start, s.end) }))
382
+ return { sig, units, open: '{\n ', join: '\n ', close: '\n}' }
383
+ }
384
+ }
385
+ return null
386
+ }
387
+
388
+ // Stable key for an object-literal property, so two agents adding DIFFERENT keys union.
389
+ function propKey(p, i) {
390
+ if (p.type === 'SpreadElement') return 'spread:' + i
391
+ if (p.key && p.key.type === 'Identifier' && !p.computed) return 'prop:' + p.key.name
392
+ if (p.key && p.key.type === 'Literal') return 'prop:' + String(p.key.value)
393
+ return 'prop:' + i
394
+ }
395
+
396
+ // Does `src` parse either as top-level code OR as a class member? (The inner-merge result
397
+ // for a method is a fragment that's only valid inside a class — this lets us validate it.)
398
+ function parsesUnit(src) {
399
+ if (parses(src)) return true
400
+ try { parse(MEMBER_WRAP + src + '}'); return true } catch { return false }
401
+ }
402
+
403
+ // The provider contract every ICR language module implements.
404
+ export const javascript = {
405
+ id: 'javascript',
406
+ exts: ['.js', '.mjs', '.cjs'],
407
+ parses,
408
+ units,
409
+ declaredNames,
410
+ usedIdentifiers, // approximate (kept for reference)
411
+ referencedFreeNames, // scope-aware — what the engine prefers for the dangling check
412
+ declBody,
413
+ renameRefs, // rewrites every matching identifier (kept for reference)
414
+ renameFreeRefs, // scope-aware — only rewrites references that resolve to the binding
415
+ splitUnit, // finer granularity: split a function/class/method into keyed inner units
416
+ parsesUnit, // validity oracle that also accepts a class-member fragment
417
+ mergeUnit, // language-specific same-key merge (import specifier union)
418
+ }
package/lang-python.js ADDED
@@ -0,0 +1,149 @@
1
+ // Python language provider for ICR — indentation-based structural splitter.
2
+ // Implements the ICR provider contract { id, exts, parses, units, declaredNames,
3
+ // splitUnit, parsesUnit } so the shared merge engine merges Python by structure:
4
+ // top-level defs/classes/statements are units keyed by name; editing different
5
+ // methods of the same class merges via splitUnit. Heuristic (tracks #-comments,
6
+ // triple-quoted and normal strings, bracket depth) — not a full parser; parses()
7
+ // is an approximate balanced-delimiter check.
8
+
9
+ function scannerAt(src) {
10
+ const n = src.length
11
+ return function at(i) {
12
+ if (src[i] === '#') { let j = i + 1; while (j < n && src[j] !== '\n') j++; return j }
13
+ for (const q of ['"""', "'''"]) if (src.startsWith(q, i)) { let j = i + 3; while (j < n && !src.startsWith(q, j)) { if (src[j] === '\\') j++; j++ } return Math.min(n, j + 3) }
14
+ if (src[i] === '"' || 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 } if (src[j] === '\n') break; j++ } return j }
15
+ return -1
16
+ }
17
+ }
18
+
19
+ // Line records: { start, end (incl newline), indent, blank, text }
20
+ function lines(src) {
21
+ const out = []
22
+ let i = 0
23
+ while (i < src.length) {
24
+ let e = src.indexOf('\n', i); if (e < 0) e = src.length; else e = e + 1
25
+ const raw = src.slice(i, e)
26
+ const trimmed = raw.replace(/\r?\n$/, '')
27
+ let indent = 0; while (indent < trimmed.length && (trimmed[indent] === ' ' || trimmed[indent] === '\t')) indent++
28
+ const blank = trimmed.trim() === '' || trimmed.trim().startsWith('#')
29
+ out.push({ start: i, end: e, indent, blank, text: raw })
30
+ i = e
31
+ }
32
+ return out
33
+ }
34
+
35
+ const HEAD = /^\s*(?:async\s+def|def)\s+([A-Za-z_]\w*)/
36
+ const CLS = /^\s*class\s+([A-Za-z_]\w*)/
37
+
38
+ function keyOf(text) {
39
+ let m = text.match(HEAD); if (m) return 'def:' + m[1]
40
+ m = text.match(CLS); if (m) return 'class:' + m[1]
41
+ return 'stmt:' + text.split('\n')[0].trim().replace(/\s+/g, ' ')
42
+ }
43
+
44
+ // Split src into units whose heads sit at exactly `base` indentation. A def/class
45
+ // head owns the following blank + more-indented lines (its body). A run of leading
46
+ // @decorator lines attaches to the head that follows. Everything else is a
47
+ // one-logical-line statement (continued while bracket depth > 0).
48
+ function blocks(src, base = 0) {
49
+ const L = lines(src)
50
+ const at = scannerAt(src)
51
+ const units = []
52
+ let i = 0
53
+ // find bracket depth over a line range (to keep continued statements together)
54
+ const bracketDelta = (s, e) => {
55
+ let d = 0, k = s
56
+ while (k < e) { const j = at(k); if (j > k) { k = j; continue } const c = src[k]; if (c === '(' || c === '[' || c === '{') d++; else if (c === ')' || c === ']' || c === '}') d--; k++ }
57
+ return d
58
+ }
59
+ while (i < L.length) {
60
+ while (i < L.length && L[i].blank) i++ // skip blank/comment gap lines
61
+ if (i >= L.length) break
62
+ if (L[i].indent !== base) { // stray deeper line (shouldn't happen at top) — treat as its own stmt
63
+ const s = L[i].start; let e = L[i].end; i++
64
+ units.push({ start: s, end: e })
65
+ continue
66
+ }
67
+ const start = L[i].start
68
+ // consume leading decorators
69
+ let isDecorator = /^\s*@/.test(L[i].text)
70
+ while (i < L.length && L[i].indent === base && /^\s*@/.test(L[i].text)) i++
71
+ const headIdx = i
72
+ const head = i < L.length ? L[i] : null
73
+ if (head && (HEAD.test(head.text) || CLS.test(head.text) || isDecorator)) {
74
+ // a block: consume the header line, then its body (blank or deeper-indented lines)
75
+ i++ // header line
76
+ while (i < L.length && (L[i].blank || L[i].indent > base)) i++
77
+ units.push({ start, end: L[i - 1].end })
78
+ } else if (head) {
79
+ // simple statement: consume continued lines while bracket depth remains open
80
+ let e = head.end, depth = bracketDelta(head.start, head.end); i++
81
+ while (i < L.length && (depth > 0 || /\\\s*$/.test(L[i - 1].text.replace(/\r?\n$/, '')))) { depth += bracketDelta(L[i].start, L[i].end); e = L[i].end; i++ }
82
+ units.push({ start, end: e })
83
+ }
84
+ }
85
+ return units
86
+ }
87
+
88
+ function keyed(raw, src) {
89
+ const seen = new Map(), out = []
90
+ for (const u of raw) {
91
+ const text = src.slice(u.start, u.end)
92
+ let k = keyOf(text)
93
+ const c = (seen.get(k) || 0) + 1; seen.set(k, c)
94
+ if (c > 1) k += '#' + c
95
+ out.push({ key: k, text, start: u.start, end: u.end })
96
+ }
97
+ return out
98
+ }
99
+
100
+ function balanced(src) {
101
+ const at = scannerAt(src)
102
+ const stack = []
103
+ const pair = { ')': '(', ']': '[', '}': '{' }
104
+ let i = 0
105
+ while (i < src.length) {
106
+ const j = at(i); if (j > i) { if (j > src.length) return false; i = j; continue }
107
+ const c = src[i]
108
+ if (c === '(' || c === '[' || c === '{') stack.push(c)
109
+ else if (pair[c]) { if (stack.pop() !== pair[c]) return false }
110
+ i++
111
+ }
112
+ return stack.length === 0
113
+ }
114
+
115
+ export const python = {
116
+ id: 'python',
117
+ exts: ['.py', '.pyi'],
118
+ parses: (src) => balanced(String(src == null ? '' : src)),
119
+ parsesUnit: (src) => balanced(String(src)),
120
+ units: (src) => keyed(blocks(String(src), 0), String(src)),
121
+ declaredNames: (src) => {
122
+ const names = new Set()
123
+ for (const u of keyed(blocks(String(src), 0), String(src))) {
124
+ const m = u.key.match(/^(?:def|class):([A-Za-z_]\w*)/)
125
+ if (m) names.add(m[1])
126
+ }
127
+ return names
128
+ },
129
+ // Descend into a class/def body so two agents editing DIFFERENT methods merge.
130
+ splitUnit: (text) => {
131
+ const src = String(text)
132
+ const L = lines(src)
133
+ let h = 0; while (h < L.length && (L[h].blank || /^\s*@/.test(L[h].text))) h++
134
+ if (h >= L.length) return null
135
+ // Only descend into a CLASS (named methods → mergeable). A def body is bare
136
+ // statements with no stable keys; descending would falsely merge two edits to the
137
+ // same function, so we don't — same-def edits become a clean conflict instead.
138
+ if (!CLS.test(L[h].text)) return null
139
+ const headEnd = L[h].end
140
+ // body = everything after the header line; its base indent is the first body line's indent
141
+ let b = h + 1; while (b < L.length && L[b].blank) b++
142
+ if (b >= L.length) return null
143
+ const bodyIndent = L[b].indent
144
+ const sig = src.slice(0, headEnd)
145
+ const body = src.slice(headEnd)
146
+ const inner = keyed(blocks(body, bodyIndent), body)
147
+ return { sig, open: '', close: '', body, units: inner, join: '' }
148
+ },
149
+ }
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "hivecode-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server that lets any AI coding agent join a Hivecode room — humans and multiple AI agents edit one live codebase with folder-scoped permissions and approval gates.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "homepage": "https://github.com/GSK7024/livecode",
7
+ "homepage": "https://github.com/Ganesh-Kute/HiveCode",
8
8
  "bugs": {
9
- "url": "https://github.com/GSK7024/livecode/issues"
9
+ "url": "https://github.com/Ganesh-Kute/HiveCode/issues"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/GSK7024/livecode"
13
+ "url": "https://github.com/Ganesh-Kute/HiveCode"
14
14
  },
15
15
  "keywords": [
16
16
  "mcp",
@@ -30,6 +30,13 @@
30
30
  "sync.js",
31
31
  "core.js",
32
32
  "token.js",
33
+ "icr.js",
34
+ "lang-js.js",
35
+ "lang-brace.js",
36
+ "lang-python.js",
37
+ "icr-merge.js",
38
+ "hive-coord.js",
39
+ "substrate.js",
33
40
  "HIVE_RULES.md",
34
41
  "MCP.md",
35
42
  "README.md",
@@ -41,7 +48,12 @@
41
48
  "scripts": {
42
49
  "start": "node server.js",
43
50
  "relay": "node server.js",
44
- "test": "node test.js"
51
+ "test": "node test.js",
52
+ "test:substrate": "node substrate-test.js",
53
+ "test:substrate:live": "node substrate-live-test.js",
54
+ "test:substrate:enforce": "node substrate-enforce-test.js",
55
+ "test:substrate:authority": "node substrate-authority-test.js",
56
+ "test:substrate:all": "node substrate-test.js && node substrate-live-test.js && node substrate-enforce-test.js && node substrate-authority-test.js"
45
57
  },
46
58
  "engines": {
47
59
  "node": ">=18"
@@ -49,6 +61,7 @@
49
61
  "dependencies": {
50
62
  "@anthropic-ai/sdk": "^0.104.2",
51
63
  "@modelcontextprotocol/sdk": "^1.29.0",
64
+ "acorn": "^8.17.0",
52
65
  "ignore": "^7.0.5",
53
66
  "ws": "^8.18.0",
54
67
  "y-websocket": "^2.0.4",