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