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

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.
@@ -0,0 +1,226 @@
1
+ export const TRAJECTORY_IMPORT_PREVIEW_SCHEMA_VERSION = 1 as const;
2
+ export const TRAJECTORY_IMPORT_DIAGNOSTIC_POLICY_VERSION = 1 as const;
3
+
4
+ export type TrajectoryImportSource =
5
+ | "claude-code"
6
+ | "codex"
7
+ | "hermes"
8
+ | "letta-code"
9
+ | "openclaw"
10
+ | "openhands";
11
+
12
+ export type CanonicalImportRecordType =
13
+ | "meta"
14
+ | "user"
15
+ | "reasoning"
16
+ | "assistant"
17
+ | "assistant-tool-call"
18
+ | "tool";
19
+
20
+ export type CanonicalImportSourceIdentityKind = "native" | "location" | "content" | "synthetic";
21
+
22
+ export interface CanonicalImportSnapshot {
23
+ source: TrajectoryImportSource;
24
+ explicitSourceId: string | null;
25
+ inputBytes: number;
26
+ inputFingerprint: string;
27
+ normalizer: {
28
+ package: "@letta-ai/trajectory";
29
+ version: string;
30
+ canonicalSchemaVersion: number;
31
+ canonicalConfigHash: string;
32
+ };
33
+ groups: Array<{
34
+ sourceGroupId: string;
35
+ records: Array<{
36
+ recordId: string;
37
+ contentHash: string;
38
+ sourceOrderId: string;
39
+ componentIndex: number;
40
+ sourceIdentityKind: CanonicalImportSourceIdentityKind;
41
+ recordType: CanonicalImportRecordType;
42
+ recordJson: string;
43
+ }>;
44
+ }>;
45
+ diagnostics: Array<{ code: string }>;
46
+ }
47
+
48
+ export type TrajectoryImportDiagnosticSeverity = "info" | "warning" | "blocker";
49
+
50
+ export interface TrajectoryImportIndexRecordV1 {
51
+ schemaVersion: 1;
52
+ kind: "trajectory-import-index-record";
53
+ stableRecordKeyHash: string;
54
+ contentDigest: string;
55
+ ordinal: number;
56
+ sourceIdentityKind: CanonicalImportSourceIdentityKind;
57
+ recordType: CanonicalImportRecordType;
58
+ }
59
+
60
+ export interface TrajectoryImportGenerationContractV1 {
61
+ normalizerVersion: string;
62
+ canonicalSchemaVersion: number;
63
+ canonicalConfigHash: string;
64
+ }
65
+
66
+ export interface TrajectoryImportPreviousSnapshotV1 {
67
+ sourceKey: string;
68
+ generationId: string;
69
+ initialInputFingerprint: string;
70
+ generationContract: TrajectoryImportGenerationContractV1;
71
+ snapshotId: string;
72
+ records: TrajectoryImportIndexRecordV1[];
73
+ }
74
+
75
+ export interface TrajectoryImportMaterializationPreviewV1 {
76
+ selectedRecords: number;
77
+ estimatedSegments: number;
78
+ truncatedSegments: number;
79
+ credentialRedactions: number;
80
+ estimatedTriggers: number;
81
+ retentionDays: number;
82
+ }
83
+
84
+ export interface TrajectoryImportDownstreamPreviewV1 {
85
+ semanticProcessing: "queue-only" | "automation-eligible";
86
+ automationKnowledge: boolean;
87
+ automationSemanticKnowledge: boolean;
88
+ mayInvokeConfiguredProviderAfterApply: boolean;
89
+ }
90
+
91
+ export interface TrajectoryImportPreviewV1 {
92
+ schemaVersion: 1;
93
+ kind: "trajectory-import-preview";
94
+ id: string;
95
+ source: TrajectoryImportSource;
96
+ sourceKey: string;
97
+ projectKey: string | null;
98
+ scope: {
99
+ roleId: string | null;
100
+ };
101
+ input: {
102
+ bytes: number;
103
+ fingerprint: string;
104
+ pathHash: string;
105
+ };
106
+ normalizer: {
107
+ version: string;
108
+ canonicalSchemaVersion: number;
109
+ canonicalConfigHash: string;
110
+ };
111
+ contracts: {
112
+ diagnosticPolicyVersion: number;
113
+ materializerVersion: number;
114
+ materializerConfigHash: string;
115
+ };
116
+ generationId: string;
117
+ snapshotId: string;
118
+ previousSnapshotId: string | null;
119
+ records: {
120
+ total: number;
121
+ new: number;
122
+ unchanged: number;
123
+ changed: number;
124
+ missing: number;
125
+ recordTypes: Record<CanonicalImportRecordType, number>;
126
+ identityKinds: Record<CanonicalImportSourceIdentityKind, number>;
127
+ };
128
+ diagnostics: Array<{
129
+ code: string;
130
+ severity: TrajectoryImportDiagnosticSeverity;
131
+ count: number;
132
+ }>;
133
+ materialization: TrajectoryImportMaterializationPreviewV1 | null;
134
+ downstream: TrajectoryImportDownstreamPreviewV1;
135
+ blockers: string[];
136
+ canApply: boolean;
137
+ rawContentStored: false;
138
+ }
139
+
140
+ export interface CreateTrajectoryImportPreviewInput {
141
+ snapshot: CanonicalImportSnapshot;
142
+ projectKey?: string | null;
143
+ roleId?: string | null;
144
+ pathHash: string;
145
+ previous?: TrajectoryImportPreviousSnapshotV1 | null;
146
+ }
147
+
148
+ export interface TrajectoryImportPreviewBuildResult {
149
+ preview: TrajectoryImportPreviewV1;
150
+ comparisonSnapshot: TrajectoryImportPreviousSnapshotV1;
151
+ }
152
+
153
+ export type TrajectoryImportApplyState = "staged" | "materializing" | "completed";
154
+
155
+ export interface TrajectoryImportManifestV1 {
156
+ schemaVersion: 1;
157
+ kind: "trajectory-import-manifest";
158
+ source: TrajectoryImportSource;
159
+ sourceKey: string;
160
+ projectKey: string;
161
+ scope: {
162
+ roleId: string | null;
163
+ };
164
+ currentGenerationId: string;
165
+ initialInputFingerprint: string;
166
+ generationContract: TrajectoryImportGenerationContractV1;
167
+ currentSnapshotId: string;
168
+ previousSnapshotId: string | null;
169
+ currentPreviewId: string;
170
+ currentInputFingerprint: string;
171
+ currentInputBytes: number;
172
+ currentIndexDigest: string;
173
+ stagedAt: string;
174
+ applyState: TrajectoryImportApplyState;
175
+ latestMaterializedSnapshotId: string | null;
176
+ rawContentStored: false;
177
+ }
178
+
179
+ export interface TrajectoryImportReceiptV1 {
180
+ schemaVersion: 1;
181
+ kind: "trajectory-import-receipt";
182
+ previewId: string;
183
+ sourceKey: string;
184
+ generationId: string;
185
+ snapshotId: string;
186
+ operation: "stage" | "materialize";
187
+ outcome: "staged" | "completed";
188
+ counts: {
189
+ total: number;
190
+ new: number;
191
+ unchanged: number;
192
+ changed: number;
193
+ missing: number;
194
+ };
195
+ createdAt: string;
196
+ rawContentStored: false;
197
+ }
198
+
199
+ export interface StageTrajectoryImportInput {
200
+ homeDir: string;
201
+ preview: TrajectoryImportPreviewV1;
202
+ comparisonSnapshot: TrajectoryImportPreviousSnapshotV1;
203
+ now?: string;
204
+ }
205
+
206
+ export interface StageTrajectoryImportResult {
207
+ status: "staged" | "unchanged";
208
+ manifest: TrajectoryImportManifestV1;
209
+ receipt: TrajectoryImportReceiptV1;
210
+ stateWrites: string[];
211
+ }
212
+
213
+ export interface TrajectoryImportMaterializationProgressV1 {
214
+ schemaVersion: 1;
215
+ kind: "trajectory-import-materialization-progress";
216
+ previewId: string;
217
+ sourceKey: string;
218
+ generationId: string;
219
+ snapshotId: string;
220
+ expectedSegmentIds: string[];
221
+ completedSegmentIds: string[];
222
+ expectedTriggerIds: string[];
223
+ completedTriggerIds: string[];
224
+ updatedAt: string;
225
+ rawContentStored: false;
226
+ }
@@ -1,13 +1,16 @@
1
1
  export * from "./schema.ts";
2
2
  export * from "./paths.ts";
3
+ export * from "./imports/index.ts";
3
4
  export * from "./evidence/analysis.ts";
4
5
  export * from "./processor/index.ts";
5
6
  export * from "./candidates/index.ts";
6
7
  export * from "./triggers/index.ts";
7
8
  export * from "./formatters.ts";
8
9
  export {
10
+ createEvolutionImprovementEval,
9
11
  createEvolutionRepoProposal,
10
12
  hasConcreteRepoProposalChanges,
13
+ isEvolutionImprovementEvalRecommended,
11
14
  sanitizeProposedChange,
12
15
  validateEvolutionDistillationBatch,
13
16
  validateEvolutionEvidenceWindow,
@@ -73,7 +73,8 @@ export type SegmentEvolutionTriggerReason =
73
73
  | "failure-signal"
74
74
  | "permission-denied"
75
75
  | "verification-after-fix"
76
- | "explicit-memory-intent";
76
+ | "explicit-memory-intent"
77
+ | "historical-import";
77
78
  export type SegmentEvolutionTriggerStrength = "normal" | "strong";
78
79
 
79
80
  export type EvolutionKnowledgeKind =
@@ -110,6 +111,47 @@ export type EvolutionProposalKind =
110
111
  | "docs"
111
112
  | "engineering-practice";
112
113
 
114
+ export type EvolutionImprovementEvalStatus =
115
+ | "not-requested"
116
+ | "awaiting-eval"
117
+ | "evaluating"
118
+ | "effective"
119
+ | "ineffective"
120
+ | "inconclusive";
121
+
122
+ export interface EvolutionImprovementEvalResult {
123
+ targetImproved: boolean | null;
124
+ hardRegression: boolean | null;
125
+ summary: string;
126
+ assertions: Array<{
127
+ label: string;
128
+ status: "pass" | "fail" | "unknown";
129
+ }>;
130
+ evaluatedBy: "local-code-agent" | "local-eval-orchestrator";
131
+ metrics: {
132
+ durationMs: number;
133
+ modelCalls: number;
134
+ inputTokens: number | null;
135
+ outputTokens: number | null;
136
+ toolCalls: number | null;
137
+ };
138
+ retention: {
139
+ metadataOnly: true;
140
+ replayInputStored: false;
141
+ replayOutputStored: false;
142
+ };
143
+ }
144
+
145
+ export interface EvolutionImprovementEval {
146
+ recommended: boolean;
147
+ requested: boolean;
148
+ status: EvolutionImprovementEvalStatus;
149
+ requestedAt: string | null;
150
+ evaluatedAt: string | null;
151
+ evidenceRef: string | null;
152
+ result: EvolutionImprovementEvalResult | null;
153
+ }
154
+
113
155
  export interface EvolutionPrivacyFields {
114
156
  classification: "local-private";
115
157
  rawPromptsStored: false;
@@ -355,6 +397,7 @@ export interface EvolutionRepoProposal {
355
397
  reason: string | null;
356
398
  decidedAt: string;
357
399
  };
400
+ improvementEval?: EvolutionImprovementEval;
358
401
  apply: {
359
402
  autoApply: false;
360
403
  requiresExplicitCommand: true;
@@ -13,6 +13,8 @@ import type {
13
13
  EvolutionEvidenceSourceKind,
14
14
  EvolutionEvidenceWindow,
15
15
  EvolutionEvosCase,
16
+ EvolutionImprovementEval,
17
+ EvolutionImprovementEvalStatus,
16
18
  EvolutionKnowledgeKind,
17
19
  EvolutionKnowledgeRecord,
18
20
  EvolutionNormalizedEventType,
@@ -75,6 +77,14 @@ export const PROPOSAL_KINDS: readonly EvolutionProposalKind[] = [
75
77
  "docs",
76
78
  "engineering-practice",
77
79
  ];
80
+ export const IMPROVEMENT_EVAL_STATUSES: readonly EvolutionImprovementEvalStatus[] = [
81
+ "not-requested",
82
+ "awaiting-eval",
83
+ "evaluating",
84
+ "effective",
85
+ "ineffective",
86
+ "inconclusive",
87
+ ];
78
88
  export const EVENT_KINDS: readonly EvolutionEvidenceEventKind[] = [
79
89
  "trace",
80
90
  "tool-call",
@@ -148,6 +158,7 @@ export const SEGMENT_TRIGGER_REASONS: readonly SegmentEvolutionTriggerReason[] =
148
158
  "permission-denied",
149
159
  "verification-after-fix",
150
160
  "explicit-memory-intent",
161
+ "historical-import",
151
162
  ];
152
163
  export const SEGMENT_TRIGGER_STRENGTHS: readonly SegmentEvolutionTriggerStrength[] = [
153
164
  "normal",
@@ -352,6 +363,9 @@ export function createEvolutionRepoProposal(
352
363
  ? {}
353
364
  : { proposedChange: sanitizeProposedChange(file.proposedChange) }),
354
365
  })),
366
+ ...(input.improvementEval === undefined
367
+ ? {}
368
+ : { improvementEval: sanitizeEvolutionImprovementEval(input.improvementEval) }),
355
369
  privacy: { ...input.privacy },
356
370
  };
357
371
  proposal.privacy.internalLinksStored =
@@ -408,6 +422,9 @@ export function validateEvolutionRepoProposal(proposal: EvolutionRepoProposal):
408
422
  throw new Error("Repo proposal decision timestamp must be valid.");
409
423
  }
410
424
  }
425
+ if (proposal.improvementEval !== undefined) {
426
+ validateEvolutionImprovementEval(proposal.improvementEval);
427
+ }
411
428
  if (proposal.apply.autoApply !== false || proposal.apply.requiresExplicitCommand !== true) {
412
429
  throw new Error("Repo proposal must require explicit apply.");
413
430
  }
@@ -416,6 +433,28 @@ export function validateEvolutionRepoProposal(proposal: EvolutionRepoProposal):
416
433
  assertNoForbiddenRawFields(proposal);
417
434
  }
418
435
 
436
+ export function isEvolutionImprovementEvalRecommended(kind: EvolutionProposalKind): boolean {
437
+ return !["ci", "test", "docs"].includes(kind);
438
+ }
439
+
440
+ export function createEvolutionImprovementEval(input: {
441
+ kind: EvolutionProposalKind;
442
+ requested?: boolean;
443
+ now?: string | Date;
444
+ }): EvolutionImprovementEval {
445
+ const requested = input.requested ?? false;
446
+ const requestedAt = requested ? normalizeTimestampValue(input.now) : null;
447
+ return {
448
+ recommended: isEvolutionImprovementEvalRecommended(input.kind),
449
+ requested,
450
+ status: requested ? "awaiting-eval" : "not-requested",
451
+ requestedAt,
452
+ evaluatedAt: null,
453
+ evidenceRef: null,
454
+ result: null,
455
+ };
456
+ }
457
+
419
458
  export function hasConcreteRepoProposalChanges(proposal: EvolutionRepoProposal): boolean {
420
459
  return (
421
460
  proposal.targetRepoPath !== null &&
@@ -427,6 +466,154 @@ export function hasConcreteRepoProposalChanges(proposal: EvolutionRepoProposal):
427
466
  );
428
467
  }
429
468
 
469
+ function validateEvolutionImprovementEval(value: EvolutionImprovementEval): void {
470
+ if (!isRecord(value)) throw new Error("Repo proposal improvementEval must be an object.");
471
+ if (typeof value.recommended !== "boolean" || typeof value.requested !== "boolean") {
472
+ throw new Error("Repo proposal improvementEval flags must be booleans.");
473
+ }
474
+ assertEnum("improvementEval.status", value.status, IMPROVEMENT_EVAL_STATUSES);
475
+ assertNullableTimestamp("improvementEval.requestedAt", value.requestedAt);
476
+ assertNullableTimestamp("improvementEval.evaluatedAt", value.evaluatedAt);
477
+ if (value.evidenceRef !== null) assertString("improvementEval.evidenceRef", value.evidenceRef);
478
+ if (!value.requested) {
479
+ if (
480
+ value.status !== "not-requested" ||
481
+ value.requestedAt !== null ||
482
+ value.evaluatedAt !== null ||
483
+ value.result !== null
484
+ ) {
485
+ throw new Error("A non-requested improvement eval cannot have evaluation state.");
486
+ }
487
+ return;
488
+ }
489
+ if (value.status === "not-requested" || value.requestedAt === null) {
490
+ throw new Error("A requested improvement eval must be awaiting or completed.");
491
+ }
492
+ const completed = ["effective", "ineffective", "inconclusive"].includes(value.status);
493
+ if (completed !== (value.result !== null && value.evaluatedAt !== null)) {
494
+ throw new Error("Completed improvement eval state and result must be stored together.");
495
+ }
496
+ if (value.result === null) return;
497
+ if (value.result.targetImproved !== null && typeof value.result.targetImproved !== "boolean") {
498
+ throw new Error("Improvement eval targetImproved must be boolean or null.");
499
+ }
500
+ if (value.result.hardRegression !== null && typeof value.result.hardRegression !== "boolean") {
501
+ throw new Error("Improvement eval hardRegression must be boolean or null.");
502
+ }
503
+ if (
504
+ value.status === "effective" &&
505
+ (value.result.targetImproved !== true || value.result.hardRegression !== false)
506
+ ) {
507
+ throw new Error("An effective improvement eval must improve the target without regression.");
508
+ }
509
+ if (
510
+ value.status === "ineffective" &&
511
+ value.result.targetImproved !== false &&
512
+ value.result.hardRegression !== true
513
+ ) {
514
+ throw new Error("An ineffective improvement eval must fail the target or a hard gate.");
515
+ }
516
+ if (
517
+ value.status === "inconclusive" &&
518
+ (value.result.targetImproved === false ||
519
+ value.result.hardRegression === true ||
520
+ (value.result.targetImproved === true && value.result.hardRegression === false))
521
+ ) {
522
+ throw new Error("An inconclusive improvement eval cannot contain a definitive gate result.");
523
+ }
524
+ assertString("improvementEval.result.summary", value.result.summary);
525
+ if (!Array.isArray(value.result.assertions) || value.result.assertions.length > 8) {
526
+ throw new Error("Improvement eval assertions must contain at most 8 items.");
527
+ }
528
+ for (const assertion of value.result.assertions) {
529
+ assertString("improvementEval.result.assertions.label", assertion.label);
530
+ assertEnum("improvementEval.result.assertions.status", assertion.status, [
531
+ "pass",
532
+ "fail",
533
+ "unknown",
534
+ ]);
535
+ }
536
+ if (
537
+ value.result.evaluatedBy !== "local-code-agent" &&
538
+ value.result.evaluatedBy !== "local-eval-orchestrator"
539
+ ) {
540
+ throw new Error("Improvement eval evaluatedBy is invalid.");
541
+ }
542
+ if (!isRecord(value.result.metrics)) {
543
+ throw new Error("Improvement eval metrics must be an object.");
544
+ }
545
+ if (
546
+ !isNonNegativeInteger(value.result.metrics.durationMs) ||
547
+ !isNonNegativeInteger(value.result.metrics.modelCalls)
548
+ ) {
549
+ throw new Error("Improvement eval duration and model call count are required.");
550
+ }
551
+ if (
552
+ (value.result.evaluatedBy === "local-code-agent" && value.result.metrics.modelCalls !== 1) ||
553
+ (value.result.evaluatedBy === "local-eval-orchestrator" &&
554
+ value.result.metrics.modelCalls !== 0)
555
+ ) {
556
+ throw new Error("Improvement eval model call metadata is inconsistent.");
557
+ }
558
+ for (const [field, metric] of [
559
+ ["inputTokens", value.result.metrics.inputTokens],
560
+ ["outputTokens", value.result.metrics.outputTokens],
561
+ ["toolCalls", value.result.metrics.toolCalls],
562
+ ] as const) {
563
+ if (metric !== null && !isNonNegativeInteger(metric)) {
564
+ throw new Error(`Improvement eval metric ${field} must be a non-negative integer or null.`);
565
+ }
566
+ }
567
+ if (
568
+ value.result.retention.metadataOnly !== true ||
569
+ value.result.retention.replayInputStored !== false ||
570
+ value.result.retention.replayOutputStored !== false
571
+ ) {
572
+ throw new Error("Improvement eval must retain metadata-only results.");
573
+ }
574
+ }
575
+
576
+ function sanitizeEvolutionImprovementEval(
577
+ value: EvolutionImprovementEval,
578
+ ): EvolutionImprovementEval {
579
+ return {
580
+ ...value,
581
+ evidenceRef: value.evidenceRef === null ? null : sanitizeId(value.evidenceRef),
582
+ result:
583
+ value.result === null
584
+ ? null
585
+ : {
586
+ ...value.result,
587
+ summary: sanitizeText(value.result.summary),
588
+ assertions: value.result.assertions.slice(0, 8).map((assertion) => ({
589
+ label: sanitizeText(assertion.label),
590
+ status: assertion.status,
591
+ })),
592
+ metrics: { ...value.result.metrics },
593
+ retention: { ...value.result.retention },
594
+ },
595
+ };
596
+ }
597
+
598
+ function assertNullableTimestamp(field: string, value: string | null): void {
599
+ if (value !== null && Number.isNaN(new Date(value).getTime())) {
600
+ throw new Error(`${field} must be null or a valid timestamp.`);
601
+ }
602
+ }
603
+
604
+ function isNonNegativeInteger(value: unknown): value is number {
605
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
606
+ }
607
+
608
+ function normalizeTimestampValue(value: string | Date | undefined): string {
609
+ const timestamp =
610
+ value instanceof Date ? value.toISOString() : (value ?? new Date().toISOString());
611
+ if (Number.isNaN(new Date(timestamp).getTime())) {
612
+ throw new Error("Improvement eval timestamp must be valid.");
613
+ }
614
+ return timestamp;
615
+ }
616
+
430
617
  export function validateEvolutionReviewCandidate(candidate: EvolutionReviewCandidate): void {
431
618
  if (!isRecord(candidate)) throw new Error("Review candidate must be an object.");
432
619
  if (candidate.schemaVersion !== 1) {