@evo-dev/core 0.0.1-alpha.3 → 0.0.1-alpha.5

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 (45) hide show
  1. package/dist/config/index.js +164 -125
  2. package/dist/index.js +8965 -7632
  3. package/package.json +1 -1
  4. package/src/agents/index.ts +1 -1
  5. package/src/code-agent-traces/index.ts +3 -10
  6. package/src/config/settings.ts +14 -0
  7. package/src/config/store.ts +1 -1
  8. package/src/daemon/index.ts +1 -1
  9. package/src/evolution/candidates/index.ts +505 -0
  10. package/src/evolution/control/index.ts +19 -0
  11. package/src/evolution/evidence/analysis.ts +529 -0
  12. package/src/evolution/evidence/index.ts +3 -0
  13. package/src/evolution/evidence/session-memory/constants.ts +12 -0
  14. package/src/evolution/evidence/session-memory/index.ts +5 -0
  15. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  16. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  17. package/src/evolution/evidence/session-memory/segment.ts +192 -0
  18. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  19. package/src/evolution/evidence/session-memory/storage.ts +266 -0
  20. package/src/evolution/evidence/session-memory/types.ts +207 -0
  21. package/src/evolution/evidence/session-memory/updater.ts +184 -0
  22. package/src/evolution/formatters.ts +169 -0
  23. package/src/evolution/index.ts +16 -2827
  24. package/src/{knowledge → evolution/knowledge}/index.ts +16 -81
  25. package/src/evolution/paths.ts +44 -0
  26. package/src/evolution/processor/distillation.ts +445 -0
  27. package/src/evolution/processor/index.ts +3 -0
  28. package/src/evolution/processor/process.ts +235 -0
  29. package/src/evolution/schema.ts +544 -0
  30. package/src/evolution/shared.ts +737 -0
  31. package/src/evolution/triggers/classification.ts +102 -0
  32. package/src/evolution/triggers/index.ts +295 -0
  33. package/src/hooks/index.ts +54 -7
  34. package/src/index.ts +10 -2
  35. package/src/runtime-logs/index.ts +4 -12
  36. package/src/team/index.ts +1 -1
  37. package/src/utils/errors.ts +13 -0
  38. package/src/utils/fs.ts +40 -0
  39. package/src/utils/hash.ts +9 -0
  40. package/src/utils/ids.ts +12 -0
  41. package/src/utils/index.ts +7 -0
  42. package/src/utils/parsing.ts +11 -0
  43. package/src/utils/text.ts +18 -0
  44. package/src/utils/time.ts +5 -0
  45. /package/src/{learning → evolution/review}/index.ts +0 -0
@@ -2,13 +2,13 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
- import { resolveEvoDevPaths } from "../config/paths.ts";
6
- import {
7
- type EvolutionDistillationBatch,
8
- type EvolutionEvosCase,
9
- type EvolutionKnowledgeRecord,
10
- listEvolutionEvosCases,
11
- } from "../evolution/index.ts";
5
+ import { resolveEvoDevPaths } from "../../config/paths.ts";
6
+ import { listEvolutionEvosCases } from "../candidates/index.ts";
7
+ import type {
8
+ EvolutionDistillationBatch,
9
+ EvolutionEvosCase,
10
+ EvolutionKnowledgeRecord,
11
+ } from "../schema.ts";
12
12
 
13
13
  export type OkfKnowledgeDecision =
14
14
  | "auto-accept"
@@ -1656,10 +1656,10 @@ export function decideOkfKnowledgeCandidate(
1656
1656
  if (highRiskRequiresHuman) {
1657
1657
  return {
1658
1658
  ...sanitized,
1659
- decision: "needs-human",
1659
+ decision: "no_write",
1660
1660
  scores,
1661
- reviewState: "needs-human",
1662
- decisionReason: "Needs human review: high-risk domain requires review.",
1661
+ reviewState: "auto-stored/unreviewed",
1662
+ decisionReason: "No write: high-risk knowledge is not eligible for automatic activation.",
1663
1663
  };
1664
1664
  }
1665
1665
 
@@ -1677,12 +1677,12 @@ export function decideOkfKnowledgeCandidate(
1677
1677
  if (reusable) {
1678
1678
  return {
1679
1679
  ...sanitized,
1680
- decision: "needs-human",
1680
+ decision: "no_write",
1681
1681
  scores,
1682
- reviewState: "needs-human",
1682
+ reviewState: "auto-stored/unreviewed",
1683
1683
  decisionReason:
1684
1684
  conflict?.reason ??
1685
- explainNeedsHumanDecision(sanitized, scores, hasVerification, hasScopeTags),
1685
+ explainAutomaticNoWriteDecision(sanitized, scores, hasVerification, hasScopeTags),
1686
1686
  };
1687
1687
  }
1688
1688
 
@@ -1746,12 +1746,7 @@ export async function activateOkfKnowledgePlan(input: {
1746
1746
  continue;
1747
1747
  }
1748
1748
  if (candidate.decision === "needs-human") {
1749
- await writeNeedsHumanKnowledgeCandidate({
1750
- homeDir: input.homeDir,
1751
- plan: input.plan,
1752
- candidate,
1753
- });
1754
- needsHumanCandidates.push(candidate.id);
1749
+ skippedCandidates.push(candidate.id);
1755
1750
  continue;
1756
1751
  }
1757
1752
  if (candidate.targetStore !== "okf") {
@@ -3165,66 +3160,6 @@ function createOverlayUpdates(
3165
3160
  ];
3166
3161
  }
3167
3162
 
3168
- async function writeNeedsHumanKnowledgeCandidate(input: {
3169
- homeDir: string;
3170
- plan: OkfKnowledgePlan;
3171
- candidate: OkfKnowledgePlanCandidate;
3172
- }): Promise<string> {
3173
- const root = join(resolveEvoDevPaths(input.homeDir).stateDir, "evolution");
3174
- const targetDir = join(
3175
- root,
3176
- sanitizePlanStorageId("projectKey", input.plan.projectKey),
3177
- sanitizePlanStorageId("runId", input.plan.runId),
3178
- "review-candidates",
3179
- );
3180
- const targetPath = join(
3181
- targetDir,
3182
- `${sanitizePlanStorageId("candidateId", input.candidate.id)}.json`,
3183
- );
3184
- assertPathDescendant(root, targetPath, "reviewCandidatePath");
3185
- const candidateSnapshot = sanitizeReviewQueueValue(input.candidate);
3186
- await writeJson(
3187
- targetPath,
3188
- {
3189
- schemaVersion: 1,
3190
- kind: "evolution-review-candidate",
3191
- id: input.candidate.id,
3192
- projectKey: input.plan.projectKey,
3193
- runId: input.plan.runId,
3194
- createdAt: input.plan.createdAt,
3195
- candidateKind: input.candidate.kind,
3196
- title: sanitizeOkfText(input.candidate.title),
3197
- targetStore: input.candidate.targetStore,
3198
- targetPath: sanitizeOkfText(input.candidate.targetPath),
3199
- stableKey: sanitizeOkfText(input.candidate.stableKey),
3200
- reviewState: "needs-human",
3201
- reasons: [sanitizeOkfText(input.candidate.decisionReason)],
3202
- candidate: candidateSnapshot,
3203
- provenance: {
3204
- runId: input.plan.runId,
3205
- evidenceWindowId: input.plan.evidenceWindowId,
3206
- evidenceRefs: input.candidate.evidenceRefs.map(sanitizeOkfText),
3207
- createdBy: "evodev",
3208
- rawLogsStored: false,
3209
- rawPromptsStored: false,
3210
- sourceDumpsStored: false,
3211
- rawCommandOutputStored: false,
3212
- },
3213
- privacy: {
3214
- classification: "local-private",
3215
- rawPromptsStored: false,
3216
- rawLogsStored: false,
3217
- sourceDumpsStored: false,
3218
- rawCommandOutputStored: false,
3219
- secretsStored: false,
3220
- internalLinksStored: false,
3221
- },
3222
- },
3223
- { overwrite: true },
3224
- );
3225
- return targetPath;
3226
- }
3227
-
3228
3163
  function sanitizeOkfPlanCandidate(candidate: OkfKnowledgePlanCandidate): OkfKnowledgePlanCandidate {
3229
3164
  return sanitizeReviewQueueValue(candidate) as OkfKnowledgePlanCandidate;
3230
3165
  }
@@ -3292,7 +3227,7 @@ function hasHighRiskHumanReviewSignal(candidate: unknown): boolean {
3292
3227
  return HUMAN_REVIEW_DOMAIN_PATTERN.test(values.join(" "));
3293
3228
  }
3294
3229
 
3295
- function explainNeedsHumanDecision(
3230
+ function explainAutomaticNoWriteDecision(
3296
3231
  candidate: OkfKnowledgePlanCandidate,
3297
3232
  scores: OkfKnowledgeCandidateScores,
3298
3233
  hasVerification: boolean,
@@ -3308,7 +3243,7 @@ function explainNeedsHumanDecision(
3308
3243
  if (scores.privacyRisk > 2) reasons.push("privacy risk is above auto-accept");
3309
3244
  if (scores.stability < 3) reasons.push("stability is below auto-accept");
3310
3245
  if (scores.duplicationRisk > 2) reasons.push("duplication risk is above auto-accept");
3311
- return `Needs human review: ${reasons.join("; ") || "auto-accept gates were not all satisfied"}.`;
3246
+ return `No write: ${reasons.join("; ") || "auto-accept gates were not all satisfied"}.`;
3312
3247
  }
3313
3248
 
3314
3249
  function resolveCandidateReviewStateForWrite(
@@ -0,0 +1,44 @@
1
+ import { join } from "node:path";
2
+ import { resolveEvoDevPaths } from "../config/paths.ts";
3
+ import type { EvolutionResolvedPaths } from "./schema.ts";
4
+ import { assertPathDescendant, sanitizeStorageId } from "./shared.ts";
5
+
6
+ export function resolveEvolutionPaths(input: {
7
+ homeDir: string;
8
+ projectKey: string;
9
+ runId: string;
10
+ }): EvolutionResolvedPaths {
11
+ const paths = resolveEvoDevPaths(input.homeDir);
12
+ const projectKey = sanitizeStorageId("projectKey", input.projectKey);
13
+ const runId = sanitizeStorageId("runId", input.runId);
14
+ const evolutionStateDir = join(paths.stateDir, "evolution");
15
+ const projectStateDir = join(evolutionStateDir, projectKey);
16
+ const runStateDir = join(evolutionStateDir, projectKey, runId);
17
+ const repoProposalsDir = join(runStateDir, "proposals");
18
+ const reviewCandidatesDir = join(runStateDir, "review-candidates");
19
+ const knowledgeProjectDir = join(paths.knowledgeDir, projectKey);
20
+ const evosCasesProjectDir = join(paths.evosCasesDir, projectKey);
21
+ assertPathDescendant(evolutionStateDir, runStateDir, "runStateDir");
22
+ assertPathDescendant(evolutionStateDir, projectStateDir, "projectStateDir");
23
+ assertPathDescendant(evolutionStateDir, reviewCandidatesDir, "reviewCandidatesDir");
24
+ assertPathDescendant(paths.knowledgeDir, knowledgeProjectDir, "knowledgeProjectDir");
25
+ assertPathDescendant(paths.evosCasesDir, evosCasesProjectDir, "evosCasesProjectDir");
26
+ return {
27
+ projectKey,
28
+ runId,
29
+ evolutionStateDir,
30
+ runStateDir,
31
+ evidenceWindowPath: join(runStateDir, "evidence-window.json"),
32
+ batchPath: join(runStateDir, "distillation-batch.json"),
33
+ triggersDir: join(runStateDir, "triggers"),
34
+ reviewCandidatesDir,
35
+ repoProposalsDir,
36
+ repoProposalsIndexPath: join(runStateDir, "proposals.index.json"),
37
+ knowledgeProjectDir,
38
+ knowledgeRecordsDir: join(knowledgeProjectDir, "records"),
39
+ knowledgeIndexPath: join(knowledgeProjectDir, "index.json"),
40
+ knowledgeReviewHistoryPath: join(projectStateDir, "knowledge-review-history.jsonl"),
41
+ evosCasesProjectDir,
42
+ evosIndexPath: paths.evosIndexPath,
43
+ };
44
+ }
@@ -0,0 +1,445 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { resolveEvoDevPaths } from "../../config/paths.ts";
4
+ import {
5
+ createStableId,
6
+ normalizeTimestamp,
7
+ readJsonFiles,
8
+ writeJsonFile as writeJson,
9
+ } from "../../utils/index.ts";
10
+ import {
11
+ activateOkfKnowledgePlan,
12
+ createOkfKnowledgePlanFromDistillationBatch,
13
+ } from "../knowledge/index.ts";
14
+ import { resolveEvolutionPaths } from "../paths.ts";
15
+ import type {
16
+ EvolutionActivationResult,
17
+ EvolutionConfidence,
18
+ EvolutionDistillationBatch,
19
+ EvolutionEvidenceWindow,
20
+ EvolutionEvosCase,
21
+ EvolutionKnowledgeRecord,
22
+ EvolutionRepoProposal,
23
+ EvolutionWriteResult,
24
+ } from "../schema.ts";
25
+ import {
26
+ collectRoleIds,
27
+ createEvolutionEvosCase,
28
+ createEvolutionKnowledgeRecord,
29
+ createPrivacyFields,
30
+ hasConcreteRepoProposalChanges,
31
+ listDirectoryNames,
32
+ parseEvosCase,
33
+ parseKnowledgeRecord,
34
+ parseRepoProposal,
35
+ sanitizeText,
36
+ validateEvolutionDistillationBatch,
37
+ validateEvolutionEvidenceWindow,
38
+ validateEvolutionEvosCase,
39
+ validateEvolutionRepoProposal,
40
+ } from "../shared.ts";
41
+ import { chooseEvosTriggerKind, createTriggerSummary } from "../triggers/classification.ts";
42
+
43
+ export function createEvolutionDistillationBatch(input: {
44
+ evidenceWindow: EvolutionEvidenceWindow;
45
+ now?: string | Date;
46
+ warnings?: string[];
47
+ }): EvolutionDistillationBatch {
48
+ validateEvolutionEvidenceWindow(input.evidenceWindow);
49
+ const createdAt = normalizeTimestamp(input.now);
50
+ const roleIds = collectRoleIds(input.evidenceWindow);
51
+ const sourceRefs = input.evidenceWindow.sourceRefs.map((sourceRef) => sourceRef.id);
52
+ const failedSignals = input.evidenceWindow.events.filter(
53
+ (event) =>
54
+ event.kind === "error" ||
55
+ event.triggerReason === "stop-failure" ||
56
+ event.triggerReason === "tool-failure" ||
57
+ event.triggerReason === "permission-denied" ||
58
+ /fail|failed|failure|error|issue/i.test(event.summary),
59
+ );
60
+ const verificationSignals = input.evidenceWindow.events
61
+ .filter(
62
+ (event) => event.kind === "verification" || /test|lint|typecheck|build/i.test(event.summary),
63
+ )
64
+ .slice(0, 8)
65
+ .map((event) => event.summary);
66
+ const autoKnowledgeEligible =
67
+ input.evidenceWindow.events.length > 0 &&
68
+ failedSignals.length === 0 &&
69
+ verificationSignals.length > 0 &&
70
+ input.evidenceWindow.triggerPolicy.distillRecommended &&
71
+ input.evidenceWindow.triggerPolicy.evidenceSignals.failures === 0;
72
+ const confidence: EvolutionConfidence = autoKnowledgeEligible
73
+ ? "high"
74
+ : failedSignals.length === 0
75
+ ? "medium"
76
+ : "low";
77
+
78
+ const knowledgeRecords = !autoKnowledgeEligible
79
+ ? []
80
+ : [
81
+ createEvolutionKnowledgeRecord({
82
+ id: createStableId("k", [input.evidenceWindow.id, "run-summary"]),
83
+ kind: "run-summary",
84
+ title: `Project run ${input.evidenceWindow.runId} evidence summary`,
85
+ projectKey: input.evidenceWindow.projectKey,
86
+ summary: createRunSummary(input.evidenceWindow, roleIds, failedSignals.length),
87
+ body: [
88
+ `Run ${input.evidenceWindow.runId} produced ${input.evidenceWindow.events.length} redacted metadata evidence event(s).`,
89
+ `Observed role(s): ${roleIds.length === 0 ? "none" : roleIds.join(", ")}.`,
90
+ "This record is derived from metadata-only evidence; active use depends on OKF plan review state.",
91
+ ].join("\n"),
92
+ roleTags: roleIds,
93
+ tags: ["project-run", "auto-stored", "unreviewed"],
94
+ reviewState: "auto-stored/unreviewed",
95
+ authority: "contextual",
96
+ confidence,
97
+ provenance: createProvenance(input.evidenceWindow, createdAt, sourceRefs),
98
+ relations: {
99
+ relatedKnowledgeIds: [],
100
+ evosCaseIds: [createStableId("evo", [input.evidenceWindow.id, "case"])],
101
+ proposalIds: [],
102
+ supersedes: [],
103
+ },
104
+ privacy: createPrivacyFields(),
105
+ runtime: { canLoad: true, hardBlocking: false },
106
+ }),
107
+ ];
108
+
109
+ const evosCases =
110
+ input.evidenceWindow.events.length === 0
111
+ ? []
112
+ : [
113
+ createEvolutionEvosCase({
114
+ id: createStableId("evo", [input.evidenceWindow.id, "case"]),
115
+ projectKey: input.evidenceWindow.projectKey,
116
+ title: `Run ${input.evidenceWindow.runId} evolution case`,
117
+ roleTags: roleIds,
118
+ tags: [
119
+ "project-run",
120
+ "evidence-window",
121
+ `trigger-${input.evidenceWindow.triggerPolicy.strongest}`,
122
+ ],
123
+ reviewState: "auto-stored/unreviewed",
124
+ confidence,
125
+ trigger: {
126
+ kind: chooseEvosTriggerKind(input.evidenceWindow),
127
+ summary: createTriggerSummary(input.evidenceWindow),
128
+ },
129
+ intervention: {
130
+ summary: `Collected ${input.evidenceWindow.events.length} metadata event(s) and ${input.evidenceWindow.episodes.length} episode(s) from EvoDev execution logs.`,
131
+ roleIds,
132
+ },
133
+ result: {
134
+ summary:
135
+ failedSignals.length === 0
136
+ ? "No failure signal was detected in the redacted evidence summaries."
137
+ : `${failedSignals.length} potential failure or issue signal(s) were detected.`,
138
+ verificationSignals,
139
+ },
140
+ expectedFutureBehavior:
141
+ "Future role agents should inspect accepted knowledge first and treat unreviewed cases as contextual evidence only.",
142
+ provenance: createProvenance(input.evidenceWindow, createdAt, sourceRefs),
143
+ privacy: createPrivacyFields(),
144
+ }),
145
+ ];
146
+
147
+ // Failure metadata is evidence, not a repository change proposal. A proposal is only valid
148
+ // after another stage supplies concrete target files and proposed changes.
149
+ const repoProposals: EvolutionRepoProposal[] = [];
150
+
151
+ const batch: EvolutionDistillationBatch = {
152
+ schemaVersion: 1,
153
+ id: createStableId("batch", [input.evidenceWindow.id, createdAt]),
154
+ projectKey: input.evidenceWindow.projectKey,
155
+ runId: input.evidenceWindow.runId,
156
+ createdAt,
157
+ evidenceWindow: input.evidenceWindow,
158
+ knowledgeRecords,
159
+ evosCases,
160
+ repoProposals,
161
+ warnings: (input.warnings ?? []).map(sanitizeText),
162
+ };
163
+ validateEvolutionDistillationBatch(batch);
164
+ return batch;
165
+ }
166
+
167
+ export async function writeEvolutionDistillationBatch(input: {
168
+ homeDir: string;
169
+ batch: EvolutionDistillationBatch;
170
+ overwrite?: boolean;
171
+ }): Promise<EvolutionWriteResult> {
172
+ validateEvolutionDistillationBatch(input.batch);
173
+ const paths = resolveEvolutionPaths({
174
+ homeDir: input.homeDir,
175
+ projectKey: input.batch.projectKey,
176
+ runId: input.batch.runId,
177
+ });
178
+ const overwrite = input.overwrite === true;
179
+ await writeJson(paths.evidenceWindowPath, input.batch.evidenceWindow, { overwrite });
180
+ await writeJson(paths.batchPath, input.batch, { overwrite });
181
+
182
+ const knowledgePaths: string[] = [];
183
+ for (const record of input.batch.knowledgeRecords) {
184
+ const path = join(paths.knowledgeRecordsDir, `${record.id}.json`);
185
+ await writeJson(path, record, { overwrite });
186
+ knowledgePaths.push(path);
187
+ }
188
+
189
+ const evosCasePaths: string[] = [];
190
+ for (const evosCase of input.batch.evosCases) {
191
+ const path = join(paths.evosCasesProjectDir, `${evosCase.id}.json`);
192
+ await writeJson(path, evosCase, { overwrite });
193
+ evosCasePaths.push(path);
194
+ }
195
+
196
+ const repoProposalPaths: string[] = [];
197
+ for (const proposal of input.batch.repoProposals) {
198
+ const path = join(paths.repoProposalsDir, `${proposal.id}.json`);
199
+ await writeJson(path, proposal, { overwrite });
200
+ repoProposalPaths.push(path);
201
+ }
202
+
203
+ const allProjectKnowledgeRecords = await readJsonFiles(
204
+ paths.knowledgeRecordsDir,
205
+ parseKnowledgeRecord,
206
+ );
207
+ await writeJson(
208
+ paths.knowledgeIndexPath,
209
+ {
210
+ schemaVersion: 1,
211
+ projectKey: input.batch.projectKey,
212
+ updatedAt: input.batch.createdAt,
213
+ records: allProjectKnowledgeRecords.map((record) => ({
214
+ id: record.id,
215
+ kind: record.kind,
216
+ title: record.title,
217
+ reviewState: record.reviewState,
218
+ roleTags: record.roleTags,
219
+ })),
220
+ },
221
+ { overwrite: true },
222
+ );
223
+
224
+ const evosCasesRootDir = resolveEvoDevPaths(input.homeDir).evosCasesDir;
225
+ const evosProjectKeys = await listDirectoryNames(evosCasesRootDir);
226
+ const evosProjects = await Promise.all(
227
+ evosProjectKeys.map(async (projectKey) => {
228
+ const cases = await readJsonFiles(join(evosCasesRootDir, projectKey), parseEvosCase);
229
+ return {
230
+ projectKey,
231
+ cases: cases.map((evosCase) => ({
232
+ id: evosCase.id,
233
+ title: evosCase.title,
234
+ reviewState: evosCase.reviewState,
235
+ })),
236
+ };
237
+ }),
238
+ );
239
+ await writeJson(
240
+ paths.evosIndexPath,
241
+ {
242
+ schemaVersion: 1,
243
+ updatedAt: input.batch.createdAt,
244
+ projects: evosProjects,
245
+ },
246
+ { overwrite: true },
247
+ );
248
+
249
+ const allRunProposals = await readJsonFiles(paths.repoProposalsDir, parseRepoProposal);
250
+ await writeJson(
251
+ paths.repoProposalsIndexPath,
252
+ {
253
+ schemaVersion: 1,
254
+ projectKey: input.batch.projectKey,
255
+ runId: input.batch.runId,
256
+ updatedAt: input.batch.createdAt,
257
+ proposals: allRunProposals.map((proposal) => ({
258
+ id: proposal.id,
259
+ kind: proposal.kind,
260
+ title: proposal.title,
261
+ reviewState: proposal.reviewState,
262
+ })),
263
+ },
264
+ { overwrite: true },
265
+ );
266
+
267
+ return {
268
+ evidenceWindowPath: paths.evidenceWindowPath,
269
+ batchPath: paths.batchPath,
270
+ knowledgePaths,
271
+ evosCasePaths,
272
+ repoProposalPaths,
273
+ knowledgeIndexPath: paths.knowledgeIndexPath,
274
+ evosIndexPath: paths.evosIndexPath,
275
+ };
276
+ }
277
+
278
+ export async function writeEvolutionRepoProposal(input: {
279
+ homeDir: string;
280
+ proposal: EvolutionRepoProposal;
281
+ }): Promise<{ proposalPath: string; indexPath: string }> {
282
+ validateEvolutionRepoProposal(input.proposal);
283
+ if (!hasConcreteRepoProposalChanges(input.proposal)) {
284
+ throw new Error("Repo proposal must include a target repository and concrete file changes.");
285
+ }
286
+ const paths = resolveEvolutionPaths({
287
+ homeDir: input.homeDir,
288
+ projectKey: input.proposal.projectKey,
289
+ runId: input.proposal.provenance.runId,
290
+ });
291
+ const proposalPath = join(paths.repoProposalsDir, `${input.proposal.id}.json`);
292
+ await writeJson(proposalPath, input.proposal, { overwrite: false });
293
+
294
+ const allRunProposals = await readJsonFiles(paths.repoProposalsDir, parseRepoProposal);
295
+ await writeJson(
296
+ paths.repoProposalsIndexPath,
297
+ {
298
+ schemaVersion: 1,
299
+ projectKey: input.proposal.projectKey,
300
+ runId: input.proposal.provenance.runId,
301
+ updatedAt: input.proposal.provenance.createdAt,
302
+ proposals: allRunProposals.map((proposal) => ({
303
+ id: proposal.id,
304
+ kind: proposal.kind,
305
+ title: proposal.title,
306
+ reviewState: proposal.reviewState,
307
+ })),
308
+ },
309
+ { overwrite: true },
310
+ );
311
+ return { proposalPath, indexPath: paths.repoProposalsIndexPath };
312
+ }
313
+
314
+ export async function activateEvolutionDistillationBatch(input: {
315
+ homeDir: string;
316
+ batch: EvolutionDistillationBatch;
317
+ overwrite?: boolean;
318
+ }): Promise<EvolutionActivationResult> {
319
+ validateEvolutionDistillationBatch(input.batch);
320
+ const paths = resolveEvolutionPaths({
321
+ homeDir: input.homeDir,
322
+ projectKey: input.batch.projectKey,
323
+ runId: input.batch.runId,
324
+ });
325
+ const overwrite = input.overwrite === true;
326
+ await writeJson(paths.evidenceWindowPath, input.batch.evidenceWindow, { overwrite });
327
+ await writeJson(paths.batchPath, input.batch, { overwrite });
328
+
329
+ const plan = createOkfKnowledgePlanFromDistillationBatch(input.batch, {
330
+ homeDir: input.homeDir,
331
+ });
332
+ const okf = await activateOkfKnowledgePlan({
333
+ homeDir: input.homeDir,
334
+ plan,
335
+ overwrite,
336
+ evidenceWindowPath: paths.evidenceWindowPath,
337
+ });
338
+
339
+ const evosCasePaths: string[] = [];
340
+ for (const evosCase of input.batch.evosCases) {
341
+ const matchingCandidate = plan.candidates.find((candidate) => candidate.id === evosCase.id);
342
+ const persistedEvosCase =
343
+ matchingCandidate?.decision === "auto-accept"
344
+ ? { ...evosCase, reviewState: "auto-accepted" as const }
345
+ : matchingCandidate?.decision === "needs-human"
346
+ ? { ...evosCase, reviewState: "needs-human" as const }
347
+ : evosCase;
348
+ validateEvolutionEvosCase(persistedEvosCase);
349
+ const path = join(paths.evosCasesProjectDir, `${evosCase.id}.json`);
350
+ await writeJson(path, persistedEvosCase, { overwrite });
351
+ evosCasePaths.push(path);
352
+ }
353
+
354
+ const repoProposalPaths: string[] = [];
355
+ for (const proposal of input.batch.repoProposals) {
356
+ const path = join(paths.repoProposalsDir, `${proposal.id}.json`);
357
+ await writeJson(path, proposal, { overwrite });
358
+ repoProposalPaths.push(path);
359
+ }
360
+
361
+ const evosCasesRootDir = resolveEvoDevPaths(input.homeDir).evosCasesDir;
362
+ const evosProjectKeys = await listDirectoryNames(evosCasesRootDir);
363
+ const evosProjects = await Promise.all(
364
+ evosProjectKeys.map(async (projectKey) => {
365
+ const cases = await readJsonFiles(join(evosCasesRootDir, projectKey), parseEvosCase);
366
+ return {
367
+ projectKey,
368
+ cases: cases.map((evosCase) => ({
369
+ id: evosCase.id,
370
+ title: evosCase.title,
371
+ reviewState: evosCase.reviewState,
372
+ })),
373
+ };
374
+ }),
375
+ );
376
+ await writeJson(
377
+ paths.evosIndexPath,
378
+ {
379
+ schemaVersion: 1,
380
+ updatedAt: input.batch.createdAt,
381
+ projects: evosProjects,
382
+ },
383
+ { overwrite: true },
384
+ );
385
+
386
+ const allRunProposals = await readJsonFiles(paths.repoProposalsDir, parseRepoProposal);
387
+ await writeJson(
388
+ paths.repoProposalsIndexPath,
389
+ {
390
+ schemaVersion: 1,
391
+ projectKey: input.batch.projectKey,
392
+ runId: input.batch.runId,
393
+ updatedAt: input.batch.createdAt,
394
+ proposals: allRunProposals.map((proposal) => ({
395
+ id: proposal.id,
396
+ kind: proposal.kind,
397
+ title: proposal.title,
398
+ reviewState: proposal.reviewState,
399
+ })),
400
+ },
401
+ { overwrite: true },
402
+ );
403
+
404
+ return {
405
+ evidenceWindowPath: paths.evidenceWindowPath,
406
+ batchPath: paths.batchPath,
407
+ okf,
408
+ evosCasePaths,
409
+ repoProposalPaths,
410
+ evosIndexPath: paths.evosIndexPath,
411
+ };
412
+ }
413
+
414
+ function createRunSummary(
415
+ evidenceWindow: EvolutionEvidenceWindow,
416
+ roleIds: string[],
417
+ failedSignalCount: number,
418
+ ): string {
419
+ const failureClause =
420
+ failedSignalCount === 0
421
+ ? "No failure signal was detected in metadata summaries."
422
+ : `${failedSignalCount} potential failure or issue signal(s) were detected.`;
423
+ return sanitizeText(
424
+ `Run ${evidenceWindow.runId} produced ${evidenceWindow.events.length} redacted event(s) across ${roleIds.length} role(s). ${failureClause}`,
425
+ );
426
+ }
427
+
428
+ function createProvenance(
429
+ evidenceWindow: EvolutionEvidenceWindow,
430
+ createdAt: string,
431
+ sourceRefs: string[],
432
+ ): EvolutionKnowledgeRecord["provenance"] {
433
+ return {
434
+ runId: evidenceWindow.runId,
435
+ taskId: evidenceWindow.taskId,
436
+ evidenceWindowId: evidenceWindow.id,
437
+ sourceRefs,
438
+ createdAt,
439
+ createdBy: "evodev",
440
+ rawLogsStored: false,
441
+ rawPromptsStored: false,
442
+ sourceDumpsStored: false,
443
+ rawCommandOutputStored: false,
444
+ };
445
+ }
@@ -0,0 +1,3 @@
1
+ export { analyzeEvolutionRun } from "../evidence/analysis.ts";
2
+ export * from "./distillation.ts";
3
+ export * from "./process.ts";