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

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
@@ -0,0 +1,520 @@
1
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, isAbsolute, join, normalize, relative } from "node:path";
3
+ import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { resolveProjectWorkspaceFromCwd } from "../projects/index.ts";
5
+ import { resolveTraceSessionKey, resolveTraceTeamContext } from "../runtime-logs/index.ts";
6
+ import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
7
+
8
+ export type CodeAgentTraceTarget = "claude" | "codex";
9
+ export type CodeAgentTraceRefSource =
10
+ | "hook-payload"
11
+ | "environment"
12
+ | "adapter-discovery"
13
+ | "manual";
14
+
15
+ export interface CodeAgentTraceRefV1 {
16
+ version: 1;
17
+ id: string;
18
+ target: CodeAgentTraceTarget;
19
+ sessionKey: string;
20
+ nativeSessionIdHash: string | null;
21
+ projectKey: string | null;
22
+ runId: string | null;
23
+ roleId: string | null;
24
+ cwdHash: string | null;
25
+ discoveredAt: string;
26
+ source: CodeAgentTraceRefSource;
27
+ tracePath: string | null;
28
+ tracePathExistsAtDiscovery: boolean | null;
29
+ tracePathTrusted: boolean;
30
+ contentStoredByEvoDev: false;
31
+ contentReadableByDefault: false;
32
+ notes: string[];
33
+ }
34
+
35
+ export interface CodeAgentTraceRefPaths {
36
+ rootDir: string;
37
+ claudeDir: string;
38
+ codexDir: string;
39
+ }
40
+
41
+ export interface CreateCodeAgentTraceRefInput {
42
+ target: CodeAgentTraceTarget;
43
+ sessionKey: string;
44
+ nativeSessionId?: string | null;
45
+ nativeSessionIdHash?: string | null;
46
+ projectKey?: string | null;
47
+ runId?: string | null;
48
+ roleId?: string | null;
49
+ cwd?: string | null;
50
+ cwdHash?: string | null;
51
+ discoveredAt?: Date | string;
52
+ source: CodeAgentTraceRefSource;
53
+ tracePath?: string | null;
54
+ tracePathExistsAtDiscovery?: boolean | null;
55
+ tracePathTrusted?: boolean;
56
+ notes?: string[];
57
+ }
58
+
59
+ export interface CodeAgentTraceRefRecord {
60
+ ref: CodeAgentTraceRefV1;
61
+ path: string;
62
+ }
63
+
64
+ export interface CodeAgentTraceRefListInput {
65
+ homeDir: string;
66
+ target?: CodeAgentTraceTarget;
67
+ projectKey?: string;
68
+ runId?: string;
69
+ roleId?: string;
70
+ }
71
+
72
+ export interface RecordCodeAgentTraceRefFromHookInput {
73
+ homeDir: string;
74
+ target: CodeAgentTraceTarget;
75
+ payload: Record<string, unknown>;
76
+ environment?: Record<string, string | undefined>;
77
+ now?: Date | string;
78
+ }
79
+
80
+ const CODE_AGENT_TRACE_TARGETS = ["claude", "codex"] as const;
81
+ const CODE_AGENT_TRACE_REF_SOURCES = [
82
+ "hook-payload",
83
+ "environment",
84
+ "adapter-discovery",
85
+ "manual",
86
+ ] as const;
87
+ const SENSITIVE_IDENTIFIER_PATTERN =
88
+ /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;
89
+ const SENSITIVE_PATH_PATTERN =
90
+ /(^|[/\\._-])(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials)([/\\._-]|$)/i;
91
+
92
+ export function resolveCodeAgentTraceRefPaths(homeDir: string): CodeAgentTraceRefPaths {
93
+ const rootDir = join(resolveEvoDevPaths(homeDir).stateDir, "code-agent-traces");
94
+ return {
95
+ rootDir,
96
+ claudeDir: join(rootDir, "claude"),
97
+ codexDir: join(rootDir, "codex"),
98
+ };
99
+ }
100
+
101
+ export function createCodeAgentTraceRef(input: CreateCodeAgentTraceRefInput): CodeAgentTraceRefV1 {
102
+ const sessionKey = sanitizePersistentIdentifier(input.sessionKey, "session");
103
+ const nativeSessionId = input.nativeSessionId ?? null;
104
+ const nativeSessionIdHash =
105
+ input.nativeSessionIdHash === undefined
106
+ ? hashOptionalIdentifier(nativeSessionId)
107
+ : sanitizeHashMetadata(input.nativeSessionIdHash);
108
+ const cwdHash =
109
+ input.cwdHash === undefined
110
+ ? hashOptionalIdentifier(input.cwd ?? null)
111
+ : sanitizeHashMetadata(input.cwdHash);
112
+ const notes = [...(input.notes ?? [])];
113
+ const normalizedTracePath = normalizeTracePathForStorage(input.tracePath ?? null, notes, {
114
+ nativeSessionId,
115
+ nativeSessionIdHash,
116
+ });
117
+
118
+ return {
119
+ version: 1,
120
+ id: `trace-ref-${input.target}-${sessionKey}`,
121
+ target: input.target,
122
+ sessionKey,
123
+ nativeSessionIdHash,
124
+ projectKey:
125
+ input.projectKey === undefined || input.projectKey === null
126
+ ? null
127
+ : sanitizePersistentIdentifier(input.projectKey, "project"),
128
+ runId:
129
+ input.runId === undefined || input.runId === null
130
+ ? null
131
+ : sanitizePersistentIdentifier(input.runId, "run"),
132
+ roleId:
133
+ input.roleId === undefined || input.roleId === null
134
+ ? null
135
+ : sanitizePersistentIdentifier(input.roleId, "role"),
136
+ cwdHash,
137
+ discoveredAt: normalizeTimestamp(input.discoveredAt),
138
+ source: input.source,
139
+ tracePath: normalizedTracePath,
140
+ tracePathExistsAtDiscovery:
141
+ normalizedTracePath === null ? null : (input.tracePathExistsAtDiscovery ?? null),
142
+ tracePathTrusted: normalizedTracePath === null ? false : (input.tracePathTrusted ?? false),
143
+ contentStoredByEvoDev: false,
144
+ contentReadableByDefault: false,
145
+ notes: sanitizeNotes(notes),
146
+ };
147
+ }
148
+
149
+ export function parseCodeAgentTraceRef(value: unknown): CodeAgentTraceRefV1 {
150
+ if (!isRecord(value)) throw new Error("Code Agent trace ref must be an object.");
151
+ if (value.version !== 1) throw new Error("Code Agent trace ref version must be 1.");
152
+ assertString("id", value.id);
153
+ assertEnum("target", value.target, CODE_AGENT_TRACE_TARGETS);
154
+ assertString("sessionKey", value.sessionKey);
155
+ assertNullableString("nativeSessionIdHash", value.nativeSessionIdHash);
156
+ assertNullableString("projectKey", value.projectKey);
157
+ assertNullableString("runId", value.runId);
158
+ assertNullableString("roleId", value.roleId);
159
+ assertNullableString("cwdHash", value.cwdHash);
160
+ assertString("discoveredAt", value.discoveredAt);
161
+ assertEnum("source", value.source, CODE_AGENT_TRACE_REF_SOURCES);
162
+ assertNullableString("tracePath", value.tracePath);
163
+ if (
164
+ value.tracePathExistsAtDiscovery !== null &&
165
+ typeof value.tracePathExistsAtDiscovery !== "boolean"
166
+ ) {
167
+ throw new Error("tracePathExistsAtDiscovery must be boolean or null.");
168
+ }
169
+ if (typeof value.tracePathTrusted !== "boolean") {
170
+ throw new Error("tracePathTrusted must be boolean.");
171
+ }
172
+ if (value.contentStoredByEvoDev !== false) {
173
+ throw new Error("contentStoredByEvoDev must be false.");
174
+ }
175
+ if (value.contentReadableByDefault !== false) {
176
+ throw new Error("contentReadableByDefault must be false.");
177
+ }
178
+ if (!Array.isArray(value.notes) || !value.notes.every((note) => typeof note === "string")) {
179
+ throw new Error("notes must be a string array.");
180
+ }
181
+
182
+ return value as unknown as CodeAgentTraceRefV1;
183
+ }
184
+
185
+ export async function writeCodeAgentTraceRef(input: {
186
+ homeDir: string;
187
+ ref: CodeAgentTraceRefV1;
188
+ }): Promise<CodeAgentTraceRefRecord> {
189
+ const ref = sanitizeCodeAgentTraceRefForWrite(parseCodeAgentTraceRef(input.ref));
190
+ const path = resolveCodeAgentTraceRefPath({
191
+ homeDir: input.homeDir,
192
+ target: ref.target,
193
+ sessionKey: ref.sessionKey,
194
+ });
195
+ await mkdir(dirname(path), { recursive: true });
196
+ await writeFile(path, `${JSON.stringify(ref, null, 2)}\n`, "utf8");
197
+ return { ref, path };
198
+ }
199
+
200
+ export async function readCodeAgentTraceRef(input: {
201
+ homeDir: string;
202
+ id: string;
203
+ }): Promise<CodeAgentTraceRefRecord> {
204
+ const targetAndSession = parseTraceRefId(input.id);
205
+ const path = resolveCodeAgentTraceRefPath({
206
+ homeDir: input.homeDir,
207
+ target: targetAndSession.target,
208
+ sessionKey: targetAndSession.sessionKey,
209
+ });
210
+ const ref = sanitizeCodeAgentTraceRefForWrite(
211
+ parseCodeAgentTraceRef(JSON.parse(await readFile(path, "utf8"))),
212
+ );
213
+ if (ref.id !== input.id) throw new Error(`Code Agent trace ref id mismatch: ${input.id}`);
214
+ return { ref, path };
215
+ }
216
+
217
+ export async function listCodeAgentTraceRefs(
218
+ input: CodeAgentTraceRefListInput,
219
+ ): Promise<CodeAgentTraceRefRecord[]> {
220
+ const targets = input.target === undefined ? [...CODE_AGENT_TRACE_TARGETS] : [input.target];
221
+ const records: CodeAgentTraceRefRecord[] = [];
222
+ for (const target of targets) {
223
+ const dir = resolveCodeAgentTraceRefTargetDir(input.homeDir, target);
224
+ const entries = await readDirectoryEntries(dir);
225
+ for (const entry of entries) {
226
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
227
+ const path = join(dir, entry.name);
228
+ const ref = sanitizeCodeAgentTraceRefForWrite(
229
+ parseCodeAgentTraceRef(JSON.parse(await readFile(path, "utf8"))),
230
+ );
231
+ if (input.projectKey !== undefined && ref.projectKey !== input.projectKey) continue;
232
+ if (input.runId !== undefined && ref.runId !== input.runId) continue;
233
+ if (input.roleId !== undefined && ref.roleId !== input.roleId) continue;
234
+ records.push({ ref, path });
235
+ }
236
+ }
237
+ return records.sort((left, right) => left.ref.id.localeCompare(right.ref.id));
238
+ }
239
+
240
+ export async function recordCodeAgentTraceRefFromHook(
241
+ input: RecordCodeAgentTraceRefFromHookInput,
242
+ ): Promise<CodeAgentTraceRefRecord | null> {
243
+ const environment = input.environment ?? {};
244
+ const sessionIdFromPayload = optionalString(input.payload.session_id ?? input.payload.sessionId);
245
+ const sessionIdFromEnvironment = optionalString(environment.EVODEV_CODE_AGENT_SESSION_ID);
246
+ const tracePathFromPayload = readPayloadTracePath(input.payload);
247
+ const tracePathFromEnvironment = optionalString(environment.EVODEV_CODE_AGENT_TRACE_PATH);
248
+ const nativeSessionId = sessionIdFromPayload ?? sessionIdFromEnvironment;
249
+ const tracePathInput = tracePathFromPayload ?? tracePathFromEnvironment;
250
+ if (nativeSessionId === null && tracePathInput === null) return null;
251
+
252
+ const notes: string[] = [];
253
+ const normalizedPath = normalizeTracePathForInspection(tracePathInput, notes);
254
+ const pathStatus =
255
+ normalizedPath === null
256
+ ? { exists: null, trusted: false, notes: [] }
257
+ : await inspectTracePath({
258
+ homeDir: input.homeDir,
259
+ target: input.target,
260
+ tracePath: normalizedPath,
261
+ });
262
+ notes.push(...pathStatus.notes);
263
+ const team = resolveTraceTeamContext({
264
+ homeDir: input.homeDir,
265
+ payload: input.payload,
266
+ environment,
267
+ });
268
+ const cwd = optionalString(input.payload.cwd);
269
+ const workspace =
270
+ team === null && cwd !== null
271
+ ? await resolveProjectWorkspaceFromCwd({ homeDir: input.homeDir, cwd })
272
+ : null;
273
+ const source =
274
+ sessionIdFromPayload !== null || tracePathFromPayload !== null ? "hook-payload" : "environment";
275
+
276
+ const ref = createCodeAgentTraceRef({
277
+ target: input.target,
278
+ sessionKey: resolveTraceSessionKey(input.payload),
279
+ nativeSessionId,
280
+ projectKey: team?.projectKey ?? workspace?.projectKey ?? null,
281
+ runId: team?.runId ?? null,
282
+ roleId: team?.roleId ?? null,
283
+ cwd,
284
+ discoveredAt: input.now,
285
+ source,
286
+ tracePath: normalizedPath,
287
+ tracePathExistsAtDiscovery: pathStatus.exists,
288
+ tracePathTrusted: pathStatus.trusted,
289
+ notes,
290
+ });
291
+ return await writeCodeAgentTraceRef({ homeDir: input.homeDir, ref });
292
+ }
293
+
294
+ function sanitizeCodeAgentTraceRefForWrite(ref: CodeAgentTraceRefV1): CodeAgentTraceRefV1 {
295
+ return createCodeAgentTraceRef({
296
+ target: ref.target,
297
+ sessionKey: ref.sessionKey,
298
+ nativeSessionIdHash: sanitizeHashMetadata(ref.nativeSessionIdHash),
299
+ projectKey: ref.projectKey,
300
+ runId: ref.runId,
301
+ roleId: ref.roleId,
302
+ cwdHash: sanitizeHashMetadata(ref.cwdHash),
303
+ discoveredAt: ref.discoveredAt,
304
+ source: ref.source,
305
+ tracePath: ref.tracePath,
306
+ tracePathExistsAtDiscovery: ref.tracePathExistsAtDiscovery,
307
+ tracePathTrusted: ref.tracePathTrusted,
308
+ notes: ref.notes,
309
+ });
310
+ }
311
+
312
+ function resolveCodeAgentTraceRefPath(input: {
313
+ homeDir: string;
314
+ target: CodeAgentTraceTarget;
315
+ sessionKey: string;
316
+ }): string {
317
+ return join(
318
+ resolveCodeAgentTraceRefTargetDir(input.homeDir, input.target),
319
+ `${sanitizePersistentIdentifier(input.sessionKey, "session")}.json`,
320
+ );
321
+ }
322
+
323
+ function resolveCodeAgentTraceRefTargetDir(homeDir: string, target: CodeAgentTraceTarget): string {
324
+ const paths = resolveCodeAgentTraceRefPaths(homeDir);
325
+ return target === "claude" ? paths.claudeDir : paths.codexDir;
326
+ }
327
+
328
+ function parseTraceRefId(id: string): { target: CodeAgentTraceTarget; sessionKey: string } {
329
+ const match = id.match(/^trace-ref-(claude|codex)-(.+)$/);
330
+ if (match === null) throw new Error(`Invalid Code Agent trace ref id: ${id}`);
331
+ return {
332
+ target: match[1] as CodeAgentTraceTarget,
333
+ sessionKey: sanitizePersistentIdentifier(match[2] ?? "", "session"),
334
+ };
335
+ }
336
+
337
+ async function readDirectoryEntries(dir: string) {
338
+ try {
339
+ return await readdir(dir, { withFileTypes: true });
340
+ } catch (error) {
341
+ if (isNotFoundError(error)) return [];
342
+ throw error;
343
+ }
344
+ }
345
+
346
+ function readPayloadTracePath(payload: Record<string, unknown>): string | null {
347
+ return (
348
+ optionalString(payload.transcript_path) ??
349
+ optionalString(payload.transcriptPath) ??
350
+ optionalString(payload.trace_path) ??
351
+ optionalString(payload.tracePath) ??
352
+ optionalString(payload.session_path) ??
353
+ optionalString(payload.sessionPath)
354
+ );
355
+ }
356
+
357
+ function normalizeTracePathForStorage(
358
+ value: string | null,
359
+ notes: string[],
360
+ redaction: { nativeSessionId: string | null; nativeSessionIdHash: string | null },
361
+ ): string | null {
362
+ const normalized = normalizeTracePathForInspection(value, notes);
363
+ return normalized === null ? null : redactTracePathForStorage(normalized, notes, redaction);
364
+ }
365
+
366
+ function normalizeTracePathForInspection(value: string | null, notes: string[]): string | null {
367
+ if (value === null || value.trim() === "") return null;
368
+ const trimmed = value.trim();
369
+ if (/[\0\r\n]/.test(trimmed)) {
370
+ notes.push("trace path omitted: unsafe control characters");
371
+ return null;
372
+ }
373
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
374
+ notes.push("trace path omitted: URLs are not stored");
375
+ return null;
376
+ }
377
+ if (!isAbsolute(trimmed)) {
378
+ notes.push("trace path omitted: relative paths are not trusted");
379
+ return null;
380
+ }
381
+ if (hasRawTraversalSegment(trimmed)) {
382
+ notes.push("trace path omitted: traversal segments are not trusted");
383
+ return null;
384
+ }
385
+ if (SENSITIVE_PATH_PATTERN.test(trimmed)) {
386
+ notes.push("trace path omitted: sensitive-looking path segment");
387
+ return null;
388
+ }
389
+ return normalize(trimmed);
390
+ }
391
+
392
+ function redactTracePathForStorage(
393
+ path: string,
394
+ notes: string[],
395
+ redaction: { nativeSessionId: string | null; nativeSessionIdHash: string | null },
396
+ ): string {
397
+ if (redaction.nativeSessionId === null || redaction.nativeSessionId.trim() === "") return path;
398
+ if (!path.includes(redaction.nativeSessionId)) return path;
399
+ notes.push("trace-path-redacted-native-session-id");
400
+ return path
401
+ .split(redaction.nativeSessionId)
402
+ .join(redaction.nativeSessionIdHash ?? "native-session-id-redacted");
403
+ }
404
+
405
+ async function inspectTracePath(input: {
406
+ homeDir: string;
407
+ target: CodeAgentTraceTarget;
408
+ tracePath: string;
409
+ }): Promise<{ exists: boolean | null; trusted: boolean; notes: string[] }> {
410
+ const notes: string[] = [];
411
+ let exists: boolean | null = null;
412
+ try {
413
+ await stat(input.tracePath);
414
+ exists = true;
415
+ } catch (error) {
416
+ if (isNotFoundError(error)) {
417
+ exists = false;
418
+ notes.push("trace path was not present at discovery");
419
+ } else {
420
+ exists = null;
421
+ notes.push("trace path existence could not be checked");
422
+ }
423
+ }
424
+
425
+ const trusted = exists === true && isDescendant(resolveTargetUserDir(input), input.tracePath);
426
+ if (exists === true && !trusted) {
427
+ notes.push("trace path exists outside the target user-level directory");
428
+ }
429
+ return { exists, trusted, notes };
430
+ }
431
+
432
+ function resolveTargetUserDir(input: { homeDir: string; target: CodeAgentTraceTarget }): string {
433
+ return normalize(
434
+ join(stripTrailingSlash(input.homeDir), input.target === "claude" ? ".claude" : ".codex"),
435
+ );
436
+ }
437
+
438
+ function isDescendant(parent: string, child: string): boolean {
439
+ const relation = relative(normalize(parent), normalize(child));
440
+ return relation === "" || (!relation.startsWith("..") && !isAbsolute(relation));
441
+ }
442
+
443
+ function hasRawTraversalSegment(path: string): boolean {
444
+ return path.split(/[/\\]+/).some((part) => part === "..");
445
+ }
446
+
447
+ function hashOptionalIdentifier(value: string | null): string | null {
448
+ if (value === null || value.trim() === "") return null;
449
+ return `sha256-${sha256Short(value)}`;
450
+ }
451
+
452
+ function sanitizeHashMetadata(value: string | null): string | null {
453
+ if (value === null || value.trim() === "") return null;
454
+ if (/^sha256-[a-f0-9]{16}$/i.test(value)) return value;
455
+ return hashOptionalIdentifier(value);
456
+ }
457
+
458
+ function sanitizePersistentIdentifier(value: string, prefix: string): string {
459
+ const pathSafe = value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || `${prefix}-local`;
460
+ if (!SENSITIVE_IDENTIFIER_PATTERN.test(value) && !SENSITIVE_IDENTIFIER_PATTERN.test(pathSafe)) {
461
+ return pathSafe;
462
+ }
463
+ return `${prefix}-${sha256Short(value)}`;
464
+ }
465
+
466
+ function sanitizeNotes(notes: string[]): string[] {
467
+ return notes
468
+ .filter((note) => note.trim() !== "")
469
+ .slice(0, 20)
470
+ .map((note) => sanitizeNote(note));
471
+ }
472
+
473
+ function sanitizeNote(note: string): string {
474
+ const truncated = note.trim().slice(0, 240);
475
+ if (SENSITIVE_IDENTIFIER_PATTERN.test(truncated)) return "trace ref note redacted";
476
+ return truncated;
477
+ }
478
+
479
+ function optionalString(value: unknown): string | null {
480
+ return typeof value === "string" && value.trim() !== "" ? value : null;
481
+ }
482
+
483
+ function assertString(label: string, value: unknown): asserts value is string {
484
+ if (typeof value !== "string" || value.trim() === "") {
485
+ throw new Error(`${label} must be a non-empty string.`);
486
+ }
487
+ }
488
+
489
+ function assertNullableString(label: string, value: unknown): asserts value is string | null {
490
+ if (value !== null && typeof value !== "string") {
491
+ throw new Error(`${label} must be a string or null.`);
492
+ }
493
+ }
494
+
495
+ function assertEnum<T extends readonly string[]>(
496
+ label: string,
497
+ value: unknown,
498
+ allowed: T,
499
+ ): asserts value is T[number] {
500
+ if (typeof value !== "string" || !allowed.includes(value)) {
501
+ throw new Error(`${label} is invalid.`);
502
+ }
503
+ }
504
+
505
+ function stripTrailingSlash(path: string): string {
506
+ if (path === "/") return path;
507
+ return path.replace(/[/\\]+$/, "");
508
+ }
509
+
510
+ function isNotFoundError(error: unknown): boolean {
511
+ return (
512
+ error instanceof Error &&
513
+ (("code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") ||
514
+ error.message.includes("ENOENT"))
515
+ );
516
+ }
517
+
518
+ function isRecord(value: unknown): value is Record<string, unknown> {
519
+ return typeof value === "object" && value !== null && !Array.isArray(value);
520
+ }
@@ -8,13 +8,18 @@ export {
8
8
  } from "./registry.ts";
9
9
  export {
10
10
  type EvoDevSettings,
11
+ type EvolutionSettings,
12
+ type MemorySettings,
11
13
  type PluginSettings,
12
14
  type SettingsInput,
13
15
  type TeamRuntimeSettings,
16
+ createDefaultMemorySettings,
17
+ createDefaultEvolutionSettings,
14
18
  createDefaultTeamRuntimeSettings,
15
19
  createDefaultSettings,
16
20
  mergeSettings,
17
21
  parseSettings,
22
+ readRuntimeInjectionSettings,
18
23
  } from "./settings.ts";
19
24
  export {
20
25
  type InstallState,
@@ -29,7 +29,7 @@ export function resolveEvoDevPaths(homeDir: string = getHomeDir()): EvoDevPaths
29
29
  const evosDir = `${rootDir}/evos`;
30
30
  const roleAgentsDir = `${rootDir}/agents/roles`;
31
31
  const teamsDir = `${rootDir}/teams`;
32
- const runsDir = `${rootDir}/runs`;
32
+ const runsDir = `${teamsDir}/runs`;
33
33
 
34
34
  return {
35
35
  homeDir: normalizedHome,