@evomap/evolver-core 2.0.0-beta.1 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/assets/gep/genes.jsonl +5 -0
  2. package/dist/algo/candidateAssembly.js +23 -14
  3. package/dist/algo/capabilityCandidates.d.ts +2 -0
  4. package/dist/algo/capabilityCandidates.js +5 -0
  5. package/dist/algo/conversationSniffer.d.ts +18 -0
  6. package/dist/algo/conversationSniffer.js +132 -0
  7. package/dist/algo/cycleEngine.d.ts +11 -0
  8. package/dist/algo/cycleEngine.js +14 -6
  9. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  10. package/dist/algo/cycleFailureClassifier.js +13 -5
  11. package/dist/algo/geneIntake.d.ts +14 -4
  12. package/dist/algo/geneIntake.js +37 -9
  13. package/dist/algo/geneSelection.d.ts +18 -1
  14. package/dist/algo/geneSelection.js +42 -18
  15. package/dist/algo/index.d.ts +2 -0
  16. package/dist/algo/index.js +2 -0
  17. package/dist/algo/memoryGraph.d.ts +62 -0
  18. package/dist/algo/memoryGraph.js +86 -0
  19. package/dist/algo/orchestrator.d.ts +3 -0
  20. package/dist/algo/orchestrator.js +14 -2
  21. package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
  22. package/dist/assetstore/assetSidecarRecords.js +142 -0
  23. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  24. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  25. package/dist/assetstore/assetStoreHealth.d.ts +75 -0
  26. package/dist/assetstore/assetStoreHealth.js +277 -0
  27. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  28. package/dist/assetstore/assetStoreLayout.js +6 -0
  29. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  30. package/dist/assetstore/assetStoreStorage.js +318 -0
  31. package/dist/assetstore/assetSyncLedger.d.ts +32 -0
  32. package/dist/assetstore/assetSyncLedger.js +66 -0
  33. package/dist/assetstore/index.d.ts +5 -1
  34. package/dist/assetstore/index.js +5 -1
  35. package/dist/assetstore/localJsonl.d.ts +10 -2
  36. package/dist/assetstore/localJsonl.js +93 -37
  37. package/dist/assetstore/pendingSignals.js +3 -1
  38. package/dist/assetstore/provenance.d.ts +20 -2
  39. package/dist/assetstore/provenance.js +92 -56
  40. package/dist/assetstore/provider.d.ts +2 -0
  41. package/dist/assetstore/reviewFilter.js +5 -2
  42. package/dist/assetstore/reviewLedger.d.ts +14 -2
  43. package/dist/assetstore/reviewLedger.js +78 -43
  44. package/dist/assetstore/seedGenes.d.ts +3 -0
  45. package/dist/assetstore/seedGenes.js +134 -0
  46. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  47. package/dist/benchmark/antiGeneBenchmark.js +11 -1
  48. package/dist/benchmark/antiGeneImpact.d.ts +16 -0
  49. package/dist/benchmark/antiGeneImpact.js +34 -0
  50. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  51. package/dist/benchmark/antiGeneRollout.js +13 -1
  52. package/dist/benchmark/index.d.ts +2 -1
  53. package/dist/benchmark/index.js +2 -1
  54. package/dist/benchmark/triggerShift.d.ts +62 -0
  55. package/dist/benchmark/triggerShift.js +106 -0
  56. package/dist/events/eventArchive.d.ts +65 -0
  57. package/dist/events/eventArchive.js +343 -0
  58. package/dist/events/eventStore.js +2 -12
  59. package/dist/events/ingest.d.ts +1 -1
  60. package/dist/events/ingest.js +2 -0
  61. package/dist/events/paths.d.ts +1 -1
  62. package/dist/events/paths.js +2 -2
  63. package/dist/events/public.d.ts +3 -1
  64. package/dist/events/public.js +2 -1
  65. package/dist/events/retention.d.ts +24 -1
  66. package/dist/events/retention.js +133 -37
  67. package/dist/exec/autoExec.d.ts +6 -1
  68. package/dist/exec/autoExec.js +34 -0
  69. package/dist/exec/autonomousCycle.d.ts +2 -0
  70. package/dist/exec/autonomousCycle.js +5 -0
  71. package/dist/exec/claudeBridge.d.ts +7 -2
  72. package/dist/exec/claudeBridge.js +115 -14
  73. package/dist/exec/prompt.js +9 -0
  74. package/dist/exec/runnerRegistry.d.ts +64 -18
  75. package/dist/exec/runnerRegistry.js +341 -38
  76. package/dist/exec/selfPrObfuscation.d.ts +2 -1
  77. package/dist/exec/selfPrObfuscation.js +19 -6
  78. package/dist/hub/agentDirectory.d.ts +90 -0
  79. package/dist/hub/agentDirectory.js +104 -0
  80. package/dist/hub/bindings.js +23 -3
  81. package/dist/hub/capability.d.ts +14 -2
  82. package/dist/hub/fake.d.ts +4 -2
  83. package/dist/hub/fake.js +3 -2
  84. package/dist/hub/index.d.ts +1 -0
  85. package/dist/hub/index.js +1 -0
  86. package/dist/mailbox/catalog.js +3 -0
  87. package/dist/mailbox/ipcServer.js +2 -2
  88. package/dist/mailbox/store.d.ts +14 -0
  89. package/dist/mailbox/store.js +44 -2
  90. package/dist/material/consumer.js +9 -6
  91. package/dist/material/index.d.ts +1 -0
  92. package/dist/material/index.js +1 -0
  93. package/dist/material/materialArchive.d.ts +81 -0
  94. package/dist/material/materialArchive.js +466 -0
  95. package/dist/material/materialStore.d.ts +1 -0
  96. package/dist/material/materialStore.js +6 -13
  97. package/dist/ops/savingsCore.js +1 -2
  98. package/dist/ops/selfUpdate.d.ts +10 -1
  99. package/dist/ops/selfUpdate.js +64 -15
  100. package/dist/schema/common.d.ts +1 -1
  101. package/dist/schema/common.js +1 -1
  102. package/dist/schema/material.d.ts +5 -5
  103. package/dist/trace/trajectoryExport.js +2 -2
  104. package/dist/util/fileLock.d.ts +19 -2
  105. package/dist/util/fileLock.js +166 -31
  106. package/dist/wire/geneHints.d.ts +42 -1
  107. package/dist/wire/geneHints.js +52 -1
  108. package/dist/wire/index.d.ts +14 -2
  109. package/dist/wire/index.js +1 -1
  110. package/package.json +6 -1
@@ -4,6 +4,8 @@ import { type GeneHealth } from './geneHealth.js';
4
4
  import type { GeneLearningView } from '../assetstore/learningHistory.js';
5
5
  import type { AssetRecord } from '../assetstore/provider.js';
6
6
  import { type ExplorationInput } from './exploration.js';
7
+ import type { GenerationSource } from '../wire/index.js';
8
+ import type { MemoryGraphGeneEvidence } from './memoryGraph.js';
7
9
  /** 一个候选 gene 的选择期素材. */
8
10
  export interface GeneCandidateInput {
9
11
  geneId: string;
@@ -34,6 +36,8 @@ export interface GeneCandidateInput {
34
36
  * set the hard gates already produced, so a high confidence can never resurrect a gene that was excluded.
35
37
  */
36
38
  confidence?: number;
39
+ /** Scoped local MemoryGraph outcome signal in [-1,1]. Data-only and never executable prompt content. */
40
+ memoryBoost?: number;
37
41
  /**
38
42
  * Cross-runtime reuse sentiment in [-1, 1] for this gene (#268 phase 1): the net of self-reported reuse
39
43
  * SUCCESSes vs negatives (failed/mismatched/stale/unsafe), computed upstream from reuse-outcome events
@@ -43,6 +47,11 @@ export interface GeneCandidateInput {
43
47
  * effect, so the factor is dormant until a caller injects the signal (default-off).
44
48
  */
45
49
  reuseAdjust?: number;
50
+ /**
51
+ * Authoritative provenance tag from Gene.generation_meta.source. The legacy `gene_distilled_` prefix remains a
52
+ * fallback only for old candidates that do not carry this field.
53
+ */
54
+ generationSource?: GenerationSource;
46
55
  }
47
56
  export interface SelectionInput {
48
57
  signals: readonly string[];
@@ -72,6 +81,8 @@ export interface SelectionInput {
72
81
  * for prompt rendering only; they never enter scoring, fallback, or forced selection.
73
82
  */
74
83
  antiWarnings?: readonly AntiWarning[];
84
+ /** Structured, scoped outcome evidence selected upstream. Contains no raw memory text or instructions. */
85
+ memoryEvidence?: readonly MemoryGraphGeneEvidence[];
75
86
  }
76
87
  export interface ScoredCandidate {
77
88
  geneId: string;
@@ -99,6 +110,10 @@ export interface GeneDecision {
99
110
  antiWarnings?: AntiWarning[];
100
111
  weightsVersion: string;
101
112
  strategyName: string;
113
+ /** Human-readable explanation for the winning candidate. */
114
+ selectedReason?: string;
115
+ /** Bounded structured outcome evidence for prompt enrichment. */
116
+ memoryEvidence?: MemoryGraphGeneEvidence[];
102
117
  }
103
118
  /**
104
119
  * Weight of the preferred-gene confidence factor (fourth factor, positive cross-cycle learning). Kept small so
@@ -112,12 +127,14 @@ export declare const CONFIDENCE_WEIGHT = 0.15;
112
127
  * ±REUSE_WEIGHT and can never dominate health/signal-match.
113
128
  */
114
129
  export declare const REUSE_WEIGHT = 0.1;
130
+ /** Weight of scoped local MemoryGraph outcome evidence. */
131
+ export declare const MEMORY_GRAPH_WEIGHT = 0.12;
115
132
  /**
116
133
  * Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
117
134
  * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
118
135
  * weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
119
136
  */
120
- export declare const SELECTION_WEIGHTS_VERSION = "sel-3(gh-1,conf=0.15,reuse=0.1)";
137
+ export declare const SELECTION_WEIGHTS_VERSION = "sel-4(gh-1,conf=0.15,memory=0.12,reuse=0.1)";
121
138
  /** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
122
139
  export declare const engineHealthSelection: Strategy<SelectionInput, GeneDecision>;
123
140
  /** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
@@ -4,9 +4,17 @@ import { tagOverlapScore, bagCosine } from '../signals/expand.js';
4
4
  import { driftSelect } from './exploration.js';
5
5
  import { isDistilledGeneId } from './geneIntake.js';
6
6
  function decisionWithWarnings(input, decision) {
7
+ const selected = decision.selectedGeneId
8
+ ? decision.candidates.find((candidate) => candidate.geneId === decision.selectedGeneId || candidate.assetId === decision.selectedAssetId)
9
+ : undefined;
10
+ const enriched = {
11
+ ...decision,
12
+ ...(selected ? { selectedReason: selected.reasons.join('; ') } : {}),
13
+ ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: [...input.memoryEvidence] } : {}),
14
+ };
7
15
  return input.antiWarnings && input.antiWarnings.length > 0
8
- ? { ...decision, antiWarnings: [...input.antiWarnings] }
9
- : decision;
16
+ ? { ...enriched, antiWarnings: [...input.antiWarnings] }
17
+ : enriched;
10
18
  }
11
19
  function signalMatchScore(signals, match) {
12
20
  if (signals.length === 0 || match.length === 0)
@@ -29,12 +37,14 @@ export const CONFIDENCE_WEIGHT = 0.15;
29
37
  * ±REUSE_WEIGHT and can never dominate health/signal-match.
30
38
  */
31
39
  export const REUSE_WEIGHT = 0.1;
40
+ /** Weight of scoped local MemoryGraph outcome evidence. */
41
+ export const MEMORY_GRAPH_WEIGHT = 0.12;
32
42
  /**
33
43
  * Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
34
44
  * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
35
45
  * weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
36
46
  */
37
- export const SELECTION_WEIGHTS_VERSION = `sel-3(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},reuse=${REUSE_WEIGHT})`;
47
+ export const SELECTION_WEIGHTS_VERSION = `sel-4(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT})`;
38
48
  /**
39
49
  * Match score with semantic signal expansion (ported from v1): literal coverage first, then fill the
40
50
  * recall gap with semantic tag overlap, then with bag-of-words cosine similarity. A perfect literal match
@@ -65,13 +75,16 @@ function scoreCandidate(signals, c) {
65
75
  // Cross-runtime reuse sentiment (#268 phase 1): clamped to [-1,1] so an upstream bug cannot turn the soft nudge
66
76
  // into a dominant (or unbounded) term. Absent → 0 → no effect (default-off until a caller injects it).
67
77
  const reuse = Math.max(-1, Math.min(1, c.reuseAdjust ?? 0));
68
- const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf + REUSE_WEIGHT * reuse;
78
+ const memory = Math.max(-1, Math.min(1, c.memoryBoost ?? 0));
79
+ const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf + MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse;
69
80
  const breakdown = m.tag > 0 || m.cos > 0 ? `(literal=${m.literal.toFixed(2)}+tag=${m.tag.toFixed(2)}+cos=${m.cos.toFixed(2)})` : '';
70
81
  const reasons = [`health=${health.score.toFixed(3)}(succ=${health.successRate.toFixed(2)},reuse=${health.reuseCount})`, `信号匹配=${m.score.toFixed(2)}${breakdown}`];
71
82
  if (epi > 0)
72
83
  reasons.push(`epigenetic 环境抑制 -${epi.toFixed(2)}`);
73
84
  if (conf > 0)
74
85
  reasons.push(`preferred-gene confidence +${(CONFIDENCE_WEIGHT * conf).toFixed(3)} (conf=${conf.toFixed(2)})`);
86
+ if (memory !== 0)
87
+ reasons.push(`scoped memory-graph outcome ${memory >= 0 ? '+' : ''}${(MEMORY_GRAPH_WEIGHT * memory).toFixed(3)} (boost=${memory.toFixed(2)})`);
75
88
  if (reuse !== 0)
76
89
  reasons.push(`cross-runtime reuse ${reuse >= 0 ? '+' : ''}${(REUSE_WEIGHT * reuse).toFixed(3)} (sentiment=${reuse.toFixed(2)})`);
77
90
  return { geneId: c.geneId, ...(c.assetId ? { assetId: c.assetId } : {}), score, health, reasons };
@@ -88,6 +101,14 @@ function matchesScoredIdentity(a, b) {
88
101
  function withoutCandidateIdentity(scored, c) {
89
102
  return scored.filter((s) => !matchesCandidateIdentity(s, c));
90
103
  }
104
+ function isReusableGenerationSource(source) {
105
+ return source === 'distilled' || source === 'evolved';
106
+ }
107
+ function isReusableFallbackCandidate(c) {
108
+ if (c.generationSource !== undefined)
109
+ return isReusableGenerationSource(c.generationSource);
110
+ return isDistilledGeneId(c.geneId);
111
+ }
91
112
  function forcedSelection(input, scored, scoreForcedFallback, floor) {
92
113
  const forcedGeneId = input.forcedGeneId?.trim();
93
114
  if (!forcedGeneId)
@@ -116,7 +137,7 @@ function forcedSelection(input, scored, scoreForcedFallback, floor) {
116
137
  forcedFallback.reasons.push('forced gene rejected: epigenetic suppression');
117
138
  return { scoredForChoice: scoredCandidatesOnly, forceRejected: true };
118
139
  }
119
- if (!isDistilledGeneId(fallback.geneId)) {
140
+ if (!isReusableFallbackCandidate(fallback)) {
120
141
  forcedFallback.reasons.push('forced gene rejected: suppressed fallback');
121
142
  return { scoredForChoice: scoredCandidatesOnly, forceRejected: true };
122
143
  }
@@ -141,26 +162,29 @@ export const engineHealthSelection = {
141
162
  let selectedAssetId = forced.selectedAssetId ?? chosen?.assetId;
142
163
  if (!selectedGeneId) {
143
164
  // Issue #97 (ported from v1 selector.js): no candidate matched any live signal (every gene scored <= 0). Rather
144
- // than fall straight through to a blind innovate, reuse a *distilled* gene if one is available. NB the prefix
145
- // does NOT mean "skill-derived" in v2 the way it did in v1: v1 tagged only skill genes gene_distilled_ (auto-
146
- // evolved genes were gene_auto_), but v2's intakeGene tags EVERY intaken gene gene_distilled_ (autoExec even
147
- // intakes task strategies that way) and has no gene_auto_. So this pool is "any trusted/approved/non-suppressed
148
- // pooled gene that does not match", not specifically skill-derived — the safety rests on the upstream gates
149
- // (trust #30 / review #89-91 / signal-ban / inert-ban #195) plus the epigenetic skip below, not on a skill-
150
- // provenance low-blast-radius assumption. We skip any distilled fallback epigenetically suppressed in this env;
151
- // v2's event-log-derived epigeneticPenalty is the analog of v1's asset-mark hard suppression (related band, not
152
- // the identical predicate). No usable distilled fallback → stay null so the caller still innovates (the contract).
165
+ // than fall straight through to a blind innovate, reuse a *distilled* (or evolved) gene if one is available.
166
+ // The fallback pool is built by candidateAssembly, which now filters by generation_meta.source {distilled,
167
+ // evolved} (V1 #302), falling back to the `gene_distilled_` id namespace for legacy genes. The id-prefix check
168
+ // below is a defense-in-depth filter for callers that bypass candidateAssembly; the authoritative provenance
169
+ // lives on generation_meta (see geneGenerationSource). The safety rests on the upstream gates (trust #30 /
170
+ // review #89-91 / signal-ban / inert-ban #195) plus the epigenetic skip below, not on a skill-provenance
171
+ // low-blast-radius assumption. We skip any distilled fallback epigenetically suppressed in this env; v2's
172
+ // event-log-derived epigeneticPenalty is the analog of v1's asset-mark hard suppression (related band, not the
173
+ // identical predicate). No usable distilled fallback → stay null so the caller still innovates (the contract).
153
174
  //
154
175
  // v1 #97 parity: the distilled fallback is a *no-signal-match* last resort — it must fire only when NOTHING
155
176
  // scored above zero, NOT merely when nothing cleared a positive `floor`. Otherwise a real but weak signal match
156
177
  // (0 < score <= floor) would be preempted by an unrelated zero-evidence distilled gene; V1 (which has no floor)
157
178
  // keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
158
179
  const noSignalMatch = !forced.forceRejected && forced.scoredForChoice.every((s) => s.score <= 0);
159
- const fb = noSignalMatch
160
- ? (input.distilledFallback ?? []).find((c) => isDistilledGeneId(c.geneId) && (c.epigeneticPenalty ?? 0) === 0)
180
+ const fallback = noSignalMatch
181
+ ? (input.distilledFallback ?? [])
182
+ .filter((candidate) => isReusableFallbackCandidate(candidate) && (candidate.epigeneticPenalty ?? 0) === 0)
183
+ .map((candidate) => ({ candidate, scored: scoreCandidate(input.signals, candidate) }))
184
+ .sort((left, right) => right.scored.score - left.scored.score || left.candidate.geneId.localeCompare(right.candidate.geneId))[0]
161
185
  : undefined;
162
- if (fb) {
163
- const fbScored = scoreCandidate(input.signals, fb);
186
+ if (fallback) {
187
+ const { candidate: fb, scored: fbScored } = fallback;
164
188
  fbScored.reasons.push('distilled_fallback(#97): 无信号匹配, 低置信度复用蒸馏 gene');
165
189
  scored.push(fbScored);
166
190
  selectedGeneId = fb.geneId;
@@ -2,7 +2,9 @@ export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
3
  export * from './epigenetics.js';
4
4
  export * from './confidence.js';
5
+ export * from './memoryGraph.js';
5
6
  export * from './capabilityCandidates.js';
7
+ export * from './conversationSniffer.js';
6
8
  export * from './geneIntake.js';
7
9
  export * from './candidateAssembly.js';
8
10
  export * from './genePromotion.js';
@@ -2,7 +2,9 @@ export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
3
  export * from './epigenetics.js';
4
4
  export * from './confidence.js';
5
+ export * from './memoryGraph.js';
5
6
  export * from './capabilityCandidates.js';
7
+ export * from './conversationSniffer.js';
6
8
  export * from './geneIntake.js';
7
9
  export * from './candidateAssembly.js';
8
10
  export * from './genePromotion.js';
@@ -0,0 +1,62 @@
1
+ export type MemoryGraphOutcomeStatus = 'success' | 'failed';
2
+ export interface MemoryGraphOutcomeRecord {
3
+ version: 2;
4
+ kind: 'outcome';
5
+ provenance: 'v2_local' | 'v1_import';
6
+ workspaceScope: string;
7
+ userScope: string;
8
+ signalFingerprint: string;
9
+ signals: readonly string[];
10
+ geneId: string;
11
+ status: MemoryGraphOutcomeStatus;
12
+ score: number;
13
+ at: string;
14
+ /** Stable local-source identity used to make one-time imports crash-retry safe. */
15
+ sourceFingerprint?: string;
16
+ successCount?: number;
17
+ failCount?: number;
18
+ }
19
+ export interface MemoryGraphGeneEvidence {
20
+ geneId: string;
21
+ boost: number;
22
+ expectedSuccess: number;
23
+ successCount: number;
24
+ failCount: number;
25
+ attempts: number;
26
+ similarity: number;
27
+ lastAt: string;
28
+ }
29
+ export interface MemoryGraphDiagnostics {
30
+ bytesRead: number;
31
+ recordsRead: number;
32
+ corruptLines: number;
33
+ oversizedLines: number;
34
+ scopeRejected: number;
35
+ provenanceRejected: number;
36
+ truncated: boolean;
37
+ recovery: 'healthy' | 'degraded' | 'recovered' | 'empty';
38
+ /** The graph was healthy enough to address, but another process held its maintenance lock. */
39
+ busy?: boolean;
40
+ }
41
+ export interface MemoryGraphAdvice {
42
+ genes: readonly MemoryGraphGeneEvidence[];
43
+ diagnostics: MemoryGraphDiagnostics;
44
+ }
45
+ export interface MemoryGraphQueryInput {
46
+ workspace: string;
47
+ signals: readonly string[];
48
+ }
49
+ export interface MemoryGraphRecordInput extends MemoryGraphQueryInput {
50
+ geneId: string;
51
+ status: MemoryGraphOutcomeStatus;
52
+ score: number;
53
+ at: string;
54
+ }
55
+ export interface MemoryGraphProvider {
56
+ query(input: MemoryGraphQueryInput): Promise<MemoryGraphAdvice> | MemoryGraphAdvice;
57
+ recordOutcome(input: MemoryGraphRecordInput): Promise<void> | void;
58
+ }
59
+ export declare function normalizeMemorySignals(signals: readonly string[]): string[];
60
+ export declare function memorySignalFingerprint(signals: readonly string[]): string;
61
+ export declare function safeMemoryGeneId(value: string): string;
62
+ export declare function deriveMemoryGraphAdvice(records: readonly MemoryGraphOutcomeRecord[], signals: readonly string[], nowMs: number, diagnostics: MemoryGraphDiagnostics): MemoryGraphAdvice;
@@ -0,0 +1,86 @@
1
+ const MAX_SIGNALS = 32;
2
+ const MAX_SIGNAL_CHARS = 120;
3
+ const MAX_GENE_CHARS = 240;
4
+ const MIN_SIMILARITY = 0.34;
5
+ const HALF_LIFE_DAYS = 30;
6
+ export function normalizeMemorySignals(signals) {
7
+ return [...new Set(signals
8
+ .filter((signal) => typeof signal === 'string')
9
+ .map((signal) => signal.trim().toLowerCase().slice(0, MAX_SIGNAL_CHARS))
10
+ .filter(Boolean))]
11
+ .sort()
12
+ .slice(0, MAX_SIGNALS);
13
+ }
14
+ export function memorySignalFingerprint(signals) {
15
+ return normalizeMemorySignals(signals).join('|') || '(none)';
16
+ }
17
+ export function safeMemoryGeneId(value) {
18
+ return value.trim().slice(0, MAX_GENE_CHARS);
19
+ }
20
+ export function deriveMemoryGraphAdvice(records, signals, nowMs, diagnostics) {
21
+ const currentSignals = normalizeMemorySignals(signals);
22
+ const aggregates = new Map();
23
+ for (const record of records) {
24
+ const similarity = jaccard(currentSignals, record.signals);
25
+ if (similarity < MIN_SIMILARITY)
26
+ continue;
27
+ const successCount = boundedCount(record.successCount ?? (record.status === 'success' ? 1 : 0));
28
+ const failCount = boundedCount(record.failCount ?? (record.status === 'failed' ? 1 : 0));
29
+ const total = successCount + failCount;
30
+ if (total === 0)
31
+ continue;
32
+ const decay = decayWeight(record.at, nowMs);
33
+ const weight = similarity * decay;
34
+ const current = aggregates.get(record.geneId) ?? { success: 0, fail: 0, weightedSimilarity: 0, weight: 0, lastAt: record.at };
35
+ current.success += successCount * weight;
36
+ current.fail += failCount * weight;
37
+ current.weightedSimilarity += similarity * total;
38
+ current.weight += total;
39
+ if (Date.parse(record.at) > Date.parse(current.lastAt))
40
+ current.lastAt = record.at;
41
+ aggregates.set(record.geneId, current);
42
+ }
43
+ const genes = [];
44
+ for (const [geneId, aggregate] of aggregates) {
45
+ const successCount = Math.round(aggregate.success);
46
+ const failCount = Math.round(aggregate.fail);
47
+ const attempts = successCount + failCount;
48
+ if (attempts === 0)
49
+ continue;
50
+ const expectedSuccess = (aggregate.success + 1) / (aggregate.success + aggregate.fail + 2);
51
+ const similarity = aggregate.weight > 0 ? Math.min(1, aggregate.weightedSimilarity / aggregate.weight) : 0;
52
+ const confidence = Math.min(1, Math.log2(attempts + 1) / 3);
53
+ const boost = clamp((expectedSuccess - 0.5) * 2 * similarity * confidence, -1, 1);
54
+ genes.push({ geneId, boost, expectedSuccess, successCount, failCount, attempts, similarity, lastAt: aggregate.lastAt });
55
+ }
56
+ genes.sort((left, right) => Math.abs(right.boost) - Math.abs(left.boost) || right.attempts - left.attempts || left.geneId.localeCompare(right.geneId));
57
+ return { genes: genes.slice(0, 64), diagnostics };
58
+ }
59
+ function boundedCount(value) {
60
+ if (!Number.isFinite(value) || value <= 0)
61
+ return 0;
62
+ return Math.min(1_000_000, Math.floor(value));
63
+ }
64
+ function jaccard(left, right) {
65
+ const a = new Set(normalizeMemorySignals(left));
66
+ const b = new Set(normalizeMemorySignals(right));
67
+ if (a.size === 0 && b.size === 0)
68
+ return 1;
69
+ if (a.size === 0 || b.size === 0)
70
+ return 0;
71
+ let intersection = 0;
72
+ for (const value of a)
73
+ if (b.has(value))
74
+ intersection += 1;
75
+ return intersection / (a.size + b.size - intersection);
76
+ }
77
+ function decayWeight(at, nowMs) {
78
+ const timestamp = Date.parse(at);
79
+ if (!Number.isFinite(timestamp))
80
+ return 0;
81
+ const ageDays = Math.max(0, nowMs - timestamp) / 86_400_000;
82
+ return Math.pow(0.5, ageDays / HALF_LIFE_DAYS);
83
+ }
84
+ function clamp(value, min, max) {
85
+ return Math.max(min, Math.min(max, value));
86
+ }
@@ -7,6 +7,7 @@ import type { GeneCandidateInput } from './geneSelection.js';
7
7
  import { CycleEngine, type CycleInput, type CycleResult, type SolidifyPermitGate } from './cycleEngine.js';
8
8
  import { type PendingSignalsContext } from '../assetstore/pendingSignals.js';
9
9
  import { type ReuseOutcomeSummary, type ReuseOutcomeEvent } from '../ops/reuseOutcomes.js';
10
+ import type { MemoryGraphAdvice } from './memoryGraph.js';
10
11
  export interface RunCycleOptions {
11
12
  cycleId: string;
12
13
  problem: ProblemPattern;
@@ -60,6 +61,8 @@ export interface RunCycleOptions {
60
61
  * agent self-report) gains teeth on selection. Omit → no recall contribution. Never feeds quarantine.
61
62
  */
62
63
  recallEvents?: readonly ReuseOutcomeEvent[];
64
+ /** Scoped local MemoryGraph query result. Omit to keep selection unchanged. */
65
+ memoryGraphAdvice?: MemoryGraphAdvice;
63
66
  }
64
67
  /** Drive one full evolution cycle end-to-end: assemble candidates from the store, then run the cycle. */
65
68
  export declare function runEvolutionCycle(engine: CycleEngine, store: AssetStoreProvider, opts: RunCycleOptions): Promise<CycleResult>;
@@ -38,7 +38,18 @@ export async function runEvolutionCycle(engine, store, opts) {
38
38
  ...(opts.hubCandidates && opts.hubCandidates.length > 0 ? { hubCandidates: opts.hubCandidates } : {}),
39
39
  ...(reuseCounts.size > 0 ? { reuseCounts } : {}),
40
40
  };
41
- const { candidates, distilledFallback, antiWarnings } = await assembleSelectionPool(store, signals, asmOpts);
41
+ const { candidates: assembledCandidates, distilledFallback, antiWarnings } = await assembleSelectionPool(store, signals, asmOpts);
42
+ const memoryByGene = new Map((opts.memoryGraphAdvice?.genes ?? []).map((evidence) => [evidence.geneId, evidence]));
43
+ const candidates = assembledCandidates.map((candidate) => {
44
+ const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
45
+ return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
46
+ });
47
+ const memoryDistilledFallback = distilledFallback.map((candidate) => {
48
+ const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
49
+ return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
50
+ });
51
+ const memoryEligible = [...candidates, ...memoryDistilledFallback];
52
+ const memoryEvidence = opts.memoryGraphAdvice?.genes.filter((evidence) => memoryEligible.some((candidate) => candidate.geneId === evidence.geneId || candidate.assetId === evidence.geneId)).slice(0, 3) ?? [];
42
53
  return engine.runCycle({
43
54
  cycleId: opts.cycleId,
44
55
  problem: opts.problem,
@@ -57,7 +68,8 @@ export async function runEvolutionCycle(engine, store, opts) {
57
68
  ...(opts.solidifyPermit ? { solidifyPermit: opts.solidifyPermit } : {}),
58
69
  // #97: forward the distilled-gene fallback pool so a no-signal-match cycle reuses a distilled strategy
59
70
  // (instead of a blind innovate) when nothing clears the floor.
60
- ...(distilledFallback.length > 0 ? { distilledFallback } : {}),
71
+ ...(memoryDistilledFallback.length > 0 ? { distilledFallback: memoryDistilledFallback } : {}),
61
72
  ...(antiWarnings.length > 0 ? { antiWarnings } : {}),
73
+ ...(memoryEvidence.length > 0 ? { memoryEvidence } : {}),
62
74
  });
63
75
  }
@@ -0,0 +1,23 @@
1
+ import type { AssetSyncRecord } from './assetSyncLedger.js';
2
+ import type { ProvenanceRecord } from './provenance.js';
3
+ import type { ReviewRecord } from './reviewLedger.js';
4
+ export type AssetSidecarKind = 'provenance' | 'review' | 'asset-sync';
5
+ export type AssetSidecarCorruptionReason = 'invalid_row' | 'unterminated';
6
+ export interface ParsedSidecarJsonl<T> {
7
+ records: T[];
8
+ rows: number;
9
+ validRows: number;
10
+ corruptRows: number;
11
+ unterminated: boolean;
12
+ }
13
+ export declare class CorruptAssetSidecarError extends Error {
14
+ readonly sidecar: AssetSidecarKind;
15
+ readonly reason: AssetSidecarCorruptionReason;
16
+ readonly code = "CORRUPT_ASSET_SIDECAR";
17
+ constructor(sidecar: AssetSidecarKind, reason: AssetSidecarCorruptionReason);
18
+ }
19
+ export declare function parseSidecarJsonl<T>(raw: string, parseRecord: (value: unknown) => T | null): ParsedSidecarJsonl<T>;
20
+ export declare function assertTrustSidecarHealthy<T>(sidecar: Extract<AssetSidecarKind, 'provenance' | 'review'>, parsed: ParsedSidecarJsonl<T>): void;
21
+ export declare function parseProvenanceRecord(value: unknown): ProvenanceRecord | null;
22
+ export declare function parseReviewRecord(value: unknown): ReviewRecord | null;
23
+ export declare function parseAssetSyncRecord(value: unknown): AssetSyncRecord | null;
@@ -0,0 +1,142 @@
1
+ export class CorruptAssetSidecarError extends Error {
2
+ sidecar;
3
+ reason;
4
+ code = 'CORRUPT_ASSET_SIDECAR';
5
+ constructor(sidecar, reason) {
6
+ super(`corrupt asset sidecar: ${sidecar}/${reason}`);
7
+ this.sidecar = sidecar;
8
+ this.reason = reason;
9
+ this.name = 'CorruptAssetSidecarError';
10
+ }
11
+ }
12
+ export function parseSidecarJsonl(raw, parseRecord) {
13
+ const records = [];
14
+ let rows = 0;
15
+ let corruptRows = 0;
16
+ for (const line of raw.split('\n')) {
17
+ if (!line.trim())
18
+ continue;
19
+ rows += 1;
20
+ try {
21
+ const record = parseRecord(JSON.parse(line));
22
+ if (record)
23
+ records.push(record);
24
+ else
25
+ corruptRows += 1;
26
+ }
27
+ catch {
28
+ corruptRows += 1;
29
+ }
30
+ }
31
+ return {
32
+ records,
33
+ rows,
34
+ validRows: records.length,
35
+ corruptRows,
36
+ unterminated: raw.length > 0 && !raw.endsWith('\n'),
37
+ };
38
+ }
39
+ export function assertTrustSidecarHealthy(sidecar, parsed) {
40
+ if (parsed.unterminated)
41
+ throw new CorruptAssetSidecarError(sidecar, 'unterminated');
42
+ if (parsed.corruptRows > 0)
43
+ throw new CorruptAssetSidecarError(sidecar, 'invalid_row');
44
+ }
45
+ export function parseProvenanceRecord(value) {
46
+ const record = objectRecord(value);
47
+ if (!record)
48
+ return null;
49
+ const assetId = stringField(record, 'assetId');
50
+ const source = stringField(record, 'source');
51
+ const at = stringField(record, 'at');
52
+ if (!assetId
53
+ || (source !== 'local' && source !== 'migrated' && source !== 'hub')
54
+ || typeof record['trusted'] !== 'boolean'
55
+ || !at
56
+ || Number.isNaN(Date.parse(at))) {
57
+ return null;
58
+ }
59
+ const decision = record['decision'] === 'promoted' || record['decision'] === 'revoked'
60
+ ? record['decision']
61
+ : undefined;
62
+ const decidedBy = stringField(record, 'decidedBy');
63
+ const promotedBy = stringField(record, 'promotedBy');
64
+ const reason = stringField(record, 'reason');
65
+ return {
66
+ assetId,
67
+ source,
68
+ trusted: record['trusted'],
69
+ at,
70
+ ...(decision ? { decision } : {}),
71
+ ...(decidedBy ? { decidedBy } : {}),
72
+ ...(promotedBy ? { promotedBy } : {}),
73
+ ...(reason ? { reason } : {}),
74
+ };
75
+ }
76
+ export function parseReviewRecord(value) {
77
+ const record = objectRecord(value);
78
+ if (!record)
79
+ return null;
80
+ const assetId = stringField(record, 'assetId');
81
+ const state = stringField(record, 'state');
82
+ const at = stringField(record, 'at');
83
+ if (!assetId
84
+ || (state !== 'quarantined' && state !== 'approved' && state !== 'rejected')
85
+ || !at
86
+ || Number.isNaN(Date.parse(at))) {
87
+ return null;
88
+ }
89
+ const by = stringField(record, 'by');
90
+ const reason = stringField(record, 'reason');
91
+ return {
92
+ assetId,
93
+ state,
94
+ at,
95
+ ...(by ? { by } : {}),
96
+ ...(reason ? { reason } : {}),
97
+ };
98
+ }
99
+ export function parseAssetSyncRecord(value) {
100
+ const record = objectRecord(value);
101
+ if (!record)
102
+ return null;
103
+ const assetId = stringField(record, 'assetId');
104
+ const type = stringField(record, 'type');
105
+ const source = stringField(record, 'source');
106
+ const scope = stringField(record, 'scope');
107
+ const syncedAt = stringField(record, 'syncedAt');
108
+ const remoteAssetId = stringField(record, 'remoteAssetId');
109
+ if (!assetId
110
+ || (type !== 'Gene' && type !== 'Capsule')
111
+ || source !== 'hub'
112
+ || (scope !== 'purchased' && scope !== 'published')
113
+ || !syncedAt
114
+ || !remoteAssetId) {
115
+ return null;
116
+ }
117
+ const logicalId = stringField(record, 'logicalId');
118
+ const status = stringField(record, 'status');
119
+ const forced = record['forced'] === true;
120
+ const collisionWithAssetId = stringField(record, 'collisionWithAssetId');
121
+ return {
122
+ assetId,
123
+ type,
124
+ source,
125
+ scope,
126
+ syncedAt,
127
+ remoteAssetId,
128
+ ...(logicalId ? { logicalId } : {}),
129
+ ...(status ? { status } : {}),
130
+ ...(forced ? { forced: true } : {}),
131
+ ...(collisionWithAssetId ? { collisionWithAssetId } : {}),
132
+ };
133
+ }
134
+ function objectRecord(value) {
135
+ return value && typeof value === 'object' && !Array.isArray(value)
136
+ ? value
137
+ : null;
138
+ }
139
+ function stringField(value, key) {
140
+ const raw = value[key];
141
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
142
+ }
@@ -0,0 +1,48 @@
1
+ import { type ReleaseLockReason } from '../util/fileLock.js';
2
+ import { type AssetStoreLockDeps } from './assetStoreStorage.js';
3
+ import { type AssetSidecarKind } from './assetSidecarRecords.js';
4
+ export type { AssetSidecarKind } from './assetSidecarRecords.js';
5
+ export type AssetSidecarRecoveryFailureReason = 'sidecar_missing' | 'sidecar_not_degraded' | 'replacement_missing' | 'replacement_too_large' | 'replacement_invalid_utf8' | 'replacement_invalid' | 'replacement_unterminated' | 'valid_history_missing' | 'corrective_record_required' | 'unsafe_corrective_record' | 'acknowledgement_required' | 'sidecar_too_large' | 'sidecar_changed' | 'replacement_changed' | 'backup_conflict';
6
+ export declare class AssetSidecarRecoveryError extends Error {
7
+ readonly reason: AssetSidecarRecoveryFailureReason;
8
+ readonly code = "ASSET_SIDECAR_RECOVERY_FAILED";
9
+ constructor(reason: AssetSidecarRecoveryFailureReason);
10
+ }
11
+ export interface AssetSidecarRecoveryFileReport {
12
+ rows: number;
13
+ validRows: number;
14
+ corruptRows: number;
15
+ unterminated: boolean;
16
+ digest: string;
17
+ }
18
+ export interface AssetSidecarReplacementReport extends AssetSidecarRecoveryFileReport {
19
+ preservedValidRows: number;
20
+ addedRecords: number;
21
+ correctiveRecords: number;
22
+ }
23
+ export interface AssetSidecarRecoveryReport {
24
+ mode: 'preview' | 'write';
25
+ sidecar: AssetSidecarKind;
26
+ changed: boolean;
27
+ wouldWrite: boolean;
28
+ acknowledgementRequired: boolean;
29
+ current: AssetSidecarRecoveryFileReport;
30
+ replacement: AssetSidecarReplacementReport;
31
+ backupId?: string;
32
+ lockReleaseWarning?: ReleaseLockReason;
33
+ }
34
+ export interface RecoverAssetSidecarOptions {
35
+ baseDir: string;
36
+ sidecar: AssetSidecarKind;
37
+ replacementPath: string;
38
+ write?: boolean;
39
+ acknowledgeCorruptHistory?: boolean;
40
+ maxFileBytes?: number;
41
+ deps?: {
42
+ beforeBackup?: () => void;
43
+ beforeReplace?: () => void;
44
+ lock?: AssetStoreLockDeps;
45
+ };
46
+ }
47
+ export declare const DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES: number;
48
+ export declare function recoverAssetSidecar(opts: RecoverAssetSidecarOptions): AssetSidecarRecoveryReport;