mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
import { createHash } from 'node:crypto'
|
|
import type {
|
|
SkillLearningProjectContext,
|
|
SkillLearningScope,
|
|
StoredSkillObservation,
|
|
} from './observationStore.js'
|
|
import type { Instinct as BaseInstinct, InstinctStatus } from './types.js'
|
|
|
|
export type { Instinct } from './types.js'
|
|
|
|
export type StoredInstinct = BaseInstinct & {
|
|
observationIds?: string[]
|
|
}
|
|
|
|
export type InstinctCandidate = Omit<
|
|
StoredInstinct,
|
|
'id' | 'createdAt' | 'updatedAt' | 'status'
|
|
> & {
|
|
id?: string
|
|
status?: InstinctStatus
|
|
}
|
|
|
|
export function createInstinct(
|
|
candidate: InstinctCandidate,
|
|
now = new Date().toISOString(),
|
|
): StoredInstinct {
|
|
return normalizeInstinct({
|
|
id:
|
|
candidate.id ??
|
|
buildInstinctId(candidate.trigger, candidate.action, candidate.scope),
|
|
...candidate,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
status: candidate.status ?? 'pending',
|
|
})
|
|
}
|
|
|
|
export function normalizeInstinct(instinct: StoredInstinct): StoredInstinct {
|
|
return {
|
|
...instinct,
|
|
id: instinct.id || buildInstinctId(instinct.trigger, instinct.action),
|
|
confidence: clampConfidence(instinct.confidence),
|
|
evidence: Array.from(new Set(instinct.evidence.filter(Boolean))),
|
|
evidenceOutcome: instinct.evidenceOutcome,
|
|
observationIds: instinct.observationIds
|
|
? Array.from(new Set(instinct.observationIds))
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
export function serializeInstinct(instinct: StoredInstinct): string {
|
|
return `${JSON.stringify(normalizeInstinct(instinct), null, 2)}\n`
|
|
}
|
|
|
|
export function parseInstinct(content: string): StoredInstinct {
|
|
return normalizeInstinct(JSON.parse(content) as StoredInstinct)
|
|
}
|
|
|
|
export function buildInstinctId(
|
|
trigger: string,
|
|
action: string,
|
|
scope: SkillLearningScope = 'project',
|
|
): string {
|
|
const slug = `${trigger} ${action}`
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-|-$/g, '')
|
|
.slice(0, 48)
|
|
const hash = createHash('sha1')
|
|
.update(`${scope}\n${trigger}\n${action}`)
|
|
.digest('hex')
|
|
.slice(0, 10)
|
|
return `${slug || 'instinct'}-${hash}`
|
|
}
|
|
|
|
export function candidateFromObservation(
|
|
observation: StoredSkillObservation,
|
|
project?: SkillLearningProjectContext,
|
|
): Partial<InstinctCandidate> {
|
|
return {
|
|
scope: project?.scope ?? 'project',
|
|
projectId: project?.projectId ?? observation.projectId,
|
|
projectName: project?.projectName ?? observation.projectName,
|
|
source: 'session-observation',
|
|
evidence: [
|
|
observation.messageText ??
|
|
observation.toolOutput ??
|
|
observation.toolInput ??
|
|
observation.toolName ??
|
|
observation.id,
|
|
],
|
|
observationIds: [observation.id],
|
|
}
|
|
}
|
|
|
|
export function isContradictingInstinct(
|
|
existing: StoredInstinct,
|
|
incoming: StoredInstinct,
|
|
): boolean {
|
|
const existingTrigger = existing.trigger.toLowerCase()
|
|
const incomingTrigger = incoming.trigger.toLowerCase()
|
|
if (existingTrigger !== incomingTrigger) return false
|
|
|
|
const existingAction = existing.action.toLowerCase()
|
|
const incomingAction = incoming.action.toLowerCase()
|
|
return (
|
|
existingAction.includes('avoid') !== incomingAction.includes('avoid') ||
|
|
existingAction.includes('prefer') !== incomingAction.includes('prefer')
|
|
)
|
|
}
|
|
|
|
export function clampConfidence(confidence: number): number {
|
|
if (Number.isNaN(confidence)) return 0
|
|
return Math.max(0, Math.min(1, Number(confidence.toFixed(2))))
|
|
}
|