@evo-dev/core 0.0.1-alpha → 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 (85) hide show
  1. package/assets/agents/review/code-reviewer/examples.md +1 -1
  2. package/assets/agents/review/code-reviewer/prompt.md +1 -1
  3. package/assets/agents/review/code-reviewer/verification.md +1 -1
  4. package/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
  5. package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
  6. package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
  7. package/assets/team/agents/code-reviewer.md +48 -0
  8. package/assets/team/agents/docs-maintainer.md +51 -0
  9. package/assets/team/agents/implementation-engineer.md +51 -0
  10. package/assets/team/agents/product-scope-analyst.md +58 -0
  11. package/assets/team/agents/release-engineer.md +55 -0
  12. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  13. package/assets/team/agents/solution-architect.md +51 -0
  14. package/assets/team/agents/verification-engineer.md +51 -0
  15. package/assets/team/team.md +102 -0
  16. package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
  17. package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
  18. package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
  19. package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
  20. package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
  21. package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
  22. package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
  23. package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
  24. package/dist/config/index.js +1115 -81
  25. package/dist/index.js +13796 -2196
  26. package/dist/plugins/index.js +32 -32
  27. package/package.json +5 -1
  28. package/src/agents/index.ts +63 -292
  29. package/src/code-agent-traces/index.ts +520 -0
  30. package/src/config/index.ts +7 -0
  31. package/src/config/paths.ts +30 -0
  32. package/src/config/settings.ts +201 -0
  33. package/src/config/store.ts +152 -0
  34. package/src/daemon/index.ts +462 -40
  35. package/src/evolution/candidates/index.ts +564 -0
  36. package/src/evolution/control/index.ts +20 -0
  37. package/src/evolution/evidence/analysis.ts +533 -0
  38. package/src/evolution/evidence/index.ts +3 -0
  39. package/src/evolution/evidence/session-memory/analysis.ts +281 -0
  40. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  41. package/src/evolution/evidence/session-memory/index.ts +7 -0
  42. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  43. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  44. package/src/evolution/evidence/session-memory/segment.ts +202 -0
  45. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  46. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  47. package/src/evolution/evidence/session-memory/storage.ts +379 -0
  48. package/src/evolution/evidence/session-memory/types.ts +221 -0
  49. package/src/evolution/evidence/session-memory/updater.ts +191 -0
  50. package/src/evolution/formatters.ts +169 -0
  51. package/src/evolution/index.ts +16 -0
  52. package/src/evolution/knowledge/index.ts +5427 -0
  53. package/src/evolution/paths.ts +44 -0
  54. package/src/evolution/processor/distillation.ts +518 -0
  55. package/src/evolution/processor/index.ts +3 -0
  56. package/src/evolution/processor/process.ts +528 -0
  57. package/src/{learning → evolution/review}/index.ts +10 -14
  58. package/src/evolution/schema.ts +568 -0
  59. package/src/evolution/shared.ts +758 -0
  60. package/src/evolution/triggers/classification.ts +102 -0
  61. package/src/evolution/triggers/index.ts +295 -0
  62. package/src/hooks/index.ts +652 -376
  63. package/src/index.ts +16 -3
  64. package/src/pack/index.ts +13 -13
  65. package/src/plugins/capabilities.ts +40 -42
  66. package/src/plugins/index.ts +0 -1
  67. package/src/plugins/types.ts +4 -0
  68. package/src/projects/index.ts +453 -0
  69. package/src/protected-zones/index.ts +29 -11
  70. package/src/runtime-logs/index.ts +790 -0
  71. package/src/sync/orchestrator.ts +6 -0
  72. package/src/team/index.ts +3642 -0
  73. package/src/team/mcp.ts +405 -0
  74. package/src/team/prompts.ts +141 -0
  75. package/src/utils/errors.ts +13 -0
  76. package/src/utils/fs.ts +40 -0
  77. package/src/utils/hash.ts +9 -0
  78. package/src/utils/ids.ts +12 -0
  79. package/src/utils/index.ts +7 -0
  80. package/src/utils/parsing.ts +11 -0
  81. package/src/utils/text.ts +18 -0
  82. package/src/utils/time.ts +5 -0
  83. package/src/workflow/index.ts +6 -24
  84. package/src/project/index.ts +0 -507
  85. package/src/task/index.ts +0 -840
@@ -0,0 +1,790 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { dirname, isAbsolute, join, relative } from "node:path";
3
+ import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
5
+
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[];
10
+
11
+ export interface CliLogEntryV1 {
12
+ version: 1;
13
+ kind: "cli";
14
+ timestamp: string;
15
+ date: string;
16
+ phase: EvoDevLogPhase;
17
+ command: string | null;
18
+ argv: string[];
19
+ cwd: string | null;
20
+ pid: number;
21
+ exitCode: number | null;
22
+ durationMs: number | null;
23
+ error: string | null;
24
+ }
25
+
26
+ export interface TraceLogEntryV1 {
27
+ version: 1;
28
+ kind: "trace";
29
+ timestamp: string;
30
+ date: string;
31
+ phase: EvoDevLogPhase;
32
+ sessionKey: string;
33
+ target: string;
34
+ runtime: {
35
+ surface: "cli" | "plugin";
36
+ argv: string[];
37
+ runtimeFile: string | null;
38
+ };
39
+ input: {
40
+ stdinBytes: number | null;
41
+ payload: unknown;
42
+ } | null;
43
+ event: {
44
+ eventId: string;
45
+ type: string;
46
+ summary: string;
47
+ metadata: unknown;
48
+ decision: unknown;
49
+ } | null;
50
+ result: {
51
+ enabled: boolean;
52
+ summary: string;
53
+ stateWrites: string[];
54
+ warnings: string[];
55
+ formattedResponse: string | null;
56
+ durationMs: number | null;
57
+ } | null;
58
+ error: string | null;
59
+ team: TraceLogTeamContext | null;
60
+ }
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
+
106
+ export interface TraceLogTeamContext {
107
+ runId: string;
108
+ roleId: string;
109
+ projectKey: string;
110
+ }
111
+
112
+ export interface CliLogInput {
113
+ phase: EvoDevLogPhase;
114
+ argv: string[];
115
+ timestamp?: Date | string;
116
+ cwd?: string | null;
117
+ pid?: number;
118
+ exitCode?: number | null;
119
+ durationMs?: number | null;
120
+ error?: unknown;
121
+ }
122
+
123
+ export interface TraceLogInput {
124
+ phase: EvoDevLogPhase;
125
+ target: string;
126
+ runtime: TraceLogEntryV1["runtime"];
127
+ sessionKey?: string;
128
+ payload?: Record<string, unknown> | null;
129
+ stdinBytes?: number | null;
130
+ event?: TraceLogEntryV1["event"] | null;
131
+ result?: TraceLogEntryV1["result"] | null;
132
+ timestamp?: Date | string;
133
+ error?: unknown;
134
+ team?: TraceLogTeamContext | null;
135
+ }
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
+
164
+ export interface EvoDevLogPaths {
165
+ logsDir: string;
166
+ cliDir: string;
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;
179
+ traceRootDir: string;
180
+ traceDateDir: string;
181
+ traceSessionDir: string | null;
182
+ traceLogPath: string | null;
183
+ projectRunDir: string | null;
184
+ projectRunAgentsDir: string | null;
185
+ agentLogDir: string | null;
186
+ agentTraceLogPath: string | null;
187
+ }
188
+
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
+
219
+ export function createCliLogEntry(input: CliLogInput): CliLogEntryV1 {
220
+ const timestamp = normalizeTimestamp(input.timestamp);
221
+ return {
222
+ version: 1,
223
+ kind: "cli",
224
+ timestamp,
225
+ date: formatLocalDateKey(timestamp),
226
+ phase: input.phase,
227
+ command: input.argv[0] ?? null,
228
+ argv: input.argv.map((arg) => truncateString(arg)),
229
+ cwd: input.cwd === undefined ? process.cwd() : input.cwd,
230
+ pid: input.pid ?? process.pid,
231
+ exitCode: input.exitCode ?? null,
232
+ durationMs: input.durationMs ?? null,
233
+ error: input.error === undefined ? null : describeError(input.error),
234
+ };
235
+ }
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
+
258
+ export function createTraceLogEntry(input: TraceLogInput): TraceLogEntryV1 {
259
+ const timestamp = normalizeTimestamp(input.timestamp);
260
+ const payload = input.payload ?? null;
261
+ const sessionKey = input.sessionKey ?? resolveTraceSessionKey(payload);
262
+ return {
263
+ version: 1,
264
+ kind: "trace",
265
+ timestamp,
266
+ date: formatLocalDateKey(timestamp),
267
+ phase: input.phase,
268
+ sessionKey,
269
+ target: truncateString(input.target),
270
+ runtime: {
271
+ surface: input.runtime.surface,
272
+ argv: input.runtime.argv.map((arg) => truncateString(arg)),
273
+ runtimeFile:
274
+ input.runtime.runtimeFile === null ? null : truncateString(input.runtime.runtimeFile),
275
+ },
276
+ input:
277
+ payload === null
278
+ ? null
279
+ : {
280
+ stdinBytes: input.stdinBytes ?? null,
281
+ payload,
282
+ },
283
+ event: input.event === undefined ? null : input.event,
284
+ result: input.result === undefined ? null : input.result,
285
+ error: input.error === undefined ? null : describeError(input.error),
286
+ team:
287
+ input.team === undefined || input.team === null
288
+ ? null
289
+ : {
290
+ runId: sanitizePathSegment(input.team.runId),
291
+ roleId: sanitizePathSegment(input.team.roleId),
292
+ projectKey: sanitizePathSegment(input.team.projectKey),
293
+ },
294
+ };
295
+ }
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
+
373
+ export function resolveEvoDevLogPaths(input: {
374
+ homeDir: string;
375
+ timestamp?: Date | string;
376
+ sessionKey?: string | null;
377
+ team?: TraceLogTeamContext | null;
378
+ }): EvoDevLogPaths {
379
+ const paths = resolveEvoDevPaths(input.homeDir);
380
+ const date = formatLocalDateKey(normalizeTimestamp(input.timestamp));
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"));
403
+ const traceRootDir = join(paths.logsDir, "trace");
404
+ const traceDateDir = join(traceRootDir, date);
405
+ const traceSessionDir =
406
+ input.sessionKey === undefined || input.sessionKey === null
407
+ ? null
408
+ : join(traceDateDir, sanitizePersistentIdentifier(input.sessionKey, "session"));
409
+ const projectRunDir =
410
+ input.team === undefined || input.team === null
411
+ ? null
412
+ : join(
413
+ paths.logsDir,
414
+ sanitizePersistentIdentifier(input.team.projectKey, "project"),
415
+ sanitizePersistentIdentifier(input.team.runId, "run"),
416
+ );
417
+ const projectRunAgentsDir = projectRunDir === null ? null : join(projectRunDir, "agents");
418
+ const agentLogDir =
419
+ projectRunAgentsDir === null || input.team === undefined || input.team === null
420
+ ? null
421
+ : join(projectRunAgentsDir, sanitizePersistentIdentifier(input.team.roleId, "role"));
422
+
423
+ return {
424
+ logsDir: paths.logsDir,
425
+ cliDir,
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"),
439
+ traceRootDir,
440
+ traceDateDir,
441
+ traceSessionDir,
442
+ traceLogPath: traceSessionDir === null ? null : join(traceSessionDir, "trace.log"),
443
+ projectRunDir,
444
+ projectRunAgentsDir,
445
+ agentLogDir,
446
+ agentTraceLogPath: agentLogDir === null ? null : join(agentLogDir, "trace.log"),
447
+ };
448
+ }
449
+
450
+ export async function appendCliLogEntry(homeDir: string, entry: CliLogEntryV1): Promise<string> {
451
+ const path = resolveEvoDevLogPaths({ homeDir, timestamp: entry.timestamp }).cliLogPath;
452
+ await appendJsonLine(path, entry);
453
+ return path;
454
+ }
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
+
465
+ export async function appendTraceLogEntry(
466
+ homeDir: string,
467
+ entry: TraceLogEntryV1,
468
+ ): Promise<string> {
469
+ const sanitizedEntry = sanitizeTraceLogEntryForAppend(entry);
470
+ const path = resolveEvoDevLogPaths({
471
+ homeDir,
472
+ timestamp: sanitizedEntry.timestamp,
473
+ sessionKey: sanitizedEntry.sessionKey,
474
+ }).traceLogPath;
475
+ if (path === null) throw new Error("Cannot resolve trace log path without a session key.");
476
+ await appendJsonLine(path, sanitizedEntry);
477
+ const agentTraceLogPath = resolveEvoDevLogPaths({
478
+ homeDir,
479
+ timestamp: sanitizedEntry.timestamp,
480
+ sessionKey: sanitizedEntry.sessionKey,
481
+ team: sanitizedEntry.team,
482
+ }).agentTraceLogPath;
483
+ if (agentTraceLogPath !== null) {
484
+ await appendJsonLine(agentTraceLogPath, sanitizedEntry);
485
+ }
486
+ return path;
487
+ }
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
+
520
+ export function resolveTraceTeamContext(input: {
521
+ homeDir: string;
522
+ environment?: Record<string, string | undefined>;
523
+ payload?: Record<string, unknown> | null;
524
+ }): TraceLogTeamContext | null {
525
+ const environment = input.environment ?? {};
526
+ const runId = optionalString(environment.EVODEV_TEAM_RUN_ID);
527
+ const roleId = optionalString(environment.EVODEV_TEAM_ROLE_ID);
528
+ if (runId === null || roleId === null) return null;
529
+ const repoRoot =
530
+ optionalString(environment.EVODEV_TEAM_REPO_ROOT) ??
531
+ optionalString(input.payload?.cwd) ??
532
+ input.homeDir;
533
+ return {
534
+ runId: sanitizePersistentIdentifier(runId, "run"),
535
+ roleId: sanitizePersistentIdentifier(roleId, "role"),
536
+ projectKey: resolveProjectLogKey(input.homeDir, repoRoot),
537
+ };
538
+ }
539
+
540
+ export function resolveProjectLogKey(homeDir: string, repoRoot: string): string {
541
+ const trimmedHome = stripTrailingSlash(homeDir);
542
+ const trimmedRepo = stripTrailingSlash(repoRoot);
543
+ const relativePath = relative(trimmedHome, trimmedRepo);
544
+ const source =
545
+ relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath)
546
+ ? relativePath
547
+ : trimmedRepo.replace(/^[/\\]+/, "");
548
+ const projectKey =
549
+ source
550
+ .split(/[/\\]+/)
551
+ .filter(Boolean)
552
+ .map((part) => sanitizePathSegment(part))
553
+ .join("-") || "project-local";
554
+ return sanitizePersistentIdentifier(projectKey, "project");
555
+ }
556
+
557
+ export function resolveTraceSessionKey(payload: unknown): string {
558
+ const record = isRecord(payload) ? payload : {};
559
+ const sessionId = optionalString(record.session_id ?? record.sessionId);
560
+ const cwd = optionalString(record.cwd);
561
+ const source = sessionId ?? cwd ?? "local";
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
+ });
640
+ }
641
+
642
+ async function appendJsonLine(path: string, value: unknown): Promise<void> {
643
+ await mkdir(dirname(path), { recursive: true });
644
+ await appendFile(path, `${JSON.stringify(value)}\n`, "utf8");
645
+ }
646
+
647
+ function formatLocalDateKey(timestamp: string): string {
648
+ const date = new Date(timestamp);
649
+ const year = date.getFullYear();
650
+ const month = String(date.getMonth() + 1).padStart(2, "0");
651
+ const day = String(date.getDate()).padStart(2, "0");
652
+ return `${year}-${month}-${day}`;
653
+ }
654
+
655
+ function truncateString(value: string): string {
656
+ if (value.length <= MAX_LOG_STRING_LENGTH) return value;
657
+ return `${value.slice(0, MAX_LOG_STRING_LENGTH)}...[truncated:${value.length - MAX_LOG_STRING_LENGTH}]`;
658
+ }
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
+
762
+ function sanitizePathSegment(value: string): string {
763
+ return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "session-local";
764
+ }
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
+
774
+ function stripTrailingSlash(path: string): string {
775
+ if (path === "/" || /^[A-Za-z]:[\\/]?$/.test(path)) return path;
776
+ return path.replace(/[/\\]+$/, "");
777
+ }
778
+
779
+ function optionalString(value: unknown): string | null {
780
+ return typeof value === "string" && value.length > 0 ? value : null;
781
+ }
782
+
783
+ function describeError(error: unknown): string {
784
+ if (error instanceof Error) return truncateString(error.message);
785
+ return truncateString(String(error));
786
+ }
787
+
788
+ function isRecord(value: unknown): value is Record<string, unknown> {
789
+ return typeof value === "object" && value !== null && !Array.isArray(value);
790
+ }