@yuaone/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +15 -0
  3. package/dist/__tests__/context-manager.test.d.ts +6 -0
  4. package/dist/__tests__/context-manager.test.d.ts.map +1 -0
  5. package/dist/__tests__/context-manager.test.js +220 -0
  6. package/dist/__tests__/context-manager.test.js.map +1 -0
  7. package/dist/__tests__/governor.test.d.ts +6 -0
  8. package/dist/__tests__/governor.test.d.ts.map +1 -0
  9. package/dist/__tests__/governor.test.js +210 -0
  10. package/dist/__tests__/governor.test.js.map +1 -0
  11. package/dist/__tests__/model-router.test.d.ts +6 -0
  12. package/dist/__tests__/model-router.test.d.ts.map +1 -0
  13. package/dist/__tests__/model-router.test.js +329 -0
  14. package/dist/__tests__/model-router.test.js.map +1 -0
  15. package/dist/agent-logger.d.ts +384 -0
  16. package/dist/agent-logger.d.ts.map +1 -0
  17. package/dist/agent-logger.js +820 -0
  18. package/dist/agent-logger.js.map +1 -0
  19. package/dist/agent-loop.d.ts +163 -0
  20. package/dist/agent-loop.d.ts.map +1 -0
  21. package/dist/agent-loop.js +609 -0
  22. package/dist/agent-loop.js.map +1 -0
  23. package/dist/agent-modes.d.ts +85 -0
  24. package/dist/agent-modes.d.ts.map +1 -0
  25. package/dist/agent-modes.js +418 -0
  26. package/dist/agent-modes.js.map +1 -0
  27. package/dist/approval.d.ts +137 -0
  28. package/dist/approval.d.ts.map +1 -0
  29. package/dist/approval.js +299 -0
  30. package/dist/approval.js.map +1 -0
  31. package/dist/async-completion-queue.d.ts +56 -0
  32. package/dist/async-completion-queue.d.ts.map +1 -0
  33. package/dist/async-completion-queue.js +77 -0
  34. package/dist/async-completion-queue.js.map +1 -0
  35. package/dist/auto-fix.d.ts +174 -0
  36. package/dist/auto-fix.d.ts.map +1 -0
  37. package/dist/auto-fix.js +319 -0
  38. package/dist/auto-fix.js.map +1 -0
  39. package/dist/codebase-context.d.ts +396 -0
  40. package/dist/codebase-context.d.ts.map +1 -0
  41. package/dist/codebase-context.js +1260 -0
  42. package/dist/codebase-context.js.map +1 -0
  43. package/dist/conflict-resolver.d.ts +191 -0
  44. package/dist/conflict-resolver.d.ts.map +1 -0
  45. package/dist/conflict-resolver.js +524 -0
  46. package/dist/conflict-resolver.js.map +1 -0
  47. package/dist/constants.d.ts +52 -0
  48. package/dist/constants.d.ts.map +1 -0
  49. package/dist/constants.js +141 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/context-budget.d.ts +435 -0
  52. package/dist/context-budget.d.ts.map +1 -0
  53. package/dist/context-budget.js +903 -0
  54. package/dist/context-budget.js.map +1 -0
  55. package/dist/context-compressor.d.ts +143 -0
  56. package/dist/context-compressor.d.ts.map +1 -0
  57. package/dist/context-compressor.js +511 -0
  58. package/dist/context-compressor.js.map +1 -0
  59. package/dist/context-manager.d.ts +112 -0
  60. package/dist/context-manager.d.ts.map +1 -0
  61. package/dist/context-manager.js +247 -0
  62. package/dist/context-manager.js.map +1 -0
  63. package/dist/continuous-reflection.d.ts +267 -0
  64. package/dist/continuous-reflection.d.ts.map +1 -0
  65. package/dist/continuous-reflection.js +338 -0
  66. package/dist/continuous-reflection.js.map +1 -0
  67. package/dist/cross-file-refactor.d.ts +352 -0
  68. package/dist/cross-file-refactor.d.ts.map +1 -0
  69. package/dist/cross-file-refactor.js +1544 -0
  70. package/dist/cross-file-refactor.js.map +1 -0
  71. package/dist/dag-orchestrator.d.ts +138 -0
  72. package/dist/dag-orchestrator.d.ts.map +1 -0
  73. package/dist/dag-orchestrator.js +379 -0
  74. package/dist/dag-orchestrator.js.map +1 -0
  75. package/dist/debate-orchestrator.d.ts +301 -0
  76. package/dist/debate-orchestrator.d.ts.map +1 -0
  77. package/dist/debate-orchestrator.js +719 -0
  78. package/dist/debate-orchestrator.js.map +1 -0
  79. package/dist/dependency-analyzer.d.ts +113 -0
  80. package/dist/dependency-analyzer.d.ts.map +1 -0
  81. package/dist/dependency-analyzer.js +444 -0
  82. package/dist/dependency-analyzer.js.map +1 -0
  83. package/dist/design-loop.d.ts +59 -0
  84. package/dist/design-loop.d.ts.map +1 -0
  85. package/dist/design-loop.js +344 -0
  86. package/dist/design-loop.js.map +1 -0
  87. package/dist/doc-intelligence.d.ts +383 -0
  88. package/dist/doc-intelligence.d.ts.map +1 -0
  89. package/dist/doc-intelligence.js +1307 -0
  90. package/dist/doc-intelligence.js.map +1 -0
  91. package/dist/dynamic-role-generator.d.ts +76 -0
  92. package/dist/dynamic-role-generator.d.ts.map +1 -0
  93. package/dist/dynamic-role-generator.js +194 -0
  94. package/dist/dynamic-role-generator.js.map +1 -0
  95. package/dist/errors.d.ts +69 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +102 -0
  98. package/dist/errors.js.map +1 -0
  99. package/dist/event-bus.d.ts +159 -0
  100. package/dist/event-bus.d.ts.map +1 -0
  101. package/dist/event-bus.js +305 -0
  102. package/dist/event-bus.js.map +1 -0
  103. package/dist/execution-engine.d.ts +425 -0
  104. package/dist/execution-engine.d.ts.map +1 -0
  105. package/dist/execution-engine.js +1555 -0
  106. package/dist/execution-engine.js.map +1 -0
  107. package/dist/git-intelligence.d.ts +306 -0
  108. package/dist/git-intelligence.d.ts.map +1 -0
  109. package/dist/git-intelligence.js +1099 -0
  110. package/dist/git-intelligence.js.map +1 -0
  111. package/dist/governor.d.ts +77 -0
  112. package/dist/governor.d.ts.map +1 -0
  113. package/dist/governor.js +161 -0
  114. package/dist/governor.js.map +1 -0
  115. package/dist/hierarchical-planner.d.ts +313 -0
  116. package/dist/hierarchical-planner.d.ts.map +1 -0
  117. package/dist/hierarchical-planner.js +981 -0
  118. package/dist/hierarchical-planner.js.map +1 -0
  119. package/dist/index.d.ts +121 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +123 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/intent-inference.d.ts +103 -0
  124. package/dist/intent-inference.d.ts.map +1 -0
  125. package/dist/intent-inference.js +605 -0
  126. package/dist/intent-inference.js.map +1 -0
  127. package/dist/interrupt-manager.d.ts +143 -0
  128. package/dist/interrupt-manager.d.ts.map +1 -0
  129. package/dist/interrupt-manager.js +196 -0
  130. package/dist/interrupt-manager.js.map +1 -0
  131. package/dist/kernel.d.ts +564 -0
  132. package/dist/kernel.d.ts.map +1 -0
  133. package/dist/kernel.js +1419 -0
  134. package/dist/kernel.js.map +1 -0
  135. package/dist/language-support.d.ts +232 -0
  136. package/dist/language-support.d.ts.map +1 -0
  137. package/dist/language-support.js +1134 -0
  138. package/dist/language-support.js.map +1 -0
  139. package/dist/llm-client.d.ts +82 -0
  140. package/dist/llm-client.d.ts.map +1 -0
  141. package/dist/llm-client.js +475 -0
  142. package/dist/llm-client.js.map +1 -0
  143. package/dist/mcp-client.d.ts +232 -0
  144. package/dist/mcp-client.d.ts.map +1 -0
  145. package/dist/mcp-client.js +718 -0
  146. package/dist/mcp-client.js.map +1 -0
  147. package/dist/memory-manager.d.ts +200 -0
  148. package/dist/memory-manager.d.ts.map +1 -0
  149. package/dist/memory-manager.js +568 -0
  150. package/dist/memory-manager.js.map +1 -0
  151. package/dist/memory.d.ts +87 -0
  152. package/dist/memory.d.ts.map +1 -0
  153. package/dist/memory.js +341 -0
  154. package/dist/memory.js.map +1 -0
  155. package/dist/model-router.d.ts +245 -0
  156. package/dist/model-router.d.ts.map +1 -0
  157. package/dist/model-router.js +632 -0
  158. package/dist/model-router.js.map +1 -0
  159. package/dist/parallel-executor.d.ts +125 -0
  160. package/dist/parallel-executor.d.ts.map +1 -0
  161. package/dist/parallel-executor.js +201 -0
  162. package/dist/parallel-executor.js.map +1 -0
  163. package/dist/perf-optimizer.d.ts +212 -0
  164. package/dist/perf-optimizer.d.ts.map +1 -0
  165. package/dist/perf-optimizer.js +721 -0
  166. package/dist/perf-optimizer.js.map +1 -0
  167. package/dist/persona.d.ts +305 -0
  168. package/dist/persona.d.ts.map +1 -0
  169. package/dist/persona.js +887 -0
  170. package/dist/persona.js.map +1 -0
  171. package/dist/planner.d.ts +70 -0
  172. package/dist/planner.d.ts.map +1 -0
  173. package/dist/planner.js +264 -0
  174. package/dist/planner.js.map +1 -0
  175. package/dist/qa-pipeline.d.ts +365 -0
  176. package/dist/qa-pipeline.d.ts.map +1 -0
  177. package/dist/qa-pipeline.js +1352 -0
  178. package/dist/qa-pipeline.js.map +1 -0
  179. package/dist/reasoning-adapter.d.ts +116 -0
  180. package/dist/reasoning-adapter.d.ts.map +1 -0
  181. package/dist/reasoning-adapter.js +187 -0
  182. package/dist/reasoning-adapter.js.map +1 -0
  183. package/dist/role-registry.d.ts +55 -0
  184. package/dist/role-registry.d.ts.map +1 -0
  185. package/dist/role-registry.js +192 -0
  186. package/dist/role-registry.js.map +1 -0
  187. package/dist/sandbox-tiers.d.ts +327 -0
  188. package/dist/sandbox-tiers.d.ts.map +1 -0
  189. package/dist/sandbox-tiers.js +928 -0
  190. package/dist/sandbox-tiers.js.map +1 -0
  191. package/dist/security-scanner.d.ts +222 -0
  192. package/dist/security-scanner.d.ts.map +1 -0
  193. package/dist/security-scanner.js +1129 -0
  194. package/dist/security-scanner.js.map +1 -0
  195. package/dist/security.d.ts +93 -0
  196. package/dist/security.d.ts.map +1 -0
  197. package/dist/security.js +393 -0
  198. package/dist/security.js.map +1 -0
  199. package/dist/self-reflection.d.ts +397 -0
  200. package/dist/self-reflection.d.ts.map +1 -0
  201. package/dist/self-reflection.js +908 -0
  202. package/dist/self-reflection.js.map +1 -0
  203. package/dist/session-persistence.d.ts +191 -0
  204. package/dist/session-persistence.d.ts.map +1 -0
  205. package/dist/session-persistence.js +395 -0
  206. package/dist/session-persistence.js.map +1 -0
  207. package/dist/speculative-executor.d.ts +210 -0
  208. package/dist/speculative-executor.d.ts.map +1 -0
  209. package/dist/speculative-executor.js +618 -0
  210. package/dist/speculative-executor.js.map +1 -0
  211. package/dist/state-machine.d.ts +289 -0
  212. package/dist/state-machine.d.ts.map +1 -0
  213. package/dist/state-machine.js +695 -0
  214. package/dist/state-machine.js.map +1 -0
  215. package/dist/sub-agent.d.ts +177 -0
  216. package/dist/sub-agent.d.ts.map +1 -0
  217. package/dist/sub-agent.js +303 -0
  218. package/dist/sub-agent.js.map +1 -0
  219. package/dist/system-prompt.d.ts +26 -0
  220. package/dist/system-prompt.d.ts.map +1 -0
  221. package/dist/system-prompt.js +84 -0
  222. package/dist/system-prompt.js.map +1 -0
  223. package/dist/test-intelligence.d.ts +439 -0
  224. package/dist/test-intelligence.d.ts.map +1 -0
  225. package/dist/test-intelligence.js +1165 -0
  226. package/dist/test-intelligence.js.map +1 -0
  227. package/dist/types.d.ts +632 -0
  228. package/dist/types.d.ts.map +1 -0
  229. package/dist/types.js +6 -0
  230. package/dist/types.js.map +1 -0
  231. package/dist/vector-index.d.ts +314 -0
  232. package/dist/vector-index.d.ts.map +1 -0
  233. package/dist/vector-index.js +618 -0
  234. package/dist/vector-index.js.map +1 -0
  235. package/package.json +41 -0
@@ -0,0 +1,721 @@
1
+ /**
2
+ * @module perf-optimizer
3
+ * @description YUAN Performance Optimizer — monitors and optimizes agent execution performance.
4
+ *
5
+ * Provides:
6
+ * - Execution timing per phase and tool call
7
+ * - Token budget tracking and waste detection
8
+ * - Parallelization hint generation from task dependency graphs
9
+ * - Content-addressable caching for tool calls and LLM responses
10
+ * - Human-readable performance reports with efficiency scoring
11
+ *
12
+ * Only depends on Node builtins (crypto for cache keys).
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ // ══════════════════════════════════════════════════════════════════════
16
+ // Constants
17
+ // ══════════════════════════════════════════════════════════════════════
18
+ const DEFAULT_MAX_CACHE_SIZE = 500;
19
+ const DEFAULT_MAX_CACHE_MEMORY = 50 * 1024 * 1024; // 50 MB
20
+ const DEFAULT_BOTTLENECK_THRESHOLD = 40; // percent
21
+ const MAX_METRICS = 10_000;
22
+ const MAX_TOOL_CALL_RECORDS = 5_000;
23
+ const MAX_SUGGESTIONS = 50;
24
+ const MAX_BOTTLENECKS = 50;
25
+ const MAX_PARALLEL_HINTS = 100;
26
+ /** Estimated average ms saved per cache hit (for reporting) */
27
+ const AVG_CACHE_HIT_SAVING_MS = 200;
28
+ // ══════════════════════════════════════════════════════════════════════
29
+ // PerfOptimizer
30
+ // ══════════════════════════════════════════════════════════════════════
31
+ /**
32
+ * Monitors and optimizes agent execution performance.
33
+ *
34
+ * Tracks phase timing, token usage, tool call durations, and caching.
35
+ * Generates actionable performance reports with bottleneck identification,
36
+ * parallelization hints, and an overall efficiency score.
37
+ */
38
+ export class PerfOptimizer {
39
+ config;
40
+ // Phase tracking
41
+ activePhases = new Map();
42
+ completedPhases = [];
43
+ // Tool call tracking
44
+ toolCallRecords = [];
45
+ // Metrics
46
+ metrics = [];
47
+ // Cache
48
+ cache = new Map();
49
+ totalCacheMemory = 0;
50
+ globalCacheHits = 0;
51
+ globalCacheMisses = 0;
52
+ // Historical data for comparison
53
+ previousRunDurations = [];
54
+ constructor(config) {
55
+ this.config = {
56
+ enableCaching: config?.enableCaching ?? true,
57
+ maxCacheSize: config?.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE,
58
+ maxCacheMemory: config?.maxCacheMemory ?? DEFAULT_MAX_CACHE_MEMORY,
59
+ trackParallelHints: config?.trackParallelHints ?? true,
60
+ bottleneckThresholdPercent: config?.bottleneckThresholdPercent ?? DEFAULT_BOTTLENECK_THRESHOLD,
61
+ };
62
+ }
63
+ // ────────────────────────────────────────────────────────────────────
64
+ // Tracking
65
+ // ────────────────────────────────────────────────────────────────────
66
+ /**
67
+ * Mark the start of an execution phase.
68
+ * @param phase - Phase name (e.g. "analyze", "plan", "implement", "verify", "fix")
69
+ */
70
+ startPhase(phase) {
71
+ this.activePhases.set(phase, {
72
+ phase,
73
+ startTime: Date.now(),
74
+ tokens: { input: 0, output: 0 },
75
+ toolCalls: 0,
76
+ cacheHits: 0,
77
+ cacheMisses: 0,
78
+ });
79
+ this.addMetric({
80
+ name: `phase.${phase}.start`,
81
+ category: "timing",
82
+ value: Date.now(),
83
+ unit: "epoch_ms",
84
+ timestamp: Date.now(),
85
+ });
86
+ }
87
+ /**
88
+ * Mark the end of an execution phase.
89
+ * @param phase - Phase name that was previously started
90
+ * @param tokens - Optional token usage for this phase
91
+ */
92
+ endPhase(phase, tokens) {
93
+ const active = this.activePhases.get(phase);
94
+ if (!active)
95
+ return;
96
+ const endTime = Date.now();
97
+ const durationMs = endTime - active.startTime;
98
+ if (tokens) {
99
+ active.tokens.input += tokens.input;
100
+ active.tokens.output += tokens.output;
101
+ }
102
+ const completed = {
103
+ phase: active.phase,
104
+ startTime: active.startTime,
105
+ endTime,
106
+ durationMs,
107
+ tokensUsed: { ...active.tokens },
108
+ toolCalls: active.toolCalls,
109
+ cacheHits: active.cacheHits,
110
+ cacheMisses: active.cacheMisses,
111
+ };
112
+ this.completedPhases.push(completed);
113
+ this.activePhases.delete(phase);
114
+ this.addMetric({
115
+ name: `phase.${phase}.duration`,
116
+ category: "timing",
117
+ value: durationMs,
118
+ unit: "ms",
119
+ timestamp: endTime,
120
+ });
121
+ }
122
+ /**
123
+ * Record a tool call with its duration.
124
+ * Automatically tracks cache hits/misses for the active phase.
125
+ * @param tool - Tool name
126
+ * @param input - Tool input (used for cache key generation)
127
+ * @param durationMs - How long the tool call took
128
+ */
129
+ recordToolCall(tool, input, durationMs) {
130
+ const inputHash = this.hashInput(tool, input);
131
+ const cached = this.cache.has(inputHash);
132
+ if (this.toolCallRecords.length < MAX_TOOL_CALL_RECORDS) {
133
+ this.toolCallRecords.push({
134
+ tool,
135
+ inputHash,
136
+ durationMs,
137
+ timestamp: Date.now(),
138
+ cached,
139
+ });
140
+ }
141
+ // Update active phase counters
142
+ for (const active of this.activePhases.values()) {
143
+ active.toolCalls++;
144
+ if (cached) {
145
+ active.cacheHits++;
146
+ }
147
+ else {
148
+ active.cacheMisses++;
149
+ }
150
+ }
151
+ this.addMetric({
152
+ name: `tool.${tool}.duration`,
153
+ category: "timing",
154
+ value: durationMs,
155
+ unit: "ms",
156
+ timestamp: Date.now(),
157
+ });
158
+ }
159
+ /**
160
+ * Record token usage for a specific phase.
161
+ * @param phase - Phase name
162
+ * @param input - Input tokens consumed
163
+ * @param output - Output tokens generated
164
+ */
165
+ recordTokenUsage(phase, input, output) {
166
+ const active = this.activePhases.get(phase);
167
+ if (active) {
168
+ active.tokens.input += input;
169
+ active.tokens.output += output;
170
+ }
171
+ // Also update completed phase if it exists (for late-arriving token counts)
172
+ const completed = this.completedPhases.find((p) => p.phase === phase);
173
+ if (completed) {
174
+ completed.tokensUsed.input += input;
175
+ completed.tokensUsed.output += output;
176
+ }
177
+ this.addMetric({
178
+ name: `tokens.${phase}`,
179
+ category: "tokens",
180
+ value: input + output,
181
+ unit: "tokens",
182
+ timestamp: Date.now(),
183
+ });
184
+ }
185
+ // ────────────────────────────────────────────────────────────────────
186
+ // Caching
187
+ // ────────────────────────────────────────────────────────────────────
188
+ /**
189
+ * Retrieve a cached tool call result.
190
+ * @param tool - Tool name
191
+ * @param input - Tool input
192
+ * @returns Cached result or undefined if not found
193
+ */
194
+ getCached(tool, input) {
195
+ if (!this.config.enableCaching)
196
+ return undefined;
197
+ const key = this.hashInput(tool, input);
198
+ const entry = this.cache.get(key);
199
+ if (!entry) {
200
+ this.globalCacheMisses++;
201
+ return undefined;
202
+ }
203
+ entry.hits++;
204
+ entry.lastAccessedAt = Date.now();
205
+ this.globalCacheHits++;
206
+ return entry.value;
207
+ }
208
+ /**
209
+ * Store a tool call result in the cache.
210
+ * Automatically evicts LRU entries when limits are exceeded.
211
+ * @param tool - Tool name
212
+ * @param input - Tool input
213
+ * @param result - Result to cache
214
+ */
215
+ setCached(tool, input, result) {
216
+ if (!this.config.enableCaching)
217
+ return;
218
+ const key = this.hashInput(tool, input);
219
+ const serialized = JSON.stringify(result);
220
+ const sizeBytes = Buffer.byteLength(serialized, "utf8");
221
+ // Don't cache if single entry exceeds half the memory limit
222
+ if (sizeBytes > this.config.maxCacheMemory / 2)
223
+ return;
224
+ // Evict if needed
225
+ this.evictIfNeeded(sizeBytes);
226
+ // Remove old entry if updating
227
+ const existing = this.cache.get(key);
228
+ if (existing) {
229
+ this.totalCacheMemory -= existing.sizeBytes;
230
+ }
231
+ const now = Date.now();
232
+ this.cache.set(key, {
233
+ key,
234
+ value: result,
235
+ hits: 0,
236
+ createdAt: now,
237
+ lastAccessedAt: now,
238
+ sizeBytes,
239
+ });
240
+ this.totalCacheMemory += sizeBytes;
241
+ }
242
+ /**
243
+ * Clear all cached entries.
244
+ */
245
+ clearCache() {
246
+ this.cache.clear();
247
+ this.totalCacheMemory = 0;
248
+ }
249
+ // ────────────────────────────────────────────────────────────────────
250
+ // Analysis
251
+ // ────────────────────────────────────────────────────────────────────
252
+ /**
253
+ * Identify execution bottlenecks based on phase timing and token usage.
254
+ * @returns Array of bottleneck descriptions with suggestions
255
+ */
256
+ getBottlenecks() {
257
+ const bottlenecks = [];
258
+ const totalMs = this.getTotalDuration();
259
+ if (totalMs === 0)
260
+ return bottlenecks;
261
+ const threshold = this.config.bottleneckThresholdPercent;
262
+ for (const phase of this.completedPhases) {
263
+ const pct = (phase.durationMs / totalMs) * 100;
264
+ // Phase taking too long
265
+ if (pct > threshold) {
266
+ bottlenecks.push({
267
+ phase: phase.phase,
268
+ issue: `Phase "${phase.phase}" consumed ${pct.toFixed(1)}% of total time (${phase.durationMs}ms)`,
269
+ impact: pct > 60 ? "high" : "medium",
270
+ suggestion: `Consider breaking "${phase.phase}" into smaller sub-phases or parallelizing tool calls within it`,
271
+ estimatedSavingMs: Math.round(phase.durationMs * (1 - threshold / pct)),
272
+ });
273
+ }
274
+ // High cache miss rate with many tool calls
275
+ const totalCacheOps = phase.cacheHits + phase.cacheMisses;
276
+ if (totalCacheOps > 5 &&
277
+ phase.cacheMisses / totalCacheOps > 0.8) {
278
+ bottlenecks.push({
279
+ phase: phase.phase,
280
+ issue: `Phase "${phase.phase}" has ${((phase.cacheMisses / totalCacheOps) * 100).toFixed(0)}% cache miss rate across ${totalCacheOps} operations`,
281
+ impact: "medium",
282
+ suggestion: "Enable or increase cache size to avoid repeated identical tool calls",
283
+ estimatedSavingMs: Math.round(phase.cacheMisses * AVG_CACHE_HIT_SAVING_MS * 0.5),
284
+ });
285
+ }
286
+ // Token waste: high output tokens relative to input
287
+ const totalTokens = phase.tokensUsed.input + phase.tokensUsed.output;
288
+ if (totalTokens > 10_000 &&
289
+ phase.tokensUsed.output > phase.tokensUsed.input * 3) {
290
+ bottlenecks.push({
291
+ phase: phase.phase,
292
+ issue: `Phase "${phase.phase}" has disproportionate output tokens (${phase.tokensUsed.output} out vs ${phase.tokensUsed.input} in)`,
293
+ impact: "low",
294
+ suggestion: "Consider requesting more concise LLM responses or summarizing intermediate outputs",
295
+ });
296
+ }
297
+ }
298
+ // Detect repeated tool calls (same tool + input)
299
+ const callCounts = new Map();
300
+ for (const rec of this.toolCallRecords) {
301
+ const count = (callCounts.get(rec.inputHash) ?? 0) + 1;
302
+ callCounts.set(rec.inputHash, count);
303
+ }
304
+ for (const [hash, count] of callCounts) {
305
+ if (count >= 3) {
306
+ const sample = this.toolCallRecords.find((r) => r.inputHash === hash);
307
+ if (sample) {
308
+ bottlenecks.push({
309
+ phase: "global",
310
+ issue: `Tool "${sample.tool}" called ${count} times with identical input`,
311
+ impact: count >= 5 ? "high" : "medium",
312
+ suggestion: `Cache results for "${sample.tool}" to avoid redundant calls`,
313
+ estimatedSavingMs: Math.round((count - 1) * sample.durationMs),
314
+ });
315
+ }
316
+ }
317
+ }
318
+ // Historical comparison
319
+ if (this.previousRunDurations.length > 0) {
320
+ const avg = this.previousRunDurations.reduce((a, b) => a + b, 0) /
321
+ this.previousRunDurations.length;
322
+ if (totalMs > avg * 1.5) {
323
+ bottlenecks.push({
324
+ phase: "global",
325
+ issue: `This run (${totalMs}ms) is ${((totalMs / avg - 1) * 100).toFixed(0)}% slower than average (${Math.round(avg)}ms)`,
326
+ impact: totalMs > avg * 2 ? "high" : "medium",
327
+ suggestion: "Check for new bottlenecks or increased task complexity",
328
+ estimatedSavingMs: Math.round(totalMs - avg),
329
+ });
330
+ }
331
+ }
332
+ return bottlenecks.slice(0, MAX_BOTTLENECKS);
333
+ }
334
+ /**
335
+ * Analyze a task dependency graph and identify parallelization opportunities.
336
+ * @param taskDeps - Map of taskId → array of dependency taskIds
337
+ * @returns Array of parallelization hints with estimated speedup
338
+ */
339
+ getParallelHints(taskDeps) {
340
+ if (!this.config.trackParallelHints)
341
+ return [];
342
+ const hints = [];
343
+ // Find groups of tasks with the same dependencies (can run in parallel)
344
+ const depSignatures = new Map();
345
+ for (const [taskId, deps] of taskDeps) {
346
+ const sig = [...deps].sort().join(",");
347
+ const group = depSignatures.get(sig) ?? [];
348
+ group.push(taskId);
349
+ depSignatures.set(sig, group);
350
+ }
351
+ // Build duration map from tool call records
352
+ const taskDurations = new Map();
353
+ for (const rec of this.toolCallRecords) {
354
+ // Use tool name as rough proxy for task duration
355
+ const current = taskDurations.get(rec.tool) ?? 0;
356
+ taskDurations.set(rec.tool, current + rec.durationMs);
357
+ }
358
+ for (const [_sig, group] of depSignatures) {
359
+ if (group.length < 2)
360
+ continue;
361
+ // Estimate durations: use recorded data or default 1000ms
362
+ const durations = group.map((id) => taskDurations.get(id) ?? 1000);
363
+ const sequentialMs = durations.reduce((a, b) => a + b, 0);
364
+ const parallelMs = Math.max(...durations);
365
+ const speedup = parallelMs > 0 ? sequentialMs / parallelMs : 1;
366
+ if (speedup > 1.2) {
367
+ hints.push({
368
+ taskIds: group,
369
+ currentSequentialMs: sequentialMs,
370
+ estimatedParallelMs: parallelMs,
371
+ speedupFactor: Math.round(speedup * 100) / 100,
372
+ });
373
+ }
374
+ }
375
+ // Also check for independent tasks (no deps at all)
376
+ const noDeps = [];
377
+ for (const [taskId, deps] of taskDeps) {
378
+ if (deps.length === 0)
379
+ noDeps.push(taskId);
380
+ }
381
+ if (noDeps.length >= 2) {
382
+ const durations = noDeps.map((id) => taskDurations.get(id) ?? 1000);
383
+ const sequentialMs = durations.reduce((a, b) => a + b, 0);
384
+ const parallelMs = Math.max(...durations);
385
+ const speedup = parallelMs > 0 ? sequentialMs / parallelMs : 1;
386
+ if (speedup > 1.2) {
387
+ // Avoid duplicate if already added via signature grouping
388
+ const alreadyAdded = hints.some((h) => h.taskIds.length === noDeps.length &&
389
+ h.taskIds.every((id) => noDeps.includes(id)));
390
+ if (!alreadyAdded) {
391
+ hints.push({
392
+ taskIds: noDeps,
393
+ currentSequentialMs: sequentialMs,
394
+ estimatedParallelMs: parallelMs,
395
+ speedupFactor: Math.round(speedup * 100) / 100,
396
+ });
397
+ }
398
+ }
399
+ }
400
+ return hints.slice(0, MAX_PARALLEL_HINTS);
401
+ }
402
+ /**
403
+ * Calculate an overall efficiency score (0-100) based on multiple factors.
404
+ * @returns Efficiency score where 100 is optimal
405
+ */
406
+ getEfficiencyScore() {
407
+ let score = 100;
408
+ const totalMs = this.getTotalDuration();
409
+ if (totalMs === 0)
410
+ return score;
411
+ // Penalty: bottlenecks
412
+ const bottlenecks = this.getBottlenecks();
413
+ for (const b of bottlenecks) {
414
+ switch (b.impact) {
415
+ case "high":
416
+ score -= 15;
417
+ break;
418
+ case "medium":
419
+ score -= 8;
420
+ break;
421
+ case "low":
422
+ score -= 3;
423
+ break;
424
+ }
425
+ }
426
+ // Bonus: good cache hit rate
427
+ const totalCacheOps = this.globalCacheHits + this.globalCacheMisses;
428
+ if (totalCacheOps > 0) {
429
+ const hitRate = this.globalCacheHits / totalCacheOps;
430
+ if (hitRate > 0.5)
431
+ score += 5;
432
+ if (hitRate > 0.7)
433
+ score += 5;
434
+ }
435
+ // Penalty: token waste (repeated identical tool calls)
436
+ const duplicateToolCalls = this.countDuplicateToolCalls();
437
+ const totalToolCalls = this.toolCallRecords.length;
438
+ if (totalToolCalls > 0) {
439
+ const wasteRatio = duplicateToolCalls / totalToolCalls;
440
+ if (wasteRatio > 0.3)
441
+ score -= 10;
442
+ else if (wasteRatio > 0.1)
443
+ score -= 5;
444
+ }
445
+ // Penalty: slower than historical average
446
+ if (this.previousRunDurations.length > 0) {
447
+ const avg = this.previousRunDurations.reduce((a, b) => a + b, 0) /
448
+ this.previousRunDurations.length;
449
+ if (totalMs > avg * 2)
450
+ score -= 10;
451
+ else if (totalMs > avg * 1.5)
452
+ score -= 5;
453
+ }
454
+ return Math.max(0, Math.min(100, score));
455
+ }
456
+ // ────────────────────────────────────────────────────────────────────
457
+ // Reporting
458
+ // ────────────────────────────────────────────────────────────────────
459
+ /**
460
+ * Generate a complete performance report for the session.
461
+ * @param sessionId - Session identifier
462
+ * @returns Structured performance report
463
+ */
464
+ generateReport(sessionId) {
465
+ const totalDurationMs = this.getTotalDuration();
466
+ const bottlenecks = this.getBottlenecks();
467
+ const parallelHints = this.getParallelHints(new Map());
468
+ // Token summary
469
+ const byPhase = {};
470
+ let totalTokens = 0;
471
+ for (const phase of this.completedPhases) {
472
+ const phaseTotal = phase.tokensUsed.input + phase.tokensUsed.output;
473
+ byPhase[phase.phase] = phaseTotal;
474
+ totalTokens += phaseTotal;
475
+ }
476
+ const wasteEstimate = this.estimateTokenWaste();
477
+ // Cache stats
478
+ const totalCacheOps = this.globalCacheHits + this.globalCacheMisses;
479
+ const hitRate = totalCacheOps > 0 ? this.globalCacheHits / totalCacheOps : 0;
480
+ const savedMs = this.globalCacheHits * AVG_CACHE_HIT_SAVING_MS;
481
+ const suggestions = this.generateSuggestions(bottlenecks);
482
+ return {
483
+ sessionId,
484
+ totalDurationMs,
485
+ phases: [...this.completedPhases],
486
+ bottlenecks,
487
+ cacheStats: {
488
+ hits: this.globalCacheHits,
489
+ misses: this.globalCacheMisses,
490
+ hitRate: Math.round(hitRate * 1000) / 1000,
491
+ savedMs,
492
+ },
493
+ parallelHints,
494
+ tokenSummary: {
495
+ total: totalTokens,
496
+ byPhase,
497
+ wasteEstimate,
498
+ },
499
+ efficiencyScore: this.getEfficiencyScore(),
500
+ suggestions,
501
+ };
502
+ }
503
+ /**
504
+ * Format a performance report as a human-readable string.
505
+ * @param report - Report to format
506
+ * @returns Multi-line formatted string
507
+ */
508
+ formatReport(report) {
509
+ const lines = [];
510
+ lines.push("═══════════════════════════════════════════════════");
511
+ lines.push(` YUAN Performance Report — Session: ${report.sessionId}`);
512
+ lines.push("═══════════════════════════════════════════════════");
513
+ lines.push("");
514
+ // Overview
515
+ lines.push(`Total Duration: ${this.formatMs(report.totalDurationMs)}`);
516
+ lines.push(`Efficiency Score: ${report.efficiencyScore}/100`);
517
+ lines.push(`Total Tokens: ${report.tokenSummary.total.toLocaleString()}`);
518
+ lines.push("");
519
+ // Phase breakdown
520
+ lines.push("── Phase Breakdown ──────────────────────────────");
521
+ for (const phase of report.phases) {
522
+ const pct = report.totalDurationMs > 0
523
+ ? ((phase.durationMs / report.totalDurationMs) * 100).toFixed(1)
524
+ : "0.0";
525
+ const tokens = phase.tokensUsed.input + phase.tokensUsed.output;
526
+ lines.push(` ${phase.phase.padEnd(15)} ${this.formatMs(phase.durationMs).padStart(10)} (${pct}%) tokens: ${tokens.toLocaleString().padStart(8)} tools: ${phase.toolCalls} cache: ${phase.cacheHits}/${phase.cacheHits + phase.cacheMisses}`);
527
+ }
528
+ lines.push("");
529
+ // Cache stats
530
+ lines.push("── Cache Statistics ─────────────────────────────");
531
+ lines.push(` Hits: ${report.cacheStats.hits} Misses: ${report.cacheStats.misses} Rate: ${(report.cacheStats.hitRate * 100).toFixed(1)}% Saved: ~${this.formatMs(report.cacheStats.savedMs)}`);
532
+ lines.push("");
533
+ // Bottlenecks
534
+ if (report.bottlenecks.length > 0) {
535
+ lines.push("── Bottlenecks ──────────────────────────────────");
536
+ for (const b of report.bottlenecks) {
537
+ const icon = b.impact === "high"
538
+ ? "[HIGH]"
539
+ : b.impact === "medium"
540
+ ? "[MED] "
541
+ : "[LOW] ";
542
+ lines.push(` ${icon} ${b.issue}`);
543
+ lines.push(` -> ${b.suggestion}`);
544
+ if (b.estimatedSavingMs) {
545
+ lines.push(` -> Potential saving: ~${this.formatMs(b.estimatedSavingMs)}`);
546
+ }
547
+ }
548
+ lines.push("");
549
+ }
550
+ // Parallel hints
551
+ if (report.parallelHints.length > 0) {
552
+ lines.push("── Parallelization Opportunities ────────────────");
553
+ for (const h of report.parallelHints) {
554
+ lines.push(` Tasks [${h.taskIds.join(", ")}]: ${this.formatMs(h.currentSequentialMs)} -> ${this.formatMs(h.estimatedParallelMs)} (${h.speedupFactor}x speedup)`);
555
+ }
556
+ lines.push("");
557
+ }
558
+ // Token waste
559
+ if (report.tokenSummary.wasteEstimate > 0) {
560
+ lines.push("── Token Waste ──────────────────────────────────");
561
+ lines.push(` Estimated waste: ~${report.tokenSummary.wasteEstimate.toLocaleString()} tokens`);
562
+ lines.push("");
563
+ }
564
+ // Suggestions
565
+ if (report.suggestions.length > 0) {
566
+ lines.push("── Suggestions ──────────────────────────────────");
567
+ for (const s of report.suggestions) {
568
+ lines.push(` * ${s}`);
569
+ }
570
+ lines.push("");
571
+ }
572
+ lines.push("═══════════════════════════════════════════════════");
573
+ return lines.join("\n");
574
+ }
575
+ // ────────────────────────────────────────────────────────────────────
576
+ // Reset & Utility
577
+ // ────────────────────────────────────────────────────────────────────
578
+ /**
579
+ * Store current run duration for future historical comparison, then reset all state.
580
+ */
581
+ reset() {
582
+ const totalMs = this.getTotalDuration();
583
+ if (totalMs > 0) {
584
+ this.previousRunDurations.push(totalMs);
585
+ // Keep bounded
586
+ if (this.previousRunDurations.length > 100) {
587
+ this.previousRunDurations = this.previousRunDurations.slice(-50);
588
+ }
589
+ }
590
+ this.activePhases.clear();
591
+ this.completedPhases = [];
592
+ this.toolCallRecords = [];
593
+ this.metrics = [];
594
+ this.clearCache();
595
+ this.globalCacheHits = 0;
596
+ this.globalCacheMisses = 0;
597
+ }
598
+ // ────────────────────────────────────────────────────────────────────
599
+ // Private helpers
600
+ // ────────────────────────────────────────────────────────────────────
601
+ /** Add a metric, bounded by MAX_METRICS */
602
+ addMetric(metric) {
603
+ if (this.metrics.length < MAX_METRICS) {
604
+ this.metrics.push(metric);
605
+ }
606
+ }
607
+ /** Compute total duration from completed phases */
608
+ getTotalDuration() {
609
+ if (this.completedPhases.length === 0)
610
+ return 0;
611
+ const start = Math.min(...this.completedPhases.map((p) => p.startTime));
612
+ const end = Math.max(...this.completedPhases.map((p) => p.endTime));
613
+ return end - start;
614
+ }
615
+ /** Generate a content-addressable hash for tool+input */
616
+ hashInput(tool, input) {
617
+ const content = `${tool}:${JSON.stringify(input)}`;
618
+ return createHash("sha256").update(content).digest("hex").slice(0, 32);
619
+ }
620
+ /** Evict LRU cache entries until there's room */
621
+ evictIfNeeded(newEntrySize) {
622
+ // Evict by count
623
+ while (this.cache.size >= this.config.maxCacheSize) {
624
+ this.evictLRU();
625
+ }
626
+ // Evict by memory
627
+ while (this.totalCacheMemory + newEntrySize > this.config.maxCacheMemory &&
628
+ this.cache.size > 0) {
629
+ this.evictLRU();
630
+ }
631
+ }
632
+ /** Remove the least recently used cache entry */
633
+ evictLRU() {
634
+ let oldestKey = null;
635
+ let oldestTime = Infinity;
636
+ for (const [key, entry] of this.cache) {
637
+ if (entry.lastAccessedAt < oldestTime) {
638
+ oldestTime = entry.lastAccessedAt;
639
+ oldestKey = key;
640
+ }
641
+ }
642
+ if (oldestKey) {
643
+ const entry = this.cache.get(oldestKey);
644
+ if (entry) {
645
+ this.totalCacheMemory -= entry.sizeBytes;
646
+ }
647
+ this.cache.delete(oldestKey);
648
+ }
649
+ }
650
+ /** Count tool calls with duplicate inputs */
651
+ countDuplicateToolCalls() {
652
+ const seen = new Set();
653
+ let dupes = 0;
654
+ for (const rec of this.toolCallRecords) {
655
+ if (seen.has(rec.inputHash)) {
656
+ dupes++;
657
+ }
658
+ else {
659
+ seen.add(rec.inputHash);
660
+ }
661
+ }
662
+ return dupes;
663
+ }
664
+ /** Estimate wasted tokens from redundant tool calls */
665
+ estimateTokenWaste() {
666
+ // Each redundant tool call wastes ~input description + output tokens
667
+ const dupes = this.countDuplicateToolCalls();
668
+ // Rough estimate: 500 tokens per redundant call (tool description + result)
669
+ return dupes * 500;
670
+ }
671
+ /** Generate actionable suggestions from bottleneck analysis */
672
+ generateSuggestions(bottlenecks) {
673
+ const suggestions = [];
674
+ // From bottlenecks
675
+ for (const b of bottlenecks) {
676
+ if (b.impact === "high") {
677
+ suggestions.push(b.suggestion);
678
+ }
679
+ }
680
+ // Cache suggestions
681
+ const totalCacheOps = this.globalCacheHits + this.globalCacheMisses;
682
+ if (totalCacheOps > 10) {
683
+ const hitRate = this.globalCacheHits / totalCacheOps;
684
+ if (hitRate < 0.3) {
685
+ suggestions.push("Cache hit rate is below 30%. Consider enabling caching or increasing cache size.");
686
+ }
687
+ }
688
+ else if (totalCacheOps === 0 && this.toolCallRecords.length > 10) {
689
+ suggestions.push("No cache usage detected. Enable caching to avoid redundant tool calls.");
690
+ }
691
+ // Token suggestions
692
+ let totalTokens = 0;
693
+ for (const phase of this.completedPhases) {
694
+ totalTokens +=
695
+ phase.tokensUsed.input + phase.tokensUsed.output;
696
+ }
697
+ if (totalTokens > 200_000) {
698
+ suggestions.push("Token usage exceeds 200k. Consider context compression or summarizing intermediate results.");
699
+ }
700
+ // Phase balance
701
+ if (this.completedPhases.length > 1) {
702
+ const totalMs = this.getTotalDuration();
703
+ const implementPhase = this.completedPhases.find((p) => p.phase === "implement");
704
+ if (implementPhase &&
705
+ totalMs > 0 &&
706
+ implementPhase.durationMs / totalMs < 0.3) {
707
+ suggestions.push("Implementation phase is less than 30% of total time. Analysis/planning may be over-scoped.");
708
+ }
709
+ }
710
+ return suggestions.slice(0, MAX_SUGGESTIONS);
711
+ }
712
+ /** Format milliseconds as human-readable string */
713
+ formatMs(ms) {
714
+ if (ms < 1000)
715
+ return `${ms}ms`;
716
+ if (ms < 60_000)
717
+ return `${(ms / 1000).toFixed(1)}s`;
718
+ return `${(ms / 60_000).toFixed(1)}m`;
719
+ }
720
+ }
721
+ //# sourceMappingURL=perf-optimizer.js.map