ucn 5.5.1 → 5.5.3

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 (97) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +40 -18
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +32 -21
  6. package/cli/index.js +2 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/bridge.js +15 -65
  12. package/core/brief.js +8 -4
  13. package/core/build-worker.js +2 -1
  14. package/core/cache.js +137 -6
  15. package/core/call-name-cache.js +134 -0
  16. package/core/callable-scope.js +21 -0
  17. package/core/callers.js +4726 -940
  18. package/core/check.js +22 -5
  19. package/core/class-identity.js +399 -12
  20. package/core/client-receivers.js +189 -0
  21. package/core/constructor-calls.js +831 -0
  22. package/core/constructor-hierarchy.js +147 -0
  23. package/core/constructor-protocol.js +196 -0
  24. package/core/contract-membership.js +346 -140
  25. package/core/cpp-conversion-constraints.js +123 -0
  26. package/core/cpp-conversion-syntax.js +216 -0
  27. package/core/cpp-conversions.js +414 -0
  28. package/core/cpp-scope.js +245 -44
  29. package/core/deadcode.js +61 -16
  30. package/core/decorator-binding.js +137 -3
  31. package/core/enum-reference-syntax.js +134 -0
  32. package/core/execute.js +17 -1
  33. package/core/external-macros.js +42 -10
  34. package/core/go-packages.js +171 -0
  35. package/core/graph-build.js +2 -0
  36. package/core/graph.js +75 -59
  37. package/core/ignore-rules.js +2 -1
  38. package/core/imports.js +145 -19
  39. package/core/index-ir.js +41 -4
  40. package/core/inprocess-clients.js +41 -15
  41. package/core/ir.js +27 -6
  42. package/core/macro-expansion.js +38 -2
  43. package/core/out-of-class-members.js +182 -0
  44. package/core/output/analysis-ext.js +4 -2
  45. package/core/output/analysis.js +89 -20
  46. package/core/output/graph.js +12 -3
  47. package/core/output/lines.js +4 -3
  48. package/core/output/public.js +2 -0
  49. package/core/output/reporting.js +12 -1
  50. package/core/output/shared.js +2 -0
  51. package/core/output/tracing.js +28 -22
  52. package/core/output-budget.js +2 -2
  53. package/core/parallel-build.js +52 -1
  54. package/core/project.js +217 -112
  55. package/core/provenance-constructor-base.js +161 -0
  56. package/core/provenance-facts.js +257 -27
  57. package/core/provenance-overload.js +458 -54
  58. package/core/provenance-type.js +487 -0
  59. package/core/provenance.js +234 -15
  60. package/core/public-command.js +17 -2
  61. package/core/query-bodies.js +159 -0
  62. package/core/reflection.js +270 -2
  63. package/core/rename-token-syntax.js +111 -0
  64. package/core/reporting.js +13 -7
  65. package/core/request-helpers.js +63 -7
  66. package/core/rust-macro-expansion.js +151 -19
  67. package/core/rust-modules.js +1 -1
  68. package/core/rust-names.js +607 -0
  69. package/core/search.js +70 -17
  70. package/core/shared.js +98 -1
  71. package/core/source-memo.js +201 -0
  72. package/core/text-usage-syntax.js +38 -0
  73. package/core/tracing.js +7 -1
  74. package/core/type-denotation.js +124 -37
  75. package/core/type-references.js +615 -246
  76. package/core/value-references.js +2777 -0
  77. package/core/verify.js +1097 -281
  78. package/languages/adapter.js +28 -4
  79. package/languages/c-family.js +1118 -164
  80. package/languages/constructor-bases.js +105 -0
  81. package/languages/cpp-platform.js +26 -0
  82. package/languages/cpp-site-syntax.js +83 -0
  83. package/languages/csharp-scope.js +439 -0
  84. package/languages/csharp.js +428 -68
  85. package/languages/go.js +385 -207
  86. package/languages/html.js +4 -2
  87. package/languages/index.js +397 -2
  88. package/languages/java-platform.js +226 -0
  89. package/languages/java-unicode.js +269 -0
  90. package/languages/java.js +461 -117
  91. package/languages/javascript.js +418 -66
  92. package/languages/lexical-scope.js +190 -23
  93. package/languages/python.js +404 -54
  94. package/languages/rust.js +510 -82
  95. package/languages/utils.js +67 -8
  96. package/mcp/server.js +8 -2
  97. package/package.json +3 -3
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ const { langTraits, getParser } = require('../languages');
4
+ const { parsedSource } = require('./source-memo');
5
+ const { referenceScope } = require('../languages/lexical-scope');
6
+ const { declarationIdentity, sameDeclaration, identityKey } = require('./provenance');
7
+ const { factIndex, sealFacts, replayed, seedReplay, refusedProof } = require('./provenance-overload');
8
+ const { FILE_FIELDS, resolveTypeLookup } = require('./provenance-type');
9
+
10
+ function basesOf(index, owner, config) {
11
+ const entry = index.files.get(owner.file), language = entry.language;
12
+ const source = index._readFile(owner.file);
13
+ const content = config.source ? config.source(source, getParser(language)) : source;
14
+ if (content == null) return null;
15
+ const tree = parsedSource(config.syntaxLanguage || language, content);
16
+ if (!tree) return null;
17
+ {
18
+ const nodes = tree.rootNode.descendantsOfType(config.classNodes).filter(node =>
19
+ node.childForFieldName('name')?.text === owner.name &&
20
+ node.startPosition.row + 1 >= owner.startLine && node.endPosition.row + 1 === owner.endLine);
21
+ if (nodes.length !== 1 || nodes[0].hasError) return null;
22
+ const node = nodes[0];
23
+ const header = config.headerField ? node.childForFieldName(config.headerField)
24
+ : node.namedChildren.find(child => child.type === config.headerNode);
25
+ let bases = (header?.namedChildren || []).filter(base => !base.type.endsWith('comment') &&
26
+ base.type !== config.keywordNode);
27
+ if (config.order === 'single') {
28
+ const clause = bases.find(base => base.type === 'extends_clause');
29
+ if (clause) bases = [clause.childForFieldName('value') || clause.namedChild(0)];
30
+ else if (bases.some(base => base.type === 'implements_clause')) bases = [];
31
+ if (bases.length > 1) return null;
32
+ }
33
+ const parents = [];
34
+ for (let base of bases) {
35
+ while (base?.type === 'subscript') base = base.childForFieldName('value');
36
+ let name, qualifier = null, root = base;
37
+ if (base?.type === 'identifier') name = base.text;
38
+ else if (base?.type === 'attribute' || base?.type === 'member_expression') {
39
+ const object = base.childForFieldName('object');
40
+ name = (base.childForFieldName('attribute') || base.childForFieldName('property'))?.text;
41
+ qualifier = object?.text;
42
+ root = object;
43
+ while (root?.type === 'attribute' || root?.type === 'member_expression') root = root.childForFieldName('object');
44
+ }
45
+ if (!name || root?.type !== 'identifier') return null;
46
+ const scope = referenceScope(root, config.syntaxLanguage || language, new Map(), root.text, { includeClasses: true });
47
+ const builtin = langTraits(language)?.constructorProtocol?.builtinRoot;
48
+ if (!qualifier && name === builtin && scope === 'module' &&
49
+ !(entry.moduleAssignedNames || []).includes(name) &&
50
+ !(entry.importBindings || []).some(binding => (binding.alias || binding.name) === name) &&
51
+ !(index.symbols.get(name) || []).some(def => def.file === owner.file)) continue;
52
+ const query = { file: owner.file, relativePath: entry.relativePath,
53
+ line: base.startPosition.row + 1, column: base.startPosition.column,
54
+ name, qualifier, construction: true };
55
+ const candidates = require('./constructor-calls').constructionTypeCandidates(index, name).filter(def => def.type === 'class' &&
56
+ (scope === 'module' || scope?.local && scope.defRows?.includes(def.startLine - 1) && def.file === owner.file) &&
57
+ resolveTypeLookup(index, query, declarationIdentity(def)));
58
+ if (candidates.length !== 1) return null;
59
+ parents.push(candidates[0]);
60
+ }
61
+ return parents;
62
+ }
63
+ }
64
+
65
+ function selectHierarchy(index, identity) {
66
+ index._classIdentity = null;
67
+ index._cppScope = null;
68
+ index._typeDenotation = null;
69
+ const constructed = (index.symbols.get(identity.name) || []).find(def => sameDeclaration(declarationIdentity(def), identity));
70
+ if (!constructed) return null;
71
+ const { ownConstructors } = require('./constructor-calls');
72
+ const config = langTraits(index.files.get(constructed.file)?.language)?.constructorInheritance;
73
+ if (!config) return null;
74
+ const memo = new Map(), visiting = new Set();
75
+ const linearize = owner => {
76
+ const key = identityKey(declarationIdentity(owner));
77
+ if (memo.has(key)) return memo.get(key);
78
+ if (visiting.has(key)) return null;
79
+ visiting.add(key);
80
+ const parents = basesOf(index, owner, config);
81
+ if (!parents) return null;
82
+ const lists = parents.map(linearize);
83
+ if (lists.some(list => !list)) return null;
84
+ lists.push([...parents]);
85
+ const order = [owner];
86
+ while (lists.some(list => list.length)) {
87
+ const active = lists.filter(list => list.length);
88
+ const head = active.map(list => list[0]).find(candidate =>
89
+ !active.some(list => list.slice(1).some(def => sameDeclaration(declarationIdentity(def), declarationIdentity(candidate)))));
90
+ if (!head) return null;
91
+ order.push(head);
92
+ for (const list of active) if (sameDeclaration(declarationIdentity(list[0]), declarationIdentity(head))) list.shift();
93
+ }
94
+ visiting.delete(key); memo.set(key, order);
95
+ return order;
96
+ };
97
+ const order = linearize(constructed);
98
+ if (!order || index.hasMissingProvenanceFacts) return null;
99
+ for (const declaring of order) {
100
+ const members = ownConstructors(index, declaring);
101
+ if (members.length) return { constructed, declaring, members };
102
+ const initializer = langTraits(index.files.get(declaring.file)?.language)?.constructorProtocol?.initializer;
103
+ if (initializer && require('./constructor-calls').ownMembers(index, declaring).some(def => def.name === initializer)) return null;
104
+ }
105
+ // Every class of the complete order is a project class and none declares
106
+ // a constructor: the construction runs no project constructor body (fix
107
+ // #421). The caller still checks the allocation protocol.
108
+ return { constructed, none: true };
109
+ }
110
+
111
+ function captureHierarchy(index, owner) {
112
+ const reads = {}, constructed = declarationIdentity(owner);
113
+ try {
114
+ const result = selectHierarchy(factIndex(reads, index, FILE_FIELDS), constructed);
115
+ if (!result) return null;
116
+ if (result.none) return { none: true };
117
+ // Return live definitions for the caller, keeping only source reads
118
+ // and identities in the independently replayed witness.
119
+ const declaring = (index.symbols.get(result.declaring.name) || []).find(def =>
120
+ sameDeclaration(declarationIdentity(def), declarationIdentity(result.declaring)));
121
+ if (!declaring) return null;
122
+ const witness = { constructed, reads: sealFacts(reads) };
123
+ seedReplay(witness.reads, JSON.stringify(['hierarchy', constructed]), {
124
+ declaring: declarationIdentity(result.declaring), members: result.members.map(declarationIdentity) });
125
+ return { declaring, members: require('./constructor-calls').ownConstructors(index, declaring), witness };
126
+ } catch (error) { return refusedProof(error); }
127
+ }
128
+
129
+ function validateHierarchy(witness, constructed, declaring, members) {
130
+ if (!witness || !sameDeclaration(witness.constructed, constructed)) return false;
131
+ let key;
132
+ try { key = JSON.stringify(['hierarchy', constructed]); }
133
+ catch { return false; }
134
+ const result = replayed(witness.reads, key, () => {
135
+ try {
136
+ const index = factIndex(witness.reads, undefined, FILE_FIELDS);
137
+ const selected = selectHierarchy(index, constructed);
138
+ return selected && !selected.none && !index.hasMissingProvenanceFacts ? { declaring: declarationIdentity(selected.declaring),
139
+ members: selected.members.map(declarationIdentity) } : null;
140
+ } catch (error) { return refusedProof(error); }
141
+ });
142
+ return !!result && sameDeclaration(result.declaring, declaring) &&
143
+ result.members.length === members.length && result.members.every(def =>
144
+ members.some(member => sameDeclaration(def, member)));
145
+ }
146
+
147
+ module.exports = { captureHierarchy, validateHierarchy };
@@ -0,0 +1,196 @@
1
+ 'use strict';
2
+
3
+ const { langTraits, getParser, safeParse } = require('../languages');
4
+ const { sameNode } = require('../languages/utils');
5
+ const { referenceScope, scopeFields, pythonBindingSites } = require('../languages/lexical-scope');
6
+ const { ownerDefinitionOf } = require('./class-identity');
7
+ const { declarationIdentity, sameDeclaration, identityKey } = require('./provenance');
8
+ const { captureTypeLookup, validateTypeLookup } = require('./provenance-type');
9
+
10
+ function ownedMembers(index, owner) {
11
+ return (index.files.get(owner.file)?.symbols || []).filter(def =>
12
+ sameDeclaration(declarationIdentity(ownerDefinitionOf(index, def)), declarationIdentity(owner)));
13
+ }
14
+
15
+ function builtinFacts(index, owner, node, name, language) {
16
+ const entry = index.files.get(owner.file);
17
+ return { name, scope: scopeFields(referenceScope(node, language)),
18
+ imports: (entry.importBindings || []).filter(binding => (binding.alias || binding.name) === name),
19
+ assigned: (entry.moduleAssignedNames || []).includes(name),
20
+ declarations: (index.symbols.get(name) || []).filter(def => def.file === owner.file).map(declarationIdentity) };
21
+ }
22
+
23
+ function builtinUnbound(facts, name) {
24
+ return facts?.name === name && facts.scope?.scopeBinding === 'module' && facts.assigned === false &&
25
+ Array.isArray(facts.imports) && !facts.imports.length &&
26
+ Array.isArray(facts.declarations) && !facts.declarations.length;
27
+ }
28
+
29
+ function baseName(node) {
30
+ while (node?.type === 'subscript') node = node.childForFieldName('value');
31
+ if (node?.type === 'identifier') return { name: node.text, qualifier: null, root: node };
32
+ if (node?.type !== 'attribute') return null;
33
+ const object = node.childForFieldName('object'), name = node.childForFieldName('attribute');
34
+ let root = object;
35
+ while (root?.type === 'attribute') root = root.childForFieldName('object');
36
+ return root?.type === 'identifier' && name ? { name: name.text, qualifier: object.text, root } : null;
37
+ }
38
+
39
+ /** Raw class, base-binding and allocation syntax needed by the protocol replay. */
40
+ function collectConstructorProtocol(index, owner, seen = new Set()) {
41
+ const entry = index.files.get(owner.file), language = entry?.language;
42
+ const protocol = langTraits(language)?.constructorProtocol;
43
+ if (!protocol) return null;
44
+ const key = identityKey(declarationIdentity(owner));
45
+ if (seen.has(key)) return null;
46
+ const visited = new Set(seen).add(key);
47
+ const content = index._readFile(owner.file);
48
+ const shared = index._getParsedTree(owner.file, content, language);
49
+ const tree = shared || safeParse(getParser(language), content);
50
+ if (!tree) return null;
51
+ try {
52
+ // One native walk per tree and operation lists its class nodes for
53
+ // every owner a query reads (fix #435: one walk per owner).
54
+ const memo = typeof index._opMemo === 'function' && !index.isProvenanceFactIndex
55
+ ? index._opMemo('protocolClassNodes', () => new WeakMap()) : null;
56
+ let classNodes = memo?.get(tree);
57
+ if (!classNodes) {
58
+ classNodes = tree.rootNode.descendantsOfType(protocol.classNode);
59
+ memo?.set(tree, classNodes);
60
+ }
61
+ const nodes = classNodes.filter(node =>
62
+ node.childForFieldName('name')?.text === owner.name && node.startPosition.row + 1 >= owner.startLine &&
63
+ node.endPosition.row + 1 === owner.endLine);
64
+ if (nodes.length !== 1) return null;
65
+ const node = nodes[0], members = ownedMembers(index, owner);
66
+ const headerNode = node.childForFieldName(protocol.headerField);
67
+ const args = headerNode?.namedChildren || [];
68
+ const bases = args.filter(arg => arg.type !== protocol.keywordNode && !arg.type.endsWith('comment'));
69
+ const header = { name: owner.name, startLine: node.startPosition.row + 1, endLine: owner.endLine,
70
+ hasError: node.hasError, baseCount: bases.length,
71
+ baseEndLine: (headerNode || node).endPosition.row + 1,
72
+ keywords: args.filter(arg => arg.type === protocol.keywordNode).map(arg => arg.childForFieldName('name')?.text),
73
+ decorators: node.parent?.type === 'decorated_definition'
74
+ ? node.parent.namedChildren.filter(child => child.type === 'decorator').map(child => child.text) : [] };
75
+ const baseFacts = bases.map(base => {
76
+ const spelling = baseName(base);
77
+ if (!spelling) return null;
78
+ const site = { file: entry.relativePath, line: base.startPosition.row + 1, column: base.startPosition.column };
79
+ if (!spelling.qualifier && spelling.name === protocol.builtinRoot) {
80
+ return { site, builtin: builtinFacts(index, owner, spelling.root, spelling.name, language) };
81
+ }
82
+ const scope = scopeFields(referenceScope(spelling.root, language, new Map(), spelling.root.text,
83
+ { includeClasses: true }));
84
+ const parents = require('./constructor-calls').constructionTypeCandidates(index, spelling.name);
85
+ const matches = parents.filter(parent => parent.type === 'class').map(parent => ({ parent,
86
+ binding: captureTypeLookup(index, { file: owner.file, relativePath: entry.relativePath,
87
+ line: site.line, column: site.column, name: spelling.name, qualifier: spelling.qualifier,
88
+ construction: true }, parent) }))
89
+ .filter(match => match.binding);
90
+ if (matches.length !== 1) return { site, scope, binding: null, protocol: null };
91
+ return { site, scope, binding: matches[0].binding,
92
+ protocol: collectConstructorProtocol(index, matches[0].parent, visited) };
93
+ });
94
+ const allocators = members.filter(def => def.name === protocol.allocator).map(def => {
95
+ const functions = node.childForFieldName('body')?.namedChildren.flatMap(child =>
96
+ child.type === 'decorated_definition' ? child.namedChildren : [child]) || [];
97
+ const fn = functions.find(child => child.type === protocol.functionNode &&
98
+ child.childForFieldName('name')?.text === protocol.allocator &&
99
+ child.startPosition.row + 1 >= def.startLine && child.endPosition.row + 1 === def.endLine);
100
+ const rootName = fn?.descendantsOfType('identifier').find(token => token.text === protocol.builtinRoot &&
101
+ token.parent?.type === 'attribute' && sameNode(token.parent.childForFieldName('object'), token));
102
+ return { declaration: declarationIdentity(def), source: fn?.text || null,
103
+ builtin: rootName ? builtinFacts(index, owner, rootName, protocol.builtinRoot, language) : null };
104
+ });
105
+ return { owner: { ...declarationIdentity(owner), decorators: owner.decorators || [] }, header,
106
+ declarations: members.map(def => ({ ...declarationIdentity(def), decorators: def.decorators || [] })),
107
+ bases: baseFacts, allocators };
108
+ } finally { if (!shared) tree.delete?.(); }
109
+ }
110
+
111
+ function allocatorPreservesClass(data, language, protocol) {
112
+ if (!data.source || !builtinUnbound(data.builtin, protocol.builtinRoot)) return false;
113
+ const tree = safeParse(getParser(language), data.source);
114
+ if (!tree) return false;
115
+ try {
116
+ const fn = tree.rootNode.namedChild(0);
117
+ if (tree.rootNode.hasError || tree.rootNode.namedChildCount !== 1 || fn.type !== protocol.functionNode ||
118
+ fn.childForFieldName('name')?.text !== protocol.allocator ||
119
+ fn.endPosition.row - fn.startPosition.row !== data.declaration.endLine - data.declaration.startLine) return false;
120
+ const parameters = fn.childForFieldName('parameters');
121
+ const first = parameters?.namedChild(0);
122
+ const className = first?.type === 'identifier' ? first : first?.namedChildren.find(node => node.type === 'identifier');
123
+ if (!className) return false;
124
+ const classBindings = pythonBindingSites(fn, className.text);
125
+ if (classBindings.length !== 1 || !sameNode(classBindings[0], className) ||
126
+ pythonBindingSites(fn, protocol.builtinRoot).length) return false;
127
+ const body = fn.childForFieldName('body');
128
+ const statements = body?.namedChildren.filter(node => !node.type.endsWith('comment')) || [];
129
+ if (statements.at(-1)?.type !== 'return_statement') return false;
130
+ const owns = node => {
131
+ for (let parent = node.parent; parent; parent = parent.parent) {
132
+ if (parent.type === protocol.functionNode) return sameNode(parent, fn);
133
+ }
134
+ return false;
135
+ };
136
+ const preserves = (value, seen = new Set()) => {
137
+ if (!value) return false;
138
+ if (value.type === 'call') {
139
+ const callee = value.childForFieldName('function'), args = value.childForFieldName('arguments')?.namedChildren || [];
140
+ return callee?.type === 'attribute' && callee.childForFieldName('object')?.type === 'identifier' &&
141
+ callee.childForFieldName('object').text === protocol.builtinRoot &&
142
+ callee.childForFieldName('attribute')?.text === protocol.allocator && args.length === 1 &&
143
+ args[0].type === 'identifier' && args[0].text === className.text;
144
+ }
145
+ if (value.type !== 'identifier' || seen.has(value.text)) return false;
146
+ const bindings = pythonBindingSites(fn, value.text);
147
+ const binding = bindings[0], assignment = binding?.parent;
148
+ return bindings.length === 1 && assignment.type === 'assignment' &&
149
+ sameNode(assignment.childForFieldName('left'), binding) && binding.startIndex < value.startIndex &&
150
+ preserves(assignment.childForFieldName('right'), new Set(seen).add(value.text));
151
+ };
152
+ const returns = fn.descendantsOfType('return_statement').filter(owns);
153
+ if (!returns.length || returns.some(node => !preserves(node.namedChild(0)))) return false;
154
+ const protocolNames = new Set([protocol.initializer, protocol.allocator, '__class__', '__call__']);
155
+ for (const assignment of fn.descendantsOfType(['assignment', 'augmented_assignment', 'delete_statement']).filter(owns)) {
156
+ const left = assignment.childForFieldName('left') || assignment.namedChild(0);
157
+ if (left?.type === 'attribute' && protocolNames.has(left.childForFieldName('attribute')?.text)) return false;
158
+ }
159
+ return true;
160
+ } finally { tree.delete?.(); }
161
+ }
162
+
163
+ function validateConstructorProtocol(data, owner, members, language, allocationNeeded = true, seen = new Set()) {
164
+ const protocol = langTraits(language)?.constructorProtocol;
165
+ if (!protocol) return true;
166
+ const key = identityKey(owner), header = data?.header;
167
+ if (!data || seen.has(key) || !sameDeclaration(data.owner, owner) || !header ||
168
+ header.name !== owner.name || header.startLine < owner.startLine || header.endLine !== owner.endLine ||
169
+ header.hasError || data.owner.decorators?.length || !Array.isArray(header.decorators) || header.decorators.length ||
170
+ !Array.isArray(header.keywords) || header.keywords.includes(protocol.metaclass) ||
171
+ !Array.isArray(data.declarations) || !Array.isArray(data.bases) || data.bases.length !== header.baseCount ||
172
+ data.declarations.some(def => def.file !== owner.file || def.className !== owner.name ||
173
+ def.startLine < owner.startLine || def.endLine > owner.endLine ||
174
+ (def.namespace || null) !== (owner.namespace || null)) ||
175
+ data.declarations.some(def => members.some(member => sameDeclaration(member, def)) && def.decorators?.length)) return false;
176
+ const allocators = data.declarations.filter(def => def.name === protocol.allocator);
177
+ if (allocationNeeded && allocators.length && (allocators.length !== 1 || allocators[0].decorators?.length ||
178
+ !Array.isArray(data.allocators) || data.allocators.length !== 1 ||
179
+ !sameDeclaration(data.allocators[0].declaration, allocators[0]) ||
180
+ !allocatorPreservesClass(data.allocators[0], language, protocol))) return false;
181
+ const visited = new Set(seen).add(key);
182
+ return data.bases.every(base => {
183
+ if (!base || base.site?.file !== owner.file || base.site.line < header.startLine ||
184
+ base.site.line > header.baseEndLine) return false;
185
+ if (base.builtin) return builtinUnbound(base.builtin, protocol.builtinRoot);
186
+ const parent = base.protocol?.owner;
187
+ const scope = base.scope;
188
+ return parent && (scope?.scopeBinding === 'module' || scope?.scopeBinding === 'local' &&
189
+ scope.scopeDefLines?.includes(parent.startLine) && parent.file === owner.file) &&
190
+ validateTypeLookup(base.binding, parent, base.site) &&
191
+ validateConstructorProtocol(base.protocol, parent, members, language,
192
+ allocationNeeded && allocators.length === 0, visited);
193
+ });
194
+ }
195
+
196
+ module.exports = { collectConstructorProtocol, validateConstructorProtocol };