@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.
- package/dist/config/index.js +164 -125
- package/dist/index.js +8965 -7632
- package/package.json +1 -1
- package/src/agents/index.ts +1 -1
- package/src/code-agent-traces/index.ts +3 -10
- package/src/config/settings.ts +14 -0
- package/src/config/store.ts +1 -1
- package/src/daemon/index.ts +1 -1
- package/src/evolution/candidates/index.ts +505 -0
- package/src/evolution/control/index.ts +19 -0
- package/src/evolution/evidence/analysis.ts +529 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/constants.ts +12 -0
- package/src/evolution/evidence/session-memory/index.ts +5 -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 +192 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +266 -0
- package/src/evolution/evidence/session-memory/types.ts +207 -0
- package/src/evolution/evidence/session-memory/updater.ts +184 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -2827
- package/src/{knowledge → evolution/knowledge}/index.ts +16 -81
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +445 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +235 -0
- package/src/evolution/schema.ts +544 -0
- package/src/evolution/shared.ts +737 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +54 -7
- package/src/index.ts +10 -2
- package/src/runtime-logs/index.ts +4 -12
- package/src/team/index.ts +1 -1
- 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/{learning → evolution/review}/index.ts +0 -0
package/src/evolution/index.ts
CHANGED
|
@@ -1,2827 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| "task-contract"
|
|
18
|
-
| "verification"
|
|
19
|
-
| "review-summary"
|
|
20
|
-
| "user-feedback"
|
|
21
|
-
| "code-agent-trace-ref";
|
|
22
|
-
|
|
23
|
-
export type EvolutionEvidenceEventKind =
|
|
24
|
-
| "trace"
|
|
25
|
-
| "tool-call"
|
|
26
|
-
| "skill-call"
|
|
27
|
-
| "subagent"
|
|
28
|
-
| "verification"
|
|
29
|
-
| "review"
|
|
30
|
-
| "user-feedback"
|
|
31
|
-
| "error"
|
|
32
|
-
| "run-state";
|
|
33
|
-
|
|
34
|
-
export type EvolutionNormalizedEventType =
|
|
35
|
-
| "SessionStart"
|
|
36
|
-
| "UserPromptSubmit"
|
|
37
|
-
| "UserPromptExpansion"
|
|
38
|
-
| "PreToolUse"
|
|
39
|
-
| "PermissionRequest"
|
|
40
|
-
| "PostToolUse"
|
|
41
|
-
| "PostToolUseFailure"
|
|
42
|
-
| "PostToolBatch"
|
|
43
|
-
| "PermissionDenied"
|
|
44
|
-
| "SubagentStart"
|
|
45
|
-
| "Stop"
|
|
46
|
-
| "StopFailure"
|
|
47
|
-
| "TeammateIdle"
|
|
48
|
-
| "SubagentStop"
|
|
49
|
-
| "TaskCreated"
|
|
50
|
-
| "TaskCompleted"
|
|
51
|
-
| "PreCompact"
|
|
52
|
-
| "PostCompact"
|
|
53
|
-
| "SessionEnd"
|
|
54
|
-
| "ConfigChange"
|
|
55
|
-
| "CwdChanged"
|
|
56
|
-
| "FileChanged"
|
|
57
|
-
| "WorktreeCreate"
|
|
58
|
-
| "WorktreeRemove"
|
|
59
|
-
| "unknown";
|
|
60
|
-
|
|
61
|
-
export type EvolutionTriggerStrength = "none" | "conditional" | "strong";
|
|
62
|
-
export type EvolutionTriggerReason =
|
|
63
|
-
| "none"
|
|
64
|
-
| "turn-completed"
|
|
65
|
-
| "task-completed"
|
|
66
|
-
| "session-ended"
|
|
67
|
-
| "role-completed"
|
|
68
|
-
| "stop-failure"
|
|
69
|
-
| "tool-failure"
|
|
70
|
-
| "permission-denied"
|
|
71
|
-
| "explicit-command"
|
|
72
|
-
| "failure-signal";
|
|
73
|
-
|
|
74
|
-
export type EvolutionEpisodeKind = "session" | "turn" | "task" | "role";
|
|
75
|
-
export type EvolutionEpisodeStatus = "open" | "closed" | "failed";
|
|
76
|
-
export type EvolutionTriggerStatus = "pending" | "processing" | "consumed" | "failed" | "skipped";
|
|
77
|
-
|
|
78
|
-
export type EvolutionKnowledgeKind =
|
|
79
|
-
| "run-summary"
|
|
80
|
-
| "lesson"
|
|
81
|
-
| "rule"
|
|
82
|
-
| "workflow-hint"
|
|
83
|
-
| "skill-gap"
|
|
84
|
-
| "role-note"
|
|
85
|
-
| "verification-pattern";
|
|
86
|
-
|
|
87
|
-
export type EvolutionReviewState =
|
|
88
|
-
| "auto-accepted"
|
|
89
|
-
| "auto-stored/unreviewed"
|
|
90
|
-
| "needs-human"
|
|
91
|
-
| "accepted"
|
|
92
|
-
| "rejected"
|
|
93
|
-
| "deferred"
|
|
94
|
-
| "stale"
|
|
95
|
-
| "deprecated"
|
|
96
|
-
| "superseded"
|
|
97
|
-
| "revoked";
|
|
98
|
-
|
|
99
|
-
export type EvolutionAuthority = "contextual" | "reviewed";
|
|
100
|
-
export type EvolutionConfidence = "low" | "medium" | "high";
|
|
101
|
-
|
|
102
|
-
export type EvolutionProposalKind =
|
|
103
|
-
| "skill"
|
|
104
|
-
| "rule"
|
|
105
|
-
| "role-agent"
|
|
106
|
-
| "team"
|
|
107
|
-
| "ci"
|
|
108
|
-
| "test"
|
|
109
|
-
| "docs"
|
|
110
|
-
| "engineering-practice";
|
|
111
|
-
|
|
112
|
-
export interface EvolutionPrivacyFields {
|
|
113
|
-
classification: "local-private";
|
|
114
|
-
rawPromptsStored: false;
|
|
115
|
-
rawLogsStored: false;
|
|
116
|
-
sourceDumpsStored: false;
|
|
117
|
-
rawCommandOutputStored: false;
|
|
118
|
-
secretsStored: false;
|
|
119
|
-
internalLinksStored: false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface EvolutionEvidenceWindow {
|
|
123
|
-
schemaVersion: 1;
|
|
124
|
-
id: string;
|
|
125
|
-
kind: "evidence-window";
|
|
126
|
-
projectKey: string;
|
|
127
|
-
runId: string;
|
|
128
|
-
taskId: string | null;
|
|
129
|
-
createdAt: string;
|
|
130
|
-
sourceRefs: EvolutionEvidenceSourceRef[];
|
|
131
|
-
events: EvolutionEvidenceEvent[];
|
|
132
|
-
episodes: EvolutionEpisode[];
|
|
133
|
-
triggerPolicy: EvolutionTriggerPolicy;
|
|
134
|
-
privacy: EvolutionPrivacyFields;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface EvolutionEvidenceSourceRef {
|
|
138
|
-
id: string;
|
|
139
|
-
kind: EvolutionEvidenceSourceKind;
|
|
140
|
-
path: string;
|
|
141
|
-
roleId: string | null;
|
|
142
|
-
rawContentStored: false;
|
|
143
|
-
externalContentCopied: false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface EvolutionEvidenceEvent {
|
|
147
|
-
id: string;
|
|
148
|
-
kind: EvolutionEvidenceEventKind;
|
|
149
|
-
eventType: EvolutionNormalizedEventType;
|
|
150
|
-
hookEventId: string | null;
|
|
151
|
-
occurredAt: string | null;
|
|
152
|
-
summary: string;
|
|
153
|
-
roleId: string | null;
|
|
154
|
-
taskId: string | null;
|
|
155
|
-
evidenceRef: string;
|
|
156
|
-
episodeIds: string[];
|
|
157
|
-
triggerStrength: EvolutionTriggerStrength;
|
|
158
|
-
triggerReason: EvolutionTriggerReason;
|
|
159
|
-
rawContentStored: false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export interface EvolutionEpisode {
|
|
163
|
-
id: string;
|
|
164
|
-
kind: EvolutionEpisodeKind;
|
|
165
|
-
status: EvolutionEpisodeStatus;
|
|
166
|
-
roleId: string | null;
|
|
167
|
-
taskId: string | null;
|
|
168
|
-
startedAt: string | null;
|
|
169
|
-
endedAt: string | null;
|
|
170
|
-
eventIds: string[];
|
|
171
|
-
triggerStrength: EvolutionTriggerStrength;
|
|
172
|
-
triggerReason: EvolutionTriggerReason;
|
|
173
|
-
summary: string;
|
|
174
|
-
rawContentStored: false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export interface EvolutionTriggerPolicy {
|
|
178
|
-
strongest: EvolutionTriggerStrength;
|
|
179
|
-
reasons: EvolutionTriggerReason[];
|
|
180
|
-
distillRecommended: boolean;
|
|
181
|
-
evidenceSignals: {
|
|
182
|
-
failures: number;
|
|
183
|
-
verifications: number;
|
|
184
|
-
roleLifecycle: number;
|
|
185
|
-
skillCalls: number;
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export interface EvolutionTriggerRecord {
|
|
190
|
-
schemaVersion: 1;
|
|
191
|
-
id: string;
|
|
192
|
-
kind: "evolution-trigger";
|
|
193
|
-
projectKey: string;
|
|
194
|
-
runId: string;
|
|
195
|
-
roleId: string | null;
|
|
196
|
-
taskId: string | null;
|
|
197
|
-
eventType: EvolutionNormalizedEventType;
|
|
198
|
-
eventId: string | null;
|
|
199
|
-
evidenceRef: string | null;
|
|
200
|
-
summary: string;
|
|
201
|
-
triggerStrength: EvolutionTriggerStrength;
|
|
202
|
-
triggerReason: EvolutionTriggerReason;
|
|
203
|
-
status: EvolutionTriggerStatus;
|
|
204
|
-
attempts: number;
|
|
205
|
-
createdAt: string;
|
|
206
|
-
updatedAt: string;
|
|
207
|
-
processedBatchId: string | null;
|
|
208
|
-
lastError: string | null;
|
|
209
|
-
rawContentStored: false;
|
|
210
|
-
privacy: EvolutionPrivacyFields;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export interface EvolutionTriggerDecision {
|
|
214
|
-
eventType: EvolutionNormalizedEventType;
|
|
215
|
-
strength: EvolutionTriggerStrength;
|
|
216
|
-
reason: EvolutionTriggerReason;
|
|
217
|
-
shouldQueue: boolean;
|
|
218
|
-
summary: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export interface EvolutionKnowledgeRecord {
|
|
222
|
-
schemaVersion: 1;
|
|
223
|
-
id: string;
|
|
224
|
-
kind: EvolutionKnowledgeKind;
|
|
225
|
-
title: string;
|
|
226
|
-
projectKey: string;
|
|
227
|
-
summary: string;
|
|
228
|
-
body: string;
|
|
229
|
-
roleTags: string[];
|
|
230
|
-
tags: string[];
|
|
231
|
-
reviewState: EvolutionReviewState;
|
|
232
|
-
authority: EvolutionAuthority;
|
|
233
|
-
confidence: EvolutionConfidence;
|
|
234
|
-
provenance: {
|
|
235
|
-
runId: string;
|
|
236
|
-
taskId: string | null;
|
|
237
|
-
evidenceWindowId: string;
|
|
238
|
-
sourceRefs: string[];
|
|
239
|
-
createdAt: string;
|
|
240
|
-
createdBy: "evodev";
|
|
241
|
-
rawLogsStored: false;
|
|
242
|
-
rawPromptsStored: false;
|
|
243
|
-
sourceDumpsStored: false;
|
|
244
|
-
rawCommandOutputStored: false;
|
|
245
|
-
};
|
|
246
|
-
relations: {
|
|
247
|
-
relatedKnowledgeIds: string[];
|
|
248
|
-
evosCaseIds: string[];
|
|
249
|
-
proposalIds: string[];
|
|
250
|
-
supersedes: string[];
|
|
251
|
-
};
|
|
252
|
-
privacy: EvolutionPrivacyFields;
|
|
253
|
-
runtime: {
|
|
254
|
-
canLoad: boolean;
|
|
255
|
-
hardBlocking: false;
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export interface EvolutionEvosCase {
|
|
260
|
-
schemaVersion: 1;
|
|
261
|
-
id: string;
|
|
262
|
-
kind: "evos-case";
|
|
263
|
-
projectKey: string;
|
|
264
|
-
title: string;
|
|
265
|
-
roleTags: string[];
|
|
266
|
-
tags: string[];
|
|
267
|
-
reviewState: EvolutionReviewState;
|
|
268
|
-
confidence: EvolutionConfidence;
|
|
269
|
-
trigger: {
|
|
270
|
-
kind: "task-completion" | "plan-task-completion" | "session-completion" | "manual";
|
|
271
|
-
summary: string;
|
|
272
|
-
};
|
|
273
|
-
intervention: {
|
|
274
|
-
summary: string;
|
|
275
|
-
roleIds: string[];
|
|
276
|
-
};
|
|
277
|
-
result: {
|
|
278
|
-
summary: string;
|
|
279
|
-
verificationSignals: string[];
|
|
280
|
-
};
|
|
281
|
-
expectedFutureBehavior: string;
|
|
282
|
-
provenance: {
|
|
283
|
-
runId: string;
|
|
284
|
-
taskId: string | null;
|
|
285
|
-
evidenceWindowId: string;
|
|
286
|
-
sourceRefs: string[];
|
|
287
|
-
createdAt: string;
|
|
288
|
-
createdBy: "evodev";
|
|
289
|
-
rawLogsStored: false;
|
|
290
|
-
rawPromptsStored: false;
|
|
291
|
-
sourceDumpsStored: false;
|
|
292
|
-
rawCommandOutputStored: false;
|
|
293
|
-
};
|
|
294
|
-
privacy: EvolutionPrivacyFields;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export interface EvolutionRepoProposal {
|
|
298
|
-
schemaVersion: 1;
|
|
299
|
-
id: string;
|
|
300
|
-
kind: EvolutionProposalKind;
|
|
301
|
-
projectKey: string;
|
|
302
|
-
title: string;
|
|
303
|
-
summary: string;
|
|
304
|
-
rationale: string;
|
|
305
|
-
roleTags: string[];
|
|
306
|
-
tags: string[];
|
|
307
|
-
reviewState: "pending" | "accepted" | "rejected" | "deferred" | "applied";
|
|
308
|
-
confidence: EvolutionConfidence;
|
|
309
|
-
targetRepoPath: string | null;
|
|
310
|
-
plannedFiles: Array<{
|
|
311
|
-
relativePath: string;
|
|
312
|
-
action: "create" | "update";
|
|
313
|
-
reason: string;
|
|
314
|
-
}>;
|
|
315
|
-
apply: {
|
|
316
|
-
autoApply: false;
|
|
317
|
-
requiresExplicitCommand: true;
|
|
318
|
-
rollbackPlan: string;
|
|
319
|
-
};
|
|
320
|
-
provenance: {
|
|
321
|
-
runId: string;
|
|
322
|
-
taskId: string | null;
|
|
323
|
-
evidenceWindowId: string;
|
|
324
|
-
sourceRefs: string[];
|
|
325
|
-
createdAt: string;
|
|
326
|
-
createdBy: "evodev";
|
|
327
|
-
rawLogsStored: false;
|
|
328
|
-
rawPromptsStored: false;
|
|
329
|
-
sourceDumpsStored: false;
|
|
330
|
-
rawCommandOutputStored: false;
|
|
331
|
-
};
|
|
332
|
-
privacy: EvolutionPrivacyFields;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export interface EvolutionReviewCandidate {
|
|
336
|
-
schemaVersion: 1;
|
|
337
|
-
kind: "evolution-review-candidate";
|
|
338
|
-
id: string;
|
|
339
|
-
projectKey: string;
|
|
340
|
-
runId: string;
|
|
341
|
-
createdAt: string;
|
|
342
|
-
candidateKind: string;
|
|
343
|
-
title: string;
|
|
344
|
-
targetStore: string;
|
|
345
|
-
targetPath: string;
|
|
346
|
-
stableKey: string;
|
|
347
|
-
reviewState: "needs-human";
|
|
348
|
-
reasons: string[];
|
|
349
|
-
candidate: unknown;
|
|
350
|
-
provenance: {
|
|
351
|
-
runId: string;
|
|
352
|
-
evidenceWindowId: string;
|
|
353
|
-
evidenceRefs: string[];
|
|
354
|
-
createdBy: "evodev";
|
|
355
|
-
rawLogsStored: false;
|
|
356
|
-
rawPromptsStored: false;
|
|
357
|
-
sourceDumpsStored: false;
|
|
358
|
-
rawCommandOutputStored: false;
|
|
359
|
-
};
|
|
360
|
-
privacy: EvolutionPrivacyFields;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export interface EvolutionDistillationBatch {
|
|
364
|
-
schemaVersion: 1;
|
|
365
|
-
id: string;
|
|
366
|
-
projectKey: string;
|
|
367
|
-
runId: string;
|
|
368
|
-
createdAt: string;
|
|
369
|
-
evidenceWindow: EvolutionEvidenceWindow;
|
|
370
|
-
knowledgeRecords: EvolutionKnowledgeRecord[];
|
|
371
|
-
evosCases: EvolutionEvosCase[];
|
|
372
|
-
repoProposals: EvolutionRepoProposal[];
|
|
373
|
-
warnings: string[];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export interface EvolutionAnalyzeInput {
|
|
377
|
-
homeDir: string;
|
|
378
|
-
runId: string;
|
|
379
|
-
projectKey?: string;
|
|
380
|
-
projectDir?: string;
|
|
381
|
-
now?: string | Date;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export interface EvolutionAnalyzeResult {
|
|
385
|
-
evidenceWindow: EvolutionEvidenceWindow;
|
|
386
|
-
warnings: string[];
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export interface EvolutionWriteResult {
|
|
390
|
-
evidenceWindowPath: string;
|
|
391
|
-
batchPath: string;
|
|
392
|
-
knowledgePaths: string[];
|
|
393
|
-
evosCasePaths: string[];
|
|
394
|
-
repoProposalPaths: string[];
|
|
395
|
-
knowledgeIndexPath: string;
|
|
396
|
-
evosIndexPath: string;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export interface EvolutionActivationResult {
|
|
400
|
-
evidenceWindowPath: string;
|
|
401
|
-
batchPath: string;
|
|
402
|
-
okf: OkfKnowledgeActivationResult;
|
|
403
|
-
evosCasePaths: string[];
|
|
404
|
-
repoProposalPaths: string[];
|
|
405
|
-
evosIndexPath: string;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export interface EvolutionReviewSnapshot {
|
|
409
|
-
projectKey: string | null;
|
|
410
|
-
knowledgeRecords: EvolutionKnowledgeRecord[];
|
|
411
|
-
evosCases: EvolutionEvosCase[];
|
|
412
|
-
repoProposals: EvolutionRepoProposal[];
|
|
413
|
-
reviewCandidates: EvolutionReviewCandidate[];
|
|
414
|
-
triggers: EvolutionTriggerRecord[];
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export interface EvolutionEvosCaseQueryResult {
|
|
418
|
-
cases: EvolutionEvosCase[];
|
|
419
|
-
warnings: string[];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export interface EvolutionResolvedPaths {
|
|
423
|
-
projectKey: string;
|
|
424
|
-
runId: string;
|
|
425
|
-
evolutionStateDir: string;
|
|
426
|
-
runStateDir: string;
|
|
427
|
-
evidenceWindowPath: string;
|
|
428
|
-
batchPath: string;
|
|
429
|
-
triggersDir: string;
|
|
430
|
-
reviewCandidatesDir: string;
|
|
431
|
-
repoProposalsDir: string;
|
|
432
|
-
repoProposalsIndexPath: string;
|
|
433
|
-
knowledgeProjectDir: string;
|
|
434
|
-
knowledgeRecordsDir: string;
|
|
435
|
-
knowledgeIndexPath: string;
|
|
436
|
-
evosCasesProjectDir: string;
|
|
437
|
-
evosIndexPath: string;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
export interface EvolutionTriggerInput {
|
|
441
|
-
homeDir: string;
|
|
442
|
-
projectKey: string;
|
|
443
|
-
runId: string;
|
|
444
|
-
roleId?: string | null;
|
|
445
|
-
taskId?: string | null;
|
|
446
|
-
eventType: string;
|
|
447
|
-
eventId?: string | null;
|
|
448
|
-
evidenceRef?: string | null;
|
|
449
|
-
summary?: string | null;
|
|
450
|
-
now?: string | Date;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export interface EvolutionTriggerListInput {
|
|
454
|
-
homeDir: string;
|
|
455
|
-
projectKey?: string;
|
|
456
|
-
runId?: string;
|
|
457
|
-
status?: EvolutionTriggerStatus;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export interface EvolutionProcessInput {
|
|
461
|
-
homeDir: string;
|
|
462
|
-
projectKey?: string;
|
|
463
|
-
runId?: string;
|
|
464
|
-
limit?: number;
|
|
465
|
-
now?: string | Date;
|
|
466
|
-
dryRun?: boolean;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export interface EvolutionProcessResult {
|
|
470
|
-
processed: number;
|
|
471
|
-
consumed: number;
|
|
472
|
-
skipped: number;
|
|
473
|
-
failed: number;
|
|
474
|
-
pending: number;
|
|
475
|
-
triggerIds: string[];
|
|
476
|
-
batchIds: string[];
|
|
477
|
-
warnings: string[];
|
|
478
|
-
dryRun: boolean;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const MAX_EVIDENCE_EVENTS = 200;
|
|
482
|
-
const MAX_TEXT_LENGTH = 600;
|
|
483
|
-
const PROCESS_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
484
|
-
const REVIEW_STATES: readonly EvolutionReviewState[] = [
|
|
485
|
-
"auto-accepted",
|
|
486
|
-
"auto-stored/unreviewed",
|
|
487
|
-
"needs-human",
|
|
488
|
-
"accepted",
|
|
489
|
-
"rejected",
|
|
490
|
-
"deferred",
|
|
491
|
-
"stale",
|
|
492
|
-
"deprecated",
|
|
493
|
-
"superseded",
|
|
494
|
-
"revoked",
|
|
495
|
-
];
|
|
496
|
-
const KNOWLEDGE_KINDS: readonly EvolutionKnowledgeKind[] = [
|
|
497
|
-
"run-summary",
|
|
498
|
-
"lesson",
|
|
499
|
-
"rule",
|
|
500
|
-
"workflow-hint",
|
|
501
|
-
"skill-gap",
|
|
502
|
-
"role-note",
|
|
503
|
-
"verification-pattern",
|
|
504
|
-
];
|
|
505
|
-
const PROPOSAL_KINDS: readonly EvolutionProposalKind[] = [
|
|
506
|
-
"skill",
|
|
507
|
-
"rule",
|
|
508
|
-
"role-agent",
|
|
509
|
-
"team",
|
|
510
|
-
"ci",
|
|
511
|
-
"test",
|
|
512
|
-
"docs",
|
|
513
|
-
"engineering-practice",
|
|
514
|
-
];
|
|
515
|
-
const EVENT_KINDS: readonly EvolutionEvidenceEventKind[] = [
|
|
516
|
-
"trace",
|
|
517
|
-
"tool-call",
|
|
518
|
-
"skill-call",
|
|
519
|
-
"subagent",
|
|
520
|
-
"verification",
|
|
521
|
-
"review",
|
|
522
|
-
"user-feedback",
|
|
523
|
-
"error",
|
|
524
|
-
"run-state",
|
|
525
|
-
];
|
|
526
|
-
const NORMALIZED_EVENT_TYPES: readonly EvolutionNormalizedEventType[] = [
|
|
527
|
-
"SessionStart",
|
|
528
|
-
"UserPromptSubmit",
|
|
529
|
-
"UserPromptExpansion",
|
|
530
|
-
"PreToolUse",
|
|
531
|
-
"PermissionRequest",
|
|
532
|
-
"PostToolUse",
|
|
533
|
-
"PostToolUseFailure",
|
|
534
|
-
"PostToolBatch",
|
|
535
|
-
"PermissionDenied",
|
|
536
|
-
"SubagentStart",
|
|
537
|
-
"Stop",
|
|
538
|
-
"StopFailure",
|
|
539
|
-
"TeammateIdle",
|
|
540
|
-
"SubagentStop",
|
|
541
|
-
"TaskCreated",
|
|
542
|
-
"TaskCompleted",
|
|
543
|
-
"PreCompact",
|
|
544
|
-
"PostCompact",
|
|
545
|
-
"SessionEnd",
|
|
546
|
-
"ConfigChange",
|
|
547
|
-
"CwdChanged",
|
|
548
|
-
"FileChanged",
|
|
549
|
-
"WorktreeCreate",
|
|
550
|
-
"WorktreeRemove",
|
|
551
|
-
"unknown",
|
|
552
|
-
];
|
|
553
|
-
const TRIGGER_STRENGTHS: readonly EvolutionTriggerStrength[] = ["none", "conditional", "strong"];
|
|
554
|
-
const TRIGGER_REASONS: readonly EvolutionTriggerReason[] = [
|
|
555
|
-
"none",
|
|
556
|
-
"turn-completed",
|
|
557
|
-
"task-completed",
|
|
558
|
-
"session-ended",
|
|
559
|
-
"role-completed",
|
|
560
|
-
"stop-failure",
|
|
561
|
-
"tool-failure",
|
|
562
|
-
"permission-denied",
|
|
563
|
-
"explicit-command",
|
|
564
|
-
"failure-signal",
|
|
565
|
-
];
|
|
566
|
-
const EPISODE_KINDS: readonly EvolutionEpisodeKind[] = ["session", "turn", "task", "role"];
|
|
567
|
-
const EPISODE_STATUSES: readonly EvolutionEpisodeStatus[] = ["open", "closed", "failed"];
|
|
568
|
-
const TRIGGER_STATUSES: readonly EvolutionTriggerStatus[] = [
|
|
569
|
-
"pending",
|
|
570
|
-
"processing",
|
|
571
|
-
"consumed",
|
|
572
|
-
"failed",
|
|
573
|
-
"skipped",
|
|
574
|
-
];
|
|
575
|
-
const FORBIDDEN_RAW_KEYS = new Set([
|
|
576
|
-
"commandhistory",
|
|
577
|
-
"commandoutput",
|
|
578
|
-
"credential",
|
|
579
|
-
"credentials",
|
|
580
|
-
"env",
|
|
581
|
-
"fullsource",
|
|
582
|
-
"memorybody",
|
|
583
|
-
"password",
|
|
584
|
-
"privatekey",
|
|
585
|
-
"prompt",
|
|
586
|
-
"promptbody",
|
|
587
|
-
"prompttext",
|
|
588
|
-
"rawcommand",
|
|
589
|
-
"rawcommandoutput",
|
|
590
|
-
"rawlog",
|
|
591
|
-
"rawlogs",
|
|
592
|
-
"rawoutput",
|
|
593
|
-
"rawpayload",
|
|
594
|
-
"rawprompt",
|
|
595
|
-
"secret",
|
|
596
|
-
"secretvalue",
|
|
597
|
-
"source",
|
|
598
|
-
"sourcebody",
|
|
599
|
-
"sourcecode",
|
|
600
|
-
"sourcecontent",
|
|
601
|
-
"sourcetext",
|
|
602
|
-
"stderr",
|
|
603
|
-
"stdout",
|
|
604
|
-
"token",
|
|
605
|
-
"transcript",
|
|
606
|
-
"transcriptbody",
|
|
607
|
-
"transcripttext",
|
|
608
|
-
]);
|
|
609
|
-
const SENSITIVE_TEXT_PATTERN =
|
|
610
|
-
/https?:\/\/\S+|\b(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials|secret[\s_-]*token(?:[\s_-]*repro)?|raw[\s_-]*(?:log|logs|output|source|prompt)(?:[\s_-]*repro)?|shell[\s_-]*history|command[\s_-]*history)\b/i;
|
|
611
|
-
const SENSITIVE_TEXT_REPLACE_PATTERN =
|
|
612
|
-
/https?:\/\/\S+|\b(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials|secret[\s_-]*token(?:[\s_-]*repro)?|raw[\s_-]*(?:log|logs|output|source|prompt)(?:[\s_-]*repro)?|shell[\s_-]*history|command[\s_-]*history)\b/gi;
|
|
613
|
-
|
|
614
|
-
export async function analyzeEvolutionRun(
|
|
615
|
-
input: EvolutionAnalyzeInput,
|
|
616
|
-
): Promise<EvolutionAnalyzeResult> {
|
|
617
|
-
const projectKey = resolveEvolutionProjectKey(input);
|
|
618
|
-
const runId = sanitizeId(input.runId);
|
|
619
|
-
const paths = resolveEvolutionPaths({ homeDir: input.homeDir, projectKey, runId });
|
|
620
|
-
const warnings: string[] = [];
|
|
621
|
-
const sourceRefs: EvolutionEvidenceSourceRef[] = [];
|
|
622
|
-
const linkedTraceRefIds = new Set<string>();
|
|
623
|
-
const events: EvolutionEvidenceEvent[] = [];
|
|
624
|
-
const logsDir = resolveEvoDevPaths(input.homeDir).logsDir;
|
|
625
|
-
const projectRunAgentsDir = join(logsDir, "teams", projectKey, runId, "agents");
|
|
626
|
-
const legacyProjectRunAgentsDir = join(logsDir, projectKey, runId, "agents");
|
|
627
|
-
const agentDirs = (await pathExists(projectRunAgentsDir))
|
|
628
|
-
? await readdir(projectRunAgentsDir, { withFileTypes: true })
|
|
629
|
-
: [];
|
|
630
|
-
const legacyAgentDirs = (await pathExists(legacyProjectRunAgentsDir))
|
|
631
|
-
? await readdir(legacyProjectRunAgentsDir, { withFileTypes: true })
|
|
632
|
-
: [];
|
|
633
|
-
|
|
634
|
-
if (agentDirs.length === 0 && legacyAgentDirs.length === 0) {
|
|
635
|
-
warnings.push(
|
|
636
|
-
`No agent execution event directory found: ${displayPath(input.homeDir, projectRunAgentsDir)}`,
|
|
637
|
-
);
|
|
638
|
-
} else {
|
|
639
|
-
const rolesWithExecutionEvents = new Set<string>();
|
|
640
|
-
for (const roleDir of agentDirs.filter((entry) => entry.isDirectory())) {
|
|
641
|
-
const roleId = sanitizeId(roleDir.name);
|
|
642
|
-
const eventsPath = join(projectRunAgentsDir, roleDir.name, "events.jsonl");
|
|
643
|
-
if (!(await pathExists(eventsPath))) continue;
|
|
644
|
-
rolesWithExecutionEvents.add(roleId);
|
|
645
|
-
|
|
646
|
-
const sourceRef: EvolutionEvidenceSourceRef = {
|
|
647
|
-
id: `source-${sourceRefs.length + 1}`,
|
|
648
|
-
kind: "evodev-execution-event",
|
|
649
|
-
path: displayPath(input.homeDir, eventsPath),
|
|
650
|
-
roleId,
|
|
651
|
-
rawContentStored: false,
|
|
652
|
-
externalContentCopied: false,
|
|
653
|
-
};
|
|
654
|
-
sourceRefs.push(sourceRef);
|
|
655
|
-
const parsed = await readExecutionEvidenceEvents({
|
|
656
|
-
path: eventsPath,
|
|
657
|
-
roleId,
|
|
658
|
-
sourceRefId: sourceRef.id,
|
|
659
|
-
existingCount: events.length,
|
|
660
|
-
});
|
|
661
|
-
events.push(...parsed.events);
|
|
662
|
-
for (const traceRefId of parsed.traceRefIds) linkedTraceRefIds.add(traceRefId);
|
|
663
|
-
warnings.push(...parsed.warnings);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
for (const roleDir of legacyAgentDirs.filter((entry) => entry.isDirectory())) {
|
|
667
|
-
const roleId = sanitizeId(roleDir.name);
|
|
668
|
-
if (rolesWithExecutionEvents.has(roleId)) continue;
|
|
669
|
-
const tracePath = join(legacyProjectRunAgentsDir, roleDir.name, "trace.log");
|
|
670
|
-
if (!(await pathExists(tracePath))) continue;
|
|
671
|
-
|
|
672
|
-
const sourceRef: EvolutionEvidenceSourceRef = {
|
|
673
|
-
id: `source-${sourceRefs.length + 1}`,
|
|
674
|
-
kind: "team-agent-trace",
|
|
675
|
-
path: displayPath(input.homeDir, tracePath),
|
|
676
|
-
roleId,
|
|
677
|
-
rawContentStored: false,
|
|
678
|
-
externalContentCopied: false,
|
|
679
|
-
};
|
|
680
|
-
sourceRefs.push(sourceRef);
|
|
681
|
-
const parsed = await readTraceEvidenceEvents({
|
|
682
|
-
path: tracePath,
|
|
683
|
-
roleId,
|
|
684
|
-
sourceRefId: sourceRef.id,
|
|
685
|
-
existingCount: events.length,
|
|
686
|
-
});
|
|
687
|
-
events.push(...parsed.events);
|
|
688
|
-
warnings.push(...parsed.warnings);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
for (const traceRefId of [...linkedTraceRefIds].sort()) {
|
|
693
|
-
try {
|
|
694
|
-
const record = await readCodeAgentTraceRef({ homeDir: input.homeDir, id: traceRefId });
|
|
695
|
-
sourceRefs.push({
|
|
696
|
-
id: `source-${sourceRefs.length + 1}`,
|
|
697
|
-
kind: "code-agent-trace-ref",
|
|
698
|
-
path: displayPath(input.homeDir, record.path),
|
|
699
|
-
roleId: record.ref.roleId,
|
|
700
|
-
rawContentStored: false,
|
|
701
|
-
externalContentCopied: false,
|
|
702
|
-
});
|
|
703
|
-
} catch {
|
|
704
|
-
warnings.push(
|
|
705
|
-
`Code Agent trace ref metadata not found for linked ref: ${sanitizeText(traceRefId)}.`,
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (events.length > MAX_EVIDENCE_EVENTS) {
|
|
711
|
-
warnings.push(
|
|
712
|
-
`Evidence events truncated from ${events.length} to ${MAX_EVIDENCE_EVENTS} metadata events.`,
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
const limitedEvents = events.slice(0, MAX_EVIDENCE_EVENTS);
|
|
716
|
-
const episodeAnalysis = buildEvolutionEpisodeAnalysis(limitedEvents);
|
|
717
|
-
|
|
718
|
-
const evidenceWindow: EvolutionEvidenceWindow = {
|
|
719
|
-
schemaVersion: 1,
|
|
720
|
-
id: createStableId("evidence", [projectKey, runId]),
|
|
721
|
-
kind: "evidence-window",
|
|
722
|
-
projectKey,
|
|
723
|
-
runId,
|
|
724
|
-
taskId: null,
|
|
725
|
-
createdAt: normalizeTimestamp(input.now),
|
|
726
|
-
sourceRefs,
|
|
727
|
-
events: episodeAnalysis.events,
|
|
728
|
-
episodes: episodeAnalysis.episodes,
|
|
729
|
-
triggerPolicy: episodeAnalysis.triggerPolicy,
|
|
730
|
-
privacy: createPrivacyFields(),
|
|
731
|
-
};
|
|
732
|
-
validateEvolutionEvidenceWindow(evidenceWindow);
|
|
733
|
-
|
|
734
|
-
return {
|
|
735
|
-
evidenceWindow,
|
|
736
|
-
warnings,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
export function createEvolutionDistillationBatch(input: {
|
|
741
|
-
evidenceWindow: EvolutionEvidenceWindow;
|
|
742
|
-
now?: string | Date;
|
|
743
|
-
warnings?: string[];
|
|
744
|
-
}): EvolutionDistillationBatch {
|
|
745
|
-
validateEvolutionEvidenceWindow(input.evidenceWindow);
|
|
746
|
-
const createdAt = normalizeTimestamp(input.now);
|
|
747
|
-
const roleIds = collectRoleIds(input.evidenceWindow);
|
|
748
|
-
const sourceRefs = input.evidenceWindow.sourceRefs.map((sourceRef) => sourceRef.id);
|
|
749
|
-
const failedSignals = input.evidenceWindow.events.filter(
|
|
750
|
-
(event) =>
|
|
751
|
-
event.kind === "error" ||
|
|
752
|
-
event.triggerReason === "stop-failure" ||
|
|
753
|
-
event.triggerReason === "tool-failure" ||
|
|
754
|
-
event.triggerReason === "permission-denied" ||
|
|
755
|
-
/fail|failed|failure|error|issue/i.test(event.summary),
|
|
756
|
-
);
|
|
757
|
-
const verificationSignals = input.evidenceWindow.events
|
|
758
|
-
.filter(
|
|
759
|
-
(event) => event.kind === "verification" || /test|lint|typecheck|build/i.test(event.summary),
|
|
760
|
-
)
|
|
761
|
-
.slice(0, 8)
|
|
762
|
-
.map((event) => event.summary);
|
|
763
|
-
const autoKnowledgeEligible =
|
|
764
|
-
input.evidenceWindow.events.length > 0 &&
|
|
765
|
-
failedSignals.length === 0 &&
|
|
766
|
-
verificationSignals.length > 0 &&
|
|
767
|
-
input.evidenceWindow.triggerPolicy.distillRecommended &&
|
|
768
|
-
input.evidenceWindow.triggerPolicy.evidenceSignals.failures === 0;
|
|
769
|
-
const confidence: EvolutionConfidence = autoKnowledgeEligible
|
|
770
|
-
? "high"
|
|
771
|
-
: failedSignals.length === 0
|
|
772
|
-
? "medium"
|
|
773
|
-
: "low";
|
|
774
|
-
|
|
775
|
-
const knowledgeRecords = !autoKnowledgeEligible
|
|
776
|
-
? []
|
|
777
|
-
: [
|
|
778
|
-
createEvolutionKnowledgeRecord({
|
|
779
|
-
id: createStableId("k", [input.evidenceWindow.id, "run-summary"]),
|
|
780
|
-
kind: "run-summary",
|
|
781
|
-
title: `Project run ${input.evidenceWindow.runId} evidence summary`,
|
|
782
|
-
projectKey: input.evidenceWindow.projectKey,
|
|
783
|
-
summary: createRunSummary(input.evidenceWindow, roleIds, failedSignals.length),
|
|
784
|
-
body: [
|
|
785
|
-
`Run ${input.evidenceWindow.runId} produced ${input.evidenceWindow.events.length} redacted metadata evidence event(s).`,
|
|
786
|
-
`Observed role(s): ${roleIds.length === 0 ? "none" : roleIds.join(", ")}.`,
|
|
787
|
-
"This record is derived from metadata-only evidence; active use depends on OKF plan review state.",
|
|
788
|
-
].join("\n"),
|
|
789
|
-
roleTags: roleIds,
|
|
790
|
-
tags: ["project-run", "auto-stored", "unreviewed"],
|
|
791
|
-
reviewState: "auto-stored/unreviewed",
|
|
792
|
-
authority: "contextual",
|
|
793
|
-
confidence,
|
|
794
|
-
provenance: createProvenance(input.evidenceWindow, createdAt, sourceRefs),
|
|
795
|
-
relations: {
|
|
796
|
-
relatedKnowledgeIds: [],
|
|
797
|
-
evosCaseIds: [createStableId("evo", [input.evidenceWindow.id, "case"])],
|
|
798
|
-
proposalIds:
|
|
799
|
-
failedSignals.length === 0
|
|
800
|
-
? []
|
|
801
|
-
: [createStableId("proposal", [input.evidenceWindow.id, "engineering-practice"])],
|
|
802
|
-
supersedes: [],
|
|
803
|
-
},
|
|
804
|
-
privacy: createPrivacyFields(),
|
|
805
|
-
runtime: { canLoad: true, hardBlocking: false },
|
|
806
|
-
}),
|
|
807
|
-
];
|
|
808
|
-
|
|
809
|
-
const evosCases =
|
|
810
|
-
input.evidenceWindow.events.length === 0
|
|
811
|
-
? []
|
|
812
|
-
: [
|
|
813
|
-
createEvolutionEvosCase({
|
|
814
|
-
id: createStableId("evo", [input.evidenceWindow.id, "case"]),
|
|
815
|
-
projectKey: input.evidenceWindow.projectKey,
|
|
816
|
-
title: `Run ${input.evidenceWindow.runId} evolution case`,
|
|
817
|
-
roleTags: roleIds,
|
|
818
|
-
tags: [
|
|
819
|
-
"project-run",
|
|
820
|
-
"evidence-window",
|
|
821
|
-
`trigger-${input.evidenceWindow.triggerPolicy.strongest}`,
|
|
822
|
-
],
|
|
823
|
-
reviewState: "auto-stored/unreviewed",
|
|
824
|
-
confidence,
|
|
825
|
-
trigger: {
|
|
826
|
-
kind: chooseEvosTriggerKind(input.evidenceWindow),
|
|
827
|
-
summary: createTriggerSummary(input.evidenceWindow),
|
|
828
|
-
},
|
|
829
|
-
intervention: {
|
|
830
|
-
summary: `Collected ${input.evidenceWindow.events.length} metadata event(s) and ${input.evidenceWindow.episodes.length} episode(s) from EvoDev execution logs.`,
|
|
831
|
-
roleIds,
|
|
832
|
-
},
|
|
833
|
-
result: {
|
|
834
|
-
summary:
|
|
835
|
-
failedSignals.length === 0
|
|
836
|
-
? "No failure signal was detected in the redacted evidence summaries."
|
|
837
|
-
: `${failedSignals.length} potential failure or issue signal(s) were detected.`,
|
|
838
|
-
verificationSignals,
|
|
839
|
-
},
|
|
840
|
-
expectedFutureBehavior:
|
|
841
|
-
"Future role agents should inspect accepted knowledge first and treat unreviewed cases as contextual evidence only.",
|
|
842
|
-
provenance: createProvenance(input.evidenceWindow, createdAt, sourceRefs),
|
|
843
|
-
privacy: createPrivacyFields(),
|
|
844
|
-
}),
|
|
845
|
-
];
|
|
846
|
-
|
|
847
|
-
const repoProposals =
|
|
848
|
-
failedSignals.length === 0
|
|
849
|
-
? []
|
|
850
|
-
: [
|
|
851
|
-
createEvolutionRepoProposal({
|
|
852
|
-
id: createStableId("proposal", [input.evidenceWindow.id, "engineering-practice"]),
|
|
853
|
-
kind: "engineering-practice",
|
|
854
|
-
projectKey: input.evidenceWindow.projectKey,
|
|
855
|
-
title: `Review repeated failure signals for ${input.evidenceWindow.runId}`,
|
|
856
|
-
summary:
|
|
857
|
-
"Failure or issue signals appeared in redacted run evidence; review whether repository rules, tests, skills, or role agents need improvement.",
|
|
858
|
-
rationale:
|
|
859
|
-
"EvoDev stores this as a proposal only. It must not modify the user repository until an explicit apply command is implemented and invoked.",
|
|
860
|
-
roleTags: roleIds,
|
|
861
|
-
tags: ["proposal", "failure-signal"],
|
|
862
|
-
reviewState: "pending",
|
|
863
|
-
confidence: "low",
|
|
864
|
-
targetRepoPath: null,
|
|
865
|
-
plannedFiles: [],
|
|
866
|
-
apply: {
|
|
867
|
-
autoApply: false,
|
|
868
|
-
requiresExplicitCommand: true,
|
|
869
|
-
rollbackPlan: "No repository files are changed by this proposal.",
|
|
870
|
-
},
|
|
871
|
-
provenance: createProvenance(input.evidenceWindow, createdAt, sourceRefs),
|
|
872
|
-
privacy: createPrivacyFields(),
|
|
873
|
-
}),
|
|
874
|
-
];
|
|
875
|
-
|
|
876
|
-
const batch: EvolutionDistillationBatch = {
|
|
877
|
-
schemaVersion: 1,
|
|
878
|
-
id: createStableId("batch", [input.evidenceWindow.id, createdAt]),
|
|
879
|
-
projectKey: input.evidenceWindow.projectKey,
|
|
880
|
-
runId: input.evidenceWindow.runId,
|
|
881
|
-
createdAt,
|
|
882
|
-
evidenceWindow: input.evidenceWindow,
|
|
883
|
-
knowledgeRecords,
|
|
884
|
-
evosCases,
|
|
885
|
-
repoProposals,
|
|
886
|
-
warnings: (input.warnings ?? []).map(sanitizeText),
|
|
887
|
-
};
|
|
888
|
-
validateEvolutionDistillationBatch(batch);
|
|
889
|
-
return batch;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
export async function writeEvolutionDistillationBatch(input: {
|
|
893
|
-
homeDir: string;
|
|
894
|
-
batch: EvolutionDistillationBatch;
|
|
895
|
-
overwrite?: boolean;
|
|
896
|
-
}): Promise<EvolutionWriteResult> {
|
|
897
|
-
validateEvolutionDistillationBatch(input.batch);
|
|
898
|
-
const paths = resolveEvolutionPaths({
|
|
899
|
-
homeDir: input.homeDir,
|
|
900
|
-
projectKey: input.batch.projectKey,
|
|
901
|
-
runId: input.batch.runId,
|
|
902
|
-
});
|
|
903
|
-
const overwrite = input.overwrite === true;
|
|
904
|
-
await writeJson(paths.evidenceWindowPath, input.batch.evidenceWindow, { overwrite });
|
|
905
|
-
await writeJson(paths.batchPath, input.batch, { overwrite });
|
|
906
|
-
|
|
907
|
-
const knowledgePaths: string[] = [];
|
|
908
|
-
for (const record of input.batch.knowledgeRecords) {
|
|
909
|
-
const path = join(paths.knowledgeRecordsDir, `${record.id}.json`);
|
|
910
|
-
await writeJson(path, record, { overwrite });
|
|
911
|
-
knowledgePaths.push(path);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const evosCasePaths: string[] = [];
|
|
915
|
-
for (const evosCase of input.batch.evosCases) {
|
|
916
|
-
const path = join(paths.evosCasesProjectDir, `${evosCase.id}.json`);
|
|
917
|
-
await writeJson(path, evosCase, { overwrite });
|
|
918
|
-
evosCasePaths.push(path);
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const repoProposalPaths: string[] = [];
|
|
922
|
-
for (const proposal of input.batch.repoProposals) {
|
|
923
|
-
const path = join(paths.repoProposalsDir, `${proposal.id}.json`);
|
|
924
|
-
await writeJson(path, proposal, { overwrite });
|
|
925
|
-
repoProposalPaths.push(path);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
const allProjectKnowledgeRecords = await readJsonFiles(
|
|
929
|
-
paths.knowledgeRecordsDir,
|
|
930
|
-
parseKnowledgeRecord,
|
|
931
|
-
);
|
|
932
|
-
await writeJson(
|
|
933
|
-
paths.knowledgeIndexPath,
|
|
934
|
-
{
|
|
935
|
-
schemaVersion: 1,
|
|
936
|
-
projectKey: input.batch.projectKey,
|
|
937
|
-
updatedAt: input.batch.createdAt,
|
|
938
|
-
records: allProjectKnowledgeRecords.map((record) => ({
|
|
939
|
-
id: record.id,
|
|
940
|
-
kind: record.kind,
|
|
941
|
-
title: record.title,
|
|
942
|
-
reviewState: record.reviewState,
|
|
943
|
-
roleTags: record.roleTags,
|
|
944
|
-
})),
|
|
945
|
-
},
|
|
946
|
-
{ overwrite: true },
|
|
947
|
-
);
|
|
948
|
-
|
|
949
|
-
const evosCasesRootDir = resolveEvoDevPaths(input.homeDir).evosCasesDir;
|
|
950
|
-
const evosProjectKeys = await listDirectoryNames(evosCasesRootDir);
|
|
951
|
-
const evosProjects = await Promise.all(
|
|
952
|
-
evosProjectKeys.map(async (projectKey) => {
|
|
953
|
-
const cases = await readJsonFiles(join(evosCasesRootDir, projectKey), parseEvosCase);
|
|
954
|
-
return {
|
|
955
|
-
projectKey,
|
|
956
|
-
cases: cases.map((evosCase) => ({
|
|
957
|
-
id: evosCase.id,
|
|
958
|
-
title: evosCase.title,
|
|
959
|
-
reviewState: evosCase.reviewState,
|
|
960
|
-
})),
|
|
961
|
-
};
|
|
962
|
-
}),
|
|
963
|
-
);
|
|
964
|
-
await writeJson(
|
|
965
|
-
paths.evosIndexPath,
|
|
966
|
-
{
|
|
967
|
-
schemaVersion: 1,
|
|
968
|
-
updatedAt: input.batch.createdAt,
|
|
969
|
-
projects: evosProjects,
|
|
970
|
-
},
|
|
971
|
-
{ overwrite: true },
|
|
972
|
-
);
|
|
973
|
-
|
|
974
|
-
const allRunProposals = await readJsonFiles(paths.repoProposalsDir, parseRepoProposal);
|
|
975
|
-
await writeJson(
|
|
976
|
-
paths.repoProposalsIndexPath,
|
|
977
|
-
{
|
|
978
|
-
schemaVersion: 1,
|
|
979
|
-
projectKey: input.batch.projectKey,
|
|
980
|
-
runId: input.batch.runId,
|
|
981
|
-
updatedAt: input.batch.createdAt,
|
|
982
|
-
proposals: allRunProposals.map((proposal) => ({
|
|
983
|
-
id: proposal.id,
|
|
984
|
-
kind: proposal.kind,
|
|
985
|
-
title: proposal.title,
|
|
986
|
-
reviewState: proposal.reviewState,
|
|
987
|
-
})),
|
|
988
|
-
},
|
|
989
|
-
{ overwrite: true },
|
|
990
|
-
);
|
|
991
|
-
|
|
992
|
-
return {
|
|
993
|
-
evidenceWindowPath: paths.evidenceWindowPath,
|
|
994
|
-
batchPath: paths.batchPath,
|
|
995
|
-
knowledgePaths,
|
|
996
|
-
evosCasePaths,
|
|
997
|
-
repoProposalPaths,
|
|
998
|
-
knowledgeIndexPath: paths.knowledgeIndexPath,
|
|
999
|
-
evosIndexPath: paths.evosIndexPath,
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
export async function activateEvolutionDistillationBatch(input: {
|
|
1004
|
-
homeDir: string;
|
|
1005
|
-
batch: EvolutionDistillationBatch;
|
|
1006
|
-
overwrite?: boolean;
|
|
1007
|
-
}): Promise<EvolutionActivationResult> {
|
|
1008
|
-
validateEvolutionDistillationBatch(input.batch);
|
|
1009
|
-
const paths = resolveEvolutionPaths({
|
|
1010
|
-
homeDir: input.homeDir,
|
|
1011
|
-
projectKey: input.batch.projectKey,
|
|
1012
|
-
runId: input.batch.runId,
|
|
1013
|
-
});
|
|
1014
|
-
const overwrite = input.overwrite === true;
|
|
1015
|
-
await writeJson(paths.evidenceWindowPath, input.batch.evidenceWindow, { overwrite });
|
|
1016
|
-
await writeJson(paths.batchPath, input.batch, { overwrite });
|
|
1017
|
-
|
|
1018
|
-
const plan = createOkfKnowledgePlanFromDistillationBatch(input.batch, {
|
|
1019
|
-
homeDir: input.homeDir,
|
|
1020
|
-
});
|
|
1021
|
-
const okf = await activateOkfKnowledgePlan({
|
|
1022
|
-
homeDir: input.homeDir,
|
|
1023
|
-
plan,
|
|
1024
|
-
overwrite,
|
|
1025
|
-
evidenceWindowPath: paths.evidenceWindowPath,
|
|
1026
|
-
});
|
|
1027
|
-
|
|
1028
|
-
const evosCasePaths: string[] = [];
|
|
1029
|
-
for (const evosCase of input.batch.evosCases) {
|
|
1030
|
-
const matchingCandidate = plan.candidates.find((candidate) => candidate.id === evosCase.id);
|
|
1031
|
-
const persistedEvosCase =
|
|
1032
|
-
matchingCandidate?.decision === "auto-accept"
|
|
1033
|
-
? { ...evosCase, reviewState: "auto-accepted" as const }
|
|
1034
|
-
: matchingCandidate?.decision === "needs-human"
|
|
1035
|
-
? { ...evosCase, reviewState: "needs-human" as const }
|
|
1036
|
-
: evosCase;
|
|
1037
|
-
validateEvolutionEvosCase(persistedEvosCase);
|
|
1038
|
-
const path = join(paths.evosCasesProjectDir, `${evosCase.id}.json`);
|
|
1039
|
-
await writeJson(path, persistedEvosCase, { overwrite });
|
|
1040
|
-
evosCasePaths.push(path);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
const repoProposalPaths: string[] = [];
|
|
1044
|
-
for (const proposal of input.batch.repoProposals) {
|
|
1045
|
-
const path = join(paths.repoProposalsDir, `${proposal.id}.json`);
|
|
1046
|
-
await writeJson(path, proposal, { overwrite });
|
|
1047
|
-
repoProposalPaths.push(path);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
const evosCasesRootDir = resolveEvoDevPaths(input.homeDir).evosCasesDir;
|
|
1051
|
-
const evosProjectKeys = await listDirectoryNames(evosCasesRootDir);
|
|
1052
|
-
const evosProjects = await Promise.all(
|
|
1053
|
-
evosProjectKeys.map(async (projectKey) => {
|
|
1054
|
-
const cases = await readJsonFiles(join(evosCasesRootDir, projectKey), parseEvosCase);
|
|
1055
|
-
return {
|
|
1056
|
-
projectKey,
|
|
1057
|
-
cases: cases.map((evosCase) => ({
|
|
1058
|
-
id: evosCase.id,
|
|
1059
|
-
title: evosCase.title,
|
|
1060
|
-
reviewState: evosCase.reviewState,
|
|
1061
|
-
})),
|
|
1062
|
-
};
|
|
1063
|
-
}),
|
|
1064
|
-
);
|
|
1065
|
-
await writeJson(
|
|
1066
|
-
paths.evosIndexPath,
|
|
1067
|
-
{
|
|
1068
|
-
schemaVersion: 1,
|
|
1069
|
-
updatedAt: input.batch.createdAt,
|
|
1070
|
-
projects: evosProjects,
|
|
1071
|
-
},
|
|
1072
|
-
{ overwrite: true },
|
|
1073
|
-
);
|
|
1074
|
-
|
|
1075
|
-
const allRunProposals = await readJsonFiles(paths.repoProposalsDir, parseRepoProposal);
|
|
1076
|
-
await writeJson(
|
|
1077
|
-
paths.repoProposalsIndexPath,
|
|
1078
|
-
{
|
|
1079
|
-
schemaVersion: 1,
|
|
1080
|
-
projectKey: input.batch.projectKey,
|
|
1081
|
-
runId: input.batch.runId,
|
|
1082
|
-
updatedAt: input.batch.createdAt,
|
|
1083
|
-
proposals: allRunProposals.map((proposal) => ({
|
|
1084
|
-
id: proposal.id,
|
|
1085
|
-
kind: proposal.kind,
|
|
1086
|
-
title: proposal.title,
|
|
1087
|
-
reviewState: proposal.reviewState,
|
|
1088
|
-
})),
|
|
1089
|
-
},
|
|
1090
|
-
{ overwrite: true },
|
|
1091
|
-
);
|
|
1092
|
-
|
|
1093
|
-
return {
|
|
1094
|
-
evidenceWindowPath: paths.evidenceWindowPath,
|
|
1095
|
-
batchPath: paths.batchPath,
|
|
1096
|
-
okf,
|
|
1097
|
-
evosCasePaths,
|
|
1098
|
-
repoProposalPaths,
|
|
1099
|
-
evosIndexPath: paths.evosIndexPath,
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
export async function readEvolutionReviewSnapshot(input: {
|
|
1104
|
-
homeDir: string;
|
|
1105
|
-
projectKey?: string;
|
|
1106
|
-
}): Promise<EvolutionReviewSnapshot> {
|
|
1107
|
-
const paths = resolveEvoDevPaths(input.homeDir);
|
|
1108
|
-
const projectKeys =
|
|
1109
|
-
input.projectKey === undefined
|
|
1110
|
-
? await listEvolutionReviewProjectKeys(paths)
|
|
1111
|
-
: [sanitizeStorageId("projectKey", input.projectKey)];
|
|
1112
|
-
const knowledgeRecords: EvolutionKnowledgeRecord[] = [];
|
|
1113
|
-
const evosCases: EvolutionEvosCase[] = [];
|
|
1114
|
-
const repoProposals: EvolutionRepoProposal[] = [];
|
|
1115
|
-
const reviewCandidates: EvolutionReviewCandidate[] = [];
|
|
1116
|
-
const triggers: EvolutionTriggerRecord[] = [];
|
|
1117
|
-
|
|
1118
|
-
for (const projectKey of projectKeys) {
|
|
1119
|
-
const resolved = resolveEvolutionPaths({
|
|
1120
|
-
homeDir: input.homeDir,
|
|
1121
|
-
projectKey,
|
|
1122
|
-
runId: "review",
|
|
1123
|
-
});
|
|
1124
|
-
knowledgeRecords.push(
|
|
1125
|
-
...(await readJsonFiles(resolved.knowledgeRecordsDir, parseKnowledgeRecord)),
|
|
1126
|
-
);
|
|
1127
|
-
evosCases.push(...(await readJsonFiles(resolved.evosCasesProjectDir, parseEvosCase)));
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const evolutionStateDir = join(paths.stateDir, "evolution");
|
|
1131
|
-
const stateProjectKeys =
|
|
1132
|
-
input.projectKey === undefined
|
|
1133
|
-
? await listDirectoryNames(evolutionStateDir)
|
|
1134
|
-
: [sanitizeStorageId("projectKey", input.projectKey)];
|
|
1135
|
-
for (const projectKey of stateProjectKeys) {
|
|
1136
|
-
const projectStateDir = join(evolutionStateDir, projectKey);
|
|
1137
|
-
const runIds = await listDirectoryNames(projectStateDir);
|
|
1138
|
-
for (const runId of runIds) {
|
|
1139
|
-
const proposalsDir = join(projectStateDir, runId, "proposals");
|
|
1140
|
-
const reviewCandidatesDir = join(projectStateDir, runId, "review-candidates");
|
|
1141
|
-
repoProposals.push(...(await readJsonFiles(proposalsDir, parseRepoProposal)));
|
|
1142
|
-
reviewCandidates.push(...(await readJsonFiles(reviewCandidatesDir, parseReviewCandidate)));
|
|
1143
|
-
triggers.push(
|
|
1144
|
-
...(await readJsonFiles(join(projectStateDir, runId, "triggers"), parseTrigger)),
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
return {
|
|
1150
|
-
projectKey:
|
|
1151
|
-
input.projectKey === undefined ? null : sanitizeStorageId("projectKey", input.projectKey),
|
|
1152
|
-
knowledgeRecords,
|
|
1153
|
-
evosCases,
|
|
1154
|
-
repoProposals,
|
|
1155
|
-
reviewCandidates,
|
|
1156
|
-
triggers,
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
async function listEvolutionReviewProjectKeys(
|
|
1161
|
-
paths: ReturnType<typeof resolveEvoDevPaths>,
|
|
1162
|
-
): Promise<string[]> {
|
|
1163
|
-
const legacyKnowledgeProjectKeys = (
|
|
1164
|
-
await Promise.all(
|
|
1165
|
-
(
|
|
1166
|
-
await listDirectoryNames(paths.knowledgeDir)
|
|
1167
|
-
).map(async (projectKey) =>
|
|
1168
|
-
(await pathExists(join(paths.knowledgeDir, projectKey, "records"))) ? projectKey : null,
|
|
1169
|
-
),
|
|
1170
|
-
)
|
|
1171
|
-
).filter((projectKey): projectKey is string => projectKey !== null);
|
|
1172
|
-
return uniqueSorted([
|
|
1173
|
-
...legacyKnowledgeProjectKeys,
|
|
1174
|
-
...(await listDirectoryNames(paths.evosCasesDir)),
|
|
1175
|
-
]);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
export async function listEvolutionKnowledgeRecords(input: {
|
|
1179
|
-
homeDir: string;
|
|
1180
|
-
projectKey?: string;
|
|
1181
|
-
}): Promise<EvolutionKnowledgeRecord[]> {
|
|
1182
|
-
return (
|
|
1183
|
-
await readEvolutionReviewSnapshot({
|
|
1184
|
-
homeDir: input.homeDir,
|
|
1185
|
-
projectKey: input.projectKey,
|
|
1186
|
-
})
|
|
1187
|
-
).knowledgeRecords.sort((left, right) => left.id.localeCompare(right.id));
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
export async function listEvolutionEvosCases(input: {
|
|
1191
|
-
homeDir: string;
|
|
1192
|
-
projectKey?: string;
|
|
1193
|
-
roleId?: string;
|
|
1194
|
-
reviewStates?: EvolutionReviewState[];
|
|
1195
|
-
}): Promise<EvolutionEvosCaseQueryResult> {
|
|
1196
|
-
const paths = resolveEvoDevPaths(input.homeDir);
|
|
1197
|
-
const projectKeys =
|
|
1198
|
-
input.projectKey === undefined
|
|
1199
|
-
? await listDirectoryNames(paths.evosCasesDir)
|
|
1200
|
-
: [sanitizeStorageId("projectKey", input.projectKey)];
|
|
1201
|
-
const roleId = input.roleId === undefined ? null : sanitizeId(input.roleId);
|
|
1202
|
-
const reviewStates = input.reviewStates ?? ["accepted", "auto-accepted"];
|
|
1203
|
-
const cases: EvolutionEvosCase[] = [];
|
|
1204
|
-
const warnings: string[] = [];
|
|
1205
|
-
|
|
1206
|
-
for (const projectKey of projectKeys) {
|
|
1207
|
-
const projectDir = join(paths.evosCasesDir, projectKey);
|
|
1208
|
-
if (!(await pathExists(projectDir))) continue;
|
|
1209
|
-
const entries = await readdir(projectDir, { withFileTypes: true });
|
|
1210
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1211
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
1212
|
-
const sourceLink = `cases/${projectKey}/${entry.name}`;
|
|
1213
|
-
try {
|
|
1214
|
-
const value = JSON.parse(await readFile(join(projectDir, entry.name), "utf8")) as unknown;
|
|
1215
|
-
const evosCase = parseEvosCase(value);
|
|
1216
|
-
if (!reviewStates.includes(evosCase.reviewState)) continue;
|
|
1217
|
-
if (
|
|
1218
|
-
roleId !== null &&
|
|
1219
|
-
evosCase.roleTags.length > 0 &&
|
|
1220
|
-
!evosCase.roleTags.includes(roleId)
|
|
1221
|
-
) {
|
|
1222
|
-
continue;
|
|
1223
|
-
}
|
|
1224
|
-
cases.push(evosCase);
|
|
1225
|
-
} catch {
|
|
1226
|
-
warnings.push(
|
|
1227
|
-
`Omitted unsafe or invalid evos case: ${sourceLink}. Run evodev knowledge lint.`,
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
return {
|
|
1234
|
-
cases: cases.sort((left, right) => {
|
|
1235
|
-
const project = left.projectKey.localeCompare(right.projectKey);
|
|
1236
|
-
if (project !== 0) return project;
|
|
1237
|
-
return left.id.localeCompare(right.id);
|
|
1238
|
-
}),
|
|
1239
|
-
warnings,
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
export async function readEvolutionKnowledgeRecordById(input: {
|
|
1244
|
-
homeDir: string;
|
|
1245
|
-
knowledgeId: string;
|
|
1246
|
-
projectKey?: string;
|
|
1247
|
-
}): Promise<EvolutionKnowledgeRecord> {
|
|
1248
|
-
const matches = (await listEvolutionKnowledgeRecords(input)).filter(
|
|
1249
|
-
(record) => record.id === input.knowledgeId,
|
|
1250
|
-
);
|
|
1251
|
-
if (matches.length === 0) throw new Error(`Knowledge record not found: ${input.knowledgeId}`);
|
|
1252
|
-
if (matches.length > 1) {
|
|
1253
|
-
throw new Error(`Knowledge record id is ambiguous across projects: ${input.knowledgeId}`);
|
|
1254
|
-
}
|
|
1255
|
-
return matches[0] as EvolutionKnowledgeRecord;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
export async function updateEvolutionKnowledgeReviewState(input: {
|
|
1259
|
-
homeDir: string;
|
|
1260
|
-
knowledgeId: string;
|
|
1261
|
-
projectKey?: string;
|
|
1262
|
-
reviewState: "accepted" | "rejected" | "deferred";
|
|
1263
|
-
}): Promise<{ path: string; record: EvolutionKnowledgeRecord }> {
|
|
1264
|
-
const record = await readEvolutionKnowledgeRecordById(input);
|
|
1265
|
-
const next: EvolutionKnowledgeRecord = {
|
|
1266
|
-
...record,
|
|
1267
|
-
reviewState: input.reviewState,
|
|
1268
|
-
authority: input.reviewState === "accepted" ? "reviewed" : "contextual",
|
|
1269
|
-
runtime: {
|
|
1270
|
-
...record.runtime,
|
|
1271
|
-
canLoad: input.reviewState === "accepted",
|
|
1272
|
-
hardBlocking: false,
|
|
1273
|
-
},
|
|
1274
|
-
};
|
|
1275
|
-
validateEvolutionKnowledgeRecord(next);
|
|
1276
|
-
const paths = resolveEvolutionPaths({
|
|
1277
|
-
homeDir: input.homeDir,
|
|
1278
|
-
projectKey: next.projectKey,
|
|
1279
|
-
runId: "review",
|
|
1280
|
-
});
|
|
1281
|
-
const path = join(paths.knowledgeRecordsDir, `${next.id}.json`);
|
|
1282
|
-
await writeJson(path, next, { overwrite: true });
|
|
1283
|
-
const allProjectKnowledgeRecords = await readJsonFiles(
|
|
1284
|
-
paths.knowledgeRecordsDir,
|
|
1285
|
-
parseKnowledgeRecord,
|
|
1286
|
-
);
|
|
1287
|
-
await writeJson(
|
|
1288
|
-
paths.knowledgeIndexPath,
|
|
1289
|
-
{
|
|
1290
|
-
version: 1,
|
|
1291
|
-
kind: "evolution-knowledge-index",
|
|
1292
|
-
projectKey: next.projectKey,
|
|
1293
|
-
updatedAt: new Date().toISOString(),
|
|
1294
|
-
records: allProjectKnowledgeRecords.map((item) => ({
|
|
1295
|
-
id: item.id,
|
|
1296
|
-
kind: item.kind,
|
|
1297
|
-
title: item.title,
|
|
1298
|
-
roleTags: item.roleTags,
|
|
1299
|
-
tags: item.tags,
|
|
1300
|
-
reviewState: item.reviewState,
|
|
1301
|
-
authority: item.authority,
|
|
1302
|
-
confidence: item.confidence,
|
|
1303
|
-
runtime: item.runtime,
|
|
1304
|
-
})),
|
|
1305
|
-
},
|
|
1306
|
-
{ overwrite: true },
|
|
1307
|
-
);
|
|
1308
|
-
return { path, record: next };
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
export function formatEvolutionAnalyzeResult(result: EvolutionAnalyzeResult): string {
|
|
1312
|
-
const roles = collectRoleIds(result.evidenceWindow);
|
|
1313
|
-
return [
|
|
1314
|
-
"EvoDev evolution analyze",
|
|
1315
|
-
"",
|
|
1316
|
-
`Project: ${result.evidenceWindow.projectKey}`,
|
|
1317
|
-
`Run: ${result.evidenceWindow.runId}`,
|
|
1318
|
-
`Evidence window: ${result.evidenceWindow.id}`,
|
|
1319
|
-
`Sources: ${result.evidenceWindow.sourceRefs.length}`,
|
|
1320
|
-
`Events: ${result.evidenceWindow.events.length}`,
|
|
1321
|
-
`Episodes: ${result.evidenceWindow.episodes.length}`,
|
|
1322
|
-
`Trigger: ${result.evidenceWindow.triggerPolicy.strongest} (${result.evidenceWindow.triggerPolicy.reasons.join(", ") || "none"}); distillRecommended=${result.evidenceWindow.triggerPolicy.distillRecommended}`,
|
|
1323
|
-
`Roles: ${roles.length === 0 ? "none" : roles.join(", ")}`,
|
|
1324
|
-
"Privacy: metadata-only; rawLogs=false; rawPrompts=false; sourceDumps=false; rawCommandOutput=false",
|
|
1325
|
-
...(result.warnings.length === 0
|
|
1326
|
-
? ["Warnings: none"]
|
|
1327
|
-
: ["Warnings:", ...result.warnings.map((warning) => ` - ${warning}`)]),
|
|
1328
|
-
].join("\n");
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
export function formatEvolutionDistillationBatch(batch: EvolutionDistillationBatch): string {
|
|
1332
|
-
return [
|
|
1333
|
-
"EvoDev evolution distillation",
|
|
1334
|
-
"",
|
|
1335
|
-
`Batch: ${batch.id}`,
|
|
1336
|
-
`Project: ${batch.projectKey}`,
|
|
1337
|
-
`Run: ${batch.runId}`,
|
|
1338
|
-
`Evidence events: ${batch.evidenceWindow.events.length}`,
|
|
1339
|
-
`Episodes: ${batch.evidenceWindow.episodes.length}`,
|
|
1340
|
-
`Trigger: ${batch.evidenceWindow.triggerPolicy.strongest}; distillRecommended=${batch.evidenceWindow.triggerPolicy.distillRecommended}`,
|
|
1341
|
-
`Knowledge records: ${batch.knowledgeRecords.length}`,
|
|
1342
|
-
`Evos cases: ${batch.evosCases.length}`,
|
|
1343
|
-
`Repo proposals: ${batch.repoProposals.length}`,
|
|
1344
|
-
"Default batch review state: contextual until the OKF plan auto-accepts or queues review.",
|
|
1345
|
-
"Runtime authority: accepted/auto-accepted only; hardBlocking=false.",
|
|
1346
|
-
...(batch.knowledgeRecords.length === 0
|
|
1347
|
-
? ["Knowledge: none"]
|
|
1348
|
-
: [
|
|
1349
|
-
"Knowledge:",
|
|
1350
|
-
...batch.knowledgeRecords.map(
|
|
1351
|
-
(record) =>
|
|
1352
|
-
` - ${record.id} (${record.kind}, ${record.reviewState}, ${record.confidence}): ${record.title}`,
|
|
1353
|
-
),
|
|
1354
|
-
]),
|
|
1355
|
-
...(batch.evosCases.length === 0
|
|
1356
|
-
? ["Evos cases: none"]
|
|
1357
|
-
: [
|
|
1358
|
-
"Evos cases:",
|
|
1359
|
-
...batch.evosCases.map(
|
|
1360
|
-
(evosCase) =>
|
|
1361
|
-
` - ${evosCase.id} (${evosCase.reviewState}, ${evosCase.confidence}): ${evosCase.title}`,
|
|
1362
|
-
),
|
|
1363
|
-
]),
|
|
1364
|
-
...(batch.repoProposals.length === 0
|
|
1365
|
-
? ["Repo proposals: none"]
|
|
1366
|
-
: [
|
|
1367
|
-
"Repo proposals:",
|
|
1368
|
-
...batch.repoProposals.map(
|
|
1369
|
-
(proposal) =>
|
|
1370
|
-
` - ${proposal.id} (${proposal.kind}, ${proposal.reviewState}, ${proposal.confidence}): ${proposal.title}`,
|
|
1371
|
-
),
|
|
1372
|
-
]),
|
|
1373
|
-
...(batch.warnings.length === 0
|
|
1374
|
-
? []
|
|
1375
|
-
: ["Warnings:", ...batch.warnings.map((warning) => ` - ${warning}`)]),
|
|
1376
|
-
].join("\n");
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
export function formatEvolutionWriteResult(result: EvolutionWriteResult): string {
|
|
1380
|
-
return [
|
|
1381
|
-
"EvoDev evolution write",
|
|
1382
|
-
"",
|
|
1383
|
-
`Evidence window: ${result.evidenceWindowPath}`,
|
|
1384
|
-
`Distillation batch: ${result.batchPath}`,
|
|
1385
|
-
`Knowledge records: ${result.knowledgePaths.length}`,
|
|
1386
|
-
...result.knowledgePaths.map((path) => ` - ${path}`),
|
|
1387
|
-
`Evos cases: ${result.evosCasePaths.length}`,
|
|
1388
|
-
...result.evosCasePaths.map((path) => ` - ${path}`),
|
|
1389
|
-
`Repo proposals: ${result.repoProposalPaths.length}`,
|
|
1390
|
-
...result.repoProposalPaths.map((path) => ` - ${path}`),
|
|
1391
|
-
`Knowledge index: ${result.knowledgeIndexPath}`,
|
|
1392
|
-
`Evos index: ${result.evosIndexPath}`,
|
|
1393
|
-
].join("\n");
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
export function formatEvolutionActivationResult(result: EvolutionActivationResult): string {
|
|
1397
|
-
return [
|
|
1398
|
-
"EvoDev evolution activate",
|
|
1399
|
-
"",
|
|
1400
|
-
`Evidence window: ${result.evidenceWindowPath}`,
|
|
1401
|
-
`Distillation batch: ${result.batchPath}`,
|
|
1402
|
-
`OKF concepts: ${result.okf.conceptPaths.length}`,
|
|
1403
|
-
...result.okf.conceptPaths.map((path) => ` - ${path}`),
|
|
1404
|
-
`OKF overlays: ${result.okf.overlayPaths.length}`,
|
|
1405
|
-
...result.okf.overlayPaths.map((path) => ` - ${path}`),
|
|
1406
|
-
`Skipped candidates: ${result.okf.skippedCandidates.length}`,
|
|
1407
|
-
...result.okf.skippedCandidates.map((id) => ` - ${id}`),
|
|
1408
|
-
`Needs human: ${result.okf.needsHumanCandidates.length}`,
|
|
1409
|
-
...result.okf.needsHumanCandidates.map((id) => ` - ${id}`),
|
|
1410
|
-
`Evos cases: ${result.evosCasePaths.length}`,
|
|
1411
|
-
...result.evosCasePaths.map((path) => ` - ${path}`),
|
|
1412
|
-
`Repo proposals: ${result.repoProposalPaths.length}`,
|
|
1413
|
-
...result.repoProposalPaths.map((path) => ` - ${path}`),
|
|
1414
|
-
`OKF derived indexes: ${result.okf.derivedIndexPaths.length}`,
|
|
1415
|
-
...result.okf.derivedIndexPaths.map((path) => ` - ${path}`),
|
|
1416
|
-
`Evos index: ${result.evosIndexPath}`,
|
|
1417
|
-
].join("\n");
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
export function formatEvolutionProcessResult(result: EvolutionProcessResult): string {
|
|
1421
|
-
return [
|
|
1422
|
-
"EvoDev evolution process",
|
|
1423
|
-
"",
|
|
1424
|
-
`Mode: ${result.dryRun ? "dry-run" : "write"}`,
|
|
1425
|
-
`Processed triggers: ${result.processed}`,
|
|
1426
|
-
`Consumed: ${result.consumed}`,
|
|
1427
|
-
`Skipped: ${result.skipped}`,
|
|
1428
|
-
`Failed: ${result.failed}`,
|
|
1429
|
-
`Still pending: ${result.pending}`,
|
|
1430
|
-
`Batches: ${result.batchIds.length}`,
|
|
1431
|
-
...result.batchIds.map((batchId) => ` - ${batchId}`),
|
|
1432
|
-
...(result.warnings.length === 0
|
|
1433
|
-
? ["Warnings: none"]
|
|
1434
|
-
: ["Warnings:", ...result.warnings.map((warning) => ` - ${warning}`)]),
|
|
1435
|
-
].join("\n");
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
export function formatEvolutionReviewSnapshot(snapshot: EvolutionReviewSnapshot): string {
|
|
1439
|
-
return [
|
|
1440
|
-
"EvoDev evolution review snapshot",
|
|
1441
|
-
"",
|
|
1442
|
-
`Project: ${snapshot.projectKey ?? "all"}`,
|
|
1443
|
-
`Knowledge records: ${snapshot.knowledgeRecords.length}`,
|
|
1444
|
-
...snapshot.knowledgeRecords.map(
|
|
1445
|
-
(record) =>
|
|
1446
|
-
` - ${record.id} (${record.projectKey}, ${record.kind}, ${record.reviewState}, ${record.confidence}): ${record.title}`,
|
|
1447
|
-
),
|
|
1448
|
-
`Evos cases: ${snapshot.evosCases.length}`,
|
|
1449
|
-
...snapshot.evosCases.map(
|
|
1450
|
-
(evosCase) =>
|
|
1451
|
-
` - ${evosCase.id} (${evosCase.projectKey}, ${evosCase.reviewState}, ${evosCase.confidence}): ${evosCase.title}`,
|
|
1452
|
-
),
|
|
1453
|
-
`Repo proposals: ${snapshot.repoProposals.length}`,
|
|
1454
|
-
...snapshot.repoProposals.map(
|
|
1455
|
-
(proposal) =>
|
|
1456
|
-
` - ${proposal.id} (${proposal.projectKey}, ${proposal.kind}, ${proposal.reviewState}, ${proposal.confidence}): ${proposal.title}`,
|
|
1457
|
-
),
|
|
1458
|
-
`Review candidates: ${snapshot.reviewCandidates.length}`,
|
|
1459
|
-
...snapshot.reviewCandidates.map(
|
|
1460
|
-
(candidate) =>
|
|
1461
|
-
` - ${candidate.id} (${candidate.projectKey}, ${candidate.candidateKind}, ${candidate.reviewState}): ${candidate.title}`,
|
|
1462
|
-
),
|
|
1463
|
-
`Triggers: ${snapshot.triggers.length}`,
|
|
1464
|
-
...snapshot.triggers.map(
|
|
1465
|
-
(trigger) =>
|
|
1466
|
-
` - ${trigger.id} (${trigger.projectKey}, ${trigger.runId}, ${trigger.eventType}, ${trigger.status}, ${trigger.triggerStrength}): ${trigger.summary}`,
|
|
1467
|
-
),
|
|
1468
|
-
].join("\n");
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
export function resolveEvolutionPaths(input: {
|
|
1472
|
-
homeDir: string;
|
|
1473
|
-
projectKey: string;
|
|
1474
|
-
runId: string;
|
|
1475
|
-
}): EvolutionResolvedPaths {
|
|
1476
|
-
const paths = resolveEvoDevPaths(input.homeDir);
|
|
1477
|
-
const projectKey = sanitizeStorageId("projectKey", input.projectKey);
|
|
1478
|
-
const runId = sanitizeStorageId("runId", input.runId);
|
|
1479
|
-
const evolutionStateDir = join(paths.stateDir, "evolution");
|
|
1480
|
-
const runStateDir = join(evolutionStateDir, projectKey, runId);
|
|
1481
|
-
const repoProposalsDir = join(runStateDir, "proposals");
|
|
1482
|
-
const reviewCandidatesDir = join(runStateDir, "review-candidates");
|
|
1483
|
-
const knowledgeProjectDir = join(paths.knowledgeDir, projectKey);
|
|
1484
|
-
const evosCasesProjectDir = join(paths.evosCasesDir, projectKey);
|
|
1485
|
-
assertPathDescendant(evolutionStateDir, runStateDir, "runStateDir");
|
|
1486
|
-
assertPathDescendant(evolutionStateDir, reviewCandidatesDir, "reviewCandidatesDir");
|
|
1487
|
-
assertPathDescendant(paths.knowledgeDir, knowledgeProjectDir, "knowledgeProjectDir");
|
|
1488
|
-
assertPathDescendant(paths.evosCasesDir, evosCasesProjectDir, "evosCasesProjectDir");
|
|
1489
|
-
return {
|
|
1490
|
-
projectKey,
|
|
1491
|
-
runId,
|
|
1492
|
-
evolutionStateDir,
|
|
1493
|
-
runStateDir,
|
|
1494
|
-
evidenceWindowPath: join(runStateDir, "evidence-window.json"),
|
|
1495
|
-
batchPath: join(runStateDir, "distillation-batch.json"),
|
|
1496
|
-
triggersDir: join(runStateDir, "triggers"),
|
|
1497
|
-
reviewCandidatesDir,
|
|
1498
|
-
repoProposalsDir,
|
|
1499
|
-
repoProposalsIndexPath: join(runStateDir, "proposals.index.json"),
|
|
1500
|
-
knowledgeProjectDir,
|
|
1501
|
-
knowledgeRecordsDir: join(knowledgeProjectDir, "records"),
|
|
1502
|
-
knowledgeIndexPath: join(knowledgeProjectDir, "index.json"),
|
|
1503
|
-
evosCasesProjectDir,
|
|
1504
|
-
evosIndexPath: paths.evosIndexPath,
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
export function validateEvolutionEvidenceWindow(window: EvolutionEvidenceWindow): void {
|
|
1509
|
-
if (!isRecord(window)) throw new Error("Evidence window must be an object.");
|
|
1510
|
-
if (window.schemaVersion !== 1) throw new Error("Evidence window schemaVersion must be 1.");
|
|
1511
|
-
if (window.kind !== "evidence-window") throw new Error("Evidence window kind is invalid.");
|
|
1512
|
-
assertString("projectKey", window.projectKey);
|
|
1513
|
-
assertString("runId", window.runId);
|
|
1514
|
-
assertString("id", window.id);
|
|
1515
|
-
assertString("createdAt", window.createdAt);
|
|
1516
|
-
assertPrivacy(window.privacy);
|
|
1517
|
-
if (!Array.isArray(window.sourceRefs)) throw new Error("sourceRefs must be an array.");
|
|
1518
|
-
for (const sourceRef of window.sourceRefs) {
|
|
1519
|
-
assertString("sourceRefs.id", sourceRef.id);
|
|
1520
|
-
assertString("sourceRefs.path", sourceRef.path);
|
|
1521
|
-
if (sourceRef.rawContentStored !== false)
|
|
1522
|
-
throw new Error("sourceRefs.rawContentStored must be false.");
|
|
1523
|
-
if (sourceRef.externalContentCopied !== false) {
|
|
1524
|
-
throw new Error("sourceRefs.externalContentCopied must be false.");
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
if (!Array.isArray(window.events)) throw new Error("events must be an array.");
|
|
1528
|
-
for (const event of window.events) {
|
|
1529
|
-
assertString("events.id", event.id);
|
|
1530
|
-
assertEnum("events.kind", event.kind, EVENT_KINDS);
|
|
1531
|
-
assertEnum("events.eventType", event.eventType, NORMALIZED_EVENT_TYPES);
|
|
1532
|
-
assertEnum("events.triggerStrength", event.triggerStrength, TRIGGER_STRENGTHS);
|
|
1533
|
-
assertEnum("events.triggerReason", event.triggerReason, TRIGGER_REASONS);
|
|
1534
|
-
assertString("events.summary", event.summary);
|
|
1535
|
-
assertString("events.evidenceRef", event.evidenceRef);
|
|
1536
|
-
if (!Array.isArray(event.episodeIds)) throw new Error("events.episodeIds must be an array.");
|
|
1537
|
-
for (const episodeId of event.episodeIds) assertString("events.episodeIds", episodeId);
|
|
1538
|
-
if (event.rawContentStored !== false) throw new Error("events.rawContentStored must be false.");
|
|
1539
|
-
}
|
|
1540
|
-
if (!Array.isArray(window.episodes)) throw new Error("episodes must be an array.");
|
|
1541
|
-
for (const episode of window.episodes) {
|
|
1542
|
-
assertString("episodes.id", episode.id);
|
|
1543
|
-
assertEnum("episodes.kind", episode.kind, EPISODE_KINDS);
|
|
1544
|
-
assertEnum("episodes.status", episode.status, EPISODE_STATUSES);
|
|
1545
|
-
assertEnum("episodes.triggerStrength", episode.triggerStrength, TRIGGER_STRENGTHS);
|
|
1546
|
-
assertEnum("episodes.triggerReason", episode.triggerReason, TRIGGER_REASONS);
|
|
1547
|
-
assertString("episodes.summary", episode.summary);
|
|
1548
|
-
if (!Array.isArray(episode.eventIds)) throw new Error("episodes.eventIds must be an array.");
|
|
1549
|
-
for (const eventId of episode.eventIds) assertString("episodes.eventIds", eventId);
|
|
1550
|
-
if (episode.rawContentStored !== false)
|
|
1551
|
-
throw new Error("episodes.rawContentStored must be false.");
|
|
1552
|
-
}
|
|
1553
|
-
assertEnum("triggerPolicy.strongest", window.triggerPolicy.strongest, TRIGGER_STRENGTHS);
|
|
1554
|
-
if (!Array.isArray(window.triggerPolicy.reasons))
|
|
1555
|
-
throw new Error("triggerPolicy.reasons must be an array.");
|
|
1556
|
-
for (const reason of window.triggerPolicy.reasons) {
|
|
1557
|
-
assertEnum("triggerPolicy.reasons", reason, TRIGGER_REASONS);
|
|
1558
|
-
}
|
|
1559
|
-
assertNoForbiddenRawFields(window);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
export function validateEvolutionKnowledgeRecord(record: EvolutionKnowledgeRecord): void {
|
|
1563
|
-
if (!isRecord(record)) throw new Error("Knowledge record must be an object.");
|
|
1564
|
-
if (record.schemaVersion !== 1) throw new Error("Knowledge record schemaVersion must be 1.");
|
|
1565
|
-
assertEnum("kind", record.kind, KNOWLEDGE_KINDS);
|
|
1566
|
-
assertEnum("reviewState", record.reviewState, REVIEW_STATES);
|
|
1567
|
-
assertEnum("authority", record.authority, ["contextual", "reviewed"]);
|
|
1568
|
-
if (record.reviewState === "accepted" && record.authority !== "reviewed") {
|
|
1569
|
-
throw new Error("Accepted knowledge must use reviewed authority.");
|
|
1570
|
-
}
|
|
1571
|
-
if (
|
|
1572
|
-
(record.reviewState === "auto-stored/unreviewed" ||
|
|
1573
|
-
record.reviewState === "auto-accepted" ||
|
|
1574
|
-
record.reviewState === "needs-human") &&
|
|
1575
|
-
record.authority !== "contextual"
|
|
1576
|
-
) {
|
|
1577
|
-
throw new Error("Unreviewed knowledge must remain contextual.");
|
|
1578
|
-
}
|
|
1579
|
-
if (record.runtime.hardBlocking !== false) {
|
|
1580
|
-
throw new Error("Knowledge runtime.hardBlocking must be false.");
|
|
1581
|
-
}
|
|
1582
|
-
assertPrivacy(record.privacy);
|
|
1583
|
-
assertProvenance(record.provenance);
|
|
1584
|
-
assertNoForbiddenRawFields(record);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
export function validateEvolutionDistillationBatch(batch: EvolutionDistillationBatch): void {
|
|
1588
|
-
if (!isRecord(batch)) throw new Error("Distillation batch must be an object.");
|
|
1589
|
-
if (batch.schemaVersion !== 1) throw new Error("Distillation batch schemaVersion must be 1.");
|
|
1590
|
-
validateEvolutionEvidenceWindow(batch.evidenceWindow);
|
|
1591
|
-
for (const record of batch.knowledgeRecords) validateEvolutionKnowledgeRecord(record);
|
|
1592
|
-
for (const evosCase of batch.evosCases) validateEvolutionEvosCase(evosCase);
|
|
1593
|
-
for (const proposal of batch.repoProposals) validateEvolutionRepoProposal(proposal);
|
|
1594
|
-
assertNoForbiddenRawFields(batch);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
export function resolveEvolutionTriggerDecision(input: {
|
|
1598
|
-
eventType: string;
|
|
1599
|
-
summary?: string | null;
|
|
1600
|
-
}): EvolutionTriggerDecision {
|
|
1601
|
-
const eventType = normalizeEvolutionEventType(input.eventType);
|
|
1602
|
-
const trigger = decideEvolutionTrigger({
|
|
1603
|
-
eventType,
|
|
1604
|
-
summary: input.summary ?? "",
|
|
1605
|
-
kind: "trace",
|
|
1606
|
-
});
|
|
1607
|
-
return {
|
|
1608
|
-
eventType,
|
|
1609
|
-
strength: trigger.strength,
|
|
1610
|
-
reason: trigger.reason,
|
|
1611
|
-
shouldQueue: trigger.strength !== "none",
|
|
1612
|
-
summary: createTriggerDecisionSummary(eventType, trigger.strength, trigger.reason),
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
export async function enqueueEvolutionTrigger(
|
|
1617
|
-
input: EvolutionTriggerInput,
|
|
1618
|
-
): Promise<EvolutionTriggerRecord | null> {
|
|
1619
|
-
const decision = resolveEvolutionTriggerDecision({
|
|
1620
|
-
eventType: input.eventType,
|
|
1621
|
-
summary: input.summary,
|
|
1622
|
-
});
|
|
1623
|
-
if (!decision.shouldQueue) return null;
|
|
1624
|
-
|
|
1625
|
-
const now = normalizeTimestamp(input.now);
|
|
1626
|
-
const projectKey = sanitizeId(input.projectKey);
|
|
1627
|
-
const runId = sanitizeId(input.runId);
|
|
1628
|
-
const id = createStableId("trigger", [
|
|
1629
|
-
projectKey,
|
|
1630
|
-
runId,
|
|
1631
|
-
input.roleId ?? "",
|
|
1632
|
-
input.eventId ?? "",
|
|
1633
|
-
decision.eventType,
|
|
1634
|
-
now,
|
|
1635
|
-
]);
|
|
1636
|
-
const trigger: EvolutionTriggerRecord = {
|
|
1637
|
-
schemaVersion: 1,
|
|
1638
|
-
id,
|
|
1639
|
-
kind: "evolution-trigger",
|
|
1640
|
-
projectKey,
|
|
1641
|
-
runId,
|
|
1642
|
-
roleId: input.roleId === undefined || input.roleId === null ? null : sanitizeId(input.roleId),
|
|
1643
|
-
taskId: input.taskId === undefined || input.taskId === null ? null : sanitizeId(input.taskId),
|
|
1644
|
-
eventType: decision.eventType,
|
|
1645
|
-
eventId:
|
|
1646
|
-
input.eventId === undefined || input.eventId === null ? null : sanitizeId(input.eventId),
|
|
1647
|
-
evidenceRef:
|
|
1648
|
-
input.evidenceRef === undefined || input.evidenceRef === null
|
|
1649
|
-
? null
|
|
1650
|
-
: sanitizeText(input.evidenceRef),
|
|
1651
|
-
summary: sanitizeText(input.summary ?? decision.summary),
|
|
1652
|
-
triggerStrength: decision.strength,
|
|
1653
|
-
triggerReason: decision.reason,
|
|
1654
|
-
status: "pending",
|
|
1655
|
-
attempts: 0,
|
|
1656
|
-
createdAt: now,
|
|
1657
|
-
updatedAt: now,
|
|
1658
|
-
processedBatchId: null,
|
|
1659
|
-
lastError: null,
|
|
1660
|
-
rawContentStored: false,
|
|
1661
|
-
privacy: createPrivacyFields(),
|
|
1662
|
-
};
|
|
1663
|
-
validateEvolutionTriggerRecord(trigger);
|
|
1664
|
-
const paths = resolveEvolutionPaths({ homeDir: input.homeDir, projectKey, runId });
|
|
1665
|
-
await writeJson(join(paths.triggersDir, `${trigger.id}.json`), trigger, { overwrite: false });
|
|
1666
|
-
return trigger;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
export async function listEvolutionTriggers(
|
|
1670
|
-
input: EvolutionTriggerListInput,
|
|
1671
|
-
): Promise<EvolutionTriggerRecord[]> {
|
|
1672
|
-
const paths = resolveEvoDevPaths(input.homeDir);
|
|
1673
|
-
const evolutionStateDir = join(paths.stateDir, "evolution");
|
|
1674
|
-
const projectKeys =
|
|
1675
|
-
input.projectKey === undefined
|
|
1676
|
-
? await listDirectoryNames(evolutionStateDir)
|
|
1677
|
-
: [sanitizeStorageId("projectKey", input.projectKey)];
|
|
1678
|
-
const triggers: EvolutionTriggerRecord[] = [];
|
|
1679
|
-
for (const projectKey of projectKeys) {
|
|
1680
|
-
const projectStateDir = join(evolutionStateDir, projectKey);
|
|
1681
|
-
const runIds =
|
|
1682
|
-
input.runId === undefined
|
|
1683
|
-
? await listDirectoryNames(projectStateDir)
|
|
1684
|
-
: [sanitizeStorageId("runId", input.runId)];
|
|
1685
|
-
for (const runId of runIds) {
|
|
1686
|
-
const runTriggers = await readJsonFiles(
|
|
1687
|
-
join(projectStateDir, runId, "triggers"),
|
|
1688
|
-
parseTrigger,
|
|
1689
|
-
);
|
|
1690
|
-
triggers.push(
|
|
1691
|
-
...runTriggers.filter(
|
|
1692
|
-
(trigger) => input.status === undefined || trigger.status === input.status,
|
|
1693
|
-
),
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
return triggers.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
export async function processEvolutionTriggers(
|
|
1701
|
-
input: EvolutionProcessInput,
|
|
1702
|
-
): Promise<EvolutionProcessResult> {
|
|
1703
|
-
const now = normalizeTimestamp(input.now);
|
|
1704
|
-
const limit = input.limit ?? 20;
|
|
1705
|
-
const dryRun = input.dryRun === true;
|
|
1706
|
-
const warnings: string[] = [];
|
|
1707
|
-
const lock = dryRun ? null : await acquireEvolutionProcessLock(input.homeDir, now);
|
|
1708
|
-
if (!dryRun && lock === null) {
|
|
1709
|
-
warnings.push("Evolution trigger processor is already running; this pass was skipped.");
|
|
1710
|
-
return {
|
|
1711
|
-
processed: 0,
|
|
1712
|
-
consumed: 0,
|
|
1713
|
-
skipped: 0,
|
|
1714
|
-
failed: 0,
|
|
1715
|
-
pending: 0,
|
|
1716
|
-
triggerIds: [],
|
|
1717
|
-
batchIds: [],
|
|
1718
|
-
warnings,
|
|
1719
|
-
dryRun,
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
try {
|
|
1724
|
-
const pending = (
|
|
1725
|
-
await listEvolutionTriggers({
|
|
1726
|
-
homeDir: input.homeDir,
|
|
1727
|
-
projectKey: input.projectKey,
|
|
1728
|
-
runId: input.runId,
|
|
1729
|
-
status: "pending",
|
|
1730
|
-
})
|
|
1731
|
-
).slice(0, limit);
|
|
1732
|
-
const grouped = groupTriggersByRun(pending);
|
|
1733
|
-
const result: EvolutionProcessResult = {
|
|
1734
|
-
processed: 0,
|
|
1735
|
-
consumed: 0,
|
|
1736
|
-
skipped: 0,
|
|
1737
|
-
failed: 0,
|
|
1738
|
-
pending: 0,
|
|
1739
|
-
triggerIds: pending.map((trigger) => trigger.id),
|
|
1740
|
-
batchIds: [],
|
|
1741
|
-
warnings,
|
|
1742
|
-
dryRun,
|
|
1743
|
-
};
|
|
1744
|
-
|
|
1745
|
-
for (const triggers of grouped) {
|
|
1746
|
-
result.processed += triggers.length;
|
|
1747
|
-
if (dryRun) {
|
|
1748
|
-
result.pending += triggers.length;
|
|
1749
|
-
continue;
|
|
1750
|
-
}
|
|
1751
|
-
await updateTriggers(input.homeDir, triggers, {
|
|
1752
|
-
status: "processing",
|
|
1753
|
-
updatedAt: now,
|
|
1754
|
-
attempts: (trigger) => trigger.attempts + 1,
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
|
-
try {
|
|
1758
|
-
const first = triggers[0];
|
|
1759
|
-
if (first === undefined) continue;
|
|
1760
|
-
const analysis = await analyzeEvolutionRun({
|
|
1761
|
-
homeDir: input.homeDir,
|
|
1762
|
-
projectKey: first.projectKey,
|
|
1763
|
-
runId: first.runId,
|
|
1764
|
-
now,
|
|
1765
|
-
});
|
|
1766
|
-
warnings.push(...analysis.warnings);
|
|
1767
|
-
const missingTrace = triggers.some(
|
|
1768
|
-
(trigger) =>
|
|
1769
|
-
trigger.eventId !== null &&
|
|
1770
|
-
!analysis.evidenceWindow.events.some((event) => event.hookEventId === trigger.eventId),
|
|
1771
|
-
);
|
|
1772
|
-
if (missingTrace) {
|
|
1773
|
-
result.pending += triggers.length;
|
|
1774
|
-
await updateTriggers(input.homeDir, triggers, {
|
|
1775
|
-
status: "pending",
|
|
1776
|
-
updatedAt: now,
|
|
1777
|
-
lastError: "waiting-for-trace",
|
|
1778
|
-
});
|
|
1779
|
-
continue;
|
|
1780
|
-
}
|
|
1781
|
-
if (!analysis.evidenceWindow.triggerPolicy.distillRecommended) {
|
|
1782
|
-
result.skipped += triggers.length;
|
|
1783
|
-
await updateTriggers(input.homeDir, triggers, {
|
|
1784
|
-
status: "skipped",
|
|
1785
|
-
updatedAt: now,
|
|
1786
|
-
lastError: "no-distillation-signal",
|
|
1787
|
-
});
|
|
1788
|
-
continue;
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
const batch = createEvolutionDistillationBatch({
|
|
1792
|
-
evidenceWindow: analysis.evidenceWindow,
|
|
1793
|
-
warnings: analysis.warnings,
|
|
1794
|
-
now,
|
|
1795
|
-
});
|
|
1796
|
-
await activateEvolutionDistillationBatch({
|
|
1797
|
-
homeDir: input.homeDir,
|
|
1798
|
-
batch,
|
|
1799
|
-
overwrite: true,
|
|
1800
|
-
});
|
|
1801
|
-
result.consumed += triggers.length;
|
|
1802
|
-
result.batchIds.push(batch.id);
|
|
1803
|
-
await updateTriggers(input.homeDir, triggers, {
|
|
1804
|
-
status: "consumed",
|
|
1805
|
-
updatedAt: now,
|
|
1806
|
-
processedBatchId: batch.id,
|
|
1807
|
-
lastError: null,
|
|
1808
|
-
});
|
|
1809
|
-
} catch (error) {
|
|
1810
|
-
result.failed += triggers.length;
|
|
1811
|
-
const message = sanitizeText(error instanceof Error ? error.message : String(error));
|
|
1812
|
-
warnings.push(message);
|
|
1813
|
-
await updateTriggers(input.homeDir, triggers, {
|
|
1814
|
-
status: "failed",
|
|
1815
|
-
updatedAt: now,
|
|
1816
|
-
lastError: message,
|
|
1817
|
-
});
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
return result;
|
|
1822
|
-
} finally {
|
|
1823
|
-
if (lock !== null) await releaseEvolutionProcessLock(lock);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
function createEvolutionKnowledgeRecord(
|
|
1828
|
-
input: Omit<EvolutionKnowledgeRecord, "schemaVersion">,
|
|
1829
|
-
): EvolutionKnowledgeRecord {
|
|
1830
|
-
const record: EvolutionKnowledgeRecord = {
|
|
1831
|
-
...input,
|
|
1832
|
-
schemaVersion: 1,
|
|
1833
|
-
id: sanitizeId(input.id),
|
|
1834
|
-
title: sanitizeText(input.title),
|
|
1835
|
-
projectKey: sanitizeId(input.projectKey),
|
|
1836
|
-
summary: sanitizeText(input.summary),
|
|
1837
|
-
body: sanitizeText(input.body),
|
|
1838
|
-
roleTags: uniqueSanitizedIds(input.roleTags),
|
|
1839
|
-
tags: uniqueSanitizedIds(input.tags),
|
|
1840
|
-
};
|
|
1841
|
-
validateEvolutionKnowledgeRecord(record);
|
|
1842
|
-
return record;
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
function createEvolutionEvosCase(
|
|
1846
|
-
input: Omit<EvolutionEvosCase, "schemaVersion" | "kind">,
|
|
1847
|
-
): EvolutionEvosCase {
|
|
1848
|
-
const evosCase: EvolutionEvosCase = {
|
|
1849
|
-
...input,
|
|
1850
|
-
schemaVersion: 1,
|
|
1851
|
-
kind: "evos-case",
|
|
1852
|
-
id: sanitizeId(input.id),
|
|
1853
|
-
projectKey: sanitizeId(input.projectKey),
|
|
1854
|
-
title: sanitizeText(input.title),
|
|
1855
|
-
roleTags: uniqueSanitizedIds(input.roleTags),
|
|
1856
|
-
tags: uniqueSanitizedIds(input.tags),
|
|
1857
|
-
trigger: {
|
|
1858
|
-
kind: input.trigger.kind,
|
|
1859
|
-
summary: sanitizeText(input.trigger.summary),
|
|
1860
|
-
},
|
|
1861
|
-
intervention: {
|
|
1862
|
-
summary: sanitizeText(input.intervention.summary),
|
|
1863
|
-
roleIds: uniqueSanitizedIds(input.intervention.roleIds),
|
|
1864
|
-
},
|
|
1865
|
-
result: {
|
|
1866
|
-
summary: sanitizeText(input.result.summary),
|
|
1867
|
-
verificationSignals: input.result.verificationSignals.map(sanitizeText),
|
|
1868
|
-
},
|
|
1869
|
-
expectedFutureBehavior: sanitizeText(input.expectedFutureBehavior),
|
|
1870
|
-
};
|
|
1871
|
-
validateEvolutionEvosCase(evosCase);
|
|
1872
|
-
return evosCase;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
function createEvolutionRepoProposal(
|
|
1876
|
-
input: Omit<EvolutionRepoProposal, "schemaVersion">,
|
|
1877
|
-
): EvolutionRepoProposal {
|
|
1878
|
-
const proposal: EvolutionRepoProposal = {
|
|
1879
|
-
...input,
|
|
1880
|
-
schemaVersion: 1,
|
|
1881
|
-
id: sanitizeId(input.id),
|
|
1882
|
-
projectKey: sanitizeId(input.projectKey),
|
|
1883
|
-
title: sanitizeText(input.title),
|
|
1884
|
-
summary: sanitizeText(input.summary),
|
|
1885
|
-
rationale: sanitizeText(input.rationale),
|
|
1886
|
-
roleTags: uniqueSanitizedIds(input.roleTags),
|
|
1887
|
-
tags: uniqueSanitizedIds(input.tags),
|
|
1888
|
-
plannedFiles: input.plannedFiles.map((file) => ({
|
|
1889
|
-
relativePath: sanitizeRelativePath(file.relativePath),
|
|
1890
|
-
action: file.action,
|
|
1891
|
-
reason: sanitizeText(file.reason),
|
|
1892
|
-
})),
|
|
1893
|
-
};
|
|
1894
|
-
validateEvolutionRepoProposal(proposal);
|
|
1895
|
-
return proposal;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
function validateEvolutionEvosCase(evosCase: EvolutionEvosCase): void {
|
|
1899
|
-
if (!isRecord(evosCase)) throw new Error("Evos case must be an object.");
|
|
1900
|
-
if (evosCase.schemaVersion !== 1) throw new Error("Evos case schemaVersion must be 1.");
|
|
1901
|
-
if (evosCase.kind !== "evos-case") throw new Error("Evos case kind is invalid.");
|
|
1902
|
-
assertEnum("reviewState", evosCase.reviewState, REVIEW_STATES);
|
|
1903
|
-
assertPrivacy(evosCase.privacy);
|
|
1904
|
-
assertProvenance(evosCase.provenance);
|
|
1905
|
-
assertNoForbiddenRawFields(evosCase);
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
function validateEvolutionRepoProposal(proposal: EvolutionRepoProposal): void {
|
|
1909
|
-
if (!isRecord(proposal)) throw new Error("Repo proposal must be an object.");
|
|
1910
|
-
if (proposal.schemaVersion !== 1) throw new Error("Repo proposal schemaVersion must be 1.");
|
|
1911
|
-
assertEnum("kind", proposal.kind, PROPOSAL_KINDS);
|
|
1912
|
-
assertEnum("reviewState", proposal.reviewState, [
|
|
1913
|
-
"pending",
|
|
1914
|
-
"accepted",
|
|
1915
|
-
"rejected",
|
|
1916
|
-
"deferred",
|
|
1917
|
-
"applied",
|
|
1918
|
-
]);
|
|
1919
|
-
if (proposal.apply.autoApply !== false || proposal.apply.requiresExplicitCommand !== true) {
|
|
1920
|
-
throw new Error("Repo proposal must require explicit apply.");
|
|
1921
|
-
}
|
|
1922
|
-
assertPrivacy(proposal.privacy);
|
|
1923
|
-
assertProvenance(proposal.provenance);
|
|
1924
|
-
assertNoForbiddenRawFields(proposal);
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
function validateEvolutionReviewCandidate(candidate: EvolutionReviewCandidate): void {
|
|
1928
|
-
if (!isRecord(candidate)) throw new Error("Review candidate must be an object.");
|
|
1929
|
-
if (candidate.schemaVersion !== 1) {
|
|
1930
|
-
throw new Error("Review candidate schemaVersion must be 1.");
|
|
1931
|
-
}
|
|
1932
|
-
if (candidate.kind !== "evolution-review-candidate") {
|
|
1933
|
-
throw new Error("Review candidate kind is invalid.");
|
|
1934
|
-
}
|
|
1935
|
-
assertString("reviewCandidate.id", candidate.id);
|
|
1936
|
-
assertString("reviewCandidate.projectKey", candidate.projectKey);
|
|
1937
|
-
assertString("reviewCandidate.runId", candidate.runId);
|
|
1938
|
-
assertString("reviewCandidate.createdAt", candidate.createdAt);
|
|
1939
|
-
assertString("reviewCandidate.candidateKind", candidate.candidateKind);
|
|
1940
|
-
assertString("reviewCandidate.title", candidate.title);
|
|
1941
|
-
assertString("reviewCandidate.targetStore", candidate.targetStore);
|
|
1942
|
-
assertString("reviewCandidate.targetPath", candidate.targetPath);
|
|
1943
|
-
assertString("reviewCandidate.stableKey", candidate.stableKey);
|
|
1944
|
-
if (candidate.reviewState !== "needs-human") {
|
|
1945
|
-
throw new Error("Review candidate reviewState must be needs-human.");
|
|
1946
|
-
}
|
|
1947
|
-
if (!Array.isArray(candidate.reasons))
|
|
1948
|
-
throw new Error("Review candidate reasons must be an array.");
|
|
1949
|
-
for (const reason of candidate.reasons) assertString("reviewCandidate.reasons", reason);
|
|
1950
|
-
if (!isRecord(candidate.provenance))
|
|
1951
|
-
throw new Error("Review candidate provenance must be an object.");
|
|
1952
|
-
assertString("reviewCandidate.provenance.runId", candidate.provenance.runId);
|
|
1953
|
-
assertString(
|
|
1954
|
-
"reviewCandidate.provenance.evidenceWindowId",
|
|
1955
|
-
candidate.provenance.evidenceWindowId,
|
|
1956
|
-
);
|
|
1957
|
-
if (candidate.provenance.createdBy !== "evodev") {
|
|
1958
|
-
throw new Error("Review candidate provenance.createdBy must be evodev.");
|
|
1959
|
-
}
|
|
1960
|
-
if (!Array.isArray(candidate.provenance.evidenceRefs)) {
|
|
1961
|
-
throw new Error("Review candidate evidenceRefs must be an array.");
|
|
1962
|
-
}
|
|
1963
|
-
for (const evidenceRef of candidate.provenance.evidenceRefs) {
|
|
1964
|
-
assertString("reviewCandidate.provenance.evidenceRefs", evidenceRef);
|
|
1965
|
-
}
|
|
1966
|
-
if (
|
|
1967
|
-
candidate.provenance.rawLogsStored !== false ||
|
|
1968
|
-
candidate.provenance.rawPromptsStored !== false ||
|
|
1969
|
-
candidate.provenance.sourceDumpsStored !== false ||
|
|
1970
|
-
candidate.provenance.rawCommandOutputStored !== false
|
|
1971
|
-
) {
|
|
1972
|
-
throw new Error("Review candidate provenance must remain metadata-only.");
|
|
1973
|
-
}
|
|
1974
|
-
assertPrivacy(candidate.privacy);
|
|
1975
|
-
assertNoForbiddenRawFields(candidate);
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
function validateEvolutionTriggerRecord(trigger: EvolutionTriggerRecord): void {
|
|
1979
|
-
if (!isRecord(trigger)) throw new Error("Evolution trigger must be an object.");
|
|
1980
|
-
if (trigger.schemaVersion !== 1) throw new Error("Evolution trigger schemaVersion must be 1.");
|
|
1981
|
-
if (trigger.kind !== "evolution-trigger") throw new Error("Evolution trigger kind is invalid.");
|
|
1982
|
-
assertString("trigger.id", trigger.id);
|
|
1983
|
-
assertString("trigger.projectKey", trigger.projectKey);
|
|
1984
|
-
assertString("trigger.runId", trigger.runId);
|
|
1985
|
-
assertEnum("trigger.eventType", trigger.eventType, NORMALIZED_EVENT_TYPES);
|
|
1986
|
-
assertEnum("trigger.triggerStrength", trigger.triggerStrength, TRIGGER_STRENGTHS);
|
|
1987
|
-
assertEnum("trigger.triggerReason", trigger.triggerReason, TRIGGER_REASONS);
|
|
1988
|
-
assertEnum("trigger.status", trigger.status, TRIGGER_STATUSES);
|
|
1989
|
-
assertString("trigger.summary", trigger.summary);
|
|
1990
|
-
assertString("trigger.createdAt", trigger.createdAt);
|
|
1991
|
-
assertString("trigger.updatedAt", trigger.updatedAt);
|
|
1992
|
-
if (typeof trigger.attempts !== "number" || trigger.attempts < 0) {
|
|
1993
|
-
throw new Error("trigger.attempts must be a non-negative number.");
|
|
1994
|
-
}
|
|
1995
|
-
if (trigger.rawContentStored !== false)
|
|
1996
|
-
throw new Error("trigger.rawContentStored must be false.");
|
|
1997
|
-
assertPrivacy(trigger.privacy);
|
|
1998
|
-
assertNoForbiddenRawFields(trigger);
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
async function readExecutionEvidenceEvents(input: {
|
|
2002
|
-
path: string;
|
|
2003
|
-
roleId: string;
|
|
2004
|
-
sourceRefId: string;
|
|
2005
|
-
existingCount: number;
|
|
2006
|
-
}): Promise<{ events: EvolutionEvidenceEvent[]; warnings: string[]; traceRefIds: string[] }> {
|
|
2007
|
-
const raw = await readFile(input.path, "utf8");
|
|
2008
|
-
const lines = raw.split("\n");
|
|
2009
|
-
const events: EvolutionEvidenceEvent[] = [];
|
|
2010
|
-
const warnings: string[] = [];
|
|
2011
|
-
const traceRefIds = new Set<string>();
|
|
2012
|
-
|
|
2013
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
2014
|
-
const line = lines[index]?.trim();
|
|
2015
|
-
if (line === undefined || line === "") continue;
|
|
2016
|
-
|
|
2017
|
-
try {
|
|
2018
|
-
const value = JSON.parse(line) as unknown;
|
|
2019
|
-
if (!isRecord(value) || value.kind !== "evodev-execution-event") continue;
|
|
2020
|
-
const codeAgentTraceRefId = optionalString(value.codeAgentTraceRefId);
|
|
2021
|
-
if (codeAgentTraceRefId !== null) traceRefIds.add(codeAgentTraceRefId);
|
|
2022
|
-
const event = createEvidenceEventFromExecutionEvent({
|
|
2023
|
-
executionEvent: value as unknown as EvoDevExecutionEventV1,
|
|
2024
|
-
roleId: input.roleId,
|
|
2025
|
-
sourceRefId: input.sourceRefId,
|
|
2026
|
-
lineNumber: index + 1,
|
|
2027
|
-
sequence: input.existingCount + events.length + 1,
|
|
2028
|
-
});
|
|
2029
|
-
if (event !== null) events.push(event);
|
|
2030
|
-
} catch {
|
|
2031
|
-
warnings.push(
|
|
2032
|
-
`Skipped malformed execution event JSON at ${basename(input.path)}:${index + 1}.`,
|
|
2033
|
-
);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
return { events, warnings, traceRefIds: [...traceRefIds] };
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
function createEvidenceEventFromExecutionEvent(input: {
|
|
2041
|
-
executionEvent: EvoDevExecutionEventV1;
|
|
2042
|
-
roleId: string;
|
|
2043
|
-
sourceRefId: string;
|
|
2044
|
-
lineNumber: number;
|
|
2045
|
-
sequence: number;
|
|
2046
|
-
}): EvolutionEvidenceEvent | null {
|
|
2047
|
-
const metadata = isRecord(input.executionEvent.metadata) ? input.executionEvent.metadata : {};
|
|
2048
|
-
const phase = optionalString(metadata.phase);
|
|
2049
|
-
const target = optionalString(input.executionEvent.target);
|
|
2050
|
-
const eventType = optionalString(input.executionEvent.eventType);
|
|
2051
|
-
const summarySource =
|
|
2052
|
-
optionalString(input.executionEvent.summary) ?? [target, phase].filter(Boolean).join(" ");
|
|
2053
|
-
if (summarySource.trim() === "") return null;
|
|
2054
|
-
const normalizedType = normalizeEvolutionEventType(eventType);
|
|
2055
|
-
const kind = classifyEvidenceEvent({
|
|
2056
|
-
error: null,
|
|
2057
|
-
summary: summarySource,
|
|
2058
|
-
target,
|
|
2059
|
-
eventType,
|
|
2060
|
-
phase,
|
|
2061
|
-
});
|
|
2062
|
-
const trigger = decideEvolutionTrigger({
|
|
2063
|
-
eventType: normalizedType,
|
|
2064
|
-
summary: summarySource,
|
|
2065
|
-
kind,
|
|
2066
|
-
});
|
|
2067
|
-
|
|
2068
|
-
return {
|
|
2069
|
-
id: `event-${String(input.sequence).padStart(4, "0")}`,
|
|
2070
|
-
kind,
|
|
2071
|
-
eventType: normalizedType,
|
|
2072
|
-
hookEventId: sanitizeOptionalId(input.executionEvent.eventId),
|
|
2073
|
-
occurredAt: optionalString(input.executionEvent.timestamp),
|
|
2074
|
-
summary: sanitizeText(summarySource),
|
|
2075
|
-
roleId: sanitizeOptionalId(input.executionEvent.roleId) ?? sanitizeId(input.roleId),
|
|
2076
|
-
taskId: sanitizeOptionalId(input.executionEvent.taskId),
|
|
2077
|
-
evidenceRef: `${input.sourceRefId}#L${input.lineNumber}`,
|
|
2078
|
-
episodeIds: [],
|
|
2079
|
-
triggerStrength: trigger.strength,
|
|
2080
|
-
triggerReason: trigger.reason,
|
|
2081
|
-
rawContentStored: false,
|
|
2082
|
-
};
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
async function readTraceEvidenceEvents(input: {
|
|
2086
|
-
path: string;
|
|
2087
|
-
roleId: string;
|
|
2088
|
-
sourceRefId: string;
|
|
2089
|
-
existingCount: number;
|
|
2090
|
-
}): Promise<{ events: EvolutionEvidenceEvent[]; warnings: string[] }> {
|
|
2091
|
-
const raw = await readFile(input.path, "utf8");
|
|
2092
|
-
const lines = raw.split("\n");
|
|
2093
|
-
const events: EvolutionEvidenceEvent[] = [];
|
|
2094
|
-
const warnings: string[] = [];
|
|
2095
|
-
|
|
2096
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
2097
|
-
const line = lines[index]?.trim();
|
|
2098
|
-
if (line === undefined || line === "") continue;
|
|
2099
|
-
|
|
2100
|
-
try {
|
|
2101
|
-
const value = JSON.parse(line) as unknown;
|
|
2102
|
-
if (!isRecord(value)) continue;
|
|
2103
|
-
const event = createEvidenceEventFromTrace({
|
|
2104
|
-
trace: value,
|
|
2105
|
-
roleId: input.roleId,
|
|
2106
|
-
sourceRefId: input.sourceRefId,
|
|
2107
|
-
lineNumber: index + 1,
|
|
2108
|
-
sequence: input.existingCount + events.length + 1,
|
|
2109
|
-
});
|
|
2110
|
-
if (event !== null) events.push(event);
|
|
2111
|
-
} catch {
|
|
2112
|
-
warnings.push(`Skipped malformed trace JSON at ${basename(input.path)}:${index + 1}.`);
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
return { events, warnings };
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
function createEvidenceEventFromTrace(input: {
|
|
2120
|
-
trace: Record<string, unknown>;
|
|
2121
|
-
roleId: string;
|
|
2122
|
-
sourceRefId: string;
|
|
2123
|
-
lineNumber: number;
|
|
2124
|
-
sequence: number;
|
|
2125
|
-
}): EvolutionEvidenceEvent | null {
|
|
2126
|
-
const phase = optionalString(input.trace.phase);
|
|
2127
|
-
const target = optionalString(input.trace.target);
|
|
2128
|
-
const traceEvent = isRecord(input.trace.event) ? input.trace.event : null;
|
|
2129
|
-
const result = isRecord(input.trace.result) ? input.trace.result : null;
|
|
2130
|
-
const error = optionalString(input.trace.error);
|
|
2131
|
-
const eventSummary = traceEvent === null ? null : optionalString(traceEvent.summary);
|
|
2132
|
-
const eventType = traceEvent === null ? null : optionalString(traceEvent.type);
|
|
2133
|
-
const eventId = traceEvent === null ? null : optionalString(traceEvent.eventId);
|
|
2134
|
-
const resultSummary = result === null ? null : optionalString(result.summary);
|
|
2135
|
-
const summarySource =
|
|
2136
|
-
error ?? resultSummary ?? eventSummary ?? [target, phase].filter(Boolean).join(" ");
|
|
2137
|
-
if (summarySource.trim() === "") return null;
|
|
2138
|
-
const normalizedType = normalizeEvolutionEventType(eventType);
|
|
2139
|
-
const trigger = decideEvolutionTrigger({
|
|
2140
|
-
eventType: normalizedType,
|
|
2141
|
-
summary: summarySource,
|
|
2142
|
-
kind: classifyEvidenceEvent({
|
|
2143
|
-
error,
|
|
2144
|
-
summary: summarySource,
|
|
2145
|
-
target,
|
|
2146
|
-
eventType,
|
|
2147
|
-
phase,
|
|
2148
|
-
}),
|
|
2149
|
-
});
|
|
2150
|
-
|
|
2151
|
-
return {
|
|
2152
|
-
id: `event-${String(input.sequence).padStart(4, "0")}`,
|
|
2153
|
-
kind: classifyEvidenceEvent({
|
|
2154
|
-
error,
|
|
2155
|
-
summary: summarySource,
|
|
2156
|
-
target,
|
|
2157
|
-
eventType,
|
|
2158
|
-
phase,
|
|
2159
|
-
}),
|
|
2160
|
-
eventType: normalizedType,
|
|
2161
|
-
hookEventId: eventId === null ? null : sanitizeId(eventId),
|
|
2162
|
-
occurredAt: optionalString(input.trace.timestamp),
|
|
2163
|
-
summary: sanitizeText(summarySource),
|
|
2164
|
-
roleId: sanitizeId(input.roleId),
|
|
2165
|
-
taskId: extractTraceTaskId(input.trace),
|
|
2166
|
-
evidenceRef: `${input.sourceRefId}#L${input.lineNumber}`,
|
|
2167
|
-
episodeIds: [],
|
|
2168
|
-
triggerStrength: trigger.strength,
|
|
2169
|
-
triggerReason: trigger.reason,
|
|
2170
|
-
rawContentStored: false,
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
function classifyEvidenceEvent(input: {
|
|
2175
|
-
error: string | null;
|
|
2176
|
-
summary: string;
|
|
2177
|
-
target: string | null;
|
|
2178
|
-
eventType: string | null;
|
|
2179
|
-
phase: string | null;
|
|
2180
|
-
}): EvolutionEvidenceEventKind {
|
|
2181
|
-
const haystack = [input.summary, input.target, input.eventType, input.phase]
|
|
2182
|
-
.filter(Boolean)
|
|
2183
|
-
.join(" ");
|
|
2184
|
-
const normalizedType = normalizeEvolutionEventType(input.eventType);
|
|
2185
|
-
if (
|
|
2186
|
-
normalizedType === "StopFailure" ||
|
|
2187
|
-
normalizedType === "PostToolUseFailure" ||
|
|
2188
|
-
normalizedType === "PermissionDenied"
|
|
2189
|
-
) {
|
|
2190
|
-
return "error";
|
|
2191
|
-
}
|
|
2192
|
-
if (
|
|
2193
|
-
normalizedType === "PreToolUse" ||
|
|
2194
|
-
normalizedType === "PostToolUse" ||
|
|
2195
|
-
normalizedType === "PermissionRequest" ||
|
|
2196
|
-
normalizedType === "PostToolBatch"
|
|
2197
|
-
) {
|
|
2198
|
-
return "tool-call";
|
|
2199
|
-
}
|
|
2200
|
-
if (normalizedType === "SubagentStart" || normalizedType === "SubagentStop") return "subagent";
|
|
2201
|
-
if (
|
|
2202
|
-
normalizedType === "SessionStart" ||
|
|
2203
|
-
normalizedType === "SessionEnd" ||
|
|
2204
|
-
normalizedType === "UserPromptSubmit" ||
|
|
2205
|
-
normalizedType === "Stop" ||
|
|
2206
|
-
normalizedType === "TaskCreated" ||
|
|
2207
|
-
normalizedType === "TaskCompleted"
|
|
2208
|
-
) {
|
|
2209
|
-
return "run-state";
|
|
2210
|
-
}
|
|
2211
|
-
if (input.error !== null || /error|failed|failure|issue/i.test(haystack)) return "error";
|
|
2212
|
-
if (/skill/i.test(haystack)) return "skill-call";
|
|
2213
|
-
if (/subagent|agent|team|role/i.test(haystack)) return "subagent";
|
|
2214
|
-
if (/tool/i.test(haystack)) return "tool-call";
|
|
2215
|
-
if (/verify|verification|test|lint|typecheck|build/i.test(haystack)) return "verification";
|
|
2216
|
-
if (/review/i.test(haystack)) return "review";
|
|
2217
|
-
if (/completed|started|stopped|running/i.test(haystack)) return "run-state";
|
|
2218
|
-
return "trace";
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
function buildEvolutionEpisodeAnalysis(events: EvolutionEvidenceEvent[]): {
|
|
2222
|
-
events: EvolutionEvidenceEvent[];
|
|
2223
|
-
episodes: EvolutionEpisode[];
|
|
2224
|
-
triggerPolicy: EvolutionTriggerPolicy;
|
|
2225
|
-
} {
|
|
2226
|
-
const episodeLinks = new Map<string, string[]>();
|
|
2227
|
-
const episodes: EvolutionEpisode[] = [];
|
|
2228
|
-
const addEpisode = (input: Omit<EvolutionEpisode, "rawContentStored">) => {
|
|
2229
|
-
if (input.eventIds.length === 0) return;
|
|
2230
|
-
const episode: EvolutionEpisode = { ...input, rawContentStored: false };
|
|
2231
|
-
episodes.push(episode);
|
|
2232
|
-
for (const eventId of episode.eventIds) {
|
|
2233
|
-
episodeLinks.set(eventId, [...(episodeLinks.get(eventId) ?? []), episode.id]);
|
|
2234
|
-
}
|
|
2235
|
-
};
|
|
2236
|
-
|
|
2237
|
-
if (events.length > 0) {
|
|
2238
|
-
const strongest = strongestTrigger(events);
|
|
2239
|
-
addEpisode({
|
|
2240
|
-
id: "episode-session-0001",
|
|
2241
|
-
kind: "session",
|
|
2242
|
-
status: events.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
|
|
2243
|
-
roleId: null,
|
|
2244
|
-
taskId: null,
|
|
2245
|
-
startedAt: events[0]?.occurredAt ?? null,
|
|
2246
|
-
endedAt: events.at(-1)?.occurredAt ?? null,
|
|
2247
|
-
eventIds: events.map((event) => event.id),
|
|
2248
|
-
triggerStrength: strongest.strength,
|
|
2249
|
-
triggerReason: strongest.reason,
|
|
2250
|
-
summary: `Session episode with ${events.length} metadata event(s).`,
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
let turnEvents: EvolutionEvidenceEvent[] = [];
|
|
2255
|
-
let turnIndex = 1;
|
|
2256
|
-
const closeTurn = (status: EvolutionEpisodeStatus) => {
|
|
2257
|
-
if (turnEvents.length === 0) return;
|
|
2258
|
-
const strongest = strongestTrigger(turnEvents);
|
|
2259
|
-
addEpisode({
|
|
2260
|
-
id: `episode-turn-${String(turnIndex).padStart(4, "0")}`,
|
|
2261
|
-
kind: "turn",
|
|
2262
|
-
status,
|
|
2263
|
-
roleId: firstRoleId(turnEvents),
|
|
2264
|
-
taskId: firstTaskId(turnEvents),
|
|
2265
|
-
startedAt: turnEvents[0]?.occurredAt ?? null,
|
|
2266
|
-
endedAt: turnEvents.at(-1)?.occurredAt ?? null,
|
|
2267
|
-
eventIds: turnEvents.map((event) => event.id),
|
|
2268
|
-
triggerStrength: strongest.strength,
|
|
2269
|
-
triggerReason: strongest.reason,
|
|
2270
|
-
summary: `Turn episode closed with ${turnEvents.length} metadata event(s).`,
|
|
2271
|
-
});
|
|
2272
|
-
turnEvents = [];
|
|
2273
|
-
turnIndex += 1;
|
|
2274
|
-
};
|
|
2275
|
-
for (const event of events) {
|
|
2276
|
-
if (event.eventType === "UserPromptSubmit") {
|
|
2277
|
-
closeTurn("open");
|
|
2278
|
-
turnEvents = [event];
|
|
2279
|
-
continue;
|
|
2280
|
-
}
|
|
2281
|
-
if (turnEvents.length > 0) turnEvents.push(event);
|
|
2282
|
-
if (event.eventType === "Stop" || event.eventType === "StopFailure") {
|
|
2283
|
-
closeTurn(event.eventType === "StopFailure" ? "failed" : "closed");
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
closeTurn("open");
|
|
2287
|
-
|
|
2288
|
-
const taskIds = uniqueSanitizedIds(
|
|
2289
|
-
events.flatMap((event) => (event.taskId === null ? [] : [event.taskId])),
|
|
2290
|
-
);
|
|
2291
|
-
taskIds.forEach((taskId, index) => {
|
|
2292
|
-
const taskEvents = events.filter((event) => event.taskId === taskId);
|
|
2293
|
-
const strongest = strongestTrigger(taskEvents);
|
|
2294
|
-
addEpisode({
|
|
2295
|
-
id: `episode-task-${String(index + 1).padStart(4, "0")}`,
|
|
2296
|
-
kind: "task",
|
|
2297
|
-
status: taskEvents.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
|
|
2298
|
-
roleId: firstRoleId(taskEvents),
|
|
2299
|
-
taskId,
|
|
2300
|
-
startedAt: taskEvents[0]?.occurredAt ?? null,
|
|
2301
|
-
endedAt: taskEvents.at(-1)?.occurredAt ?? null,
|
|
2302
|
-
eventIds: taskEvents.map((event) => event.id),
|
|
2303
|
-
triggerStrength: strongest.strength,
|
|
2304
|
-
triggerReason: strongest.reason,
|
|
2305
|
-
summary: `Task episode ${taskId} contains ${taskEvents.length} metadata event(s).`,
|
|
2306
|
-
});
|
|
2307
|
-
});
|
|
2308
|
-
|
|
2309
|
-
const roleIds = uniqueSanitizedIds(
|
|
2310
|
-
events.flatMap((event) => (event.roleId === null ? [] : [event.roleId])),
|
|
2311
|
-
);
|
|
2312
|
-
roleIds.forEach((roleId, index) => {
|
|
2313
|
-
const roleEvents = events.filter((event) => event.roleId === roleId);
|
|
2314
|
-
const strongest = strongestTrigger(roleEvents);
|
|
2315
|
-
addEpisode({
|
|
2316
|
-
id: `episode-role-${String(index + 1).padStart(4, "0")}`,
|
|
2317
|
-
kind: "role",
|
|
2318
|
-
status: roleEvents.some((event) => event.eventType === "StopFailure") ? "failed" : "closed",
|
|
2319
|
-
roleId,
|
|
2320
|
-
taskId: firstTaskId(roleEvents),
|
|
2321
|
-
startedAt: roleEvents[0]?.occurredAt ?? null,
|
|
2322
|
-
endedAt: roleEvents.at(-1)?.occurredAt ?? null,
|
|
2323
|
-
eventIds: roleEvents.map((event) => event.id),
|
|
2324
|
-
triggerStrength: strongest.strength,
|
|
2325
|
-
triggerReason: strongest.reason,
|
|
2326
|
-
summary: `Role episode ${roleId} contains ${roleEvents.length} metadata event(s).`,
|
|
2327
|
-
});
|
|
2328
|
-
});
|
|
2329
|
-
|
|
2330
|
-
return {
|
|
2331
|
-
events: events.map((event) => ({ ...event, episodeIds: episodeLinks.get(event.id) ?? [] })),
|
|
2332
|
-
episodes,
|
|
2333
|
-
triggerPolicy: createTriggerPolicy(events),
|
|
2334
|
-
};
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function normalizeEvolutionEventType(
|
|
2338
|
-
value: string | null | undefined,
|
|
2339
|
-
): EvolutionNormalizedEventType {
|
|
2340
|
-
if (value === "AgentStop") return "SubagentStop";
|
|
2341
|
-
if (typeof value === "string" && (NORMALIZED_EVENT_TYPES as readonly string[]).includes(value)) {
|
|
2342
|
-
return value as EvolutionNormalizedEventType;
|
|
2343
|
-
}
|
|
2344
|
-
return "unknown";
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
function decideEvolutionTrigger(input: {
|
|
2348
|
-
eventType: EvolutionNormalizedEventType;
|
|
2349
|
-
summary: string;
|
|
2350
|
-
kind: EvolutionEvidenceEventKind;
|
|
2351
|
-
}): { strength: EvolutionTriggerStrength; reason: EvolutionTriggerReason } {
|
|
2352
|
-
if (input.eventType === "TaskCompleted") return { strength: "strong", reason: "task-completed" };
|
|
2353
|
-
if (input.eventType === "StopFailure") return { strength: "strong", reason: "stop-failure" };
|
|
2354
|
-
if (input.eventType === "PostToolUseFailure")
|
|
2355
|
-
return { strength: "strong", reason: "tool-failure" };
|
|
2356
|
-
if (input.eventType === "PermissionDenied")
|
|
2357
|
-
return { strength: "strong", reason: "permission-denied" };
|
|
2358
|
-
if (input.eventType === "Stop") return { strength: "conditional", reason: "turn-completed" };
|
|
2359
|
-
if (input.eventType === "SessionEnd") return { strength: "conditional", reason: "session-ended" };
|
|
2360
|
-
if (input.eventType === "SubagentStop")
|
|
2361
|
-
return { strength: "conditional", reason: "role-completed" };
|
|
2362
|
-
if (input.kind === "error" || /fail|failed|failure|error|issue/i.test(input.summary)) {
|
|
2363
|
-
return { strength: "strong", reason: "failure-signal" };
|
|
2364
|
-
}
|
|
2365
|
-
return { strength: "none", reason: "none" };
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
function createTriggerPolicy(events: EvolutionEvidenceEvent[]): EvolutionTriggerPolicy {
|
|
2369
|
-
const failures = events.filter((event) => event.kind === "error").length;
|
|
2370
|
-
const verifications = events.filter((event) => event.kind === "verification").length;
|
|
2371
|
-
const roleLifecycle = events.filter((event) => event.kind === "subagent").length;
|
|
2372
|
-
const skillCalls = events.filter((event) => event.kind === "skill-call").length;
|
|
2373
|
-
const strongest = strongestTrigger(events);
|
|
2374
|
-
const reasons = uniqueTriggerReasons(
|
|
2375
|
-
events.flatMap((event) => (event.triggerReason === "none" ? [] : [event.triggerReason])),
|
|
2376
|
-
);
|
|
2377
|
-
const hasSignal = failures > 0 || verifications > 0 || roleLifecycle > 0 || skillCalls > 0;
|
|
2378
|
-
return {
|
|
2379
|
-
strongest: strongest.strength,
|
|
2380
|
-
reasons,
|
|
2381
|
-
distillRecommended:
|
|
2382
|
-
strongest.strength === "strong" || (strongest.strength === "conditional" && hasSignal),
|
|
2383
|
-
evidenceSignals: { failures, verifications, roleLifecycle, skillCalls },
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2387
|
-
function strongestTrigger(events: EvolutionEvidenceEvent[]): {
|
|
2388
|
-
strength: EvolutionTriggerStrength;
|
|
2389
|
-
reason: EvolutionTriggerReason;
|
|
2390
|
-
} {
|
|
2391
|
-
const strong = events.find((event) => event.triggerStrength === "strong");
|
|
2392
|
-
if (strong !== undefined) return { strength: "strong", reason: strong.triggerReason };
|
|
2393
|
-
const conditional = events.find((event) => event.triggerStrength === "conditional");
|
|
2394
|
-
if (conditional !== undefined) {
|
|
2395
|
-
return { strength: "conditional", reason: conditional.triggerReason };
|
|
2396
|
-
}
|
|
2397
|
-
return { strength: "none", reason: "none" };
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
function uniqueTriggerReasons(values: EvolutionTriggerReason[]): EvolutionTriggerReason[] {
|
|
2401
|
-
return [...new Set(values)].filter((reason) => reason !== "none");
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
function chooseEvosTriggerKind(
|
|
2405
|
-
evidenceWindow: EvolutionEvidenceWindow,
|
|
2406
|
-
): EvolutionEvosCase["trigger"]["kind"] {
|
|
2407
|
-
if (evidenceWindow.triggerPolicy.reasons.includes("task-completed")) return "task-completion";
|
|
2408
|
-
if (evidenceWindow.triggerPolicy.reasons.includes("turn-completed")) return "session-completion";
|
|
2409
|
-
if (evidenceWindow.triggerPolicy.reasons.includes("session-ended")) return "session-completion";
|
|
2410
|
-
return "manual";
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
function createTriggerSummary(evidenceWindow: EvolutionEvidenceWindow): string {
|
|
2414
|
-
const reasons = evidenceWindow.triggerPolicy.reasons.join(", ") || "manual";
|
|
2415
|
-
return sanitizeText(
|
|
2416
|
-
`Evolution distillation used ${evidenceWindow.triggerPolicy.strongest} trigger evidence: ${reasons}.`,
|
|
2417
|
-
);
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
function createTriggerDecisionSummary(
|
|
2421
|
-
eventType: EvolutionNormalizedEventType,
|
|
2422
|
-
strength: EvolutionTriggerStrength,
|
|
2423
|
-
reason: EvolutionTriggerReason,
|
|
2424
|
-
): string {
|
|
2425
|
-
return `Hook event ${eventType} classified as ${strength} evolution trigger (${reason}).`;
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
function firstRoleId(events: EvolutionEvidenceEvent[]): string | null {
|
|
2429
|
-
return events.find((event) => event.roleId !== null)?.roleId ?? null;
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
function firstTaskId(events: EvolutionEvidenceEvent[]): string | null {
|
|
2433
|
-
return events.find((event) => event.taskId !== null)?.taskId ?? null;
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
function extractTraceTaskId(trace: Record<string, unknown>): string | null {
|
|
2437
|
-
const event = isRecord(trace.event) ? trace.event : {};
|
|
2438
|
-
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
2439
|
-
const input = isRecord(trace.input) ? trace.input : {};
|
|
2440
|
-
const payload = isRecord(input.payload) ? input.payload : {};
|
|
2441
|
-
return sanitizeOptionalId(
|
|
2442
|
-
metadata.taskId ??
|
|
2443
|
-
metadata.task_id ??
|
|
2444
|
-
payload.taskId ??
|
|
2445
|
-
payload.task_id ??
|
|
2446
|
-
payload.taskID ??
|
|
2447
|
-
null,
|
|
2448
|
-
);
|
|
2449
|
-
}
|
|
2450
|
-
|
|
2451
|
-
function createRunSummary(
|
|
2452
|
-
evidenceWindow: EvolutionEvidenceWindow,
|
|
2453
|
-
roleIds: string[],
|
|
2454
|
-
failedSignalCount: number,
|
|
2455
|
-
): string {
|
|
2456
|
-
const failureClause =
|
|
2457
|
-
failedSignalCount === 0
|
|
2458
|
-
? "No failure signal was detected in metadata summaries."
|
|
2459
|
-
: `${failedSignalCount} potential failure or issue signal(s) were detected.`;
|
|
2460
|
-
return sanitizeText(
|
|
2461
|
-
`Run ${evidenceWindow.runId} produced ${evidenceWindow.events.length} redacted event(s) across ${roleIds.length} role(s). ${failureClause}`,
|
|
2462
|
-
);
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
function createProvenance(
|
|
2466
|
-
evidenceWindow: EvolutionEvidenceWindow,
|
|
2467
|
-
createdAt: string,
|
|
2468
|
-
sourceRefs: string[],
|
|
2469
|
-
): EvolutionKnowledgeRecord["provenance"] {
|
|
2470
|
-
return {
|
|
2471
|
-
runId: evidenceWindow.runId,
|
|
2472
|
-
taskId: evidenceWindow.taskId,
|
|
2473
|
-
evidenceWindowId: evidenceWindow.id,
|
|
2474
|
-
sourceRefs,
|
|
2475
|
-
createdAt,
|
|
2476
|
-
createdBy: "evodev",
|
|
2477
|
-
rawLogsStored: false,
|
|
2478
|
-
rawPromptsStored: false,
|
|
2479
|
-
sourceDumpsStored: false,
|
|
2480
|
-
rawCommandOutputStored: false,
|
|
2481
|
-
};
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
function parseKnowledgeRecord(value: unknown): EvolutionKnowledgeRecord {
|
|
2485
|
-
validateEvolutionKnowledgeRecord(value as EvolutionKnowledgeRecord);
|
|
2486
|
-
return value as EvolutionKnowledgeRecord;
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
function parseEvosCase(value: unknown): EvolutionEvosCase {
|
|
2490
|
-
validateEvolutionEvosCase(value as EvolutionEvosCase);
|
|
2491
|
-
return value as EvolutionEvosCase;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
function parseRepoProposal(value: unknown): EvolutionRepoProposal {
|
|
2495
|
-
validateEvolutionRepoProposal(value as EvolutionRepoProposal);
|
|
2496
|
-
return value as EvolutionRepoProposal;
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
function parseReviewCandidate(value: unknown): EvolutionReviewCandidate {
|
|
2500
|
-
validateEvolutionReviewCandidate(value as EvolutionReviewCandidate);
|
|
2501
|
-
return value as EvolutionReviewCandidate;
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
|
-
function parseTrigger(value: unknown): EvolutionTriggerRecord {
|
|
2505
|
-
validateEvolutionTriggerRecord(value as EvolutionTriggerRecord);
|
|
2506
|
-
return value as EvolutionTriggerRecord;
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
async function readJsonFiles<T>(dir: string, parse: (value: unknown) => T): Promise<T[]> {
|
|
2510
|
-
if (!(await pathExists(dir))) return [];
|
|
2511
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
2512
|
-
const values: T[] = [];
|
|
2513
|
-
for (const entry of entries) {
|
|
2514
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
2515
|
-
values.push(parse(JSON.parse(await readFile(join(dir, entry.name), "utf8")) as unknown));
|
|
2516
|
-
}
|
|
2517
|
-
return values;
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
|
-
function groupTriggersByRun(triggers: EvolutionTriggerRecord[]): EvolutionTriggerRecord[][] {
|
|
2521
|
-
const groups = new Map<string, EvolutionTriggerRecord[]>();
|
|
2522
|
-
for (const trigger of triggers) {
|
|
2523
|
-
const key = `${trigger.projectKey}\0${trigger.runId}`;
|
|
2524
|
-
groups.set(key, [...(groups.get(key) ?? []), trigger]);
|
|
2525
|
-
}
|
|
2526
|
-
return [...groups.values()];
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
async function acquireEvolutionProcessLock(
|
|
2530
|
-
homeDir: string,
|
|
2531
|
-
now: string,
|
|
2532
|
-
): Promise<{ path: string } | null> {
|
|
2533
|
-
const paths = resolveEvoDevPaths(homeDir);
|
|
2534
|
-
const lockPath = join(paths.stateDir, "evolution", ".process.lock");
|
|
2535
|
-
await mkdir(dirname(lockPath), { recursive: true });
|
|
2536
|
-
try {
|
|
2537
|
-
await writeFile(
|
|
2538
|
-
lockPath,
|
|
2539
|
-
`${JSON.stringify(
|
|
2540
|
-
{
|
|
2541
|
-
schemaVersion: 1,
|
|
2542
|
-
kind: "evolution-process-lock",
|
|
2543
|
-
createdAt: now,
|
|
2544
|
-
pid: process.pid,
|
|
2545
|
-
},
|
|
2546
|
-
null,
|
|
2547
|
-
2,
|
|
2548
|
-
)}\n`,
|
|
2549
|
-
{ encoding: "utf8", flag: "wx" },
|
|
2550
|
-
);
|
|
2551
|
-
return { path: lockPath };
|
|
2552
|
-
} catch (error) {
|
|
2553
|
-
if (!isFileExistsError(error)) throw error;
|
|
2554
|
-
const current = await stat(lockPath).catch(() => null);
|
|
2555
|
-
if (current !== null && Date.now() - current.mtimeMs > PROCESS_LOCK_STALE_MS) {
|
|
2556
|
-
await rm(lockPath, { force: true });
|
|
2557
|
-
return acquireEvolutionProcessLock(homeDir, now);
|
|
2558
|
-
}
|
|
2559
|
-
return null;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
async function releaseEvolutionProcessLock(lock: { path: string }): Promise<void> {
|
|
2564
|
-
await rm(lock.path, { force: true });
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
async function updateTriggers(
|
|
2568
|
-
homeDir: string,
|
|
2569
|
-
triggers: EvolutionTriggerRecord[],
|
|
2570
|
-
patch: {
|
|
2571
|
-
status: EvolutionTriggerStatus;
|
|
2572
|
-
updatedAt: string;
|
|
2573
|
-
attempts?: ((trigger: EvolutionTriggerRecord) => number) | number;
|
|
2574
|
-
processedBatchId?: string | null;
|
|
2575
|
-
lastError?: string | null;
|
|
2576
|
-
},
|
|
2577
|
-
): Promise<void> {
|
|
2578
|
-
for (const trigger of triggers) {
|
|
2579
|
-
const next: EvolutionTriggerRecord = {
|
|
2580
|
-
...trigger,
|
|
2581
|
-
status: patch.status,
|
|
2582
|
-
updatedAt: patch.updatedAt,
|
|
2583
|
-
attempts:
|
|
2584
|
-
typeof patch.attempts === "function"
|
|
2585
|
-
? patch.attempts(trigger)
|
|
2586
|
-
: (patch.attempts ?? trigger.attempts),
|
|
2587
|
-
processedBatchId:
|
|
2588
|
-
patch.processedBatchId === undefined ? trigger.processedBatchId : patch.processedBatchId,
|
|
2589
|
-
lastError: patch.lastError === undefined ? trigger.lastError : patch.lastError,
|
|
2590
|
-
};
|
|
2591
|
-
validateEvolutionTriggerRecord(next);
|
|
2592
|
-
const paths = resolveEvolutionPaths({
|
|
2593
|
-
homeDir,
|
|
2594
|
-
projectKey: next.projectKey,
|
|
2595
|
-
runId: next.runId,
|
|
2596
|
-
});
|
|
2597
|
-
await writeJson(join(paths.triggersDir, `${next.id}.json`), next, { overwrite: true });
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
function isFileExistsError(error: unknown): boolean {
|
|
2602
|
-
return (
|
|
2603
|
-
error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "EEXIST"
|
|
2604
|
-
);
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
async function listDirectoryNames(dir: string): Promise<string[]> {
|
|
2608
|
-
if (!(await pathExists(dir))) return [];
|
|
2609
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
2610
|
-
return entries
|
|
2611
|
-
.filter((entry) => entry.isDirectory())
|
|
2612
|
-
.map((entry) => entry.name)
|
|
2613
|
-
.sort();
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
function uniqueSorted(values: string[]): string[] {
|
|
2617
|
-
return [...new Set(values)].sort();
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
async function writeJson(
|
|
2621
|
-
path: string,
|
|
2622
|
-
value: unknown,
|
|
2623
|
-
options: { overwrite: boolean },
|
|
2624
|
-
): Promise<void> {
|
|
2625
|
-
await mkdir(dirname(path), { recursive: true });
|
|
2626
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, {
|
|
2627
|
-
encoding: "utf8",
|
|
2628
|
-
flag: options.overwrite ? "w" : "wx",
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
function resolveEvolutionProjectKey(input: EvolutionAnalyzeInput): string {
|
|
2633
|
-
if (input.projectKey !== undefined) return sanitizeId(input.projectKey);
|
|
2634
|
-
if (input.projectDir !== undefined) return resolveProjectLogKey(input.homeDir, input.projectDir);
|
|
2635
|
-
throw new Error("Missing project scope: pass --project <projectKey> or --project-dir <path>.");
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
function collectRoleIds(evidenceWindow: EvolutionEvidenceWindow): string[] {
|
|
2639
|
-
return uniqueSanitizedIds([
|
|
2640
|
-
...evidenceWindow.sourceRefs.flatMap((sourceRef) =>
|
|
2641
|
-
sourceRef.roleId === null ? [] : [sourceRef.roleId],
|
|
2642
|
-
),
|
|
2643
|
-
...evidenceWindow.events.flatMap((event) => (event.roleId === null ? [] : [event.roleId])),
|
|
2644
|
-
]);
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
function createStableId(prefix: string, parts: string[]): string {
|
|
2648
|
-
const hash = createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
2649
|
-
return `${prefix}-${hash}`;
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
function createPrivacyFields(): EvolutionPrivacyFields {
|
|
2653
|
-
return {
|
|
2654
|
-
classification: "local-private",
|
|
2655
|
-
rawPromptsStored: false,
|
|
2656
|
-
rawLogsStored: false,
|
|
2657
|
-
sourceDumpsStored: false,
|
|
2658
|
-
rawCommandOutputStored: false,
|
|
2659
|
-
secretsStored: false,
|
|
2660
|
-
internalLinksStored: false,
|
|
2661
|
-
};
|
|
2662
|
-
}
|
|
2663
|
-
|
|
2664
|
-
function assertPrivacy(privacy: EvolutionPrivacyFields): void {
|
|
2665
|
-
if (!isRecord(privacy)) throw new Error("privacy must be an object.");
|
|
2666
|
-
if (privacy.classification !== "local-private") {
|
|
2667
|
-
throw new Error("privacy.classification must be local-private.");
|
|
2668
|
-
}
|
|
2669
|
-
if (
|
|
2670
|
-
privacy.rawPromptsStored !== false ||
|
|
2671
|
-
privacy.rawLogsStored !== false ||
|
|
2672
|
-
privacy.sourceDumpsStored !== false ||
|
|
2673
|
-
privacy.rawCommandOutputStored !== false ||
|
|
2674
|
-
privacy.secretsStored !== false ||
|
|
2675
|
-
privacy.internalLinksStored !== false
|
|
2676
|
-
) {
|
|
2677
|
-
throw new Error("Evolution privacy fields must remain metadata-only and raw-content-free.");
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
function assertProvenance(provenance: EvolutionKnowledgeRecord["provenance"]): void {
|
|
2682
|
-
if (!isRecord(provenance)) throw new Error("provenance must be an object.");
|
|
2683
|
-
assertString("provenance.runId", provenance.runId);
|
|
2684
|
-
assertString("provenance.evidenceWindowId", provenance.evidenceWindowId);
|
|
2685
|
-
assertString("provenance.createdAt", provenance.createdAt);
|
|
2686
|
-
if (provenance.createdBy !== "evodev") throw new Error("provenance.createdBy must be evodev.");
|
|
2687
|
-
if (
|
|
2688
|
-
provenance.rawLogsStored !== false ||
|
|
2689
|
-
provenance.rawPromptsStored !== false ||
|
|
2690
|
-
provenance.sourceDumpsStored !== false ||
|
|
2691
|
-
provenance.rawCommandOutputStored !== false
|
|
2692
|
-
) {
|
|
2693
|
-
throw new Error(
|
|
2694
|
-
"Evolution provenance cannot store raw logs, prompts, source, or command output.",
|
|
2695
|
-
);
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
function assertEnum<T extends string>(
|
|
2700
|
-
field: string,
|
|
2701
|
-
value: unknown,
|
|
2702
|
-
allowed: readonly T[],
|
|
2703
|
-
): asserts value is T {
|
|
2704
|
-
if (typeof value !== "string" || !allowed.includes(value as T)) {
|
|
2705
|
-
throw new Error(`${field} must be one of: ${allowed.join(", ")}`);
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
function assertString(field: string, value: unknown): asserts value is string {
|
|
2710
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
2711
|
-
throw new Error(`${field} must be a non-empty string.`);
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
function assertNoForbiddenRawFields(value: unknown, path = ""): void {
|
|
2716
|
-
if (value === null || value === undefined) return;
|
|
2717
|
-
if (typeof value === "string") {
|
|
2718
|
-
if (SENSITIVE_TEXT_PATTERN.test(value)) {
|
|
2719
|
-
throw new Error(`Evolution record contains sensitive content at ${path || "value"}.`);
|
|
2720
|
-
}
|
|
2721
|
-
return;
|
|
2722
|
-
}
|
|
2723
|
-
if (typeof value !== "object") return;
|
|
2724
|
-
if (Array.isArray(value)) {
|
|
2725
|
-
value.forEach((item, index) => assertNoForbiddenRawFields(item, `${path}[${index}]`));
|
|
2726
|
-
return;
|
|
2727
|
-
}
|
|
2728
|
-
for (const [key, child] of Object.entries(value)) {
|
|
2729
|
-
const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2730
|
-
if (FORBIDDEN_RAW_KEYS.has(normalizedKey)) {
|
|
2731
|
-
throw new Error(
|
|
2732
|
-
`Evolution record contains forbidden raw field: ${path ? `${path}.` : ""}${key}`,
|
|
2733
|
-
);
|
|
2734
|
-
}
|
|
2735
|
-
assertNoForbiddenRawFields(child, path ? `${path}.${key}` : key);
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
function sanitizeText(value: string): string {
|
|
2740
|
-
return value
|
|
2741
|
-
.replace(SENSITIVE_TEXT_REPLACE_PATTERN, "[redacted]")
|
|
2742
|
-
.replace(/\s+/g, " ")
|
|
2743
|
-
.trim()
|
|
2744
|
-
.slice(0, MAX_TEXT_LENGTH);
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
function sanitizeId(value: string): string {
|
|
2748
|
-
return (
|
|
2749
|
-
value
|
|
2750
|
-
.replace(/[^a-zA-Z0-9._/-]/g, "-")
|
|
2751
|
-
.replace(/[\\/]+/g, "-")
|
|
2752
|
-
.slice(0, 160) || "local"
|
|
2753
|
-
);
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
function sanitizeStorageId(field: string, value: string): string {
|
|
2757
|
-
const sanitized = sanitizeId(value);
|
|
2758
|
-
if (sanitized === "." || sanitized === "..") {
|
|
2759
|
-
throw new Error(`Invalid evolution ${field}: ${value}`);
|
|
2760
|
-
}
|
|
2761
|
-
if (sanitized.includes("/") || sanitized.includes("\\")) {
|
|
2762
|
-
throw new Error(`Invalid evolution ${field}: ${value}`);
|
|
2763
|
-
}
|
|
2764
|
-
return sanitized;
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
function assertPathDescendant(root: string, candidate: string, field: string): void {
|
|
2768
|
-
const normalizedRoot = resolve(root);
|
|
2769
|
-
const normalizedCandidate = resolve(candidate);
|
|
2770
|
-
const relativePath = relative(normalizedRoot, normalizedCandidate);
|
|
2771
|
-
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2772
|
-
throw new Error(`Evolution path ${field} escaped expected root.`);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
function sanitizeOptionalId(value: unknown): string | null {
|
|
2777
|
-
if (typeof value !== "string" || value.trim() === "") return null;
|
|
2778
|
-
return sanitizeId(value);
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
function uniqueSanitizedIds(values: string[]): string[] {
|
|
2782
|
-
return [...new Set(values.map(sanitizeId).filter((value) => value !== ""))];
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
function sanitizeRelativePath(value: string): string {
|
|
2786
|
-
const sanitized = value.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
2787
|
-
if (sanitized === "" || sanitized.startsWith("../") || sanitized.includes("/../")) {
|
|
2788
|
-
throw new Error(`Invalid relative path in repo proposal: ${value}`);
|
|
2789
|
-
}
|
|
2790
|
-
return sanitized;
|
|
2791
|
-
}
|
|
2792
|
-
|
|
2793
|
-
function normalizeTimestamp(value?: string | Date): string {
|
|
2794
|
-
if (value instanceof Date) return value.toISOString();
|
|
2795
|
-
if (typeof value === "string" && value.trim() !== "") return new Date(value).toISOString();
|
|
2796
|
-
return new Date().toISOString();
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
function displayPath(homeDir: string, path: string): string {
|
|
2800
|
-
const relativePath = relative(homeDir, path);
|
|
2801
|
-
if (relativePath !== "" && !relativePath.startsWith("..")) return `~/${relativePath}`;
|
|
2802
|
-
return path;
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
async function pathExists(path: string): Promise<boolean> {
|
|
2806
|
-
try {
|
|
2807
|
-
await stat(path);
|
|
2808
|
-
return true;
|
|
2809
|
-
} catch (error) {
|
|
2810
|
-
if (
|
|
2811
|
-
error instanceof Error &&
|
|
2812
|
-
"code" in error &&
|
|
2813
|
-
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
2814
|
-
) {
|
|
2815
|
-
return false;
|
|
2816
|
-
}
|
|
2817
|
-
throw error;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
function optionalString(value: unknown): string | null {
|
|
2822
|
-
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
2826
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2827
|
-
}
|
|
1
|
+
export * from "./schema.ts";
|
|
2
|
+
export * from "./paths.ts";
|
|
3
|
+
export * from "./evidence/analysis.ts";
|
|
4
|
+
export * from "./processor/index.ts";
|
|
5
|
+
export * from "./candidates/index.ts";
|
|
6
|
+
export * from "./triggers/index.ts";
|
|
7
|
+
export * from "./formatters.ts";
|
|
8
|
+
export {
|
|
9
|
+
createEvolutionRepoProposal,
|
|
10
|
+
hasConcreteRepoProposalChanges,
|
|
11
|
+
sanitizeProposedChange,
|
|
12
|
+
validateEvolutionDistillationBatch,
|
|
13
|
+
validateEvolutionEvidenceWindow,
|
|
14
|
+
validateEvolutionKnowledgeRecord,
|
|
15
|
+
validateEvolutionRepoProposal,
|
|
16
|
+
} from "./shared.ts";
|