aegiscode 3.1.10 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/bin/aegis.js +165 -0
  2. package/package.json +18 -86
  3. package/LICENSE +0 -21
  4. package/README.md +0 -394
  5. package/dist/main.js +0 -2171
  6. package/gui/run-gui.cjs +0 -13
  7. package/src/agent/Agent.ts +0 -903
  8. package/src/agent/SimpleAgent.ts +0 -48
  9. package/src/agent/index.ts +0 -54
  10. package/src/agent/orchestrator/AppBuilder.ts +0 -443
  11. package/src/agent/orchestrator/CouncilAgent.ts +0 -310
  12. package/src/agent/orchestrator/DiscussionRoom.ts +0 -462
  13. package/src/agent/orchestrator/OrchestratorAgent.ts +0 -637
  14. package/src/agent/orchestrator/index.ts +0 -38
  15. package/src/agent/orchestrator/utils.ts +0 -397
  16. package/src/agent/pricing.ts +0 -115
  17. package/src/agent/router.ts +0 -74
  18. package/src/agent/routerStats.ts +0 -121
  19. package/src/agent/types.ts +0 -318
  20. package/src/auth/login.ts +0 -383
  21. package/src/cli/config.ts +0 -189
  22. package/src/cli/index.ts +0 -17
  23. package/src/cli/middleware.ts +0 -119
  24. package/src/cli/types.ts +0 -75
  25. package/src/config/ConfigManager.ts +0 -587
  26. package/src/config/index.ts +0 -7
  27. package/src/config/types.ts +0 -584
  28. package/src/context/CompactionService.ts +0 -300
  29. package/src/context/ContextManager.ts +0 -450
  30. package/src/context/FileAnalyzer.ts +0 -267
  31. package/src/context/TokenCounter.ts +0 -265
  32. package/src/context/index.ts +0 -27
  33. package/src/context/storage/CacheStore.ts +0 -176
  34. package/src/context/storage/JSONLStore.ts +0 -201
  35. package/src/context/storage/MemoryStore.ts +0 -205
  36. package/src/context/storage/PersistentStore.ts +0 -327
  37. package/src/context/storage/index.ts +0 -9
  38. package/src/context/storage/pathUtils.ts +0 -114
  39. package/src/context/test.ts +0 -309
  40. package/src/context/types.ts +0 -268
  41. package/src/hooks/HookExecutor.ts +0 -434
  42. package/src/hooks/HookManager.ts +0 -596
  43. package/src/hooks/HookService.ts +0 -269
  44. package/src/hooks/Matcher.ts +0 -157
  45. package/src/hooks/index.ts +0 -63
  46. package/src/hooks/types.ts +0 -424
  47. package/src/main.tsx +0 -596
  48. package/src/mcp/HealthMonitor.ts +0 -150
  49. package/src/mcp/McpClient.ts +0 -491
  50. package/src/mcp/McpRegistry.ts +0 -321
  51. package/src/mcp/createMcpTool.ts +0 -251
  52. package/src/mcp/index.ts +0 -15
  53. package/src/mcp/server.ts +0 -334
  54. package/src/mcp/test-server.ts +0 -88
  55. package/src/mcp/test.ts +0 -372
  56. package/src/mcp/types.ts +0 -247
  57. package/src/memory/AgentMemoryBus.ts +0 -432
  58. package/src/memory/CloudSync.ts +0 -99
  59. package/src/memory/DriveSync.ts +0 -106
  60. package/src/memory/SharedMemory.ts +0 -951
  61. package/src/memory/index.ts +0 -14
  62. package/src/memory/machineFingerprint.ts +0 -40
  63. package/src/orchestrator/SubAgentMetadata.ts +0 -136
  64. package/src/prompts/builder.ts +0 -213
  65. package/src/prompts/default.ts +0 -144
  66. package/src/prompts/index.ts +0 -16
  67. package/src/prompts/plan.ts +0 -64
  68. package/src/prompts/test.ts +0 -78
  69. package/src/services/AnthropicChatService.ts +0 -341
  70. package/src/services/ChatService.ts +0 -347
  71. package/src/services/ClaudeCliChatService.ts +0 -256
  72. package/src/services/CloudSync.ts +0 -168
  73. package/src/services/CostLedger.ts +0 -211
  74. package/src/services/Heartbeat.ts +0 -135
  75. package/src/services/LearningCollector.ts +0 -291
  76. package/src/services/OllamaInstaller.ts +0 -342
  77. package/src/services/VersionChecker.ts +0 -445
  78. package/src/services/index.ts +0 -57
  79. package/src/services/streaming/OpenAIEventAdapter.ts +0 -126
  80. package/src/services/streaming/RenderingProfile.ts +0 -90
  81. package/src/services/streaming/StreamEventParser.ts +0 -181
  82. package/src/services/streaming/ThrottledRenderer.ts +0 -139
  83. package/src/services/streaming/TranscriptBuffer.ts +0 -574
  84. package/src/services/streaming/eventStatusMap.ts +0 -52
  85. package/src/services/streaming/index.ts +0 -46
  86. package/src/services/streaming/renderFormatting.ts +0 -79
  87. package/src/services/streaming/types.ts +0 -234
  88. package/src/skills/SkillLoader.ts +0 -126
  89. package/src/skills/SkillRegistry.ts +0 -366
  90. package/src/skills/index.ts +0 -48
  91. package/src/skills/types.ts +0 -146
  92. package/src/slash-commands/billing.ts +0 -70
  93. package/src/slash-commands/build.ts +0 -413
  94. package/src/slash-commands/builtinCommands.ts +0 -2733
  95. package/src/slash-commands/clone.ts +0 -242
  96. package/src/slash-commands/council.ts +0 -125
  97. package/src/slash-commands/custom/CustomCommandExecutor.ts +0 -143
  98. package/src/slash-commands/custom/CustomCommandLoader.ts +0 -251
  99. package/src/slash-commands/custom/CustomCommandRegistry.ts +0 -144
  100. package/src/slash-commands/custom/index.ts +0 -7
  101. package/src/slash-commands/debate.ts +0 -254
  102. package/src/slash-commands/gmail.ts +0 -105
  103. package/src/slash-commands/index.ts +0 -388
  104. package/src/slash-commands/mcpCommand.ts +0 -205
  105. package/src/slash-commands/types.ts +0 -201
  106. package/src/store/index.ts +0 -76
  107. package/src/store/selectors.ts +0 -246
  108. package/src/store/slices/appSlice.ts +0 -205
  109. package/src/store/slices/commandSlice.ts +0 -115
  110. package/src/store/slices/configSlice.ts +0 -46
  111. package/src/store/slices/focusSlice.ts +0 -64
  112. package/src/store/slices/index.ts +0 -9
  113. package/src/store/slices/sessionSlice.ts +0 -424
  114. package/src/store/streaming-buffer.ts +0 -425
  115. package/src/store/test.ts +0 -296
  116. package/src/store/types.ts +0 -274
  117. package/src/store/vanilla.ts +0 -186
  118. package/src/tools/builtin/bash.ts +0 -236
  119. package/src/tools/builtin/council.ts +0 -105
  120. package/src/tools/builtin/edit.ts +0 -213
  121. package/src/tools/builtin/glob.ts +0 -136
  122. package/src/tools/builtin/grep.ts +0 -263
  123. package/src/tools/builtin/index.ts +0 -61
  124. package/src/tools/builtin/memory.ts +0 -66
  125. package/src/tools/builtin/read.ts +0 -168
  126. package/src/tools/builtin/skill.ts +0 -97
  127. package/src/tools/builtin/snapshot.ts +0 -40
  128. package/src/tools/builtin/task.ts +0 -106
  129. package/src/tools/builtin/write.ts +0 -134
  130. package/src/tools/createTool.ts +0 -221
  131. package/src/tools/execution/ExecutionPipeline.ts +0 -263
  132. package/src/tools/execution/index.ts +0 -40
  133. package/src/tools/execution/stages/CacheStage.ts +0 -131
  134. package/src/tools/execution/stages/ConfirmationStage.ts +0 -203
  135. package/src/tools/execution/stages/DiscoveryStage.ts +0 -46
  136. package/src/tools/execution/stages/ExecutionStage.ts +0 -48
  137. package/src/tools/execution/stages/FormattingStage.ts +0 -44
  138. package/src/tools/execution/stages/HookStage.ts +0 -72
  139. package/src/tools/execution/stages/PermissionStage.ts +0 -287
  140. package/src/tools/execution/stages/PostHookStage.ts +0 -71
  141. package/src/tools/execution/stages/index.ts +0 -12
  142. package/src/tools/execution/test.ts +0 -266
  143. package/src/tools/execution/types.ts +0 -273
  144. package/src/tools/index.ts +0 -81
  145. package/src/tools/registry.ts +0 -304
  146. package/src/tools/schemas.ts +0 -109
  147. package/src/tools/test.ts +0 -220
  148. package/src/tools/types.ts +0 -175
  149. package/src/tools/validation/PermissionChecker.ts +0 -242
  150. package/src/tools/validation/SensitiveFileDetector.ts +0 -210
  151. package/src/tools/validation/index.ts +0 -11
  152. package/src/ui/App.tsx +0 -166
  153. package/src/ui/components/AegisInterface.tsx +0 -484
  154. package/src/ui/components/common/ChatSearch.tsx +0 -150
  155. package/src/ui/components/common/ErrorBoundary.tsx +0 -82
  156. package/src/ui/components/common/ExitMessage.tsx +0 -120
  157. package/src/ui/components/common/LoadingIndicator.tsx +0 -49
  158. package/src/ui/components/common/index.ts +0 -6
  159. package/src/ui/components/dialog/ConfirmationPrompt.tsx +0 -208
  160. package/src/ui/components/dialog/InteractiveSelector.tsx +0 -149
  161. package/src/ui/components/dialog/SetupWizard.tsx +0 -297
  162. package/src/ui/components/dialog/UpdatePrompt.tsx +0 -155
  163. package/src/ui/components/dialog/index.ts +0 -8
  164. package/src/ui/components/index.ts +0 -28
  165. package/src/ui/components/input/CommandSuggestions.tsx +0 -139
  166. package/src/ui/components/input/CustomTextInput.tsx +0 -220
  167. package/src/ui/components/input/InputArea.tsx +0 -361
  168. package/src/ui/components/input/PromptSuggestions.tsx +0 -66
  169. package/src/ui/components/input/index.ts +0 -6
  170. package/src/ui/components/layout/ChatStatusBar.tsx +0 -90
  171. package/src/ui/components/layout/ContextBar.tsx +0 -79
  172. package/src/ui/components/layout/MessageArea.tsx +0 -96
  173. package/src/ui/components/layout/MessageList.tsx +0 -647
  174. package/src/ui/components/layout/MessageSeparator.tsx +0 -26
  175. package/src/ui/components/layout/WelcomeMessage.tsx +0 -93
  176. package/src/ui/components/layout/index.ts +0 -7
  177. package/src/ui/components/markdown/CodeHighlighter.tsx +0 -292
  178. package/src/ui/components/markdown/MessageRenderer.tsx +0 -1211
  179. package/src/ui/components/markdown/index.ts +0 -8
  180. package/src/ui/components/markdown/parser.ts +0 -336
  181. package/src/ui/components/markdown/types.ts +0 -66
  182. package/src/ui/focus/FocusManager.ts +0 -137
  183. package/src/ui/focus/index.ts +0 -13
  184. package/src/ui/focus/types.ts +0 -54
  185. package/src/ui/focus/useFocus.ts +0 -75
  186. package/src/ui/hooks/index.ts +0 -11
  187. package/src/ui/hooks/useAgent.ts +0 -284
  188. package/src/ui/hooks/useCommandHistory.ts +0 -87
  189. package/src/ui/hooks/useCommandProcessor.ts +0 -443
  190. package/src/ui/hooks/useConfirmation.ts +0 -99
  191. package/src/ui/hooks/useCtrlCHandler.ts +0 -100
  192. package/src/ui/hooks/useInputBuffer.ts +0 -122
  193. package/src/ui/hooks/useTerminalSize.ts +0 -68
  194. package/src/ui/hooks/useTerminalWidth.ts +0 -5
  195. package/src/ui/hooks/useWindowedList.ts +0 -118
  196. package/src/ui/render-debugger.ts +0 -621
  197. package/src/ui/test.ts +0 -189
  198. package/src/ui/themes/ThemeManager.ts +0 -332
  199. package/src/ui/themes/aegisTheme.ts +0 -87
  200. package/src/ui/themes/darkTheme.ts +0 -87
  201. package/src/ui/themes/defaultTheme.ts +0 -85
  202. package/src/ui/themes/index.ts +0 -10
  203. package/src/ui/themes/lightTheme.ts +0 -89
  204. package/src/ui/themes/popularThemes.ts +0 -187
  205. package/src/ui/themes/types.ts +0 -130
  206. package/src/utils/clipboard.ts +0 -48
  207. package/src/utils/debug.ts +0 -43
  208. package/src/utils/environment.ts +0 -68
  209. package/src/utils/index.ts +0 -10
@@ -1,621 +0,0 @@
1
- /**
2
- * AEGISCode Rendering Debugger
3
- *
4
- * Injects probes into the rendering pipeline to detect:
5
- * 1. Re-render storms (>10 renders/sec on any component)
6
- * 2. Cascading re-renders (parent triggers child unnecessarily)
7
- * 3. Stale closures / missing deps (state change without re-render)
8
- * 4. Infinite render loops (render → state → render without RAF break)
9
- * 5. Streaming render latency (time between store flush and DOM update)
10
- * 6. Memo comparator regressions (skipped re-render when content changed)
11
- * 7. Zustand subscription leaks (subscriptions not cleaned up)
12
- *
13
- * Usage:
14
- * import { startRenderDebugger, stopRenderDebugger } from './render-debugger.js'
15
- * startRenderDebugger()
16
- *
17
- * Or via CLI:
18
- * aegis --debug-rendering
19
- */
20
-
21
- // ============ Types ============
22
-
23
- interface RenderEvent {
24
- type: 'mount' | 'update' | 'unmount' | 'state-change' | 'store-sub' | 'raf-poll'
25
- component: string
26
- timestamp: number
27
- duration?: number // ms since last render of same component
28
- prevProps?: string // JSON-hash of previous props
29
- nextProps?: string // JSON-hash of current props
30
- stack?: string // limited call stack
31
- messageId?: string // for streaming messages
32
- contentLen?: number // content length delta
33
- storeAction?: string // which store action triggered this
34
- }
35
-
36
- interface ComponentStats {
37
- renderCount: number
38
- lastRender: number
39
- minInterval: number
40
- maxInterval: number
41
- totalTime: number // ms since first render
42
- warnings: string[]
43
- }
44
-
45
- interface DebuggerState {
46
- enabled: boolean
47
- events: RenderEvent[]
48
- componentStats: Map<string, ComponentStats>
49
- intervalId: ReturnType<typeof setInterval> | null
50
- originalRaf: typeof requestAnimationFrame
51
- originalSetState: any
52
- componentRenderCounts: Map<string, number>
53
- history: RenderEvent[]
54
- }
55
-
56
- // ============ Globals ============
57
-
58
- const RENDER_STORM_THRESHOLD = 10 // renders/sec
59
- const CASCADE_THRESHOLD = 50 // ms between parent→child renders
60
- const MAX_EVENTS = 1000
61
- const REPORT_INTERVAL = 3000 // ms between auto-reports
62
-
63
- const state: DebuggerState = {
64
- enabled: false,
65
- events: [],
66
- componentStats: new Map(),
67
- intervalId: null,
68
- originalRaf: globalThis.requestAnimationFrame,
69
- originalSetState: null,
70
- componentRenderCounts: new Map(),
71
- history: [],
72
- }
73
-
74
- // ============ Helpers ============
75
-
76
- function componentName(element: any): string {
77
- if (!element) return '<unknown>'
78
- if (typeof element === 'string') return element
79
- if (element.displayName) return element.displayName
80
- if (element.name) return element.name
81
- if (element.type) {
82
- if (typeof element.type === 'string') return element.type
83
- return element.type.displayName || element.type.name || '<anonymous>'
84
- }
85
- return '<unknown>'
86
- }
87
-
88
- function hashProps(props: Record<string, any>): string {
89
- const keys = Object.keys(props).sort()
90
- const parts = keys.map(k => {
91
- const v = props[k]
92
- if (typeof v === 'function') return `${k}:fn`
93
- if (typeof v === 'object' && v !== null) return `${k}:obj`
94
- return `${k}:${String(v).slice(0, 50)}`
95
- })
96
- return parts.join('|')
97
- }
98
-
99
- function getCallStack(limit = 3): string {
100
- const err = new Error()
101
- const stack = err.stack?.split('\n').slice(3, 3 + limit) || []
102
- return stack.map(s => s.trim()).join(' ← ')
103
- }
104
-
105
- // ============ Component Render Hooks ============
106
-
107
- /**
108
- * Patch React.createElement to track component renders.
109
- * We wrap the original to count renders per component type.
110
- */
111
- function patchReactCreateElement(React: any): () => void {
112
- if (!React || !React.createElement) return () => {}
113
-
114
- const originalCreateElement = React.createElement
115
-
116
- React.createElement = function patchedCreateElement(type: any, props: any, ...children: any[]) {
117
- if (state.enabled) {
118
- const name = componentName({ type })
119
- const count = state.componentRenderCounts.get(name) || 0
120
- state.componentRenderCounts.set(name, count + 1)
121
-
122
- const event: RenderEvent = {
123
- type: count === 0 ? 'mount' : 'update',
124
- component: name,
125
- timestamp: performance.now(),
126
- stack: getCallStack(2),
127
- nextProps: props ? hashProps(props) : '{}',
128
- }
129
- state.events.push(event)
130
- if (state.events.length > MAX_EVENTS) state.events.shift()
131
- }
132
- return originalCreateElement.call(React, type, props, ...children)
133
- }
134
-
135
- return () => {
136
- React.createElement = originalCreateElement
137
- }
138
- }
139
-
140
- // ============ Store Subscription Monitor ============
141
-
142
- let storeUnsubPatched = false
143
-
144
- /**
145
- * Patch zustand subscribe to detect subscription leaks
146
- * and log store action → render causality.
147
- */
148
- function patchStoreSubscribe(store: any): () => void {
149
- if (!store || storeUnsubPatched) return () => {}
150
- storeUnsubPatched = true
151
-
152
- const origSubscribe = store.subscribe.bind(store)
153
-
154
- const wrappedSubscribe = (selector: any, callback?: any) => {
155
- const wrappedCallback = callback
156
- ? (state: any, prevState?: any) => {
157
- if (state.enabled) {
158
- state.events.push({
159
- type: 'store-sub',
160
- component: 'store',
161
- timestamp: performance.now(),
162
- storeAction: 'subscribe-callback fired',
163
- })
164
- }
165
- callback(state, prevState)
166
- }
167
- : selector
168
-
169
- const unsub = origSubscribe(selector, wrappedCallback)
170
- return unsub
171
- }
172
-
173
- store.subscribe = wrappedSubscribe
174
-
175
- return () => {
176
- store.subscribe = origSubscribe
177
- storeUnsubPatched = false
178
- }
179
- }
180
-
181
- // ============ RAF Monitor ============
182
-
183
- let rafCallCount = 0
184
- let rafResetTimer: ReturnType<typeof setTimeout> | null = null
185
-
186
- function patchRAF(): () => void {
187
- const originalRaf = globalThis.requestAnimationFrame
188
- const originalCaf = globalThis.cancelAnimationFrame
189
-
190
- const activeRafs = new Set<number>()
191
-
192
- ;(globalThis as any).requestAnimationFrame = (cb: FrameRequestCallback): number => {
193
- rafCallCount++
194
- if (rafResetTimer) clearTimeout(rafResetTimer)
195
- rafResetTimer = setTimeout(() => { rafCallCount = 0 }, 1000)
196
-
197
- const wrapped: FrameRequestCallback = (time) => {
198
- activeRafs.delete(id)
199
- cb(time)
200
- }
201
- const id = originalRaf.call(globalThis, wrapped)
202
- activeRafs.add(id)
203
- return id
204
- }
205
-
206
- ;(globalThis as any).cancelAnimationFrame = (id: number) => {
207
- activeRafs.delete(id)
208
- originalCaf.call(globalThis, id)
209
- }
210
-
211
- return () => {
212
- ;(globalThis as any).requestAnimationFrame = originalRaf
213
- ;(globalThis as any).cancelAnimationFrame = originalCaf
214
- activeRafs.forEach(id => originalCaf.call(globalThis, id))
215
- activeRafs.clear()
216
- }
217
- }
218
-
219
- // ============ Console Monitoring ============
220
-
221
- function patchConsole(): () => void {
222
- const origWarn = console.warn
223
- const origError = console.error
224
-
225
- ;(console as any).warn = (...args: any[]) => {
226
- const msg = args.join(' ')
227
- if (
228
- msg.includes('maximum update depth') ||
229
- msg.includes('React has detected') ||
230
- msg.includes('Cannot update during') ||
231
- msg.includes('render') && msg.includes('state')
232
- ) {
233
- state.events.push({
234
- type: 'update',
235
- component: '[React Warning]',
236
- timestamp: performance.now(),
237
- nextProps: msg.slice(0, 200),
238
- stack: getCallStack(5),
239
- })
240
- }
241
- return origWarn.apply(console, args)
242
- }
243
-
244
- ;(console as any).error = (...args: any[]) => {
245
- const msg = args.join(' ')
246
- if (
247
- msg.includes('Minified React error') ||
248
- msg.includes('Rendered more hooks') ||
249
- msg.includes('Rendered fewer hooks')
250
- ) {
251
- state.events.push({
252
- type: 'update',
253
- component: '[React Error]',
254
- timestamp: performance.now(),
255
- nextProps: msg.slice(0, 200),
256
- stack: getCallStack(5),
257
- })
258
- }
259
- return origError.apply(console, args)
260
- }
261
-
262
- return () => {
263
- console.warn = origWarn
264
- console.error = origError
265
- }
266
- }
267
-
268
- // ============ Analysis ============
269
-
270
- function analyzeRenderEvents(): {
271
- storms: string[]
272
- cascades: string[]
273
- loops: boolean
274
- streamingIssues: string[]
275
- summary: Record<string, number>
276
- } {
277
- const result = {
278
- storms: [] as string[],
279
- cascades: [] as string[],
280
- loops: false,
281
- streamingIssues: [] as string[],
282
- summary: {} as Record<string, number>,
283
- }
284
-
285
- const perComponent = new Map<string, RenderEvent[]>()
286
- for (const ev of state.events) {
287
- if (ev.type === 'mount' || ev.type === 'update') {
288
- const list = perComponent.get(ev.component) || []
289
- list.push(ev)
290
- perComponent.set(ev.component, list)
291
- }
292
- }
293
-
294
- // 1. Re-render storms
295
- for (const [name, events] of Array.from(perComponent.entries())) {
296
- if (events.length < 5) continue
297
-
298
- // Check any 1-second window
299
- for (let i = 0; i < events.length - RENDER_STORM_THRESHOLD; i++) {
300
- const windowEnd = events[i].timestamp + 1000
301
- const count = events.filter(e => e.timestamp >= events[i].timestamp && e.timestamp <= windowEnd).length
302
- if (count >= RENDER_STORM_THRESHOLD) {
303
- result.storms.push(`${name}: ${count} renders within 1s window (threshold: ${RENDER_STORM_THRESHOLD})`)
304
- break
305
- }
306
- }
307
- }
308
-
309
- // 2. Cascading re-renders (rapid parent→child chain)
310
- const allEvents = state.events.filter(e => e.type === 'mount' || e.type === 'update')
311
- for (let i = 1; i < allEvents.length; i++) {
312
- const interval = allEvents[i].timestamp - allEvents[i - 1].timestamp
313
- if (interval < CASCADE_THRESHOLD && allEvents[i].component !== allEvents[i - 1].component) {
314
- result.cascades.push(
315
- `${allEvents[i - 1].component} → ${allEvents[i].component} (${interval.toFixed(1)}ms)`
316
- )
317
- }
318
- }
319
-
320
- // 3. Infinite render loop detection (same component in rapid succession)
321
- let loopStreak = 0
322
- for (let i = 1; i < allEvents.length; i++) {
323
- if (
324
- allEvents[i].component === allEvents[i - 1].component &&
325
- allEvents[i].timestamp - allEvents[i - 1].timestamp < 16 // < 1 frame
326
- ) {
327
- loopStreak++
328
- if (loopStreak >= 5) {
329
- result.loops = true
330
- result.storms.push(`${allEvents[i].component}: POSSIBLE INFINITE LOOP (${loopStreak} renders in <16ms each)`)
331
- break
332
- }
333
- } else {
334
- loopStreak = 0
335
- }
336
- }
337
-
338
- // 4. Streaming latency
339
- const subEvents = state.events.filter(e => e.type === 'store-sub' && e.timestamp > 0)
340
- const renderEvents = state.events.filter(e => e.type === 'update' && e.component === 'MessageRenderer')
341
- if (subEvents.length > 0 && renderEvents.length > 0) {
342
- const avg = renderEvents.length / (subEvents.length || 1)
343
- if (avg < 0.5) {
344
- result.streamingIssues.push(`Store subscriptions (${subEvents.length}) vs renders (${renderEvents.length}): possible skipped updates (ratio: ${avg.toFixed(2)})`)
345
- }
346
- }
347
-
348
- // RAF call rate
349
- if (rafCallCount > 60) {
350
- result.streamingIssues.push(`High RAF call rate: ${rafCallCount}/sec (should be ~60 max)`)
351
- }
352
-
353
- // Summary
354
- for (const [name, events] of Array.from(perComponent.entries())) {
355
- result.summary[name] = events.length
356
- }
357
-
358
- // Deduplicate using forEach
359
- const stormSet = new Set<string>();
360
- result.storms.forEach(s => stormSet.add(s));
361
- result.storms = Array.from(stormSet);
362
- result.cascades = result.cascades.slice(0, 20)
363
-
364
- return result
365
- }
366
-
367
- // ============ Reporting ============
368
-
369
- function formatDuration(ms: number): string {
370
- if (ms < 1) return `${(ms * 1000).toFixed(0)}μs`
371
- if (ms < 1000) return `${ms.toFixed(1)}ms`
372
- return `${(ms / 1000).toFixed(1)}s`
373
- }
374
-
375
- function generateReport(): string {
376
- const analysis = analyzeRenderEvents()
377
- const lines: string[] = []
378
- const now = new Date().toISOString()
379
-
380
- lines.push(`\n${'═'.repeat(60)}`)
381
- lines.push(` AEGISCode Rendering Report — ${now}`)
382
- lines.push(`${'═'.repeat(60)}`)
383
-
384
- // Summary
385
- lines.push(`\n Total render events tracked: ${state.events.length}`)
386
- lines.push(` Components tracked: ${Object.keys(analysis.summary).length}`)
387
-
388
- lines.push(`\n ── Render counts per component ──`)
389
- const sorted = Object.entries(analysis.summary).sort((a, b) => b[1] - a[1])
390
- for (const [name, count] of sorted.slice(0, 15)) {
391
- lines.push(` ${name.padEnd(30)} ${String(count).padStart(5)} renders`)
392
- }
393
-
394
- // Storms
395
- if (analysis.storms.length > 0) {
396
- lines.push(`\n ── ⚠ RENDER STORMS ──`)
397
- for (const storm of analysis.storms) {
398
- lines.push(` šŸ”“ ${storm}`)
399
- }
400
- }
401
-
402
- // Cascades
403
- if (analysis.cascades.length > 0) {
404
- lines.push(`\n ── Re-render Cascades (top 10) ──`)
405
- for (const cascade of analysis.cascades.slice(0, 10)) {
406
- lines.push(` 🟔 ${cascade}`)
407
- }
408
- }
409
-
410
- // Infinite loops
411
- if (analysis.loops) {
412
- lines.push(`\n ── šŸ”“ INFINITE RENDER LOOP DETECTED ──`)
413
- lines.push(` Immediate action required: check state updates in render cycle`)
414
- }
415
-
416
- // Streaming issues
417
- if (analysis.streamingIssues.length > 0) {
418
- lines.push(`\n ── Streaming Issues ──`)
419
- for (const issue of analysis.streamingIssues) {
420
- lines.push(` 🟠 ${issue}`)
421
- }
422
- }
423
-
424
- // Healthy
425
- if (
426
- analysis.storms.length === 0 &&
427
- analysis.cascades.length === 0 &&
428
- !analysis.loops &&
429
- analysis.streamingIssues.length === 0
430
- ) {
431
- lines.push(`\n āœ… No rendering problems detected.`)
432
- }
433
-
434
- // Recommendations
435
- if (
436
- analysis.storms.length > 0 ||
437
- analysis.cascades.length > 0 ||
438
- analysis.loops ||
439
- analysis.streamingIssues.length > 0
440
- ) {
441
- lines.push(`\n ── Recommendations ──`)
442
-
443
- if (analysis.loops) {
444
- lines.push(` 1. Break render→setState→render cycle with useRef or useEffect`)
445
- }
446
- if (analysis.storms.length > 0) {
447
- lines.push(` 2. Add React.memo() or refine memo comparator on storm components`)
448
- lines.push(` 3. Batch store updates: use flush() pattern (already in AegisInterface)`)
449
- }
450
- if (analysis.cascades.length > 0) {
451
- lines.push(` 4. Lift state up or use useContext with stable references`)
452
- lines.push(` 5. Check useCallback/useMemo deps on parent components`)
453
- }
454
- if (analysis.streamingIssues.length > 0) {
455
- lines.push(` 6. Ensure RAF poll catches content length changes (MessageList)`)
456
- lines.push(` 7. Verify MessageRenderer comparator allows streaming updates`)
457
- }
458
- }
459
-
460
- lines.push(`${'═'.repeat(60)}\n`)
461
-
462
- return lines.join('\n')
463
- }
464
-
465
- function autoReport() {
466
- if (!state.enabled) return
467
- const report = generateReport()
468
- console.log(report)
469
- }
470
-
471
- // ============ Public API ============
472
-
473
- /**
474
- * Start the rendering debugger.
475
- * Patches React.createElement, RAF, console, and zustand store.
476
- * Begins periodic reporting of rendering health.
477
- *
478
- * @param options.reportInterval - ms between auto-reports (default 3000)
479
- * @param options.verbose - console.log every render event (default false)
480
- */
481
- export async function startRenderDebugger(options?: { reportInterval?: number; verbose?: boolean }): Promise<void> {
482
- if (state.enabled) {
483
- console.log('[RenderDebugger] Already running')
484
- return
485
- }
486
-
487
- state.enabled = true
488
- const interval = options?.reportInterval || REPORT_INTERVAL
489
- const verbose = options?.verbose || false
490
-
491
- console.log(`\nšŸ” AEGISCode Rendering Debugger started (report interval: ${interval}ms)`)
492
- if (verbose) console.log(' Verbose mode: every render event will be logged')
493
-
494
- // Patch RAF
495
- const unpatches: (() => void)[] = []
496
- unpatches.push(patchRAF())
497
-
498
- // Patch console for React warnings
499
- unpatches.push(patchConsole())
500
-
501
- // Try to patch React if available
502
- try {
503
- const React = await import('react')
504
- unpatches.push(patchReactCreateElement(React.default || React))
505
- } catch {
506
- // React not loaded yet; try again after a tick
507
- setTimeout(async () => {
508
- try {
509
- const React = await import('react')
510
- unpatches.push(patchReactCreateElement(React.default || React))
511
- } catch {}
512
- }, 100)
513
- }
514
-
515
- // Auto-reporting interval
516
- state.intervalId = setInterval(() => {
517
- autoReport()
518
- }, interval)
519
-
520
- // Store reference for cleanup
521
- ;(globalThis as any).__RENDER_DEBUGGER__ = {
522
- state,
523
- unpatches,
524
- stop: stopRenderDebugger,
525
- report: generateReport,
526
- }
527
-
528
- if (!verbose) {
529
- // In non-verbose mode, suppress individual render event logging
530
- const origPush = state.events.push.bind(state.events)
531
- state.events.push = (...items: RenderEvent[]): number => {
532
- // Only log warnings to console
533
- for (const item of items) {
534
- if (item.stack?.includes('Warning') || item.component.startsWith('[React')) {
535
- console.log(`[RenderDebugger] ${item.component}: ${item.nextProps?.slice(0, 80)}`)
536
- }
537
- }
538
- return origPush(...items)
539
- }
540
- }
541
- }
542
-
543
- /**
544
- * Stop the rendering debugger and generate a final report.
545
- */
546
- export function stopRenderDebugger(): string {
547
- if (!state.enabled) {
548
- return 'Render debugger was not running.'
549
- }
550
-
551
- state.enabled = false
552
-
553
- // Restore patches
554
- const debug = (globalThis as any).__RENDER_DEBUGGER__
555
- if (debug?.unpatches) {
556
- for (const unpatch of debug.unpatches) {
557
- try { unpatch() } catch {}
558
- }
559
- }
560
-
561
- // Clear interval
562
- if (state.intervalId) {
563
- clearInterval(state.intervalId)
564
- state.intervalId = null
565
- }
566
-
567
- delete (globalThis as any).__RENDER_DEBUGGER__
568
-
569
- const finalReport = generateReport()
570
- console.log(finalReport)
571
-
572
- console.log('šŸ“Š Render Debugger stopped.')
573
- console.log(` Events captured: ${state.events.length}`)
574
-
575
- // Reset state
576
- state.events = []
577
- state.componentStats.clear()
578
- state.componentRenderCounts.clear()
579
-
580
- return finalReport
581
- }
582
-
583
- /**
584
- * Get the current analysis without stopping the debugger.
585
- */
586
- export function getRenderReport(): string {
587
- return generateReport()
588
- }
589
-
590
- /**
591
- * Get raw render events for programmatic analysis.
592
- */
593
- export function getRenderEvents(): RenderEvent[] {
594
- return [...state.events]
595
- }
596
-
597
- /**
598
- * Get the top N most frequently rendered components.
599
- */
600
- export function getHottestComponents(n = 10): Array<{ name: string; count: number }> {
601
- const counts: Record<string, number> = {}
602
- for (const ev of state.events) {
603
- if (ev.type === 'mount' || ev.type === 'update') {
604
- counts[ev.component] = (counts[ev.component] || 0) + 1
605
- }
606
- }
607
- return Object.entries(counts)
608
- .sort((a, b) => b[1] - a[1])
609
- .slice(0, n)
610
- .map(([name, count]) => ({ name, count }))
611
- }
612
-
613
- // ============ Auto-start via CLI flag ============
614
-
615
- // Check for --debug-rendering flag
616
- const debugRenderingArg = process.argv.includes('--debug-rendering')
617
- if (debugRenderingArg) {
618
- setTimeout(() => startRenderDebugger({ verbose: false }), 0)
619
- }
620
-
621
- export default { startRenderDebugger, stopRenderDebugger, getRenderReport }