oh-my-knowledge 0.37.0 → 0.38.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.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/assets/agent-skills/omk/SKILL.md +1 -1
- package/dist/assets/agent-skills/omk/references/commands.md +21 -12
- package/dist/authoring/evolver.d.ts +4 -1
- package/dist/authoring/evolver.js +13 -8
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +26 -6
- package/dist/cli/commands/eval/gold/compare.js +7 -4
- package/dist/cli/commands/eval/index.d.ts +1 -0
- package/dist/cli/commands/eval/index.js +14 -3
- package/dist/cli/commands/evolve.d.ts +1 -0
- package/dist/cli/commands/evolve.js +45 -0
- package/dist/cli/commands/init.js +18 -6
- package/dist/cli/commands/list.d.ts +0 -2
- package/dist/cli/commands/list.js +2 -7
- package/dist/cli/commands/observe/inbox.d.ts +1 -0
- package/dist/cli/commands/observe/inbox.js +14 -4
- package/dist/cli/commands/observe/index.d.ts +11 -0
- package/dist/cli/commands/observe/index.js +35 -12
- package/dist/cli/commands/observe/ingest.d.ts +1 -0
- package/dist/cli/commands/observe/ingest.js +13 -4
- package/dist/cli/commands/observe/show.d.ts +1 -0
- package/dist/cli/commands/observe/show.js +12 -2
- package/dist/cli/commands/promote.js +1 -2
- package/dist/cli/commands/sample.js +15 -9
- package/dist/cli/commands/studio.d.ts +2 -0
- package/dist/cli/commands/studio.js +62 -12
- package/dist/cli/lib/cmd-flags.d.ts +8 -0
- package/dist/cli/lib/i18n-dict/common.js +1 -1
- package/dist/cli/lib/i18n-dict/evolve.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/evolve.js +8 -0
- package/dist/cli/lib/i18n-dict/help.js +27 -45
- package/dist/cli/lib/i18n-dict/init.js +2 -2
- package/dist/cli/lib/parse-run-config.d.ts +0 -1
- package/dist/cli/lib/parse-run-config.js +6 -4
- package/dist/cli/lib/record-evolve-outcome.d.ts +31 -0
- package/dist/cli/lib/record-evolve-outcome.js +110 -0
- package/dist/cli/lib/resolve-skill-input.d.ts +6 -0
- package/dist/cli/lib/resolve-skill-input.js +4 -2
- package/dist/diagnosis/types.js +1 -1
- package/dist/doctor/index.js +4 -1
- package/dist/eval-core/artifact-index.d.ts +106 -0
- package/dist/eval-core/artifact-index.js +277 -0
- package/dist/eval-core/default-dirs.d.ts +34 -0
- package/dist/eval-core/default-dirs.js +36 -0
- package/dist/eval-core/evaluation-execution.js +3 -3
- package/dist/eval-core/evaluation-reporting.d.ts +7 -0
- package/dist/eval-core/evaluation-reporting.js +20 -5
- package/dist/eval-core/execution-strategy.js +3 -3
- package/dist/eval-core/measurement-dirs.d.ts +18 -0
- package/dist/eval-core/measurement-dirs.js +76 -0
- package/dist/eval-workflows/batch-evaluation-workflow.js +2 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +2 -1
- package/dist/eval-workflows/run-evaluation.js +5 -2
- package/dist/executors/codex-cli.js +2 -2
- package/dist/executors/script.js +43 -4
- package/dist/inputs/materialize-copy.d.ts +1 -1
- package/dist/inputs/materialize-copy.js +3 -3
- package/dist/inputs/skill-loader.d.ts +1 -1
- package/dist/inputs/skill-loader.js +1 -1
- package/dist/managed/index.d.ts +2 -0
- package/dist/managed/index.js +2 -0
- package/dist/managed/list-query.d.ts +7 -0
- package/dist/managed/list-query.js +11 -0
- package/dist/managed/source-probe.d.ts +15 -0
- package/dist/managed/source-probe.js +129 -0
- package/dist/managed/store.d.ts +10 -0
- package/dist/managed/store.js +25 -2
- package/dist/observability/inbox.js +6 -3
- package/dist/observability/skill-health-analyzer.d.ts +3 -0
- package/dist/observability/skill-health-analyzer.js +2 -0
- package/dist/renderer/html-renderer.js +4 -2
- package/dist/renderer/managed-history-renderer.d.ts +5 -0
- package/dist/renderer/managed-history-renderer.js +255 -0
- package/dist/renderer/observation-inbox-renderer.js +19 -19
- package/dist/renderer/skill-detail-renderer.js +17 -8
- package/dist/renderer/skill-list-renderer.js +1 -0
- package/dist/server/indexed-report-store.d.ts +8 -0
- package/dist/server/indexed-report-store.js +130 -0
- package/dist/server/job-store.d.ts +0 -1
- package/dist/server/job-store.js +0 -2
- package/dist/server/report-server.d.ts +17 -3
- package/dist/server/report-server.js +231 -85
- package/dist/server/report-store.d.ts +14 -8
- package/dist/server/report-store.js +46 -0
- package/dist/server/skill-index.d.ts +7 -1
- package/dist/server/skill-index.js +48 -12
- package/dist/types/executor.d.ts +4 -2
- package/dist/types/report.d.ts +15 -0
- package/dist/types/skill-index.d.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产物发现索引(report / doctor / observe-health 三域)。
|
|
3
|
+
*
|
|
4
|
+
* 每类测量产物的正文(含逐样本 / 逐规则 / 逐信号重体)永远单份留它的项目本地 `.omk/<域>/`;产物落盘后,
|
|
5
|
+
* 在全局 `state/artifact-index/<域>/<id>.json` 留一张轻卡片(元数据 + 必要标量,剥掉重体,`path` 指向真身),
|
|
6
|
+
* 让 `omk studio` 跨项目聚合成「机器级总览」—— 当前项目 + 全局靠 live-scan 永远新鲜,别的项目靠卡片发现。
|
|
7
|
+
*
|
|
8
|
+
* 索引是可重生 scratch:写失败永不阻断正文落盘(正文是 source of truth);丢了由 live-scan / 重跑重建。
|
|
9
|
+
* 三域共用 `artifactIndexDir(domain)` + tmp-rename 原子写 + 指纹缓存读 的同一套机制,各域只差「投影成卡片」
|
|
10
|
+
* 与「卡片还原成 snapshot」两段域特定逻辑。
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, statSync } from 'node:fs';
|
|
13
|
+
import { join, resolve } from 'node:path';
|
|
14
|
+
import { DEFAULT_ARTIFACT_INDEX_DIR } from './default-dirs.js';
|
|
15
|
+
import { globalReportsDir, globalDoctorsDir, globalObserveHealthDir } from './measurement-dirs.js';
|
|
16
|
+
// ── 通用机制(域无关)──────────────────────────────────────────────────────────
|
|
17
|
+
/** 索引根:`OMK_ARTIFACT_INDEX_DIR` 覆盖(测试隔离,仿 OMK_TREES_DIR),默认 state/artifact-index。 */
|
|
18
|
+
function artifactIndexRoot() {
|
|
19
|
+
return process.env.OMK_ARTIFACT_INDEX_DIR || DEFAULT_ARTIFACT_INDEX_DIR;
|
|
20
|
+
}
|
|
21
|
+
/** 某域的索引目录 `<root>/<domain>/`。 */
|
|
22
|
+
export function artifactIndexDir(domain) {
|
|
23
|
+
return join(artifactIndexRoot(), domain);
|
|
24
|
+
}
|
|
25
|
+
/** 只索引「非全局目录」的写:全局是单一物理目录、任何 studio 都 live-scan 它 → 全局写卡片冗余;
|
|
26
|
+
* 卡片唯一价值是别项目的项目级 `.omk/<域>`。 */
|
|
27
|
+
function shouldIndexDir(outputDir, globalDir) {
|
|
28
|
+
return resolve(outputDir) !== resolve(globalDir);
|
|
29
|
+
}
|
|
30
|
+
/** report 域口径(保持向后兼容的既有导出)。 */
|
|
31
|
+
export function shouldIndexReport(outputDir) {
|
|
32
|
+
return shouldIndexDir(outputDir, globalReportsDir());
|
|
33
|
+
}
|
|
34
|
+
function safeFileName(id) {
|
|
35
|
+
return id.replaceAll(/[/\\:*?"<>|]/g, '_');
|
|
36
|
+
}
|
|
37
|
+
// 卡片读侧小 guard:索引是可重生 scratch,坏卡片(脏文件 / 别域误落 / 字段缺失)读侧从严跳过,
|
|
38
|
+
// 不让 undefined / 非法枚举 / NaN 当可信输入污染 studio。
|
|
39
|
+
const isFiniteNumber = (v) => typeof v === 'number' && Number.isFinite(v);
|
|
40
|
+
const isDoctorStatus = (v) => v === 'pass' || v === 'warn' || v === 'fail';
|
|
41
|
+
const isHealthBand = (v) => v === 'green' || v === 'yellow' || v === 'red';
|
|
42
|
+
const isConfidence = (v) => v === undefined || v === 'high' || v === 'low' || v === 'underpowered';
|
|
43
|
+
/** 原子写一张卡片(tmp+rename,防半截 JSON 被 reader 读到)。 */
|
|
44
|
+
function writeCard(domain, id, card) {
|
|
45
|
+
const indexDir = artifactIndexDir(domain);
|
|
46
|
+
mkdirSync(indexDir, { recursive: true });
|
|
47
|
+
const target = join(indexDir, `${safeFileName(id)}.json`);
|
|
48
|
+
const tmp = `${target}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
49
|
+
writeFileSync(tmp, JSON.stringify(card, null, 2));
|
|
50
|
+
renameSync(tmp, target);
|
|
51
|
+
}
|
|
52
|
+
/** 读某域全部卡片,逐条用 `valid` 谓词过滤坏文件 / 缺字段(索引是 scratch,读侧从严)。 */
|
|
53
|
+
function readArtifactCards(domain, valid) {
|
|
54
|
+
const dir = artifactIndexDir(domain);
|
|
55
|
+
if (!existsSync(dir))
|
|
56
|
+
return [];
|
|
57
|
+
let files;
|
|
58
|
+
try {
|
|
59
|
+
files = readdirSync(dir);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const f of files) {
|
|
66
|
+
if (!f.endsWith('.json') || f.includes('.json.tmp.'))
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
const c = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
70
|
+
if (valid(c))
|
|
71
|
+
out.push(c);
|
|
72
|
+
}
|
|
73
|
+
catch { /* skip corrupt card */ }
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** 删某域某 id 的卡片。幂等、best-effort。返回卡片是否曾存在。 */
|
|
78
|
+
function removeArtifactCard(domain, id) {
|
|
79
|
+
try {
|
|
80
|
+
const p = join(artifactIndexDir(domain), `${safeFileName(id)}.json`);
|
|
81
|
+
if (!existsSync(p))
|
|
82
|
+
return false;
|
|
83
|
+
unlinkSync(p);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 过滤悬空卡片:真身文件(`card.path`)已不在(项目被整目录删 / 移走等带外途径)→ 卡片视为「指向不存在的产物」。
|
|
92
|
+
* 机器级 list / merge 展示路径用 listLive*Cards,不展示这类卡片(把卡片当活指针,而非持久记录)。
|
|
93
|
+
* 注:by-id 的 get / loadXReport 兜底仍走 raw list*Cards —— 直链书签按 card.path 读到则给真身、读不到各自降级,
|
|
94
|
+
* 是 best-effort,不在这层过滤。doctor 历史 prune 已连带删卡片,故悬空主要发生在「带外删整个项目目录」。
|
|
95
|
+
*/
|
|
96
|
+
function liveCards(cards) {
|
|
97
|
+
return cards.filter((c) => existsSync(c.path));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 某域所有卡片「真身(card.path)」的存在性 + mtime/size 指纹。
|
|
101
|
+
* 消费方(indexed-report-store / buildSkillIndex)的缓存指纹必须纳入它:卡片 JSON 本身没变、只靠卡片目录
|
|
102
|
+
* mtime 的指纹检测不到「真身被带外删」,长会话会继续命中旧缓存展示悬空卡片。真身删/改 → 此 sentinel 变 →
|
|
103
|
+
* 缓存失效 → live 过滤生效。读卡片只为拿 path,字段宽松。
|
|
104
|
+
*/
|
|
105
|
+
export function cardTargetSentinel(domain) {
|
|
106
|
+
const dir = artifactIndexDir(domain);
|
|
107
|
+
if (!existsSync(dir))
|
|
108
|
+
return '';
|
|
109
|
+
let files;
|
|
110
|
+
try {
|
|
111
|
+
files = readdirSync(dir);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const f of files) {
|
|
118
|
+
if (!f.endsWith('.json') || f.includes('.json.tmp.'))
|
|
119
|
+
continue;
|
|
120
|
+
try {
|
|
121
|
+
const c = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
122
|
+
if (typeof c.path !== 'string')
|
|
123
|
+
continue;
|
|
124
|
+
try {
|
|
125
|
+
const s = statSync(c.path);
|
|
126
|
+
parts.push(`${f}=${s.mtimeMs}:${s.size}`);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
parts.push(`${f}=gone`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch { /* skip corrupt */ }
|
|
133
|
+
}
|
|
134
|
+
return parts.sort().join(',');
|
|
135
|
+
}
|
|
136
|
+
// ── report 域 ───────────────────────────────────────────────────────────────
|
|
137
|
+
/** 报告投影成卡片(剥掉 results 重体)。 */
|
|
138
|
+
function reportCard(report, sourcePath) {
|
|
139
|
+
return report.kind === 'evaluation'
|
|
140
|
+
? { domain: 'report', id: report.id, path: sourcePath, kind: 'evaluation', meta: report.meta, summary: report.summary }
|
|
141
|
+
: { domain: 'report', id: report.id, path: sourcePath, kind: 'batch-evaluation', meta: report.meta, items: report.items };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* persistReport 的索引钩子:报告落盘后 best-effort 追加卡片。永不抛、永不阻断报告落盘。
|
|
145
|
+
* 入参故意收 `{id}` 宽松(同 persistReport 的 PersistableReport):非完整报告(无 canonical kind)防御式跳过。
|
|
146
|
+
*/
|
|
147
|
+
export function indexReportWrite(report, sourcePath, outputDir) {
|
|
148
|
+
try {
|
|
149
|
+
if (!shouldIndexReport(outputDir))
|
|
150
|
+
return;
|
|
151
|
+
const doc = report;
|
|
152
|
+
if (doc.kind !== 'evaluation' && doc.kind !== 'batch-evaluation')
|
|
153
|
+
return;
|
|
154
|
+
writeCard('report', report.id, reportCard(doc, sourcePath));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// 索引可重建,失败静默(可选 stderr warn);正文已落盘不受影响。
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** 读 report 域全部卡片(跳过坏文件 / 缺字段 / 坏 kind)。 */
|
|
161
|
+
export function listReportCards() {
|
|
162
|
+
return readArtifactCards('report', (c) => {
|
|
163
|
+
const card = c;
|
|
164
|
+
// kind 白名单:cardToReportDocument 对任何非 evaluation 一律按 batch 投影,坏 kind(拼错 / 'doctor')
|
|
165
|
+
// 会污染机器级 list 成空 batch 报告。索引是可重生 scratch,读侧从严跳过坏卡片。
|
|
166
|
+
const kindOk = card?.kind === 'evaluation' || card?.kind === 'batch-evaluation';
|
|
167
|
+
return !!card && card.domain === 'report' && kindOk && typeof card.id === 'string' && typeof card.path === 'string' && !!card.meta;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** report 卡片(过滤悬空真身):供 studio 机器级 list / findBy 展示。 */
|
|
171
|
+
export function listLiveReportCards() {
|
|
172
|
+
return liveCards(listReportCards());
|
|
173
|
+
}
|
|
174
|
+
/** 卡片 → ReportDocument(results:[]):供 studio list / buildSkillIndex / trend 消费(它们不读 results)。 */
|
|
175
|
+
export function cardToReportDocument(card) {
|
|
176
|
+
return card.kind === 'evaluation'
|
|
177
|
+
? { kind: 'evaluation', id: card.id, meta: card.meta, summary: card.summary ?? {}, results: [] }
|
|
178
|
+
: { kind: 'batch-evaluation', id: card.id, mode: 'skill', meta: card.meta, items: card.items ?? [] };
|
|
179
|
+
}
|
|
180
|
+
/** 删 report 域某 id 的卡片(DELETE 报告时连卡片一起删,使其从机器级 list 消失)。幂等、best-effort。 */
|
|
181
|
+
export function removeReportCard(id) {
|
|
182
|
+
return removeArtifactCard('report', id);
|
|
183
|
+
}
|
|
184
|
+
/** persistDoctorReport per-skill 循环里的索引钩子。best-effort,永不抛、永不阻断正文落盘。 */
|
|
185
|
+
export function indexDoctorWrite(card, outputDir) {
|
|
186
|
+
try {
|
|
187
|
+
if (!shouldIndexDir(outputDir, globalDoctorsDir()))
|
|
188
|
+
return;
|
|
189
|
+
writeCard('doctor', card.id, { domain: 'doctor', ...card });
|
|
190
|
+
}
|
|
191
|
+
catch { /* 索引可重建,失败静默 */ }
|
|
192
|
+
}
|
|
193
|
+
/** 读 doctor 域全部卡片。 */
|
|
194
|
+
export function listDoctorCards() {
|
|
195
|
+
return readArtifactCards('doctor', (c) => {
|
|
196
|
+
const card = c;
|
|
197
|
+
return !!card && card.domain === 'doctor' && typeof card.id === 'string' && typeof card.path === 'string'
|
|
198
|
+
&& typeof card.skillName === 'string' && typeof card.reportId === 'string' && typeof card.timestamp === 'string'
|
|
199
|
+
&& isDoctorStatus(card.status)
|
|
200
|
+
&& isFiniteNumber(card.passCount) && isFiniteNumber(card.warnCount) && isFiniteNumber(card.failCount);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/** doctor 卡片(过滤悬空真身):供 buildSkillIndex 机器级合并。 */
|
|
204
|
+
export function listLiveDoctorCards() {
|
|
205
|
+
return liveCards(listDoctorCards());
|
|
206
|
+
}
|
|
207
|
+
/** doctor 卡片 → (skillName, SkillDoctorSnapshot)。results:[](逐规则详情需回源项目看)。 */
|
|
208
|
+
export function cardToDoctorSnapshot(card) {
|
|
209
|
+
return {
|
|
210
|
+
skillName: card.skillName,
|
|
211
|
+
snap: {
|
|
212
|
+
reportId: card.reportId, timestamp: card.timestamp, status: card.status,
|
|
213
|
+
passCount: card.passCount, warnCount: card.warnCount, failCount: card.failCount, results: [],
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** 删 doctor 域某 id(文件 stem)的卡片。doctor 历史按 50/skill prune,删正文时必须连卡片一起删,
|
|
218
|
+
* 否则被 prune 掉的报告会经卡片合并在本项目 studio「复活」。 */
|
|
219
|
+
export function removeDoctorCard(id) {
|
|
220
|
+
return removeArtifactCard('doctor', id);
|
|
221
|
+
}
|
|
222
|
+
/** persistObserveHealthReport 的索引钩子。best-effort,永不抛、永不阻断正文落盘。 */
|
|
223
|
+
export function indexObserveWrite(report, sourcePath, outputDir, id) {
|
|
224
|
+
try {
|
|
225
|
+
if (!shouldIndexDir(outputDir, globalObserveHealthDir()))
|
|
226
|
+
return;
|
|
227
|
+
const bySkill = {};
|
|
228
|
+
for (const [name, h] of Object.entries(report.bySkill || {})) {
|
|
229
|
+
bySkill[name] = {
|
|
230
|
+
toolFailureRate: h.toolFailureRate, segmentCount: h.segmentCount,
|
|
231
|
+
gap: { weightedGapRate: h.gap?.weightedGapRate ?? 0 }, confidence: h.confidence,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const card = {
|
|
235
|
+
domain: 'observe-health', id, path: sourcePath,
|
|
236
|
+
meta: { generatedAt: report.meta.generatedAt, sessionCount: report.meta.sessionCount, segmentCount: report.meta.segmentCount },
|
|
237
|
+
overall: { healthBand: report.overall.healthBand, confidence: report.overall.confidence },
|
|
238
|
+
bySkill,
|
|
239
|
+
};
|
|
240
|
+
writeCard('observe-health', id, card);
|
|
241
|
+
}
|
|
242
|
+
catch { /* 索引可重建,失败静默 */ }
|
|
243
|
+
}
|
|
244
|
+
/** 读 observe-health 域全部卡片。 */
|
|
245
|
+
export function listObserveCards() {
|
|
246
|
+
return readArtifactCards('observe-health', (c) => {
|
|
247
|
+
const card = c;
|
|
248
|
+
if (!card || card.domain !== 'observe-health' || typeof card.id !== 'string' || typeof card.path !== 'string')
|
|
249
|
+
return false;
|
|
250
|
+
const meta = card.meta;
|
|
251
|
+
const overall = card.overall;
|
|
252
|
+
const bySkill = card.bySkill;
|
|
253
|
+
if (!meta || !isFiniteNumber(meta.sessionCount) || !isFiniteNumber(meta.segmentCount) || typeof meta.generatedAt !== 'string')
|
|
254
|
+
return false;
|
|
255
|
+
if (!overall || !isHealthBand(overall.healthBand) || !isConfidence(overall.confidence))
|
|
256
|
+
return false;
|
|
257
|
+
if (!bySkill || typeof bySkill !== 'object')
|
|
258
|
+
return false;
|
|
259
|
+
// 每个 skill 的标量也校验:坏 toolFailureRate / segmentCount / gap.weightedGapRate 会让 bandFromObserveHealth /
|
|
260
|
+
// 趋势 / 健康快照出 NaN(buildSkillIndex 直接取 h.gap?.weightedGapRate ?? 0 进 gapRate、参与阈值判断)。
|
|
261
|
+
for (const h of Object.values(bySkill)) {
|
|
262
|
+
if (!h || !isFiniteNumber(h.toolFailureRate) || !isFiniteNumber(h.segmentCount) || !isConfidence(h.confidence))
|
|
263
|
+
return false;
|
|
264
|
+
if (h.gap !== undefined && h.gap.weightedGapRate !== undefined && !isFiniteNumber(h.gap.weightedGapRate))
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/** observe 卡片(过滤悬空真身):供 listAnalyses / buildSkillIndex 机器级合并。 */
|
|
271
|
+
export function listLiveObserveCards() {
|
|
272
|
+
return liveCards(listObserveCards());
|
|
273
|
+
}
|
|
274
|
+
/** 删 observe 域某 id 的卡片。 */
|
|
275
|
+
export function removeObserveCard(id) {
|
|
276
|
+
return removeArtifactCard('observe-health', id);
|
|
277
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* omk 所有默认产物的用户级根目录 —— 全部 `~/.oh-my-knowledge/*` 默认目录的单一基准,
|
|
3
|
+
* 各目录都从这里派生,不在散落各处重复拼 `join(homedir(), '.oh-my-knowledge', ...)`。
|
|
4
|
+
*
|
|
5
|
+
* `OMK_HOME` 环境变量可整体重定向这棵树:一处覆盖即把 reports / doctors / observe-health / state
|
|
6
|
+
* (cache / trees / jobs / artifact-index)全部移走。测试用它一次性隔离,免得每加一个会从深层调用点写
|
|
7
|
+
* 全局默认目录的写路径,都得单独补一个 per-dir env 兜底(否则注入不进的写路径会静默污染真实 home)。
|
|
8
|
+
* 生产不设此变量时回落到 `~/.oh-my-knowledge`,行为不变。用户迁移整个数据目录也可用它。
|
|
9
|
+
*/
|
|
10
|
+
export declare const OMK_HOME: string;
|
|
11
|
+
/**
|
|
12
|
+
* 耐久测量数据(要留、被 reportId 等引用,绝不自动删)的默认根目录。这几个是 cli 写、server(Studio)读
|
|
13
|
+
* 的同一处,必须一致 —— 否则 cli 把产物写到一处、studio 从另一处读,页面就空。放在中立的 eval-core 层,
|
|
14
|
+
* 让 cli/ 与 server/ 两个交付层都能合法 import 同一个常量,而不必让 server 反向 import cli
|
|
15
|
+
* (那是 #242 清掉的分层倒挂)。
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_REPORTS_DIR: string;
|
|
18
|
+
export declare const DEFAULT_OBSERVE_HEALTH_DIR: string;
|
|
19
|
+
export declare const DEFAULT_DOCTORS_DIR: string;
|
|
20
|
+
/**
|
|
21
|
+
* 可重生的执行 scratch(删了能重建)统一收在 `state/` 子树下,跟上面的耐久数据物理分开 ——
|
|
22
|
+
* 想清磁盘时「这一坨能整删」一眼可见。各 scratch 目录都派生自这个 state 根,避免散落硬编码。
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_STATE_DIR: string;
|
|
25
|
+
export declare const DEFAULT_CACHE_DIR: string;
|
|
26
|
+
export declare const DEFAULT_ISOLATED_CWD_DIR: string;
|
|
27
|
+
export declare const DEFAULT_TREES_DIR: string;
|
|
28
|
+
export declare const DEFAULT_JOBS_DIR: string;
|
|
29
|
+
/**
|
|
30
|
+
* 产物发现索引根:每类测量产物(report / doctor / observe-health)项目本地落盘后,在全局这里留一张
|
|
31
|
+
* 轻量目录卡片(指向项目里的真身),让 `omk studio` 跨项目聚合成「机器级总览」。索引是可重生的 scratch
|
|
32
|
+
* (丢了重跑即重建,且当前项目+全局靠 live-scan 永远覆盖),故收在 state/ 子树。
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_ARTIFACT_INDEX_DIR: string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* omk 所有默认产物的用户级根目录 —— 全部 `~/.oh-my-knowledge/*` 默认目录的单一基准,
|
|
5
|
+
* 各目录都从这里派生,不在散落各处重复拼 `join(homedir(), '.oh-my-knowledge', ...)`。
|
|
6
|
+
*
|
|
7
|
+
* `OMK_HOME` 环境变量可整体重定向这棵树:一处覆盖即把 reports / doctors / observe-health / state
|
|
8
|
+
* (cache / trees / jobs / artifact-index)全部移走。测试用它一次性隔离,免得每加一个会从深层调用点写
|
|
9
|
+
* 全局默认目录的写路径,都得单独补一个 per-dir env 兜底(否则注入不进的写路径会静默污染真实 home)。
|
|
10
|
+
* 生产不设此变量时回落到 `~/.oh-my-knowledge`,行为不变。用户迁移整个数据目录也可用它。
|
|
11
|
+
*/
|
|
12
|
+
export const OMK_HOME = process.env.OMK_HOME || join(homedir(), '.oh-my-knowledge');
|
|
13
|
+
/**
|
|
14
|
+
* 耐久测量数据(要留、被 reportId 等引用,绝不自动删)的默认根目录。这几个是 cli 写、server(Studio)读
|
|
15
|
+
* 的同一处,必须一致 —— 否则 cli 把产物写到一处、studio 从另一处读,页面就空。放在中立的 eval-core 层,
|
|
16
|
+
* 让 cli/ 与 server/ 两个交付层都能合法 import 同一个常量,而不必让 server 反向 import cli
|
|
17
|
+
* (那是 #242 清掉的分层倒挂)。
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_REPORTS_DIR = join(OMK_HOME, 'reports');
|
|
20
|
+
export const DEFAULT_OBSERVE_HEALTH_DIR = join(OMK_HOME, 'observe-health');
|
|
21
|
+
export const DEFAULT_DOCTORS_DIR = join(OMK_HOME, 'doctors');
|
|
22
|
+
/**
|
|
23
|
+
* 可重生的执行 scratch(删了能重建)统一收在 `state/` 子树下,跟上面的耐久数据物理分开 ——
|
|
24
|
+
* 想清磁盘时「这一坨能整删」一眼可见。各 scratch 目录都派生自这个 state 根,避免散落硬编码。
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_STATE_DIR = join(OMK_HOME, 'state');
|
|
27
|
+
export const DEFAULT_CACHE_DIR = join(DEFAULT_STATE_DIR, 'cache');
|
|
28
|
+
export const DEFAULT_ISOLATED_CWD_DIR = join(DEFAULT_STATE_DIR, 'isolated-cwd');
|
|
29
|
+
export const DEFAULT_TREES_DIR = join(DEFAULT_STATE_DIR, 'trees');
|
|
30
|
+
export const DEFAULT_JOBS_DIR = join(DEFAULT_STATE_DIR, 'jobs');
|
|
31
|
+
/**
|
|
32
|
+
* 产物发现索引根:每类测量产物(report / doctor / observe-health)项目本地落盘后,在全局这里留一张
|
|
33
|
+
* 轻量目录卡片(指向项目里的真身),让 `omk studio` 跨项目聚合成「机器级总览」。索引是可重生的 scratch
|
|
34
|
+
* (丢了重跑即重建,且当前项目+全局靠 live-scan 永远覆盖),故收在 state/ 子树。
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_ARTIFACT_INDEX_DIR = join(DEFAULT_STATE_DIR, 'artifact-index');
|
|
@@ -4,9 +4,9 @@ import { safeSliceForJson } from '../util/safe-slice.js';
|
|
|
4
4
|
import { grade } from '../grading/index.js';
|
|
5
5
|
import { checkFacts } from './fact-checker.js';
|
|
6
6
|
import { resolveExecutionStrategy } from './execution-strategy.js';
|
|
7
|
+
import { DEFAULT_CACHE_DIR } from './default-dirs.js';
|
|
7
8
|
import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
|
|
8
|
-
import { resolve,
|
|
9
|
-
import { homedir } from 'node:os';
|
|
9
|
+
import { resolve, dirname } from 'node:path';
|
|
10
10
|
async function runWithConcurrency(tasks, concurrency, fn) {
|
|
11
11
|
let index = 0;
|
|
12
12
|
async function worker() {
|
|
@@ -50,7 +50,7 @@ export async function executeTasks({ tasks, executor, executorName, model, noJud
|
|
|
50
50
|
results[sampleId] = { ...variants };
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
const cacheDir =
|
|
53
|
+
const cacheDir = DEFAULT_CACHE_DIR;
|
|
54
54
|
const cache = noCache ? null : createCache(cacheDir);
|
|
55
55
|
async function executeTask(task) {
|
|
56
56
|
// Skip if already have a successful result (--resume)
|
|
@@ -33,5 +33,12 @@ export interface PersistableReport {
|
|
|
33
33
|
id: string;
|
|
34
34
|
}
|
|
35
35
|
export declare function persistReport(report: PersistableReport, outputDir: string | null): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* run id 的时间戳后缀 `YYYYMMDD-HHmmss-rand4`。
|
|
38
|
+
* 含秒 + 4 位随机:id 是 run 标签(非测量数),但被 studio 机器级 dedup 与 managed 证据 (reportId,
|
|
39
|
+
* contentHash) 去重当唯一键用。分钟级会让跨项目 / 同分钟重跑撞同 id → 索引静默顶掉一份、managed 错并一条。
|
|
40
|
+
* 秒+随机根治撞名,保证每次 run 全局唯一。供 generateRunId 与 evolve 合并 id 共用,避免靠 split 反解格式。
|
|
41
|
+
*/
|
|
42
|
+
export declare function runIdSuffix(): string;
|
|
36
43
|
export declare function generateRunId(variants: string[]): string;
|
|
37
44
|
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
4
|
import { createHash } from 'node:crypto';
|
|
6
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { DEFAULT_REPORTS_DIR } from './default-dirs.js';
|
|
7
|
+
import { indexReportWrite } from './artifact-index.js';
|
|
7
8
|
import { buildVariantSummary } from './schema.js';
|
|
8
9
|
import { buildVariantConfig, resolveExecutionStrategy } from './execution-strategy.js';
|
|
9
10
|
import { getJudgePromptHash } from '../grading/judge.js';
|
|
@@ -21,7 +22,8 @@ function findPackageJson(startDir) {
|
|
|
21
22
|
return join(startDir, '..', 'package.json');
|
|
22
23
|
}
|
|
23
24
|
const PKG = JSON.parse(readFileSync(findPackageJson(__dirname), 'utf-8'));
|
|
24
|
-
|
|
25
|
+
// 写报告的默认目录 = reports 单一来源(default-dirs)。保留 DEFAULT_OUTPUT_DIR 名给既有 16 处 import,值统一,杜绝写/读两端漂移。
|
|
26
|
+
export const DEFAULT_OUTPUT_DIR = DEFAULT_REPORTS_DIR;
|
|
25
27
|
export const EVALUATION_REPORT_SCHEMA_VERSION = 4;
|
|
26
28
|
export function hashString(str) {
|
|
27
29
|
return createHash('sha256').update(str).digest('hex').slice(0, 12);
|
|
@@ -290,15 +292,28 @@ export function persistReport(report, outputDir) {
|
|
|
290
292
|
mkdirSync(outputDir, { recursive: true });
|
|
291
293
|
const filePath = join(outputDir, `${report.id}.json`);
|
|
292
294
|
writeFileSync(filePath, JSON.stringify(report, null, 2));
|
|
295
|
+
// 产物发现索引:报告落项目本地后,best-effort 追加全局轻卡片,让 omk studio 跨项目聚合成机器级总览。
|
|
296
|
+
// 永不抛、永不阻断报告落盘(正文是 source of truth)。
|
|
297
|
+
indexReportWrite(report, filePath, outputDir);
|
|
293
298
|
return filePath;
|
|
294
299
|
}
|
|
295
|
-
|
|
300
|
+
/**
|
|
301
|
+
* run id 的时间戳后缀 `YYYYMMDD-HHmmss-rand4`。
|
|
302
|
+
* 含秒 + 4 位随机:id 是 run 标签(非测量数),但被 studio 机器级 dedup 与 managed 证据 (reportId,
|
|
303
|
+
* contentHash) 去重当唯一键用。分钟级会让跨项目 / 同分钟重跑撞同 id → 索引静默顶掉一份、managed 错并一条。
|
|
304
|
+
* 秒+随机根治撞名,保证每次 run 全局唯一。供 generateRunId 与 evolve 合并 id 共用,避免靠 split 反解格式。
|
|
305
|
+
*/
|
|
306
|
+
export function runIdSuffix() {
|
|
296
307
|
const d = new Date();
|
|
297
308
|
const pad = (n) => String(n).padStart(2, '0');
|
|
298
309
|
const date = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}`;
|
|
299
|
-
const time = `${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
310
|
+
const time = `${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
311
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
312
|
+
return `${date}-${time}-${rand}`;
|
|
313
|
+
}
|
|
314
|
+
export function generateRunId(variants) {
|
|
300
315
|
const variantPart = variants
|
|
301
316
|
.map((variant) => variant.replaceAll(/[\\/:]/g, '-').replaceAll(/[^a-zA-Z0-9._@-]/g, '_'))
|
|
302
317
|
.join('-vs-');
|
|
303
|
-
return `${variantPart}-${
|
|
318
|
+
return `${variantPart}-${runIdSuffix()}`;
|
|
304
319
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { dirname
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
2
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { DEFAULT_ISOLATED_CWD_DIR } from './default-dirs.js';
|
|
4
4
|
/**
|
|
5
5
|
* Isolated cwd for strict baseline. Empty dir under ~/.oh-my-knowledge/
|
|
6
6
|
* so baseline's Glob/Read tools can't walk into the user's eval workdir (which
|
|
@@ -11,7 +11,7 @@ import { homedir } from 'node:os';
|
|
|
11
11
|
* Stable path (not per-task tmpdir) so cache keys remain consistent.
|
|
12
12
|
*/
|
|
13
13
|
function getIsolatedCwd() {
|
|
14
|
-
const dir =
|
|
14
|
+
const dir = DEFAULT_ISOLATED_CWD_DIR;
|
|
15
15
|
if (!existsSync(dir)) {
|
|
16
16
|
mkdirSync(dir, { recursive: true });
|
|
17
17
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** 项目级 observe-health 目录(相对调用时 cwd)。 */
|
|
2
|
+
export declare function projectObserveHealthDir(cwd?: string): string;
|
|
3
|
+
/** 全局 observe-health 目录。 */
|
|
4
|
+
export declare function globalObserveHealthDir(): string;
|
|
5
|
+
/** 权威 observe-health 目录:项目有报告取项目,否则全局有取全局,都空回项目(同 resolveManagedDir)。
|
|
6
|
+
* `global` 可注入(默认真实全局目录),仅供测试用受控 temp 目录复现 project↔global 兜底。 */
|
|
7
|
+
export declare function resolveObserveHealthDir(dir?: string, global?: string): string;
|
|
8
|
+
/** 项目级 doctors 目录(相对调用时 cwd)。 */
|
|
9
|
+
export declare function projectDoctorsDir(cwd?: string): string;
|
|
10
|
+
/** 全局 doctors 目录。 */
|
|
11
|
+
export declare function globalDoctorsDir(): string;
|
|
12
|
+
/** 权威 doctors 目录:项目有报告取项目,否则全局有取全局,都空回项目(同 resolveManagedDir)。
|
|
13
|
+
* `global` 可注入(默认真实全局目录),仅供测试用受控 temp 目录复现 project↔global 兜底。 */
|
|
14
|
+
export declare function resolveDoctorsDir(dir?: string, global?: string): string;
|
|
15
|
+
/** 项目级 reports 目录(相对调用时 cwd)。 */
|
|
16
|
+
export declare function projectReportsDir(cwd?: string): string;
|
|
17
|
+
/** 全局 reports 目录。 */
|
|
18
|
+
export declare function globalReportsDir(): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { DEFAULT_OBSERVE_HEALTH_DIR, DEFAULT_DOCTORS_DIR, DEFAULT_REPORTS_DIR } from './default-dirs.js';
|
|
4
|
+
/**
|
|
5
|
+
* 测量产物的「项目优先 → 全局兜底」目录解析,镜像 managed 的 `resolveManagedDir`
|
|
6
|
+
* (src/managed/store.ts)。测量产物绑用例集上下文(construct validity,不可全局化),
|
|
7
|
+
* 默认落项目 `.omk/`,全局作显式 opt-in。
|
|
8
|
+
*
|
|
9
|
+
* 「记录优先」—— 目录里有匹配 `.json` 才算数(不是「目录存在」),与 managed 同口径,
|
|
10
|
+
* 避免空项目目录遮蔽全局数据。项目目录按**调用时** `cwd()` 求值(函数,不是 import 时
|
|
11
|
+
* 冻结的常量),studio 长会话 per-request 解析才正确。
|
|
12
|
+
*/
|
|
13
|
+
/** dir 里是否有至少一个满足 match 的文件(短路,不读文件内容)。 */
|
|
14
|
+
function hasMatchingFile(dir, match) {
|
|
15
|
+
if (!existsSync(dir))
|
|
16
|
+
return false;
|
|
17
|
+
try {
|
|
18
|
+
return readdirSync(dir).some(match);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// —— observe-health(skill 健康度报告)——
|
|
25
|
+
// 文件名 `{timestamp}-observe-health.json`,匹配后缀避免别的 .json 误翻转 resolver。
|
|
26
|
+
/** 项目级 observe-health 目录(相对调用时 cwd)。 */
|
|
27
|
+
export function projectObserveHealthDir(cwd = process.cwd()) {
|
|
28
|
+
return join(cwd, '.omk', 'observe-health');
|
|
29
|
+
}
|
|
30
|
+
/** 全局 observe-health 目录。 */
|
|
31
|
+
export function globalObserveHealthDir() {
|
|
32
|
+
return DEFAULT_OBSERVE_HEALTH_DIR;
|
|
33
|
+
}
|
|
34
|
+
/** 权威 observe-health 目录:项目有报告取项目,否则全局有取全局,都空回项目(同 resolveManagedDir)。
|
|
35
|
+
* `global` 可注入(默认真实全局目录),仅供测试用受控 temp 目录复现 project↔global 兜底。 */
|
|
36
|
+
export function resolveObserveHealthDir(dir = projectObserveHealthDir(), global = globalObserveHealthDir()) {
|
|
37
|
+
const has = (d) => hasMatchingFile(d, (f) => f.endsWith('-observe-health.json'));
|
|
38
|
+
if (has(dir))
|
|
39
|
+
return dir;
|
|
40
|
+
if (dir !== global && has(global))
|
|
41
|
+
return global;
|
|
42
|
+
return dir;
|
|
43
|
+
}
|
|
44
|
+
// —— doctors(体检报告)——
|
|
45
|
+
// 文件名 `{skill}-{id}.json`,谓词用「存在任意 .json」即可(真解析在下游 scanDoctorReports)。
|
|
46
|
+
/** 项目级 doctors 目录(相对调用时 cwd)。 */
|
|
47
|
+
export function projectDoctorsDir(cwd = process.cwd()) {
|
|
48
|
+
return join(cwd, '.omk', 'doctors');
|
|
49
|
+
}
|
|
50
|
+
/** 全局 doctors 目录。 */
|
|
51
|
+
export function globalDoctorsDir() {
|
|
52
|
+
return DEFAULT_DOCTORS_DIR;
|
|
53
|
+
}
|
|
54
|
+
/** 权威 doctors 目录:项目有报告取项目,否则全局有取全局,都空回项目(同 resolveManagedDir)。
|
|
55
|
+
* `global` 可注入(默认真实全局目录),仅供测试用受控 temp 目录复现 project↔global 兜底。 */
|
|
56
|
+
export function resolveDoctorsDir(dir = projectDoctorsDir(), global = globalDoctorsDir()) {
|
|
57
|
+
const has = (d) => hasMatchingFile(d, (f) => f.endsWith('.json'));
|
|
58
|
+
if (has(dir))
|
|
59
|
+
return dir;
|
|
60
|
+
if (dir !== global && has(global))
|
|
61
|
+
return global;
|
|
62
|
+
return dir;
|
|
63
|
+
}
|
|
64
|
+
// —— reports(eval 评测报告)——
|
|
65
|
+
// reports 与 observe-health / doctors 不同:它不是「选一个权威目录」就够的展示列表,而是按 id
|
|
66
|
+
// 寻址的 store(get(id) / findByArtifactHash 被 resume / gold-compare / baseline 复用依赖)。
|
|
67
|
+
// 故记录优先在 store 层做(见 createOverlayReportStore:项目盖全局),此处只给写入侧与 --global
|
|
68
|
+
// 用的项目 / 全局目录 getter,不给 resolveReportsDir(单目录二选一会让目标 id 在另一目录时 get 落空)。
|
|
69
|
+
/** 项目级 reports 目录(相对调用时 cwd)。 */
|
|
70
|
+
export function projectReportsDir(cwd = process.cwd()) {
|
|
71
|
+
return join(cwd, '.omk', 'reports');
|
|
72
|
+
}
|
|
73
|
+
/** 全局 reports 目录。 */
|
|
74
|
+
export function globalReportsDir() {
|
|
75
|
+
return DEFAULT_REPORTS_DIR;
|
|
76
|
+
}
|
|
@@ -2,7 +2,8 @@ import { dirname } from 'node:path';
|
|
|
2
2
|
import { DEFAULT_OUTPUT_DIR, EVALUATION_REPORT_SCHEMA_VERSION, generateRunId, getCliVersion, getGitInfo, persistReport, } from '../eval-core/evaluation-reporting.js';
|
|
3
3
|
import { buildEvaluationRequest, createEvaluationRun, createSucceededJob, finalizeEvaluationRun } from '../eval-core/evaluation-job.js';
|
|
4
4
|
import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
|
|
5
|
-
import { createFileJobStore
|
|
5
|
+
import { createFileJobStore } from '../server/job-store.js';
|
|
6
|
+
import { DEFAULT_JOBS_DIR } from '../eval-core/default-dirs.js';
|
|
6
7
|
/** Batch 每个 entry 的实验结构固定:baseline control vs 当前 skill treatment。
|
|
7
8
|
* 构造 VariantSpec 交给 runEvaluation 的 spec-based 解析按身份绑定 role / 隔离——与单跑
|
|
8
9
|
* 路径同一处绑定逻辑,batch 不再自管 artifact 拼装(此前两处手抄 resolveArtifacts().map()
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* 不再单独 export 给测试 —— job 生命周期通过 orchestrator 间接验证,无独立单测。
|
|
13
13
|
*/
|
|
14
14
|
import { buildEvaluationRequest, createFailedJob, createEvaluationRun, createQueuedJob, createSucceededJob, finalizeEvaluationRun, markJobRunning, failEvaluationRun, } from '../../eval-core/evaluation-job.js';
|
|
15
|
-
import { createFileJobStore
|
|
15
|
+
import { createFileJobStore } from '../../server/job-store.js';
|
|
16
|
+
import { DEFAULT_JOBS_DIR } from '../../eval-core/default-dirs.js';
|
|
16
17
|
export async function initializeEvaluationRunState({ samplesPath, skillDir, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, concurrency, timeoutMs, noCache, blind, project, owner, tags, runId, jobStore, persistJob, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, effort, }) {
|
|
17
18
|
const effectiveJudges = judgeModels && judgeModels.length > 0
|
|
18
19
|
? judgeModels
|
|
@@ -94,8 +94,11 @@ export async function runEvaluation({ samplesPath, skillDir, variantSpecs = [],
|
|
|
94
94
|
// --resume: load existing report results to skip completed tasks
|
|
95
95
|
let existingResults;
|
|
96
96
|
if (resume) {
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// 读侧走 overlay(本次 outputDir 优先 → 全局兜底):写默认翻项目后,--resume 一个存量 / 全局报告(升级前
|
|
98
|
+
// 全部落全局)仍能 get 命中,不致整轮重跑。写仍是另起新报告到 outputDir,与读位置解耦,无 read/write 分叉。
|
|
99
|
+
const { createOverlayReportStore } = await import('../server/report-store.js');
|
|
100
|
+
const { globalReportsDir } = await import('../eval-core/measurement-dirs.js');
|
|
101
|
+
const store = createOverlayReportStore(resolve(outputDir || DEFAULT_OUTPUT_DIR), globalReportsDir());
|
|
99
102
|
const existing = await store.get(resume);
|
|
100
103
|
if (existing?.kind === 'evaluation') {
|
|
101
104
|
existingResults = {};
|
|
@@ -10,7 +10,7 @@ import { buildExecEnv, DEFAULT_TIMEOUT_MS, errorMessage, interruptedExecResult,
|
|
|
10
10
|
//
|
|
11
11
|
// undefined → 不传 -C(原行为,看 cwd 里有什么 codex 自己决定)
|
|
12
12
|
// [] → 必须提供 cwd 非空(否则 throw),caller 应传一个
|
|
13
|
-
// isolated 空目录(如 ~/.oh-my-knowledge/isolated-cwd/)
|
|
13
|
+
// isolated 空目录(如 ~/.oh-my-knowledge/state/isolated-cwd/)
|
|
14
14
|
// [...] (length>0) → throw,codex CLI 没有 partial 白名单 flag
|
|
15
15
|
export function isolateCodexCwd(allowedSkills, cwd, executorName = 'codex-cli') {
|
|
16
16
|
if (allowedSkills === undefined)
|
|
@@ -25,7 +25,7 @@ export function isolateCodexCwd(allowedSkills, cwd, executorName = 'codex-cli')
|
|
|
25
25
|
throw new Error(`${executorName} executor allowedSkills=[] 需要提供 cwd 非空(channel 3 cwd 隔离)。\n`
|
|
26
26
|
+ ' codex 没有 SDK skill 自动发现 / subagent Skill 工具这两条 channel,\n'
|
|
27
27
|
+ ' cwd 文件系统隔离是它唯一能堵 AGENTS.md / .agents/skills/ 自动加载的途径。\n'
|
|
28
|
-
+ ' caller 应传一个 isolated 空目录(如 ~/.oh-my-knowledge/isolated-cwd/)。');
|
|
28
|
+
+ ' caller 应传一个 isolated 空目录(如 ~/.oh-my-knowledge/state/isolated-cwd/)。');
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
// codex 的 verbose 降级提示是 binary 能力快照(无 --system-prompt flag / 不报 cost),
|