/** * Skill-centric 聚合 DTO 与 Insight DTO。 * * Studio 的 list / detail 视图层(renderer/skill-*-renderer)只依赖这些稳定形状, * 不直接 import server 装配层。runtime 函数(buildSkillIndex / detectInsights 等) * 仍在 src/server/skill-index.ts 与 src/server/skill-insights.ts。 */ import type { Diagnosis, StudioDiagnosisSummary } from './diagnosis.js'; import type { DoctorRuleResult, DoctorSkillStatus } from './doctor.js'; export interface SkillDoctorSnapshot { reportId: string; timestamp: string; status: DoctorSkillStatus; passCount: number; warnCount: number; failCount: number; results: DoctorRuleResult[]; } export interface SkillEvalSnapshot { reportId: string; timestamp: string; variantName: string; verdictLevel: string; verdictHeadline: string; compositeScore: number | null; passCount: number; failCount: number; tripwireCount: number; totalSamples: number; /** 该快照来自别项目的「索引卡片」(results 被剥掉,逐样本断言分布不可得),passCount/failCount 留 0 * 仅占位、不代表真实通过率。渲染端据此用占位替代 0% pass / 全通过 / 0-of-N 等会误读的派生量。 */ resultsStripped?: boolean; } export interface SkillObserveSnapshot { analysisId: string; generatedAt: string; healthBand: 'green' | 'yellow' | 'red'; failureRate: number; toolCallCount?: number; toolResolvedCount?: number; toolCancelledCount?: number; toolUnknownCount?: number; segmentCount: number; gapRate: number; stability?: 'stable' | 'unstable' | 'very-unstable' | 'unknown'; /** 统计可信度(按 segment 数)。underpowered 时下游 insight / card 不应触发硬红或 high severity。 * 历史快照缺此字段时由 segmentCount 兜底推导。 */ confidence: 'high' | 'low' | 'underpowered'; } export interface SkillGraphStageSnapshot { sourceKind: 'doctor' | 'eval' | 'observe'; sourceId: string; graphId: string; generatedAt: string; graphPath?: string; nodeCount: number; edgeCount: number; } export interface SkillGraphNodePreview { stableKey?: string; nodeKind: string; label: string; status?: string; /** Studio evidence view 用于把 assertion / diagnostic 归到对应 sample。 */ parentSampleStableKey?: string; coverage?: 'declared' | 'undeclared'; coveredBySamples?: string[]; } export interface SkillGraphCoverageEdgePreview { sampleStableKey?: string; sampleLabel: string; sampleStatus?: string; targetStableKey: string; targetNodeKind: string; targetLabel: string; } export interface SkillGraphSnapshot { /** Studio 聚合 graph sidecar 时实际采用的绑定强度。 */ bindingStrength: 'content-hash' | 'source-locator' | 'name-only' | 'mixed'; artifactHash?: string; sourceLocator?: string; doctor?: SkillGraphStageSnapshot & { references: number; scripts: number; workflows: number; workflowNodes: number; hardRules: number; definitionNodes: SkillGraphNodePreview[]; }; eval?: SkillGraphStageSnapshot & { variantName?: string; samples: number; assertions: number; failedAssertionEdges: number; diagnostics: number; measurementNodes: SkillGraphNodePreview[]; coverageEdges: number; declaredCoverageStableKeys: string[]; declaredCoverageEdges: SkillGraphCoverageEdgePreview[]; }; } export interface SkillIndexEntry { skillName: string; /** 当前(最新)snapshot — 等价于对应 history 的最后一项,空时为 null。renderer * 老路径直接读这个,不必动 history。 */ doctor: SkillDoctorSnapshot | null; eval: SkillEvalSnapshot | null; observe: SkillObserveSnapshot | null; /** 历史 snapshot,chronological 升序(最早 → 最近)。renderer 用它画 sparkline 趋势。 */ doctorHistory: SkillDoctorSnapshot[]; evalHistory: SkillEvalSnapshot[]; observeHistory: SkillObserveSnapshot[]; /** 综合健康灯。doctor / eval / observe 任一红 → red;任一黄 → yellow; * 全绿 → green;皆未跑 → gray。 */ band: 'green' | 'yellow' | 'red' | 'gray'; /** doctor/eval/observe graph sidecar 的轻量 Studio 投影。 */ graph?: SkillGraphSnapshot; } export interface SkillIndexSummary { totalSkills: number; withEval: number; withObserve: number; withDoctor: number; red: number; yellow: number; green: number; gray: number; } export interface SkillIndex { entries: SkillIndexEntry[]; summary: SkillIndexSummary; /** detectInsights 结果按 skillName 索引。在 buildSkillIndex 时同步算好,跟 SkillIndex * 本身共享同一 fingerprint 缓存(reports 数组 + dir mtime 任一变就 invalidate)。 * list 页 N×detectInsights 重算的 CPU 开销由此消除。 */ insightsBySkill: Map; diagnosticsBySkill: Map; diagnosisSummary: StudioDiagnosisSummary; } export type InsightCategory = 'environment-blocked-mocks' | 'skill-doc-gap' | 'failure-mode-skill' | 'coverage-gap' | 'production-instability' | 'skill-too-long' | 'omk-doctor-blindspot' | 'other'; export type InsightSeverity = 'high' | 'medium' | 'low'; export type InsightAudience = 'skill-author' | 'sample-author' | 'omk-maintainer'; export type InsightPerspective = 'doctor' | 'eval-score' | 'eval-functional' | 'observe'; /** 现象证据:把抽象"X 模式 N 条"翻译成用户能看到的具体行为。 */ export interface InsightIllustration { sampleId: string; /** 用户给 LLM 的 prompt(截断到 ~200 字)。 */ samplePrompt?: string; /** LLM 最终输出(截断到 ~200 字)。 */ llmOutput?: string; /** LLM 实际调的工具(简化字符串列表,顺序保留)。 */ toolCalls?: string[]; /** 哪条 assertion 没过 + value。 */ failedAssertion?: string; } export interface InsightEvidence { perspective: InsightPerspective; status: 'flagged' | 'blind' | 'silent' | 'na'; /** 简短说明(用户语,不堆术语)。 */ message: string; ref?: string; /** 如果有现象证据,展开看 1-2 条具体 sample 的实际 prompt/output/toolCalls。 */ illustrations?: InsightIllustration[]; } /** 可粘贴的 patch 片段。target 指明改哪种文件,location 是文件/章节,snippet 是代码块。 */ export interface InsightPatch { target: 'skill' | 'sample-environment' | 'sample-mocks' | 'doctor-rule'; /** 目标位置描述,如 'sample s003 的 mocks 数组' / 'SKILL.md「项目创建」节'。 */ location: string; /** 代码块或 diff 片段。 */ snippet: string; } export interface InsightRecommendation { action: string; priority: InsightSeverity; patch?: InsightPatch; } /** 该 insight 关联的具体阶段元素 — UI 渲染 timeline 时用来在阶段卡内挂 #N 徽章。 */ export interface InsightStageRefs { doctorRuleIds?: string[]; evalSampleIds?: string[]; /** observe 信号类型标签:'high-failure-rate' / 'gap' / 'uncovered-files'。 * observe 内单条信号没像 doctor rule / eval sample 那么细的 id,用类型标即可。 */ observeRefs?: string[]; } export interface Insight { id: string; category: InsightCategory; audience: InsightAudience; /** 用户语标题。 */ title: string; /** 一句话现象描述(标题之外的细节)。 */ description?: string; severity: InsightSeverity; affectedCount: number; evidence: InsightEvidence[]; recommendations: InsightRecommendation[]; /** 关联到 timeline 哪些阶段元素 — renderer 据此在阶段卡内插入 #N 徽章。 */ stageRefs?: InsightStageRefs; } export interface DetectInsightsOptions { /** * Diagnosis 是 observe 侧迁移后的主数据源。传入该字段时,observe 相关 insight * 从 Diagnosis 投影,不再从 entry.observe 重复推断,避免维护者新增规则时双写。 */ diagnostics?: Diagnosis[]; }