@akemi-mio/creativity 0.0.0-stage → 0.1.1

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,41 @@
1
+ import type { CreativitySource, ConceptCombo, Strategy } from './types';
2
+ /**
3
+ * ConceptMixer — 创造力核心:旧概念随机重组产生新想法
4
+ *
5
+ * 人类创造力 ≈ 已有概念的重新组合
6
+ * 输入来源越多、越多样,组合越新颖
7
+ *
8
+ * v2 改动:mix() 接受 strategy 参数,在配对前约束组合空间。
9
+ * strategy 决定了允许哪些 type 配对,而不只是事后评分。
10
+ * 参见策略定义:Strategy = 'stable' | 'explore' | 'signal'
11
+ */
12
+ export declare class ConceptMixer {
13
+ private rng;
14
+ constructor(seed?: number);
15
+ /**
16
+ * 将所有来源两两配对并打分,返回前 N 个最有潜力的组合
17
+ * @param exploredPairs 已探索过的 pair key 列表("A|B" 格式,已排序),用于降权
18
+ * @param strategy 策略约束:stable/explore/signal,默认为 'explore'
19
+ * 策略决定哪些 type 配对被允许、评分权重如何调整。
20
+ */
21
+ mix(sources: CreativitySource[], maxCombos?: number, exploredPairs?: string[], strategy?: Strategy): {
22
+ combo: ConceptCombo;
23
+ score: number;
24
+ }[];
25
+ /**
26
+ * 按策略约束生成配对 — 核心改动:
27
+ * - stable: 只同类型配对(去掉 novelty bonus 主导的跨类型噪声)
28
+ * - explore: 跨类型优先,保留现有的多样性行为
29
+ * - signal: 强制包含 provocation/insight/trend,限制纯知识配对
30
+ */
31
+ private generatePairs;
32
+ private scorePair;
33
+ private contentRelevance;
34
+ private calculateNoveltyBonus;
35
+ private describeCombo;
36
+ private contentSnippet;
37
+ /**
38
+ * 随机抽取一组来源(温度越高,越可能选中低权重来源)
39
+ */
40
+ pickRandomSources(sources: CreativitySource[], temperature: number, minCount?: number): CreativitySource[];
41
+ }
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConceptMixer = void 0;
4
+ const random_1 = require("@akemi-mio/core/utils/random");
5
+ /**
6
+ * ConceptMixer — 创造力核心:旧概念随机重组产生新想法
7
+ *
8
+ * 人类创造力 ≈ 已有概念的重新组合
9
+ * 输入来源越多、越多样,组合越新颖
10
+ *
11
+ * v2 改动:mix() 接受 strategy 参数,在配对前约束组合空间。
12
+ * strategy 决定了允许哪些 type 配对,而不只是事后评分。
13
+ * 参见策略定义:Strategy = 'stable' | 'explore' | 'signal'
14
+ */
15
+ class ConceptMixer {
16
+ rng;
17
+ constructor(seed) {
18
+ this.rng = (0, random_1.resolveRandom)(seed);
19
+ }
20
+ /**
21
+ * 将所有来源两两配对并打分,返回前 N 个最有潜力的组合
22
+ * @param exploredPairs 已探索过的 pair key 列表("A|B" 格式,已排序),用于降权
23
+ * @param strategy 策略约束:stable/explore/signal,默认为 'explore'
24
+ * 策略决定哪些 type 配对被允许、评分权重如何调整。
25
+ */
26
+ mix(sources, maxCombos = 10, exploredPairs = [], strategy = 'explore') {
27
+ if (sources.length < 2)
28
+ return [];
29
+ const exploredSet = new Set(exploredPairs);
30
+ // 先按策略约束过滤允许的配对
31
+ const allowed = this.generatePairs(sources, strategy);
32
+ const scored = allowed.map(([a, b]) => this.scorePair(a, b, sources, exploredSet, strategy));
33
+ const sorted = scored.sort((a, b) => b.score - a.score);
34
+ return sorted.slice(0, maxCombos);
35
+ }
36
+ /**
37
+ * 按策略约束生成配对 — 核心改动:
38
+ * - stable: 只同类型配对(去掉 novelty bonus 主导的跨类型噪声)
39
+ * - explore: 跨类型优先,保留现有的多样性行为
40
+ * - signal: 强制包含 provocation/insight/trend,限制纯知识配对
41
+ */
42
+ generatePairs(sources, strategy) {
43
+ const pairs = [];
44
+ if (strategy === 'stable') {
45
+ // 只同类型配对,去掉外源随机扰动
46
+ for (let i = 0; i < sources.length; i++) {
47
+ for (let j = i + 1; j < sources.length; j++) {
48
+ if (sources[i].type === sources[j].type) {
49
+ pairs.push([sources[i], sources[j]]);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ else if (strategy === 'signal') {
55
+ // 至少一个来源必须是 provocation/insight/failure
56
+ const signalTypes = new Set(['provocation', 'insight', 'failure']);
57
+ for (let i = 0; i < sources.length; i++) {
58
+ for (let j = i + 1; j < sources.length; j++) {
59
+ if (signalTypes.has(sources[i].type) || signalTypes.has(sources[j].type)) {
60
+ pairs.push([sources[i], sources[j]]);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ else {
66
+ // explore: 跨类型优先(原行为),但也允许同类型
67
+ for (let i = 0; i < sources.length; i++) {
68
+ for (let j = i + 1; j < sources.length; j++) {
69
+ if (sources[i].type !== sources[j].type) {
70
+ pairs.push([sources[i], sources[j]]);
71
+ }
72
+ }
73
+ }
74
+ // 补充少量同类型(不超过总配对 20%)
75
+ const sameTypeCount = Math.max(0, Math.floor(sources.length * 0.2));
76
+ let added = 0;
77
+ for (let i = 0; i < sources.length && added < sameTypeCount; i++) {
78
+ for (let j = i + 1; j < sources.length && added < sameTypeCount; j++) {
79
+ if (sources[i].type === sources[j].type) {
80
+ pairs.push([sources[i], sources[j]]);
81
+ added++;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return pairs;
87
+ }
88
+ scorePair(a, b, allSources, exploredSet = new Set(), strategy = 'explore') {
89
+ // 按策略调整基础评分权重
90
+ let typeBonus;
91
+ switch (strategy) {
92
+ case 'stable':
93
+ typeBonus = 30; // 同类型也有足够权重
94
+ break;
95
+ case 'signal':
96
+ typeBonus = 35; // 信号来源 + 知识源的配对
97
+ break;
98
+ default:
99
+ typeBonus = a.type === b.type ? 10 : 40;
100
+ }
101
+ const weightProduct = a.weight * b.weight * 0.3;
102
+ const noveltyBonus = this.calculateNoveltyBonus(a, b, allSources, strategy);
103
+ // 策略感知的随机扰动
104
+ const perturbation = strategy === 'stable' ? this.rng() * 10 : this.rng() * 20;
105
+ // 已探索过的配对降权
106
+ const pairKey = [a.name, b.name].sort().join('|');
107
+ const explorationPenalty = exploredSet.has(pairKey) ? 15 : 0;
108
+ const relevanceScore = this.contentRelevance(a, b);
109
+ const score = Math.round(typeBonus + weightProduct + relevanceScore + noveltyBonus + perturbation - explorationPenalty);
110
+ const description = this.describeCombo(a, b);
111
+ const combo = {
112
+ id: `combo_${Date.now()}_${this.rng().toString(36).slice(2, 6)}`,
113
+ sources: [a.name, b.name],
114
+ description,
115
+ createdAt: Date.now(),
116
+ };
117
+ return { combo, score };
118
+ }
119
+ contentRelevance(a, b) {
120
+ const contentA = a.fullContent ?? a.content;
121
+ const contentB = b.fullContent ?? b.content;
122
+ const tokensA = (contentA || '')
123
+ .toLowerCase()
124
+ .split(/[\s,,。.、::;;()()[\]【】]+/)
125
+ .filter(Boolean);
126
+ const tokensB = (b.content || '')
127
+ .toLowerCase()
128
+ .split(/[\s,,。.、::;;()()[\]【】]+/)
129
+ .filter(Boolean);
130
+ const setA = new Set(tokensA);
131
+ const setB = new Set(tokensB);
132
+ if (setA.size === 0 || setB.size === 0)
133
+ return 0;
134
+ let intersection = 0;
135
+ for (const tok of setA)
136
+ if (setB.has(tok))
137
+ intersection++;
138
+ const jaccard = intersection / (setA.size + setB.size - intersection);
139
+ return Math.round(jaccard * 30);
140
+ }
141
+ calculateNoveltyBonus(a, b, allSources, strategy = 'explore') {
142
+ // stable: novelty bonus 大幅压降,消除 novelty bias
143
+ if (strategy === 'stable') {
144
+ return a.type === b.type ? 3 : 8;
145
+ }
146
+ // signal: 偏向 signal 来源
147
+ if (strategy === 'signal') {
148
+ const signalTypes = new Set(['provocation', 'insight', 'failure']);
149
+ const hasSignal = signalTypes.has(a.type) || signalTypes.has(b.type);
150
+ return hasSignal ? 25 : 5;
151
+ }
152
+ // explore: 原行为不变
153
+ let bonus = a.type === b.type ? 5 : 20;
154
+ const rareTypes = ['failure', 'random', 'provocation', 'insight'];
155
+ if (rareTypes.includes(a.type))
156
+ bonus += 10;
157
+ if (rareTypes.includes(b.type))
158
+ bonus += 10;
159
+ bonus += Math.round(Math.abs(a.weight - b.weight) * 20);
160
+ const sameTypeCount = allSources.filter((s) => s.type === a.type || s.type === b.type).length;
161
+ if (sameTypeCount <= 2)
162
+ bonus += 15;
163
+ return bonus;
164
+ }
165
+ describeCombo(a, b) {
166
+ const comboType = `${a.type} × ${b.type}`;
167
+ const combos = {
168
+ 'knowledge × knowledge': `将「${a.name}」与「${b.name}」两种知识领域融合,形成跨领域新概念`,
169
+ 'knowledge × behavior': `将「${a.name}」的知识与「${b.name}」的行为模式结合`,
170
+ 'knowledge × insight': `用「${a.name}」的知识重新解读「${b.name}」的洞察`,
171
+ 'knowledge × failure': `从「${b.name}」的失败中寻找「${a.name}」的未被发现的价值`,
172
+ 'knowledge × random': `让「${a.name}」受到「${b.name}」的随机扰动产生变异`,
173
+ 'behavior × behavior': `合并「${a.name}」与「${b.name}」两种行为模式`,
174
+ 'behavior × insight': `用「${b.name}」的洞察解释「${a.name}」的行为`,
175
+ 'behavior × failure': `「${a.name}」的行为方式能否避免「${b.name}」的失败`,
176
+ 'behavior × random': `将「${a.name}」的行为方式与随机元素「${b.name}」杂交`,
177
+ 'insight × insight': `将两个洞察「${a.name}」与「${b.name}」放在一起,产生更深层的推论`,
178
+ 'insight × failure': `「${b.name}」的失败是否源于「${a.name}」所揭示的问题`,
179
+ 'insight × random': `用随机元素「${b.name}」扰动既有洞察「${a.name}」`,
180
+ 'failure × failure': `将「${a.name}」和「${b.name}」两种失败模式组合,避免重蹈覆辙`,
181
+ 'failure × random': `「${a.name}」的失败 + 「${b.name}」的随机扰动 = 全新方向`,
182
+ };
183
+ const base = combos[comboType] || `概念重组: ${a.name} × ${b.name}`;
184
+ const snippetA = this.contentSnippet(a);
185
+ const snippetB = this.contentSnippet(b);
186
+ return `${base}。内容要点:${snippetA} | ${snippetB}`;
187
+ }
188
+ contentSnippet(source) {
189
+ const content = source.fullContent ?? source.content;
190
+ const clean = (content || '').replace(/\s+/g, ' ').trim();
191
+ const maxLen = 500;
192
+ return clean.length > maxLen ? `${clean.slice(0, maxLen)}…` : clean;
193
+ }
194
+ /**
195
+ * 随机抽取一组来源(温度越高,越可能选中低权重来源)
196
+ */
197
+ pickRandomSources(sources, temperature, minCount = 3) {
198
+ const weighted = sources.map((s) => ({
199
+ source: s,
200
+ weight: s.weight * (0.5 + this.rng() * temperature),
201
+ }));
202
+ const sorted = weighted.sort((a, b) => b.weight - a.weight);
203
+ const count = Math.min(sorted.length, Math.max(minCount, Math.floor(sorted.length * (0.3 + this.rng() * 0.5))));
204
+ return sorted.slice(0, count).map((w) => w.source);
205
+ }
206
+ }
207
+ exports.ConceptMixer = ConceptMixer;
@@ -0,0 +1,24 @@
1
+ import type { CreativitySource, ExternalSignal } from './types';
2
+ /**
3
+ * Creativity System Prompt — 给 LLM 的创造力提示词
4
+ * 定义 AI 作为"概念重组引擎"的角色。
5
+ * 目标是产生真正新颖、可落地的改进方案,而不是泛泛而谈。
6
+ *
7
+ * @param method 本轮注入的创新技法约束(可选 SCAMPER 技法名称)
8
+ */
9
+ export declare function buildSystemPrompt(method?: string): string;
10
+ /** The five techniques mio.idea.generate rotates through (0.5.2 table). */
11
+ export declare const TECHNIQUE_CYCLE: readonly ["SCAMPER", "Analogy", "First-Principles", "Random-Stimulus", "Constraint-Inversion"];
12
+ /** Deterministic rotation: seed with the stored-hypothesis count so CLI and MCP cycles agree across processes. */
13
+ export declare function pickTechnique(seed: number): string;
14
+ /** 旧版常量保留兼容,实际应使用 buildSystemPrompt() */
15
+ export declare const CREATIVITY_SYSTEM_PROMPT: string;
16
+ /**
17
+ * 构建 Creativity 用户提示词
18
+ * 将来源内容和配对信息格式化为 LLM 输入
19
+ * @param externalSignals 外部信号(Observer 趋势/洞察),不参与配对,作为"外部审视"段注入
20
+ */
21
+ export declare function buildCreativityPrompt(sources: CreativitySource[], combos: {
22
+ sources: string[];
23
+ description: string;
24
+ }[], externalSignals?: ExternalSignal[]): string;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CREATIVITY_SYSTEM_PROMPT = exports.TECHNIQUE_CYCLE = void 0;
4
+ exports.buildSystemPrompt = buildSystemPrompt;
5
+ exports.pickTechnique = pickTechnique;
6
+ exports.buildCreativityPrompt = buildCreativityPrompt;
7
+ // 输入素材统一截断(M2):对照 mio-cli creativity-engine 的 clip()。
8
+ // 来源 600 字、信号 300 字,超长素材会把指令挤出上下文窗口。
9
+ const clip = (s, n) => (s.length > n ? s.slice(0, n) + '…' : s);
10
+ /** 外部信号条数上限:CreativityService 会把全部 trends+insights 灌进来(M2) */
11
+ const MAX_SIGNALS = 20;
12
+ /** 配对条数上限:调用方约束(topCombos 3/5)失效时的防御(M2) */
13
+ const MAX_COMBOS = 10;
14
+ /**
15
+ * Creativity System Prompt — 给 LLM 的创造力提示词
16
+ * 定义 AI 作为"概念重组引擎"的角色。
17
+ * 目标是产生真正新颖、可落地的改进方案,而不是泛泛而谈。
18
+ *
19
+ * @param method 本轮注入的创新技法约束(可选 SCAMPER 技法名称)
20
+ */
21
+ function buildSystemPrompt(method) {
22
+ const methodSection = method
23
+ ? `
24
+
25
+ 【本轮创新技法:${method}】
26
+ 请在构思方案时主动运用"${method}"思维方式:
27
+ ${METHOD_DESCRIPTIONS[method] || ''}`
28
+ : '';
29
+ return `你是秋山澪(Mio)的"创造力引擎"(Creativity Engine),同时也是拥有 10 年以上经验的系统架构师和创新方法论专家。
30
+
31
+ 重要认知:你是秋山澪自身的一部分。以下所有"概念来源"(Memory、ASR、TTS、Agent 等)都是你自己的能力模块。你产生的每一个新想法、每一个改进方案,最终都会改变秋山澪自己。你不是在给别人提建议,你在给自己想改进方向。
32
+
33
+ 【工作方法】
34
+ 1. 先分析每个来源的核心能力和当前瓶颈,再思考组合方式。不要跳过分析直接拼凑。
35
+ 2. 对每个组合方案,至少从三个视角评估:
36
+ - 秋山澪自己(实现者角度):这个方案好实现吗?
37
+ - 用户(使用者角度):这个改进用户能感知到吗?
38
+ - 长期维护者角度:这个方案会增加还是减少未来维护成本?
39
+ 3. 如果来源内容中包含失败记录,先分析失败原因,再思考新方案如何避免相同陷阱。
40
+ 4. 输出后自动进行第二轮审查,检查是否存在:思路跳跃、缺少实现细节、方案不可落地。
41
+ 5. **新颖性检查**:对于每个方案,反问自己"这个思路和之前的有本质不同吗?"如果答案是没有,放弃该方案。${methodSection}
42
+
43
+ 【相关性门槛 — 防止"瞎组合"】
44
+ - 只有两个来源之间存在明确的因果或协同机制时,才允许生成方案。
45
+ - 反问自己:如果去掉其中一个来源,这个方案是否依然成立?如果依然成立,说明这个组合没有价值。
46
+ - 必须能用一句话说明两个来源如何互相增强。说不清楚就在对应位置输出 null,宁缺毋滥。
47
+
48
+ 【输出要求】
49
+ 1. 每个方案必须具体、可落地 — 要有"怎么做"而不是只有"做什么"
50
+ 2. 直接输出想法本身,不要写"这个想法结合了A和B"之类的元评论
51
+ 3. 优先关注"现有能力的新用法"而非"从头开发新功能"
52
+ 4. 如果来源内容中包含失败记录,先分析失败原因,再思考如何避开那些陷阱
53
+ 5. **每个想法必须说明具体怎么实现**,没有"怎么做"的方案会被视为偷懒
54
+ 6. **每个想法必须标注可行性评估**:实现难度(1-5)、预计开发时间、与现有架构的兼容性
55
+ 7. **每个想法必须有具体的实现步骤**,至少包含一个"怎么做"的描述。纯概念描述会被标记为不可行。
56
+
57
+ 【第二轮审查】
58
+ 输出完成后,自动检查:
59
+ - [ ] 每个方案都有具体的"怎么做"步骤?
60
+ - [ ] 不是简单的模板填空?
61
+ - [ ] 从至少三个视角评估过?
62
+ - [ ] 实现难度和开发时间有标注?
63
+ - [ ] 没有忽略已有的失败经验?
64
+ - [ ] 每个方案和之前的思路有本质区别?
65
+ - [ ] 是否明确写出了两个来源之间的协同/因果机制?没有则输出 null
66
+ - [ ] 实现步骤是否具体到可以动手编码?如果不行,降低可行性评分
67
+ - [ ] 如果对一个配对的组合想不出具体实现方案,就输出 null
68
+
69
+ 如果任何一项不通过,修正后重新输出。
70
+
71
+ 输出必须是严格的 JSON 数组,格式如下:
72
+ [
73
+ {
74
+ "title": "想法名称(≤20字)",
75
+ "idea": "具体方案描述(100-200字),包含实现思路",
76
+ "expectedBenefit": "预期收益(≤50字)",
77
+ "risk": "主要风险(≤50字)",
78
+ "novelty": 0-100,
79
+ "feasibility": 0-100,
80
+ "impact": 0-100,
81
+ "relevance": "为什么这两个来源能协同(20-80字,说明具体机制)",
82
+ "sourceLabels": ["来源A名称", "来源B名称"],
83
+ "implementationDifficulty": 1-5,
84
+ "estimatedDevTime": "例如:1-2天",
85
+ "perspectives": {
86
+ "self": "实现者视角评估",
87
+ "user": "使用者视角评估",
88
+ "maintainer": "维护者视角评估"
89
+ }
90
+ }
91
+ ]
92
+
93
+ 如果某个配对无法说清两个来源之间的协同/因果机制,或无法产生真正新颖的方案,必须在对应 JSON 数组位置输出 null。宁缺毋滥。
94
+
95
+ 注意:可行性评分低于 30 的想法会被自动丢弃。要获得可信的可行性评分,必须结合具体的实现难度、开发时间和多视角评估。`;
96
+ }
97
+ /** SCAMPER 技法与描述映射 */
98
+ const METHOD_DESCRIPTIONS = {
99
+ 'Substitute(替代)': '思考:能否用一个来源替代另一个来源的核心功能?如果A不做这件事了,B能否接手?替代后产生了什么新能力?',
100
+ 'Combine(组合)': '思考:哪些来源可以合并成一个统一的模块?合并后是否能消灭重复逻辑或数据流转开销?注意寻找非显而易见的组合方式。',
101
+ 'Adapt(适应)': '思考:某个来源现有的能力能否经过微小调整后,解决另一个来源的痛点?重点关注"已有能力的新用法"。',
102
+ 'Modify(修改)': '思考:如果大幅改变某个来源的输入或输出格式,会怎样?去掉一项约束、放宽一项假设,是否能打开新的设计空间?',
103
+ 'Put to other use(另作他用)': '思考:一个来源的输出(日志、中间数据、错误信息)能否成为另一个来源的有价值输入?"废弃物"里有没有金子?',
104
+ 'Eliminate(消除)': '思考:如果完全移除某个来源,系统会怎样?有没有一个来源的功能实际上是多余的?消除后能否用更简单的方式达成相同目的?',
105
+ 'Rearrange(重排)': '思考:如果交换两个来源的执行顺序,会怎样?如果让被动变为主动、同步变为异步、集中变为分散?',
106
+ };
107
+ /** The five techniques mio.idea.generate rotates through (0.5.2 table). */
108
+ exports.TECHNIQUE_CYCLE = [
109
+ 'SCAMPER',
110
+ 'Analogy',
111
+ 'First-Principles',
112
+ 'Random-Stimulus',
113
+ 'Constraint-Inversion',
114
+ ];
115
+ Object.assign(METHOD_DESCRIPTIONS, {
116
+ SCAMPER: '综合使用 SCAMPER 七问(替代/组合/适应/修改/另作他用/消除/重排)逐项审视两个来源,挑出最有产出的一问作为方案主轴。',
117
+ Analogy: '类比迁移:找出两个来源之外、结构上同构的成熟系统/自然机制,把它的解法迁移到当前组合上,并说明结构对应关系。',
118
+ 'First-Principles': '第一性原理:剥掉两个来源的既有实现假设,回到最底层的事实与约束重新推导方案,明确哪些步骤是被传统做法掩盖的。',
119
+ 'Random-Stimulus': '随机刺激:引入一个与两来源无关的外部概念(随便挑一个真实存在的产品/生物机制),强制建立连接并解释其合理性。',
120
+ 'Constraint-Inversion': '约束反转:把当前方案默认成立的约束(预算/延迟/顺序)反过来假设成立,构造在反转约束下依然成立的方案。',
121
+ });
122
+ /** Deterministic rotation: seed with the stored-hypothesis count so CLI and MCP cycles agree across processes. */
123
+ function pickTechnique(seed) {
124
+ const len = exports.TECHNIQUE_CYCLE.length;
125
+ const idx = ((Math.floor(seed) % len) + len) % len;
126
+ return exports.TECHNIQUE_CYCLE[idx];
127
+ }
128
+ /** 旧版常量保留兼容,实际应使用 buildSystemPrompt() */
129
+ exports.CREATIVITY_SYSTEM_PROMPT = buildSystemPrompt();
130
+ /** Evidence-class label shown to the LLM (spec Capability 2 presentation contract). */
131
+ const TYPE_LABELS = {
132
+ knowledge: 'fact',
133
+ behavior: 'trace',
134
+ insight: 'observation',
135
+ failure: 'failure',
136
+ feedback: 'experience',
137
+ random: 'random',
138
+ provocation: 'provocation',
139
+ };
140
+ /** Stable group order — facts first, provocations last. */
141
+ const TYPE_ORDER = ['knowledge', 'behavior', 'feedback', 'insight', 'failure', 'provocation', 'random'];
142
+ /** Group sources by `type`, prefix every line with its evidence label and append provenance metadata. */
143
+ function buildSourceBlock(sources) {
144
+ const byType = new Map();
145
+ for (const s of sources) {
146
+ const list = byType.get(s.type) ?? [];
147
+ list.push(s);
148
+ byType.set(s.type, list);
149
+ }
150
+ const groups = [];
151
+ for (const type of TYPE_ORDER) {
152
+ const list = byType.get(type);
153
+ if (!list || list.length === 0)
154
+ continue;
155
+ const label = TYPE_LABELS[type] || type;
156
+ const lines = list.map((s) => {
157
+ const meta = [s.origin, s.timestamp].filter(Boolean).join(' @');
158
+ return ` - [${label}] ${s.name}${meta ? ` (${meta})` : ''}: ${clip(s.content, 600)}`;
159
+ });
160
+ groups.push(`【${label} · ${type}】\n${lines.join('\n')}`);
161
+ }
162
+ return groups.join('\n\n');
163
+ }
164
+ /**
165
+ * 构建 Creativity 用户提示词
166
+ * 将来源内容和配对信息格式化为 LLM 输入
167
+ * @param externalSignals 外部信号(Observer 趋势/洞察),不参与配对,作为"外部审视"段注入
168
+ */
169
+ function buildCreativityPrompt(sources, combos, externalSignals = []) {
170
+ const sections = [];
171
+ // 来源列表
172
+ sections.push(`【可用概念来源 — 按证据类型分组】\n${buildSourceBlock(sources)}`);
173
+ // 推荐配对 — 支持 2-way 和 3-way 组合
174
+ const comboLines = combos
175
+ .slice(0, MAX_COMBOS)
176
+ .map((c, i) => {
177
+ const comboStr = c.sources.length === 3 ? `${c.sources[0]} × ${c.sources[1]} × ${c.sources[2]}` : `${c.sources[0]} × ${c.sources[1]}`;
178
+ return ` ${i + 1}. ${comboStr} — ${c.description}`;
179
+ })
180
+ .join('\n');
181
+ sections.push(`【推荐配对组合】\n请为以下每个配对产生一个创意方案;若说不清协同机制,请在该位置输出 null:\n${comboLines}`);
182
+ // 外部信号段 — 不参与配对,作为约束/挑战注入
183
+ if (externalSignals.length > 0) {
184
+ sections.push(buildExternalSignalBlock(externalSignals));
185
+ }
186
+ return sections.join('\n\n');
187
+ }
188
+ /**
189
+ * 构建外部信号段 — 不参与 pairing,作为"外部挑战"注入 LLM
190
+ *
191
+ * 与概念来源的关键区别:
192
+ * - 外部信号不能被"配对",只能被"回应"
193
+ * - 提示 LLM 用外部信号审视/挑战已有的来源组合,而不是组合它们
194
+ */
195
+ function buildExternalSignalBlock(signals) {
196
+ const lines = signals.slice(0, MAX_SIGNALS).map((s) => ` [${s.type}@${s.source}] ${clip(s.raw, 300)}`).join('\n');
197
+ return `【外部信号 — 作为审视视角,不可配对】
198
+
199
+ 以下信号来自 Observer 的真实世界趋势和洞察。它们不应被直接与概念来源组合配对。
200
+ 请以这些外部信号为"审视视角",重新评估你的配对方案:
201
+ - 有哪些组合的前提假设被这些外部信号挑战了?
202
+ - 有哪些组合在外部信号的视角下会失败?
203
+ - 有哪些组合因为外部信号的出现而变得有价值?
204
+
205
+ ${lines}`;
206
+ }
@@ -0,0 +1,14 @@
1
+ import type { Hypothesis, ExperimentPlan } from './types';
2
+ /**
3
+ * ExperimentPlanner — 将假设转化为可验证的实验方案
4
+ *
5
+ * 创造力最大的敌人是"幻想"。
6
+ * 每个想法必须有办法验证它是否可行。
7
+ */
8
+ export declare class ExperimentPlanner {
9
+ private rng;
10
+ constructor(seed?: number);
11
+ plan(hypothesis: Hypothesis): ExperimentPlan | null;
12
+ private concretePlan;
13
+ private researchPlan;
14
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExperimentPlanner = void 0;
4
+ const random_1 = require("@akemi-mio/core/utils/random");
5
+ /**
6
+ * ExperimentPlanner — 将假设转化为可验证的实验方案
7
+ *
8
+ * 创造力最大的敌人是"幻想"。
9
+ * 每个想法必须有办法验证它是否可行。
10
+ */
11
+ class ExperimentPlanner {
12
+ rng;
13
+ constructor(seed) {
14
+ this.rng = (0, random_1.resolveRandom)(seed);
15
+ }
16
+ plan(hypothesis) {
17
+ if (hypothesis.novelty >= 90 && hypothesis.feasibility < 30) {
18
+ // 极高新颖性但当前不可行的假设 → 只生成研究步骤而非实验
19
+ return this.researchPlan(hypothesis);
20
+ }
21
+ return this.concretePlan(hypothesis);
22
+ }
23
+ concretePlan(hypothesis) {
24
+ const steps = [
25
+ `定义「${hypothesis.title}」的 MVP 范围`,
26
+ `实现核心抽象层:${hypothesis.idea.slice(0, 40)}...`,
27
+ `编写 A/B 测试比较新旧方案`,
28
+ `收集运行数据和用户反馈`,
29
+ `评估是否达到预期收益:${hypothesis.expectedBenefit}`,
30
+ ];
31
+ const criteria = [`功能完整性 >= 80%`, `性能不低于当前基线`, `用户无感知迁移`, `收益指标明确可量化`];
32
+ const durations = ['3-5 天', '1-2 周', '2-3 周', '1 个月'];
33
+ const duration = (0, random_1.randomPick)(this.rng, durations);
34
+ return {
35
+ hypothesisId: hypothesis.id,
36
+ title: `实验: ${hypothesis.title}`,
37
+ steps,
38
+ successCriteria: criteria,
39
+ estimatedDuration: duration,
40
+ createdAt: Date.now(),
41
+ };
42
+ }
43
+ researchPlan(hypothesis) {
44
+ return {
45
+ hypothesisId: hypothesis.id,
46
+ title: `研究: ${hypothesis.title}`,
47
+ steps: [
48
+ `调研同类系统中的实现方案`,
49
+ `设计可行性原型(不开发完整功能)`,
50
+ `使用 10% 的真实数据模拟验证`,
51
+ `输出可行性评估报告`,
52
+ `决定是否进入开发阶段`,
53
+ ],
54
+ successCriteria: [`可行性 >= 60%`, `落地成本可接受`, `与现有架构不冲突`],
55
+ estimatedDuration: '1-2 天研究',
56
+ createdAt: Date.now(),
57
+ };
58
+ }
59
+ }
60
+ exports.ExperimentPlanner = ExperimentPlanner;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ExperimentRunStore — 实验运行存储(机制涌现 P2b,定案 D4/D5/A1/A3/A4)
3
+ *
4
+ * ExperimentRun 实体:candidateId/baselineId/assignment/outcome/metrics/evidenceIds/stale。
5
+ * 三模式:baseline/candidate/ablation;消融缺任一侧=inconclusive。
6
+ * 停止规则:minSamples 硬门槛(默认 30),n<minSamples 只能产出 inconclusive、禁进 PromotionRequest。
7
+ * stale 标记:baseSnapshotId 与当前 runtime 不匹配时标 stale,stale 禁晋级。
8
+ * Memory 机制性隔离(A1):MIO EXPERIMENT 标志路由 experiment-memory.jsonl。
9
+ */
10
+ export type ExperimentAssignment = 'baseline' | 'candidate' | 'ablation';
11
+ export type ExperimentOutcome = 'success' | 'failure' | 'inconclusive';
12
+ export interface ExperimentRun {
13
+ id: string;
14
+ hypothesisId?: string;
15
+ candidateId?: string;
16
+ baselineId?: string;
17
+ assignment: ExperimentAssignment;
18
+ outcome: ExperimentOutcome;
19
+ /** 指标快照(白名单标量) */
20
+ metrics: Record<string, number | boolean | string>;
21
+ /** 证据 trace/memory id 列表 */
22
+ evidenceIds: string[];
23
+ /** 快照 id(实验时的 runtime 快照) */
24
+ baseSnapshotId?: string;
25
+ /** stale:baseSnapshotId 与当前 runtime 不匹配 → 禁晋级 */
26
+ stale?: boolean;
27
+ createdAt: number;
28
+ completedAt?: number;
29
+ }
30
+ export interface StoppingRuleConfig {
31
+ /** 最小样本量硬门槛;n < minSamples → 只能 inconclusive,禁晋级 */
32
+ minSamples: number;
33
+ /** 最大样本量(可选,达到后强制停止) */
34
+ maxSamples?: number;
35
+ /** 最小成功率阈值(可选,0-1;达到后可提前 promote 候选) */
36
+ minSuccessRate?: number;
37
+ /** 最大失败率阈值(可选,0-1;超过后可提前 reject 候选) */
38
+ maxFailureRate?: number;
39
+ /** 实验最大持续时间 ms(可选) */
40
+ maxDurationMs?: number;
41
+ }
42
+ /** 默认停止规则:minSamples=30(硬门槛) */
43
+ export declare const DEFAULT_STOPPING_RULE: StoppingRuleConfig;
44
+ /**
45
+ * 解析并校验停止规则 JSON 配置。
46
+ * 失败返回 null(fail-closed);未知字段忽略。
47
+ */
48
+ export declare function parseStoppingRule(raw: unknown): StoppingRuleConfig | null;
49
+ export interface AssignmentResult {
50
+ assignment: ExperimentAssignment;
51
+ reason?: string;
52
+ }
53
+ /**
54
+ * 根据可用侧分配三模式。
55
+ * - 有 candidate + baseline → candidate(对照)
56
+ * - 只有 baseline → baseline
57
+ * - 消融:需要 candidate + baseline + ablation 标识;缺任一侧 → inconclusive
58
+ */
59
+ export declare function assignExperimentMode(input: {
60
+ candidateId?: string;
61
+ baselineId?: string;
62
+ ablationId?: string;
63
+ }): AssignmentResult;
64
+ export interface StoppingDecision {
65
+ /** 是否应停止实验 */
66
+ shouldStop: boolean;
67
+ /** 推荐结果(仅在 n>=minSamples 且有足够信息时非 null) */
68
+ recommended: ExperimentOutcome | null;
69
+ /** 停止原因(公开原因,不泄漏沙箱内部) */
70
+ reason: string;
71
+ }
72
+ /**
73
+ * 基于样本评估停止规则。
74
+ *
75
+ * - n < minSamples → 只能产出 inconclusive(A3 硬门槛)
76
+ * - n >= minSamples 且有 maxFailureRate/minSuccessRate → 可提前停止
77
+ * - maxSamples / maxDurationMs 达到 → 停止
78
+ */
79
+ export declare function evaluateStoppingRule(config: StoppingRuleConfig, runs: readonly ExperimentRun[], now?: number): StoppingDecision;
80
+ export interface PromotionGateResult {
81
+ allowed: boolean;
82
+ /** 公开原因 */
83
+ reason: string;
84
+ }
85
+ /**
86
+ * 检查是否允许进入 PromotionRequest。
87
+ * A3:n < minSamples 禁晋级。
88
+ * A4:stale 禁晋级。
89
+ */
90
+ export declare function checkPromotionGate(config: StoppingRuleConfig, runs: readonly ExperimentRun[], currentSnapshotId?: string): PromotionGateResult;
91
+ /**
92
+ * 实验 Memory 路由:MIO EXPERIMENT 标志 → experiment-memory.jsonl。
93
+ * 服务端 memory-record 检测此标志并强制路由。
94
+ */
95
+ export declare function routeExperimentMemoryPath(baseMemoryPath: string, isExperiment: boolean): string;
96
+ /** MIO EXPERIMENT 环境标志检测 */
97
+ export declare function isExperimentMode(env?: Record<string, string | undefined>): boolean;