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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +5 -3
  2. package/assets/team/agents/code-reviewer.md +48 -0
  3. package/assets/team/agents/docs-maintainer.md +51 -0
  4. package/assets/team/agents/implementation-engineer.md +51 -0
  5. package/assets/team/agents/product-scope-analyst.md +58 -0
  6. package/assets/team/agents/release-engineer.md +55 -0
  7. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  8. package/assets/team/agents/solution-architect.md +51 -0
  9. package/assets/team/agents/verification-engineer.md +51 -0
  10. package/assets/team/team.md +102 -0
  11. package/dist/assets/index.js +5 -5
  12. package/dist/config/index.js +793 -241
  13. package/dist/index.js +20840 -12908
  14. package/dist/plugins/index.js +13 -13
  15. package/package.json +1 -1
  16. package/src/agents/index.ts +1 -265
  17. package/src/code-agent-traces/index.ts +11 -12
  18. package/src/config/index.ts +2 -0
  19. package/src/config/settings.ts +116 -7
  20. package/src/config/store.ts +1 -1
  21. package/src/daemon/index.ts +1 -41
  22. package/src/evolution/candidates/index.ts +730 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +287 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +9 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/retention.ts +643 -0
  32. package/src/evolution/evidence/session-memory/segment.ts +216 -0
  33. package/src/evolution/evidence/session-memory/semantic-packet.ts +408 -0
  34. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  35. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  36. package/src/evolution/evidence/session-memory/storage.ts +744 -0
  37. package/src/evolution/evidence/session-memory/types.ts +296 -0
  38. package/src/evolution/evidence/session-memory/updater.ts +199 -0
  39. package/src/evolution/formatters.ts +169 -0
  40. package/src/evolution/imports/apply.ts +435 -0
  41. package/src/evolution/imports/diff.ts +472 -0
  42. package/src/evolution/imports/index.ts +7 -0
  43. package/src/evolution/imports/materialize.ts +640 -0
  44. package/src/evolution/imports/paths.ts +129 -0
  45. package/src/evolution/imports/stage.ts +414 -0
  46. package/src/evolution/imports/storage.ts +952 -0
  47. package/src/evolution/imports/types.ts +226 -0
  48. package/src/evolution/index.ts +19 -2827
  49. package/src/evolution/knowledge/change-store.ts +558 -0
  50. package/src/evolution/knowledge/changes.ts +459 -0
  51. package/src/evolution/knowledge/freshness.ts +69 -0
  52. package/src/{knowledge → evolution/knowledge}/index.ts +1532 -206
  53. package/src/evolution/knowledge/review.ts +446 -0
  54. package/src/evolution/knowledge/support.ts +135 -0
  55. package/src/evolution/paths.ts +44 -0
  56. package/src/evolution/processor/distillation.ts +518 -0
  57. package/src/evolution/processor/index.ts +3 -0
  58. package/src/evolution/processor/process.ts +594 -0
  59. package/src/{learning → evolution/review}/index.ts +10 -14
  60. package/src/evolution/schema.ts +639 -0
  61. package/src/evolution/shared.ts +1053 -0
  62. package/src/evolution/triggers/classification.ts +102 -0
  63. package/src/evolution/triggers/index.ts +295 -0
  64. package/src/hooks/index.ts +281 -197
  65. package/src/index.ts +15 -4
  66. package/src/projects/index.ts +934 -0
  67. package/src/runtime-logs/index.ts +100 -13
  68. package/src/team/index.ts +582 -3
  69. package/src/utils/errors.ts +13 -0
  70. package/src/utils/fs.ts +40 -0
  71. package/src/utils/hash.ts +9 -0
  72. package/src/utils/ids.ts +12 -0
  73. package/src/utils/index.ts +7 -0
  74. package/src/utils/parsing.ts +11 -0
  75. package/src/utils/text.ts +18 -0
  76. package/src/utils/time.ts +5 -0
  77. package/src/workflow/index.ts +3 -21
  78. package/src/project/index.ts +0 -507
  79. package/src/task/index.ts +0 -840
@@ -248,18 +248,18 @@ function getEnabledPluginIds(settings) {
248
248
  return Object.entries(settings.plugins).filter(([, pluginSettings]) => pluginSettings.enabled).map(([pluginId]) => pluginId).sort((left, right) => left.localeCompare(right));
249
249
  }
250
250
  export {
251
- writeCodexCapabilityVerificationArtifact,
252
- validateCodexCapabilityVerificationArtifact,
253
- runPluginConformance,
254
- resolveCodexCapabilityArtifactPath,
255
- readCodexCapabilityVerificationArtifact,
256
- negotiatePluginCapabilities,
257
- isCapabilityUsable,
258
- getEnabledPluginIds,
259
- formatCodexCapabilityVerificationArtifact,
260
- createUnknownNegotiatedCapabilities,
261
- createPluginRegistry,
262
- createCodexCapabilityVerificationArtifact,
251
+ PluginRegistry,
263
252
  PluginRegistryError,
264
- PluginRegistry
253
+ createCodexCapabilityVerificationArtifact,
254
+ createPluginRegistry,
255
+ createUnknownNegotiatedCapabilities,
256
+ formatCodexCapabilityVerificationArtifact,
257
+ getEnabledPluginIds,
258
+ isCapabilityUsable,
259
+ negotiatePluginCapabilities,
260
+ readCodexCapabilityVerificationArtifact,
261
+ resolveCodexCapabilityArtifactPath,
262
+ runPluginConformance,
263
+ validateCodexCapabilityVerificationArtifact,
264
+ writeCodexCapabilityVerificationArtifact
265
265
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evo-dev/core",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.20",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,18 +3,9 @@ import {
3
3
  type ScopedKnowledgeContextPack,
4
4
  createScopedKnowledgeContextPack,
5
5
  formatScopedKnowledgePromptBlock,
6
- } from "../knowledge/index.ts";
7
- import type { TaskContract, TaskExecutionMode } from "../task/index.ts";
6
+ } from "../evolution/knowledge/index.ts";
8
7
 
9
8
  export type AgentPersistence = "named" | "dynamic";
10
- export type AgentLens =
11
- | "review"
12
- | "qa"
13
- | "security"
14
- | "release"
15
- | "architecture"
16
- | "migration"
17
- | "test";
18
9
  export type AgentOutputSchemaId =
19
10
  | "review-findings-v1"
20
11
  | "verification-summary-v1"
@@ -61,34 +52,6 @@ export interface AgentProfile {
61
52
  };
62
53
  }
63
54
 
64
- export interface AgentPlannedInvocation {
65
- profile: AgentProfile;
66
- lens: AgentLens;
67
- reason: string;
68
- plannedOnly: true;
69
- }
70
-
71
- export interface AgentMergePlan {
72
- strategy: "none" | "single-output" | "dedupe-preserve-conflicts";
73
- outputSchema: AgentOutputSchemaId | null;
74
- dedupeBy: string[];
75
- conflictPolicy: string[];
76
- advisoryCategories: string[];
77
- }
78
-
79
- export interface AgentComposeDryRunPlan {
80
- ok: boolean;
81
- taskId: string;
82
- mode: TaskExecutionMode | null;
83
- workflowId: string | null;
84
- agents: AgentPlannedInvocation[];
85
- permissions: AgentPermissions;
86
- mergePlan: AgentMergePlan;
87
- warnings: string[];
88
- advisories: string[];
89
- rationale: string;
90
- }
91
-
92
55
  export interface AgentContextDryRunBundle {
93
56
  profile: AgentProfile;
94
57
  context: {
@@ -138,17 +101,6 @@ const DEFAULT_AGENT_PERMISSIONS: AgentPermissions = {
138
101
  };
139
102
  const FORBIDDEN_INPUTS = ["rawPrompts", "secrets", "rawCommandLogs", "sourceCorpus"];
140
103
  const SAFE_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
141
- const PRIVACY_ADVISORY_PATTERN =
142
- /raw prompt|rawprompt|source corpus|sourcecorpus|secret|\.env|raw command|raw log|internal url|private url|network|write files|run commands|spawn agents|write memory/i;
143
- const LENS_TO_EXPERTISE: Record<AgentLens, string[]> = {
144
- review: ["code-review"],
145
- qa: ["qa", "verification"],
146
- security: ["security", "privacy"],
147
- release: ["release", "packaging"],
148
- architecture: ["architecture"],
149
- migration: ["migration", "impact-analysis"],
150
- test: ["testing"],
151
- };
152
104
 
153
105
  export function createDefaultAgentPermissions(): AgentPermissions {
154
106
  return { ...DEFAULT_AGENT_PERMISSIONS };
@@ -202,67 +154,6 @@ export async function readAgentProfile(path: string): Promise<AgentProfile> {
202
154
  return parseAgentProfile(JSON.parse(await readFile(path, "utf8")));
203
155
  }
204
156
 
205
- export function composeAgentDryRun(input: {
206
- contract: TaskContract;
207
- workflowId?: string;
208
- lenses?: AgentLens[];
209
- }): AgentComposeDryRunPlan {
210
- const mode = input.contract.route.mode;
211
- const workflowId = input.workflowId ?? input.contract.route.workflowId;
212
- const warnings: string[] = [];
213
- const permissions = createDefaultAgentPermissions();
214
- const requestedLenses = dedupeLenses(input.lenses ?? []);
215
- const advisories = collectPrivacyBoundaryAdvisories(input.contract, requestedLenses, workflowId);
216
-
217
- if (mode === "minimal") {
218
- return {
219
- ok: advisories.length === 0,
220
- taskId: input.contract.taskId,
221
- mode,
222
- workflowId,
223
- agents: [],
224
- permissions,
225
- mergePlan: createMergePlan([]),
226
- warnings,
227
- advisories,
228
- rationale: "Minimal mode does not force dynamic agents by default.",
229
- };
230
- }
231
-
232
- const selectedLenses = selectLenses(
233
- mode,
234
- requestedLenses,
235
- workflowId,
236
- input.contract.route.requiredReview,
237
- );
238
- const agents = selectedLenses.map((lens) => ({
239
- profile: createDynamicProfile(lens, input.contract, permissions),
240
- lens,
241
- reason: createLensReason(lens, mode, workflowId),
242
- plannedOnly: true as const,
243
- }));
244
-
245
- if (mode === "standard" && agents.length > 1) {
246
- warnings.push("Standard mode advisory selected more than one reviewer/triager.");
247
- }
248
-
249
- return {
250
- ok: advisories.length === 0,
251
- taskId: input.contract.taskId,
252
- mode,
253
- workflowId,
254
- agents,
255
- permissions,
256
- mergePlan: createMergePlan(agents),
257
- warnings,
258
- advisories,
259
- rationale:
260
- agents.length === 0
261
- ? "No dynamic agents selected; deterministic verification may be sufficient."
262
- : `Selected ${agents.length} planned-only agent(s) based on mode/workflow/lenses.`,
263
- };
264
- }
265
-
266
157
  export function loadAgentContextDryRun(profile: AgentProfile): AgentContextDryRunBundle {
267
158
  const parsed = parseAgentProfile(profile);
268
159
  return {
@@ -376,47 +267,6 @@ export function mergeReviewFindings(outputs: ReviewFindingOutput[]): {
376
267
  return { acceptedFindings: [...byKey.values()], unresolvedConflicts: conflicts };
377
268
  }
378
269
 
379
- export function formatAgentComposeDryRun(plan: AgentComposeDryRunPlan): string {
380
- return [
381
- "EvoDev agent compose dry-run",
382
- "",
383
- `Task: ${plan.taskId}`,
384
- `Mode: ${plan.mode ?? "not routed"}`,
385
- `Workflow: ${plan.workflowId ?? "none"}`,
386
- `Rationale: ${plan.rationale}`,
387
- "No-write/no-spawn: true",
388
- "Privacy: local-private, metadata-only, raw prompts/source/secrets/logs forbidden",
389
- "Agents:",
390
- ...(plan.agents.length === 0
391
- ? [" - none"]
392
- : plan.agents.map(
393
- (agent) =>
394
- ` - ${agent.profile.id} [${agent.lens}] traits=${agent.profile.traits.expertise.join(",")} schema=${agent.profile.output.schema} plannedOnly=${agent.plannedOnly}`,
395
- )),
396
- "Inputs:",
397
- ...(plan.agents.length === 0
398
- ? [" - none"]
399
- : [
400
- ` - required: ${plan.agents[0].profile.inputs.required.join(",")}`,
401
- ` - optional: ${plan.agents[0].profile.inputs.optional.join(",")}`,
402
- ` - forbidden: ${plan.agents[0].profile.inputs.forbidden.join(",")}`,
403
- ]),
404
- "Permissions:",
405
- ...Object.entries(plan.permissions).map(([key, value]) => ` - ${key}: ${value}`),
406
- "Merge plan:",
407
- ` - strategy: ${plan.mergePlan.strategy}`,
408
- ` - outputSchema: ${plan.mergePlan.outputSchema ?? "none"}`,
409
- "Warnings:",
410
- ...(plan.warnings.length === 0
411
- ? [" - none"]
412
- : plan.warnings.map((warning) => ` - ${warning}`)),
413
- "Advisories:",
414
- ...(plan.advisories.length === 0
415
- ? [" - none"]
416
- : plan.advisories.map((advisory) => ` - ${advisory}`)),
417
- ].join("\n");
418
- }
419
-
420
270
  export function formatAgentContextDryRun(bundle: AgentContextDryRunBundle): string {
421
271
  return [
422
272
  "EvoDev agent load-context dry-run",
@@ -436,116 +286,6 @@ export function formatAgentContextDryRun(bundle: AgentContextDryRunBundle): stri
436
286
  ].join("\n");
437
287
  }
438
288
 
439
- function createDynamicProfile(
440
- lens: AgentLens,
441
- contract: TaskContract,
442
- permissions: AgentPermissions,
443
- ): AgentProfile {
444
- return {
445
- version: 1,
446
- id: `dynamic-${lens}-${contract.taskId}`.slice(0, 100),
447
- name: `Dynamic ${lens} reviewer`,
448
- persistence: "dynamic",
449
- description: `Task-scoped ${lens} profile for metadata-only dry-run planning.`,
450
- traits: {
451
- expertise: LENS_TO_EXPERTISE[lens],
452
- stance: ["skeptical-reviewer"],
453
- approach: ["evidence-first", "advisory"],
454
- domain: ["software-rd"],
455
- },
456
- inputs: {
457
- required: ["taskContract", "scope", "evidenceSummary"],
458
- optional: ["workflowPlan"],
459
- forbidden: FORBIDDEN_INPUTS,
460
- },
461
- permissions,
462
- output: {
463
- schema: "review-findings-v1",
464
- requiredFields: ["summary", "findings", "confidence", "evidenceRefs"],
465
- },
466
- privacy: {
467
- classification: "local-private",
468
- metadataOnly: true,
469
- rawPromptStored: false,
470
- sourceContentStored: false,
471
- },
472
- };
473
- }
474
-
475
- function selectLenses(
476
- mode: TaskExecutionMode | null,
477
- requested: AgentLens[],
478
- workflowId: string | null,
479
- requiredReview: string[],
480
- ): AgentLens[] {
481
- if (requested.length > 0) return mode === "standard" ? requested.slice(0, 1) : requested;
482
- if (mode === "standard") return requiredReview.length > 0 ? ["review"] : [];
483
- if (mode === "rigorous") {
484
- if (workflowId?.includes("security")) return ["security", "review"];
485
- if (workflowId?.includes("release")) return ["release", "security"];
486
- if (workflowId?.includes("migration")) return ["migration", "architecture"];
487
- if (workflowId?.includes("architecture")) return ["architecture", "review"];
488
- return requiredReview.length > 0 ? ["review", "qa"] : [];
489
- }
490
- return [];
491
- }
492
-
493
- function createMergePlan(agents: AgentPlannedInvocation[]): AgentMergePlan {
494
- if (agents.length === 0)
495
- return {
496
- strategy: "none",
497
- outputSchema: null,
498
- dedupeBy: [],
499
- conflictPolicy: [],
500
- advisoryCategories: [],
501
- };
502
- if (agents.length === 1)
503
- return {
504
- strategy: "single-output",
505
- outputSchema: agents[0].profile.output.schema,
506
- dedupeBy: [],
507
- conflictPolicy: [],
508
- advisoryCategories: [],
509
- };
510
- return {
511
- strategy: "dedupe-preserve-conflicts",
512
- outputSchema: "review-findings-v1",
513
- dedupeBy: ["category", "path", "line", "title"],
514
- conflictPolicy: ["preserve dissent", "keep higher severity unless evidence refutes"],
515
- advisoryCategories: ["security", "privacy", "release"],
516
- };
517
- }
518
-
519
- function createLensReason(
520
- lens: AgentLens,
521
- mode: TaskExecutionMode | null,
522
- workflowId: string | null,
523
- ): string {
524
- return `${lens} lens selected for ${mode ?? "unrouted"} mode${workflowId ? ` and workflow ${workflowId}` : ""}.`;
525
- }
526
-
527
- function collectPrivacyBoundaryAdvisories(
528
- contract: TaskContract,
529
- lenses: AgentLens[],
530
- workflowId: string | null,
531
- ): string[] {
532
- const text = [
533
- contract.source.summary,
534
- contract.currentState.summary,
535
- contract.targetState.summary,
536
- contract.route.rationale,
537
- workflowId ?? "",
538
- ...contract.scope.allowedOperations,
539
- ...contract.scope.requiresUserConfirmation,
540
- ...lenses,
541
- ].join(" ");
542
- return PRIVACY_ADVISORY_PATTERN.test(text)
543
- ? [
544
- "Agent planning detected privacy/risky context requiring raw data, writes, commands, network, spawning, or memory.",
545
- ]
546
- : [];
547
- }
548
-
549
289
  function validateRequiredObject(
550
290
  output: unknown,
551
291
  fields: string[],
@@ -569,10 +309,6 @@ function isKnownOutputSchema(value: unknown): value is AgentOutputSchemaId {
569
309
  );
570
310
  }
571
311
 
572
- function dedupeLenses(lenses: AgentLens[]): AgentLens[] {
573
- return [...new Set(lenses)];
574
- }
575
-
576
312
  function severityRank(severity: AgentFindingSeverity): number {
577
313
  return { info: 0, low: 1, medium: 2, high: 3, critical: 4 }[severity];
578
314
  }
@@ -1,8 +1,9 @@
1
- import { createHash } from "node:crypto";
2
1
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
2
  import { dirname, isAbsolute, join, normalize, relative } from "node:path";
4
3
  import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { resolveProjectWorkspaceFromCwd } from "../projects/index.ts";
5
5
  import { resolveTraceSessionKey, resolveTraceTeamContext } from "../runtime-logs/index.ts";
6
+ import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
6
7
 
7
8
  export type CodeAgentTraceTarget = "claude" | "codex";
8
9
  export type CodeAgentTraceRefSource =
@@ -264,6 +265,11 @@ export async function recordCodeAgentTraceRefFromHook(
264
265
  payload: input.payload,
265
266
  environment,
266
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;
267
273
  const source =
268
274
  sessionIdFromPayload !== null || tracePathFromPayload !== null ? "hook-payload" : "environment";
269
275
 
@@ -271,10 +277,10 @@ export async function recordCodeAgentTraceRefFromHook(
271
277
  target: input.target,
272
278
  sessionKey: resolveTraceSessionKey(input.payload),
273
279
  nativeSessionId,
274
- projectKey: team?.projectKey ?? null,
280
+ projectKey: team?.projectKey ?? workspace?.projectKey ?? null,
275
281
  runId: team?.runId ?? null,
276
282
  roleId: team?.roleId ?? null,
277
- cwd: optionalString(input.payload.cwd),
283
+ cwd,
278
284
  discoveredAt: input.now,
279
285
  source,
280
286
  tracePath: normalizedPath,
@@ -440,7 +446,7 @@ function hasRawTraversalSegment(path: string): boolean {
440
446
 
441
447
  function hashOptionalIdentifier(value: string | null): string | null {
442
448
  if (value === null || value.trim() === "") return null;
443
- return `sha256-${createHash("sha256").update(value).digest("hex").slice(0, 16)}`;
449
+ return `sha256-${sha256Short(value)}`;
444
450
  }
445
451
 
446
452
  function sanitizeHashMetadata(value: string | null): string | null {
@@ -454,8 +460,7 @@ function sanitizePersistentIdentifier(value: string, prefix: string): string {
454
460
  if (!SENSITIVE_IDENTIFIER_PATTERN.test(value) && !SENSITIVE_IDENTIFIER_PATTERN.test(pathSafe)) {
455
461
  return pathSafe;
456
462
  }
457
- const hash = createHash("sha256").update(value).digest("hex").slice(0, 16);
458
- return `${prefix}-${hash}`;
463
+ return `${prefix}-${sha256Short(value)}`;
459
464
  }
460
465
 
461
466
  function sanitizeNotes(notes: string[]): string[] {
@@ -471,12 +476,6 @@ function sanitizeNote(note: string): string {
471
476
  return truncated;
472
477
  }
473
478
 
474
- function normalizeTimestamp(value?: Date | string): string {
475
- if (value instanceof Date) return value.toISOString();
476
- if (typeof value === "string" && value.trim() !== "") return new Date(value).toISOString();
477
- return new Date().toISOString();
478
- }
479
-
480
479
  function optionalString(value: unknown): string | null {
481
480
  return typeof value === "string" && value.trim() !== "" ? value : null;
482
481
  }
@@ -8,11 +8,13 @@ export {
8
8
  } from "./registry.ts";
9
9
  export {
10
10
  type EvoDevSettings,
11
+ type EvolutionSettings,
11
12
  type MemorySettings,
12
13
  type PluginSettings,
13
14
  type SettingsInput,
14
15
  type TeamRuntimeSettings,
15
16
  createDefaultMemorySettings,
17
+ createDefaultEvolutionSettings,
16
18
  createDefaultTeamRuntimeSettings,
17
19
  createDefaultSettings,
18
20
  mergeSettings,
@@ -1,4 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import {
3
+ type SessionMemoryPolicySnapshot,
4
+ createDefaultSessionMemoryPolicy,
5
+ parseSessionMemoryPolicy,
6
+ } from "../evolution/evidence/session-memory/index.ts";
2
7
  import { type HookSettings, createDefaultHookSettings, parseHookSettings } from "../hooks/index.ts";
3
8
  import { EvoDevConfigError, describeType } from "./errors.ts";
4
9
  import { resolveEvoDevPaths } from "./paths.ts";
@@ -10,10 +15,30 @@ export interface PluginSettings {
10
15
  }
11
16
 
12
17
  export interface MemorySettings {
13
- autoAccept: boolean;
18
+ reviewKnowledgeUpdates: boolean;
14
19
  runtimeInjection: boolean;
15
20
  staleReview: boolean;
16
21
  lexicalIndex: boolean;
22
+ sessionMemory: SessionMemoryPolicySnapshot;
23
+ }
24
+
25
+ type LegacyMemorySettingsInput = Partial<MemorySettings> & {
26
+ /**
27
+ * Deprecated compatibility input. This flag was never connected to OKF activation and is
28
+ * intentionally omitted from canonical settings output.
29
+ */
30
+ autoAccept?: boolean;
31
+ };
32
+
33
+ export interface EvolutionSettings {
34
+ schedule: {
35
+ dailyTime: string;
36
+ };
37
+ automation: {
38
+ knowledge: boolean;
39
+ semanticKnowledge: boolean;
40
+ recommendations: boolean;
41
+ };
17
42
  }
18
43
 
19
44
  export interface EvoDevSettings {
@@ -39,6 +64,7 @@ export interface EvoDevSettings {
39
64
  hooks: HookSettings;
40
65
  teamRuntime: TeamRuntimeSettings;
41
66
  memory: MemorySettings;
67
+ evolution: EvolutionSettings;
42
68
  }
43
69
 
44
70
  export type SettingsInput = Partial<{
@@ -55,7 +81,11 @@ export type SettingsInput = Partial<{
55
81
  doctor: Partial<EvoDevSettings["doctor"]>;
56
82
  hooks: unknown;
57
83
  teamRuntime: Partial<TeamRuntimeSettings>;
58
- memory: Partial<MemorySettings>;
84
+ memory: LegacyMemorySettingsInput;
85
+ evolution: Partial<{
86
+ schedule: Partial<EvolutionSettings["schedule"]>;
87
+ automation: Partial<EvolutionSettings["automation"]>;
88
+ }>;
59
89
  }>;
60
90
 
61
91
  export interface TeamRuntimeSettings {
@@ -96,6 +126,7 @@ export function createDefaultSettings(os: string = process.platform): EvoDevSett
96
126
  hooks: createDefaultHookSettings(),
97
127
  teamRuntime: createDefaultTeamRuntimeSettings(),
98
128
  memory: createDefaultMemorySettings(),
129
+ evolution: createDefaultEvolutionSettings(),
99
130
  };
100
131
  }
101
132
 
@@ -111,10 +142,24 @@ export function createDefaultTeamRuntimeSettings(): TeamRuntimeSettings {
111
142
 
112
143
  export function createDefaultMemorySettings(): MemorySettings {
113
144
  return {
114
- autoAccept: true,
145
+ reviewKnowledgeUpdates: true,
115
146
  runtimeInjection: true,
116
147
  staleReview: true,
117
148
  lexicalIndex: true,
149
+ sessionMemory: createDefaultSessionMemoryPolicy(),
150
+ };
151
+ }
152
+
153
+ export function createDefaultEvolutionSettings(): EvolutionSettings {
154
+ return {
155
+ schedule: {
156
+ dailyTime: "02:00",
157
+ },
158
+ automation: {
159
+ knowledge: true,
160
+ semanticKnowledge: false,
161
+ recommendations: false,
162
+ },
118
163
  };
119
164
  }
120
165
 
@@ -162,6 +207,18 @@ export function mergeSettings(
162
207
  ...defaults.memory,
163
208
  ...existing.memory,
164
209
  },
210
+ evolution: {
211
+ ...defaults.evolution,
212
+ ...existing.evolution,
213
+ schedule: {
214
+ ...defaults.evolution.schedule,
215
+ ...existing.evolution?.schedule,
216
+ },
217
+ automation: {
218
+ ...defaults.evolution.automation,
219
+ ...existing.evolution?.automation,
220
+ },
221
+ },
165
222
  };
166
223
 
167
224
  return parseSettings(merged);
@@ -222,19 +279,64 @@ export function parseSettings(value: unknown): EvoDevSettings {
222
279
  "settings.teamRuntime",
223
280
  ),
224
281
  memory: parseMemorySettings(root.memory ?? createDefaultMemorySettings(), "settings.memory"),
282
+ evolution: parseEvolutionSettings(
283
+ root.evolution ?? createDefaultEvolutionSettings(),
284
+ "settings.evolution",
285
+ ),
225
286
  };
226
287
 
227
288
  return parsed;
228
289
  }
229
290
 
291
+ function parseEvolutionSettings(value: unknown, path: string): EvolutionSettings {
292
+ const input = expectRecord(value, path);
293
+ const defaults = createDefaultEvolutionSettings();
294
+ const schedule = expectRecord(input.schedule ?? defaults.schedule, `${path}.schedule`);
295
+ const automation = expectRecord(input.automation ?? defaults.automation, `${path}.automation`);
296
+ return {
297
+ schedule: {
298
+ dailyTime: parseDailyTime(
299
+ schedule.dailyTime ?? defaults.schedule.dailyTime,
300
+ `${path}.schedule.dailyTime`,
301
+ ),
302
+ },
303
+ automation: {
304
+ knowledge:
305
+ automation.knowledge === undefined
306
+ ? defaults.automation.knowledge
307
+ : expectBoolean(automation.knowledge, `${path}.automation.knowledge`),
308
+ semanticKnowledge:
309
+ automation.semanticKnowledge === undefined
310
+ ? defaults.automation.semanticKnowledge
311
+ : expectBoolean(automation.semanticKnowledge, `${path}.automation.semanticKnowledge`),
312
+ recommendations:
313
+ automation.recommendations === undefined
314
+ ? defaults.automation.recommendations
315
+ : expectBoolean(automation.recommendations, `${path}.automation.recommendations`),
316
+ },
317
+ };
318
+ }
319
+
320
+ function parseDailyTime(value: unknown, path: string): string {
321
+ const dailyTime = expectString(value, path);
322
+ const match = /^(\d{2}):(\d{2})$/u.exec(dailyTime);
323
+ if (match === null || Number(match[1]) > 23 || Number(match[2]) > 59) {
324
+ throw new EvoDevConfigError(`Invalid ${path}; expected a 24-hour HH:MM time`);
325
+ }
326
+ return dailyTime;
327
+ }
328
+
230
329
  function parseMemorySettings(value: unknown, path: string): MemorySettings {
231
330
  const input = expectRecord(value, path);
232
331
  const defaults = createDefaultMemorySettings();
332
+ if (input.autoAccept !== undefined) {
333
+ expectBoolean(input.autoAccept, `${path}.autoAccept`);
334
+ }
233
335
  return {
234
- autoAccept:
235
- input.autoAccept === undefined
236
- ? defaults.autoAccept
237
- : expectBoolean(input.autoAccept, `${path}.autoAccept`),
336
+ reviewKnowledgeUpdates:
337
+ input.reviewKnowledgeUpdates === undefined
338
+ ? defaults.reviewKnowledgeUpdates
339
+ : expectBoolean(input.reviewKnowledgeUpdates, `${path}.reviewKnowledgeUpdates`),
238
340
  runtimeInjection:
239
341
  input.runtimeInjection === undefined
240
342
  ? defaults.runtimeInjection
@@ -247,6 +349,9 @@ function parseMemorySettings(value: unknown, path: string): MemorySettings {
247
349
  input.lexicalIndex === undefined
248
350
  ? defaults.lexicalIndex
249
351
  : expectBoolean(input.lexicalIndex, `${path}.lexicalIndex`),
352
+ sessionMemory: parseSessionMemoryPolicy(
353
+ isPlainRecord(input.sessionMemory) ? input.sessionMemory : defaults.sessionMemory,
354
+ ),
250
355
  };
251
356
  }
252
357
 
@@ -311,6 +416,10 @@ function expectRecord(value: unknown, path: string): Record<string, unknown> {
311
416
  return value as Record<string, unknown>;
312
417
  }
313
418
 
419
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
420
+ return typeof value === "object" && value !== null && !Array.isArray(value);
421
+ }
422
+
314
423
  function isNotFoundError(error: unknown): boolean {
315
424
  return (
316
425
  error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
- import { ensureOkfKnowledgeBase } from "../knowledge/index.ts";
3
+ import { ensureOkfKnowledgeBase } from "../evolution/knowledge/index.ts";
4
4
  import { EvoDevConfigError } from "./errors.ts";
5
5
  import { type EvoDevPaths, resolveEvoDevPaths } from "./paths.ts";
6
6
  import { type EvoDevRegistry, createDefaultRegistry, parseRegistry } from "./registry.ts";