@evo-dev/core 0.0.1-alpha.2 → 0.0.1-alpha.20

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 (79) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +5 -3
  2. package/assets/team/agents/code-reviewer.md +48 -0
  3. package/assets/team/agents/docs-maintainer.md +51 -0
  4. package/assets/team/agents/implementation-engineer.md +51 -0
  5. package/assets/team/agents/product-scope-analyst.md +58 -0
  6. package/assets/team/agents/release-engineer.md +55 -0
  7. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  8. package/assets/team/agents/solution-architect.md +51 -0
  9. package/assets/team/agents/verification-engineer.md +51 -0
  10. package/assets/team/team.md +102 -0
  11. package/dist/assets/index.js +5 -5
  12. package/dist/config/index.js +793 -241
  13. package/dist/index.js +20840 -12908
  14. package/dist/plugins/index.js +13 -13
  15. package/package.json +1 -1
  16. package/src/agents/index.ts +1 -265
  17. package/src/code-agent-traces/index.ts +11 -12
  18. package/src/config/index.ts +2 -0
  19. package/src/config/settings.ts +116 -7
  20. package/src/config/store.ts +1 -1
  21. package/src/daemon/index.ts +1 -41
  22. package/src/evolution/candidates/index.ts +730 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +287 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +9 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/retention.ts +643 -0
  32. package/src/evolution/evidence/session-memory/segment.ts +216 -0
  33. package/src/evolution/evidence/session-memory/semantic-packet.ts +408 -0
  34. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  35. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  36. package/src/evolution/evidence/session-memory/storage.ts +744 -0
  37. package/src/evolution/evidence/session-memory/types.ts +296 -0
  38. package/src/evolution/evidence/session-memory/updater.ts +199 -0
  39. package/src/evolution/formatters.ts +169 -0
  40. package/src/evolution/imports/apply.ts +435 -0
  41. package/src/evolution/imports/diff.ts +472 -0
  42. package/src/evolution/imports/index.ts +7 -0
  43. package/src/evolution/imports/materialize.ts +640 -0
  44. package/src/evolution/imports/paths.ts +129 -0
  45. package/src/evolution/imports/stage.ts +414 -0
  46. package/src/evolution/imports/storage.ts +952 -0
  47. package/src/evolution/imports/types.ts +226 -0
  48. package/src/evolution/index.ts +19 -2827
  49. package/src/evolution/knowledge/change-store.ts +558 -0
  50. package/src/evolution/knowledge/changes.ts +459 -0
  51. package/src/evolution/knowledge/freshness.ts +69 -0
  52. package/src/{knowledge → evolution/knowledge}/index.ts +1532 -206
  53. package/src/evolution/knowledge/review.ts +446 -0
  54. package/src/evolution/knowledge/support.ts +135 -0
  55. package/src/evolution/paths.ts +44 -0
  56. package/src/evolution/processor/distillation.ts +518 -0
  57. package/src/evolution/processor/index.ts +3 -0
  58. package/src/evolution/processor/process.ts +594 -0
  59. package/src/{learning → evolution/review}/index.ts +10 -14
  60. package/src/evolution/schema.ts +639 -0
  61. package/src/evolution/shared.ts +1053 -0
  62. package/src/evolution/triggers/classification.ts +102 -0
  63. package/src/evolution/triggers/index.ts +295 -0
  64. package/src/hooks/index.ts +281 -197
  65. package/src/index.ts +15 -4
  66. package/src/projects/index.ts +934 -0
  67. package/src/runtime-logs/index.ts +100 -13
  68. package/src/team/index.ts +582 -3
  69. package/src/utils/errors.ts +13 -0
  70. package/src/utils/fs.ts +40 -0
  71. package/src/utils/hash.ts +9 -0
  72. package/src/utils/ids.ts +12 -0
  73. package/src/utils/index.ts +7 -0
  74. package/src/utils/parsing.ts +11 -0
  75. package/src/utils/text.ts +18 -0
  76. package/src/utils/time.ts +5 -0
  77. package/src/workflow/index.ts +3 -21
  78. package/src/project/index.ts +0 -507
  79. package/src/task/index.ts +0 -840
@@ -0,0 +1,20 @@
1
+ export {
2
+ formatEvolutionProcessResult,
3
+ formatEvolutionReviewSnapshot,
4
+ } from "../formatters.ts";
5
+ export { processEvolutionTriggers } from "../processor/index.ts";
6
+ export { readEvolutionReviewSnapshot } from "../candidates/index.ts";
7
+ export {
8
+ listEvolutionTriggers,
9
+ listSegmentEvolutionTriggers,
10
+ } from "../triggers/index.ts";
11
+ export type {
12
+ EvolutionProcessInput,
13
+ EvolutionProcessProgress,
14
+ EvolutionProcessResult,
15
+ EvolutionReviewSnapshot,
16
+ EvolutionTriggerListInput,
17
+ EvolutionTriggerRecord,
18
+ SegmentEvolutionTriggerListInput,
19
+ SegmentEvolutionTriggerRecord,
20
+ } from "../schema.ts";
@@ -0,0 +1,533 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ import { readCodeAgentTraceRef } from "../../code-agent-traces/index.ts";
4
+ import { resolveEvoDevPaths } from "../../config/paths.ts";
5
+ import { type EvoDevExecutionEventV1, resolveProjectLogKey } from "../../runtime-logs/index.ts";
6
+ import { createStableId, normalizeTimestamp, pathExists } from "../../utils/index.ts";
7
+ import { resolveEvolutionPaths } from "../paths.ts";
8
+ import type {
9
+ EvolutionAnalyzeInput,
10
+ EvolutionAnalyzeResult,
11
+ EvolutionEpisode,
12
+ EvolutionEpisodeStatus,
13
+ EvolutionEvidenceEvent,
14
+ EvolutionEvidenceEventKind,
15
+ EvolutionEvidenceSourceRef,
16
+ EvolutionEvidenceWindow,
17
+ EvolutionTriggerPolicy,
18
+ EvolutionTriggerReason,
19
+ EvolutionTriggerStrength,
20
+ } from "../schema.ts";
21
+ import {
22
+ MAX_EVIDENCE_EVENTS,
23
+ createPrivacyFields,
24
+ displayPath,
25
+ isRecord,
26
+ optionalString,
27
+ sanitizeId,
28
+ sanitizeOptionalId,
29
+ sanitizeText,
30
+ uniqueSanitizedIds,
31
+ validateEvolutionEvidenceWindow,
32
+ } from "../shared.ts";
33
+ import {
34
+ createTriggerPolicy,
35
+ decideEvolutionTrigger,
36
+ normalizeEvolutionEventType,
37
+ strongestTrigger,
38
+ } from "../triggers/classification.ts";
39
+
40
+ export async function analyzeEvolutionRun(
41
+ input: EvolutionAnalyzeInput,
42
+ ): Promise<EvolutionAnalyzeResult> {
43
+ const projectKey = resolveEvolutionProjectKey(input);
44
+ const runId = sanitizeId(input.runId);
45
+ const paths = resolveEvolutionPaths({ homeDir: input.homeDir, projectKey, runId });
46
+ const warnings: string[] = [];
47
+ const sourceRefs: EvolutionEvidenceSourceRef[] = [];
48
+ const linkedTraceRefIds = new Set<string>();
49
+ const events: EvolutionEvidenceEvent[] = [];
50
+ const logsDir = resolveEvoDevPaths(input.homeDir).logsDir;
51
+ const projectRunAgentsDir = join(logsDir, "teams", projectKey, runId, "agents");
52
+ const legacyProjectRunAgentsDir = join(logsDir, projectKey, runId, "agents");
53
+ const agentDirs = (await pathExists(projectRunAgentsDir))
54
+ ? await readdir(projectRunAgentsDir, { withFileTypes: true })
55
+ : [];
56
+ const legacyAgentDirs = (await pathExists(legacyProjectRunAgentsDir))
57
+ ? await readdir(legacyProjectRunAgentsDir, { withFileTypes: true })
58
+ : [];
59
+
60
+ if (agentDirs.length === 0 && legacyAgentDirs.length === 0) {
61
+ warnings.push(
62
+ `No agent execution event directory found: ${displayPath(input.homeDir, projectRunAgentsDir)}`,
63
+ );
64
+ } else {
65
+ const rolesWithExecutionEvents = new Set<string>();
66
+ for (const roleDir of agentDirs.filter((entry) => entry.isDirectory())) {
67
+ const roleId = sanitizeId(roleDir.name);
68
+ const eventsPath = join(projectRunAgentsDir, roleDir.name, "events.jsonl");
69
+ if (!(await pathExists(eventsPath))) continue;
70
+ rolesWithExecutionEvents.add(roleId);
71
+
72
+ const sourceRef: EvolutionEvidenceSourceRef = {
73
+ id: `source-${sourceRefs.length + 1}`,
74
+ kind: "evodev-execution-event",
75
+ path: displayPath(input.homeDir, eventsPath),
76
+ roleId,
77
+ rawContentStored: false,
78
+ externalContentCopied: false,
79
+ };
80
+ sourceRefs.push(sourceRef);
81
+ const parsed = await readExecutionEvidenceEvents({
82
+ path: eventsPath,
83
+ roleId,
84
+ sourceRefId: sourceRef.id,
85
+ existingCount: events.length,
86
+ });
87
+ events.push(...parsed.events);
88
+ for (const traceRefId of parsed.traceRefIds) linkedTraceRefIds.add(traceRefId);
89
+ warnings.push(...parsed.warnings);
90
+ }
91
+
92
+ for (const roleDir of legacyAgentDirs.filter((entry) => entry.isDirectory())) {
93
+ const roleId = sanitizeId(roleDir.name);
94
+ if (rolesWithExecutionEvents.has(roleId)) continue;
95
+ const tracePath = join(legacyProjectRunAgentsDir, roleDir.name, "trace.log");
96
+ if (!(await pathExists(tracePath))) continue;
97
+
98
+ const sourceRef: EvolutionEvidenceSourceRef = {
99
+ id: `source-${sourceRefs.length + 1}`,
100
+ kind: "team-agent-trace",
101
+ path: displayPath(input.homeDir, tracePath),
102
+ roleId,
103
+ rawContentStored: false,
104
+ externalContentCopied: false,
105
+ };
106
+ sourceRefs.push(sourceRef);
107
+ const parsed = await readTraceEvidenceEvents({
108
+ path: tracePath,
109
+ roleId,
110
+ sourceRefId: sourceRef.id,
111
+ existingCount: events.length,
112
+ });
113
+ events.push(...parsed.events);
114
+ warnings.push(...parsed.warnings);
115
+ }
116
+ }
117
+
118
+ for (const traceRefId of [...linkedTraceRefIds].sort()) {
119
+ try {
120
+ const record = await readCodeAgentTraceRef({ homeDir: input.homeDir, id: traceRefId });
121
+ sourceRefs.push({
122
+ id: `source-${sourceRefs.length + 1}`,
123
+ kind: "code-agent-trace-ref",
124
+ path: displayPath(input.homeDir, record.path),
125
+ roleId: record.ref.roleId,
126
+ rawContentStored: false,
127
+ externalContentCopied: false,
128
+ });
129
+ } catch {
130
+ warnings.push(
131
+ `Code Agent trace ref metadata not found for linked ref: ${sanitizeText(traceRefId)}.`,
132
+ );
133
+ }
134
+ }
135
+
136
+ if (events.length > MAX_EVIDENCE_EVENTS) {
137
+ warnings.push(
138
+ `Evidence events truncated from ${events.length} to ${MAX_EVIDENCE_EVENTS} metadata events.`,
139
+ );
140
+ }
141
+ const limitedEvents = events.slice(0, MAX_EVIDENCE_EVENTS);
142
+ const episodeAnalysis = buildEvolutionEpisodeAnalysis(limitedEvents);
143
+
144
+ const evidenceWindow: EvolutionEvidenceWindow = {
145
+ schemaVersion: 1,
146
+ id: createStableId("evidence", [projectKey, runId]),
147
+ kind: "evidence-window",
148
+ projectKey,
149
+ runId,
150
+ taskId: null,
151
+ createdAt: normalizeTimestamp(input.now),
152
+ sourceRefs,
153
+ events: episodeAnalysis.events,
154
+ episodes: episodeAnalysis.episodes,
155
+ triggerPolicy: episodeAnalysis.triggerPolicy,
156
+ privacy: createPrivacyFields({
157
+ sourceRefs,
158
+ events: episodeAnalysis.events,
159
+ episodes: episodeAnalysis.episodes,
160
+ }),
161
+ };
162
+ validateEvolutionEvidenceWindow(evidenceWindow);
163
+
164
+ return {
165
+ evidenceWindow,
166
+ warnings,
167
+ };
168
+ }
169
+
170
+ async function readExecutionEvidenceEvents(input: {
171
+ path: string;
172
+ roleId: string;
173
+ sourceRefId: string;
174
+ existingCount: number;
175
+ }): Promise<{ events: EvolutionEvidenceEvent[]; warnings: string[]; traceRefIds: string[] }> {
176
+ const raw = await readFile(input.path, "utf8");
177
+ const lines = raw.split("\n");
178
+ const events: EvolutionEvidenceEvent[] = [];
179
+ const warnings: string[] = [];
180
+ const traceRefIds = new Set<string>();
181
+
182
+ for (let index = 0; index < lines.length; index += 1) {
183
+ const line = lines[index]?.trim();
184
+ if (line === undefined || line === "") continue;
185
+
186
+ try {
187
+ const value = JSON.parse(line) as unknown;
188
+ if (!isRecord(value) || value.kind !== "evodev-execution-event") continue;
189
+ const codeAgentTraceRefId = optionalString(value.codeAgentTraceRefId);
190
+ if (codeAgentTraceRefId !== null) traceRefIds.add(codeAgentTraceRefId);
191
+ const event = createEvidenceEventFromExecutionEvent({
192
+ executionEvent: value as unknown as EvoDevExecutionEventV1,
193
+ roleId: input.roleId,
194
+ sourceRefId: input.sourceRefId,
195
+ lineNumber: index + 1,
196
+ sequence: input.existingCount + events.length + 1,
197
+ });
198
+ if (event !== null) events.push(event);
199
+ } catch {
200
+ warnings.push(
201
+ `Skipped malformed execution event JSON at ${basename(input.path)}:${index + 1}.`,
202
+ );
203
+ }
204
+ }
205
+
206
+ return { events, warnings, traceRefIds: [...traceRefIds] };
207
+ }
208
+
209
+ function createEvidenceEventFromExecutionEvent(input: {
210
+ executionEvent: EvoDevExecutionEventV1;
211
+ roleId: string;
212
+ sourceRefId: string;
213
+ lineNumber: number;
214
+ sequence: number;
215
+ }): EvolutionEvidenceEvent | null {
216
+ const metadata = isRecord(input.executionEvent.metadata) ? input.executionEvent.metadata : {};
217
+ const phase = optionalString(metadata.phase);
218
+ const target = optionalString(input.executionEvent.target);
219
+ const eventType = optionalString(input.executionEvent.eventType);
220
+ const summarySource =
221
+ optionalString(input.executionEvent.summary) ?? [target, phase].filter(Boolean).join(" ");
222
+ if (summarySource.trim() === "") return null;
223
+ const normalizedType = normalizeEvolutionEventType(eventType);
224
+ const kind = classifyEvidenceEvent({
225
+ error: null,
226
+ summary: summarySource,
227
+ target,
228
+ eventType,
229
+ phase,
230
+ });
231
+ const trigger = decideEvolutionTrigger({
232
+ eventType: normalizedType,
233
+ summary: summarySource,
234
+ kind,
235
+ });
236
+
237
+ return {
238
+ id: `event-${String(input.sequence).padStart(4, "0")}`,
239
+ kind,
240
+ eventType: normalizedType,
241
+ hookEventId: sanitizeOptionalId(input.executionEvent.eventId),
242
+ occurredAt: optionalString(input.executionEvent.timestamp),
243
+ summary: sanitizeText(summarySource),
244
+ roleId: sanitizeOptionalId(input.executionEvent.roleId) ?? sanitizeId(input.roleId),
245
+ taskId: sanitizeOptionalId(input.executionEvent.taskId),
246
+ evidenceRef: `${input.sourceRefId}#L${input.lineNumber}`,
247
+ episodeIds: [],
248
+ triggerStrength: trigger.strength,
249
+ triggerReason: trigger.reason,
250
+ rawContentStored: false,
251
+ };
252
+ }
253
+
254
+ async function readTraceEvidenceEvents(input: {
255
+ path: string;
256
+ roleId: string;
257
+ sourceRefId: string;
258
+ existingCount: number;
259
+ }): Promise<{ events: EvolutionEvidenceEvent[]; warnings: string[] }> {
260
+ const raw = await readFile(input.path, "utf8");
261
+ const lines = raw.split("\n");
262
+ const events: EvolutionEvidenceEvent[] = [];
263
+ const warnings: string[] = [];
264
+
265
+ for (let index = 0; index < lines.length; index += 1) {
266
+ const line = lines[index]?.trim();
267
+ if (line === undefined || line === "") continue;
268
+
269
+ try {
270
+ const value = JSON.parse(line) as unknown;
271
+ if (!isRecord(value)) continue;
272
+ const event = createEvidenceEventFromTrace({
273
+ trace: value,
274
+ roleId: input.roleId,
275
+ sourceRefId: input.sourceRefId,
276
+ lineNumber: index + 1,
277
+ sequence: input.existingCount + events.length + 1,
278
+ });
279
+ if (event !== null) events.push(event);
280
+ } catch {
281
+ warnings.push(`Skipped malformed trace JSON at ${basename(input.path)}:${index + 1}.`);
282
+ }
283
+ }
284
+
285
+ return { events, warnings };
286
+ }
287
+
288
+ function createEvidenceEventFromTrace(input: {
289
+ trace: Record<string, unknown>;
290
+ roleId: string;
291
+ sourceRefId: string;
292
+ lineNumber: number;
293
+ sequence: number;
294
+ }): EvolutionEvidenceEvent | null {
295
+ const phase = optionalString(input.trace.phase);
296
+ const target = optionalString(input.trace.target);
297
+ const traceEvent = isRecord(input.trace.event) ? input.trace.event : null;
298
+ const result = isRecord(input.trace.result) ? input.trace.result : null;
299
+ const error = optionalString(input.trace.error);
300
+ const eventSummary = traceEvent === null ? null : optionalString(traceEvent.summary);
301
+ const eventType = traceEvent === null ? null : optionalString(traceEvent.type);
302
+ const eventId = traceEvent === null ? null : optionalString(traceEvent.eventId);
303
+ const resultSummary = result === null ? null : optionalString(result.summary);
304
+ const summarySource =
305
+ error ?? resultSummary ?? eventSummary ?? [target, phase].filter(Boolean).join(" ");
306
+ if (summarySource.trim() === "") return null;
307
+ const normalizedType = normalizeEvolutionEventType(eventType);
308
+ const trigger = decideEvolutionTrigger({
309
+ eventType: normalizedType,
310
+ summary: summarySource,
311
+ kind: classifyEvidenceEvent({
312
+ error,
313
+ summary: summarySource,
314
+ target,
315
+ eventType,
316
+ phase,
317
+ }),
318
+ });
319
+
320
+ return {
321
+ id: `event-${String(input.sequence).padStart(4, "0")}`,
322
+ kind: classifyEvidenceEvent({
323
+ error,
324
+ summary: summarySource,
325
+ target,
326
+ eventType,
327
+ phase,
328
+ }),
329
+ eventType: normalizedType,
330
+ hookEventId: eventId === null ? null : sanitizeId(eventId),
331
+ occurredAt: optionalString(input.trace.timestamp),
332
+ summary: sanitizeText(summarySource),
333
+ roleId: sanitizeId(input.roleId),
334
+ taskId: extractTraceTaskId(input.trace),
335
+ evidenceRef: `${input.sourceRefId}#L${input.lineNumber}`,
336
+ episodeIds: [],
337
+ triggerStrength: trigger.strength,
338
+ triggerReason: trigger.reason,
339
+ rawContentStored: false,
340
+ };
341
+ }
342
+
343
+ function classifyEvidenceEvent(input: {
344
+ error: string | null;
345
+ summary: string;
346
+ target: string | null;
347
+ eventType: string | null;
348
+ phase: string | null;
349
+ }): EvolutionEvidenceEventKind {
350
+ const haystack = [input.summary, input.target, input.eventType, input.phase]
351
+ .filter(Boolean)
352
+ .join(" ");
353
+ const normalizedType = normalizeEvolutionEventType(input.eventType);
354
+ if (
355
+ normalizedType === "StopFailure" ||
356
+ normalizedType === "PostToolUseFailure" ||
357
+ normalizedType === "PermissionDenied"
358
+ ) {
359
+ return "error";
360
+ }
361
+ if (
362
+ normalizedType === "PreToolUse" ||
363
+ normalizedType === "PostToolUse" ||
364
+ normalizedType === "PermissionRequest" ||
365
+ normalizedType === "PostToolBatch"
366
+ ) {
367
+ return "tool-call";
368
+ }
369
+ if (normalizedType === "SubagentStart" || normalizedType === "SubagentStop") return "subagent";
370
+ if (
371
+ normalizedType === "SessionStart" ||
372
+ normalizedType === "SessionEnd" ||
373
+ normalizedType === "UserPromptSubmit" ||
374
+ normalizedType === "Stop" ||
375
+ normalizedType === "TaskCreated" ||
376
+ normalizedType === "TaskCompleted"
377
+ ) {
378
+ return "run-state";
379
+ }
380
+ if (input.error !== null || /error|failed|failure|issue/i.test(haystack)) return "error";
381
+ if (/skill/i.test(haystack)) return "skill-call";
382
+ if (/subagent|agent|team|role/i.test(haystack)) return "subagent";
383
+ if (/tool/i.test(haystack)) return "tool-call";
384
+ if (/verify|verification|test|lint|typecheck|build/i.test(haystack)) return "verification";
385
+ if (/review/i.test(haystack)) return "review";
386
+ if (/completed|started|stopped|running/i.test(haystack)) return "run-state";
387
+ return "trace";
388
+ }
389
+
390
+ function buildEvolutionEpisodeAnalysis(events: EvolutionEvidenceEvent[]): {
391
+ events: EvolutionEvidenceEvent[];
392
+ episodes: EvolutionEpisode[];
393
+ triggerPolicy: EvolutionTriggerPolicy;
394
+ } {
395
+ const episodeLinks = new Map<string, string[]>();
396
+ const episodes: EvolutionEpisode[] = [];
397
+ const addEpisode = (input: Omit<EvolutionEpisode, "rawContentStored">) => {
398
+ if (input.eventIds.length === 0) return;
399
+ const episode: EvolutionEpisode = { ...input, rawContentStored: false };
400
+ episodes.push(episode);
401
+ for (const eventId of episode.eventIds) {
402
+ episodeLinks.set(eventId, [...(episodeLinks.get(eventId) ?? []), episode.id]);
403
+ }
404
+ };
405
+
406
+ if (events.length > 0) {
407
+ const strongest = strongestTrigger(events);
408
+ addEpisode({
409
+ id: "episode-session-0001",
410
+ kind: "session",
411
+ status: events.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
412
+ roleId: null,
413
+ taskId: null,
414
+ startedAt: events[0]?.occurredAt ?? null,
415
+ endedAt: events.at(-1)?.occurredAt ?? null,
416
+ eventIds: events.map((event) => event.id),
417
+ triggerStrength: strongest.strength,
418
+ triggerReason: strongest.reason,
419
+ summary: `Session episode with ${events.length} metadata event(s).`,
420
+ });
421
+ }
422
+
423
+ let turnEvents: EvolutionEvidenceEvent[] = [];
424
+ let turnIndex = 1;
425
+ const closeTurn = (status: EvolutionEpisodeStatus) => {
426
+ if (turnEvents.length === 0) return;
427
+ const strongest = strongestTrigger(turnEvents);
428
+ addEpisode({
429
+ id: `episode-turn-${String(turnIndex).padStart(4, "0")}`,
430
+ kind: "turn",
431
+ status,
432
+ roleId: firstRoleId(turnEvents),
433
+ taskId: firstTaskId(turnEvents),
434
+ startedAt: turnEvents[0]?.occurredAt ?? null,
435
+ endedAt: turnEvents.at(-1)?.occurredAt ?? null,
436
+ eventIds: turnEvents.map((event) => event.id),
437
+ triggerStrength: strongest.strength,
438
+ triggerReason: strongest.reason,
439
+ summary: `Turn episode closed with ${turnEvents.length} metadata event(s).`,
440
+ });
441
+ turnEvents = [];
442
+ turnIndex += 1;
443
+ };
444
+ for (const event of events) {
445
+ if (event.eventType === "UserPromptSubmit") {
446
+ closeTurn("open");
447
+ turnEvents = [event];
448
+ continue;
449
+ }
450
+ if (turnEvents.length > 0) turnEvents.push(event);
451
+ if (event.eventType === "Stop" || event.eventType === "StopFailure") {
452
+ closeTurn(event.eventType === "StopFailure" ? "failed" : "closed");
453
+ }
454
+ }
455
+ closeTurn("open");
456
+
457
+ const taskIds = uniqueSanitizedIds(
458
+ events.flatMap((event) => (event.taskId === null ? [] : [event.taskId])),
459
+ );
460
+ taskIds.forEach((taskId, index) => {
461
+ const taskEvents = events.filter((event) => event.taskId === taskId);
462
+ const strongest = strongestTrigger(taskEvents);
463
+ addEpisode({
464
+ id: `episode-task-${String(index + 1).padStart(4, "0")}`,
465
+ kind: "task",
466
+ status: taskEvents.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
467
+ roleId: firstRoleId(taskEvents),
468
+ taskId,
469
+ startedAt: taskEvents[0]?.occurredAt ?? null,
470
+ endedAt: taskEvents.at(-1)?.occurredAt ?? null,
471
+ eventIds: taskEvents.map((event) => event.id),
472
+ triggerStrength: strongest.strength,
473
+ triggerReason: strongest.reason,
474
+ summary: `Task episode ${taskId} contains ${taskEvents.length} metadata event(s).`,
475
+ });
476
+ });
477
+
478
+ const roleIds = uniqueSanitizedIds(
479
+ events.flatMap((event) => (event.roleId === null ? [] : [event.roleId])),
480
+ );
481
+ roleIds.forEach((roleId, index) => {
482
+ const roleEvents = events.filter((event) => event.roleId === roleId);
483
+ const strongest = strongestTrigger(roleEvents);
484
+ addEpisode({
485
+ id: `episode-role-${String(index + 1).padStart(4, "0")}`,
486
+ kind: "role",
487
+ status: roleEvents.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
488
+ roleId,
489
+ taskId: firstTaskId(roleEvents),
490
+ startedAt: roleEvents[0]?.occurredAt ?? null,
491
+ endedAt: roleEvents.at(-1)?.occurredAt ?? null,
492
+ eventIds: roleEvents.map((event) => event.id),
493
+ triggerStrength: strongest.strength,
494
+ triggerReason: strongest.reason,
495
+ summary: `Role episode ${roleId} contains ${roleEvents.length} metadata event(s).`,
496
+ });
497
+ });
498
+
499
+ return {
500
+ events: events.map((event) => ({ ...event, episodeIds: episodeLinks.get(event.id) ?? [] })),
501
+ episodes,
502
+ triggerPolicy: createTriggerPolicy(events),
503
+ };
504
+ }
505
+
506
+ function firstRoleId(events: EvolutionEvidenceEvent[]): string | null {
507
+ return events.find((event) => event.roleId !== null)?.roleId ?? null;
508
+ }
509
+
510
+ function firstTaskId(events: EvolutionEvidenceEvent[]): string | null {
511
+ return events.find((event) => event.taskId !== null)?.taskId ?? null;
512
+ }
513
+
514
+ function extractTraceTaskId(trace: Record<string, unknown>): string | null {
515
+ const event = isRecord(trace.event) ? trace.event : {};
516
+ const metadata = isRecord(event.metadata) ? event.metadata : {};
517
+ const input = isRecord(trace.input) ? trace.input : {};
518
+ const payload = isRecord(input.payload) ? input.payload : {};
519
+ return sanitizeOptionalId(
520
+ metadata.taskId ??
521
+ metadata.task_id ??
522
+ payload.taskId ??
523
+ payload.task_id ??
524
+ payload.taskID ??
525
+ null,
526
+ );
527
+ }
528
+
529
+ function resolveEvolutionProjectKey(input: EvolutionAnalyzeInput): string {
530
+ if (input.projectKey !== undefined) return sanitizeId(input.projectKey);
531
+ if (input.projectDir !== undefined) return resolveProjectLogKey(input.homeDir, input.projectDir);
532
+ throw new Error("Missing project scope: pass --project <projectKey> or --project-dir <path>.");
533
+ }
@@ -0,0 +1,3 @@
1
+ export * as runtimeLogs from "../../runtime-logs/index.ts";
2
+ export * as sessionMemory from "./session-memory/index.ts";
3
+ export * as traceRefs from "../../code-agent-traces/index.ts";