oh-my-knowledge 0.44.0 → 0.46.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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/README.zh.md +1 -1
  3. package/dist/artifact-graph/doctor.js +1 -1
  4. package/dist/artifact-graph/eval.js +120 -0
  5. package/dist/assets/agent-skills/omk/SKILL.md +3 -3
  6. package/dist/assets/agent-skills/omk/references/commands.md +13 -6
  7. package/dist/cli/commands/doctor.d.ts +3 -2
  8. package/dist/cli/commands/doctor.js +66 -63
  9. package/dist/cli/lib/cmd-flags.d.ts +3 -2
  10. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  11. package/dist/cli/lib/i18n-dict/common.js +0 -4
  12. package/dist/doctor/endpoint-rule.js +1 -1
  13. package/dist/doctor/health/composer.js +162 -50
  14. package/dist/doctor/health/consensus.d.ts +43 -0
  15. package/dist/doctor/health/consensus.js +224 -0
  16. package/dist/doctor/health/dimension-spec.d.ts +6 -0
  17. package/dist/doctor/health/register.d.ts +0 -1
  18. package/dist/doctor/health/register.js +0 -1
  19. package/dist/doctor/index.js +26 -3
  20. package/dist/doctor/messages.d.ts +1 -1
  21. package/dist/doctor/messages.js +8 -0
  22. package/dist/doctor/rules.d.ts +4 -2
  23. package/dist/doctor/rules.js +4 -2
  24. package/dist/eval-core/evaluation-reporting.js +1 -0
  25. package/dist/executors/claude-cli.js +1 -1
  26. package/dist/inputs/load-samples.js +27 -0
  27. package/dist/observability/skill-chain-advisories.js +2 -2
  28. package/dist/renderer/doctor-detail-renderer.js +30 -2
  29. package/dist/renderer/report-shell.d.ts +1 -1
  30. package/dist/renderer/report-shell.js +5 -0
  31. package/dist/renderer/skill-detail-renderer.js +908 -94
  32. package/dist/renderer/skill-list-renderer.js +1 -1
  33. package/dist/server/skill-index.js +58 -1
  34. package/dist/shared/llm-prompts/registry.js +2 -1
  35. package/dist/shared/llm-prompts/skill-health-merge.d.ts +22 -0
  36. package/dist/shared/llm-prompts/skill-health-merge.js +79 -0
  37. package/dist/shared/llm-prompts/skill-health.js +1 -1
  38. package/dist/types/artifact-graph.d.ts +1 -1
  39. package/dist/types/doctor.d.ts +32 -8
  40. package/dist/types/eval.d.ts +10 -0
  41. package/dist/types/report.d.ts +3 -1
  42. package/dist/types/skill-index.d.ts +14 -0
  43. package/package.json +6 -5
@@ -65,6 +65,14 @@ export const DOCTOR_MESSAGES = {
65
65
  zh: '{overall} | 维度: 健康 {h}/亚健康 {sh}/不健康 {bad}/不适用 {na} | finding: 错误 {err}/警告 {warn}/建议 {sug}',
66
66
  en: '{overall} | dims: healthy {h}/sub {sh}/unhealthy {bad}/n-a {na} | findings: err {err}/warn {warn}/sug {sug}',
67
67
  },
68
+ 'cli.doctor.health.summary.samples': {
69
+ zh: ' | 采样 {succeeded}/{requested} 次(并集去重,finding 标注 k/n 支持度)',
70
+ en: ' | sampled {succeeded}/{requested} (unioned & deduped; findings tagged k/n support)',
71
+ },
72
+ 'cli.doctor.health.summary.samples_degraded': {
73
+ zh: ' | 采样仅成功解析 {succeeded}/{requested} 次(已降级,finding 的 k/n 只基于成功样本)',
74
+ en: ' | parsed only {succeeded}/{requested} samples (degraded; k/n support uses successful samples only)',
75
+ },
68
76
  'cli.doctor.health.summary.no_top': {
69
77
  zh: '完整详情见 --json 输出或 --html 报告',
70
78
  en: 'Full detail in --json output or --html report',
@@ -2,8 +2,10 @@
2
2
  * omk doctor 内置规则注册表。
3
3
  *
4
4
  * BUILTIN_RULES 是**纯静态/低成本检查**, 不碰 LLM 连通性 — executor / judge
5
- * 连通性由 evaluation preflight 负责。用户入口 `omk doctor` 默认还会跑
6
- * skill_health composer 做 LLM 审计;`--static-only` 只暴露这里的静态规则。
5
+ * 连通性由 evaluation preflight 负责。用户入口 `omk doctor` 默认会跑这些静态
6
+ * 规则,再跑 skill_health composer 做 LLM 审计;`--static-only` 只跑静态规则。
7
+ * samples_contract_aligned 需要 samples,仍只归 eval preflight
8
+ * (run-evaluation.ts 经 runDoctor 调,dependencies_present 守依赖完整性)。
7
9
  *
8
10
  * 每条 rule 回答一个独立的「skill 能不能被有意义评测」子问题:
9
11
  * - skill_readable: 文件能读、内容非空且有最小长度
@@ -2,8 +2,10 @@
2
2
  * omk doctor 内置规则注册表。
3
3
  *
4
4
  * BUILTIN_RULES 是**纯静态/低成本检查**, 不碰 LLM 连通性 — executor / judge
5
- * 连通性由 evaluation preflight 负责。用户入口 `omk doctor` 默认还会跑
6
- * skill_health composer 做 LLM 审计;`--static-only` 只暴露这里的静态规则。
5
+ * 连通性由 evaluation preflight 负责。用户入口 `omk doctor` 默认会跑这些静态
6
+ * 规则,再跑 skill_health composer 做 LLM 审计;`--static-only` 只跑静态规则。
7
+ * samples_contract_aligned 需要 samples,仍只归 eval preflight
8
+ * (run-evaluation.ts 经 runDoctor 调,dependencies_present 守依赖完整性)。
7
9
  *
8
10
  * 每条 rule 回答一个独立的「skill 能不能被有意义评测」子问题:
9
11
  * - skill_readable: 文件能读、内容非空且有最小长度
@@ -273,6 +273,7 @@ export function aggregateReport({ runId, variants, model, judgeModel, noJudge, e
273
273
  ...(s.difficulty ? { difficulty: s.difficulty } : {}),
274
274
  ...(s.construct ? { construct: s.construct } : {}),
275
275
  ...(s.provenance ? { provenance: s.provenance } : {}),
276
+ ...(s.covers && s.covers.length > 0 ? { covers: s.covers } : {}),
276
277
  ...(s.tripwire ? { tripwire: true } : {}),
277
278
  }])),
278
279
  };
@@ -40,7 +40,7 @@ export async function claudeCliExecutor({ model, system, prompt, cwd, skillDir,
40
40
  if (system)
41
41
  args.push('--system-prompt', system);
42
42
  // effort 决策:
43
- // - lean=true(纯文本生成):默认 'low',但允许调用方显式覆盖(如 doctor 对大 skill 需要 'medium')。
43
+ // - lean=true(纯文本生成):默认 'low',但允许调用方显式覆盖(如 doctor 用 --effort 调高)。
44
44
  // - 否则用调用方传入的 effort,或 sonnet 默认(不传 flag = claude CLI 自己定)。
45
45
  const effectiveEffort = lean ? (effort ?? 'low') : effort;
46
46
  if (lean) {
@@ -108,6 +108,16 @@ export function validateSamples(samples) {
108
108
  // Pure documentation/diagnostic fields; do NOT participate in grading/judge/verdict.
109
109
  const VALID_DIFFICULTY = new Set(['easy', 'medium', 'hard']);
110
110
  const VALID_PROVENANCE = new Set(['human', 'llm-generated', 'production-trace']);
111
+ const VALID_COVERAGE_TARGET_KIND = new Set([
112
+ 'skill',
113
+ 'skill_file',
114
+ 'frontmatter',
115
+ 'reference',
116
+ 'script',
117
+ 'hard_rule',
118
+ 'workflow',
119
+ 'workflow_node',
120
+ ]);
111
121
  for (const [i, sample] of samples.entries()) {
112
122
  if (!sample.sample_id || typeof sample.sample_id !== 'string') {
113
123
  throw new Error(`samples[${i}] missing or invalid required field: sample_id (must be a non-empty string)`);
@@ -135,6 +145,23 @@ export function validateSamples(samples) {
135
145
  if (sample.construct !== undefined && typeof sample.construct !== 'string') {
136
146
  throw new Error(`samples[${i}] (${sample.sample_id}) invalid construct: must be a string (got ${typeof sample.construct})`);
137
147
  }
148
+ if (sample.covers !== undefined) {
149
+ if (!Array.isArray(sample.covers)) {
150
+ throw new Error(`samples[${i}] (${sample.sample_id}) invalid covers: must be an array of coverage target objects`);
151
+ }
152
+ for (const [j, target] of sample.covers.entries()) {
153
+ if (!target || typeof target !== 'object' || Array.isArray(target)) {
154
+ throw new Error(`samples[${i}] (${sample.sample_id}) covers[${j}] must be an object with targetKind/ref`);
155
+ }
156
+ const record = target;
157
+ if (typeof record.targetKind !== 'string' || !VALID_COVERAGE_TARGET_KIND.has(record.targetKind)) {
158
+ throw new Error(`samples[${i}] (${sample.sample_id}) covers[${j}] invalid targetKind: ${JSON.stringify(record.targetKind)}, expected one of [${[...VALID_COVERAGE_TARGET_KIND].join(', ')}]`);
159
+ }
160
+ if (typeof record.ref !== 'string' || !record.ref.trim()) {
161
+ throw new Error(`samples[${i}] (${sample.sample_id}) covers[${j}] invalid ref: must be a non-empty string`);
162
+ }
163
+ }
164
+ }
138
165
  // tools_called / tools_not_called: values 必须非空。空 values 在 grader 里
139
166
  // 永远 passed=true 但 weight=0,是无意义占位,污染断言计数(N/M 看上去比真实
140
167
  // 通过率高)+ 让 generator 输出可观测的 garbage 沉淀到磁盘。直接拒掉,作者
@@ -49,8 +49,8 @@ const ADVISORIES = {
49
49
  skill_md_not_found: {
50
50
  code: 'skill_md_not_found',
51
51
  shortLabel: '本地未找到 SKILL.md',
52
- message: '本地常见 skill 目录里没有这个 skill 的 SKILL.md,inbox 跑不了静态/运行时审计。如果你想看更多体检维度(依赖检查、可读性等),可以试试 omk doctor;inbox 本身不依赖它。',
53
- commandTemplate: 'omk doctor ~/.claude/skills/${skillName}/SKILL.md --static-only',
52
+ message: '本地常见 skill 目录里没有这个 skill 的 SKILL.md,inbox 跑不了静态/运行时审计。如果你想看 LLM 健康度审计(触发边界 / 文档 / 依赖 / 安全等 7 维),可以试试 omk doctor;inbox 本身不依赖它。',
53
+ commandTemplate: 'omk doctor ~/.claude/skills/${skillName}/SKILL.md',
54
54
  },
55
55
  };
56
56
  export function getSkillChainAdvisory(code) {
@@ -46,8 +46,18 @@ function renderRuleCard(r, lang, forceOpen = false) {
46
46
  const mIcon = isErr
47
47
  ? svgIcon('x', { size: 14, style: 'color:#dc2626;vertical-align:-2px;margin-right:5px' })
48
48
  : svgIcon('warn', { size: 14, style: `color:${f.level === '警告' ? '#d97706' : '#637083'};vertical-align:-2px;margin-right:5px` });
49
+ // 多采样支持度 k/n:仅 n>1 时展示(单次采样无意义)。k<n 用偏灰提示"非每次都报"。
50
+ const sup = f.support;
51
+ const supportTitle = sup && typeof sup.n === 'number' && typeof sup.k === 'number'
52
+ ? (lang === 'zh'
53
+ ? `${sup.n} 次采样里有 ${sup.k} 次报了这条`
54
+ : `Reported by ${sup.k} of ${sup.n} samples`)
55
+ : '';
56
+ const supBadge = sup && typeof sup.n === 'number' && sup.n > 1 && typeof sup.k === 'number'
57
+ ? `<span class="rs-badge rs-badge--${sup.k >= sup.n ? 'pass' : 'warn'}" title="${e(supportTitle)}" style="margin-left:6px">${sup.k}/${sup.n}</span>`
58
+ : '';
49
59
  return `<div class="rs-finding rs-finding--${isErr ? 'err' : 'warn'}">
50
- <div class="rs-finding-desc">${mIcon}${e(f.description ?? '')}</div>
60
+ <div class="rs-finding-desc">${mIcon}${e(f.description ?? '')}${supBadge}</div>
51
61
  ${f.suggestion ? `<div class="rs-finding-sug">${svgIcon('bulb', { size: 13, style: 'vertical-align:-2px;margin-right:5px' })}${e(f.suggestion)}</div>` : ''}
52
62
  </div>`;
53
63
  }).join('');
@@ -56,7 +66,25 @@ function renderRuleCard(r, lang, forceOpen = false) {
56
66
  <div class="rs-card-body">${findingHtml}</div>
57
67
  </details>`;
58
68
  }
69
+ function renderSampleDegradedAlert(summary, lang) {
70
+ const samples = summary?.detail?.samples;
71
+ if (!samples || samples.degraded !== true)
72
+ return '';
73
+ const requested = typeof samples.requested === 'number' ? samples.requested : 0;
74
+ const succeeded = typeof samples.succeeded === 'number' ? samples.succeeded : 0;
75
+ if (requested <= 1 || succeeded >= requested)
76
+ return '';
77
+ const title = lang === 'zh' ? '共识置信降级' : 'Consensus confidence degraded';
78
+ const body = lang === 'zh'
79
+ ? `本次只成功解析 ${succeeded}/${requested} 次采样,finding 支持度仅基于成功样本。建议重跑,或在限流场景下降低并发。`
80
+ : `Only ${succeeded}/${requested} samples parsed successfully. Finding support is based on successful samples only. Re-run or lower concurrency if rate-limited.`;
81
+ return `<div class="rs-alert rs-alert--warn">
82
+ <div class="rs-alert-title">${e(title)}</div>
83
+ <div class="rs-alert-body">${e(body)}</div>
84
+ </div>`;
85
+ }
59
86
  function renderSkillBlock(sk, lang) {
87
+ const summary = sk.results.find((r) => r.ruleId.endsWith(':_summary'));
60
88
  const rules = sk.results.filter((r) => !r.ruleId.endsWith(':_summary'));
61
89
  const pass = rules.filter((r) => r.status === 'pass').length;
62
90
  const warn = rules.filter((r) => r.status === 'warn').length;
@@ -78,7 +106,7 @@ function renderSkillBlock(sk, lang) {
78
106
  const oksHtml = oks.length
79
107
  ? `<div class="rs-panel"><details class="rs-fold"><summary>${zh ? `展开 ${oks.length} 条通过的规则` : `Show ${oks.length} passed rules`}</summary>${oks.map((r) => renderRuleCard(r, lang)).join('')}</details></div>`
80
108
  : '';
81
- return stats + issuesHtml + oksHtml;
109
+ return renderSampleDegradedAlert(summary, lang) + stats + issuesHtml + oksHtml;
82
110
  }
83
111
  export function renderDoctorDetail(report, skillName, langQ, lang = DEFAULT_LANG, skillContext) {
84
112
  const zh = lang === 'zh';
@@ -74,4 +74,4 @@ export interface ReportShellOpts {
74
74
  extraCss?: string;
75
75
  }
76
76
  export declare function reportShell(opts: ReportShellOpts, lang?: Lang): string;
77
- export declare const REPORT_SHELL_CSS = "\n/* \u2500\u2500 \u9762\u5305\u5C51(\u66FF\u6389\u88F8\u8FD4\u56DE\u94FE\u63A5) \u2500\u2500 */\n.rs-crumb{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:13px;color:#637083}\n.rs-crumb-link{display:inline-flex;align-items:center;gap:6px;color:#637083;text-decoration:none}\n.rs-crumb-link:hover{color:#4f46e5;text-decoration:none}\n.rs-crumb-link svg{color:#9ca3af}\n.rs-crumb-link:hover svg{color:#4f46e5}\n.rs-crumb-sep{color:#c0c6d2}\n.rs-crumb-dim{color:#637083}\n.rs-crumb-cur{color:#182033;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-crumb-spacer{flex:1}\n.rs-crumb-back{display:inline-flex;align-items:center;gap:5px;color:#9ca3af;text-decoration:none;flex-shrink:0}\n.rs-crumb-back:hover{color:#4f46e5;text-decoration:none}\n\n/* \u2500\u2500 skill \u7EF4\u5EA6\u5207\u6362\u5668(doctor/eval/observe \u4E92\u94FE)\u2014 \u5206\u6BB5\u63A7\u4EF6\u98CE \u2500\u2500 */\n.rs-dims{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 16px}\n.rs-dims-seg{display:inline-flex;align-items:stretch;background:#fff;border:1px solid #e4e8f1;border-radius:10px;padding:3px;gap:2px;box-shadow:0 1px 2px rgba(31,41,55,3%)}\n.rs-dim{display:inline-flex;align-items:center;gap:7px;font-size:13px;padding:7px 14px;border-radius:7px;color:#637083;text-decoration:none;white-space:nowrap;transition:background .12s,color .12s}\na.rs-dim:hover{background:#f1f4f9;color:#182033}\n.rs-dim-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.rs-dim-lbl{font-weight:500}\n/* \u5F53\u524D\u7EF4\u5EA6:\u6D45\u586B\u5145 + \u72B6\u6001\u70B9\u5DF2\u8868\u660E\u8EAB\u4EFD,\u6587\u5B57\u52A0\u91CD */\n.rs-dim--active{background:#f1f4f9;color:#182033;cursor:default}\n.rs-dim--active .rs-dim-lbl{font-weight:700}\n.rs-dim--empty{color:#b6bcc8;cursor:default}\n/* \u5168\u90E8\u5386\u53F2\u6309\u94AE */\n.rs-hist-btn{display:inline-flex;align-items:center;gap:6px;font-size:13px;padding:7px 13px;border-radius:9px;border:1px solid #e4e8f1;background:#fff;color:#637083;cursor:pointer;white-space:nowrap;font-family:inherit;transition:border-color .15s,color .15s}\n.rs-hist-btn:hover{border-color:#c7cedb;color:#182033}\n.rs-hist-ct{font-variant-numeric:tabular-nums;font-weight:700;color:#9ca3af;font-size:12px}\n/* \u5168\u90E8\u5386\u53F2\u5F39\u6846 */\n.rs-hist-overlay{display:none;position:fixed;inset:0;background:rgba(24,32,51,.32);z-index:50;align-items:flex-start;justify-content:center;padding:8vh 16px}\n.rs-hist-modal{background:#fff;border-radius:14px;box-shadow:0 24px 60px rgba(31,41,55,.22);width:100%;max-width:520px;max-height:76vh;display:flex;flex-direction:column;overflow:hidden}\n.rs-hist-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #eef1f6;font-size:14px;color:#182033}\n.rs-hist-x{background:transparent;border:none;color:#9ca3af;font-size:16px;cursor:pointer;line-height:1;padding:4px}\n.rs-hist-x:hover{color:#182033}\n.rs-hist-list{overflow-y:auto;padding:6px 10px 12px}\n.rs-hist-group{margin-top:8px}\n.rs-hist-group-h{display:flex;align-items:center;gap:7px;font-size:12px;color:#637083;font-weight:600;padding:8px 8px 6px}\n.rs-hist-group-dot{width:7px;height:7px;border-radius:50%}\n.rs-hist-group-h small{font-weight:500;color:#b6bcc8}\n.rs-hist-row{display:flex;align-items:baseline;gap:14px;padding:8px;border-radius:8px;text-decoration:none;color:#182033;font-size:13px}\n.rs-hist-row:hover{background:#f1f4f9}\n.rs-hist-row--cur{background:#f1f4f9;cursor:default}\n.rs-hist-date{font-variant-numeric:tabular-nums;color:#637083;min-width:88px;flex-shrink:0}\n.rs-hist-score{font-weight:700;font-variant-numeric:tabular-nums;font-family:\"SF Mono\",Menlo,monospace;min-width:72px;flex-shrink:0}\n.rs-hist-score--green{color:#1f9d63}.rs-hist-score--yellow{color:#d97706}.rs-hist-score--red{color:#dc2626}.rs-hist-score--gray{color:#9ca3af}\n.rs-hist-meta{flex:1;min-width:0;color:#9ca3af;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-hist-go{color:#c0c6d2;font-size:13px;flex-shrink:0}\n.rs-hist-row:hover .rs-hist-go{color:#637083}\n.rs-hist-row--cur .rs-hist-go{color:#4f46e5;font-weight:600;font-size:12px}\n\n/* \u2500\u2500 Hero \u2500\u2500 */\n.rs-hero{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:20px 24px;background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}\n@media(max-width:640px){.rs-hero{flex-direction:column;align-items:flex-start}}\n.rs-hero-left{display:flex;flex-direction:column;gap:4px;min-width:0}\n.rs-hero-kind{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:#637083}\n.rs-hero-icon{display:inline-flex;align-items:center}\n.rs-hero-name{margin:0;font-size:20px;font-weight:700;color:#182033;letter-spacing:-.2px}\n.rs-hero-meta{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:4px;color:#9ca3af;font-size:12px;font-variant-numeric:tabular-nums}\n.rs-hero-meta span{display:inline-flex;align-items:center;gap:4px}\n.rs-hero-score{display:flex;flex-direction:column;align-items:flex-end;gap:2px;flex-shrink:0;line-height:1}\n.rs-hero-num{font-size:40px;font-weight:800;letter-spacing:-1.5px;font-variant-numeric:tabular-nums;line-height:1}\n.rs-hero-grade{font-size:12px;font-weight:600;color:#637083}\n\n/* \u2500\u2500 \u7EDF\u8BA1\u6761 \u2500\u2500 */\n.rs-stats{display:flex;flex-wrap:wrap;gap:20px;padding:14px 20px;background:#fff;border:1px solid #e4e8f1;border-radius:10px;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}\n.rs-stat{display:flex;align-items:baseline;gap:6px}\n.rs-stat-num{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.rs-stat-label{font-size:12px;color:#637083}\n.rs-stat--pass .rs-stat-num{color:#1f9d63}\n.rs-stat--warn .rs-stat-num{color:#d97706}\n.rs-stat--fail .rs-stat-num{color:#dc2626}\n.rs-stat--total .rs-stat-num{color:#182033}\n\n/* \u2500\u2500 Panel\uFF08\u5185\u5BB9\u533A\u5757\uFF09 \u2500\u2500 */\n.rs-panel{background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);padding:18px 20px;margin-bottom:16px}\n.rs-panel-title{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:700;color:#182033;margin-bottom:14px}\n.rs-panel-title small{font-size:12px;font-weight:400;color:#9ca3af;margin-left:4px}\n\n/* \u2500\u2500 \u89C4\u5219 / \u7528\u4F8B:\u9762\u677F\u5185\u7684 hairline \u5217\u8868\u9879(\u65E0 tint \u76D2\u5B50,\u72B6\u6001\u9760\u5DE6\u4FA7\u8272\u70B9) \u2500\u2500 */\n.rs-card{border-bottom:1px solid #eef1f6;background:transparent;border-radius:8px;transition:background .12s}\n.rs-card:last-child{border-bottom:none}\ndetails.rs-card:not([open]):hover{background:#f9fafc}\ndetails.rs-card[open]{background:#fafbfd}\n.rs-card-head{display:flex;align-items:center;gap:10px;padding:12px 6px;cursor:pointer;list-style:none}\n.rs-card-head::-webkit-details-marker{display:none}\ndetails.rs-card>.rs-card-head::after{content:'\u203A';margin-left:4px;color:#c0c6d2;font-size:16px;transition:transform .15s;flex-shrink:0}\ndetails.rs-card[open]>.rs-card-head::after{transform:rotate(90deg)}\n.rs-card-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.rs-card-dot--fail{background:#dc2626}\n.rs-card-dot--warn{background:#d97706}\n.rs-card-dot--pass{background:#1f9d63}\n.rs-card-dot--skip{background:#cbd2dd}\n.rs-card-name{flex:1;min-width:0;font-size:14px;font-weight:600;color:#182033;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-card-badges{display:flex;gap:6px;flex-shrink:0}\n.rs-badge{font-size:11px;font-weight:600;padding:1px 8px;border-radius:10px}\n.rs-badge--fail{color:#dc2626;background:rgba(220,38,38,10%)}\n.rs-badge--warn{color:#d97706;background:rgba(217,119,6,10%)}\n.rs-badge--pass{color:#1f9d63;background:rgba(31,157,99,10%)}\n.rs-card-body{padding:0 6px 14px 24px}\n.rs-finding{margin-top:10px;font-size:13.5px;line-height:1.65}\n.rs-finding-desc{color:#374151;word-break:break-word}\n.rs-finding-sug{margin-top:6px;padding:8px 12px;background:rgba(79,70,229,.05);border-radius:6px;color:#4f46e5;font-size:12.5px;line-height:1.6;word-break:break-word}\n\n/* \u2500\u2500 \u6298\u53E0\u901A\u8FC7\u9879 \u2500\u2500 */\n.rs-fold{margin-top:10px}\n.rs-fold>summary{cursor:pointer;list-style:none;font-size:12.5px;color:#637083;padding:6px 0}\n.rs-fold>summary::-webkit-details-marker{display:none}\n.rs-fold>summary:hover{color:#182033}\n.rs-fold[open]>summary{margin-bottom:8px}\n\n/* \u2500\u2500 \u7A7A\u6001 \u2500\u2500 */\n.rs-empty{padding:32px 20px;text-align:center;color:#9ca3af;font-size:13px}\n";
77
+ export declare const REPORT_SHELL_CSS = "\n/* \u2500\u2500 \u9762\u5305\u5C51(\u66FF\u6389\u88F8\u8FD4\u56DE\u94FE\u63A5) \u2500\u2500 */\n.rs-crumb{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:13px;color:#637083}\n.rs-crumb-link{display:inline-flex;align-items:center;gap:6px;color:#637083;text-decoration:none}\n.rs-crumb-link:hover{color:#4f46e5;text-decoration:none}\n.rs-crumb-link svg{color:#9ca3af}\n.rs-crumb-link:hover svg{color:#4f46e5}\n.rs-crumb-sep{color:#c0c6d2}\n.rs-crumb-dim{color:#637083}\n.rs-crumb-cur{color:#182033;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-crumb-spacer{flex:1}\n.rs-crumb-back{display:inline-flex;align-items:center;gap:5px;color:#9ca3af;text-decoration:none;flex-shrink:0}\n.rs-crumb-back:hover{color:#4f46e5;text-decoration:none}\n\n/* \u2500\u2500 skill \u7EF4\u5EA6\u5207\u6362\u5668(doctor/eval/observe \u4E92\u94FE)\u2014 \u5206\u6BB5\u63A7\u4EF6\u98CE \u2500\u2500 */\n.rs-dims{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 16px}\n.rs-dims-seg{display:inline-flex;align-items:stretch;background:#fff;border:1px solid #e4e8f1;border-radius:10px;padding:3px;gap:2px;box-shadow:0 1px 2px rgba(31,41,55,3%)}\n.rs-dim{display:inline-flex;align-items:center;gap:7px;font-size:13px;padding:7px 14px;border-radius:7px;color:#637083;text-decoration:none;white-space:nowrap;transition:background .12s,color .12s}\na.rs-dim:hover{background:#f1f4f9;color:#182033}\n.rs-dim-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.rs-dim-lbl{font-weight:500}\n/* \u5F53\u524D\u7EF4\u5EA6:\u6D45\u586B\u5145 + \u72B6\u6001\u70B9\u5DF2\u8868\u660E\u8EAB\u4EFD,\u6587\u5B57\u52A0\u91CD */\n.rs-dim--active{background:#f1f4f9;color:#182033;cursor:default}\n.rs-dim--active .rs-dim-lbl{font-weight:700}\n.rs-dim--empty{color:#b6bcc8;cursor:default}\n/* \u5168\u90E8\u5386\u53F2\u6309\u94AE */\n.rs-hist-btn{display:inline-flex;align-items:center;gap:6px;font-size:13px;padding:7px 13px;border-radius:9px;border:1px solid #e4e8f1;background:#fff;color:#637083;cursor:pointer;white-space:nowrap;font-family:inherit;transition:border-color .15s,color .15s}\n.rs-hist-btn:hover{border-color:#c7cedb;color:#182033}\n.rs-hist-ct{font-variant-numeric:tabular-nums;font-weight:700;color:#9ca3af;font-size:12px}\n/* \u5168\u90E8\u5386\u53F2\u5F39\u6846 */\n.rs-hist-overlay{display:none;position:fixed;inset:0;background:rgba(24,32,51,.32);z-index:50;align-items:flex-start;justify-content:center;padding:8vh 16px}\n.rs-hist-modal{background:#fff;border-radius:14px;box-shadow:0 24px 60px rgba(31,41,55,.22);width:100%;max-width:520px;max-height:76vh;display:flex;flex-direction:column;overflow:hidden}\n.rs-hist-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #eef1f6;font-size:14px;color:#182033}\n.rs-hist-x{background:transparent;border:none;color:#9ca3af;font-size:16px;cursor:pointer;line-height:1;padding:4px}\n.rs-hist-x:hover{color:#182033}\n.rs-hist-list{overflow-y:auto;padding:6px 10px 12px}\n.rs-hist-group{margin-top:8px}\n.rs-hist-group-h{display:flex;align-items:center;gap:7px;font-size:12px;color:#637083;font-weight:600;padding:8px 8px 6px}\n.rs-hist-group-dot{width:7px;height:7px;border-radius:50%}\n.rs-hist-group-h small{font-weight:500;color:#b6bcc8}\n.rs-hist-row{display:flex;align-items:baseline;gap:14px;padding:8px;border-radius:8px;text-decoration:none;color:#182033;font-size:13px}\n.rs-hist-row:hover{background:#f1f4f9}\n.rs-hist-row--cur{background:#f1f4f9;cursor:default}\n.rs-hist-date{font-variant-numeric:tabular-nums;color:#637083;min-width:88px;flex-shrink:0}\n.rs-hist-score{font-weight:700;font-variant-numeric:tabular-nums;font-family:\"SF Mono\",Menlo,monospace;min-width:72px;flex-shrink:0}\n.rs-hist-score--green{color:#1f9d63}.rs-hist-score--yellow{color:#d97706}.rs-hist-score--red{color:#dc2626}.rs-hist-score--gray{color:#9ca3af}\n.rs-hist-meta{flex:1;min-width:0;color:#9ca3af;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-hist-go{color:#c0c6d2;font-size:13px;flex-shrink:0}\n.rs-hist-row:hover .rs-hist-go{color:#637083}\n.rs-hist-row--cur .rs-hist-go{color:#4f46e5;font-weight:600;font-size:12px}\n\n/* \u2500\u2500 Hero \u2500\u2500 */\n.rs-hero{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:20px 24px;background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}\n@media(max-width:640px){.rs-hero{flex-direction:column;align-items:flex-start}}\n.rs-hero-left{display:flex;flex-direction:column;gap:4px;min-width:0}\n.rs-hero-kind{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:#637083}\n.rs-hero-icon{display:inline-flex;align-items:center}\n.rs-hero-name{margin:0;font-size:20px;font-weight:700;color:#182033;letter-spacing:-.2px}\n.rs-hero-meta{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:4px;color:#9ca3af;font-size:12px;font-variant-numeric:tabular-nums}\n.rs-hero-meta span{display:inline-flex;align-items:center;gap:4px}\n.rs-hero-score{display:flex;flex-direction:column;align-items:flex-end;gap:2px;flex-shrink:0;line-height:1}\n.rs-hero-num{font-size:40px;font-weight:800;letter-spacing:-1.5px;font-variant-numeric:tabular-nums;line-height:1}\n.rs-hero-grade{font-size:12px;font-weight:600;color:#637083}\n\n/* \u2500\u2500 \u7EDF\u8BA1\u6761 \u2500\u2500 */\n.rs-stats{display:flex;flex-wrap:wrap;gap:20px;padding:14px 20px;background:#fff;border:1px solid #e4e8f1;border-radius:10px;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}\n.rs-stat{display:flex;align-items:baseline;gap:6px}\n.rs-stat-num{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.rs-stat-label{font-size:12px;color:#637083}\n.rs-stat--pass .rs-stat-num{color:#1f9d63}\n.rs-stat--warn .rs-stat-num{color:#d97706}\n.rs-stat--fail .rs-stat-num{color:#dc2626}\n.rs-stat--total .rs-stat-num{color:#182033}\n\n/* \u2500\u2500 \u63D0\u793A\u6761 \u2500\u2500 */\n.rs-alert{display:flex;flex-direction:column;gap:3px;padding:12px 16px;border:1px solid #f5c26b;border-radius:10px;background:#fff8ed;color:#92400e;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}\n.rs-alert-title{font-size:13px;font-weight:800}\n.rs-alert-body{font-size:12.5px;line-height:1.65}\n\n/* \u2500\u2500 Panel\uFF08\u5185\u5BB9\u533A\u5757\uFF09 \u2500\u2500 */\n.rs-panel{background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);padding:18px 20px;margin-bottom:16px}\n.rs-panel-title{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:700;color:#182033;margin-bottom:14px}\n.rs-panel-title small{font-size:12px;font-weight:400;color:#9ca3af;margin-left:4px}\n\n/* \u2500\u2500 \u89C4\u5219 / \u7528\u4F8B:\u9762\u677F\u5185\u7684 hairline \u5217\u8868\u9879(\u65E0 tint \u76D2\u5B50,\u72B6\u6001\u9760\u5DE6\u4FA7\u8272\u70B9) \u2500\u2500 */\n.rs-card{border-bottom:1px solid #eef1f6;background:transparent;border-radius:8px;transition:background .12s}\n.rs-card:last-child{border-bottom:none}\ndetails.rs-card:not([open]):hover{background:#f9fafc}\ndetails.rs-card[open]{background:#fafbfd}\n.rs-card-head{display:flex;align-items:center;gap:10px;padding:12px 6px;cursor:pointer;list-style:none}\n.rs-card-head::-webkit-details-marker{display:none}\ndetails.rs-card>.rs-card-head::after{content:'\u203A';margin-left:4px;color:#c0c6d2;font-size:16px;transition:transform .15s;flex-shrink:0}\ndetails.rs-card[open]>.rs-card-head::after{transform:rotate(90deg)}\n.rs-card-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.rs-card-dot--fail{background:#dc2626}\n.rs-card-dot--warn{background:#d97706}\n.rs-card-dot--pass{background:#1f9d63}\n.rs-card-dot--skip{background:#cbd2dd}\n.rs-card-name{flex:1;min-width:0;font-size:14px;font-weight:600;color:#182033;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rs-card-badges{display:flex;gap:6px;flex-shrink:0}\n.rs-badge{font-size:11px;font-weight:600;padding:1px 8px;border-radius:10px}\n.rs-badge--fail{color:#dc2626;background:rgba(220,38,38,10%)}\n.rs-badge--warn{color:#d97706;background:rgba(217,119,6,10%)}\n.rs-badge--pass{color:#1f9d63;background:rgba(31,157,99,10%)}\n.rs-card-body{padding:0 6px 14px 24px}\n.rs-finding{margin-top:10px;font-size:13.5px;line-height:1.65}\n.rs-finding-desc{color:#374151;word-break:break-word}\n.rs-finding-sug{margin-top:6px;padding:8px 12px;background:rgba(79,70,229,.05);border-radius:6px;color:#4f46e5;font-size:12.5px;line-height:1.6;word-break:break-word}\n\n/* \u2500\u2500 \u6298\u53E0\u901A\u8FC7\u9879 \u2500\u2500 */\n.rs-fold{margin-top:10px}\n.rs-fold>summary{cursor:pointer;list-style:none;font-size:12.5px;color:#637083;padding:6px 0}\n.rs-fold>summary::-webkit-details-marker{display:none}\n.rs-fold>summary:hover{color:#182033}\n.rs-fold[open]>summary{margin-bottom:8px}\n\n/* \u2500\u2500 \u7A7A\u6001 \u2500\u2500 */\n.rs-empty{padding:32px 20px;text-align:center;color:#9ca3af;font-size:13px}\n";
@@ -181,6 +181,11 @@ a.rs-dim:hover{background:#f1f4f9;color:#182033}
181
181
  .rs-stat--fail .rs-stat-num{color:#dc2626}
182
182
  .rs-stat--total .rs-stat-num{color:#182033}
183
183
 
184
+ /* ── 提示条 ── */
185
+ .rs-alert{display:flex;flex-direction:column;gap:3px;padding:12px 16px;border:1px solid #f5c26b;border-radius:10px;background:#fff8ed;color:#92400e;box-shadow:0 8px 24px rgba(31,41,55,4%);margin-bottom:16px}
186
+ .rs-alert-title{font-size:13px;font-weight:800}
187
+ .rs-alert-body{font-size:12.5px;line-height:1.65}
188
+
184
189
  /* ── Panel(内容区块) ── */
185
190
  .rs-panel{background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);padding:18px 20px;margin-bottom:16px}
186
191
  .rs-panel-title{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:700;color:#182033;margin-bottom:14px}