mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 06:15:51 +00:00
feat: 添加 skill learning 技能学习闭环系统
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
174
src/services/skillLearning/evolution.ts
Normal file
174
src/services/skillLearning/evolution.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { Instinct } from './instinctParser.js'
|
||||
import { shouldGenerateSkillFromInstincts } from './learningPolicy.js'
|
||||
import {
|
||||
generateSkillDraft,
|
||||
type SkillGeneratorOptions,
|
||||
} from './skillGenerator.js'
|
||||
import {
|
||||
generateCommandDraft,
|
||||
type CommandGeneratorOptions,
|
||||
type LearnedCommandDraft,
|
||||
} from './commandGenerator.js'
|
||||
import {
|
||||
generateAgentDraft,
|
||||
type AgentGeneratorOptions,
|
||||
type LearnedAgentDraft,
|
||||
} from './agentGenerator.js'
|
||||
import { getSkillLearningConfig } from './config.js'
|
||||
import type { LearnedSkillDraft } from './types.js'
|
||||
|
||||
export type EvolutionCandidate = {
|
||||
target: 'skill' | 'command' | 'agent'
|
||||
trigger: string
|
||||
domain: string
|
||||
instincts: Instinct[]
|
||||
averageConfidence: number
|
||||
}
|
||||
|
||||
export type LearnedArtifactDraft =
|
||||
| { kind: 'skill'; draft: LearnedSkillDraft }
|
||||
| { kind: 'command'; draft: LearnedCommandDraft }
|
||||
| { kind: 'agent'; draft: LearnedAgentDraft }
|
||||
|
||||
export function clusterInstincts(instincts: Instinct[]): EvolutionCandidate[] {
|
||||
const groups = new Map<string, Instinct[]>()
|
||||
for (const instinct of instincts) {
|
||||
if (instinct.status !== 'active' && instinct.status !== 'pending') continue
|
||||
const key = `${instinct.domain}:${normalizedTrigger(instinct.trigger)}`
|
||||
const group = groups.get(key) ?? []
|
||||
group.push(instinct)
|
||||
groups.set(key, group)
|
||||
}
|
||||
|
||||
return Array.from(groups.values())
|
||||
.filter(group => {
|
||||
// Require the cluster-size floor unconditionally. Single-shot
|
||||
// high-confidence instincts previously bypassed this via the
|
||||
// `|| confidence >= 0.8` OR, which let one message become a
|
||||
// persistent policy — exactly the H15 risk the threshold guards
|
||||
// against. Repeated independent observation is non-negotiable.
|
||||
return group.length >= getSkillLearningConfig().minClusterSize
|
||||
})
|
||||
.map(group => {
|
||||
const averageConfidence =
|
||||
group.reduce((sum, instinct) => sum + instinct.confidence, 0) /
|
||||
group.length
|
||||
return {
|
||||
target: classifyEvolutionTarget(group),
|
||||
trigger: group[0]?.trigger ?? 'learned pattern',
|
||||
domain: group[0]?.domain ?? 'project',
|
||||
instincts: group,
|
||||
averageConfidence: Number(averageConfidence.toFixed(2)),
|
||||
}
|
||||
})
|
||||
.sort((a, b) => b.averageConfidence - a.averageConfidence)
|
||||
}
|
||||
|
||||
export function classifyEvolutionTarget(
|
||||
instinctsOrCandidate: Instinct[] | EvolutionCandidate,
|
||||
): 'skill' | 'command' | 'agent' {
|
||||
const instincts = Array.isArray(instinctsOrCandidate)
|
||||
? instinctsOrCandidate
|
||||
: instinctsOrCandidate.instincts
|
||||
const text = instincts
|
||||
.map(i => `${i.trigger} ${i.action}`)
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
if (/user asks|explicitly request|command|run /.test(text)) return 'command'
|
||||
if (
|
||||
instincts.length >= 4 &&
|
||||
/(debug|investigate|research|multi-step)/.test(text)
|
||||
) {
|
||||
return 'agent'
|
||||
}
|
||||
return 'skill'
|
||||
}
|
||||
|
||||
export function suggestEvolutions(instincts: Instinct[]): EvolutionCandidate[] {
|
||||
return clusterInstincts(instincts)
|
||||
}
|
||||
|
||||
export function generateSkillCandidates(
|
||||
instincts: Instinct[],
|
||||
options?: SkillGeneratorOptions,
|
||||
): LearnedSkillDraft[] {
|
||||
return clusterInstincts(instincts)
|
||||
.filter(
|
||||
candidate =>
|
||||
candidate.target === 'skill' &&
|
||||
shouldGenerateSkillFromInstincts(candidate.instincts),
|
||||
)
|
||||
.map(candidate =>
|
||||
generateSkillDraft(candidate.instincts, {
|
||||
...options,
|
||||
scope: candidate.instincts[0]?.scope ?? 'project',
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function generateCommandCandidates(
|
||||
instincts: Instinct[],
|
||||
options?: CommandGeneratorOptions,
|
||||
): LearnedCommandDraft[] {
|
||||
return clusterInstincts(instincts)
|
||||
.filter(
|
||||
candidate =>
|
||||
candidate.target === 'command' &&
|
||||
shouldGenerateSkillFromInstincts(candidate.instincts),
|
||||
)
|
||||
.map(candidate =>
|
||||
generateCommandDraft(candidate.instincts, {
|
||||
...options,
|
||||
scope: candidate.instincts[0]?.scope ?? 'project',
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function generateAgentCandidates(
|
||||
instincts: Instinct[],
|
||||
options?: AgentGeneratorOptions,
|
||||
): LearnedAgentDraft[] {
|
||||
return clusterInstincts(instincts)
|
||||
.filter(
|
||||
candidate =>
|
||||
candidate.target === 'agent' &&
|
||||
shouldGenerateSkillFromInstincts(candidate.instincts),
|
||||
)
|
||||
.map(candidate =>
|
||||
generateAgentDraft(candidate.instincts, {
|
||||
...options,
|
||||
scope: candidate.instincts[0]?.scope ?? 'project',
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function generateAllCandidates(
|
||||
instincts: Instinct[],
|
||||
options?: {
|
||||
skill?: SkillGeneratorOptions
|
||||
command?: CommandGeneratorOptions
|
||||
agent?: AgentGeneratorOptions
|
||||
},
|
||||
): LearnedArtifactDraft[] {
|
||||
return [
|
||||
...generateSkillCandidates(instincts, options?.skill).map(
|
||||
(draft): LearnedArtifactDraft => ({ kind: 'skill', draft }),
|
||||
),
|
||||
...generateCommandCandidates(instincts, options?.command).map(
|
||||
(draft): LearnedArtifactDraft => ({ kind: 'command', draft }),
|
||||
),
|
||||
...generateAgentCandidates(instincts, options?.agent).map(
|
||||
(draft): LearnedArtifactDraft => ({ kind: 'agent', draft }),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function normalizedTrigger(trigger: string): string {
|
||||
return trigger
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, ' ')
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 6)
|
||||
.join(' ')
|
||||
}
|
||||
Reference in New Issue
Block a user