oh-my-knowledge 0.26.0 → 0.28.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 +105 -346
- package/README.zh.md +142 -375
- package/dist/src/analysis/report-diagnostics.d.ts +2 -2
- package/dist/src/analysis/report-diagnostics.js +2 -2
- package/dist/src/analysis/sample-diagnostics.d.ts +3 -3
- package/dist/src/analysis/sample-diagnostics.js +3 -3
- package/dist/src/authoring/generator.d.ts.map +1 -1
- package/dist/src/authoring/generator.js +9 -8
- package/dist/src/authoring/generator.js.map +1 -1
- package/dist/src/cli/cli-exit.d.ts +15 -0
- package/dist/src/cli/cli-exit.d.ts.map +1 -0
- package/dist/src/cli/cli-exit.js +19 -0
- package/dist/src/cli/cli-exit.js.map +1 -0
- package/dist/src/cli/commands/_shared.d.ts +12 -0
- package/dist/src/cli/commands/_shared.d.ts.map +1 -0
- package/dist/src/cli/commands/_shared.js +26 -0
- package/dist/src/cli/commands/_shared.js.map +1 -0
- package/dist/src/cli/commands/doctor.d.ts +2 -0
- package/dist/src/cli/commands/doctor.d.ts.map +1 -0
- package/dist/src/cli/commands/doctor.js +174 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/eval-gold.d.ts +2 -0
- package/dist/src/cli/commands/eval-gold.d.ts.map +1 -0
- package/dist/src/cli/commands/eval-gold.js +137 -0
- package/dist/src/cli/commands/eval-gold.js.map +1 -0
- package/dist/src/cli/commands/eval-runner.d.ts +2 -0
- package/dist/src/cli/commands/eval-runner.d.ts.map +1 -0
- package/dist/src/cli/commands/eval-runner.js +299 -0
- package/dist/src/cli/commands/eval-runner.js.map +1 -0
- package/dist/src/cli/commands/eval.d.ts +2 -0
- package/dist/src/cli/commands/eval.d.ts.map +1 -0
- package/dist/src/cli/commands/eval.js +11 -0
- package/dist/src/cli/commands/eval.js.map +1 -0
- package/dist/src/cli/commands/evolve.d.ts +2 -0
- package/dist/src/cli/commands/evolve.d.ts.map +1 -0
- package/dist/src/cli/commands/evolve.js +115 -0
- package/dist/src/cli/commands/evolve.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts +2 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -0
- package/dist/src/cli/commands/init.js +110 -0
- package/dist/src/cli/commands/init.js.map +1 -0
- package/dist/src/cli/commands/observe.d.ts +2 -0
- package/dist/src/cli/commands/observe.d.ts.map +1 -0
- package/dist/src/cli/commands/observe.js +77 -0
- package/dist/src/cli/commands/observe.js.map +1 -0
- package/dist/src/cli/commands/registry.d.ts +11 -0
- package/dist/src/cli/commands/registry.d.ts.map +1 -0
- package/dist/src/cli/commands/registry.js +24 -0
- package/dist/src/cli/commands/registry.js.map +1 -0
- package/dist/src/cli/commands/sample.d.ts +2 -0
- package/dist/src/cli/commands/sample.d.ts.map +1 -0
- package/dist/src/cli/commands/sample.js +121 -0
- package/dist/src/cli/commands/sample.js.map +1 -0
- package/dist/src/cli/commands/studio.d.ts +2 -0
- package/dist/src/cli/commands/studio.d.ts.map +1 -0
- package/dist/src/cli/commands/studio.js +76 -0
- package/dist/src/cli/commands/studio.js.map +1 -0
- package/dist/src/cli/i18n-dict.d.ts +2 -4
- package/dist/src/cli/i18n-dict.d.ts.map +1 -1
- package/dist/src/cli/i18n-dict.js +461 -675
- package/dist/src/cli/i18n-dict.js.map +1 -1
- package/dist/src/cli/index.js +43 -1516
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/parse-run-config.d.ts +3 -4
- package/dist/src/cli/parse-run-config.d.ts.map +1 -1
- package/dist/src/cli/parse-run-config.js +5 -5
- package/dist/src/cli/parse-run-config.js.map +1 -1
- package/dist/src/cli/parse-strict.d.ts +0 -13
- package/dist/src/cli/parse-strict.d.ts.map +1 -1
- package/dist/src/cli/parse-strict.js +2 -1
- package/dist/src/cli/parse-strict.js.map +1 -1
- package/dist/src/doctor/health/builtin-dimensions.d.ts +11 -0
- package/dist/src/doctor/health/builtin-dimensions.d.ts.map +1 -0
- package/dist/src/doctor/health/builtin-dimensions.js +94 -0
- package/dist/src/doctor/health/builtin-dimensions.js.map +1 -0
- package/dist/src/doctor/health/composer.d.ts +19 -0
- package/dist/src/doctor/health/composer.d.ts.map +1 -0
- package/dist/src/doctor/health/composer.js +289 -0
- package/dist/src/doctor/health/composer.js.map +1 -0
- package/dist/src/doctor/health/dimension-registry.d.ts +13 -0
- package/dist/src/doctor/health/dimension-registry.d.ts.map +1 -0
- package/dist/src/doctor/health/dimension-registry.js +28 -0
- package/dist/src/doctor/health/dimension-registry.js.map +1 -0
- package/dist/src/doctor/health/dimension-spec.d.ts +46 -0
- package/dist/src/doctor/health/dimension-spec.d.ts.map +1 -0
- package/dist/src/doctor/health/dimension-spec.js +12 -0
- package/dist/src/doctor/health/dimension-spec.js.map +1 -0
- package/dist/src/doctor/health/parser.d.ts +27 -0
- package/dist/src/doctor/health/parser.d.ts.map +1 -0
- package/dist/src/doctor/health/parser.js +190 -0
- package/dist/src/doctor/health/parser.js.map +1 -0
- package/dist/src/doctor/health/prompt-builder.d.ts +22 -0
- package/dist/src/doctor/health/prompt-builder.d.ts.map +1 -0
- package/dist/src/doctor/health/prompt-builder.js +162 -0
- package/dist/src/doctor/health/prompt-builder.js.map +1 -0
- package/dist/src/doctor/health/register.d.ts +13 -0
- package/dist/src/doctor/health/register.d.ts.map +1 -0
- package/dist/src/doctor/health/register.js +20 -0
- package/dist/src/doctor/health/register.js.map +1 -0
- package/dist/src/doctor/html-renderer.d.ts +20 -0
- package/dist/src/doctor/html-renderer.d.ts.map +1 -0
- package/dist/src/doctor/html-renderer.js +366 -0
- package/dist/src/doctor/html-renderer.js.map +1 -0
- package/dist/src/doctor/index.d.ts +1 -1
- package/dist/src/doctor/index.d.ts.map +1 -1
- package/dist/src/doctor/index.js +57 -8
- package/dist/src/doctor/index.js.map +1 -1
- package/dist/src/doctor/preflight.d.ts +2 -2
- package/dist/src/doctor/preflight.js +2 -2
- package/dist/src/doctor/renderer.d.ts +4 -0
- package/dist/src/doctor/renderer.d.ts.map +1 -1
- package/dist/src/doctor/renderer.js +72 -18
- package/dist/src/doctor/renderer.js.map +1 -1
- package/dist/src/doctor/rules.d.ts +6 -5
- package/dist/src/doctor/rules.d.ts.map +1 -1
- package/dist/src/doctor/rules.js +4 -3
- package/dist/src/doctor/rules.js.map +1 -1
- package/dist/src/eval-core/fact-checker.js +1 -1
- package/dist/src/eval-core/fact-checker.js.map +1 -1
- package/dist/src/eval-core/layer-gates.d.ts +1 -1
- package/dist/src/eval-core/layer-gates.js +1 -1
- package/dist/src/eval-core/verdict.d.ts +4 -4
- package/dist/src/eval-core/verdict.d.ts.map +1 -1
- package/dist/src/eval-core/verdict.js +2 -2
- package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts +15 -2
- package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts.map +1 -1
- package/dist/src/eval-workflows/batch-evaluation-workflow.js +13 -2
- package/dist/src/eval-workflows/batch-evaluation-workflow.js.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts +6 -3
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.js +10 -9
- package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.d.ts +1 -1
- package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.js +13 -6
- package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
- package/dist/src/executors/script.d.ts.map +1 -1
- package/dist/src/executors/script.js +16 -3
- package/dist/src/executors/script.js.map +1 -1
- package/dist/src/grading/debias-validate.d.ts +2 -2
- package/dist/src/grading/debias-validate.js +2 -2
- package/dist/src/grading/gold-cli.d.ts +2 -5
- package/dist/src/grading/gold-cli.d.ts.map +1 -1
- package/dist/src/grading/gold-cli.js +4 -8
- package/dist/src/grading/gold-cli.js.map +1 -1
- package/dist/src/grading/judge.d.ts +1 -1
- package/dist/src/renderer/html-renderer.js +1 -1
- package/dist/src/renderer/layout.js +5 -5
- package/dist/src/renderer/layout.js.map +1 -1
- package/dist/src/renderer/skill-health-renderer.d.ts +1 -1
- package/dist/src/renderer/skill-health-renderer.js +1 -1
- package/dist/src/renderer/summary.js +8 -8
- package/dist/src/server/report-server.d.ts.map +1 -1
- package/dist/src/server/report-server.js +8 -7
- package/dist/src/server/report-server.js.map +1 -1
- package/dist/src/types/doctor.d.ts +44 -6
- package/dist/src/types/doctor.d.ts.map +1 -1
- package/dist/src/types/doctor.js +3 -0
- package/dist/src/types/doctor.js.map +1 -1
- package/dist/src/types/eval.d.ts +1 -1
- package/dist/src/types/report.d.ts +2 -2
- package/dist/src/types/report.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/src/cli/coverage-renderer.d.ts +0 -15
- package/dist/src/cli/coverage-renderer.d.ts.map +0 -1
- package/dist/src/cli/coverage-renderer.js +0 -74
- package/dist/src/cli/coverage-renderer.js.map +0 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill_health composer rule — 健康度体检的"复合 rule"。
|
|
3
|
+
*
|
|
4
|
+
* 单次 LLM 调用产出全部已注册维度的 checklist;composer.checkAll() 返回 N+1 条
|
|
5
|
+
* outcome:N = 注册维度数(每维一条),1 = summary(top_suggestions / overall_health
|
|
6
|
+
* / feature_points 落在这条)。
|
|
7
|
+
*
|
|
8
|
+
* doctor engine 检测 kind='composer' 时把 outcome[] 展开成 N+1 条独立 DoctorRuleResult,
|
|
9
|
+
* 共享 groupId = composer.id,renderer 按 groupId 分组渲染。
|
|
10
|
+
*
|
|
11
|
+
* 工厂模式 makeSkillHealthComposer(execFactory) 给测试注入 mock executor。
|
|
12
|
+
* 默认 import 时不注册;register.ts 才做 registerRule 副作用。
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { tCli } from '../../cli/i18n.js';
|
|
17
|
+
import { createExecutor } from '../../executors/index.js';
|
|
18
|
+
import { getRegisteredHealthDimensions } from './dimension-registry.js';
|
|
19
|
+
import { buildHealthPrompt } from './prompt-builder.js';
|
|
20
|
+
import { deriveOverallHealth, extractJson, parseHealthOutput } from './parser.js';
|
|
21
|
+
export const SKILL_HEALTH_COMPOSER_ID = 'skill_health';
|
|
22
|
+
const defaultExecutorFactory = createExecutor;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Status mapping
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
function mapDimLevelToStatus(level, missing) {
|
|
27
|
+
if (missing)
|
|
28
|
+
return 'skipped';
|
|
29
|
+
switch (level) {
|
|
30
|
+
case '不健康': return 'fail';
|
|
31
|
+
case '亚健康': return 'warn';
|
|
32
|
+
case '健康': return 'pass';
|
|
33
|
+
case '不适用': return 'skipped';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Per-dim outcome rendering
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function dimMessage(dim, r, lang) {
|
|
40
|
+
if (r._missing) {
|
|
41
|
+
return tCli('cli.doctor.health.dim.missing', lang, { dim: dim.displayName });
|
|
42
|
+
}
|
|
43
|
+
const errs = r.findings.filter((f) => f.level === '错误').length;
|
|
44
|
+
const warns = r.findings.filter((f) => f.level === '警告').length;
|
|
45
|
+
const sugs = r.findings.filter((f) => f.level === '建议').length;
|
|
46
|
+
return tCli('cli.doctor.health.dim.message', lang, {
|
|
47
|
+
level: r.level, err: errs, warn: warns, sug: sugs,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function dimHint(r) {
|
|
51
|
+
if (r.suggestions.length === 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
return r.suggestions[0];
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Sub-files / siblings discovery (与上一版本相同)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
function listSubFiles(skillRoot) {
|
|
59
|
+
if (!skillRoot || !existsSync(skillRoot))
|
|
60
|
+
return [];
|
|
61
|
+
const out = [];
|
|
62
|
+
const walk = (dir, rel, depth) => {
|
|
63
|
+
if (depth > 2)
|
|
64
|
+
return;
|
|
65
|
+
let entries = [];
|
|
66
|
+
try {
|
|
67
|
+
entries = readdirSync(dir);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (entry.startsWith('.'))
|
|
74
|
+
continue;
|
|
75
|
+
if (entry === 'SKILL.md' && rel === '')
|
|
76
|
+
continue;
|
|
77
|
+
if (entry === 'node_modules')
|
|
78
|
+
continue;
|
|
79
|
+
const full = join(dir, entry);
|
|
80
|
+
const relPath = rel ? `${rel}/${entry}` : entry;
|
|
81
|
+
let st;
|
|
82
|
+
try {
|
|
83
|
+
st = statSync(full);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (st.isDirectory())
|
|
89
|
+
walk(full, relPath, depth + 1);
|
|
90
|
+
else if (st.isFile())
|
|
91
|
+
out.push(relPath);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
walk(skillRoot, '', 0);
|
|
95
|
+
return out.sort();
|
|
96
|
+
}
|
|
97
|
+
function listSiblingSkills(skillRoot) {
|
|
98
|
+
if (!skillRoot || !existsSync(skillRoot))
|
|
99
|
+
return [];
|
|
100
|
+
const parent = dirname(skillRoot);
|
|
101
|
+
if (!existsSync(parent))
|
|
102
|
+
return [];
|
|
103
|
+
const self = skillRoot.split('/').pop() ?? '';
|
|
104
|
+
let entries = [];
|
|
105
|
+
try {
|
|
106
|
+
entries = readdirSync(parent);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
return entries
|
|
112
|
+
.filter((e) => !e.startsWith('.') && e !== self)
|
|
113
|
+
.filter((e) => {
|
|
114
|
+
try {
|
|
115
|
+
return statSync(join(parent, e)).isDirectory();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
.sort();
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Composer factory
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
export function makeSkillHealthComposer(execFactory = defaultExecutorFactory) {
|
|
127
|
+
return {
|
|
128
|
+
id: SKILL_HEALTH_COMPOSER_ID,
|
|
129
|
+
kind: 'composer',
|
|
130
|
+
severity: 'fatal',
|
|
131
|
+
labelKey: 'cli.doctor.rule.skill_health_check',
|
|
132
|
+
checkAll: (ctx) => composerCheckAll(ctx, execFactory),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function composerCheckAll(ctx, execFactory) {
|
|
136
|
+
const dims = getRegisteredHealthDimensions();
|
|
137
|
+
// Programmatic opt-out: runHealthCheck=false → 给 summary 一条 skipped,
|
|
138
|
+
// 各维度不展开避免污染报告。CLI 的 --static-only 会直接过滤掉 composer rule。
|
|
139
|
+
if (!ctx.runHealthCheck) {
|
|
140
|
+
return [{
|
|
141
|
+
subId: '_summary',
|
|
142
|
+
severity: 'info',
|
|
143
|
+
labelKey: 'cli.doctor.rule.skill_health_check',
|
|
144
|
+
status: 'skipped',
|
|
145
|
+
message: tCli('cli.doctor.health.skipped', ctx.lang),
|
|
146
|
+
}];
|
|
147
|
+
}
|
|
148
|
+
// 没注册任何维度 → 短路
|
|
149
|
+
if (dims.length === 0) {
|
|
150
|
+
return [{
|
|
151
|
+
subId: '_summary',
|
|
152
|
+
severity: 'info',
|
|
153
|
+
labelKey: 'cli.doctor.rule.skill_health_check',
|
|
154
|
+
status: 'skipped',
|
|
155
|
+
message: tCli('cli.doctor.health.no_dimensions', ctx.lang),
|
|
156
|
+
}];
|
|
157
|
+
}
|
|
158
|
+
const content = ctx.artifact.content;
|
|
159
|
+
if (!content || content.trim().length === 0) {
|
|
160
|
+
// skill_readable rule 也会 fail;这里只是 short-circuit 防止白白调 LLM
|
|
161
|
+
return [{
|
|
162
|
+
subId: '_summary',
|
|
163
|
+
severity: 'info',
|
|
164
|
+
labelKey: 'cli.doctor.rule.skill_health_check',
|
|
165
|
+
status: 'skipped',
|
|
166
|
+
message: tCli('cli.doctor.health.skipped', ctx.lang),
|
|
167
|
+
}];
|
|
168
|
+
}
|
|
169
|
+
const skillName = ctx.artifact.name.replace(/\.md$/, '').split('/').pop() ?? ctx.artifact.name;
|
|
170
|
+
const skillRoot = ctx.artifact.skillRoot ?? null;
|
|
171
|
+
const subFiles = listSubFiles(skillRoot);
|
|
172
|
+
const siblingSkills = listSiblingSkills(skillRoot);
|
|
173
|
+
const prompt = buildHealthPrompt({ skillName, skillContent: content, skillRoot, subFiles, siblingSkills }, dims);
|
|
174
|
+
const executor = execFactory(ctx.executorName);
|
|
175
|
+
let res;
|
|
176
|
+
try {
|
|
177
|
+
res = await executor({
|
|
178
|
+
model: ctx.model,
|
|
179
|
+
prompt,
|
|
180
|
+
cwd: skillRoot ?? ctx.cwd,
|
|
181
|
+
skillDir: skillRoot ?? null,
|
|
182
|
+
timeoutMs: ctx.timeoutMs,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
187
|
+
return [errorSummaryOutcome(ctx, 'executor', msg)];
|
|
188
|
+
}
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
return [errorSummaryOutcome(ctx, 'executor', res.error ?? 'unknown')];
|
|
191
|
+
}
|
|
192
|
+
if (!res.output || res.output.trim().length === 0) {
|
|
193
|
+
return [errorSummaryOutcome(ctx, 'empty_output', '')];
|
|
194
|
+
}
|
|
195
|
+
const cleaned = extractJson(res.output);
|
|
196
|
+
if (cleaned === null) {
|
|
197
|
+
return [errorSummaryOutcome(ctx, 'extract', '', { rawOutput: res.output.slice(0, 4000) })];
|
|
198
|
+
}
|
|
199
|
+
const parseResult = parseHealthOutput(cleaned, dims);
|
|
200
|
+
const overall = parseResult.overall ?? deriveOverallHealth(parseResult.byDimId);
|
|
201
|
+
// 诊断:如果所有维度都被标 _missing,说明 LLM 输出 JSON 里 dim_id 字段缺失或写错。
|
|
202
|
+
// 把 cleaned JSON 的前 3KB 塞进 summary 的 detail.rawSample,方便用户排查。
|
|
203
|
+
const allMissing = [...parseResult.byDimId.values()].every((r) => r._missing);
|
|
204
|
+
const rawSample = allMissing ? cleaned.slice(0, 3000) : undefined;
|
|
205
|
+
// 汇总 finding 计数(框架算,不信 LLM 算术)
|
|
206
|
+
const finding = { '错误': 0, '警告': 0, '建议': 0 };
|
|
207
|
+
const dimCount = { '健康': 0, '亚健康': 0, '不健康': 0, '不适用': 0 };
|
|
208
|
+
for (const r of parseResult.byDimId.values()) {
|
|
209
|
+
if (r._missing)
|
|
210
|
+
continue;
|
|
211
|
+
dimCount[r.level] += 1;
|
|
212
|
+
for (const f of r.findings)
|
|
213
|
+
finding[f.level] += 1;
|
|
214
|
+
}
|
|
215
|
+
// N 条维度 outcome
|
|
216
|
+
const dimOutcomes = dims.map((dim) => {
|
|
217
|
+
const r = parseResult.byDimId.get(dim.id);
|
|
218
|
+
return {
|
|
219
|
+
subId: dim.id,
|
|
220
|
+
labelKey: dim.labelKey,
|
|
221
|
+
severity: dim.severity,
|
|
222
|
+
status: mapDimLevelToStatus(r.level, r._missing),
|
|
223
|
+
message: dimMessage(dim, r, ctx.lang),
|
|
224
|
+
hint: dimHint(r),
|
|
225
|
+
detail: {
|
|
226
|
+
dimensionId: dim.id,
|
|
227
|
+
displayName: dim.displayName,
|
|
228
|
+
level: r.level,
|
|
229
|
+
findings: r.findings,
|
|
230
|
+
suggestions: r.suggestions,
|
|
231
|
+
missing: r._missing ?? false,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
// 1 条 summary outcome
|
|
236
|
+
const summaryOutcome = {
|
|
237
|
+
subId: '_summary',
|
|
238
|
+
severity: 'info',
|
|
239
|
+
labelKey: 'cli.doctor.health.summary.label',
|
|
240
|
+
status: 'pass', // summary 永远 pass(信息性,不影响 outcome)
|
|
241
|
+
message: tCli('cli.doctor.health.summary.message', ctx.lang, {
|
|
242
|
+
overall,
|
|
243
|
+
h: dimCount['健康'], sh: dimCount['亚健康'], bad: dimCount['不健康'], na: dimCount['不适用'],
|
|
244
|
+
err: finding['错误'], warn: finding['警告'], sug: finding['建议'],
|
|
245
|
+
}),
|
|
246
|
+
hint: parseResult.topSuggestions.slice(0, 3).join(' | ')
|
|
247
|
+
|| tCli('cli.doctor.health.summary.no_top', ctx.lang),
|
|
248
|
+
detail: {
|
|
249
|
+
overall,
|
|
250
|
+
dimensionLevelCount: dimCount,
|
|
251
|
+
findingCountByLevel: finding,
|
|
252
|
+
topSuggestions: parseResult.topSuggestions,
|
|
253
|
+
featurePoints: parseResult.featurePoints,
|
|
254
|
+
durationMs: res.durationMs,
|
|
255
|
+
durationApiMs: res.durationApiMs,
|
|
256
|
+
// turns:claude-cli 在内部跑的 LLM 轮次(每轮 = 1 次 messages.create)
|
|
257
|
+
// numTurns:assistant turn 数 / fullNumTurns:含 tool turn 的总数
|
|
258
|
+
llmTurns: { numTurns: res.numTurns, fullNumTurns: res.fullNumTurns },
|
|
259
|
+
tokens: {
|
|
260
|
+
input: res.inputTokens, output: res.outputTokens,
|
|
261
|
+
cacheRead: res.cacheReadTokens, cacheCreation: res.cacheCreationTokens,
|
|
262
|
+
},
|
|
263
|
+
costUSD: res.costUSD,
|
|
264
|
+
executor: { name: ctx.executorName, model: ctx.model },
|
|
265
|
+
...(rawSample !== undefined ? { rawSample, _diagnostic: 'all dims missing — likely dim_id field absent in LLM output' } : {}),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return [...dimOutcomes, summaryOutcome];
|
|
269
|
+
}
|
|
270
|
+
function errorSummaryOutcome(ctx, phase, errMsg, extra = {}) {
|
|
271
|
+
const messageKey = phase === 'executor'
|
|
272
|
+
? 'cli.doctor.health.fail.executor'
|
|
273
|
+
: phase === 'empty_output'
|
|
274
|
+
? 'cli.doctor.health.fail.empty_output'
|
|
275
|
+
: 'cli.doctor.health.fail.parse';
|
|
276
|
+
const hintKey = phase === 'executor'
|
|
277
|
+
? 'cli.doctor.health.hint.executor'
|
|
278
|
+
: 'cli.doctor.health.hint.parse';
|
|
279
|
+
return {
|
|
280
|
+
subId: '_summary',
|
|
281
|
+
severity: 'fatal',
|
|
282
|
+
labelKey: 'cli.doctor.health.summary.label',
|
|
283
|
+
status: 'fail',
|
|
284
|
+
message: tCli(messageKey, ctx.lang, { error: errMsg.slice(0, 200) }),
|
|
285
|
+
hint: tCli(hintKey, ctx.lang),
|
|
286
|
+
detail: { phase, error: errMsg, ...extra },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=composer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.js","sourceRoot":"","sources":["../../../../src/doctor/health/composer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDimensionSpec 全局注册表(module-level singleton)。
|
|
3
|
+
*
|
|
4
|
+
* 内置 7 个维度由 health/register.ts 注册;用户通过 registerHealthDimension()
|
|
5
|
+
* 注册自定义维度。注册顺序就是 prompt 中的章节编号顺序。
|
|
6
|
+
*
|
|
7
|
+
* id 冲突会抛 — 防止意外覆盖。__resetForTest 给单测用。
|
|
8
|
+
*/
|
|
9
|
+
import type { HealthDimensionSpec } from './dimension-spec.js';
|
|
10
|
+
export declare function registerHealthDimension(spec: HealthDimensionSpec): void;
|
|
11
|
+
export declare function getRegisteredHealthDimensions(): HealthDimensionSpec[];
|
|
12
|
+
export declare function __resetHealthDimensionsForTest(): void;
|
|
13
|
+
//# sourceMappingURL=dimension-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimension-registry.d.ts","sourceRoot":"","sources":["../../../../src/doctor/health/dimension-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK/D,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CASvE;AAED,wBAAgB,6BAA6B,IAAI,mBAAmB,EAAE,CAErE;AAED,wBAAgB,8BAA8B,IAAI,IAAI,CAGrD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDimensionSpec 全局注册表(module-level singleton)。
|
|
3
|
+
*
|
|
4
|
+
* 内置 7 个维度由 health/register.ts 注册;用户通过 registerHealthDimension()
|
|
5
|
+
* 注册自定义维度。注册顺序就是 prompt 中的章节编号顺序。
|
|
6
|
+
*
|
|
7
|
+
* id 冲突会抛 — 防止意外覆盖。__resetForTest 给单测用。
|
|
8
|
+
*/
|
|
9
|
+
const dimensions = [];
|
|
10
|
+
const seenIds = new Set();
|
|
11
|
+
export function registerHealthDimension(spec) {
|
|
12
|
+
if (!spec.id || spec.id.includes(':')) {
|
|
13
|
+
throw new Error(`invalid health dimension id: "${spec.id}" (non-empty, no ":")`);
|
|
14
|
+
}
|
|
15
|
+
if (seenIds.has(spec.id)) {
|
|
16
|
+
throw new Error(`health dimension id collision: ${spec.id}`);
|
|
17
|
+
}
|
|
18
|
+
seenIds.add(spec.id);
|
|
19
|
+
dimensions.push(spec);
|
|
20
|
+
}
|
|
21
|
+
export function getRegisteredHealthDimensions() {
|
|
22
|
+
return [...dimensions]; // 防止外部 mutate
|
|
23
|
+
}
|
|
24
|
+
export function __resetHealthDimensionsForTest() {
|
|
25
|
+
dimensions.length = 0;
|
|
26
|
+
seenIds.clear();
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=dimension-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimension-registry.js","sourceRoot":"","sources":["../../../../src/doctor/health/dimension-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,UAAU,GAA0B,EAAE,CAAC;AAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;AAElC,MAAM,UAAU,uBAAuB,CAAC,IAAyB;IAC/D,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,EAAE,uBAAuB,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,cAAc;AACxC,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC5C,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IACtB,OAAO,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDimensionSpec — 健康度维度的可扩展接口。
|
|
3
|
+
*
|
|
4
|
+
* 用户写一个 spec 描述自己想加的维度,通过 registerHealthDimension(spec) 注册;
|
|
5
|
+
* composer 在拼 prompt 时把所有注册的 spec 的 promptSection 拼进"检查维度"区块,
|
|
6
|
+
* 同时把 spec.id 加入 dim_id 枚举,LLM 必须在 checklist 中按每个 id 出一项。
|
|
7
|
+
*
|
|
8
|
+
* 维度结果(level + findings)的解析按统一规则跑;若维度需要特殊解析(eg. 把
|
|
9
|
+
* subtype 作为 finding 一级字段),后续可以加可选 hooks(parseFindings 等)。
|
|
10
|
+
*/
|
|
11
|
+
import type { DoctorSeverity } from '../../types/doctor.js';
|
|
12
|
+
export type HealthDimensionLevel = '健康' | '亚健康' | '不健康' | '不适用';
|
|
13
|
+
export type HealthFindingLevel = '错误' | '警告' | '建议';
|
|
14
|
+
export interface HealthFinding {
|
|
15
|
+
level: HealthFindingLevel;
|
|
16
|
+
/** 仅 security 维度填(`不可逆操作 | 凭据硬编码 | 个人化耦合`),其它维度空字符串。
|
|
17
|
+
* 保留为通用字段,未来其它维度也可借此做子分类。 */
|
|
18
|
+
subtype?: string;
|
|
19
|
+
evidence: string;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HealthDimensionResult {
|
|
23
|
+
level: HealthDimensionLevel;
|
|
24
|
+
findings: HealthFinding[];
|
|
25
|
+
suggestions: string[];
|
|
26
|
+
/** 内部标记:LLM 没产出该维度时,parser 会塞一个 placeholder + _missing=true,
|
|
27
|
+
* composer 据此把 status 置为 skipped + message 提示 LLM 漏报。 */
|
|
28
|
+
_missing?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface HealthDimensionSpec {
|
|
31
|
+
/** 稳定的英文 id。会成为 prompt 里 dim_id 的取值,以及 ruleId 后缀。
|
|
32
|
+
* 不允许重复;不允许包含 `:`(避免和 composer ruleId 分隔符冲突)。 */
|
|
33
|
+
id: string;
|
|
34
|
+
/** 给人看的简短显示名(LLM prompt 里的章节标题用 + renderer 用)。
|
|
35
|
+
* 可以是中文或多语言混用,稳定性不重要(因为 LLM 通过 id 派发,不看 displayName)。 */
|
|
36
|
+
displayName: string;
|
|
37
|
+
/** i18n key,doctor result 的 labelKey 用。renderer 走 tCli 出最终标题。 */
|
|
38
|
+
labelKey: string;
|
|
39
|
+
/** 该维度的严重度。`不健康` → fail 时,severity=fatal 才会让 doctor outcome 进 `failed`。
|
|
40
|
+
* 内置 7 维度按重要性区分(eg. dependency/security 是 fatal, doc-clarity 是 warn)。 */
|
|
41
|
+
severity: DoctorSeverity;
|
|
42
|
+
/** 拼到 prompt 的检查内容片段(纯 markdown,**不写 ## N. 标题** —— 框架自动加章节号)。
|
|
43
|
+
* 描述这一维度查什么、判定标准、example。 */
|
|
44
|
+
promptSection: string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=dimension-spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimension-spec.d.ts","sourceRoot":"","sources":["../../../../src/doctor/health/dimension-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B;kCAC8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;8DAC0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC;sDACkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX;8DAC0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB;8EAC0E;IAC1E,QAAQ,EAAE,cAAc,CAAC;IACzB;kCAC8B;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDimensionSpec — 健康度维度的可扩展接口。
|
|
3
|
+
*
|
|
4
|
+
* 用户写一个 spec 描述自己想加的维度,通过 registerHealthDimension(spec) 注册;
|
|
5
|
+
* composer 在拼 prompt 时把所有注册的 spec 的 promptSection 拼进"检查维度"区块,
|
|
6
|
+
* 同时把 spec.id 加入 dim_id 枚举,LLM 必须在 checklist 中按每个 id 出一项。
|
|
7
|
+
*
|
|
8
|
+
* 维度结果(level + findings)的解析按统一规则跑;若维度需要特殊解析(eg. 把
|
|
9
|
+
* subtype 作为 finding 一级字段),后续可以加可选 hooks(parseFindings 等)。
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=dimension-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimension-spec.js","sourceRoot":"","sources":["../../../../src/doctor/health/dimension-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM 输出解析:JSON 提取 + dim_id 派发 + 容错。
|
|
3
|
+
*
|
|
4
|
+
* 流程:
|
|
5
|
+
* 1. extractJson 从 LLM 文本(可能带寒暄/code fence)抽出顶层 JSON。
|
|
6
|
+
* 2. 按 dim_id 把 checklist 项派发到 Map<dimId, HealthDimensionResult>。
|
|
7
|
+
* 3. 缺失维度(LLM 漏报)塞 _missing=true 的 placeholder,composer 据此 status=skipped。
|
|
8
|
+
* 4. 不合法 dim_id(LLM 编了 registry 之外的)直接丢弃。
|
|
9
|
+
*
|
|
10
|
+
* dimension_level_count / finding_count_by_level 由调用方 / renderer 自己算
|
|
11
|
+
* (LLM 算术不可靠,prompt 已声明不输出这两字段)。
|
|
12
|
+
*/
|
|
13
|
+
import type { HealthDimensionResult, HealthDimensionSpec } from './dimension-spec.js';
|
|
14
|
+
/** 深度跟踪 brace,在 LLM 文本里找出顶层 JSON。多个候选时优先选含 ≥2 required key 的。 */
|
|
15
|
+
export declare function extractJson(text: string): string | null;
|
|
16
|
+
export interface HealthParseResult {
|
|
17
|
+
byDimId: Map<string, HealthDimensionResult>;
|
|
18
|
+
/** LLM 给的 overall(框架视情况覆盖)。 */
|
|
19
|
+
overall: '良好' | '部分缺陷' | '严重缺陷' | null;
|
|
20
|
+
topSuggestions: string[];
|
|
21
|
+
/** 原样保留 feature_points,塞进 composer 主结果 detail。 */
|
|
22
|
+
featurePoints: unknown[];
|
|
23
|
+
}
|
|
24
|
+
/** 从 cleaned JSON 字串(extractJson 的输出)派发到各维度。 */
|
|
25
|
+
export declare function parseHealthOutput(jsonText: string, dims: HealthDimensionSpec[]): HealthParseResult;
|
|
26
|
+
export declare function deriveOverallHealth(byDimId: Map<string, HealthDimensionResult>): '良好' | '部分缺陷' | '严重缺陷';
|
|
27
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/doctor/health/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EAIpB,MAAM,qBAAqB,CAAC;AAS7B,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoDvD;AA0BD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC5C,+BAA+B;IAC/B,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kDAAkD;IAClD,aAAa,EAAE,OAAO,EAAE,CAAC;CAC1B;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,mBAAmB,EAAE,GAC1B,iBAAiB,CAuDnB;AAYD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAWvG"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM 输出解析:JSON 提取 + dim_id 派发 + 容错。
|
|
3
|
+
*
|
|
4
|
+
* 流程:
|
|
5
|
+
* 1. extractJson 从 LLM 文本(可能带寒暄/code fence)抽出顶层 JSON。
|
|
6
|
+
* 2. 按 dim_id 把 checklist 项派发到 Map<dimId, HealthDimensionResult>。
|
|
7
|
+
* 3. 缺失维度(LLM 漏报)塞 _missing=true 的 placeholder,composer 据此 status=skipped。
|
|
8
|
+
* 4. 不合法 dim_id(LLM 编了 registry 之外的)直接丢弃。
|
|
9
|
+
*
|
|
10
|
+
* dimension_level_count / finding_count_by_level 由调用方 / renderer 自己算
|
|
11
|
+
* (LLM 算术不可靠,prompt 已声明不输出这两字段)。
|
|
12
|
+
*/
|
|
13
|
+
const REQUIRED_TOP_KEYS = new Set(['skill_name', 'checklist', 'summary']);
|
|
14
|
+
/** 深度跟踪 brace,在 LLM 文本里找出顶层 JSON。多个候选时优先选含 ≥2 required key 的。 */
|
|
15
|
+
export function extractJson(text) {
|
|
16
|
+
if (!text)
|
|
17
|
+
return null;
|
|
18
|
+
const candidates = [];
|
|
19
|
+
let pos = 0;
|
|
20
|
+
while (true) {
|
|
21
|
+
const start = text.indexOf('{', pos);
|
|
22
|
+
if (start === -1)
|
|
23
|
+
break;
|
|
24
|
+
let depth = 0;
|
|
25
|
+
let inString = false;
|
|
26
|
+
let escape = false;
|
|
27
|
+
let end = -1;
|
|
28
|
+
for (let i = start; i < text.length; i += 1) {
|
|
29
|
+
const c = text[i];
|
|
30
|
+
if (escape) {
|
|
31
|
+
escape = false;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (inString) {
|
|
35
|
+
if (c === '\\')
|
|
36
|
+
escape = true;
|
|
37
|
+
else if (c === '"')
|
|
38
|
+
inString = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (c === '"')
|
|
42
|
+
inString = true;
|
|
43
|
+
else if (c === '{')
|
|
44
|
+
depth += 1;
|
|
45
|
+
else if (c === '}') {
|
|
46
|
+
depth -= 1;
|
|
47
|
+
if (depth === 0) {
|
|
48
|
+
end = i;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (end === -1)
|
|
54
|
+
break;
|
|
55
|
+
const cand = text.slice(start, end + 1);
|
|
56
|
+
try {
|
|
57
|
+
const obj = JSON.parse(cand);
|
|
58
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
59
|
+
candidates.push({ text: cand, obj: obj });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// skip non-parseable; brace inside string is the common reason
|
|
64
|
+
}
|
|
65
|
+
pos = start + 1;
|
|
66
|
+
}
|
|
67
|
+
if (candidates.length === 0)
|
|
68
|
+
return null;
|
|
69
|
+
const strong = candidates.filter((c) => {
|
|
70
|
+
let hits = 0;
|
|
71
|
+
for (const k of REQUIRED_TOP_KEYS)
|
|
72
|
+
if (k in c.obj)
|
|
73
|
+
hits += 1;
|
|
74
|
+
return hits >= 2;
|
|
75
|
+
});
|
|
76
|
+
if (strong.length > 0) {
|
|
77
|
+
strong.sort((a, b) => b.text.length - a.text.length);
|
|
78
|
+
return strong[0].text;
|
|
79
|
+
}
|
|
80
|
+
const keyed = candidates.filter((c) => 'skill_name' in c.obj);
|
|
81
|
+
const pool = keyed.length > 0 ? keyed : candidates;
|
|
82
|
+
pool.sort((a, b) => b.text.length - a.text.length);
|
|
83
|
+
return pool[0].text;
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Type guards & dispatch
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function isDimLevel(s) {
|
|
89
|
+
return s === '健康' || s === '亚健康' || s === '不健康' || s === '不适用';
|
|
90
|
+
}
|
|
91
|
+
function isFindingLevel(s) {
|
|
92
|
+
return s === '错误' || s === '警告' || s === '建议';
|
|
93
|
+
}
|
|
94
|
+
function asFinding(raw) {
|
|
95
|
+
if (!raw || typeof raw !== 'object')
|
|
96
|
+
return null;
|
|
97
|
+
const r = raw;
|
|
98
|
+
if (!isFindingLevel(r.level))
|
|
99
|
+
return null;
|
|
100
|
+
return {
|
|
101
|
+
level: r.level,
|
|
102
|
+
subtype: typeof r.subtype === 'string' ? r.subtype : '',
|
|
103
|
+
evidence: typeof r.evidence === 'string' ? r.evidence : '',
|
|
104
|
+
description: typeof r.description === 'string' ? r.description : '',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** 从 cleaned JSON 字串(extractJson 的输出)派发到各维度。 */
|
|
108
|
+
export function parseHealthOutput(jsonText, dims) {
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(jsonText);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
parsed = {};
|
|
115
|
+
}
|
|
116
|
+
const validIds = new Set(dims.map((d) => d.id));
|
|
117
|
+
const byDimId = new Map();
|
|
118
|
+
const checklist = Array.isArray(parsed.checklist) ? parsed.checklist : [];
|
|
119
|
+
for (const itemRaw of checklist) {
|
|
120
|
+
if (!itemRaw || typeof itemRaw !== 'object')
|
|
121
|
+
continue;
|
|
122
|
+
const item = itemRaw;
|
|
123
|
+
const dimId = item.dim_id;
|
|
124
|
+
if (typeof dimId !== 'string' || !validIds.has(dimId))
|
|
125
|
+
continue; // 容错:丢弃未注册 id
|
|
126
|
+
if (byDimId.has(dimId))
|
|
127
|
+
continue; // 容错:重复 id 取首条
|
|
128
|
+
const findingsRaw = Array.isArray(item.findings) ? item.findings : [];
|
|
129
|
+
const findings = [];
|
|
130
|
+
for (const fRaw of findingsRaw) {
|
|
131
|
+
const f = asFinding(fRaw);
|
|
132
|
+
if (f)
|
|
133
|
+
findings.push(f);
|
|
134
|
+
}
|
|
135
|
+
const suggestions = Array.isArray(item.suggestions)
|
|
136
|
+
? item.suggestions.filter((s) => typeof s === 'string')
|
|
137
|
+
: [];
|
|
138
|
+
const level = isDimLevel(item.level)
|
|
139
|
+
? item.level
|
|
140
|
+
: deriveDimLevel(findings);
|
|
141
|
+
byDimId.set(dimId, { level, findings, suggestions });
|
|
142
|
+
}
|
|
143
|
+
// 漏报维度填 placeholder
|
|
144
|
+
for (const dim of dims) {
|
|
145
|
+
if (!byDimId.has(dim.id)) {
|
|
146
|
+
byDimId.set(dim.id, { level: '不适用', findings: [], suggestions: [], _missing: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const summaryRaw = (parsed.summary && typeof parsed.summary === 'object'
|
|
150
|
+
? parsed.summary
|
|
151
|
+
: {});
|
|
152
|
+
const validOveralls = new Set(['良好', '部分缺陷', '严重缺陷']);
|
|
153
|
+
const overallRaw = summaryRaw.overall_health;
|
|
154
|
+
const overall = typeof overallRaw === 'string' && validOveralls.has(overallRaw)
|
|
155
|
+
? overallRaw
|
|
156
|
+
: null;
|
|
157
|
+
const topSuggestions = Array.isArray(summaryRaw.top_suggestions)
|
|
158
|
+
? summaryRaw.top_suggestions.filter((s) => typeof s === 'string')
|
|
159
|
+
: [];
|
|
160
|
+
const featurePoints = Array.isArray(parsed.feature_points) ? parsed.feature_points : [];
|
|
161
|
+
return { byDimId, overall, topSuggestions, featurePoints };
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Aggregation helpers
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
function deriveDimLevel(findings) {
|
|
167
|
+
if (findings.some((f) => f.level === '错误'))
|
|
168
|
+
return '不健康';
|
|
169
|
+
if (findings.length > 0)
|
|
170
|
+
return '亚健康';
|
|
171
|
+
return '健康';
|
|
172
|
+
}
|
|
173
|
+
export function deriveOverallHealth(byDimId) {
|
|
174
|
+
let hasUnhealthy = false;
|
|
175
|
+
let hasSubhealthy = false;
|
|
176
|
+
for (const r of byDimId.values()) {
|
|
177
|
+
if (r._missing)
|
|
178
|
+
continue; // 漏报不算
|
|
179
|
+
if (r.level === '不健康')
|
|
180
|
+
hasUnhealthy = true;
|
|
181
|
+
else if (r.level === '亚健康')
|
|
182
|
+
hasSubhealthy = true;
|
|
183
|
+
}
|
|
184
|
+
if (hasUnhealthy)
|
|
185
|
+
return '严重缺陷';
|
|
186
|
+
if (hasSubhealthy)
|
|
187
|
+
return '部分缺陷';
|
|
188
|
+
return '良好';
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=parser.js.map
|