@akemi-mio/creativity 0.1.0 → 0.2.0

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,133 @@
1
+ "use strict";
2
+ /**
3
+ * HypothesisFeedbackEvent — 正向反馈闭环证据层(设计 v1.0,P0)
4
+ *
5
+ * 只做三件事:类型契约、规范化校验、幂等键构建。
6
+ * 不做聚合/权重/快照(P1 的 FeedbackLearningService),
7
+ * 不写 Memory、不改 Hypothesis.status、不进晋级门输入。
8
+ *
9
+ * 双轴落点:
10
+ * - CLI/MCP 生产端 → JSONL `feedback-events.jsonl`(交换格式,见 JsonlFeedbackEventStore)
11
+ * - 主进程权威侧 → SQLite `hypothesis_feedback_events`(见 DrizzleFeedbackEventStore)
12
+ * 唯一真相源方向:CLI 只追加 JSONL,主进程 ingest 单向导入(spec §九 R3)。
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.FEEDBACK_SOURCE_TYPES = exports.FEEDBACK_EVENT_TYPES = exports.FEEDBACK_EVENT_SCHEMA_VERSION = void 0;
16
+ exports.buildFeedbackIdempotencyKey = buildFeedbackIdempotencyKey;
17
+ exports.normalizeFeedbackEvent = normalizeFeedbackEvent;
18
+ exports.feedbackEvidenceDimension = feedbackEvidenceDimension;
19
+ exports.FEEDBACK_EVENT_SCHEMA_VERSION = 1;
20
+ exports.FEEDBACK_EVENT_TYPES = [
21
+ 'validated',
22
+ 'adopted',
23
+ 'implemented',
24
+ 'rejected',
25
+ 'regression_failed',
26
+ ];
27
+ exports.FEEDBACK_SOURCE_TYPES = [
28
+ 'experiment',
29
+ 'memory',
30
+ 'adoption',
31
+ 'commit',
32
+ 'manual',
33
+ ];
34
+ function toEpochMs(value, now) {
35
+ if (value === undefined || value === null || value === '')
36
+ return now;
37
+ if (typeof value === 'number' && Number.isFinite(value))
38
+ return value;
39
+ if (typeof value === 'string') {
40
+ const parsed = Date.parse(value);
41
+ if (Number.isFinite(parsed))
42
+ return parsed;
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * 幂等键 = 自然键拼接。同 hypothesisId+eventType+sourceType+sourceId(+commitSha/experimentRunId)
48
+ * 的重复提交会得到同键,存储层靠唯一约束去重。
49
+ */
50
+ function buildFeedbackIdempotencyKey(parts) {
51
+ return [
52
+ parts.hypothesisId,
53
+ parts.eventType,
54
+ parts.sourceType,
55
+ parts.sourceId,
56
+ parts.commitSha || '',
57
+ parts.experimentRunId || '',
58
+ ].join(':');
59
+ }
60
+ function newEventId() {
61
+ return `fbe_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
62
+ }
63
+ /**
64
+ * 规范化 + 校验。不合格事件不丢弃、不生效——调用方写入隔离队列。
65
+ * 注意:本函数不校验 hypothesisId 是否指向真实假设(那需要 store 查询),
66
+ * 由生产者/ingest 在调用前或调用后单独把关(spec §五.1)。
67
+ */
68
+ function normalizeFeedbackEvent(raw, now = Date.now()) {
69
+ if (!raw || typeof raw !== 'object') {
70
+ return { ok: false, reason: 'missing_hypothesis_id', raw };
71
+ }
72
+ const input = raw;
73
+ const hypothesisId = typeof input.hypothesisId === 'string' ? input.hypothesisId.trim() : '';
74
+ if (!hypothesisId) {
75
+ return { ok: false, reason: 'missing_hypothesis_id', raw };
76
+ }
77
+ if (!exports.FEEDBACK_EVENT_TYPES.includes(input.eventType)) {
78
+ return { ok: false, reason: 'invalid_event_type', raw };
79
+ }
80
+ if (!exports.FEEDBACK_SOURCE_TYPES.includes(input.sourceType)) {
81
+ return { ok: false, reason: 'invalid_source_type', raw };
82
+ }
83
+ const sourceId = typeof input.sourceId === 'string' ? input.sourceId.trim() : '';
84
+ if (!sourceId) {
85
+ return { ok: false, reason: 'missing_source_id', raw };
86
+ }
87
+ const commitSha = typeof input.commitSha === 'string' && input.commitSha.trim() ? input.commitSha.trim() : undefined;
88
+ if (input.eventType === 'implemented' && !commitSha) {
89
+ return { ok: false, reason: 'missing_commit_sha_for_implemented', raw };
90
+ }
91
+ const experimentRunId = typeof input.experimentRunId === 'string' && input.experimentRunId.trim() ? input.experimentRunId.trim() : undefined;
92
+ const sourceVersion = typeof input.sourceVersion === 'string' && input.sourceVersion.trim() ? input.sourceVersion.trim() : undefined;
93
+ const evidenceRef = typeof input.evidenceRef === 'string' && input.evidenceRef.trim() ? input.evidenceRef.trim() : undefined;
94
+ const occurredAt = toEpochMs(input.occurredAt, now);
95
+ if (occurredAt === null) {
96
+ return { ok: false, reason: 'invalid_occurred_at', raw };
97
+ }
98
+ const idempotencyKey = typeof input.idempotencyKey === 'string' && input.idempotencyKey.trim()
99
+ ? input.idempotencyKey.trim()
100
+ : buildFeedbackIdempotencyKey({ hypothesisId, eventType: input.eventType, sourceType: input.sourceType, sourceId, commitSha, experimentRunId });
101
+ const event = {
102
+ id: typeof input.id === 'string' && input.id.trim() ? input.id.trim() : newEventId(),
103
+ hypothesisId,
104
+ eventType: input.eventType,
105
+ sourceType: input.sourceType,
106
+ sourceId,
107
+ ...(sourceVersion ? { sourceVersion } : {}),
108
+ ...(commitSha ? { commitSha } : {}),
109
+ ...(experimentRunId ? { experimentRunId } : {}),
110
+ ...(evidenceRef ? { evidenceRef } : {}),
111
+ occurredAt,
112
+ ingestedAt: now,
113
+ idempotencyKey,
114
+ schemaVersion: exports.FEEDBACK_EVENT_SCHEMA_VERSION,
115
+ };
116
+ return { ok: true, event };
117
+ }
118
+ /** 证据维度归类(spec §五.0):选择 / 质量 / 落地。聚合时分维度累计,不合并成"成功次数"。 */
119
+ function feedbackEvidenceDimension(eventType) {
120
+ switch (eventType) {
121
+ case 'adopted':
122
+ return 'selection';
123
+ case 'validated':
124
+ return 'quality';
125
+ case 'implemented':
126
+ return 'landing';
127
+ case 'rejected':
128
+ case 'regression_failed':
129
+ return 'negative';
130
+ default:
131
+ return 'negative';
132
+ }
133
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * FeedbackLearning — 正向反馈闭环离线学习核心(设计 v1.0,P1)
3
+ *
4
+ * 纯计算,不碰 DB、不写 Memory、不改假设状态、不进晋级门输入。
5
+ * 输入规范化证据事件 + 假设 provenance,输出:
6
+ * - ConceptPairWeight:概念对 × 三维度(选择/质量/落地)加权证据
7
+ * - FeedbackSnapshot:不可变 + 版本号(sha256 前 12 位)
8
+ * - 离线回放:原始 vs 反馈策略的逐对调整与证据追溯
9
+ *
10
+ * 评审决议(spec §十三)落地:
11
+ * §13.2 带权 Beta 变体:衰减后计数为小数,不取整(取整顺序相关会破坏 reproducibility)。
12
+ * §13.3 三维度归一分母 = 该维度 S_d + F_d,不跨维度、不用假设数;无事件维度取先验均值。
13
+ * §13.1 batch 身份:experimentRunId 优先,否则 hypothesisId(或 implemented 的 commitSha)。
14
+ * §13.4 快照只带结构化字段,禁止 raw 事件正文。
15
+ */
16
+ import { type HypothesisFeedbackEvent, type HypothesisFeedbackEventType } from './FeedbackEvents';
17
+ import type { FeedbackMixerInput } from './ConceptMixer';
18
+ export type FeedbackDimension = 'selection' | 'quality' | 'landing' | 'negative';
19
+ export declare const POSITIVE_DIMENSIONS: readonly FeedbackDimension[];
20
+ export interface DimensionEstimate {
21
+ /** 衰减后的正向证据计数(小数,带权 Beta 变体) */
22
+ positiveCount: number;
23
+ /** 衰减后的负向证据计数(小数) */
24
+ negativeCount: number;
25
+ /** 原始事件数(未衰减,用于追溯) */
26
+ evidenceCount: number;
27
+ /** Beta 后验均值 θ_d = (α0+S_d)/(α0+β0+S_d+F_d);无事件时 = 先验均值 */
28
+ posteriorMean: number;
29
+ /** 置信度 = (S+F)/(S+F+α0+β0),先验信息之外的占比 */
30
+ confidence: number;
31
+ /** 该维度最近证据时间,epoch ms */
32
+ lastEvidenceAt: number;
33
+ }
34
+ export type AttributionKind = 'pair' | 'hypothesis_only';
35
+ export interface ConceptPairWeight {
36
+ conceptAId: string;
37
+ conceptBId: string;
38
+ /** 规范化 pair key(已排序),用于跨实现稳定对齐 */
39
+ pairKey: string;
40
+ /** 归因级别:pair=可归因到概念对;hypothesis_only=provenance 缺失 */
41
+ attribution: AttributionKind;
42
+ selection: DimensionEstimate;
43
+ quality: DimensionEstimate;
44
+ landing: DimensionEstimate;
45
+ negative: DimensionEstimate;
46
+ lastEvidenceAt: number;
47
+ /** 产出该权重的快照版本(由 buildFeedbackSnapshot 回填) */
48
+ feedbackSnapshotVersion?: string;
49
+ }
50
+ export interface FeedbackLearningConfig {
51
+ /** 时间衰减半衰期(天),w(t)=2^(-Δt/H) */
52
+ halfLifeDays: number;
53
+ /** Beta 先验参数 */
54
+ alpha0: number;
55
+ beta0: number;
56
+ /** 置信度下限所需的最小样本数(参考量) */
57
+ minSamples: number;
58
+ /** 每类事件的证据权重上限(初始实验参数,spec §五.0/§13.3) */
59
+ weightCaps: {
60
+ validated: number;
61
+ adopted: number;
62
+ implemented: number;
63
+ rejected: number;
64
+ regression_failed: number;
65
+ };
66
+ /** 归因覆盖率低于此值时,只启用假设级归因(R5) */
67
+ attributionMinCoverage: number;
68
+ /** 探索预算比例(P2 消费,快照 hash 必须含) */
69
+ explorationBudget: number;
70
+ /** λ clamp 占总评分范围百分比(P2 消费,快照 hash 必须含) */
71
+ lambdaClampPct: number;
72
+ }
73
+ export declare const DEFAULT_FEEDBACK_LEARNING_CONFIG: FeedbackLearningConfig;
74
+ export interface HypothesisProvenanceLike {
75
+ sources?: Array<{
76
+ name: string;
77
+ type: string;
78
+ origin?: string;
79
+ timestamp?: string;
80
+ }>;
81
+ }
82
+ /** 单条事件对某维度的贡献权重(未衰减)。负向事件归 negative 维度。 */
83
+ export declare function evidenceWeight(eventType: HypothesisFeedbackEventType, caps: FeedbackLearningConfig['weightCaps']): number;
84
+ export declare function isNegativeEvent(eventType: HypothesisFeedbackEventType): boolean;
85
+ /** 时间衰减 w(t) = 2^(-Δt/H),Δt 以天为单位;未来事件不放大。 */
86
+ export declare function timeDecay(occurredAt: number, now: number, halfLifeDays: number): number;
87
+ /** 概念身份:取 origin(稳定字段),缺失时回退 name 并标记 hypothesis_only。 */
88
+ export declare function conceptIdOf(source: {
89
+ name: string;
90
+ origin?: string;
91
+ }): string;
92
+ export declare function pairKeyOf(a: string, b: string): string;
93
+ /** 稳定序列化:对象键排序、数组保序,保证跨平台 hash 一致。 */
94
+ export declare function canonicalJson(value: unknown): string;
95
+ export declare function hashPrefix(input: string, length?: number): string;
96
+ export interface EvidenceTraceEntry {
97
+ hypothesisId: string;
98
+ eventType: HypothesisFeedbackEventType;
99
+ sourceId: string;
100
+ pairKey: string;
101
+ dimension: FeedbackDimension;
102
+ weight: number;
103
+ batchKey: string;
104
+ duplicate: boolean;
105
+ }
106
+ export interface AggregateResult {
107
+ pairs: ConceptPairWeight[];
108
+ /** 正向事件中可归因到概念对的比例(R5) */
109
+ attributionCoverage: number;
110
+ /** 重复事件造成的重复加权比例(去重后为 0;验收指标) */
111
+ duplicateApplicationRate: number;
112
+ appliedCount: number;
113
+ /** 被去重抑制的重复事件数(可观测,非验收指标) */
114
+ dedupedEventCount: number;
115
+ /** 逐事件证据追溯 */
116
+ trace: EvidenceTraceEntry[];
117
+ }
118
+ /**
119
+ * 聚合概念对权重。
120
+ *
121
+ * @param events 规范化后的证据事件(可乱序;内部稳定排序)
122
+ * @param hypotheses hypothesisId → provenance(用于概念对归因)
123
+ * @param config 学习配置
124
+ * @param now 评估时刻,epoch ms
125
+ */
126
+ export declare function aggregateConceptPairWeights(events: HypothesisFeedbackEvent[], hypotheses: Record<string, HypothesisProvenanceLike | undefined>, config?: FeedbackLearningConfig, now?: number): AggregateResult;
127
+ export interface FeedbackSnapshot {
128
+ /** 不可变版本号 = sha256(排序后规范化事件窗口 + 配置 + schemaVersion) 前 12 位 */
129
+ version: string;
130
+ configHash: string;
131
+ generatedAt: number;
132
+ eventCount: number;
133
+ attributionCoverage: number;
134
+ duplicateApplicationRate: number;
135
+ /** 被去重抑制的重复事件数(可观测) */
136
+ dedupedEventCount: number;
137
+ pairs: ConceptPairWeight[];
138
+ }
139
+ /**
140
+ * 构建不可变快照。同输入 + 同配置 + 同版本 → 同 hash(100% 可复现)。
141
+ */
142
+ export declare function buildFeedbackSnapshot(events: HypothesisFeedbackEvent[], hypotheses: Record<string, HypothesisProvenanceLike | undefined>, config?: FeedbackLearningConfig, now?: number): FeedbackSnapshot;
143
+ export interface FeedbackAdjustment {
144
+ pairKey: string;
145
+ /** 综合调整 ∈ [-1, 1] */
146
+ adjustment: number;
147
+ evidenceCount: number;
148
+ posteriorMean: number;
149
+ confidence: number;
150
+ /** λ clamp 后的调整(占总评分范围的比例上限) */
151
+ clampedAdjustment: number;
152
+ }
153
+ /**
154
+ * 离线回放:给定概念对权重,计算有界反馈调整(λ clamp)。
155
+ * 不读 DB、不写任何状态;供 P2 接入 ConceptMixer 前做离线对比。
156
+ */
157
+ export declare function replayFeedbackAdjustment(weight: ConceptPairWeight, config?: FeedbackLearningConfig): FeedbackAdjustment;
158
+ export type FeedbackBand = 'low' | 'medium' | 'high';
159
+ export interface FeedbackPromptSource {
160
+ pairKey: string;
161
+ conceptA: string;
162
+ conceptB: string;
163
+ dimension: FeedbackDimension;
164
+ evidenceCount: number;
165
+ posteriorBand: FeedbackBand;
166
+ confidenceBand: FeedbackBand;
167
+ scope: string;
168
+ limits: string;
169
+ }
170
+ export interface FeedbackPromptFailure {
171
+ pairKey: string;
172
+ conceptA: string;
173
+ conceptB: string;
174
+ dimension: FeedbackDimension;
175
+ evidenceCount: number;
176
+ scope: string;
177
+ limits: string;
178
+ }
179
+ export interface FeedbackPromptBlock {
180
+ version: string;
181
+ verified: FeedbackPromptSource[];
182
+ failures: FeedbackPromptFailure[];
183
+ }
184
+ /** 确定性裁剪:字符串上限 120 字符(§13.4)。 */
185
+ export declare function clipPromptText(s: string, limit?: number): string;
186
+ /**
187
+ * 从不可变快照构建结构化提示块。只带结构化字段,永不回灌 raw 事件正文
188
+ * 或 provenance.origin 自由文本(§13.4,红队注入面)。
189
+ */
190
+ export declare function buildFeedbackPromptBlock(snapshot: FeedbackSnapshot): FeedbackPromptBlock;
191
+ /**
192
+ * 快照 → ConceptMixer 有界消费输入(spec §六.2)。
193
+ * pairAdjustments 用快照 pairKey(conceptId 对齐);λ clamp 由 mixer 再兜底。
194
+ */
195
+ export declare function buildMixerInputFromSnapshot(snapshot: FeedbackSnapshot, config?: FeedbackLearningConfig): FeedbackMixerInput;