@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.10

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 (66) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +117 -114
  2. package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
  3. package/assets/team/agents/code-reviewer.md +48 -0
  4. package/assets/team/agents/docs-maintainer.md +51 -0
  5. package/assets/team/agents/implementation-engineer.md +51 -0
  6. package/assets/team/agents/product-scope-analyst.md +58 -0
  7. package/assets/team/agents/release-engineer.md +55 -0
  8. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  9. package/assets/team/agents/solution-architect.md +51 -0
  10. package/assets/team/agents/verification-engineer.md +51 -0
  11. package/assets/team/team.md +102 -0
  12. package/dist/config/index.js +925 -97
  13. package/dist/index.js +13107 -5618
  14. package/package.json +5 -1
  15. package/src/agents/index.ts +56 -264
  16. package/src/code-agent-traces/index.ts +520 -0
  17. package/src/config/index.ts +5 -0
  18. package/src/config/paths.ts +1 -1
  19. package/src/config/settings.ts +149 -0
  20. package/src/config/store.ts +2 -0
  21. package/src/daemon/index.ts +99 -50
  22. package/src/evolution/candidates/index.ts +564 -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 +281 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +7 -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/segment.ts +202 -0
  32. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  33. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  34. package/src/evolution/evidence/session-memory/storage.ts +379 -0
  35. package/src/evolution/evidence/session-memory/types.ts +221 -0
  36. package/src/evolution/evidence/session-memory/updater.ts +191 -0
  37. package/src/evolution/formatters.ts +169 -0
  38. package/src/evolution/index.ts +16 -2356
  39. package/src/evolution/knowledge/index.ts +5427 -0
  40. package/src/evolution/paths.ts +44 -0
  41. package/src/evolution/processor/distillation.ts +518 -0
  42. package/src/evolution/processor/index.ts +3 -0
  43. package/src/evolution/processor/process.ts +528 -0
  44. package/src/{learning → evolution/review}/index.ts +10 -14
  45. package/src/evolution/schema.ts +568 -0
  46. package/src/evolution/shared.ts +758 -0
  47. package/src/evolution/triggers/classification.ts +102 -0
  48. package/src/evolution/triggers/index.ts +295 -0
  49. package/src/hooks/index.ts +438 -179
  50. package/src/index.ts +12 -3
  51. package/src/projects/index.ts +453 -0
  52. package/src/runtime-logs/index.ts +490 -24
  53. package/src/team/index.ts +1429 -185
  54. package/src/team/mcp.ts +9 -5
  55. package/src/team/prompts.ts +141 -0
  56. package/src/utils/errors.ts +13 -0
  57. package/src/utils/fs.ts +40 -0
  58. package/src/utils/hash.ts +9 -0
  59. package/src/utils/ids.ts +12 -0
  60. package/src/utils/index.ts +7 -0
  61. package/src/utils/parsing.ts +11 -0
  62. package/src/utils/text.ts +18 -0
  63. package/src/utils/time.ts +5 -0
  64. package/src/workflow/index.ts +3 -21
  65. package/src/project/index.ts +0 -507
  66. package/src/task/index.ts +0 -840
@@ -1,9 +1,12 @@
1
- import { createHash } from "node:crypto";
2
1
  import { appendFile, mkdir } from "node:fs/promises";
3
2
  import { dirname, isAbsolute, join, relative } from "node:path";
4
3
  import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
5
5
 
6
6
  export type EvoDevLogPhase = "started" | "completed" | "failed";
7
+ export type EvoDevDebugLogLevel = "verbose" | "debug" | "info" | "warn" | "error";
8
+ export type EvoDevExecutionEventTarget = "claude" | "codex" | "cli" | "team" | "unknown";
9
+ export type EvoDevExecutionEventMetadataValue = string | number | boolean | string[];
7
10
 
8
11
  export interface CliLogEntryV1 {
9
12
  version: 1;
@@ -56,6 +59,50 @@ export interface TraceLogEntryV1 {
56
59
  team: TraceLogTeamContext | null;
57
60
  }
58
61
 
62
+ export interface EvoDevExecutionEventV1 {
63
+ version: 1;
64
+ kind: "evodev-execution-event";
65
+ eventId: string;
66
+ timestamp: string;
67
+ date: string;
68
+ target: EvoDevExecutionEventTarget;
69
+ eventType: string;
70
+ projectKey: string | null;
71
+ runId: string | null;
72
+ roleId: string | null;
73
+ taskId: string | null;
74
+ sessionKey: string;
75
+ summary: string;
76
+ metadata: Record<string, EvoDevExecutionEventMetadataValue>;
77
+ decision: {
78
+ action: "allow" | "warn";
79
+ reason: string;
80
+ } | null;
81
+ codeAgentTraceRefId: string | null;
82
+ rawPayloadStored: false;
83
+ rawPromptStored: false;
84
+ rawOutputStored: false;
85
+ sourceContentStored: false;
86
+ secretsStored: false;
87
+ }
88
+
89
+ export interface DebugLogEntryV1 {
90
+ version: 1;
91
+ kind: "debug";
92
+ timestamp: string;
93
+ date: string;
94
+ level: EvoDevDebugLogLevel;
95
+ category: string;
96
+ message: string;
97
+ data: unknown;
98
+ context: {
99
+ command: string | null;
100
+ argv: string[];
101
+ cwd: string | null;
102
+ pid: number;
103
+ };
104
+ }
105
+
59
106
  export interface TraceLogTeamContext {
60
107
  runId: string;
61
108
  roleId: string;
@@ -87,10 +134,48 @@ export interface TraceLogInput {
87
134
  team?: TraceLogTeamContext | null;
88
135
  }
89
136
 
137
+ export interface EvoDevExecutionEventInput {
138
+ target: string;
139
+ eventType: string;
140
+ sessionKey: string;
141
+ summary: string;
142
+ metadata?: Record<string, unknown>;
143
+ timestamp?: Date | string;
144
+ eventId?: string;
145
+ projectKey?: string | null;
146
+ runId?: string | null;
147
+ roleId?: string | null;
148
+ taskId?: string | null;
149
+ decision?: EvoDevExecutionEventV1["decision"];
150
+ codeAgentTraceRefId?: string | null;
151
+ }
152
+
153
+ export interface DebugLogInput {
154
+ level?: EvoDevDebugLogLevel;
155
+ category: string;
156
+ message: string;
157
+ data?: unknown;
158
+ timestamp?: Date | string;
159
+ argv?: string[];
160
+ cwd?: string | null;
161
+ pid?: number;
162
+ }
163
+
90
164
  export interface EvoDevLogPaths {
91
165
  logsDir: string;
92
166
  cliDir: string;
93
167
  cliLogPath: string;
168
+ debugDir: string;
169
+ debugLogPath: string;
170
+ evodevEventRootDir: string;
171
+ evodevEventDateDir: string;
172
+ evodevEventSessionDir: string | null;
173
+ evodevEventsPath: string | null;
174
+ teamsRootDir: string;
175
+ teamRunDir: string | null;
176
+ teamRunAgentsDir: string | null;
177
+ teamAgentLogDir: string | null;
178
+ teamAgentEventsPath: string | null;
94
179
  traceRootDir: string;
95
180
  traceDateDir: string;
96
181
  traceSessionDir: string | null;
@@ -102,6 +187,35 @@ export interface EvoDevLogPaths {
102
187
  }
103
188
 
104
189
  const MAX_LOG_STRING_LENGTH = 4096;
190
+ const MAX_EVENT_STRING_LENGTH = 512;
191
+ const MAX_DEBUG_ARRAY_LENGTH = 50;
192
+ const MAX_DEBUG_OBJECT_KEYS = 100;
193
+ const MAX_DEBUG_DEPTH = 5;
194
+ const SAFE_EXECUTION_METADATA_KEYS = new Set([
195
+ "phase",
196
+ "runtimeSurface",
197
+ "stdinBytes",
198
+ "durationMs",
199
+ "enabled",
200
+ "stateWriteCount",
201
+ "warningCount",
202
+ "commandClass",
203
+ "exitCode",
204
+ "status",
205
+ "redactionCount",
206
+ "redactionLabels",
207
+ "normalizedEventType",
208
+ ]);
209
+ const SENSITIVE_EVENT_TEXT_PATTERN =
210
+ /https?:\/\/\S+|(^|[^a-z0-9])(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|stdout|stderr|transcript|formattedresponse|additionalcontext)([^a-z0-9]|$)|raw[\s_-]?(payload|prompt|output|source)|source[\s_-]?dump/i;
211
+ const SAFE_REDACTION_LABELS = new Set([
212
+ "raw-command",
213
+ "raw-command-output",
214
+ "raw-prompt",
215
+ "sensitive-command",
216
+ "sensitive-text",
217
+ ]);
218
+
105
219
  export function createCliLogEntry(input: CliLogInput): CliLogEntryV1 {
106
220
  const timestamp = normalizeTimestamp(input.timestamp);
107
221
  return {
@@ -120,6 +234,27 @@ export function createCliLogEntry(input: CliLogInput): CliLogEntryV1 {
120
234
  };
121
235
  }
122
236
 
237
+ export function createDebugLogEntry(input: DebugLogInput): DebugLogEntryV1 {
238
+ const timestamp = normalizeTimestamp(input.timestamp);
239
+ const argv = input.argv ?? [];
240
+ return {
241
+ version: 1,
242
+ kind: "debug",
243
+ timestamp,
244
+ date: formatLocalDateKey(timestamp),
245
+ level: input.level ?? "debug",
246
+ category: truncateString(input.category),
247
+ message: truncateString(input.message),
248
+ data: input.data === undefined ? null : sanitizeDebugValue(input.data),
249
+ context: {
250
+ command: argv[0] ?? null,
251
+ argv: argv.map((arg) => truncateString(arg)),
252
+ cwd: input.cwd === undefined ? process.cwd() : input.cwd,
253
+ pid: input.pid ?? process.pid,
254
+ },
255
+ };
256
+ }
257
+
123
258
  export function createTraceLogEntry(input: TraceLogInput): TraceLogEntryV1 {
124
259
  const timestamp = normalizeTimestamp(input.timestamp);
125
260
  const payload = input.payload ?? null;
@@ -159,6 +294,82 @@ export function createTraceLogEntry(input: TraceLogInput): TraceLogEntryV1 {
159
294
  };
160
295
  }
161
296
 
297
+ export function createEvoDevExecutionEvent(
298
+ input: EvoDevExecutionEventInput,
299
+ ): EvoDevExecutionEventV1 {
300
+ const timestamp = normalizeTimestamp(input.timestamp);
301
+ const sessionKey = sanitizePersistentIdentifier(input.sessionKey, "session");
302
+ const target = normalizeExecutionEventTarget(input.target);
303
+ const eventType = sanitizeEventText(input.eventType, "unknown");
304
+ const projectKey =
305
+ input.projectKey === undefined || input.projectKey === null
306
+ ? null
307
+ : sanitizePersistentIdentifier(input.projectKey, "project");
308
+ const runId =
309
+ input.runId === undefined || input.runId === null
310
+ ? null
311
+ : sanitizePersistentIdentifier(input.runId, "run");
312
+ const roleId =
313
+ input.roleId === undefined || input.roleId === null
314
+ ? null
315
+ : sanitizePersistentIdentifier(input.roleId, "role");
316
+ const taskId =
317
+ input.taskId === undefined || input.taskId === null
318
+ ? null
319
+ : sanitizePersistentIdentifier(input.taskId, "task");
320
+ const metadata = sanitizeExecutionMetadata(input.metadata ?? {});
321
+ const summary = sanitizeEventText(input.summary, "EvoDev execution event recorded.");
322
+ const decision: EvoDevExecutionEventV1["decision"] =
323
+ input.decision === undefined || input.decision === null
324
+ ? null
325
+ : {
326
+ action: input.decision.action === "warn" ? "warn" : "allow",
327
+ reason: sanitizeEventText(input.decision.reason, "Decision reason redacted."),
328
+ };
329
+ const codeAgentTraceRefId =
330
+ input.codeAgentTraceRefId === undefined || input.codeAgentTraceRefId === null
331
+ ? null
332
+ : sanitizePersistentIdentifier(input.codeAgentTraceRefId, "trace-ref");
333
+
334
+ return {
335
+ version: 1,
336
+ kind: "evodev-execution-event",
337
+ eventId:
338
+ input.eventId === undefined
339
+ ? createExecutionEventId({
340
+ timestamp,
341
+ target,
342
+ eventType,
343
+ sessionKey,
344
+ projectKey,
345
+ runId,
346
+ roleId,
347
+ taskId,
348
+ summary,
349
+ metadata,
350
+ })
351
+ : sanitizePersistentIdentifier(input.eventId, "event"),
352
+ timestamp,
353
+ date: formatLocalDateKey(timestamp),
354
+ target,
355
+ eventType,
356
+ projectKey,
357
+ runId,
358
+ roleId,
359
+ taskId,
360
+ sessionKey,
361
+ summary,
362
+ metadata,
363
+ decision,
364
+ codeAgentTraceRefId,
365
+ rawPayloadStored: false,
366
+ rawPromptStored: false,
367
+ rawOutputStored: false,
368
+ sourceContentStored: false,
369
+ secretsStored: false,
370
+ };
371
+ }
372
+
162
373
  export function resolveEvoDevLogPaths(input: {
163
374
  homeDir: string;
164
375
  timestamp?: Date | string;
@@ -168,30 +379,63 @@ export function resolveEvoDevLogPaths(input: {
168
379
  const paths = resolveEvoDevPaths(input.homeDir);
169
380
  const date = formatLocalDateKey(normalizeTimestamp(input.timestamp));
170
381
  const cliDir = join(paths.logsDir, "cli");
382
+ const debugDir = join(paths.logsDir, "debug");
383
+ const evodevEventRootDir = join(paths.logsDir, "evodev");
384
+ const evodevEventDateDir = join(evodevEventRootDir, date);
385
+ const evodevEventSessionDir =
386
+ input.sessionKey === undefined || input.sessionKey === null
387
+ ? null
388
+ : join(evodevEventDateDir, sanitizePersistentIdentifier(input.sessionKey, "session"));
389
+ const teamsRootDir = join(paths.logsDir, "teams");
390
+ const teamRunDir =
391
+ input.team === undefined || input.team === null
392
+ ? null
393
+ : join(
394
+ teamsRootDir,
395
+ sanitizePersistentIdentifier(input.team.projectKey, "project"),
396
+ sanitizePersistentIdentifier(input.team.runId, "run"),
397
+ );
398
+ const teamRunAgentsDir = teamRunDir === null ? null : join(teamRunDir, "agents");
399
+ const teamAgentLogDir =
400
+ teamRunAgentsDir === null || input.team === undefined || input.team === null
401
+ ? null
402
+ : join(teamRunAgentsDir, sanitizePersistentIdentifier(input.team.roleId, "role"));
171
403
  const traceRootDir = join(paths.logsDir, "trace");
172
404
  const traceDateDir = join(traceRootDir, date);
173
405
  const traceSessionDir =
174
406
  input.sessionKey === undefined || input.sessionKey === null
175
407
  ? null
176
- : join(traceDateDir, sanitizePathSegment(input.sessionKey));
408
+ : join(traceDateDir, sanitizePersistentIdentifier(input.sessionKey, "session"));
177
409
  const projectRunDir =
178
410
  input.team === undefined || input.team === null
179
411
  ? null
180
412
  : join(
181
413
  paths.logsDir,
182
- sanitizePathSegment(input.team.projectKey),
183
- sanitizePathSegment(input.team.runId),
414
+ sanitizePersistentIdentifier(input.team.projectKey, "project"),
415
+ sanitizePersistentIdentifier(input.team.runId, "run"),
184
416
  );
185
417
  const projectRunAgentsDir = projectRunDir === null ? null : join(projectRunDir, "agents");
186
418
  const agentLogDir =
187
419
  projectRunAgentsDir === null || input.team === undefined || input.team === null
188
420
  ? null
189
- : join(projectRunAgentsDir, sanitizePathSegment(input.team.roleId));
421
+ : join(projectRunAgentsDir, sanitizePersistentIdentifier(input.team.roleId, "role"));
190
422
 
191
423
  return {
192
424
  logsDir: paths.logsDir,
193
425
  cliDir,
194
426
  cliLogPath: join(cliDir, `${date}.log`),
427
+ debugDir,
428
+ debugLogPath: join(debugDir, `${date}.log`),
429
+ evodevEventRootDir,
430
+ evodevEventDateDir,
431
+ evodevEventSessionDir,
432
+ evodevEventsPath:
433
+ evodevEventSessionDir === null ? null : join(evodevEventSessionDir, "events.jsonl"),
434
+ teamsRootDir,
435
+ teamRunDir,
436
+ teamRunAgentsDir,
437
+ teamAgentLogDir,
438
+ teamAgentEventsPath: teamAgentLogDir === null ? null : join(teamAgentLogDir, "events.jsonl"),
195
439
  traceRootDir,
196
440
  traceDateDir,
197
441
  traceSessionDir,
@@ -209,29 +453,70 @@ export async function appendCliLogEntry(homeDir: string, entry: CliLogEntryV1):
209
453
  return path;
210
454
  }
211
455
 
456
+ export async function appendDebugLogEntry(
457
+ homeDir: string,
458
+ entry: DebugLogEntryV1,
459
+ ): Promise<string> {
460
+ const path = resolveEvoDevLogPaths({ homeDir, timestamp: entry.timestamp }).debugLogPath;
461
+ await appendJsonLine(path, entry);
462
+ return path;
463
+ }
464
+
212
465
  export async function appendTraceLogEntry(
213
466
  homeDir: string,
214
467
  entry: TraceLogEntryV1,
215
468
  ): Promise<string> {
469
+ const sanitizedEntry = sanitizeTraceLogEntryForAppend(entry);
216
470
  const path = resolveEvoDevLogPaths({
217
471
  homeDir,
218
- timestamp: entry.timestamp,
219
- sessionKey: entry.sessionKey,
472
+ timestamp: sanitizedEntry.timestamp,
473
+ sessionKey: sanitizedEntry.sessionKey,
220
474
  }).traceLogPath;
221
475
  if (path === null) throw new Error("Cannot resolve trace log path without a session key.");
222
- await appendJsonLine(path, entry);
476
+ await appendJsonLine(path, sanitizedEntry);
223
477
  const agentTraceLogPath = resolveEvoDevLogPaths({
224
478
  homeDir,
225
- timestamp: entry.timestamp,
226
- sessionKey: entry.sessionKey,
227
- team: entry.team,
479
+ timestamp: sanitizedEntry.timestamp,
480
+ sessionKey: sanitizedEntry.sessionKey,
481
+ team: sanitizedEntry.team,
228
482
  }).agentTraceLogPath;
229
483
  if (agentTraceLogPath !== null) {
230
- await appendJsonLine(agentTraceLogPath, entry);
484
+ await appendJsonLine(agentTraceLogPath, sanitizedEntry);
231
485
  }
232
486
  return path;
233
487
  }
234
488
 
489
+ export async function appendEvoDevExecutionEvent(
490
+ homeDir: string,
491
+ event: EvoDevExecutionEventV1,
492
+ ): Promise<string> {
493
+ const sanitizedEvent = sanitizeEvoDevExecutionEventForAppend(event);
494
+ const team =
495
+ sanitizedEvent.projectKey === null ||
496
+ sanitizedEvent.runId === null ||
497
+ sanitizedEvent.roleId === null
498
+ ? null
499
+ : {
500
+ projectKey: sanitizedEvent.projectKey,
501
+ runId: sanitizedEvent.runId,
502
+ roleId: sanitizedEvent.roleId,
503
+ };
504
+ const paths = resolveEvoDevLogPaths({
505
+ homeDir,
506
+ timestamp: sanitizedEvent.timestamp,
507
+ sessionKey: sanitizedEvent.sessionKey,
508
+ team,
509
+ });
510
+ if (paths.evodevEventsPath === null) {
511
+ throw new Error("Cannot resolve EvoDev execution event path without a session key.");
512
+ }
513
+ await appendJsonLine(paths.evodevEventsPath, sanitizedEvent);
514
+ if (paths.teamAgentEventsPath !== null) {
515
+ await appendJsonLine(paths.teamAgentEventsPath, sanitizedEvent);
516
+ }
517
+ return paths.evodevEventsPath;
518
+ }
519
+
235
520
  export function resolveTraceTeamContext(input: {
236
521
  homeDir: string;
237
522
  environment?: Record<string, string | undefined>;
@@ -246,8 +531,8 @@ export function resolveTraceTeamContext(input: {
246
531
  optionalString(input.payload?.cwd) ??
247
532
  input.homeDir;
248
533
  return {
249
- runId: sanitizePathSegment(runId),
250
- roleId: sanitizePathSegment(roleId),
534
+ runId: sanitizePersistentIdentifier(runId, "run"),
535
+ roleId: sanitizePersistentIdentifier(roleId, "role"),
251
536
  projectKey: resolveProjectLogKey(input.homeDir, repoRoot),
252
537
  };
253
538
  }
@@ -260,13 +545,13 @@ export function resolveProjectLogKey(homeDir: string, repoRoot: string): string
260
545
  relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath)
261
546
  ? relativePath
262
547
  : trimmedRepo.replace(/^[/\\]+/, "");
263
- return (
548
+ const projectKey =
264
549
  source
265
550
  .split(/[/\\]+/)
266
551
  .filter(Boolean)
267
552
  .map((part) => sanitizePathSegment(part))
268
- .join("-") || "project-local"
269
- );
553
+ .join("-") || "project-local";
554
+ return sanitizePersistentIdentifier(projectKey, "project");
270
555
  }
271
556
 
272
557
  export function resolveTraceSessionKey(payload: unknown): string {
@@ -274,7 +559,84 @@ export function resolveTraceSessionKey(payload: unknown): string {
274
559
  const sessionId = optionalString(record.session_id ?? record.sessionId);
275
560
  const cwd = optionalString(record.cwd);
276
561
  const source = sessionId ?? cwd ?? "local";
277
- return `session-${createHash("sha256").update(source).digest("hex").slice(0, 16)}`;
562
+ return `session-${sha256Short(source)}`;
563
+ }
564
+
565
+ function sanitizeTraceLogEntryForAppend(entry: TraceLogEntryV1): TraceLogEntryV1 {
566
+ return {
567
+ version: 1,
568
+ kind: "trace",
569
+ timestamp: normalizeTimestamp(entry.timestamp),
570
+ date: formatLocalDateKey(normalizeTimestamp(entry.timestamp)),
571
+ phase: entry.phase,
572
+ sessionKey: sanitizePersistentIdentifier(entry.sessionKey, "session"),
573
+ target: normalizeExecutionEventTarget(entry.target),
574
+ runtime: {
575
+ surface: entry.runtime.surface,
576
+ argv: [],
577
+ runtimeFile: null,
578
+ },
579
+ input:
580
+ entry.input === null
581
+ ? null
582
+ : {
583
+ stdinBytes: entry.input.stdinBytes,
584
+ payload: null,
585
+ },
586
+ event:
587
+ entry.event === null
588
+ ? null
589
+ : {
590
+ eventId: sanitizePersistentIdentifier(entry.event.eventId, "event"),
591
+ type: sanitizeEventText(entry.event.type, "unknown"),
592
+ summary: "Legacy trace event payload omitted; metadata-only compatibility entry.",
593
+ metadata: {},
594
+ decision: null,
595
+ },
596
+ result:
597
+ entry.result === null
598
+ ? null
599
+ : {
600
+ enabled: entry.result.enabled,
601
+ summary: "Legacy trace result payload omitted; metadata-only compatibility entry.",
602
+ stateWrites: [],
603
+ warnings: [],
604
+ formattedResponse: null,
605
+ durationMs: entry.result.durationMs,
606
+ },
607
+ error:
608
+ entry.error === null
609
+ ? null
610
+ : "Legacy trace error omitted; metadata-only compatibility entry.",
611
+ team:
612
+ entry.team === null
613
+ ? null
614
+ : {
615
+ runId: sanitizePersistentIdentifier(entry.team.runId, "run"),
616
+ roleId: sanitizePersistentIdentifier(entry.team.roleId, "role"),
617
+ projectKey: sanitizePersistentIdentifier(entry.team.projectKey, "project"),
618
+ },
619
+ };
620
+ }
621
+
622
+ function sanitizeEvoDevExecutionEventForAppend(
623
+ event: EvoDevExecutionEventV1,
624
+ ): EvoDevExecutionEventV1 {
625
+ return createEvoDevExecutionEvent({
626
+ eventId: event.eventId,
627
+ timestamp: event.timestamp,
628
+ target: event.target,
629
+ eventType: event.eventType,
630
+ projectKey: event.projectKey,
631
+ runId: event.runId,
632
+ roleId: event.roleId,
633
+ taskId: event.taskId,
634
+ sessionKey: event.sessionKey,
635
+ summary: event.summary,
636
+ metadata: event.metadata,
637
+ decision: event.decision,
638
+ codeAgentTraceRefId: event.codeAgentTraceRefId,
639
+ });
278
640
  }
279
641
 
280
642
  async function appendJsonLine(path: string, value: unknown): Promise<void> {
@@ -282,12 +644,6 @@ async function appendJsonLine(path: string, value: unknown): Promise<void> {
282
644
  await appendFile(path, `${JSON.stringify(value)}\n`, "utf8");
283
645
  }
284
646
 
285
- function normalizeTimestamp(value?: Date | string): string {
286
- if (value instanceof Date) return value.toISOString();
287
- if (typeof value === "string" && value.trim() !== "") return new Date(value).toISOString();
288
- return new Date().toISOString();
289
- }
290
-
291
647
  function formatLocalDateKey(timestamp: string): string {
292
648
  const date = new Date(timestamp);
293
649
  const year = date.getFullYear();
@@ -301,10 +657,120 @@ function truncateString(value: string): string {
301
657
  return `${value.slice(0, MAX_LOG_STRING_LENGTH)}...[truncated:${value.length - MAX_LOG_STRING_LENGTH}]`;
302
658
  }
303
659
 
660
+ function truncateEventString(value: string): string {
661
+ if (value.length <= MAX_EVENT_STRING_LENGTH) return value;
662
+ return `${value.slice(0, MAX_EVENT_STRING_LENGTH)}...[truncated:${value.length - MAX_EVENT_STRING_LENGTH}]`;
663
+ }
664
+
665
+ function sanitizeEventText(value: string, fallback: string): string {
666
+ const text = truncateEventString(value.trim());
667
+ if (text === "") return fallback;
668
+ if (SENSITIVE_EVENT_TEXT_PATTERN.test(text)) return fallback;
669
+ return text;
670
+ }
671
+
672
+ function sanitizeExecutionMetadata(
673
+ metadata: Record<string, unknown>,
674
+ ): Record<string, EvoDevExecutionEventMetadataValue> {
675
+ const safe: Record<string, EvoDevExecutionEventMetadataValue> = {};
676
+ for (const [key, value] of Object.entries(metadata)) {
677
+ if (!SAFE_EXECUTION_METADATA_KEYS.has(key)) continue;
678
+ const sanitized = sanitizeExecutionMetadataValue(key, value);
679
+ if (sanitized !== null) safe[key] = sanitized;
680
+ }
681
+ return safe;
682
+ }
683
+
684
+ function sanitizeExecutionMetadataValue(
685
+ key: string,
686
+ value: unknown,
687
+ ): EvoDevExecutionEventMetadataValue | null {
688
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
689
+ if (typeof value === "boolean") return value;
690
+ if (typeof value === "string") return sanitizeEventText(value, "[redacted]");
691
+ if (Array.isArray(value)) {
692
+ const items = value
693
+ .filter((item): item is string => typeof item === "string")
694
+ .slice(0, 50)
695
+ .map((item) =>
696
+ key === "redactionLabels" && SAFE_REDACTION_LABELS.has(item)
697
+ ? item
698
+ : sanitizeEventText(item, "[redacted]"),
699
+ );
700
+ return items;
701
+ }
702
+ return null;
703
+ }
704
+
705
+ function normalizeExecutionEventTarget(value: string): EvoDevExecutionEventTarget {
706
+ if (value === "claude" || value === "codex" || value === "cli" || value === "team") {
707
+ return value;
708
+ }
709
+ return "unknown";
710
+ }
711
+
712
+ function createExecutionEventId(input: {
713
+ timestamp: string;
714
+ target: EvoDevExecutionEventTarget;
715
+ eventType: string;
716
+ sessionKey: string;
717
+ projectKey: string | null;
718
+ runId: string | null;
719
+ roleId: string | null;
720
+ taskId: string | null;
721
+ summary: string;
722
+ metadata: Record<string, EvoDevExecutionEventMetadataValue>;
723
+ }): string {
724
+ return `exec-${sha256Short(JSON.stringify(input))}`;
725
+ }
726
+
727
+ function sanitizeDebugValue(value: unknown, depth = 0): unknown {
728
+ if (typeof value === "string") return truncateString(value);
729
+ if (
730
+ value === null ||
731
+ typeof value === "number" ||
732
+ typeof value === "boolean" ||
733
+ typeof value === "undefined"
734
+ ) {
735
+ return value;
736
+ }
737
+ if (typeof value === "bigint") return value.toString();
738
+ if (value instanceof Error) return describeError(value);
739
+ if (depth >= MAX_DEBUG_DEPTH) return "[max-depth]";
740
+ if (Array.isArray(value)) {
741
+ const items = value
742
+ .slice(0, MAX_DEBUG_ARRAY_LENGTH)
743
+ .map((item) => sanitizeDebugValue(item, depth + 1));
744
+ if (value.length > MAX_DEBUG_ARRAY_LENGTH) {
745
+ items.push(`[truncated:${value.length - MAX_DEBUG_ARRAY_LENGTH}]`);
746
+ }
747
+ return items;
748
+ }
749
+ if (isRecord(value)) {
750
+ const output: Record<string, unknown> = {};
751
+ const entries = Object.entries(value).slice(0, MAX_DEBUG_OBJECT_KEYS);
752
+ for (const [key, nestedValue] of entries) {
753
+ output[truncateString(key)] = sanitizeDebugValue(nestedValue, depth + 1);
754
+ }
755
+ const extraKeys = Object.keys(value).length - entries.length;
756
+ if (extraKeys > 0) output["[truncatedKeys]"] = extraKeys;
757
+ return output;
758
+ }
759
+ return truncateString(String(value));
760
+ }
761
+
304
762
  function sanitizePathSegment(value: string): string {
305
763
  return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "session-local";
306
764
  }
307
765
 
766
+ function sanitizePersistentIdentifier(value: string, prefix: string): string {
767
+ const pathSafe = value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || `${prefix}-local`;
768
+ if (!SENSITIVE_EVENT_TEXT_PATTERN.test(value) && !SENSITIVE_EVENT_TEXT_PATTERN.test(pathSafe)) {
769
+ return pathSafe;
770
+ }
771
+ return `${prefix}-${sha256Short(value)}`;
772
+ }
773
+
308
774
  function stripTrailingSlash(path: string): string {
309
775
  if (path === "/" || /^[A-Za-z]:[\\/]?$/.test(path)) return path;
310
776
  return path.replace(/[/\\]+$/, "");