ucn 5.4.1 → 5.5.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.
Files changed (87) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +18 -12
  6. package/cli/index.js +42 -16
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +30 -0
  9. package/core/analysis.js +508 -264
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +522 -31
  16. package/core/callers.js +12053 -1047
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis-ext.js +6 -6
  38. package/core/output/analysis.js +18 -2
  39. package/core/output/brief.js +3 -0
  40. package/core/output/doctor.js +4 -0
  41. package/core/output/endpoints.js +15 -6
  42. package/core/output/find.js +11 -2
  43. package/core/output/lines.js +8 -2
  44. package/core/output/refactoring.js +121 -14
  45. package/core/output/reporting.js +31 -3
  46. package/core/output/shared.js +14 -0
  47. package/core/output-budget.js +316 -122
  48. package/core/parallel-build.js +124 -47
  49. package/core/parser.js +3 -2
  50. package/core/project.js +457 -60
  51. package/core/provenance-facts.js +156 -12
  52. package/core/provenance-overload.js +30 -3
  53. package/core/provenance.js +104 -30
  54. package/core/pytest-fixtures.js +434 -0
  55. package/core/python-fixture-flow.js +17 -3
  56. package/core/python-modules.js +173 -0
  57. package/core/reflection.js +877 -0
  58. package/core/registry.js +1 -1
  59. package/core/reporting.js +234 -52
  60. package/core/request-helpers.js +561 -0
  61. package/core/route-graph.js +1922 -0
  62. package/core/route-lists.js +2414 -0
  63. package/core/rust-macro-expansion.js +2113 -0
  64. package/core/rust-modules.js +185 -0
  65. package/core/search.js +121 -29
  66. package/core/shared.js +19 -1
  67. package/core/test-name-bindings.js +256 -0
  68. package/core/type-denotation.js +1161 -0
  69. package/core/type-references.js +3229 -0
  70. package/core/verify.js +3360 -672
  71. package/languages/adapter.js +31 -5
  72. package/languages/c-family.js +4061 -212
  73. package/languages/c-macro-invocations.js +380 -0
  74. package/languages/c-preprocessor.js +254 -0
  75. package/languages/csharp.js +1406 -49
  76. package/languages/go.js +1243 -70
  77. package/languages/index.js +728 -6
  78. package/languages/java.js +473 -61
  79. package/languages/javascript.js +1270 -197
  80. package/languages/lexical-scope.js +1280 -0
  81. package/languages/python.js +1557 -72
  82. package/languages/rust-macro-rules.js +1020 -0
  83. package/languages/rust-value-flow.js +416 -0
  84. package/languages/rust.js +2237 -115
  85. package/languages/utils.js +298 -4
  86. package/mcp/server.js +25 -11
  87. package/package.json +1 -1
package/core/brief.js CHANGED
@@ -123,6 +123,7 @@ function brief(index, name, options = {}) {
123
123
  ...(def.decorators && def.decorators.length && { decorators: def.decorators }),
124
124
  ...(def.docstring && { docstring: firstSentence(def.docstring) }),
125
125
  ...(def.className && { className: def.className }),
126
+ ...(def.macroExpansion && { macroExpansion: def.macroExpansion }),
126
127
  ...(def.isAsync && { isAsync: true }),
127
128
  ...(def.isGenerator && { isGenerator: true }),
128
129
  };
@@ -186,9 +187,14 @@ function brief(index, name, options = {}) {
186
187
  const fileImports = collectImportNames(fileEntry);
187
188
 
188
189
  const sideEffects = classifySideEffects(bodyText, language, fileImports);
190
+ // The operation's shared tree when one is active (fix #365); the
191
+ // file's indexed hash pins it to this content.
192
+ const indexedTree = fileEntry && typeof index._getParsedTree === 'function'
193
+ ? index._getParsedTree(filePath, fileContent, language) : null;
189
194
  const complexity = computeAstComplexity(fileContent, language, {
190
195
  startLine: def.startLine || 1,
191
196
  endLine: def.endLine || def.startLine || 1,
197
+ ...(indexedTree && { tree: indexedTree }),
192
198
  });
193
199
 
194
200
  return {
@@ -3,12 +3,17 @@
3
3
  /**
4
4
  * core/build-worker.js - Worker thread for parallel index building
5
5
  *
6
- * Receives a chunk of file paths, parses each file, extracts symbols and calls,
7
- * then sends results back to the main thread via MessagePort.
6
+ * Claims files from a shared queue, parses each file, extracts symbols and
7
+ * calls, then sends results back to the main thread via MessagePort.
8
8
  * Mirrors the indexFile() logic in project.js.
9
9
  */
10
10
 
11
- const { workerData } = require('worker_threads');
11
+ // Reuse V8 code caches across processes (Node >= 22.1; fix #365): compiling
12
+ // the engine's modules is a fixed cost of every command. Node validates each
13
+ // entry against the source, and NODE_DISABLE_COMPILE_CACHE turns it off.
14
+ try { require('module').enableCompileCache?.(); } catch (_) { /* optional */ }
15
+
16
+ const { workerData, receiveMessageOnPort } = require('worker_threads');
12
17
  const fs = require('fs');
13
18
  const path = require('path');
14
19
  const crypto = require('crypto');
@@ -16,7 +21,42 @@ const { detectLanguage, getParser, getLanguageAdapter } = require('../languages'
16
21
  const { validateFileIR } = require('./ir');
17
22
  const { createFileEntryFromIR, populateFileEntryFromIR } = require('./index-ir');
18
23
 
19
- const { files, rootDir, existingHashes, signal, workerIndex, port } = workerData;
24
+ const { files, rootDir, existingHashes, signal, workerIndex, queueIndex, port, control, dictionarySignal } = workerData;
25
+ const { contextFor } = require('./external-macros');
26
+ // The main thread computes the macro dictionary while the workers parse
27
+ // (fix #396) and posts it before raising the signal. A file waits for it
28
+ // only when its recovery consults it.
29
+ let macroDictionary = workerData.macroDictionary || null;
30
+ let dictionaryPending = !!dictionarySignal;
31
+ function awaitDictionary() {
32
+ if (dictionaryPending) {
33
+ Atomics.wait(new Int32Array(dictionarySignal), 0, 0);
34
+ macroDictionary = receiveMessageOnPort(port)?.message?.macroDictionary || null;
35
+ dictionaryPending = false;
36
+ }
37
+ return macroDictionary;
38
+ }
39
+ const EMPTY_MACROS = new Map();
40
+ function externalMacrosFor(language) {
41
+ if (!dictionaryPending) return macroDictionary ? contextFor(macroDictionary, language) : null;
42
+ if (!getLanguageAdapter(language)?.traits?.textualIncludes) return null;
43
+ let resolved;
44
+ const resolve = () => {
45
+ if (resolved === undefined) {
46
+ const dictionary = awaitDictionary();
47
+ resolved = dictionary ? contextFor(dictionary, language) : null;
48
+ }
49
+ return resolved;
50
+ };
51
+ // Every build's files share one dictionary, so one key serves this
52
+ // worker's tree cache.
53
+ return {
54
+ key: 'build',
55
+ consulted: new Set(),
56
+ get objectBodies() { return resolve()?.objectBodies || EMPTY_MACROS; },
57
+ get functionMacros() { return resolve()?.functionMacros || EMPTY_MACROS; },
58
+ };
59
+ }
20
60
  const signalArray = new Int32Array(signal);
21
61
 
22
62
  function processFile(filePath) {
@@ -43,7 +83,9 @@ function processFile(filePath) {
43
83
  // by both worker and sequential builds.
44
84
  const adapter = getLanguageAdapter(language);
45
85
  const parser = getParser(language);
46
- const ir = adapter.analyze(content, parser, filePath);
86
+ // C/C++: the project macro dictionary (fix #396), as indexFile reads it.
87
+ const externalMacros = externalMacrosFor(language);
88
+ const ir = adapter.analyze(content, parser, filePath, externalMacros ? { externalMacros } : undefined);
47
89
  const irFailures = validateFileIR(ir);
48
90
  if (irFailures.length > 0) {
49
91
  throw new Error(`Invalid ${language} IR: ${irFailures.join('; ')}`);
@@ -101,23 +143,45 @@ function processFile(filePath) {
101
143
  }
102
144
 
103
145
  // Process all files
146
+ (async () => {
104
147
  try {
105
- const results = [];
106
- for (const filePath of files) {
148
+ for (;;) {
149
+ const next = Atomics.add(signalArray, queueIndex, 1);
150
+ if (next >= files.length) break;
151
+ const filePath = files[next];
152
+ let result;
107
153
  try {
108
- results.push(processFile(filePath));
154
+ result = processFile(filePath);
109
155
  } catch (e) {
110
- results.push({ filePath, error: e.message });
156
+ result = { filePath, error: e.message };
111
157
  }
158
+ port.postMessage([result]);
159
+ // Yield between files so native-tree finalizers of the previous
160
+ // file's garbage run while the build continues (fix #365): a worker
161
+ // that never returns to its event loop keeps every tree it parsed
162
+ // alive until it exits, and with no idle workers the peaks coincide.
163
+ await new Promise(resolve => setImmediate(resolve));
112
164
  }
113
-
114
- port.postMessage(results);
115
- port.close();
165
+ // The dictionary message is consumed before any later job on the port.
166
+ awaitDictionary();
116
167
  Atomics.store(signalArray, workerIndex, 1);
117
168
  Atomics.notify(signalArray, workerIndex);
118
169
  } catch (e) {
119
- // Worker-level error — signal completion with empty results
170
+ // Worker-level error: signal completion (2 = not reusable).
120
171
  try { port.postMessage([]); port.close(); } catch (_) { /* ignore */ }
121
- Atomics.store(signalArray, workerIndex, 1);
172
+ Atomics.store(signalArray, workerIndex, 2);
122
173
  Atomics.notify(signalArray, workerIndex);
174
+ return;
175
+ }
176
+ // A worker kept for the build's next phase (fix #388) waits for its job:
177
+ // Rust macro expansion, run with this thread's already optimized parser.
178
+ if (control) {
179
+ const controlArray = new Int32Array(control);
180
+ Atomics.wait(controlArray, workerIndex, 0);
181
+ if (Atomics.load(controlArray, workerIndex) === 2) {
182
+ const job = receiveMessageOnPort(port)?.message;
183
+ if (job) require('./rust-macro-expansion').runExpansionQueue(job, port);
184
+ }
123
185
  }
186
+ try { port.close(); } catch (_) { /* closed */ }
187
+ })();