@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.
- package/dist/ConceptMixer.d.ts +41 -0
- package/dist/ConceptMixer.js +207 -0
- package/dist/CreativityPrompt.d.ts +24 -0
- package/dist/CreativityPrompt.js +206 -0
- package/dist/ExperimentPlanner.d.ts +14 -0
- package/dist/ExperimentPlanner.js +60 -0
- package/dist/ExperimentRunStore.d.ts +97 -0
- package/dist/ExperimentRunStore.js +160 -0
- package/dist/HypothesisGenerator.d.ts +60 -0
- package/dist/HypothesisGenerator.js +246 -0
- package/dist/IdeaGenerator.d.ts +51 -0
- package/dist/IdeaGenerator.js +145 -0
- package/dist/InsightSamplingExperiment.d.ts +82 -0
- package/dist/InsightSamplingExperiment.js +308 -0
- package/dist/LocalModelService.d.ts +38 -0
- package/dist/LocalModelService.js +162 -0
- package/dist/MechanismGraph.d.ts +46 -0
- package/dist/MechanismGraph.js +76 -0
- package/dist/MechanismRegistry.d.ts +118 -0
- package/dist/MechanismRegistry.js +233 -0
- package/dist/NoveltyScorer.d.ts +40 -0
- package/dist/NoveltyScorer.js +93 -0
- package/dist/SourceAggregator.d.ts +21 -0
- package/dist/SourceAggregator.js +59 -0
- package/dist/TemplateLibrary.d.ts +29 -0
- package/dist/TemplateLibrary.js +484 -0
- package/dist/WhenThenRunner.d.ts +135 -0
- package/dist/WhenThenRunner.js +358 -0
- package/dist/types.d.ts +213 -0
- package/dist/types.js +9 -0
- package/package.json +67 -4
- package/README.md +0 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveMechanismGraph = deriveMechanismGraph;
|
|
4
|
+
exports.relationsToEdges = relationsToEdges;
|
|
5
|
+
exports.isMechanismHypothesis = isMechanismHypothesis;
|
|
6
|
+
function asString(v, fallback = '') {
|
|
7
|
+
return typeof v === 'string' && v.length > 0 ? v : fallback;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 从 traces.jsonl 行事件聚合机制边。
|
|
11
|
+
*
|
|
12
|
+
* 规则:
|
|
13
|
+
* - 仅计入 payload.capabilityId 存在的事件(能力绑定相关)。
|
|
14
|
+
* - 每条边键 = `${agent}|${capabilityId}|${outcome}`,count 累加。
|
|
15
|
+
* - inference:payload.inference === 'identity' 或 event_type 含 identity
|
|
16
|
+
* 时记 identity;否则记 inferred。
|
|
17
|
+
* - confidence:边内出现过任一 identity → high,否则 low。
|
|
18
|
+
*/
|
|
19
|
+
function deriveMechanismGraph(events) {
|
|
20
|
+
const edgeMap = new Map();
|
|
21
|
+
let eventCount = 0;
|
|
22
|
+
for (const ev of events) {
|
|
23
|
+
const capabilityId = asString(ev.payload?.capabilityId);
|
|
24
|
+
if (!capabilityId)
|
|
25
|
+
continue;
|
|
26
|
+
const agent = asString(ev.agent, 'unknown');
|
|
27
|
+
const outcome = asString(ev.outcome, 'unknown');
|
|
28
|
+
eventCount++;
|
|
29
|
+
const key = `${agent}|${capabilityId}|${outcome}`;
|
|
30
|
+
let edge = edgeMap.get(key);
|
|
31
|
+
if (!edge) {
|
|
32
|
+
edge = {
|
|
33
|
+
agent,
|
|
34
|
+
capabilityId,
|
|
35
|
+
outcome,
|
|
36
|
+
count: 0,
|
|
37
|
+
confidence: 'low',
|
|
38
|
+
inferences: [],
|
|
39
|
+
};
|
|
40
|
+
edgeMap.set(key, edge);
|
|
41
|
+
}
|
|
42
|
+
edge.count++;
|
|
43
|
+
const rawInference = ev.payload?.inference;
|
|
44
|
+
const inference = rawInference === 'identity' || (typeof ev.event_type === 'string' && ev.event_type.includes('identity')) ? 'identity' : 'inferred';
|
|
45
|
+
if (!edge.inferences.includes(inference))
|
|
46
|
+
edge.inferences.push(inference);
|
|
47
|
+
if (inference === 'identity')
|
|
48
|
+
edge.confidence = 'high';
|
|
49
|
+
}
|
|
50
|
+
const edges = [...edgeMap.values()].sort((a, b) => {
|
|
51
|
+
const c = b.count - a.count;
|
|
52
|
+
if (c !== 0)
|
|
53
|
+
return c;
|
|
54
|
+
const k = `${a.agent}|${a.capabilityId}|${a.outcome}`.localeCompare(`${b.agent}|${b.capabilityId}|${b.outcome}`);
|
|
55
|
+
return k;
|
|
56
|
+
});
|
|
57
|
+
return { edges, eventCount };
|
|
58
|
+
}
|
|
59
|
+
/** 把机制假设的 proposedRelations 投影为与派生图同形状的边(供对照,confidence 走 inference 字段)。 */
|
|
60
|
+
function relationsToEdges(agent, relations) {
|
|
61
|
+
return relations.map((r) => {
|
|
62
|
+
const inference = r.inference ?? 'inferred';
|
|
63
|
+
return {
|
|
64
|
+
agent,
|
|
65
|
+
capabilityId: r.from,
|
|
66
|
+
outcome: r.to,
|
|
67
|
+
count: 1,
|
|
68
|
+
confidence: inference === 'identity' ? 'high' : 'low',
|
|
69
|
+
inferences: [inference],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** 是否为机制假设:带非空 proposedRelations 即机制假设,否则普通创意(一个 store 两种粒度)。 */
|
|
74
|
+
function isMechanismHypothesis(h) {
|
|
75
|
+
return Array.isArray(h.proposedRelations) && h.proposedRelations.length > 0;
|
|
76
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MechanismRegistry — 机制版本注册表 + PromotionRequest 薄实现(机制涌现 P4,定案 D5/A5)
|
|
3
|
+
*
|
|
4
|
+
* 生产轴:mechanisms.jsonl 存 MechanismVersion(mechanismId/version/status active|rolled_back/
|
|
5
|
+
* rollbackTo/approvedBy/submittedBy)。A5 代码级校验:Registry 写入时断言批准者≠提交者。
|
|
6
|
+
* PromotionRequest:提交前硬闸复用 checkPromotionGate(stale / n<minSamples 禁止提交),
|
|
7
|
+
* 批准走 pending → approved|rejected,批准时激活新版本并标旧版本 rolled_back。
|
|
8
|
+
* 回滚:rolled_back 路径——当前 active 标 rolled_back(rollbackTo=目标版本),目标版本重新激活。
|
|
9
|
+
*/
|
|
10
|
+
import { type StoppingRuleConfig, type ExperimentRun } from './ExperimentRunStore';
|
|
11
|
+
export type MechanismStatus = 'active' | 'rolled_back';
|
|
12
|
+
export interface MechanismVersion {
|
|
13
|
+
mechanismId: string;
|
|
14
|
+
/** 单调递增版本号,从 1 开始 */
|
|
15
|
+
version: number;
|
|
16
|
+
status: MechanismStatus;
|
|
17
|
+
/** 回滚目标版本(仅 status=rolled_back 时可设,指回滚后应激活的版本) */
|
|
18
|
+
rollbackTo?: number;
|
|
19
|
+
/** A5:批准者 agent id(≠submittedBy) */
|
|
20
|
+
approvedBy: string;
|
|
21
|
+
/** 提交者 agent id */
|
|
22
|
+
submittedBy: string;
|
|
23
|
+
hypothesisId?: string;
|
|
24
|
+
experimentRunIds?: string[];
|
|
25
|
+
createdAt: number;
|
|
26
|
+
}
|
|
27
|
+
export type ApprovalStatus = 'pending' | 'approved' | 'rejected';
|
|
28
|
+
export interface PromotionRequest {
|
|
29
|
+
id: string;
|
|
30
|
+
mechanismId: string;
|
|
31
|
+
version: number;
|
|
32
|
+
hypothesisId?: string;
|
|
33
|
+
experimentRunIds: string[];
|
|
34
|
+
submittedBy: string;
|
|
35
|
+
approvalStatus: ApprovalStatus;
|
|
36
|
+
approvedBy?: string;
|
|
37
|
+
/** 拒绝/批准的公开原因 */
|
|
38
|
+
reason?: string;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
decidedAt?: number;
|
|
41
|
+
}
|
|
42
|
+
/** A5:批准者必须与提交者不同,且双方非空。 */
|
|
43
|
+
export declare function canIndependentlyApprove(submittedBy: string, approvedBy: string): boolean;
|
|
44
|
+
export interface SubmitGateResult {
|
|
45
|
+
allowed: boolean;
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
/** 提交 PromotionRequest 前的硬闸检查。stale 或 n<minSamples 一律禁止提交。 */
|
|
49
|
+
export declare function checkSubmitGate(config: StoppingRuleConfig, runs: readonly ExperimentRun[], currentSnapshotId?: string): SubmitGateResult;
|
|
50
|
+
export interface SubmitPromotionInput {
|
|
51
|
+
mechanismId: string;
|
|
52
|
+
version: number;
|
|
53
|
+
hypothesisId?: string;
|
|
54
|
+
experimentRunIds?: string[];
|
|
55
|
+
submittedBy: string;
|
|
56
|
+
}
|
|
57
|
+
export type SubmitResult = {
|
|
58
|
+
ok: true;
|
|
59
|
+
request: PromotionRequest;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
reason: string;
|
|
63
|
+
};
|
|
64
|
+
export type DecideResult = {
|
|
65
|
+
ok: true;
|
|
66
|
+
request: PromotionRequest;
|
|
67
|
+
activated?: MechanismVersion;
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
reason: string;
|
|
71
|
+
};
|
|
72
|
+
export type RollbackResult = {
|
|
73
|
+
ok: true;
|
|
74
|
+
rolledBack: MechanismVersion;
|
|
75
|
+
restored: MechanismVersion;
|
|
76
|
+
} | {
|
|
77
|
+
ok: false;
|
|
78
|
+
reason: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* 薄 Registry:文件级 jsonl 存储(机制设计定稿 P4 首版,不必上数据库)。
|
|
82
|
+
* 状态机:
|
|
83
|
+
* - PromotionRequest: pending → approved | rejected(批准时激活版本)
|
|
84
|
+
* - MechanismVersion: active | rolled_back(rolled_back 可带 rollbackTo)
|
|
85
|
+
*/
|
|
86
|
+
export declare class MechanismRegistryStore {
|
|
87
|
+
private readonly versionsPath;
|
|
88
|
+
private readonly requestsPath?;
|
|
89
|
+
private versions;
|
|
90
|
+
private requests;
|
|
91
|
+
private requestSeq;
|
|
92
|
+
constructor(versionsPath: string, requestsPath?: string | undefined);
|
|
93
|
+
listVersions(mechanismId?: string): MechanismVersion[];
|
|
94
|
+
getActive(mechanismId: string): MechanismVersion | null;
|
|
95
|
+
getHistory(mechanismId: string): MechanismVersion[];
|
|
96
|
+
listRequests(status?: ApprovalStatus): PromotionRequest[];
|
|
97
|
+
getRequest(id: string): PromotionRequest | null;
|
|
98
|
+
/**
|
|
99
|
+
* 提交 PromotionRequest。
|
|
100
|
+
* 硬闸:stale / snapshot_mismatch / below_min_samples 一律拒绝(复用 checkSubmitGate)。
|
|
101
|
+
* A5 双人:提交时不做批准校验(批准时校验)。
|
|
102
|
+
*/
|
|
103
|
+
submit(input: SubmitPromotionInput, gate: SubmitGateResult): SubmitResult;
|
|
104
|
+
/**
|
|
105
|
+
* 批准 PromotionRequest。
|
|
106
|
+
* A5 代码级校验:批准者必须≠提交者(写进代码不是文档)。
|
|
107
|
+
* 批准成功:写入 MechanismVersion active,旧 active 标 rolled_back。
|
|
108
|
+
*/
|
|
109
|
+
approve(requestId: string, approvedBy: string, now?: number): DecideResult;
|
|
110
|
+
reject(requestId: string, approvedBy: string, reason: string, now?: number): DecideResult;
|
|
111
|
+
/**
|
|
112
|
+
* 回滚:当前 active 标 rolled_back(rollbackTo=目标版本),目标版本重新激活。
|
|
113
|
+
* 生产观察恶化触发重评估 → 调用本方法回到 rollbackTo 指定的历史版本。
|
|
114
|
+
*/
|
|
115
|
+
rollback(mechanismId: string, now?: number): RollbackResult;
|
|
116
|
+
private persistVersions;
|
|
117
|
+
private persistRequests;
|
|
118
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MechanismRegistry — 机制版本注册表 + PromotionRequest 薄实现(机制涌现 P4,定案 D5/A5)
|
|
4
|
+
*
|
|
5
|
+
* 生产轴:mechanisms.jsonl 存 MechanismVersion(mechanismId/version/status active|rolled_back/
|
|
6
|
+
* rollbackTo/approvedBy/submittedBy)。A5 代码级校验:Registry 写入时断言批准者≠提交者。
|
|
7
|
+
* PromotionRequest:提交前硬闸复用 checkPromotionGate(stale / n<minSamples 禁止提交),
|
|
8
|
+
* 批准走 pending → approved|rejected,批准时激活新版本并标旧版本 rolled_back。
|
|
9
|
+
* 回滚:rolled_back 路径——当前 active 标 rolled_back(rollbackTo=目标版本),目标版本重新激活。
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MechanismRegistryStore = void 0;
|
|
13
|
+
exports.canIndependentlyApprove = canIndependentlyApprove;
|
|
14
|
+
exports.checkSubmitGate = checkSubmitGate;
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_path_1 = require("node:path");
|
|
17
|
+
const ExperimentRunStore_1 = require("./ExperimentRunStore");
|
|
18
|
+
// ══════════════════════════════════════════
|
|
19
|
+
// A5 代码级校验(批准者≠提交者,写进代码不是文档)
|
|
20
|
+
// ══════════════════════════════════════════
|
|
21
|
+
/** A5:批准者必须与提交者不同,且双方非空。 */
|
|
22
|
+
function canIndependentlyApprove(submittedBy, approvedBy) {
|
|
23
|
+
if (!submittedBy || !approvedBy)
|
|
24
|
+
return false;
|
|
25
|
+
return submittedBy !== approvedBy;
|
|
26
|
+
}
|
|
27
|
+
/** 提交 PromotionRequest 前的硬闸检查。stale 或 n<minSamples 一律禁止提交。 */
|
|
28
|
+
function checkSubmitGate(config, runs, currentSnapshotId) {
|
|
29
|
+
const gate = (0, ExperimentRunStore_1.checkPromotionGate)(config, runs, currentSnapshotId);
|
|
30
|
+
if (!gate.allowed) {
|
|
31
|
+
return { allowed: false, reason: gate.reason };
|
|
32
|
+
}
|
|
33
|
+
return { allowed: true, reason: 'ok' };
|
|
34
|
+
}
|
|
35
|
+
// ══════════════════════════════════════════
|
|
36
|
+
// 文件持久化(jsonl,append-only 语义,整文件重写)
|
|
37
|
+
// ══════════════════════════════════════════
|
|
38
|
+
function readJsonl(path) {
|
|
39
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
40
|
+
return [];
|
|
41
|
+
const raw = (0, node_fs_1.readFileSync)(path, 'utf8').trim();
|
|
42
|
+
if (!raw)
|
|
43
|
+
return [];
|
|
44
|
+
return raw
|
|
45
|
+
.split('\n')
|
|
46
|
+
.map((line) => line.trim())
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.map((line) => JSON.parse(line));
|
|
49
|
+
}
|
|
50
|
+
function writeJsonl(path, entries) {
|
|
51
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
52
|
+
const body = entries.map((e) => JSON.stringify(e)).join('\n');
|
|
53
|
+
(0, node_fs_1.writeFileSync)(path, body ? `${body}\n` : '', 'utf8');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 薄 Registry:文件级 jsonl 存储(机制设计定稿 P4 首版,不必上数据库)。
|
|
57
|
+
* 状态机:
|
|
58
|
+
* - PromotionRequest: pending → approved | rejected(批准时激活版本)
|
|
59
|
+
* - MechanismVersion: active | rolled_back(rolled_back 可带 rollbackTo)
|
|
60
|
+
*/
|
|
61
|
+
class MechanismRegistryStore {
|
|
62
|
+
versionsPath;
|
|
63
|
+
requestsPath;
|
|
64
|
+
versions = [];
|
|
65
|
+
requests = [];
|
|
66
|
+
requestSeq = 0;
|
|
67
|
+
constructor(versionsPath, requestsPath) {
|
|
68
|
+
this.versionsPath = versionsPath;
|
|
69
|
+
this.requestsPath = requestsPath;
|
|
70
|
+
this.versions = readJsonl(versionsPath);
|
|
71
|
+
const reqPath = requestsPath ?? versionsPath.replace(/mechanisms\.jsonl$/, 'promotion-requests.jsonl');
|
|
72
|
+
this.requests = readJsonl(reqPath);
|
|
73
|
+
// 恢复 requestSeq(幂等续号)
|
|
74
|
+
for (const r of this.requests) {
|
|
75
|
+
const m = /^pr-(\d+)$/.exec(r.id);
|
|
76
|
+
if (m)
|
|
77
|
+
this.requestSeq = Math.max(this.requestSeq, Number(m[1]));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// ── 查询 ──
|
|
81
|
+
listVersions(mechanismId) {
|
|
82
|
+
if (!mechanismId)
|
|
83
|
+
return [...this.versions];
|
|
84
|
+
return this.versions.filter((v) => v.mechanismId === mechanismId);
|
|
85
|
+
}
|
|
86
|
+
getActive(mechanismId) {
|
|
87
|
+
const actives = this.versions.filter((v) => v.mechanismId === mechanismId && v.status === 'active');
|
|
88
|
+
// 防御:多 active 取最高版本
|
|
89
|
+
if (actives.length === 0)
|
|
90
|
+
return null;
|
|
91
|
+
return actives.reduce((a, b) => (b.version > a.version ? b : a));
|
|
92
|
+
}
|
|
93
|
+
getHistory(mechanismId) {
|
|
94
|
+
return this.versions.filter((v) => v.mechanismId === mechanismId).sort((a, b) => a.version - b.version);
|
|
95
|
+
}
|
|
96
|
+
listRequests(status) {
|
|
97
|
+
if (!status)
|
|
98
|
+
return [...this.requests];
|
|
99
|
+
return this.requests.filter((r) => r.approvalStatus === status);
|
|
100
|
+
}
|
|
101
|
+
getRequest(id) {
|
|
102
|
+
return this.requests.find((r) => r.id === id) ?? null;
|
|
103
|
+
}
|
|
104
|
+
// ── 提交(Pending) ──
|
|
105
|
+
/**
|
|
106
|
+
* 提交 PromotionRequest。
|
|
107
|
+
* 硬闸:stale / snapshot_mismatch / below_min_samples 一律拒绝(复用 checkSubmitGate)。
|
|
108
|
+
* A5 双人:提交时不做批准校验(批准时校验)。
|
|
109
|
+
*/
|
|
110
|
+
submit(input, gate) {
|
|
111
|
+
if (!gate.allowed) {
|
|
112
|
+
return { ok: false, reason: gate.reason };
|
|
113
|
+
}
|
|
114
|
+
if (!input.mechanismId || !input.submittedBy) {
|
|
115
|
+
return { ok: false, reason: 'invalid_input' };
|
|
116
|
+
}
|
|
117
|
+
if (!Number.isInteger(input.version) || input.version < 1) {
|
|
118
|
+
return { ok: false, reason: 'invalid_version' };
|
|
119
|
+
}
|
|
120
|
+
// 拒绝重复 pending(同 mechanismId+version)
|
|
121
|
+
const dup = this.requests.find((r) => r.mechanismId === input.mechanismId && r.version === input.version && r.approvalStatus === 'pending');
|
|
122
|
+
if (dup) {
|
|
123
|
+
return { ok: false, reason: 'duplicate_pending' };
|
|
124
|
+
}
|
|
125
|
+
this.requestSeq += 1;
|
|
126
|
+
const request = {
|
|
127
|
+
id: `pr-${this.requestSeq}`,
|
|
128
|
+
mechanismId: input.mechanismId,
|
|
129
|
+
version: input.version,
|
|
130
|
+
hypothesisId: input.hypothesisId,
|
|
131
|
+
experimentRunIds: input.experimentRunIds ?? [],
|
|
132
|
+
submittedBy: input.submittedBy,
|
|
133
|
+
approvalStatus: 'pending',
|
|
134
|
+
createdAt: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
this.requests.push(request);
|
|
137
|
+
this.persistRequests();
|
|
138
|
+
return { ok: true, request };
|
|
139
|
+
}
|
|
140
|
+
// ── 批准(A5 双人校验 + 激活版本) ──
|
|
141
|
+
/**
|
|
142
|
+
* 批准 PromotionRequest。
|
|
143
|
+
* A5 代码级校验:批准者必须≠提交者(写进代码不是文档)。
|
|
144
|
+
* 批准成功:写入 MechanismVersion active,旧 active 标 rolled_back。
|
|
145
|
+
*/
|
|
146
|
+
approve(requestId, approvedBy, now = Date.now()) {
|
|
147
|
+
const req = this.requests.find((r) => r.id === requestId);
|
|
148
|
+
if (!req)
|
|
149
|
+
return { ok: false, reason: 'request_not_found' };
|
|
150
|
+
if (req.approvalStatus !== 'pending') {
|
|
151
|
+
return { ok: false, reason: 'not_pending' };
|
|
152
|
+
}
|
|
153
|
+
// A5:批准者≠提交者(代码级)
|
|
154
|
+
if (!canIndependentlyApprove(req.submittedBy, approvedBy)) {
|
|
155
|
+
return { ok: false, reason: 'self_approval_forbidden' };
|
|
156
|
+
}
|
|
157
|
+
// 标记旧 active → rolled_back(回滚目标=本版本)
|
|
158
|
+
const previous = this.getActive(req.mechanismId);
|
|
159
|
+
if (previous) {
|
|
160
|
+
previous.status = 'rolled_back';
|
|
161
|
+
previous.rollbackTo = req.version;
|
|
162
|
+
}
|
|
163
|
+
// 写入新 active 版本
|
|
164
|
+
const version = {
|
|
165
|
+
mechanismId: req.mechanismId,
|
|
166
|
+
version: req.version,
|
|
167
|
+
status: 'active',
|
|
168
|
+
approvedBy,
|
|
169
|
+
submittedBy: req.submittedBy,
|
|
170
|
+
hypothesisId: req.hypothesisId,
|
|
171
|
+
experimentRunIds: req.experimentRunIds,
|
|
172
|
+
createdAt: now,
|
|
173
|
+
};
|
|
174
|
+
this.versions.push(version);
|
|
175
|
+
req.approvalStatus = 'approved';
|
|
176
|
+
req.approvedBy = approvedBy;
|
|
177
|
+
req.decidedAt = now;
|
|
178
|
+
this.persistVersions();
|
|
179
|
+
this.persistRequests();
|
|
180
|
+
return { ok: true, request: req, activated: version };
|
|
181
|
+
}
|
|
182
|
+
// ── 拒绝 ──
|
|
183
|
+
reject(requestId, approvedBy, reason, now = Date.now()) {
|
|
184
|
+
const req = this.requests.find((r) => r.id === requestId);
|
|
185
|
+
if (!req)
|
|
186
|
+
return { ok: false, reason: 'request_not_found' };
|
|
187
|
+
if (req.approvalStatus !== 'pending') {
|
|
188
|
+
return { ok: false, reason: 'not_pending' };
|
|
189
|
+
}
|
|
190
|
+
req.approvalStatus = 'rejected';
|
|
191
|
+
req.approvedBy = approvedBy;
|
|
192
|
+
req.reason = reason;
|
|
193
|
+
req.decidedAt = now;
|
|
194
|
+
this.persistRequests();
|
|
195
|
+
return { ok: true, request: req };
|
|
196
|
+
}
|
|
197
|
+
// ── 回滚 ──
|
|
198
|
+
/**
|
|
199
|
+
* 回滚:当前 active 标 rolled_back(rollbackTo=目标版本),目标版本重新激活。
|
|
200
|
+
* 生产观察恶化触发重评估 → 调用本方法回到 rollbackTo 指定的历史版本。
|
|
201
|
+
*/
|
|
202
|
+
rollback(mechanismId, now = Date.now()) {
|
|
203
|
+
const current = this.getActive(mechanismId);
|
|
204
|
+
if (!current) {
|
|
205
|
+
return { ok: false, reason: 'no_active_version' };
|
|
206
|
+
}
|
|
207
|
+
const history = this.getHistory(mechanismId);
|
|
208
|
+
// 找目标:当前版本之前最高的历史版本
|
|
209
|
+
const target = history
|
|
210
|
+
.filter((v) => v.version < current.version)
|
|
211
|
+
.reduce((acc, v) => (!acc || v.version > acc.version ? v : acc), null);
|
|
212
|
+
if (!target) {
|
|
213
|
+
return { ok: false, reason: 'no_rollback_target' };
|
|
214
|
+
}
|
|
215
|
+
// 当前 → rolled_back
|
|
216
|
+
current.status = 'rolled_back';
|
|
217
|
+
current.rollbackTo = target.version;
|
|
218
|
+
// 目标 → 重新激活(更新 status;不动 createdAt 保留审计)
|
|
219
|
+
target.status = 'active';
|
|
220
|
+
target.approvedBy = current.approvedBy; // 沿用原批准者(审计不改)
|
|
221
|
+
this.persistVersions();
|
|
222
|
+
return { ok: true, rolledBack: current, restored: target };
|
|
223
|
+
}
|
|
224
|
+
// ── 持久化 ──
|
|
225
|
+
persistVersions() {
|
|
226
|
+
writeJsonl(this.versionsPath, this.versions);
|
|
227
|
+
}
|
|
228
|
+
persistRequests() {
|
|
229
|
+
const reqPath = this.requestsPath ?? this.versionsPath.replace(/mechanisms\.jsonl$/, 'promotion-requests.jsonl');
|
|
230
|
+
writeJsonl(reqPath, this.requests);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.MechanismRegistryStore = MechanismRegistryStore;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NoveltyScorer — 代码层新颖度评估
|
|
3
|
+
*
|
|
4
|
+
* 在 LLM 自评 novelty 之外,通过文本相似度对输入假设进行再评估:
|
|
5
|
+
* 1. 与近期假设(past 30)的 idea 文本对比,Jaccard 相似度 > 0.5 → 扣 15 分
|
|
6
|
+
* 2. 与已拒绝假设的 idea 文本对比,Jaccard 相似度 > 0.6 → 标记为 rejected
|
|
7
|
+
*/
|
|
8
|
+
/** Jaccard 相似度:|A ∩ B| / |A ∪ B| */
|
|
9
|
+
export declare function jaccardSimilarity(a: string, b: string): number;
|
|
10
|
+
export interface NoveltyVerdict {
|
|
11
|
+
/** 调整后的新颖度(0-100),可能被扣分 */
|
|
12
|
+
adjustedNovelty: number;
|
|
13
|
+
/** 是否应被自动拒绝(与已拒绝假设高度相似) */
|
|
14
|
+
shouldReject: boolean;
|
|
15
|
+
/** 为什么拒绝 */
|
|
16
|
+
rejectReason?: string;
|
|
17
|
+
/** 与最相似假设的信息 */
|
|
18
|
+
mostSimilarTitle?: string;
|
|
19
|
+
mostSimilarScore: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 对一条新假设进行代码层新颖度评估
|
|
23
|
+
*
|
|
24
|
+
* @param candidate 新假设
|
|
25
|
+
* @param recentHypotheses 近期假设(用于扣分)
|
|
26
|
+
* @param rejectedHypotheses 已拒绝假设(用于自动拒绝)
|
|
27
|
+
* @returns NoveltyVerdict
|
|
28
|
+
*/
|
|
29
|
+
export declare function evaluateNovelty(candidate: {
|
|
30
|
+
title: string;
|
|
31
|
+
idea: string;
|
|
32
|
+
novelty: number;
|
|
33
|
+
}, recentHypotheses: {
|
|
34
|
+
title: string;
|
|
35
|
+
idea: string;
|
|
36
|
+
novelty: number;
|
|
37
|
+
}[], rejectedHypotheses: {
|
|
38
|
+
title: string;
|
|
39
|
+
idea: string;
|
|
40
|
+
}[]): NoveltyVerdict;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NoveltyScorer — 代码层新颖度评估
|
|
4
|
+
*
|
|
5
|
+
* 在 LLM 自评 novelty 之外,通过文本相似度对输入假设进行再评估:
|
|
6
|
+
* 1. 与近期假设(past 30)的 idea 文本对比,Jaccard 相似度 > 0.5 → 扣 15 分
|
|
7
|
+
* 2. 与已拒绝假设的 idea 文本对比,Jaccard 相似度 > 0.6 → 标记为 rejected
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.jaccardSimilarity = jaccardSimilarity;
|
|
11
|
+
exports.evaluateNovelty = evaluateNovelty;
|
|
12
|
+
// ==================== Tokenizer ====================
|
|
13
|
+
/**
|
|
14
|
+
* 简易 tokenizer:将文本拆分为 token 集合
|
|
15
|
+
* - 英文词按空格/标点分割、转小写
|
|
16
|
+
* - 中文按二元组(bigram)拆分(中文分词需要词典,这里用 bigram 近似)
|
|
17
|
+
* - 去重、去空
|
|
18
|
+
*/
|
|
19
|
+
function tokenize(text) {
|
|
20
|
+
const tokens = new Set();
|
|
21
|
+
// 英文/数字 token
|
|
22
|
+
const enTokens = text
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.split(/[^a-z0-9一-鿿]+/g)
|
|
25
|
+
.filter((t) => t.length > 1 && /[a-z0-9]/.test(t));
|
|
26
|
+
for (const t of enTokens)
|
|
27
|
+
tokens.add(t);
|
|
28
|
+
// 中文 bigram
|
|
29
|
+
const chChars = text.replace(/[^一-鿿]/g, '');
|
|
30
|
+
for (let i = 0; i < chChars.length - 1; i++) {
|
|
31
|
+
tokens.add(chChars.slice(i, i + 2));
|
|
32
|
+
}
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
// ==================== Similarity ====================
|
|
36
|
+
/** Jaccard 相似度:|A ∩ B| / |A ∪ B| */
|
|
37
|
+
function jaccardSimilarity(a, b) {
|
|
38
|
+
const setA = tokenize(a);
|
|
39
|
+
const setB = tokenize(b);
|
|
40
|
+
const union = new Set([...setA, ...setB]);
|
|
41
|
+
if (union.size === 0)
|
|
42
|
+
return 0;
|
|
43
|
+
let intersection = 0;
|
|
44
|
+
for (const t of setA) {
|
|
45
|
+
if (setB.has(t))
|
|
46
|
+
intersection++;
|
|
47
|
+
}
|
|
48
|
+
return intersection / union.size;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 对一条新假设进行代码层新颖度评估
|
|
52
|
+
*
|
|
53
|
+
* @param candidate 新假设
|
|
54
|
+
* @param recentHypotheses 近期假设(用于扣分)
|
|
55
|
+
* @param rejectedHypotheses 已拒绝假设(用于自动拒绝)
|
|
56
|
+
* @returns NoveltyVerdict
|
|
57
|
+
*/
|
|
58
|
+
function evaluateNovelty(candidate, recentHypotheses, rejectedHypotheses) {
|
|
59
|
+
const candidateText = `${candidate.title} ${candidate.idea}`;
|
|
60
|
+
let adjustedNovelty = candidate.novelty;
|
|
61
|
+
let mostSimilarTitle = '';
|
|
62
|
+
let mostSimilarScore = 0;
|
|
63
|
+
// 1. 与近期假设对比
|
|
64
|
+
for (const h of recentHypotheses) {
|
|
65
|
+
const sim = jaccardSimilarity(candidateText, `${h.title} ${h.idea}`);
|
|
66
|
+
if (sim > mostSimilarScore) {
|
|
67
|
+
mostSimilarScore = sim;
|
|
68
|
+
mostSimilarTitle = h.title;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (mostSimilarScore > 0.5) {
|
|
72
|
+
adjustedNovelty = Math.max(0, adjustedNovelty - 15);
|
|
73
|
+
}
|
|
74
|
+
// 2. 与已拒绝假设对比 — 高度相似则自动拒绝
|
|
75
|
+
for (const h of rejectedHypotheses) {
|
|
76
|
+
const sim = jaccardSimilarity(candidateText, `${h.title} ${h.idea}`);
|
|
77
|
+
if (sim > 0.6) {
|
|
78
|
+
return {
|
|
79
|
+
adjustedNovelty,
|
|
80
|
+
shouldReject: true,
|
|
81
|
+
rejectReason: `与已拒绝假设"${h.title}"内容高度相似 (Jaccard=${sim.toFixed(2)})`,
|
|
82
|
+
mostSimilarTitle: h.title,
|
|
83
|
+
mostSimilarScore: sim,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
adjustedNovelty,
|
|
89
|
+
shouldReject: false,
|
|
90
|
+
mostSimilarTitle,
|
|
91
|
+
mostSimilarScore,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CreativitySource } from './types';
|
|
2
|
+
export type SourceDomain = 'module' | 'behavior' | 'observation' | 'external' | 'failure' | 'feedback';
|
|
3
|
+
export interface SourceProvider {
|
|
4
|
+
domain: SourceDomain;
|
|
5
|
+
name: string;
|
|
6
|
+
collect: () => CreativitySource[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* SourceAggregator — 来源总线
|
|
10
|
+
*
|
|
11
|
+
* 统一收集多域来源,做去重、每域限流与失败降级,
|
|
12
|
+
* 供创意产生与发酵两端共用同一份信号。
|
|
13
|
+
*/
|
|
14
|
+
export declare class SourceAggregator {
|
|
15
|
+
private providers;
|
|
16
|
+
private maxPerDomain;
|
|
17
|
+
private minDomains;
|
|
18
|
+
constructor(maxPerDomain?: number, minDomains?: number);
|
|
19
|
+
addProvider(provider: SourceProvider): void;
|
|
20
|
+
build(): CreativitySource[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SourceAggregator = void 0;
|
|
4
|
+
const Logger_1 = require("@akemi-mio/core/logger/Logger");
|
|
5
|
+
/**
|
|
6
|
+
* SourceAggregator — 来源总线
|
|
7
|
+
*
|
|
8
|
+
* 统一收集多域来源,做去重、每域限流与失败降级,
|
|
9
|
+
* 供创意产生与发酵两端共用同一份信号。
|
|
10
|
+
*/
|
|
11
|
+
class SourceAggregator {
|
|
12
|
+
providers = [];
|
|
13
|
+
maxPerDomain;
|
|
14
|
+
minDomains;
|
|
15
|
+
constructor(maxPerDomain = 4, minDomains = 3) {
|
|
16
|
+
this.maxPerDomain = maxPerDomain;
|
|
17
|
+
this.minDomains = minDomains;
|
|
18
|
+
}
|
|
19
|
+
addProvider(provider) {
|
|
20
|
+
this.providers.push(provider);
|
|
21
|
+
}
|
|
22
|
+
build() {
|
|
23
|
+
const byDomain = new Map();
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const provider of this.providers) {
|
|
26
|
+
let collected = [];
|
|
27
|
+
try {
|
|
28
|
+
collected = provider.collect() ?? [];
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
(0, Logger_1.log)('WARN', 'source_provider_failed', { provider: provider.name, error: String(err?.message ?? err) });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const unique = collected.filter((s) => {
|
|
35
|
+
const key = `${provider.domain}:${s.name}`;
|
|
36
|
+
if (seen.has(key))
|
|
37
|
+
return false;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
const list = byDomain.get(provider.domain) ?? [];
|
|
42
|
+
byDomain.set(provider.domain, [...list, ...unique]);
|
|
43
|
+
}
|
|
44
|
+
const output = [];
|
|
45
|
+
let domainsWithData = 0;
|
|
46
|
+
for (const [domain, sources] of byDomain) {
|
|
47
|
+
if (sources.length > 0)
|
|
48
|
+
domainsWithData++;
|
|
49
|
+
const sorted = [...sources].sort((a, b) => b.weight - a.weight);
|
|
50
|
+
output.push(...sorted.slice(0, this.maxPerDomain));
|
|
51
|
+
}
|
|
52
|
+
if (domainsWithData < this.minDomains) {
|
|
53
|
+
(0, Logger_1.log)('WARN', 'source_aggregator_low_diversity', { domains: domainsWithData, min: this.minDomains });
|
|
54
|
+
}
|
|
55
|
+
(0, Logger_1.log)('INFO', 'source_aggregator_build', { total: output.length, domains: domainsWithData });
|
|
56
|
+
return output;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.SourceAggregator = SourceAggregator;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CreativitySource, ConceptCombo, Hypothesis } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* TemplateLibrary — 本地假设生成引擎
|
|
4
|
+
*
|
|
5
|
+
* 使用方式:
|
|
6
|
+
* 1. selectBest(combo, sources) 根据来源类型选择 2-3 个最佳模板
|
|
7
|
+
* 2. fill(template, sourceA, sourceB) 填充模板内容
|
|
8
|
+
* 3. 直接 generate(combo, sources) 一站式生成 Hypothesis
|
|
9
|
+
*/
|
|
10
|
+
export declare class TemplateLibrary {
|
|
11
|
+
private rng;
|
|
12
|
+
constructor(seed?: number);
|
|
13
|
+
/**
|
|
14
|
+
* 为一个 ConceptCombo 生成一条假设
|
|
15
|
+
* @returns 生成的 Hypothesis,如果无适用模板则返回 null
|
|
16
|
+
*/
|
|
17
|
+
generate(combo: ConceptCombo, sources: CreativitySource[]): Hypothesis | null;
|
|
18
|
+
/**
|
|
19
|
+
* 返回适用于某个类型组合的所有模板列表
|
|
20
|
+
* @param nameKey 对 source name 排序后的组合键,用于 nameFit 二次路由
|
|
21
|
+
*/
|
|
22
|
+
private selectForType;
|
|
23
|
+
private typeKey;
|
|
24
|
+
/** 对 source name 排序生成 nameFit 查询键 */
|
|
25
|
+
private nameKey;
|
|
26
|
+
private stableHash;
|
|
27
|
+
/** 评分的小幅随机扰动(0-15),保持一定多样性 */
|
|
28
|
+
private scoreNoise;
|
|
29
|
+
}
|