@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.
- package/assets/agents/review/code-reviewer/examples.md +1 -1
- package/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/assets/agents/review/code-reviewer/verification.md +1 -1
- package/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
- package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/config/index.js +1115 -81
- package/dist/index.js +13796 -2196
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +63 -292
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +7 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +201 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +462 -40
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -0
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +652 -376
- package/src/index.ts +16 -3
- package/src/pack/index.ts +13 -13
- package/src/plugins/capabilities.ts +40 -42
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +4 -0
- package/src/projects/index.ts +453 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +790 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/team/index.ts +3642 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +6 -24
- package/src/project/index.ts +0 -507
- 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
|
+
}
|