ultracode 5.3.0 → 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.
- package/dist/chunks/analysis-tool-handlers-H2RXLDPX.js +817 -0
- package/dist/chunks/analysis-tool-handlers-RJZAR6VT.js +817 -0
- package/dist/chunks/analysis-tool-handlers-Z2RF24T7.js +13 -0
- package/dist/chunks/autodoc-tool-handlers-CV5JEQUA.js +1112 -0
- package/dist/chunks/autodoc-tool-handlers-EHTNCH6I.js +1112 -0
- package/dist/chunks/autodoc-tool-handlers-MECXQJ2K.js +138 -0
- package/dist/chunks/chaos-CO7TOBOJ.js +18 -0
- package/dist/chunks/chaos-VM2PXERO.js +1573 -0
- package/dist/chunks/chaos-W3XRVJ7K.js +1564 -0
- package/dist/chunks/chunk-6K37BWK5.js +439 -0
- package/dist/chunks/chunk-EALTCYHZ.js +10 -0
- package/dist/chunks/chunk-FTBE7VMY.js +316 -0
- package/dist/chunks/chunk-KBW6LRQP.js +322 -0
- package/dist/chunks/chunk-NKUHX4CU.js +5 -0
- package/dist/chunks/chunk-NZFF4DQ4.js +3179 -0
- package/dist/chunks/chunk-RGP5UVQ7.js +3179 -0
- package/dist/chunks/chunk-RMZXFGQZ.js +322 -0
- package/dist/chunks/chunk-UG44F23Y.js +316 -0
- package/dist/chunks/chunk-V2SCB5H5.js +4403 -0
- package/dist/chunks/chunk-V6JAQNM3.js +1 -0
- package/dist/chunks/chunk-XFGXM4CR.js +4403 -0
- package/dist/chunks/dev-agent-JVIGBMHQ.js +1 -0
- package/dist/chunks/dev-agent-TRVP5U6N.js +1624 -0
- package/dist/chunks/dev-agent-Y5G5WKQ4.js +1624 -0
- package/dist/chunks/graph-storage-factory-AYZ57YSL.js +13 -0
- package/dist/chunks/graph-storage-factory-GTAIJEI5.js +1 -0
- package/dist/chunks/graph-storage-factory-T2WO5QVG.js +13 -0
- package/dist/chunks/incremental-updater-KDIQGAUU.js +14 -0
- package/dist/chunks/incremental-updater-OJRSTO3Q.js +1 -0
- package/dist/chunks/incremental-updater-SBEBH7KF.js +14 -0
- package/dist/chunks/indexer-agent-H3QIEL3Z.js +21 -0
- package/dist/chunks/indexer-agent-KHF5JMV7.js +21 -0
- package/dist/chunks/indexer-agent-SHJD6Z77.js +1 -0
- package/dist/chunks/indexing-pipeline-J6Z4BHKF.js +1 -0
- package/dist/chunks/indexing-pipeline-OY3337QN.js +249 -0
- package/dist/chunks/indexing-pipeline-WCXIDMAP.js +249 -0
- package/dist/chunks/merge-agent-LSUBDJB2.js +2481 -0
- package/dist/chunks/merge-agent-MJEW3HWU.js +2481 -0
- package/dist/chunks/merge-agent-O45OXF33.js +11 -0
- package/dist/chunks/merge-tool-handlers-BDSVNQVZ.js +277 -0
- package/dist/chunks/merge-tool-handlers-HP7DRBXJ.js +1 -0
- package/dist/chunks/merge-tool-handlers-RUJAKE3D.js +277 -0
- package/dist/chunks/pattern-tool-handlers-L62W3CXR.js +1549 -0
- package/dist/chunks/pattern-tool-handlers-SAHX2CVW.js +13 -0
- package/dist/chunks/query-agent-3TWDFIMT.js +191 -0
- package/dist/chunks/query-agent-HXQ3BMMF.js +191 -0
- package/dist/chunks/query-agent-USMC2GNG.js +1 -0
- package/dist/chunks/semantic-agent-MQCAWIAB.js +6381 -0
- package/dist/chunks/semantic-agent-NDGR3NAK.js +6381 -0
- package/dist/chunks/semantic-agent-S4ZL6GZC.js +137 -0
- package/dist/index.js +17 -17
- package/dist/roslyn-addon/.build-hash +1 -1
- package/dist/roslyn-addon/ILGPU.Algorithms.dll +0 -0
- package/dist/roslyn-addon/ILGPU.dll +0 -0
- package/dist/roslyn-addon/UltraCode.CSharp.deps.json +35 -0
- package/dist/roslyn-addon/UltraCode.CSharp.dll +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1564 @@
|
|
|
1
|
+
import './chunk-CTXFPNDA.js';
|
|
2
|
+
import './chunk-TJSOOFXA.js';
|
|
3
|
+
import { init_logging, log } from './chunk-VCCBEJQ5.js';
|
|
4
|
+
import './chunk-NAQKA54E.js';
|
|
5
|
+
|
|
6
|
+
// src/analysis/chaos/angular-patterns.ts
|
|
7
|
+
function isStateIdentifier(name) {
|
|
8
|
+
const lowerName = name.toLowerCase();
|
|
9
|
+
const stateKeywords = [
|
|
10
|
+
"state",
|
|
11
|
+
"config",
|
|
12
|
+
"context",
|
|
13
|
+
"data",
|
|
14
|
+
"token",
|
|
15
|
+
"user",
|
|
16
|
+
"auth",
|
|
17
|
+
"session",
|
|
18
|
+
"settings",
|
|
19
|
+
"cache",
|
|
20
|
+
"store",
|
|
21
|
+
"value",
|
|
22
|
+
"status",
|
|
23
|
+
"flag",
|
|
24
|
+
"id"
|
|
25
|
+
];
|
|
26
|
+
if (stateKeywords.some((keyword) => lowerName.includes(keyword))) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const patterns = [
|
|
30
|
+
/^_[a-z]+$/,
|
|
31
|
+
// _token
|
|
32
|
+
/^is[A-Z]/,
|
|
33
|
+
// isLoading
|
|
34
|
+
/^has[A-Z]/,
|
|
35
|
+
// hasToken
|
|
36
|
+
/[Ss]tate$/,
|
|
37
|
+
// userState
|
|
38
|
+
/[Cc]onfig$/,
|
|
39
|
+
// apiConfig
|
|
40
|
+
/Subject$/
|
|
41
|
+
// userSubject
|
|
42
|
+
];
|
|
43
|
+
return patterns.some((pattern) => pattern.test(name));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/analysis/chaos/chaos-analyzer.ts
|
|
47
|
+
init_logging();
|
|
48
|
+
|
|
49
|
+
// src/analysis/chaos/csharp-patterns.ts
|
|
50
|
+
var CSHARP_ASYNC_APIS = [
|
|
51
|
+
"Task.Run",
|
|
52
|
+
"Task.Factory.StartNew",
|
|
53
|
+
"Task.WhenAll",
|
|
54
|
+
"Task.WhenAny",
|
|
55
|
+
"Task.Delay",
|
|
56
|
+
"Parallel.ForEach",
|
|
57
|
+
"Parallel.For",
|
|
58
|
+
"Parallel.ForEachAsync",
|
|
59
|
+
"ThreadPool.QueueUserWorkItem",
|
|
60
|
+
"Channel<",
|
|
61
|
+
"Channel.CreateBounded",
|
|
62
|
+
"Channel.CreateUnbounded",
|
|
63
|
+
"Timer",
|
|
64
|
+
"PeriodicTimer",
|
|
65
|
+
"BackgroundService",
|
|
66
|
+
"IHostedService"
|
|
67
|
+
];
|
|
68
|
+
var CSHARP_LOCK_PATTERNS = [
|
|
69
|
+
/\block\s*\(/,
|
|
70
|
+
/SemaphoreSlim/,
|
|
71
|
+
/Monitor\.(Enter|Exit|TryEnter)/,
|
|
72
|
+
/Interlocked\./,
|
|
73
|
+
/ReaderWriterLockSlim/,
|
|
74
|
+
/SpinLock/,
|
|
75
|
+
/Volatile\.(Read|Write)/,
|
|
76
|
+
/Mutex/,
|
|
77
|
+
/ConcurrentDictionary/,
|
|
78
|
+
/ConcurrentBag/,
|
|
79
|
+
/ConcurrentQueue/,
|
|
80
|
+
/ConcurrentStack/,
|
|
81
|
+
/ImmutableArray/,
|
|
82
|
+
/ImmutableList/,
|
|
83
|
+
/ImmutableDictionary/
|
|
84
|
+
];
|
|
85
|
+
function isCSharpStateIdentifier(entity) {
|
|
86
|
+
const name = entity.name.toLowerCase();
|
|
87
|
+
const modifiers = entity.metadata?.modifiers || [];
|
|
88
|
+
if (modifiers.includes("readonly")) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const diServicePatterns = [
|
|
92
|
+
/^_log(ger)?$/i,
|
|
93
|
+
/^_(http)?client$/i,
|
|
94
|
+
/^_mediator$/i,
|
|
95
|
+
/^_mapper$/i,
|
|
96
|
+
/^_sender$/i,
|
|
97
|
+
/^_(message|event)?bus$/i,
|
|
98
|
+
/^_publisher$/i,
|
|
99
|
+
/^_inner$/i,
|
|
100
|
+
/^_next$/i,
|
|
101
|
+
/^_handler$/i,
|
|
102
|
+
/^_service$/i,
|
|
103
|
+
/^_provider$/i,
|
|
104
|
+
/^_factory$/i,
|
|
105
|
+
/^_repository$/i,
|
|
106
|
+
/^_config(uration)?$/i,
|
|
107
|
+
/^_options$/i,
|
|
108
|
+
/^_settings$/i,
|
|
109
|
+
/^_env(ironment)?$/i
|
|
110
|
+
];
|
|
111
|
+
if (diServicePatterns.some((p) => p.test(entity.name))) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (modifiers.includes("static") && !modifiers.includes("const")) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const stateKeywords = ["state", "cache", "connection", "session", "current", "singleton", "shared", "global"];
|
|
118
|
+
if (stateKeywords.some((kw) => name.includes(kw))) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const weakStateKeywords = ["config", "configuration", "settings", "options", "context", "token", "instance"];
|
|
122
|
+
if (weakStateKeywords.some((kw) => name.includes(kw)) && !/^_[a-z]/.test(entity.name)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const typeStr = entity.type;
|
|
126
|
+
if (/^_[a-z]/.test(entity.name) && (entity.type === "variable" /* VARIABLE */ || entity.type === "constant" /* CONSTANT */ || typeStr === "field" || typeStr === "property")) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (/^(Is|Has|Can|Should|Current|Last|Previous)/.test(entity.name)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
function detectCSharpChaosPatterns(entities) {
|
|
135
|
+
const patterns = [];
|
|
136
|
+
const classesByFile = /* @__PURE__ */ new Map();
|
|
137
|
+
for (const entity of entities) {
|
|
138
|
+
if (entity.language !== "csharp" && entity.metadata?.language !== "csharp") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const modifiers = entity.metadata?.modifiers || [];
|
|
142
|
+
const returnType = entity.metadata?.returnType || "";
|
|
143
|
+
const params = entity.metadata?.parameters || [];
|
|
144
|
+
const line = entity.location?.start?.line || 0;
|
|
145
|
+
const typeStr = entity.type;
|
|
146
|
+
const isFieldLike = entity.type === "variable" /* VARIABLE */ || entity.type === "constant" /* CONSTANT */ || typeStr === "field" || typeStr === "property";
|
|
147
|
+
const isMethodLike = entity.type === "method" /* METHOD */ || entity.type === "function" /* FUNCTION */ || typeStr === "constructor";
|
|
148
|
+
if (isFieldLike && modifiers.includes("static") && !modifiers.includes("readonly") && !modifiers.includes("const")) {
|
|
149
|
+
patterns.push({
|
|
150
|
+
pattern: "mutable-static",
|
|
151
|
+
severity: "critical",
|
|
152
|
+
entityId: entity.id,
|
|
153
|
+
entityName: entity.name,
|
|
154
|
+
filePath: entity.filePath,
|
|
155
|
+
line,
|
|
156
|
+
description: `Mutable static field '${entity.name}' \u2014 shared across all threads without protection`,
|
|
157
|
+
suggestion: "Use ConcurrentDictionary, add readonly, or move state to DI-managed service"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (isMethodLike && modifiers.includes("async") && (returnType === "void" || returnType === "Void")) {
|
|
161
|
+
patterns.push({
|
|
162
|
+
pattern: "async-void",
|
|
163
|
+
severity: "high",
|
|
164
|
+
entityId: entity.id,
|
|
165
|
+
entityName: entity.name,
|
|
166
|
+
filePath: entity.filePath,
|
|
167
|
+
line,
|
|
168
|
+
description: `async void method '${entity.name}' \u2014 exceptions will crash the process`,
|
|
169
|
+
suggestion: "Replace 'async void' with 'async Task'. Only use async void for event handlers"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (entity.type === "class" /* CLASS */) {
|
|
173
|
+
const fileClasses = classesByFile.get(entity.filePath) || [];
|
|
174
|
+
fileClasses.push(entity);
|
|
175
|
+
classesByFile.set(entity.filePath, fileClasses);
|
|
176
|
+
}
|
|
177
|
+
if (isMethodLike && (entity.name === ".ctor" || typeStr === "constructor") && params.length >= 10) {
|
|
178
|
+
patterns.push({
|
|
179
|
+
pattern: "god-service",
|
|
180
|
+
severity: "medium",
|
|
181
|
+
entityId: entity.id,
|
|
182
|
+
entityName: entity.name,
|
|
183
|
+
filePath: entity.filePath,
|
|
184
|
+
line,
|
|
185
|
+
description: `Constructor with ${params.length} parameters \u2014 likely a God Service violating SRP`,
|
|
186
|
+
suggestion: "Split into smaller focused services. Group related dependencies into aggregate services"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (isMethodLike && modifiers.includes("async") && returnType !== "void" && returnType !== "Void" && (returnType.includes("Task") || returnType.includes("ValueTask")) && !params.some((p) => p.type?.includes("CancellationToken"))) {
|
|
190
|
+
patterns.push({
|
|
191
|
+
pattern: "missing-cancellation",
|
|
192
|
+
severity: "medium",
|
|
193
|
+
entityId: entity.id,
|
|
194
|
+
entityName: entity.name,
|
|
195
|
+
filePath: entity.filePath,
|
|
196
|
+
line,
|
|
197
|
+
description: `Async method '${entity.name}' without CancellationToken \u2014 cannot be cancelled gracefully`,
|
|
198
|
+
suggestion: "Add CancellationToken parameter and pass it to downstream async calls"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (isFieldLike && modifiers.includes("static") && !modifiers.includes("readonly") && !modifiers.includes("const")) {
|
|
202
|
+
const decorators = entity.metadata?.decorators || [];
|
|
203
|
+
const hasSingletonIndicator = decorators.some(
|
|
204
|
+
(d) => d.name === "Singleton" || d.name === "SingleInstance" || d.name === "ServiceLifetime.Singleton"
|
|
205
|
+
) || entity.name.toLowerCase().includes("singleton");
|
|
206
|
+
if (hasSingletonIndicator) {
|
|
207
|
+
patterns.push({
|
|
208
|
+
pattern: "singleton-mutable-state",
|
|
209
|
+
severity: "high",
|
|
210
|
+
entityId: entity.id,
|
|
211
|
+
entityName: entity.name,
|
|
212
|
+
filePath: entity.filePath,
|
|
213
|
+
line,
|
|
214
|
+
description: `Singleton class has mutable field '${entity.name}' \u2014 concurrent access will cause data races`,
|
|
215
|
+
suggestion: "Use ConcurrentDictionary/ImmutableCollections, change to Scoped lifetime, or add lock synchronization"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return patterns;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/analysis/chaos/state-detector.ts
|
|
224
|
+
init_logging();
|
|
225
|
+
|
|
226
|
+
// src/analysis/chaos/race-detector.ts
|
|
227
|
+
init_logging();
|
|
228
|
+
var HIDDEN_ASYNC_APIS = [
|
|
229
|
+
// Storage APIs - synchronous but with I/O delays
|
|
230
|
+
"localStorage",
|
|
231
|
+
"sessionStorage",
|
|
232
|
+
"indexedDB",
|
|
233
|
+
"caches",
|
|
234
|
+
// Cache API
|
|
235
|
+
// DOM APIs with async rendering effects
|
|
236
|
+
"requestAnimationFrame",
|
|
237
|
+
"requestIdleCallback",
|
|
238
|
+
"IntersectionObserver",
|
|
239
|
+
"MutationObserver",
|
|
240
|
+
"ResizeObserver",
|
|
241
|
+
"PerformanceObserver",
|
|
242
|
+
// Network/Communication
|
|
243
|
+
"fetch",
|
|
244
|
+
"XMLHttpRequest",
|
|
245
|
+
"WebSocket",
|
|
246
|
+
"EventSource",
|
|
247
|
+
"BroadcastChannel",
|
|
248
|
+
"SharedWorker",
|
|
249
|
+
"ServiceWorker",
|
|
250
|
+
// Timers
|
|
251
|
+
"setTimeout",
|
|
252
|
+
"setInterval",
|
|
253
|
+
"queueMicrotask",
|
|
254
|
+
// History API - can trigger events async
|
|
255
|
+
"history.pushState",
|
|
256
|
+
"history.replaceState",
|
|
257
|
+
// Clipboard API
|
|
258
|
+
"navigator.clipboard",
|
|
259
|
+
// Geolocation
|
|
260
|
+
"navigator.geolocation",
|
|
261
|
+
// Media APIs
|
|
262
|
+
"MediaRecorder",
|
|
263
|
+
"RTCPeerConnection",
|
|
264
|
+
// File APIs
|
|
265
|
+
"FileReader",
|
|
266
|
+
"FileWriter",
|
|
267
|
+
// C# Task-based async APIs
|
|
268
|
+
"Task.Run",
|
|
269
|
+
"Task.Factory.StartNew",
|
|
270
|
+
"Task.WhenAll",
|
|
271
|
+
"Task.WhenAny",
|
|
272
|
+
"Task.Delay",
|
|
273
|
+
"Parallel.ForEach",
|
|
274
|
+
"Parallel.For",
|
|
275
|
+
"Parallel.ForEachAsync",
|
|
276
|
+
"ThreadPool.QueueUserWorkItem",
|
|
277
|
+
"Channel.",
|
|
278
|
+
"Timer",
|
|
279
|
+
"PeriodicTimer"
|
|
280
|
+
];
|
|
281
|
+
var ASYNC_BEHAVIOR_PATTERNS = [
|
|
282
|
+
/\.addEventListener\s*\(/,
|
|
283
|
+
/\.on[A-Z]\w+\s*=/,
|
|
284
|
+
/new\s+Promise\s*\(/,
|
|
285
|
+
/\.then\s*\(/,
|
|
286
|
+
/\.catch\s*\(/,
|
|
287
|
+
/\.finally\s*\(/,
|
|
288
|
+
/callback/i,
|
|
289
|
+
/handler/i,
|
|
290
|
+
/listener/i,
|
|
291
|
+
/\.subscribe\s*\(/,
|
|
292
|
+
/\.pipe\s*\(/,
|
|
293
|
+
/rxjs/i,
|
|
294
|
+
/observable/i,
|
|
295
|
+
/subject/i,
|
|
296
|
+
/\$\./,
|
|
297
|
+
// jQuery async patterns
|
|
298
|
+
/emit/i,
|
|
299
|
+
/dispatch/i,
|
|
300
|
+
/broadcast/i,
|
|
301
|
+
// C# async patterns
|
|
302
|
+
/async\s+Task/,
|
|
303
|
+
/async\s+ValueTask/,
|
|
304
|
+
/\.ConfigureAwait\(/,
|
|
305
|
+
/CancellationToken/,
|
|
306
|
+
/IAsyncEnumerable/
|
|
307
|
+
];
|
|
308
|
+
var RaceDetector = class {
|
|
309
|
+
constructor(storage) {
|
|
310
|
+
this.storage = storage;
|
|
311
|
+
}
|
|
312
|
+
_entityCache = null;
|
|
313
|
+
/** Pre-loaded relationship lookup: entityId → Relationship[] */
|
|
314
|
+
_relLookup = null;
|
|
315
|
+
/** Cache "is parent async" result per entityId to avoid re-processing */
|
|
316
|
+
_relCache = /* @__PURE__ */ new Map();
|
|
317
|
+
/**
|
|
318
|
+
* Set pre-loaded entity cache to avoid N+1 DB queries.
|
|
319
|
+
* Call before analyzeRaces() and clear after with clearEntityCache().
|
|
320
|
+
*/
|
|
321
|
+
setEntityCache(entities) {
|
|
322
|
+
this._entityCache = new Map(entities.map((e) => [e.id, e]));
|
|
323
|
+
}
|
|
324
|
+
/** Set pre-loaded relationship lookup to avoid per-entity DB queries */
|
|
325
|
+
setRelationshipCache(lookup) {
|
|
326
|
+
this._relLookup = lookup;
|
|
327
|
+
}
|
|
328
|
+
clearEntityCache() {
|
|
329
|
+
this._entityCache = null;
|
|
330
|
+
this._relLookup = null;
|
|
331
|
+
this._relCache.clear();
|
|
332
|
+
}
|
|
333
|
+
/** Lookup entity from cache first, fallback to DB */
|
|
334
|
+
async getEntityCached(id) {
|
|
335
|
+
if (this._entityCache) {
|
|
336
|
+
return this._entityCache.get(id) || null;
|
|
337
|
+
}
|
|
338
|
+
return this.storage.getEntity(id);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Analyze state operations for race conditions
|
|
342
|
+
*/
|
|
343
|
+
async analyzeRaces(stateIdentifier, operations) {
|
|
344
|
+
const readers = operations.filter((op) => op.operationType === "read" || op.operationType === "check");
|
|
345
|
+
const writers = operations.filter(
|
|
346
|
+
(op) => op.operationType === "write" || op.operationType === "initialize" || op.operationType === "emit"
|
|
347
|
+
);
|
|
348
|
+
const tMut = performance.now();
|
|
349
|
+
const mutations = await this.analyzeMutations(writers);
|
|
350
|
+
const dtMut = performance.now() - tMut;
|
|
351
|
+
if (dtMut > 200) {
|
|
352
|
+
log.w("CHAOS_RACE", "slow_mutations", {
|
|
353
|
+
id: stateIdentifier,
|
|
354
|
+
writers: writers.length,
|
|
355
|
+
ms: +dtMut.toFixed(0),
|
|
356
|
+
cached: !!this._entityCache
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const factors = this.calculateRiskFactors(mutations);
|
|
360
|
+
const conflicts = this.detectConflicts(stateIdentifier, mutations, readers);
|
|
361
|
+
const raceRisk = this.calculateRaceRisk(factors, conflicts);
|
|
362
|
+
return {
|
|
363
|
+
stateIdentifier,
|
|
364
|
+
readers: readers.length,
|
|
365
|
+
writers: writers.length,
|
|
366
|
+
asyncWriters: mutations.filter((m) => m.isAsync).length,
|
|
367
|
+
unprotectedWriters: mutations.filter((m) => !m.hasLock).length,
|
|
368
|
+
raceRisk,
|
|
369
|
+
raceReason: this.generateRaceReason(factors, conflicts),
|
|
370
|
+
conflicts,
|
|
371
|
+
mutations
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Convert state operations to detailed mutation points.
|
|
376
|
+
* Uses parallel async context detection for better throughput.
|
|
377
|
+
*/
|
|
378
|
+
async analyzeMutations(writers) {
|
|
379
|
+
const asyncResults = await Promise.all(writers.map((w) => this.isAsyncContext(w)));
|
|
380
|
+
return writers.map((writer, i) => ({
|
|
381
|
+
file: writer.file,
|
|
382
|
+
line: writer.line,
|
|
383
|
+
entityId: writer.entityId,
|
|
384
|
+
entityName: writer.entityName,
|
|
385
|
+
mutationType: this.classifyMutation(writer),
|
|
386
|
+
condition: this.extractCondition(writer.context),
|
|
387
|
+
isAsync: asyncResults[i],
|
|
388
|
+
hasLock: this.detectLockPattern(writer.code, writer.context),
|
|
389
|
+
code: writer.code
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Check if operation is in async context
|
|
394
|
+
* Includes detection of hidden async APIs that appear synchronous.
|
|
395
|
+
* Uses entity cache to avoid N+1 DB queries.
|
|
396
|
+
*/
|
|
397
|
+
async isAsyncContext(operation) {
|
|
398
|
+
const codeToCheck = `${operation.code}
|
|
399
|
+
${operation.context || ""}`;
|
|
400
|
+
if (/async\s|await\s|\.then\(|Promise|setTimeout|setInterval|\.subscribe\(/.test(codeToCheck)) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
for (const api of HIDDEN_ASYNC_APIS) {
|
|
404
|
+
if (codeToCheck.includes(api)) return true;
|
|
405
|
+
}
|
|
406
|
+
for (const pattern of ASYNC_BEHAVIOR_PATTERNS) {
|
|
407
|
+
if (pattern.test(codeToCheck)) return true;
|
|
408
|
+
}
|
|
409
|
+
if (!operation.entityId) return false;
|
|
410
|
+
try {
|
|
411
|
+
const entity = await this.getEntityCached(operation.entityId);
|
|
412
|
+
if (!entity) return false;
|
|
413
|
+
if (entity.language === "csharp" || entity.metadata?.language === "csharp") {
|
|
414
|
+
if (entity.metadata?.modifiers?.includes("async")) return true;
|
|
415
|
+
if (/Task|ValueTask/.test(entity.metadata?.returnType || "")) return true;
|
|
416
|
+
}
|
|
417
|
+
const code = entity.code || "";
|
|
418
|
+
if (/^async\s/.test(code) || /async\s+function/.test(code)) return true;
|
|
419
|
+
for (const api of HIDDEN_ASYNC_APIS) {
|
|
420
|
+
if (code.includes(api)) return true;
|
|
421
|
+
}
|
|
422
|
+
for (const pattern of ASYNC_BEHAVIOR_PATTERNS) {
|
|
423
|
+
if (pattern.test(code)) return true;
|
|
424
|
+
}
|
|
425
|
+
if (this._relCache.has(operation.entityId)) {
|
|
426
|
+
return this._relCache.get(operation.entityId);
|
|
427
|
+
}
|
|
428
|
+
const relationships = this._relLookup?.get(operation.entityId) || await this.storage.getRelationshipsForEntity(operation.entityId);
|
|
429
|
+
let parentIsAsync = false;
|
|
430
|
+
for (const rel of relationships) {
|
|
431
|
+
if (rel.type === "contains" /* CONTAINS */) {
|
|
432
|
+
const parent = await this.getEntityCached(rel.fromId);
|
|
433
|
+
if (parent?.name && /handler|listener|callback|on[A-Z]/i.test(parent.name)) {
|
|
434
|
+
parentIsAsync = true;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
this._relCache.set(operation.entityId, parentIsAsync);
|
|
440
|
+
if (parentIsAsync) return true;
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Detect if mutation is protected by lock/mutex pattern
|
|
447
|
+
*/
|
|
448
|
+
detectLockPattern(code, context) {
|
|
449
|
+
const lockPatterns = [
|
|
450
|
+
/mutex/i,
|
|
451
|
+
/lock\(/i,
|
|
452
|
+
/semaphore/i,
|
|
453
|
+
/synchronized/i,
|
|
454
|
+
/atomic/i,
|
|
455
|
+
/\.lock\(\)/,
|
|
456
|
+
/await\s+.*lock/i,
|
|
457
|
+
/critical\s*section/i,
|
|
458
|
+
/with\s*lock/i,
|
|
459
|
+
// C# synchronization primitives
|
|
460
|
+
/\block\s*\(/,
|
|
461
|
+
/SemaphoreSlim/,
|
|
462
|
+
/Monitor\.(Enter|Exit|TryEnter)/,
|
|
463
|
+
/Interlocked\./,
|
|
464
|
+
/ReaderWriterLockSlim/,
|
|
465
|
+
/SpinLock/,
|
|
466
|
+
/Volatile\.(Read|Write)/
|
|
467
|
+
];
|
|
468
|
+
const combined = `${code}
|
|
469
|
+
${context}`;
|
|
470
|
+
return lockPatterns.some((p) => p.test(combined));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Extract guard condition from context
|
|
474
|
+
*/
|
|
475
|
+
extractCondition(context) {
|
|
476
|
+
const ifMatch = context.match(/if\s*\(([^)]+)\)/);
|
|
477
|
+
if (ifMatch?.[1]) {
|
|
478
|
+
return ifMatch[1].trim();
|
|
479
|
+
}
|
|
480
|
+
const ternaryMatch = context.match(/([^?]+)\s*\?/);
|
|
481
|
+
if (ternaryMatch?.[1] && ternaryMatch[1].length < 50) {
|
|
482
|
+
return ternaryMatch[1].trim();
|
|
483
|
+
}
|
|
484
|
+
const caseMatch = context.match(/case\s+([^:]+):/);
|
|
485
|
+
if (caseMatch?.[1]) {
|
|
486
|
+
return `case ${caseMatch[1].trim()}`;
|
|
487
|
+
}
|
|
488
|
+
return void 0;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Classify mutation type from operation
|
|
492
|
+
*/
|
|
493
|
+
classifyMutation(op) {
|
|
494
|
+
const code = op.code.toLowerCase();
|
|
495
|
+
if (/=\s*(null|undefined|false|0|''|""|``)\s*[;,)]/.test(code) || /\.(clear|reset)\(\)/.test(code)) {
|
|
496
|
+
return "reset";
|
|
497
|
+
}
|
|
498
|
+
if (/\+\+|--|\+=\s*1|-=\s*1/.test(code)) {
|
|
499
|
+
return "increment";
|
|
500
|
+
}
|
|
501
|
+
if (/=\s*!/.test(code)) {
|
|
502
|
+
return "toggle";
|
|
503
|
+
}
|
|
504
|
+
if (op.context && /if\s*\(/.test(op.context)) {
|
|
505
|
+
return "conditional-write";
|
|
506
|
+
}
|
|
507
|
+
return "write";
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Calculate risk factors from mutations
|
|
511
|
+
*/
|
|
512
|
+
calculateRiskFactors(mutations) {
|
|
513
|
+
const conditions = mutations.filter((m) => m.condition).map((m) => m.condition);
|
|
514
|
+
const uniqueConditions = new Set(conditions);
|
|
515
|
+
let oppositeConditions = 0;
|
|
516
|
+
for (const cond of uniqueConditions) {
|
|
517
|
+
const negated = cond.startsWith("!") ? cond.slice(1) : `!${cond}`;
|
|
518
|
+
if (uniqueConditions.has(negated)) {
|
|
519
|
+
oppositeConditions++;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const conditionCounts = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const cond of conditions) {
|
|
524
|
+
conditionCounts.set(cond, (conditionCounts.get(cond) || 0) + 1);
|
|
525
|
+
}
|
|
526
|
+
const sharedConditions = Array.from(conditionCounts.values()).filter((c) => c > 1).length;
|
|
527
|
+
return {
|
|
528
|
+
writerCount: mutations.length,
|
|
529
|
+
asyncBoundaries: mutations.filter((m) => m.isAsync).length,
|
|
530
|
+
sharedConditions,
|
|
531
|
+
oppositeConditions: oppositeConditions / 2,
|
|
532
|
+
// Pairs
|
|
533
|
+
hasLocks: mutations.some((m) => m.hasLock),
|
|
534
|
+
resetPoints: mutations.filter((m) => m.mutationType === "reset").length
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Detect specific race conflict patterns
|
|
539
|
+
*/
|
|
540
|
+
detectConflicts(stateIdentifier, mutations, _readers) {
|
|
541
|
+
const conflicts = [];
|
|
542
|
+
const resets = mutations.filter((m) => m.mutationType === "reset");
|
|
543
|
+
if (resets.length >= 2) {
|
|
544
|
+
conflicts.push({
|
|
545
|
+
pattern: "competing-resets",
|
|
546
|
+
severity: this.assessConflictSeverity(resets),
|
|
547
|
+
stateIdentifier,
|
|
548
|
+
description: `${resets.length} places reset ${stateIdentifier}`,
|
|
549
|
+
locations: resets,
|
|
550
|
+
explanation: `Multiple functions reset ${stateIdentifier} to initial value. If called concurrently, state may be reset while another function expects it to be set.`,
|
|
551
|
+
suggestion: "Centralize reset logic in a single function or use state machine pattern"
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const conditionalWrites = mutations.filter((m) => m.mutationType === "conditional-write" && !m.hasLock);
|
|
555
|
+
if (conditionalWrites.length >= 2) {
|
|
556
|
+
const conditionGroups = /* @__PURE__ */ new Map();
|
|
557
|
+
for (const m of conditionalWrites) {
|
|
558
|
+
if (m.condition) {
|
|
559
|
+
const key = m.condition.replace(/\s+/g, "");
|
|
560
|
+
let arr = conditionGroups.get(key);
|
|
561
|
+
if (!arr) {
|
|
562
|
+
arr = [];
|
|
563
|
+
conditionGroups.set(key, arr);
|
|
564
|
+
}
|
|
565
|
+
arr.push(m);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
for (const [condition, group] of conditionGroups) {
|
|
569
|
+
if (group.length >= 2) {
|
|
570
|
+
conflicts.push({
|
|
571
|
+
pattern: "check-then-act",
|
|
572
|
+
severity: "high",
|
|
573
|
+
stateIdentifier,
|
|
574
|
+
description: `${group.length} places check "${condition}" then modify ${stateIdentifier}`,
|
|
575
|
+
locations: group,
|
|
576
|
+
sharedCondition: condition,
|
|
577
|
+
explanation: `Multiple functions check the same condition before modifying state. Between check and act, another function may change the state.`,
|
|
578
|
+
suggestion: "Use atomic compare-and-swap, mutex, or combine into single handler"
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const asyncMutations = mutations.filter((m) => m.isAsync && !m.hasLock);
|
|
584
|
+
if (asyncMutations.length >= 2) {
|
|
585
|
+
conflicts.push({
|
|
586
|
+
pattern: "async-boundary",
|
|
587
|
+
severity: "high",
|
|
588
|
+
stateIdentifier,
|
|
589
|
+
description: `${asyncMutations.length} async operations modify ${stateIdentifier} without synchronization`,
|
|
590
|
+
locations: asyncMutations,
|
|
591
|
+
explanation: `Async operations can interleave unpredictably. One operation may overwrite changes from another.`,
|
|
592
|
+
suggestion: "Add mutex/lock or use queue-based state updates"
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const oppositeGroups = this.findOppositeConditionGroups(mutations);
|
|
596
|
+
for (const group of oppositeGroups) {
|
|
597
|
+
conflicts.push({
|
|
598
|
+
pattern: "competing-mutations",
|
|
599
|
+
severity: "critical",
|
|
600
|
+
stateIdentifier,
|
|
601
|
+
description: `Functions with opposite conditions both modify ${stateIdentifier}`,
|
|
602
|
+
locations: group,
|
|
603
|
+
explanation: `Functions checking opposite conditions (e.g., "if (x)" vs "if (!x)") both modify the state. This is a classic race condition pattern where both may execute simultaneously.`,
|
|
604
|
+
suggestion: "Merge into single handler with exclusive logic, or add explicit synchronization"
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const handlerMutations = mutations.filter((m) => /handler|listener|on[A-Z]|callback/i.test(m.entityName));
|
|
608
|
+
if (handlerMutations.length >= 2) {
|
|
609
|
+
conflicts.push({
|
|
610
|
+
pattern: "event-handler-race",
|
|
611
|
+
severity: "medium",
|
|
612
|
+
stateIdentifier,
|
|
613
|
+
description: `${handlerMutations.length} event handlers modify ${stateIdentifier}`,
|
|
614
|
+
locations: handlerMutations,
|
|
615
|
+
explanation: `Multiple event handlers modify shared state. Event ordering may vary, causing unpredictable state.`,
|
|
616
|
+
suggestion: "Consolidate handlers or use event queue with single consumer"
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return conflicts;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Find groups of mutations with opposite conditions
|
|
623
|
+
*/
|
|
624
|
+
findOppositeConditionGroups(mutations) {
|
|
625
|
+
const groups = [];
|
|
626
|
+
const processed = /* @__PURE__ */ new Set();
|
|
627
|
+
for (let i = 0; i < mutations.length; i++) {
|
|
628
|
+
const mutationI = mutations[i];
|
|
629
|
+
if (processed.has(i) || !mutationI || !mutationI.condition) continue;
|
|
630
|
+
const cond = mutationI.condition;
|
|
631
|
+
const normalizedCond = cond.replace(/\s+/g, "");
|
|
632
|
+
const negatedCond = cond.startsWith("!") ? cond.slice(1).replace(/\s+/g, "") : `!${normalizedCond}`;
|
|
633
|
+
const opposites = [mutationI];
|
|
634
|
+
processed.add(i);
|
|
635
|
+
for (let j = i + 1; j < mutations.length; j++) {
|
|
636
|
+
const mutationJ = mutations[j];
|
|
637
|
+
if (processed.has(j) || !mutationJ || !mutationJ.condition) continue;
|
|
638
|
+
const otherCond = mutationJ.condition.replace(/\s+/g, "");
|
|
639
|
+
if (otherCond === negatedCond || `!${otherCond}` === normalizedCond) {
|
|
640
|
+
opposites.push(mutationJ);
|
|
641
|
+
processed.add(j);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (opposites.length >= 2) {
|
|
645
|
+
groups.push(opposites);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return groups;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Assess severity of a conflict based on mutations
|
|
652
|
+
*/
|
|
653
|
+
assessConflictSeverity(mutations) {
|
|
654
|
+
const hasAsync = mutations.some((m) => m.isAsync);
|
|
655
|
+
const allUnprotected = mutations.every((m) => !m.hasLock);
|
|
656
|
+
const count = mutations.length;
|
|
657
|
+
if (hasAsync && allUnprotected && count >= 3) return "critical";
|
|
658
|
+
if (hasAsync && allUnprotected) return "high";
|
|
659
|
+
if (count >= 3 && allUnprotected) return "high";
|
|
660
|
+
if (count >= 2 && allUnprotected) return "medium";
|
|
661
|
+
return "low";
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Calculate overall race risk from factors and conflicts
|
|
665
|
+
*/
|
|
666
|
+
calculateRaceRisk(factors, conflicts) {
|
|
667
|
+
let risk = "none";
|
|
668
|
+
if (factors.writerCount === 1) {
|
|
669
|
+
return "none";
|
|
670
|
+
}
|
|
671
|
+
if (factors.writerCount >= 2) {
|
|
672
|
+
risk = "low";
|
|
673
|
+
}
|
|
674
|
+
if (factors.asyncBoundaries >= 2 && !factors.hasLocks) {
|
|
675
|
+
risk = "high";
|
|
676
|
+
}
|
|
677
|
+
if (factors.oppositeConditions >= 1) {
|
|
678
|
+
risk = "high";
|
|
679
|
+
}
|
|
680
|
+
if (factors.resetPoints >= 2 && factors.asyncBoundaries >= 1) {
|
|
681
|
+
risk = "critical";
|
|
682
|
+
}
|
|
683
|
+
for (const conflict of conflicts) {
|
|
684
|
+
if (this.riskLevel(conflict.severity) > this.riskLevel(risk)) {
|
|
685
|
+
risk = conflict.severity;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return risk;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Convert risk to numeric level for comparison
|
|
692
|
+
*/
|
|
693
|
+
riskLevel(risk) {
|
|
694
|
+
const levels = {
|
|
695
|
+
none: 0,
|
|
696
|
+
low: 1,
|
|
697
|
+
medium: 2,
|
|
698
|
+
high: 3,
|
|
699
|
+
critical: 4
|
|
700
|
+
};
|
|
701
|
+
return levels[risk];
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Generate human-readable race reason
|
|
705
|
+
*/
|
|
706
|
+
generateRaceReason(factors, conflicts) {
|
|
707
|
+
if (factors.writerCount <= 1) {
|
|
708
|
+
return void 0;
|
|
709
|
+
}
|
|
710
|
+
const parts = [];
|
|
711
|
+
parts.push(`${factors.writerCount} writers`);
|
|
712
|
+
if (factors.asyncBoundaries > 0) {
|
|
713
|
+
parts.push(`${factors.asyncBoundaries} async`);
|
|
714
|
+
}
|
|
715
|
+
if (!factors.hasLocks && factors.writerCount >= 2) {
|
|
716
|
+
parts.push("no sync");
|
|
717
|
+
}
|
|
718
|
+
if (conflicts.length > 0) {
|
|
719
|
+
const patterns = [...new Set(conflicts.map((c) => c.pattern))];
|
|
720
|
+
parts.push(`patterns: ${patterns.join(", ")}`);
|
|
721
|
+
}
|
|
722
|
+
return parts.join(", ");
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// src/analysis/chaos/state-detector.ts
|
|
727
|
+
function buildRelationshipLookup(relationships) {
|
|
728
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
729
|
+
for (const rel of relationships) {
|
|
730
|
+
let fromArr = lookup.get(rel.fromId);
|
|
731
|
+
if (!fromArr) {
|
|
732
|
+
fromArr = [];
|
|
733
|
+
lookup.set(rel.fromId, fromArr);
|
|
734
|
+
}
|
|
735
|
+
fromArr.push(rel);
|
|
736
|
+
if (rel.toId !== rel.fromId) {
|
|
737
|
+
let toArr = lookup.get(rel.toId);
|
|
738
|
+
if (!toArr) {
|
|
739
|
+
toArr = [];
|
|
740
|
+
lookup.set(rel.toId, toArr);
|
|
741
|
+
}
|
|
742
|
+
toArr.push(rel);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return lookup;
|
|
746
|
+
}
|
|
747
|
+
var StateDetector = class {
|
|
748
|
+
constructor(storage) {
|
|
749
|
+
this.storage = storage;
|
|
750
|
+
this.raceDetector = new RaceDetector(storage);
|
|
751
|
+
}
|
|
752
|
+
raceDetector;
|
|
753
|
+
_cachedEntities = null;
|
|
754
|
+
/** Pre-loaded functions/methods for body scanning — shared across all identifiers */
|
|
755
|
+
_cachedFunctions = null;
|
|
756
|
+
/** Pre-loaded relationships indexed by entityId for O(1) lookup */
|
|
757
|
+
_relLookup = null;
|
|
758
|
+
async detectPatterns(options, cachedEntities, relLookup) {
|
|
759
|
+
const t0 = performance.now();
|
|
760
|
+
this._cachedEntities = cachedEntities || await this.storage.getAllEntities();
|
|
761
|
+
this._relLookup = relLookup || null;
|
|
762
|
+
const patterns = [];
|
|
763
|
+
this.raceDetector.setEntityCache(this._cachedEntities);
|
|
764
|
+
if (this._relLookup) {
|
|
765
|
+
this.raceDetector.setRelationshipCache(this._relLookup);
|
|
766
|
+
}
|
|
767
|
+
const tFns = performance.now();
|
|
768
|
+
this._cachedFunctions = await this.storage.searchEntities({
|
|
769
|
+
types: ["function" /* FUNCTION */, "method" /* METHOD */, "constructor"]
|
|
770
|
+
});
|
|
771
|
+
log.i("CHAOS_DET", "preload_functions", {
|
|
772
|
+
count: this._cachedFunctions.length,
|
|
773
|
+
ms: +(performance.now() - tFns).toFixed(0)
|
|
774
|
+
});
|
|
775
|
+
if (options.stateIdentifiers && options.stateIdentifiers.length > 0) {
|
|
776
|
+
for (const identifier of options.stateIdentifiers) {
|
|
777
|
+
const pattern = await this.analyzeIdentifier(identifier);
|
|
778
|
+
if (pattern) patterns.push(pattern);
|
|
779
|
+
}
|
|
780
|
+
log.i("CHAOS_DET", "manual_identifiers", {
|
|
781
|
+
count: options.stateIdentifiers.length,
|
|
782
|
+
matched: patterns.length,
|
|
783
|
+
ms: +(performance.now() - t0).toFixed(0)
|
|
784
|
+
});
|
|
785
|
+
} else if (options.autoDetect) {
|
|
786
|
+
const tAutoStart = performance.now();
|
|
787
|
+
const identifiers = this.autoDetectStateIdentifiers(this._cachedEntities);
|
|
788
|
+
log.i("CHAOS_DET", "autoDetect", {
|
|
789
|
+
candidates: identifiers.length,
|
|
790
|
+
fromEntities: this._cachedEntities.length,
|
|
791
|
+
ms: +(performance.now() - tAutoStart).toFixed(0)
|
|
792
|
+
});
|
|
793
|
+
for (let i = 0; i < identifiers.length; i++) {
|
|
794
|
+
const tIdent = performance.now();
|
|
795
|
+
const pattern = await this.analyzeIdentifier(identifiers[i]);
|
|
796
|
+
if (pattern) patterns.push(pattern);
|
|
797
|
+
const dtIdent = performance.now() - tIdent;
|
|
798
|
+
if (dtIdent > 1e3) {
|
|
799
|
+
log.w("CHAOS_DET", "slow_identifier", {
|
|
800
|
+
identifier: identifiers[i],
|
|
801
|
+
index: i,
|
|
802
|
+
ms: +dtIdent.toFixed(0),
|
|
803
|
+
ops: pattern?.operations.length || 0,
|
|
804
|
+
writers: pattern?.raceAnalysis.writers || 0
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
log.i("CHAOS_DET", "all_identifiers", {
|
|
809
|
+
total: identifiers.length,
|
|
810
|
+
matched: patterns.length,
|
|
811
|
+
ms: +(performance.now() - t0).toFixed(0)
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
this._cachedEntities = null;
|
|
815
|
+
this._cachedFunctions = null;
|
|
816
|
+
this._relLookup = null;
|
|
817
|
+
this.raceDetector.clearEntityCache();
|
|
818
|
+
return patterns.sort((a, b) => {
|
|
819
|
+
const riskOrder = { critical: 0, high: 1, medium: 2, low: 3, none: 4 };
|
|
820
|
+
return riskOrder[a.raceAnalysis.raceRisk] - riskOrder[b.raceAnalysis.raceRisk];
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
autoDetectStateIdentifiers(entities) {
|
|
824
|
+
const identifiers = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const entity of entities) {
|
|
826
|
+
const lang = entity.language || entity.metadata?.language;
|
|
827
|
+
const isCSharp = lang === "csharp";
|
|
828
|
+
const typeStr = entity.type;
|
|
829
|
+
const isStateType = entity.type === "variable" /* VARIABLE */ || entity.type === "constant" /* CONSTANT */ || typeStr === "field" || typeStr === "property";
|
|
830
|
+
if (!isStateType) continue;
|
|
831
|
+
const isState = isCSharp ? isCSharpStateIdentifier(entity) : isStateIdentifier(entity.name);
|
|
832
|
+
if (isState) {
|
|
833
|
+
identifiers.set(entity.name, (identifiers.get(entity.name) || 0) + 1);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return Array.from(identifiers.entries()).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([name]) => name);
|
|
837
|
+
}
|
|
838
|
+
async analyzeIdentifier(identifier) {
|
|
839
|
+
const tOps = performance.now();
|
|
840
|
+
const operations = await this.findStateOperations(identifier);
|
|
841
|
+
const tOpsEnd = performance.now();
|
|
842
|
+
if (operations.length === 0) return null;
|
|
843
|
+
const relatedIdentifiers = this.findRelatedIdentifiers(identifier);
|
|
844
|
+
const tRace = performance.now();
|
|
845
|
+
const raceAnalysis = await this.raceDetector.analyzeRaces(identifier, operations);
|
|
846
|
+
const tRaceEnd = performance.now();
|
|
847
|
+
log.d("CHAOS_DET", "analyzeIdentifier", {
|
|
848
|
+
id: identifier,
|
|
849
|
+
ops: operations.length,
|
|
850
|
+
opsMs: +(tOpsEnd - tOps).toFixed(0),
|
|
851
|
+
raceMs: +(tRaceEnd - tRace).toFixed(0),
|
|
852
|
+
writers: raceAnalysis.writers,
|
|
853
|
+
risk: raceAnalysis.raceRisk
|
|
854
|
+
});
|
|
855
|
+
return {
|
|
856
|
+
identifier,
|
|
857
|
+
type: this.inferStateType(identifier, operations),
|
|
858
|
+
scope: this.inferScope(operations),
|
|
859
|
+
operations,
|
|
860
|
+
relatedIdentifiers,
|
|
861
|
+
raceAnalysis
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
async findStateOperations(identifier) {
|
|
865
|
+
const tStart = performance.now();
|
|
866
|
+
const operations = [];
|
|
867
|
+
const variables = await this.storage.searchEntities({
|
|
868
|
+
namePattern: identifier,
|
|
869
|
+
types: ["variable" /* VARIABLE */, "constant" /* CONSTANT */, "field", "property"]
|
|
870
|
+
});
|
|
871
|
+
for (const entity of variables) {
|
|
872
|
+
operations.push({
|
|
873
|
+
file: entity.filePath,
|
|
874
|
+
line: entity.location?.start?.line || 0,
|
|
875
|
+
column: entity.location?.start?.column || 0,
|
|
876
|
+
entityId: entity.id,
|
|
877
|
+
entityName: entity.name,
|
|
878
|
+
operationType: "initialize",
|
|
879
|
+
code: entity.code?.slice(0, 200) || "",
|
|
880
|
+
context: "",
|
|
881
|
+
isDefensive: false,
|
|
882
|
+
depth: 0
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
const entityLookup = /* @__PURE__ */ new Map();
|
|
886
|
+
if (this._cachedEntities) {
|
|
887
|
+
for (const e of this._cachedEntities) {
|
|
888
|
+
entityLookup.set(e.id, e);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
for (const variable of variables) {
|
|
892
|
+
const relationships = this._relLookup?.get(variable.id) || await this.storage.getRelationshipsForEntity(variable.id);
|
|
893
|
+
const refIds = [];
|
|
894
|
+
for (const rel of relationships) {
|
|
895
|
+
if (rel.type === "references" /* REFERENCES */) {
|
|
896
|
+
const refId = rel.fromId === variable.id ? rel.toId : rel.fromId;
|
|
897
|
+
if (refId !== variable.id && !entityLookup.has(refId)) {
|
|
898
|
+
refIds.push(refId);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (refIds.length > 0) {
|
|
903
|
+
const fetched = await this.storage.getEntitiesBatch(refIds);
|
|
904
|
+
for (const [id, e] of fetched) {
|
|
905
|
+
entityLookup.set(id, e);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
for (const rel of relationships) {
|
|
909
|
+
if (rel.type === "references" /* REFERENCES */) {
|
|
910
|
+
const refId = rel.fromId === variable.id ? rel.toId : rel.fromId;
|
|
911
|
+
const refEntity = entityLookup.get(refId) || null;
|
|
912
|
+
if (refEntity && refEntity.id !== variable.id) {
|
|
913
|
+
const opType = await this.classifyOperation(refEntity, identifier);
|
|
914
|
+
operations.push({
|
|
915
|
+
file: refEntity.filePath,
|
|
916
|
+
line: refEntity.location?.start?.line || 0,
|
|
917
|
+
column: refEntity.location?.start?.column || 0,
|
|
918
|
+
entityId: refEntity.id,
|
|
919
|
+
entityName: refEntity.name,
|
|
920
|
+
operationType: opType,
|
|
921
|
+
code: refEntity.code?.slice(0, 200) || "",
|
|
922
|
+
context: await this.getContext(refEntity),
|
|
923
|
+
isDefensive: this.isDefensiveCode(refEntity.code || ""),
|
|
924
|
+
depth: 1
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const functions = this._cachedFunctions || await this.storage.searchEntities({
|
|
931
|
+
types: ["function" /* FUNCTION */, "method" /* METHOD */, "constructor"]
|
|
932
|
+
});
|
|
933
|
+
const seenEntityIds = new Set(operations.map((op) => op.entityId));
|
|
934
|
+
const identifierRegex = new RegExp(`\\b${identifier}\\b`);
|
|
935
|
+
for (const fn of functions) {
|
|
936
|
+
if (!fn.code || seenEntityIds.has(fn.id)) continue;
|
|
937
|
+
if (!identifierRegex.test(fn.code)) continue;
|
|
938
|
+
const opType = this.classifyOperationFromCode(fn.code, identifier);
|
|
939
|
+
seenEntityIds.add(fn.id);
|
|
940
|
+
operations.push({
|
|
941
|
+
file: fn.filePath,
|
|
942
|
+
line: fn.location?.start?.line || 0,
|
|
943
|
+
column: fn.location?.start?.column || 0,
|
|
944
|
+
entityId: fn.id,
|
|
945
|
+
entityName: fn.name,
|
|
946
|
+
operationType: opType,
|
|
947
|
+
code: this.extractRelevantCode(fn.code, identifier),
|
|
948
|
+
context: "",
|
|
949
|
+
isDefensive: this.isDefensiveCode(fn.code),
|
|
950
|
+
depth: 1
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
const dtOps = performance.now() - tStart;
|
|
954
|
+
if (dtOps > 500) {
|
|
955
|
+
log.w("CHAOS_DET", "slow_findOps", {
|
|
956
|
+
identifier,
|
|
957
|
+
vars: variables.length,
|
|
958
|
+
fns: functions.length,
|
|
959
|
+
ops: operations.length,
|
|
960
|
+
ms: +dtOps.toFixed(0)
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
return operations;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Classify operation type from entity
|
|
967
|
+
*/
|
|
968
|
+
async classifyOperation(entity, identifier) {
|
|
969
|
+
const code = entity.code || "";
|
|
970
|
+
return this.classifyOperationFromCode(code, identifier);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Classify operation type from code string
|
|
974
|
+
*/
|
|
975
|
+
classifyOperationFromCode(code, identifier) {
|
|
976
|
+
const writePattern = new RegExp(`${identifier}\\s*=(?!=)`, "g");
|
|
977
|
+
if (writePattern.test(code)) {
|
|
978
|
+
const resetPattern = new RegExp(`${identifier}\\s*=\\s*(null|undefined|false|0|''|""|\\[\\]|\\{\\})`, "g");
|
|
979
|
+
if (resetPattern.test(code)) {
|
|
980
|
+
return "write";
|
|
981
|
+
}
|
|
982
|
+
return "write";
|
|
983
|
+
}
|
|
984
|
+
if (/\.next\(|\.emit\(/.test(code)) return "emit";
|
|
985
|
+
if (/\.subscribe\(|\.pipe\(/.test(code)) return "subscribe";
|
|
986
|
+
if (/\block\s*\(/.test(code)) return "write";
|
|
987
|
+
if (/Interlocked\./.test(code)) return "write";
|
|
988
|
+
if (/\bawait\b/.test(code)) return "read";
|
|
989
|
+
const checkPattern = new RegExp(`if\\s*\\([^)]*${identifier}`, "g");
|
|
990
|
+
if (checkPattern.test(code)) return "check";
|
|
991
|
+
const passPattern = new RegExp(`\\(\\s*[^)]*${identifier}[^)]*\\)`, "g");
|
|
992
|
+
if (passPattern.test(code)) return "pass";
|
|
993
|
+
return "read";
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Extract the most relevant code snippet containing the identifier
|
|
997
|
+
*/
|
|
998
|
+
extractRelevantCode(code, identifier) {
|
|
999
|
+
const lines = code.split("\n");
|
|
1000
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1001
|
+
const line = lines[i];
|
|
1002
|
+
if (line?.includes(identifier)) {
|
|
1003
|
+
const start = Math.max(0, i - 1);
|
|
1004
|
+
const end = Math.min(lines.length, i + 2);
|
|
1005
|
+
return lines.slice(start, end).join("\n").slice(0, 200);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return code.slice(0, 200);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Get surrounding context for an entity
|
|
1012
|
+
*/
|
|
1013
|
+
async getContext(_entity) {
|
|
1014
|
+
return "";
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Check if code contains defensive patterns
|
|
1018
|
+
*/
|
|
1019
|
+
isDefensiveCode(code) {
|
|
1020
|
+
return /!==?\s*(null|undefined)|typeof\s+\w+|&&\s*\w+\.|try\s*{|\?\?|\?\./i.test(code);
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Find identifiers with similar names
|
|
1024
|
+
*/
|
|
1025
|
+
findRelatedIdentifiers(identifier) {
|
|
1026
|
+
const related = [];
|
|
1027
|
+
const entities = this._cachedEntities || [];
|
|
1028
|
+
const variations = [
|
|
1029
|
+
`_${identifier}`,
|
|
1030
|
+
`${identifier}_`,
|
|
1031
|
+
`${identifier}Value`,
|
|
1032
|
+
`${identifier}State`,
|
|
1033
|
+
`current${identifier.charAt(0).toUpperCase()}${identifier.slice(1)}`,
|
|
1034
|
+
`saved${identifier.charAt(0).toUpperCase()}${identifier.slice(1)}`,
|
|
1035
|
+
`old${identifier.charAt(0).toUpperCase()}${identifier.slice(1)}`,
|
|
1036
|
+
`new${identifier.charAt(0).toUpperCase()}${identifier.slice(1)}`
|
|
1037
|
+
];
|
|
1038
|
+
for (const entity of entities) {
|
|
1039
|
+
if (variations.includes(entity.name) || entity.name.toLowerCase().includes(identifier.toLowerCase())) {
|
|
1040
|
+
if (entity.name !== identifier && !related.includes(entity.name)) {
|
|
1041
|
+
related.push(entity.name);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return related.slice(0, 5);
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Infer TypeScript type from operations
|
|
1049
|
+
*/
|
|
1050
|
+
inferStateType(identifier, operations) {
|
|
1051
|
+
for (const op of operations) {
|
|
1052
|
+
if (op.operationType === "initialize" && op.code) {
|
|
1053
|
+
const typeMatch = op.code.match(new RegExp(`${identifier}\\s*:\\s*([\\w<>\\[\\]|&]+)`));
|
|
1054
|
+
if (typeMatch?.[1]) return typeMatch[1];
|
|
1055
|
+
if (/=\s*(true|false)/.test(op.code)) return "boolean";
|
|
1056
|
+
if (/=\s*\d+/.test(op.code)) return "number";
|
|
1057
|
+
if (/=\s*['"`]/.test(op.code)) return "string";
|
|
1058
|
+
if (/=\s*\[/.test(op.code)) return "array";
|
|
1059
|
+
if (/=\s*\{/.test(op.code)) return "object";
|
|
1060
|
+
if (/=\s*new\s+(\w+)/.test(op.code)) {
|
|
1061
|
+
const match = op.code.match(/=\s*new\s+(\w+)/);
|
|
1062
|
+
return match?.[1] ? match[1] : "unknown";
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return "unknown";
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Infer scope from operations
|
|
1070
|
+
*/
|
|
1071
|
+
inferScope(operations) {
|
|
1072
|
+
const files = new Set(operations.map((op) => op.file));
|
|
1073
|
+
if (files.size === 1) return "local";
|
|
1074
|
+
if (files.size <= 3) return "module";
|
|
1075
|
+
return "global";
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// src/analysis/chaos/chaos-analyzer.ts
|
|
1080
|
+
var ChaosAnalyzer = class {
|
|
1081
|
+
detector;
|
|
1082
|
+
storage;
|
|
1083
|
+
_csharpPatterns = [];
|
|
1084
|
+
constructor(storage) {
|
|
1085
|
+
this.storage = storage;
|
|
1086
|
+
this.detector = new StateDetector(storage);
|
|
1087
|
+
}
|
|
1088
|
+
/** C# anti-patterns detected in last analyze() call */
|
|
1089
|
+
get csharpPatterns() {
|
|
1090
|
+
return this._csharpPatterns;
|
|
1091
|
+
}
|
|
1092
|
+
async analyze(options) {
|
|
1093
|
+
const t0 = performance.now();
|
|
1094
|
+
const allEntities = await this.storage.getAllEntities();
|
|
1095
|
+
const tEntities = performance.now();
|
|
1096
|
+
log.i("CHAOS", "1_getAllEntities", { count: allEntities.length, ms: +(tEntities - t0).toFixed(0) });
|
|
1097
|
+
const allRelationships = await this.storage.getAllRelationships();
|
|
1098
|
+
const relLookup = buildRelationshipLookup(allRelationships);
|
|
1099
|
+
const tRels = performance.now();
|
|
1100
|
+
log.i("CHAOS", "1b_getAllRelationships", {
|
|
1101
|
+
count: allRelationships.length,
|
|
1102
|
+
indexKeys: relLookup.size,
|
|
1103
|
+
ms: +(tRels - tEntities).toFixed(0)
|
|
1104
|
+
});
|
|
1105
|
+
const patterns = await this.detector.detectPatterns(options, allEntities, relLookup);
|
|
1106
|
+
const tPatterns = performance.now();
|
|
1107
|
+
log.i("CHAOS", "2_detectPatterns", { patterns: patterns.length, ms: +(tPatterns - tEntities).toFixed(0) });
|
|
1108
|
+
const results = [];
|
|
1109
|
+
try {
|
|
1110
|
+
this._csharpPatterns = detectCSharpChaosPatterns(allEntities);
|
|
1111
|
+
} catch {
|
|
1112
|
+
this._csharpPatterns = [];
|
|
1113
|
+
}
|
|
1114
|
+
const tCsharp = performance.now();
|
|
1115
|
+
log.i("CHAOS", "3_csharpPatterns", { count: this._csharpPatterns.length, ms: +(tCsharp - tPatterns).toFixed(0) });
|
|
1116
|
+
for (const pattern of patterns) {
|
|
1117
|
+
const { raceAnalysis } = pattern;
|
|
1118
|
+
const files = new Set(pattern.operations.map((op) => op.file));
|
|
1119
|
+
const chaosScore = this.calculateChaosScore(pattern, raceAnalysis);
|
|
1120
|
+
const divergenceRisk = this.assessDivergenceRisk(pattern, raceAnalysis);
|
|
1121
|
+
const hotspots = this.generateHotspots(pattern, raceAnalysis);
|
|
1122
|
+
const quickFixes = this.generateQuickFixes(pattern, raceAnalysis);
|
|
1123
|
+
const strategy = this.chooseStrategy(pattern, raceAnalysis);
|
|
1124
|
+
const summary = {
|
|
1125
|
+
overview: {
|
|
1126
|
+
stateIdentifier: pattern.identifier,
|
|
1127
|
+
totalFiles: files.size,
|
|
1128
|
+
totalOperations: pattern.operations.length,
|
|
1129
|
+
chaosScore,
|
|
1130
|
+
divergenceRisk,
|
|
1131
|
+
raceRisk: raceAnalysis.raceRisk,
|
|
1132
|
+
writers: raceAnalysis.writers,
|
|
1133
|
+
conflicts: raceAnalysis.conflicts.length
|
|
1134
|
+
},
|
|
1135
|
+
hotspots,
|
|
1136
|
+
raceConflicts: raceAnalysis.conflicts.map((c) => ({
|
|
1137
|
+
pattern: c.pattern,
|
|
1138
|
+
severity: c.severity,
|
|
1139
|
+
locations: c.locations.map((l) => `${l.file}:${l.line}`),
|
|
1140
|
+
suggestion: c.suggestion
|
|
1141
|
+
})),
|
|
1142
|
+
quickFixes,
|
|
1143
|
+
refactoringStrategy: strategy,
|
|
1144
|
+
estimatedEffort: this.estimateEffort(pattern, raceAnalysis)
|
|
1145
|
+
};
|
|
1146
|
+
const writeOps = pattern.operations.filter(
|
|
1147
|
+
(op) => op.operationType === "write" || op.operationType === "initialize" || op.operationType === "emit"
|
|
1148
|
+
);
|
|
1149
|
+
const mutationFiles = new Set(writeOps.map((op) => op.file));
|
|
1150
|
+
results.push({
|
|
1151
|
+
statePattern: pattern,
|
|
1152
|
+
flowMap: {
|
|
1153
|
+
stateIdentifier: pattern.identifier,
|
|
1154
|
+
origin: {
|
|
1155
|
+
file: pattern.operations[0]?.file || "",
|
|
1156
|
+
line: pattern.operations[0]?.line || 0,
|
|
1157
|
+
entityName: pattern.identifier,
|
|
1158
|
+
type: "initialization",
|
|
1159
|
+
code: pattern.operations[0]?.code || ""
|
|
1160
|
+
},
|
|
1161
|
+
nodes: [],
|
|
1162
|
+
edges: [],
|
|
1163
|
+
totalOperations: pattern.operations.length,
|
|
1164
|
+
mutationPoints: raceAnalysis.writers,
|
|
1165
|
+
maxDepth: Math.max(...pattern.operations.map((op) => op.depth), 0)
|
|
1166
|
+
},
|
|
1167
|
+
metrics: {
|
|
1168
|
+
stateIdentifier: pattern.identifier,
|
|
1169
|
+
coupling: {
|
|
1170
|
+
score: Math.min(100, files.size * 15),
|
|
1171
|
+
affectedComponents: pattern.operations.length,
|
|
1172
|
+
sharedStateCount: pattern.relatedIdentifiers.length,
|
|
1173
|
+
bidirectionalBindings: 0
|
|
1174
|
+
},
|
|
1175
|
+
defensive: {
|
|
1176
|
+
nullChecks: pattern.operations.filter((op) => op.isDefensive).length,
|
|
1177
|
+
typeGuards: 0,
|
|
1178
|
+
defaultValues: 0,
|
|
1179
|
+
tryCatch: 0,
|
|
1180
|
+
localCopies: 0
|
|
1181
|
+
},
|
|
1182
|
+
mutationSpread: {
|
|
1183
|
+
totalMutations: raceAnalysis.writers,
|
|
1184
|
+
filesWithMutations: mutationFiles.size,
|
|
1185
|
+
componentsWithMutations: raceAnalysis.mutations.length,
|
|
1186
|
+
averageMutationsPerComponent: mutationFiles.size > 0 ? raceAnalysis.writers / mutationFiles.size : 0
|
|
1187
|
+
},
|
|
1188
|
+
divergenceRisk,
|
|
1189
|
+
complexity: {
|
|
1190
|
+
cyclomaticComplexity: 0,
|
|
1191
|
+
cognitiveComplexity: 0
|
|
1192
|
+
},
|
|
1193
|
+
score: chaosScore
|
|
1194
|
+
},
|
|
1195
|
+
raceAnalysis,
|
|
1196
|
+
refactoringPlan: {
|
|
1197
|
+
stateIdentifier: pattern.identifier,
|
|
1198
|
+
currentMetrics: {
|
|
1199
|
+
stateIdentifier: pattern.identifier,
|
|
1200
|
+
coupling: {
|
|
1201
|
+
score: Math.min(100, files.size * 15),
|
|
1202
|
+
affectedComponents: pattern.operations.length,
|
|
1203
|
+
sharedStateCount: pattern.relatedIdentifiers.length,
|
|
1204
|
+
bidirectionalBindings: 0
|
|
1205
|
+
},
|
|
1206
|
+
defensive: {
|
|
1207
|
+
nullChecks: pattern.operations.filter((op) => op.isDefensive).length,
|
|
1208
|
+
typeGuards: 0,
|
|
1209
|
+
defaultValues: 0,
|
|
1210
|
+
tryCatch: 0,
|
|
1211
|
+
localCopies: 0
|
|
1212
|
+
},
|
|
1213
|
+
mutationSpread: {
|
|
1214
|
+
totalMutations: raceAnalysis.writers,
|
|
1215
|
+
filesWithMutations: mutationFiles.size,
|
|
1216
|
+
componentsWithMutations: raceAnalysis.mutations.length,
|
|
1217
|
+
averageMutationsPerComponent: mutationFiles.size > 0 ? raceAnalysis.writers / mutationFiles.size : 0
|
|
1218
|
+
},
|
|
1219
|
+
divergenceRisk,
|
|
1220
|
+
complexity: {
|
|
1221
|
+
cyclomaticComplexity: 0,
|
|
1222
|
+
cognitiveComplexity: 0
|
|
1223
|
+
},
|
|
1224
|
+
score: chaosScore
|
|
1225
|
+
},
|
|
1226
|
+
strategy,
|
|
1227
|
+
reasoning: this.generateReasoning(strategy, raceAnalysis),
|
|
1228
|
+
steps: [],
|
|
1229
|
+
newComponents: [],
|
|
1230
|
+
benefits: {
|
|
1231
|
+
reducedCoupling: strategy === "Service" ? 40 : strategy === "ImmutableState" ? 50 : 60,
|
|
1232
|
+
reducedMutations: Math.floor(raceAnalysis.writers / 2),
|
|
1233
|
+
reducedComplexity: raceAnalysis.conflicts.length > 0 ? 50 : 30,
|
|
1234
|
+
improvedTestability: true,
|
|
1235
|
+
improvedMaintainability: true,
|
|
1236
|
+
estimatedLOCChange: 0
|
|
1237
|
+
},
|
|
1238
|
+
risks: raceAnalysis.conflicts.length > 0 ? ["\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u0442\u0449\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0441\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0430\u0446\u0438\u0438"] : [],
|
|
1239
|
+
prerequisites: []
|
|
1240
|
+
},
|
|
1241
|
+
summary,
|
|
1242
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
log.i("CHAOS", "4_total", {
|
|
1246
|
+
entities: allEntities.length,
|
|
1247
|
+
statePatterns: patterns.length,
|
|
1248
|
+
csharpPatterns: this._csharpPatterns.length,
|
|
1249
|
+
results: results.length,
|
|
1250
|
+
totalMs: +(performance.now() - t0).toFixed(0)
|
|
1251
|
+
});
|
|
1252
|
+
return results;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Format results for AI consumption (compact, token-efficient)
|
|
1256
|
+
*/
|
|
1257
|
+
formatForAI(results) {
|
|
1258
|
+
if (results.length === 0 && this._csharpPatterns.length === 0) {
|
|
1259
|
+
return "No state patterns detected.";
|
|
1260
|
+
}
|
|
1261
|
+
const output = ["# State Chaos Analysis\n"];
|
|
1262
|
+
for (const result of results) {
|
|
1263
|
+
const { summary, raceAnalysis } = result;
|
|
1264
|
+
const { overview } = summary;
|
|
1265
|
+
const riskEmoji = this.getRiskEmoji(overview.raceRisk);
|
|
1266
|
+
output.push(`## ${riskEmoji} ${overview.stateIdentifier}`);
|
|
1267
|
+
output.push(
|
|
1268
|
+
`Chaos: ${overview.chaosScore}/100 | Files: ${overview.totalFiles} | Writers: ${overview.writers} | Race: ${overview.raceRisk}`
|
|
1269
|
+
);
|
|
1270
|
+
if (raceAnalysis.conflicts.length > 0) {
|
|
1271
|
+
output.push("\n**\u26A0\uFE0F Race Conditions:**");
|
|
1272
|
+
for (const conflict of raceAnalysis.conflicts) {
|
|
1273
|
+
output.push(`- **${conflict.pattern}** (${conflict.severity})`);
|
|
1274
|
+
output.push(` ${conflict.explanation}`);
|
|
1275
|
+
output.push(` \u{1F4CD} ${conflict.locations.map((l) => `${l.entityName}:${l.line}`).join(", ")}`);
|
|
1276
|
+
output.push(` \u{1F4A1} ${conflict.suggestion}`);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
if (summary.quickFixes.length > 0) {
|
|
1280
|
+
output.push("\n**Quick Fixes:**");
|
|
1281
|
+
for (const fix of summary.quickFixes) {
|
|
1282
|
+
output.push(`- ${fix}`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
output.push(`
|
|
1286
|
+
**Strategy:** ${summary.refactoringStrategy}`);
|
|
1287
|
+
output.push(`**Effort:** ${summary.estimatedEffort}
|
|
1288
|
+
`);
|
|
1289
|
+
output.push("---\n");
|
|
1290
|
+
}
|
|
1291
|
+
if (this._csharpPatterns.length > 0) {
|
|
1292
|
+
output.push(this.formatCSharpPatternsForAI());
|
|
1293
|
+
}
|
|
1294
|
+
return output.join("\n");
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Format C# anti-patterns for AI consumption
|
|
1298
|
+
*/
|
|
1299
|
+
formatCSharpPatternsForAI() {
|
|
1300
|
+
const lines = ["\n# C# Anti-Patterns\n"];
|
|
1301
|
+
const byPattern = /* @__PURE__ */ new Map();
|
|
1302
|
+
for (const p of this._csharpPatterns) {
|
|
1303
|
+
const arr = byPattern.get(p.pattern) || [];
|
|
1304
|
+
arr.push(p);
|
|
1305
|
+
byPattern.set(p.pattern, arr);
|
|
1306
|
+
}
|
|
1307
|
+
const severityEmoji = {
|
|
1308
|
+
critical: "\u{1F534}",
|
|
1309
|
+
high: "\u{1F7E0}",
|
|
1310
|
+
medium: "\u{1F7E1}"
|
|
1311
|
+
};
|
|
1312
|
+
for (const [pattern, items] of byPattern) {
|
|
1313
|
+
const sev = items[0].severity;
|
|
1314
|
+
lines.push(`## ${severityEmoji[sev] || "\u26AA"} ${pattern} (${items.length} found)`);
|
|
1315
|
+
for (const item of items.slice(0, 10)) {
|
|
1316
|
+
lines.push(`- **${item.entityName}** ${item.filePath}:${item.line}`);
|
|
1317
|
+
lines.push(` ${item.description}`);
|
|
1318
|
+
lines.push(` \u{1F4A1} ${item.suggestion}`);
|
|
1319
|
+
}
|
|
1320
|
+
if (items.length > 10) {
|
|
1321
|
+
lines.push(` ... and ${items.length - 10} more`);
|
|
1322
|
+
}
|
|
1323
|
+
lines.push("");
|
|
1324
|
+
}
|
|
1325
|
+
return lines.join("\n");
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Format single result with full details
|
|
1329
|
+
*/
|
|
1330
|
+
formatDetailed(result) {
|
|
1331
|
+
const { statePattern: pattern, raceAnalysis, metrics, summary } = result;
|
|
1332
|
+
const lines = [];
|
|
1333
|
+
lines.push(`# \u0410\u043D\u0430\u043B\u0438\u0437 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F: ${pattern.identifier}`);
|
|
1334
|
+
lines.push(`**\u0422\u0438\u043F:** ${pattern.type || "unknown"}`);
|
|
1335
|
+
lines.push(`**Scope:** ${pattern.scope}`);
|
|
1336
|
+
lines.push("");
|
|
1337
|
+
lines.push("## \u041C\u0435\u0442\u0440\u0438\u043A\u0438");
|
|
1338
|
+
lines.push(`- **Chaos Score:** ${metrics.score}/100`);
|
|
1339
|
+
lines.push(`- **Divergence Risk:** ${metrics.divergenceRisk}`);
|
|
1340
|
+
lines.push(`- **Race Risk:** ${raceAnalysis.raceRisk}`);
|
|
1341
|
+
lines.push(`- **\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u0439:** ${pattern.operations.length}`);
|
|
1342
|
+
lines.push(`- **\u0424\u0430\u0439\u043B\u043E\u0432:** ${new Set(pattern.operations.map((op) => op.file)).size}`);
|
|
1343
|
+
lines.push(`- **\u041F\u0438\u0441\u0430\u0442\u0435\u043B\u0435\u0439:** ${raceAnalysis.writers} (async: ${raceAnalysis.asyncWriters})`);
|
|
1344
|
+
lines.push("");
|
|
1345
|
+
if (raceAnalysis.conflicts.length > 0) {
|
|
1346
|
+
lines.push("## \u26A0\uFE0F \u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0435 \u0433\u043E\u043D\u043A\u0438");
|
|
1347
|
+
for (const conflict of raceAnalysis.conflicts) {
|
|
1348
|
+
lines.push(`### ${conflict.pattern} (${conflict.severity})`);
|
|
1349
|
+
lines.push(`**\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435:** ${conflict.description}`);
|
|
1350
|
+
lines.push(`**\u041E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u0435:** ${conflict.explanation}`);
|
|
1351
|
+
lines.push("**\u041B\u043E\u043A\u0430\u0446\u0438\u0438:**");
|
|
1352
|
+
for (const loc of conflict.locations) {
|
|
1353
|
+
lines.push(`- \`${loc.entityName}\` \u0432 ${loc.file}:${loc.line}`);
|
|
1354
|
+
if (loc.condition) {
|
|
1355
|
+
lines.push(` \u0423\u0441\u043B\u043E\u0432\u0438\u0435: \`${loc.condition}\``);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
lines.push(`**\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u044F:** ${conflict.suggestion}`);
|
|
1359
|
+
lines.push("");
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
if (raceAnalysis.mutations.length > 0) {
|
|
1363
|
+
lines.push("## \u0422\u043E\u0447\u043A\u0438 \u043C\u0443\u0442\u0430\u0446\u0438\u0438");
|
|
1364
|
+
for (const mut of raceAnalysis.mutations) {
|
|
1365
|
+
const flags = [];
|
|
1366
|
+
if (mut.isAsync) flags.push("async");
|
|
1367
|
+
if (!mut.hasLock) flags.push("no-lock");
|
|
1368
|
+
const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
1369
|
+
lines.push(`- **${mut.entityName}** (${mut.mutationType})${flagStr}`);
|
|
1370
|
+
lines.push(` ${mut.file}:${mut.line}`);
|
|
1371
|
+
if (mut.condition) {
|
|
1372
|
+
lines.push(` \u0423\u0441\u043B\u043E\u0432\u0438\u0435: \`${mut.condition}\``);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
lines.push("");
|
|
1376
|
+
}
|
|
1377
|
+
if (pattern.relatedIdentifiers.length > 0) {
|
|
1378
|
+
lines.push("## \u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u044B");
|
|
1379
|
+
lines.push(pattern.relatedIdentifiers.map((id) => `\`${id}\``).join(", "));
|
|
1380
|
+
lines.push("");
|
|
1381
|
+
}
|
|
1382
|
+
lines.push("## \u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u0438");
|
|
1383
|
+
lines.push(`**\u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F:** ${summary.refactoringStrategy}`);
|
|
1384
|
+
lines.push(`**\u041E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u0438\u0435:** ${result.refactoringPlan.reasoning}`);
|
|
1385
|
+
lines.push(`**Effort:** ${summary.estimatedEffort}`);
|
|
1386
|
+
return lines.join("\n");
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Calculate overall chaos score combining spread and race risk
|
|
1390
|
+
*/
|
|
1391
|
+
calculateChaosScore(pattern, raceAnalysis) {
|
|
1392
|
+
const baseScore = Math.min(50, pattern.operations.length * 3);
|
|
1393
|
+
const fileScore = Math.min(20, new Set(pattern.operations.map((op) => op.file)).size * 5);
|
|
1394
|
+
const raceScore = this.raceRiskToScore(raceAnalysis.raceRisk);
|
|
1395
|
+
return Math.min(100, baseScore + fileScore + raceScore);
|
|
1396
|
+
}
|
|
1397
|
+
raceRiskToScore(risk) {
|
|
1398
|
+
const scores = {
|
|
1399
|
+
none: 0,
|
|
1400
|
+
low: 5,
|
|
1401
|
+
medium: 15,
|
|
1402
|
+
high: 25,
|
|
1403
|
+
critical: 30
|
|
1404
|
+
};
|
|
1405
|
+
return scores[risk];
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Assess divergence risk combining chaos and race factors
|
|
1409
|
+
*/
|
|
1410
|
+
assessDivergenceRisk(pattern, raceAnalysis) {
|
|
1411
|
+
const opCount = pattern.operations.length;
|
|
1412
|
+
const fileCount = new Set(pattern.operations.map((op) => op.file)).size;
|
|
1413
|
+
const raceRisk = raceAnalysis.raceRisk;
|
|
1414
|
+
if (raceRisk === "critical") return "critical";
|
|
1415
|
+
if (raceRisk === "high" && opCount >= 10) return "critical";
|
|
1416
|
+
if (raceRisk === "high" || fileCount >= 5 && opCount >= 15) return "high";
|
|
1417
|
+
if (raceRisk === "medium" || fileCount >= 3 && opCount >= 8) return "medium";
|
|
1418
|
+
return "low";
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Generate hotspots from pattern and race analysis
|
|
1422
|
+
*/
|
|
1423
|
+
generateHotspots(_pattern, raceAnalysis) {
|
|
1424
|
+
const hotspots = [];
|
|
1425
|
+
for (const conflict of raceAnalysis.conflicts) {
|
|
1426
|
+
for (const loc of conflict.locations) {
|
|
1427
|
+
hotspots.push({
|
|
1428
|
+
file: loc.file,
|
|
1429
|
+
entity: loc.entityName,
|
|
1430
|
+
issues: [conflict.pattern, conflict.description],
|
|
1431
|
+
priority: conflict.severity === "critical" || conflict.severity === "high" ? "high" : "medium"
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
for (const mut of raceAnalysis.mutations) {
|
|
1436
|
+
if (mut.isAsync && !mut.hasLock) {
|
|
1437
|
+
const existing = hotspots.find((h) => h.file === mut.file && h.entity === mut.entityName);
|
|
1438
|
+
if (existing) {
|
|
1439
|
+
existing.issues.push("unprotected async write");
|
|
1440
|
+
} else {
|
|
1441
|
+
hotspots.push({
|
|
1442
|
+
file: mut.file,
|
|
1443
|
+
entity: mut.entityName,
|
|
1444
|
+
issues: ["unprotected async write"],
|
|
1445
|
+
priority: "medium"
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return hotspots.sort((a, b) => a.priority === "high" ? -1 : b.priority === "high" ? 1 : 0).slice(0, 5);
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Generate quick fix suggestions
|
|
1454
|
+
*/
|
|
1455
|
+
generateQuickFixes(pattern, raceAnalysis) {
|
|
1456
|
+
const fixes = [];
|
|
1457
|
+
if (raceAnalysis.conflicts.some((c) => c.pattern === "competing-resets")) {
|
|
1458
|
+
fixes.push("\u041E\u0431\u044A\u0435\u0434\u0438\u043D\u0438\u0442\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u0438 \u0441\u0431\u0440\u043E\u0441\u0430 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \u0432 \u0435\u0434\u0438\u043D\u044B\u0439 \u043C\u0435\u0442\u043E\u0434 reset()");
|
|
1459
|
+
}
|
|
1460
|
+
if (raceAnalysis.asyncWriters > 0 && raceAnalysis.unprotectedWriters > 0) {
|
|
1461
|
+
fixes.push("\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u043C\u044C\u044E\u0442\u0435\u043A\u0441 \u0438\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 queue \u0434\u043B\u044F \u0430\u0441\u0438\u043D\u0445\u0440\u043E\u043D\u043D\u044B\u0445 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439");
|
|
1462
|
+
}
|
|
1463
|
+
if (raceAnalysis.conflicts.some((c) => c.pattern === "check-then-act")) {
|
|
1464
|
+
fixes.push("\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 \u0430\u0442\u043E\u043C\u0430\u0440\u043D\u044B\u0435 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u043B\u0438 compare-and-swap \u043F\u0430\u0442\u0442\u0435\u0440\u043D");
|
|
1465
|
+
}
|
|
1466
|
+
if (pattern.relatedIdentifiers.length > 2) {
|
|
1467
|
+
fixes.push(`\u041A\u043E\u043D\u0441\u043E\u043B\u0438\u0434\u0438\u0440\u0443\u0439\u0442\u0435 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435: ${pattern.relatedIdentifiers.slice(0, 3).join(", ")}`);
|
|
1468
|
+
}
|
|
1469
|
+
if (new Set(pattern.operations.map((op) => op.file)).size > 3) {
|
|
1470
|
+
fixes.push("\u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u0446\u0435\u043D\u0442\u0440\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043B\u044F \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C");
|
|
1471
|
+
}
|
|
1472
|
+
const allCode = pattern.operations.map((op) => op.code).join("\n");
|
|
1473
|
+
if (/async\s+void\b/.test(allCode)) {
|
|
1474
|
+
fixes.push("\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u0435 'async void' \u043D\u0430 'async Task' \u2014 async void \u043D\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0435\u0442 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F");
|
|
1475
|
+
}
|
|
1476
|
+
if (/static\b/.test(allCode) && !/readonly|const|ConcurrentDictionary/.test(allCode)) {
|
|
1477
|
+
fixes.push("\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 ConcurrentDictionary \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u043C\u0435\u0441\u0442\u0438\u0442\u0435 state \u0432 DI-managed service");
|
|
1478
|
+
}
|
|
1479
|
+
if (/\.ctor|constructor/i.test(pattern.identifier) && pattern.operations.length > 10) {
|
|
1480
|
+
fixes.push("\u0420\u0430\u0437\u0434\u0435\u043B\u0438\u0442\u0435 god-service \u043D\u0430 \u043C\u0435\u043D\u044C\u0448\u0438\u0435 \u0441\u0444\u043E\u043A\u0443\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B");
|
|
1481
|
+
}
|
|
1482
|
+
if (/async\s+Task/.test(allCode) && !/CancellationToken/.test(allCode)) {
|
|
1483
|
+
fixes.push("\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 CancellationToken \u0432 async \u043C\u0435\u0442\u043E\u0434\u044B");
|
|
1484
|
+
}
|
|
1485
|
+
return fixes;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Choose refactoring strategy based on analysis
|
|
1489
|
+
*/
|
|
1490
|
+
chooseStrategy(pattern, raceAnalysis) {
|
|
1491
|
+
if (raceAnalysis.raceRisk === "critical" || raceAnalysis.conflicts.length >= 3) {
|
|
1492
|
+
return "StateMachine";
|
|
1493
|
+
}
|
|
1494
|
+
const allCode = pattern.operations.map((op) => op.code).join("\n");
|
|
1495
|
+
const isCSharpContext = /\bTask\b|\basync\s+Task\b|\bawait\b.*\.\w+Async\b/.test(allCode) || pattern.operations.some((op) => op.file.endsWith(".cs"));
|
|
1496
|
+
if (isCSharpContext) {
|
|
1497
|
+
if (/static\b/.test(allCode) && /Singleton|AddSingleton/.test(allCode)) {
|
|
1498
|
+
return "DI_Lifetime";
|
|
1499
|
+
}
|
|
1500
|
+
if (raceAnalysis.raceRisk === "high" && raceAnalysis.asyncWriters > 2) {
|
|
1501
|
+
return "Channel";
|
|
1502
|
+
}
|
|
1503
|
+
if (raceAnalysis.writers > 5 && raceAnalysis.unprotectedWriters > 3) {
|
|
1504
|
+
return "ImmutableState";
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (raceAnalysis.raceRisk === "high" && raceAnalysis.asyncWriters > 2) {
|
|
1508
|
+
return "EventBus";
|
|
1509
|
+
}
|
|
1510
|
+
const files = new Set(pattern.operations.map((op) => op.file)).size;
|
|
1511
|
+
if (files > 5) {
|
|
1512
|
+
return "Store";
|
|
1513
|
+
}
|
|
1514
|
+
if (pattern.operations.some((op) => op.angularPattern === "behavior_subject")) {
|
|
1515
|
+
return "Service";
|
|
1516
|
+
}
|
|
1517
|
+
return "Service";
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Generate reasoning for chosen strategy
|
|
1521
|
+
*/
|
|
1522
|
+
generateReasoning(strategy, raceAnalysis) {
|
|
1523
|
+
const reasons = {
|
|
1524
|
+
StateMachine: `\u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u044B \u043A\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0433\u043E\u043D\u043A\u0438 (${raceAnalysis.conflicts.length} \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432). State machine \u043E\u0431\u0435\u0441\u043F\u0435\u0447\u0438\u0442 \u044F\u0432\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u044B \u0438 \u043F\u0440\u0435\u0434\u043E\u0442\u0432\u0440\u0430\u0442\u0438\u0442 invalid states.`,
|
|
1525
|
+
EventBus: `${raceAnalysis.asyncWriters} \u0430\u0441\u0438\u043D\u0445\u0440\u043E\u043D\u043D\u044B\u0445 \u043F\u0438\u0441\u0430\u0442\u0435\u043B\u0435\u0439 \u0441\u043E\u0437\u0434\u0430\u044E\u0442 \u043D\u0435\u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0443\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435. Event-driven \u0430\u0440\u0445\u0438\u0442\u0435\u043A\u0442\u0443\u0440\u0430 \u0443\u043F\u043E\u0440\u044F\u0434\u043E\u0447\u0438\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F.`,
|
|
1526
|
+
Store: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0440\u0430\u0437\u0431\u0440\u043E\u0441\u0430\u043D\u043E \u043F\u043E \u043C\u043D\u043E\u0433\u0438\u043C \u0444\u0430\u0439\u043B\u0430\u043C. \u0426\u0435\u043D\u0442\u0440\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u044B\u0439 store \u043E\u0431\u0435\u0441\u043F\u0435\u0447\u0438\u0442 single source of truth.",
|
|
1527
|
+
Service: "\u0426\u0435\u043D\u0442\u0440\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u043F\u0440\u043E\u0441\u0442\u0438\u0442 \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0438 \u0434\u043E\u0431\u0430\u0432\u0438\u0442 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C \u0434\u043E\u0441\u0442\u0443\u043F\u0430.",
|
|
1528
|
+
Context: "Context API \u043F\u043E\u0434\u043E\u0439\u0434\u0451\u0442 \u0434\u043B\u044F \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u0440\u0435\u0432\u043E \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432.",
|
|
1529
|
+
Signal: "Angular Signals \u043E\u0431\u0435\u0441\u043F\u0435\u0447\u0430\u0442 \u0440\u0435\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0441\u0442\u044C \u0441 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u043C \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u0435\u043C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439.",
|
|
1530
|
+
StateManager: "\u041A\u0430\u0441\u0442\u043E\u043C\u043D\u044B\u0439 StateManager \u043F\u043E\u0437\u0432\u043E\u043B\u0438\u0442 \u0440\u0435\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u0447\u043D\u0443\u044E \u043B\u043E\u0433\u0438\u043A\u0443 \u0441\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0430\u0446\u0438\u0438.",
|
|
1531
|
+
DI_Lifetime: "Mutable state \u0432 Singleton-\u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442 \u0433\u043E\u043D\u043A\u0438. \u0418\u0437\u043C\u0435\u043D\u0438\u0442\u0435 lifetime \u043D\u0430 Scoped \u0438\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 ImmutableDictionary.",
|
|
1532
|
+
Channel: `${raceAnalysis.asyncWriters} async producer/consumer \u043D\u0443\u0436\u0434\u0430\u044E\u0442\u0441\u044F \u0432 \u0443\u043F\u043E\u0440\u044F\u0434\u043E\u0447\u0435\u043D\u043D\u043E\u0439 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0435. System.Threading.Channels \u043E\u0431\u0435\u0441\u043F\u0435\u0447\u0438\u0442 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u0443\u044E \u043E\u0447\u0435\u0440\u0435\u0434\u044C.`,
|
|
1533
|
+
ImmutableState: "\u041C\u043D\u043E\u0436\u0435\u0441\u0442\u0432\u043E \u043D\u0435\u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0438\u0440\u0443\u0435\u043C\u044B\u0445 \u043C\u0443\u0442\u0430\u0446\u0438\u0439. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 record types \u0438 ImmutableCollections \u0434\u043B\u044F \u043F\u0440\u0435\u0434\u043E\u0442\u0432\u0440\u0430\u0449\u0435\u043D\u0438\u044F \u0433\u043E\u043D\u043E\u043A."
|
|
1534
|
+
};
|
|
1535
|
+
return reasons[strategy];
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Estimate implementation effort
|
|
1539
|
+
*/
|
|
1540
|
+
estimateEffort(pattern, raceAnalysis) {
|
|
1541
|
+
const files = new Set(pattern.operations.map((op) => op.file)).size;
|
|
1542
|
+
const conflicts = raceAnalysis.conflicts.length;
|
|
1543
|
+
if (conflicts >= 3 || files > 10) return "\u0412\u044B\u0441\u043E\u043A\u0430\u044F (1-2 \u043D\u0435\u0434\u0435\u043B\u0438)";
|
|
1544
|
+
if (conflicts >= 1 || files > 5) return "\u0421\u0440\u0435\u0434\u043D\u044F\u044F (2-5 \u0434\u043D\u0435\u0439)";
|
|
1545
|
+
return "\u041D\u0438\u0437\u043A\u0430\u044F (1-2 \u0434\u043D\u044F)";
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Get emoji for risk level
|
|
1549
|
+
*/
|
|
1550
|
+
getRiskEmoji(risk) {
|
|
1551
|
+
const emojis = {
|
|
1552
|
+
none: "\u2705",
|
|
1553
|
+
low: "\u{1F7E2}",
|
|
1554
|
+
medium: "\u{1F7E1}",
|
|
1555
|
+
high: "\u{1F7E0}",
|
|
1556
|
+
critical: "\u{1F534}"
|
|
1557
|
+
};
|
|
1558
|
+
return emojis[risk];
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1562
|
+
export { CSHARP_ASYNC_APIS, CSHARP_LOCK_PATTERNS, ChaosAnalyzer, RaceDetector, StateDetector, detectCSharpChaosPatterns, isCSharpStateIdentifier, isStateIdentifier };
|
|
1563
|
+
//# sourceMappingURL=chaos-W3XRVJ7K.js.map
|
|
1564
|
+
//# sourceMappingURL=chaos-W3XRVJ7K.js.map
|