bilmem 0.1.0 → 0.2.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 +163 -20
- package/dist/{canonical-CglQQ0xc.d.ts → canonical-JS6HUWGc.d.ts} +24 -1
- package/dist/cli/index.js +1433 -200
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +52 -5
- package/dist/core/index.js +623 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{filesystem-B3qAej0l.d.ts → filesystem-CrAkhr85.d.ts} +1 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +1451 -303
- package/dist/index.js.map +1 -1
- package/dist/install/index.d.ts +59 -3
- package/dist/install/index.js +1245 -111
- package/dist/install/index.js.map +1 -1
- package/dist/jev/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +12 -1
- package/dist/mcp/index.js +846 -97
- package/dist/mcp/index.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/scanner/index.d.ts +2 -2
- package/dist/scanner/index.js +6 -1
- package/dist/scanner/index.js.map +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/stats/index.d.ts +3 -3
- package/dist/stats/index.js +45 -1
- package/dist/stats/index.js.map +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +6 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/{types-Nxq5Gzay.d.ts → types-DNlvHsj9.d.ts} +120 -3
- package/package.json +1 -1
package/dist/rules/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { P as ProjectContext } from '../filesystem-
|
|
1
|
+
import { Y as RuleSet, X as RuleDefinition, E as EvaluatedRule, K as Knowledge, q as Observation } from '../types-DNlvHsj9.js';
|
|
2
|
+
import { P as ProjectContext } from '../filesystem-CrAkhr85.js';
|
|
3
3
|
|
|
4
4
|
interface RuleValidationResult {
|
|
5
5
|
valid: boolean;
|
package/dist/rules/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/rules/loader.ts","../../src/core/knowledge.ts","../../src/rules/evaluator.ts"],"sourcesContent":["import YAML from 'yaml';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { RuleDefinition, RuleSet, KnowledgeType } from '../core/types.js';\n\nexport interface RuleValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validates whether an object conforms to the RuleDefinition specification.\n */\nexport function validateRuleDefinition(rule: unknown, index: number = 0): string[] {\n const errors: string[] = [];\n if (!rule || typeof rule !== 'object') {\n errors.push(`Rule at index ${index} must be an object.`);\n return errors;\n }\n\n const r = rule as Partial<RuleDefinition>;\n if (!r.id || typeof r.id !== 'string' || !r.id.trim()) {\n errors.push(`Rule at index ${index} requires a non-empty string 'id'.`);\n }\n\n if (!r.statement || typeof r.statement !== 'string' || !r.statement.trim()) {\n errors.push(`Rule '${r.id || index}' requires a non-empty string 'statement'.`);\n }\n\n if (r.type) {\n const validTypes: KnowledgeType[] = [\n 'rule',\n 'convention',\n 'decision',\n 'lesson',\n 'architecture',\n 'fact'\n ];\n if (!validTypes.includes(r.type)) {\n errors.push(`Rule '${r.id || index}' has invalid type '${r.type}'. Allowed: ${validTypes.join(', ')}.`);\n }\n }\n\n if (r.confidence !== undefined) {\n if (typeof r.confidence !== 'number' || r.confidence < 0 || r.confidence > 1) {\n errors.push(`Rule '${r.id || index}' confidence must be a number between 0.0 and 1.0.`);\n }\n }\n\n if (r.positive !== undefined && !Array.isArray(r.positive)) {\n errors.push(`Rule '${r.id || index}' positive cues must be an array of strings.`);\n }\n\n if (r.negative !== undefined && !Array.isArray(r.negative)) {\n errors.push(`Rule '${r.id || index}' negative cues must be an array of strings.`);\n }\n\n return errors;\n}\n\n/**\n * Validates a RuleSet structure.\n */\nexport function validateRuleSet(data: unknown): RuleValidationResult {\n const errors: string[] = [];\n if (!data || typeof data !== 'object') {\n return { valid: false, errors: ['RuleSet content must be an object or an array of rules.'] };\n }\n\n let rules: unknown[] = [];\n if (Array.isArray(data)) {\n rules = data;\n } else if ('rules' in data && Array.isArray((data as any).rules)) {\n rules = (data as any).rules;\n } else if ('id' in data && 'statement' in data) {\n rules = [data];\n } else {\n return { valid: false, errors: ['RuleSet must contain a \"rules\" array or be a list of rule definitions.'] };\n }\n\n if (rules.length === 0) {\n errors.push('RuleSet contains no rules.');\n }\n\n rules.forEach((rule, idx) => {\n errors.push(...validateRuleDefinition(rule, idx));\n });\n\n return {\n valid: errors.length === 0,\n errors\n };\n}\n\n/**\n * Parses a YAML string into a normalized RuleSet.\n */\nexport function parseRuleSetYaml(content: string, sourcePath: string = 'inline.yml'): RuleSet {\n let parsed: unknown;\n try {\n parsed = YAML.parse(content);\n } catch (err: any) {\n throw new Error(`YAML syntax error in ${sourcePath}: ${err.message}`);\n }\n\n const validation = validateRuleSet(parsed);\n if (!validation.valid) {\n throw new Error(`Invalid RuleSet in ${sourcePath}:\\n- ${validation.errors.join('\\n- ')}`);\n }\n\n if (Array.isArray(parsed)) {\n return {\n name: path.basename(sourcePath, path.extname(sourcePath)),\n rules: parsed as RuleDefinition[]\n };\n }\n\n if ('id' in (parsed as any) && 'statement' in (parsed as any) && !('rules' in (parsed as any))) {\n return {\n name: (parsed as any).id,\n rules: [parsed as RuleDefinition]\n };\n }\n\n const set = parsed as RuleSet;\n return {\n name: set.name ?? path.basename(sourcePath, path.extname(sourcePath)),\n version: set.version ?? 1,\n scope: set.scope ?? 'project',\n description: set.description,\n rules: set.rules\n };\n}\n\n/**\n * Formats a RuleSet into clean, Git-friendly YAML.\n */\nexport function stringifyRuleSetYaml(ruleSet: RuleSet): string {\n const payload: Record<string, unknown> = {};\n if (ruleSet.name) payload.name = ruleSet.name;\n if (ruleSet.version !== undefined) payload.version = ruleSet.version;\n if (ruleSet.scope) payload.scope = ruleSet.scope;\n if (ruleSet.description) payload.description = ruleSet.description;\n\n payload.rules = ruleSet.rules.map((r) => {\n const ruleObj: Record<string, unknown> = {\n id: r.id\n };\n if (r.name) ruleObj.name = r.name;\n ruleObj.statement = r.statement;\n if (r.type && r.type !== 'rule') ruleObj.type = r.type;\n if (r.confidence !== undefined && r.confidence !== 0.95) ruleObj.confidence = r.confidence;\n if (r.scope && r.scope !== 'project') ruleObj.scope = r.scope;\n if (r.positive && r.positive.length > 0) ruleObj.positive = r.positive;\n if (r.negative && r.negative.length > 0) ruleObj.negative = r.negative;\n if (r.tags && r.tags.length > 0) ruleObj.tags = r.tags;\n if (r.when && Object.keys(r.when).length > 0) ruleObj.when = r.when;\n if (r.details && Object.keys(r.details).length > 0) ruleObj.details = r.details;\n return ruleObj;\n });\n\n return YAML.stringify(payload, { indent: 2, lineWidth: 100 });\n}\n\n/**\n * Reads and parses a single YAML rule file.\n */\nexport async function loadRuleSetFile(filePath: string): Promise<RuleSet> {\n const content = await fs.readFile(filePath, 'utf-8');\n return parseRuleSetYaml(content, filePath);\n}\n\n/**\n * Finds all rule YAML files in standard bilmem locations.\n */\nexport async function findRuleFiles(rootDir: string, storageRulesDir?: string): Promise<string[]> {\n const candidates: string[] = [];\n\n // 1. Storage rules directory: .bilmem/rules/*.yml, *.yaml\n const rulesDir = storageRulesDir || path.resolve(rootDir, '.bilmem', 'rules');\n try {\n const entries = await fs.readdir(rulesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))) {\n candidates.push(path.join(rulesDir, entry.name));\n }\n }\n } catch {\n // Directory might not exist yet\n }\n\n // 2. Storage root: .bilmem/rules.yml, .bilmem/rules.yaml\n for (const name of ['rules.yml', 'rules.yaml']) {\n const p = path.resolve(rootDir, '.bilmem', name);\n try {\n const st = await fs.stat(p);\n if (st.isFile()) candidates.push(p);\n } catch {}\n }\n\n // 3. Project root: rules.yml, rules.yaml, bilmem.rules.yml, bilmem.rules.yaml\n for (const name of ['bilmem.rules.yml', 'bilmem.rules.yaml', 'rules.yml', 'rules.yaml']) {\n const p = path.resolve(rootDir, name);\n try {\n const st = await fs.stat(p);\n if (st.isFile()) candidates.push(p);\n } catch {}\n }\n\n // Deduplicate canonical paths\n return Array.from(new Set(candidates));\n}\n\n/**\n * Loads and combines all rule sets discovered in project and storage.\n */\nexport async function loadAllProjectRuleSets(\n rootDir: string,\n storageRulesDir?: string\n): Promise<{ ruleSets: RuleSet[]; sourceFiles: string[] }> {\n const files = await findRuleFiles(rootDir, storageRulesDir);\n const ruleSets: RuleSet[] = [];\n const loadedFiles: string[] = [];\n\n for (const file of files) {\n try {\n const set = await loadRuleSetFile(file);\n ruleSets.push(set);\n loadedFiles.push(file);\n } catch (err: any) {\n // Log or keep error context without failing the entire scan\n console.warn(`[bilmem] Warning: Failed to load rule set from ${file}: ${err.message}`);\n }\n }\n\n return { ruleSets, sourceFiles: loadedFiles };\n}\n","import { createHash } from 'node:crypto';\nimport type {\n Knowledge,\n KnowledgeType,\n KnowledgeStatus,\n KnowledgeStage,\n KnowledgeHistoryEntry,\n KnowledgeOutcomesSummary,\n Scope,\n Observation,\n Evidence,\n Provenance,\n ReconciliationResult,\n ReconciliationAction,\n ConfidenceTier\n} from './types.js';\nimport { determineInitialStage, calculateKnowledgeValue, evaluatePromotion } from './quarantine.js';\nimport type { MetricEvent } from '../stats/types.js';\n\n/**\n * Deterministic mapping between confidence tiers and normalized float scores.\n * Replaces arbitrary precision floats with explainable thresholds.\n */\nexport function tierToConfidence(tier: ConfidenceTier): number {\n switch (tier) {\n case 'low':\n return 0.50;\n case 'medium':\n return 0.75;\n case 'high':\n return 0.95;\n }\n}\n\nexport function confidenceToTier(score: number): ConfidenceTier {\n if (score >= 0.85) return 'high';\n if (score >= 0.65) return 'medium';\n return 'low';\n}\n\nexport function hashString(input: string): string {\n return createHash('sha256').update(input.trim().toLowerCase()).digest('hex').slice(0, 16);\n}\n\nexport function createObservation(params: {\n source: string;\n claim: string;\n provenance?: Provenance;\n confidence?: number;\n positive?: string[];\n negative?: string[];\n details?: Record<string, unknown>;\n}): Observation {\n const provenance: Provenance = params.provenance ?? {\n type: 'file',\n path: params.source,\n timestamp: new Date().toISOString()\n };\n const contentForHash = `${params.source}:${params.claim}:${provenance.commit || provenance.timestamp || Date.now()}`;\n const id = `obs_${hashString(contentForHash)}`;\n\n return {\n id,\n source: params.source,\n claim: params.claim.trim(),\n provenance,\n confidence: Math.min(1.0, Math.max(0.0, params.confidence ?? 0.8)),\n positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),\n negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),\n details: params.details,\n recordedAt: new Date().toISOString()\n };\n}\n\n/**\n * Creates or consolidates evidence based on underlying source.\n * Crucial constraint: Multiple agents reading the same source must NOT count as independent evidence.\n */\nexport function createEvidence(\n observation: Observation,\n type: 'supports' | 'contradicts' | 'observes' = 'supports'\n): Evidence {\n const sourceHash = hashString(observation.source);\n const id = `evi_${sourceHash}`;\n\n return {\n id,\n source: observation.source,\n sourceHash,\n observationIds: [observation.id],\n type,\n claim: observation.claim,\n provenance: observation.provenance,\n weight: 1.0,\n firstObservedAt: observation.recordedAt,\n lastObservedAt: observation.recordedAt\n };\n}\n\nexport function createKnowledge(params: {\n id: string;\n type?: KnowledgeType;\n status?: KnowledgeStatus;\n stage?: KnowledgeStage;\n statement: string;\n positive?: string[];\n negative?: string[];\n relations?: string[];\n depends_on?: string[];\n resolves?: string[];\n question?: string;\n confidence?: number;\n confidenceTier?: ConfidenceTier;\n evidence?: string[];\n scope?: Scope;\n tags?: string[];\n history?: KnowledgeHistoryEntry[];\n outcomesSummary?: KnowledgeOutcomesSummary;\n}): Knowledge {\n const now = new Date().toISOString();\n const confidenceTier =\n params.confidenceTier ??\n (params.confidence !== undefined ? confidenceToTier(params.confidence) : undefined);\n const confidence =\n params.confidence !== undefined\n ? params.confidence\n : (confidenceTier ? tierToConfidence(confidenceTier) : 0.85);\n\n const dependsOn = new Set<string>(params.depends_on ?? []);\n if (params.evidence) {\n for (const ev of params.evidence) {\n if (ev.startsWith('file:') || ev.startsWith('test:') || ev.startsWith('code:')) {\n dependsOn.add(ev);\n } else if (ev.includes('/') || ev.endsWith('.json') || ev.endsWith('.yaml') || ev.endsWith('.yml')) {\n dependsOn.add(`file:${ev}`);\n }\n }\n }\n\n const initialStage = params.stage ?? determineInitialStage({\n type: params.type ?? 'lesson',\n evidence: params.evidence ?? [],\n confidence\n });\n\n const status = params.status ?? (initialStage === 'candidate' ? 'candidate' : 'active');\n\n const history: KnowledgeHistoryEntry[] = params.history ?? [\n {\n timestamp: now,\n stage: initialStage,\n status,\n reason: `Initial knowledge recorded (${params.type ?? 'lesson'})`,\n source: params.evidence?.[0]\n }\n ];\n\n const knowledgeItem: Knowledge = {\n id: params.id.trim(),\n type: params.type ?? 'lesson',\n status,\n stage: initialStage,\n statement: params.statement.trim(),\n positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),\n negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),\n relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),\n depends_on: Array.from(dependsOn),\n resolves: params.resolves ? Array.from(new Set(params.resolves.map((r) => r.trim()))) : undefined,\n question: params.question?.trim(),\n confidence: Math.min(1.0, Math.max(0.0, confidence)),\n confidenceTier,\n evidence: Array.from(new Set(params.evidence ?? [])),\n scope: params.scope ?? 'project',\n tags: Array.from(new Set(params.tags?.map((t) => t.trim().toLowerCase()) ?? [])),\n temporal: {\n revision: 1\n },\n metrics: {\n reuse_count: 0,\n support_count: 1,\n contradiction_count: 0,\n successful_outcomes: 0,\n failed_outcomes: 0\n },\n outcomesSummary: params.outcomesSummary ?? {\n applied: 0,\n successful: 0,\n misleading: 0,\n neutral: 0\n },\n history,\n value: 0,\n createdAt: now,\n updatedAt: now\n };\n\n knowledgeItem.value = calculateKnowledgeValue(knowledgeItem);\n return knowledgeItem;\n}\n\n/**\n * Calculate effective confidence combining evidence ratio and outcome history.\n */\nexport function calculateEffectiveConfidence(knowledge: Knowledge): number {\n const m = knowledge.metrics;\n const base = knowledge.confidence;\n\n // Evidence ratio dampening: heavily penalizes contradictions\n const evRatio = (m.support_count + 1) / (m.support_count + 3 * m.contradiction_count + 1);\n\n const totalOutcomes = m.successful_outcomes + m.failed_outcomes;\n let outRatio = 1.0;\n let evWeight = 0.6;\n let outWeight = 0.4;\n\n if (totalOutcomes === 0) {\n evWeight = 0.85;\n outWeight = 0.15;\n } else {\n outRatio = (m.successful_outcomes + 1) / (totalOutcomes + 1);\n }\n\n const effective = base * (evWeight * evRatio + outWeight * outRatio);\n return Math.round(Math.min(1.0, Math.max(0.05, effective)) * 100) / 100;\n}\n\nconst knowledgeTokenCache = new WeakMap<Knowledge, Set<string>>();\n\n/**\n * Tokenize string into lowercase terms for lexical similarity comparison.\n */\nfunction tokenize(text: string): Set<string> {\n return new Set(\n text\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, ' ')\n .split(/\\s+/)\n .filter((t) => t.length > 2)\n );\n}\n\nfunction getCachedTokens(k: Knowledge): Set<string> {\n let tokens = knowledgeTokenCache.get(k);\n if (!tokens) {\n tokens = tokenize(k.statement);\n knowledgeTokenCache.set(k, tokens);\n }\n return tokens;\n}\n\nfunction jaccardSimilarity(setA: Set<string>, setB: Set<string>): number {\n if (setA.size === 0 && setB.size === 0) return 1.0;\n let intersection = 0;\n for (const item of setA) {\n if (setB.has(item)) intersection++;\n }\n const union = setA.size + setB.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n/**\n * Known polar antonym word pairs.\n */\nconst POLAR_ANTONYMS: Array<[string, string]> = [\n ['enabled', 'disabled'],\n ['enable', 'disable'],\n ['active', 'inactive'],\n ['true', 'false'],\n ['on', 'off'],\n ['yes', 'no'],\n ['allowed', 'disallowed'],\n ['allowed', 'prohibited'],\n ['allowed', 'forbidden'],\n ['permit', 'deny'],\n ['allow', 'forbid'],\n ['allow', 'prohibit'],\n ['always', 'never'],\n ['should', 'should not'],\n ['must', 'must not'],\n ['required', 'optional'],\n ['required', 'forbidden'],\n ['valid', 'invalid'],\n ['safe', 'unsafe'],\n ['sync', 'async'],\n ['synchronous', 'asynchronous'],\n ['public', 'private']\n];\n\nconst NEGATION_PATTERNS = [\n /\\bnot\\b/i,\n /\\bnever\\b/i,\n /\\bno\\b/i,\n /\\bcannot\\b/i,\n /\\bwithout\\b/i,\n /\\bdisallow\\b/i,\n /\\bforbid\\b/i,\n /\\bprohibit\\b/i,\n /\\bavoid\\b/i\n];\n\n/**\n * Checks if two statements express opposite polarity or semantic contradiction.\n * Essential rule: High lexical similarity does NOT imply MERGE.\n * \"cache enabled in production\" vs \"cache disabled in production\" have opposite polarity.\n */\nexport function detectPolarContradiction(\n stmtA: string,\n stmtB: string\n): { contradicted: boolean; reason?: string } {\n const lowerA = stmtA.toLowerCase();\n const lowerB = stmtB.toLowerCase();\n\n // 1. Check known antonym pairs\n for (const [pos, neg] of POLAR_ANTONYMS) {\n const aHasPos = new RegExp(`\\\\b${pos}\\\\b`, 'i').test(lowerA);\n const aHasNeg = new RegExp(`\\\\b${neg}\\\\b`, 'i').test(lowerA);\n const bHasPos = new RegExp(`\\\\b${pos}\\\\b`, 'i').test(lowerB);\n const bHasNeg = new RegExp(`\\\\b${neg}\\\\b`, 'i').test(lowerB);\n\n if ((aHasPos && bHasNeg) || (aHasNeg && bHasPos)) {\n return {\n contradicted: true,\n reason: `Polar opposite terms detected: '${pos}' vs '${neg}'`\n };\n }\n }\n\n // 2. Check asymmetric negation (one statement has negation, the other does not)\n const aHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerA));\n const bHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerB));\n\n if (aHasNegation !== bHasNegation) {\n return {\n contradicted: true,\n reason: 'Asymmetric negation detected (one statement asserts, the other negates)'\n };\n }\n\n return { contradicted: false };\n}\n\n/**\n * Reconcile an incoming candidate lesson with existing knowledge.\n * Classifies the action as: NEW, MERGE, UPDATE, SUPPORT, CONTRADICT, SPECIALIZE, GENERALIZE, IGNORE.\n */\nexport function reconcileKnowledge(\n candidate: Partial<Knowledge> & { statement: string },\n existingList: Knowledge[]\n): ReconciliationResult {\n const candidateId = candidate.id?.trim();\n const candidateStmt = candidate.statement.trim();\n const candidateTokens = tokenize(candidateStmt);\n const candidatePos = new Set((candidate.positive ?? []).map((p) => p.trim().toLowerCase()));\n\n // 1. Direct ID match\n if (candidateId) {\n const directMatch = existingList.find((k) => k.id === candidateId);\n if (directMatch) {\n // Check for exact identical statement\n if (directMatch.statement.trim().toLowerCase() === candidateStmt.toLowerCase()) {\n // If evidence is identical and cues are identical, IGNORE to avoid duplicate bloat\n const hasNewEvidence = (candidate.evidence ?? []).some((e) => !directMatch.evidence.includes(e));\n if (!hasNewEvidence) {\n return {\n action: 'IGNORE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Identical statement and evidence already recorded'\n };\n }\n\n return {\n action: 'SUPPORT',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Confirms existing knowledge with new evidence source'\n };\n }\n\n // Check if candidate contradicts existing statement (e.g. contains negation or opposing cues)\n const opposingPositive = (candidate.negative ?? []).some((n) => directMatch.positive.includes(n));\n const opposingNegative = (candidate.positive ?? []).some((p) => directMatch.negative.includes(p));\n const polarCheck = detectPolarContradiction(candidateStmt, directMatch.statement);\n\n if (opposingPositive || opposingNegative || polarCheck.contradicted) {\n // Check if contexts differ (e.g. dev vs prod) -> SPECIALIZE\n const distinctCues = Array.from(candidatePos).filter((c) => !directMatch.positive.includes(c));\n const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);\n const mentionsEnvB = /(production|prod)/i.test(directMatch.statement);\n const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(directMatch.statement);\n const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);\n\n if (distinctCues.length > 0 || (mentionsEnvA && mentionsEnvB) || (mentionsEnvBDev && mentionsEnvAProd)) {\n return {\n action: 'SPECIALIZE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: `Conflicting claim applies under specialized context cues: [${distinctCues.join(', ')}] (${polarCheck.reason || 'opposing polarity'})`\n };\n }\n\n return {\n action: 'CONTRADICT',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: `Contradicts existing statement (${polarCheck.reason || 'opposing polarity'}) for ID \"${directMatch.id}\"`\n };\n }\n\n return {\n action: 'UPDATE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Refines or updates existing knowledge statement'\n };\n }\n }\n\n // 2. Search for nearby existing knowledge by statement similarity and contextual overlap\n let bestMatch: Knowledge | null = null;\n let bestSim = 0;\n let bestPolarCheck: { contradicted: boolean; reason?: string } = { contradicted: false };\n\n for (const existing of existingList) {\n const existingTokens = getCachedTokens(existing);\n const sim = jaccardSimilarity(candidateTokens, existingTokens);\n\n // Explicit polar contradiction check: opposing polar assertions on same topic\n const polarCheck = detectPolarContradiction(candidateStmt, existing.statement);\n if (polarCheck.contradicted) {\n let sharedCount = 0;\n for (const t of candidateTokens) {\n if (existingTokens.has(t)) sharedCount++;\n }\n const sharedCues = (candidate.positive ?? []).filter((p) => existing.positive.includes(p));\n if (sharedCount >= 2 || (sharedCues.length >= 1 && sim >= 0.35) || sim >= 0.40) {\n bestMatch = existing;\n bestSim = Math.max(sim, 0.85);\n bestPolarCheck = polarCheck;\n break;\n }\n }\n\n if (sim > bestSim) {\n bestSim = sim;\n bestMatch = existing;\n bestPolarCheck = polarCheck;\n }\n }\n\n if (bestMatch && bestSim >= 0.70) {\n // CRITICAL: First evaluate contradiction and polar polarity BEFORE any merge decision!\n const polarCheck = bestPolarCheck.contradicted\n ? bestPolarCheck\n : detectPolarContradiction(candidateStmt, bestMatch.statement);\n const hasOpposingCues =\n (candidate.negative ?? []).some((n) => bestMatch!.positive.includes(n)) ||\n (candidate.positive ?? []).some((p) => bestMatch!.negative.includes(p));\n\n if (polarCheck.contradicted || hasOpposingCues) {\n // Check if contexts differ (e.g. dev vs prod) -> SPECIALIZE\n const distinctCues = Array.from(candidatePos).filter((c) => !bestMatch!.positive.includes(c));\n const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);\n const mentionsEnvB = /(production|prod)/i.test(bestMatch.statement);\n const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(bestMatch.statement);\n const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);\n\n if (distinctCues.length > 0 || (mentionsEnvA && mentionsEnvB) || (mentionsEnvBDev && mentionsEnvAProd)) {\n return {\n action: 'SPECIALIZE',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `High textual similarity (${Math.round(bestSim * 100)}%) with polar distinction across contexts (${polarCheck.reason || 'opposing cues'})`,\n score: bestSim\n };\n }\n\n return {\n action: 'CONTRADICT',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Contradicting statement (${polarCheck.reason || 'opposing cues'}) despite high textual similarity (${Math.round(bestSim * 100)}%)`,\n score: bestSim\n };\n }\n\n // Only if polarity is compatible and similarity is nearly identical:\n if (bestSim >= 0.90) {\n return {\n action: 'MERGE',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Nearly identical statement (${Math.round(bestSim * 100)}% match) with compatible polarity; merging cues and evidence`,\n score: bestSim\n };\n }\n\n return {\n action: 'SUPPORT',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Supports existing knowledge (${Math.round(bestSim * 100)}% similarity) with compatible polarity`,\n score: bestSim\n };\n }\n\n // 3. Fallback: completely new knowledge\n return {\n action: 'NEW',\n reason: 'No conflicting or matching knowledge found; creating new knowledge entry'\n };\n}\n\n/**\n * Apply reconciliation to knowledge base.\n */\nexport function applyReconciliation(\n reconciliation: ReconciliationResult,\n candidate: Partial<Knowledge> & { statement: string },\n existingList: Knowledge[],\n storage?: { appendMetricEvent(event: MetricEvent): Promise<void> }\n): { updatedKnowledge: Knowledge; action: ReconciliationAction; createdId: string } {\n const now = new Date().toISOString();\n\n switch (reconciliation.action) {\n case 'SUPPORT': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.support_count++;\n target.metrics.last_used = now;\n storage?.appendMetricEvent({\n type: 'knowledge_strengthened',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n\n // Add new evidence refs\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) {\n target.evidence.push(e);\n }\n }\n }\n\n // Merge positive/negative cues if any new\n if (candidate.positive) {\n for (const p of candidate.positive) {\n const lp = p.trim().toLowerCase();\n if (!target.positive.includes(lp)) target.positive.push(lp);\n }\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n\n const promotion = evaluatePromotion(target);\n if (promotion.promoted && promotion.newStage) {\n target.stage = promotion.newStage;\n if (target.stage === 'active' && target.status === 'candidate') {\n target.status = 'active';\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: promotion.reason || `Promoted to ${target.stage}`,\n source: candidate.evidence?.[0]\n });\n } else {\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Confirmed with supporting evidence',\n source: candidate.evidence?.[0]\n });\n }\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n return { updatedKnowledge: target, action: 'SUPPORT', createdId: target.id };\n }\n\n case 'MERGE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.support_count++;\n target.metrics.last_used = now;\n storage?.appendMetricEvent({\n type: 'knowledge_merged',\n knowledgeId: target.id,\n targetId: reconciliation.targetId,\n timestamp: Date.now()\n });\n\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) target.evidence.push(e);\n }\n }\n\n if (candidate.positive) {\n for (const p of candidate.positive) {\n const lp = p.trim().toLowerCase();\n if (!target.positive.includes(lp)) target.positive.push(lp);\n }\n }\n\n if (candidate.negative) {\n for (const n of candidate.negative) {\n const ln = n.trim().toLowerCase();\n if (!target.negative.includes(ln)) target.negative.push(ln);\n }\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n\n const promotion = evaluatePromotion(target);\n if (promotion.promoted && promotion.newStage) {\n target.stage = promotion.newStage;\n if (target.stage === 'active' && target.status === 'candidate') {\n target.status = 'active';\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: promotion.reason || `Promoted to ${target.stage}`,\n source: candidate.evidence?.[0]\n });\n } else {\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Merged with compatible knowledge candidate',\n source: candidate.evidence?.[0]\n });\n }\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n return { updatedKnowledge: target, action: 'MERGE', createdId: target.id };\n }\n\n case 'UPDATE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.statement = candidate.statement;\n if (candidate.type) target.type = candidate.type;\n if (candidate.positive) {\n target.positive = Array.from(new Set([...target.positive, ...candidate.positive.map((p) => p.toLowerCase())]));\n }\n if (candidate.negative) {\n target.negative = Array.from(new Set([...target.negative, ...candidate.negative.map((n) => n.toLowerCase())]));\n }\n if (candidate.evidence) {\n target.evidence = Array.from(new Set([...target.evidence, ...candidate.evidence]));\n }\n\n target.temporal = {\n revision: (target.temporal?.revision ?? 1) + 1,\n valid_from: { timestamp: now }\n };\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Updated statement and context',\n source: candidate.evidence?.[0]\n });\n\n target.updatedAt = now;\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n storage?.appendMetricEvent({\n type: 'knowledge_strengthened',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'UPDATE', createdId: target.id };\n }\n\n case 'CONTRADICT': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.contradiction_count++;\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) target.evidence.push(e);\n }\n }\n\n if (target.metrics.contradiction_count > target.metrics.support_count) {\n target.status = 'contradicted';\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Contradicted by opposing claim',\n source: candidate.evidence?.[0]\n });\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n storage?.appendMetricEvent({\n type: 'contradiction_detected',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'CONTRADICT', createdId: target.id };\n }\n\n case 'SPECIALIZE': {\n // Create specialized branch\n const parent = existingList.find((k) => k.id === reconciliation.targetId);\n const subContext = candidate.positive?.[0] || 'specialized';\n const newId =\n candidate.id && candidate.id !== parent?.id\n ? candidate.id\n : `${parent ? parent.id : 'knowledge'}.${subContext}`;\n\n const created = createKnowledge({\n id: newId,\n type: candidate.type ?? parent?.type ?? 'lesson',\n statement: candidate.statement,\n positive: candidate.positive ?? [],\n negative: candidate.negative ?? [],\n relations: parent ? [parent.id, ...(candidate.relations ?? [])] : (candidate.relations ?? []),\n confidence: candidate.confidence ?? 0.85,\n evidence: candidate.evidence ?? [],\n scope: candidate.scope ?? parent?.scope ?? 'project',\n tags: candidate.tags ?? parent?.tags ?? []\n });\n\n if (parent) {\n if (!parent.relations.includes(newId)) {\n parent.relations.push(newId);\n }\n if (!parent.history) {\n parent.history = [\n {\n timestamp: parent.createdAt || now,\n stage: parent.stage,\n status: parent.status,\n reason: 'Initial knowledge recorded'\n }\n ];\n }\n parent.history.push({\n timestamp: now,\n stage: parent.stage,\n status: parent.status,\n reason: `Specialized into contextual branch ${newId}`\n });\n }\n\n storage?.appendMetricEvent({\n type: 'knowledge_specialized',\n knowledgeId: created.id,\n timestamp: Date.now()\n });\n\n return { updatedKnowledge: created, action: 'SPECIALIZE', createdId: created.id };\n }\n\n case 'IGNORE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n storage?.appendMetricEvent({\n type: 'duplicate_ignored',\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'IGNORE', createdId: target.id };\n }\n\n case 'NEW':\n default: {\n const newId = candidate.id || `kn_${hashString(candidate.statement)}`;\n const created = createKnowledge({\n id: newId,\n type: candidate.type,\n statement: candidate.statement,\n positive: candidate.positive,\n negative: candidate.negative,\n relations: candidate.relations,\n confidence: candidate.confidence,\n evidence: candidate.evidence,\n scope: candidate.scope,\n tags: candidate.tags\n });\n storage?.appendMetricEvent({\n type: 'knowledge_created',\n knowledgeId: created.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: created, action: 'NEW', createdId: created.id };\n }\n }\n}\n","import type {\n RuleDefinition,\n RuleSet,\n EvaluatedRule,\n Observation,\n Knowledge\n} from '../core/types.js';\nimport { createObservation } from '../core/knowledge.js';\nimport type { ProjectContext } from '../scanner/filesystem.js';\n\n/**\n * Converts a simple glob-like pattern (e.g. *.sh, src/**\\/*.ts) into a regular expression.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = glob.trim().replace(/^\\//, '').replace(/\\/$/, '');\n pattern = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n pattern = pattern.replace(/\\*\\*/g, '.*');\n pattern = pattern.replace(/\\*/g, '[^/]*');\n pattern = pattern.replace(/\\?/g, '[^/]');\n return new RegExp(`(^|/)${pattern}$`, 'i');\n}\n\n/**\n * Evaluates a single rule condition against the project context.\n */\nexport async function evaluateRule(\n rule: RuleDefinition,\n context: ProjectContext\n): Promise<EvaluatedRule> {\n const cond = rule.when;\n\n // Unconditional rules are always matched\n if (!cond || Object.keys(cond).length === 0) {\n return {\n rule,\n matched: true,\n reason: 'Always active project rule'\n };\n }\n\n const matchedFiles: string[] = [];\n\n // 1. Check has_file condition\n if (cond.has_file) {\n const files = Array.isArray(cond.has_file) ? cond.has_file : [cond.has_file];\n for (const f of files) {\n if (!context.hasFile(f)) {\n return {\n rule,\n matched: false,\n reason: `Required file '${f}' not found in project`\n };\n }\n matchedFiles.push(f);\n }\n }\n\n // 2. Check missing_file condition\n if (cond.missing_file) {\n const files = Array.isArray(cond.missing_file) ? cond.missing_file : [cond.missing_file];\n for (const f of files) {\n if (context.hasFile(f)) {\n return {\n rule,\n matched: false,\n reason: `Prohibited file '${f}' is present in project`\n };\n }\n }\n }\n\n // 3. Check any_file condition\n if (cond.any_file && cond.any_file.length > 0) {\n let anyFound = false;\n for (const f of cond.any_file) {\n if (context.hasFile(f)) {\n anyFound = true;\n matchedFiles.push(f);\n break;\n }\n }\n if (!anyFound) {\n return {\n rule,\n matched: false,\n reason: `None of the expected files were found: ${cond.any_file.join(', ')}`\n };\n }\n }\n\n // 4. Check file_pattern condition\n if (cond.file_pattern) {\n const regex = globToRegex(cond.file_pattern);\n const matches = context.files.filter(\n (f) => regex.test(f.relativePath) || regex.test(f.name)\n );\n if (matches.length === 0) {\n return {\n rule,\n matched: false,\n reason: `No files matched pattern '${cond.file_pattern}'`\n };\n }\n matchedFiles.push(...matches.slice(0, 5).map((m) => m.relativePath));\n }\n\n // 5. Check content_match condition\n if (cond.content_match) {\n const { files: targetFile, pattern } = cond.content_match;\n const regex = new RegExp(pattern, 'm');\n\n if (targetFile) {\n const content = await context.readText(targetFile);\n if (!content || !regex.test(content)) {\n return {\n rule,\n matched: false,\n reason: `Pattern '${pattern}' was not found in file '${targetFile}'`\n };\n }\n matchedFiles.push(targetFile);\n } else {\n // If no specific file target, search across top candidate files\n let contentFound = false;\n for (const file of context.files.slice(0, 20)) {\n const content = await context.readText(file.relativePath);\n if (content && regex.test(content)) {\n contentFound = true;\n matchedFiles.push(file.relativePath);\n break;\n }\n }\n if (!contentFound) {\n return {\n rule,\n matched: false,\n reason: `Pattern '${pattern}' was not found in project files`\n };\n }\n }\n }\n\n return {\n rule,\n matched: true,\n reason: 'Rule condition matched repository structure',\n matchedFiles: matchedFiles.length > 0 ? Array.from(new Set(matchedFiles)) : undefined\n };\n}\n\n/**\n * Evaluates all rules within a RuleSet against the project context.\n */\nexport async function evaluateRuleSet(\n ruleSet: RuleSet,\n context: ProjectContext\n): Promise<EvaluatedRule[]> {\n const results: EvaluatedRule[] = [];\n for (const rule of ruleSet.rules) {\n results.push(await evaluateRule(rule, context));\n }\n return results;\n}\n\n/**\n * Converts a matched rule into a standard Observation.\n */\nexport function ruleToObservation(\n rule: RuleDefinition,\n evaluated: EvaluatedRule,\n sourceFile: string\n): Observation {\n const now = new Date().toISOString();\n const ruleType = rule.type ?? 'rule';\n\n const positive = [\n ...(rule.positive ?? []),\n rule.id,\n ruleType,\n ...(rule.tags ?? [])\n ];\n\n return createObservation({\n source: `rule:${rule.id}`,\n claim: rule.statement,\n confidence: rule.confidence ?? 0.95,\n provenance: {\n type: 'file',\n path: sourceFile,\n timestamp: now\n },\n positive: Array.from(new Set(positive)),\n negative: rule.negative ? [...rule.negative] : [],\n details: {\n ruleId: rule.id,\n ruleName: rule.name,\n ruleType,\n matchedReason: evaluated.reason,\n matchedFiles: evaluated.matchedFiles,\n scope: rule.scope ?? 'project',\n ...(rule.details ?? {})\n }\n });\n}\n\n/**\n * Converts a matched rule into a candidate Knowledge item.\n */\nexport function ruleToCandidateLesson(\n rule: RuleDefinition,\n evaluated: EvaluatedRule,\n obsSource: string\n): Partial<Knowledge> & { statement: string } {\n const ruleType = rule.type ?? 'rule';\n const positive = [\n ...(rule.positive ?? []),\n rule.id,\n ruleType,\n ...(rule.tags ?? [])\n ];\n\n return {\n id: `rule.${rule.id}`,\n type: ruleType,\n statement: rule.statement,\n positive: Array.from(new Set(positive)),\n negative: rule.negative ? [...rule.negative] : [],\n confidence: rule.confidence ?? 0.95,\n evidence: [obsSource],\n scope: rule.scope ?? 'project',\n tags: rule.tags ? [...rule.tags] : []\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/rules/loader.ts","../../src/core/knowledge.ts","../../src/rules/evaluator.ts"],"sourcesContent":["import YAML from 'yaml';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { RuleDefinition, RuleSet, KnowledgeType } from '../core/types.js';\n\nexport interface RuleValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validates whether an object conforms to the RuleDefinition specification.\n */\nexport function validateRuleDefinition(rule: unknown, index: number = 0): string[] {\n const errors: string[] = [];\n if (!rule || typeof rule !== 'object') {\n errors.push(`Rule at index ${index} must be an object.`);\n return errors;\n }\n\n const r = rule as Partial<RuleDefinition>;\n if (!r.id || typeof r.id !== 'string' || !r.id.trim()) {\n errors.push(`Rule at index ${index} requires a non-empty string 'id'.`);\n }\n\n if (!r.statement || typeof r.statement !== 'string' || !r.statement.trim()) {\n errors.push(`Rule '${r.id || index}' requires a non-empty string 'statement'.`);\n }\n\n if (r.type) {\n const validTypes: KnowledgeType[] = [\n 'rule',\n 'convention',\n 'decision',\n 'lesson',\n 'architecture',\n 'fact'\n ];\n if (!validTypes.includes(r.type)) {\n errors.push(`Rule '${r.id || index}' has invalid type '${r.type}'. Allowed: ${validTypes.join(', ')}.`);\n }\n }\n\n if (r.confidence !== undefined) {\n if (typeof r.confidence !== 'number' || r.confidence < 0 || r.confidence > 1) {\n errors.push(`Rule '${r.id || index}' confidence must be a number between 0.0 and 1.0.`);\n }\n }\n\n if (r.positive !== undefined && !Array.isArray(r.positive)) {\n errors.push(`Rule '${r.id || index}' positive cues must be an array of strings.`);\n }\n\n if (r.negative !== undefined && !Array.isArray(r.negative)) {\n errors.push(`Rule '${r.id || index}' negative cues must be an array of strings.`);\n }\n\n return errors;\n}\n\n/**\n * Validates a RuleSet structure.\n */\nexport function validateRuleSet(data: unknown): RuleValidationResult {\n const errors: string[] = [];\n if (!data || typeof data !== 'object') {\n return { valid: false, errors: ['RuleSet content must be an object or an array of rules.'] };\n }\n\n let rules: unknown[] = [];\n if (Array.isArray(data)) {\n rules = data;\n } else if ('rules' in data && Array.isArray((data as any).rules)) {\n rules = (data as any).rules;\n } else if ('id' in data && 'statement' in data) {\n rules = [data];\n } else {\n return { valid: false, errors: ['RuleSet must contain a \"rules\" array or be a list of rule definitions.'] };\n }\n\n if (rules.length === 0) {\n errors.push('RuleSet contains no rules.');\n }\n\n rules.forEach((rule, idx) => {\n errors.push(...validateRuleDefinition(rule, idx));\n });\n\n return {\n valid: errors.length === 0,\n errors\n };\n}\n\n/**\n * Parses a YAML string into a normalized RuleSet.\n */\nexport function parseRuleSetYaml(content: string, sourcePath: string = 'inline.yml'): RuleSet {\n let parsed: unknown;\n try {\n parsed = YAML.parse(content);\n } catch (err: any) {\n throw new Error(`YAML syntax error in ${sourcePath}: ${err.message}`);\n }\n\n const validation = validateRuleSet(parsed);\n if (!validation.valid) {\n throw new Error(`Invalid RuleSet in ${sourcePath}:\\n- ${validation.errors.join('\\n- ')}`);\n }\n\n if (Array.isArray(parsed)) {\n return {\n name: path.basename(sourcePath, path.extname(sourcePath)),\n rules: parsed as RuleDefinition[]\n };\n }\n\n if ('id' in (parsed as any) && 'statement' in (parsed as any) && !('rules' in (parsed as any))) {\n return {\n name: (parsed as any).id,\n rules: [parsed as RuleDefinition]\n };\n }\n\n const set = parsed as RuleSet;\n return {\n name: set.name ?? path.basename(sourcePath, path.extname(sourcePath)),\n version: set.version ?? 1,\n scope: set.scope ?? 'project',\n description: set.description,\n rules: set.rules\n };\n}\n\n/**\n * Formats a RuleSet into clean, Git-friendly YAML.\n */\nexport function stringifyRuleSetYaml(ruleSet: RuleSet): string {\n const payload: Record<string, unknown> = {};\n if (ruleSet.name) payload.name = ruleSet.name;\n if (ruleSet.version !== undefined) payload.version = ruleSet.version;\n if (ruleSet.scope) payload.scope = ruleSet.scope;\n if (ruleSet.description) payload.description = ruleSet.description;\n\n payload.rules = ruleSet.rules.map((r) => {\n const ruleObj: Record<string, unknown> = {\n id: r.id\n };\n if (r.name) ruleObj.name = r.name;\n ruleObj.statement = r.statement;\n if (r.type && r.type !== 'rule') ruleObj.type = r.type;\n if (r.confidence !== undefined && r.confidence !== 0.95) ruleObj.confidence = r.confidence;\n if (r.scope && r.scope !== 'project') ruleObj.scope = r.scope;\n if (r.positive && r.positive.length > 0) ruleObj.positive = r.positive;\n if (r.negative && r.negative.length > 0) ruleObj.negative = r.negative;\n if (r.tags && r.tags.length > 0) ruleObj.tags = r.tags;\n if (r.when && Object.keys(r.when).length > 0) ruleObj.when = r.when;\n if (r.details && Object.keys(r.details).length > 0) ruleObj.details = r.details;\n return ruleObj;\n });\n\n return YAML.stringify(payload, { indent: 2, lineWidth: 100 });\n}\n\n/**\n * Reads and parses a single YAML rule file.\n */\nexport async function loadRuleSetFile(filePath: string): Promise<RuleSet> {\n const content = await fs.readFile(filePath, 'utf-8');\n return parseRuleSetYaml(content, filePath);\n}\n\n/**\n * Finds all rule YAML files in standard bilmem locations.\n */\nexport async function findRuleFiles(rootDir: string, storageRulesDir?: string): Promise<string[]> {\n const candidates: string[] = [];\n\n // 1. Storage rules directory: .bilmem/rules/*.yml, *.yaml\n const rulesDir = storageRulesDir || path.resolve(rootDir, '.bilmem', 'rules');\n try {\n const entries = await fs.readdir(rulesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))) {\n candidates.push(path.join(rulesDir, entry.name));\n }\n }\n } catch {\n // Directory might not exist yet\n }\n\n // 2. Storage root: .bilmem/rules.yml, .bilmem/rules.yaml\n for (const name of ['rules.yml', 'rules.yaml']) {\n const p = path.resolve(rootDir, '.bilmem', name);\n try {\n const st = await fs.stat(p);\n if (st.isFile()) candidates.push(p);\n } catch {}\n }\n\n // 3. Project root: rules.yml, rules.yaml, bilmem.rules.yml, bilmem.rules.yaml\n for (const name of ['bilmem.rules.yml', 'bilmem.rules.yaml', 'rules.yml', 'rules.yaml']) {\n const p = path.resolve(rootDir, name);\n try {\n const st = await fs.stat(p);\n if (st.isFile()) candidates.push(p);\n } catch {}\n }\n\n // Deduplicate canonical paths\n return Array.from(new Set(candidates));\n}\n\n/**\n * Loads and combines all rule sets discovered in project and storage.\n */\nexport async function loadAllProjectRuleSets(\n rootDir: string,\n storageRulesDir?: string\n): Promise<{ ruleSets: RuleSet[]; sourceFiles: string[] }> {\n const files = await findRuleFiles(rootDir, storageRulesDir);\n const ruleSets: RuleSet[] = [];\n const loadedFiles: string[] = [];\n\n for (const file of files) {\n try {\n const set = await loadRuleSetFile(file);\n ruleSets.push(set);\n loadedFiles.push(file);\n } catch (err: any) {\n // Log or keep error context without failing the entire scan\n console.warn(`[bilmem] Warning: Failed to load rule set from ${file}: ${err.message}`);\n }\n }\n\n return { ruleSets, sourceFiles: loadedFiles };\n}\n","import { createHash } from 'node:crypto';\nimport type {\n Knowledge,\n KnowledgeType,\n KnowledgeStatus,\n KnowledgeStage,\n KnowledgeHistoryEntry,\n KnowledgeOutcomesSummary,\n Scope,\n Observation,\n Evidence,\n Provenance,\n ReconciliationResult,\n ReconciliationAction,\n ConfidenceTier\n} from './types.js';\nimport { determineInitialStage, calculateKnowledgeValue, evaluatePromotion } from './quarantine.js';\nimport type { MetricEvent } from '../stats/types.js';\n\n/**\n * Deterministic mapping between confidence tiers and normalized float scores.\n * Replaces arbitrary precision floats with explainable thresholds.\n */\nexport function tierToConfidence(tier: ConfidenceTier): number {\n switch (tier) {\n case 'low':\n return 0.50;\n case 'medium':\n return 0.75;\n case 'high':\n return 0.95;\n }\n}\n\nexport function confidenceToTier(score: number): ConfidenceTier {\n if (score >= 0.85) return 'high';\n if (score >= 0.65) return 'medium';\n return 'low';\n}\n\nexport function hashString(input: string): string {\n return createHash('sha256').update(input.trim().toLowerCase()).digest('hex').slice(0, 16);\n}\n\nexport function createObservation(params: {\n source: string;\n claim: string;\n provenance?: Provenance;\n confidence?: number;\n positive?: string[];\n negative?: string[];\n details?: Record<string, unknown>;\n}): Observation {\n const provenance: Provenance = params.provenance ?? {\n type: 'file',\n path: params.source,\n timestamp: new Date().toISOString()\n };\n const contentForHash = `${params.source}:${params.claim}:${provenance.commit || provenance.timestamp || Date.now()}`;\n const id = `obs_${hashString(contentForHash)}`;\n\n return {\n id,\n source: params.source,\n claim: params.claim.trim(),\n provenance,\n confidence: Math.min(1.0, Math.max(0.0, params.confidence ?? 0.8)),\n positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),\n negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),\n details: params.details,\n recordedAt: new Date().toISOString()\n };\n}\n\n/**\n * Creates or consolidates evidence based on underlying source.\n * Crucial constraint: Multiple agents reading the same source must NOT count as independent evidence.\n */\nexport function createEvidence(\n observation: Observation,\n type: 'supports' | 'contradicts' | 'observes' = 'supports'\n): Evidence {\n const sourceHash = hashString(observation.source);\n const id = `evi_${sourceHash}`;\n\n return {\n id,\n source: observation.source,\n sourceHash,\n observationIds: [observation.id],\n type,\n claim: observation.claim,\n provenance: observation.provenance,\n weight: 1.0,\n firstObservedAt: observation.recordedAt,\n lastObservedAt: observation.recordedAt\n };\n}\n\nexport function createKnowledge(params: {\n id: string;\n type?: KnowledgeType;\n status?: KnowledgeStatus;\n stage?: KnowledgeStage;\n statement?: string;\n positive?: string[];\n negative?: string[];\n relations?: string[];\n depends_on?: string[];\n resolves?: string[];\n question?: string;\n confidence?: number;\n confidenceTier?: ConfidenceTier;\n evidence?: string[];\n scope?: Scope;\n tags?: string[];\n history?: KnowledgeHistoryEntry[];\n outcomesSummary?: KnowledgeOutcomesSummary;\n lesson?: string;\n failure_details?: Record<string, unknown>;\n context?: Record<string, unknown>;\n contradicts?: string[];\n blocking?: boolean;\n}): Knowledge {\n const now = new Date().toISOString();\n const confidenceTier =\n params.confidenceTier ??\n (params.confidence !== undefined ? confidenceToTier(params.confidence) : undefined);\n const confidence =\n params.confidence !== undefined\n ? params.confidence\n : (confidenceTier ? tierToConfidence(confidenceTier) : 0.85);\n\n const dependsOn = new Set<string>(params.depends_on ?? []);\n if (params.evidence) {\n for (const ev of params.evidence) {\n if (ev.startsWith('file:') || ev.startsWith('test:') || ev.startsWith('code:')) {\n dependsOn.add(ev);\n } else if (ev.includes('/') || ev.endsWith('.json') || ev.endsWith('.yaml') || ev.endsWith('.yml')) {\n dependsOn.add(`file:${ev}`);\n }\n }\n }\n\n const initialStage = params.stage ?? determineInitialStage({\n type: params.type ?? 'lesson',\n evidence: params.evidence ?? [],\n confidence\n });\n\n const status = params.status ?? (initialStage === 'candidate' ? 'candidate' : 'active');\n\n const history: KnowledgeHistoryEntry[] = params.history ?? [\n {\n timestamp: now,\n stage: initialStage,\n status,\n reason: `Initial knowledge recorded (${params.type ?? 'lesson'})`,\n source: params.evidence?.[0]\n }\n ];\n\n const knowledgeItem: Knowledge = {\n id: params.id.trim(),\n type: params.type ?? 'lesson',\n status,\n stage: initialStage,\n statement: (params.statement ?? params.question ?? params.id).trim(),\n positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),\n negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),\n relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),\n depends_on: Array.from(dependsOn),\n resolves: params.resolves ? Array.from(new Set(params.resolves.map((r) => r.trim()))) : undefined,\n question: params.question?.trim(),\n confidence: Math.min(1.0, Math.max(0.0, confidence)),\n confidenceTier,\n evidence: Array.from(new Set(params.evidence ?? [])),\n scope: params.scope ?? 'project',\n tags: Array.from(new Set(params.tags?.map((t) => t.trim().toLowerCase()) ?? [])),\n temporal: {\n revision: 1\n },\n metrics: {\n reuse_count: 0,\n support_count: 1,\n contradiction_count: 0,\n successful_outcomes: 0,\n failed_outcomes: 0\n },\n outcomesSummary: params.outcomesSummary ?? {\n applied: 0,\n successful: 0,\n misleading: 0,\n neutral: 0\n },\n history,\n value: 0,\n lesson: params.lesson,\n failure_details: params.failure_details,\n context: params.context,\n contradicts: params.contradicts ? Array.from(new Set(params.contradicts)) : undefined,\n blocking: params.blocking,\n createdAt: now,\n updatedAt: now\n };\n\n knowledgeItem.value = calculateKnowledgeValue(knowledgeItem);\n return knowledgeItem;\n}\n\n/**\n * Calculate effective confidence combining evidence ratio and outcome history.\n */\nexport function calculateEffectiveConfidence(knowledge: Knowledge): number {\n const m = knowledge.metrics;\n const base = knowledge.confidence;\n\n // Evidence ratio dampening: heavily penalizes contradictions\n const evRatio = (m.support_count + 1) / (m.support_count + 3 * m.contradiction_count + 1);\n\n const totalOutcomes = m.successful_outcomes + m.failed_outcomes;\n let outRatio = 1.0;\n let evWeight = 0.6;\n let outWeight = 0.4;\n\n if (totalOutcomes === 0) {\n evWeight = 0.85;\n outWeight = 0.15;\n } else {\n outRatio = (m.successful_outcomes + 1) / (totalOutcomes + 1);\n }\n\n const effective = base * (evWeight * evRatio + outWeight * outRatio);\n return Math.round(Math.min(1.0, Math.max(0.05, effective)) * 100) / 100;\n}\n\nconst knowledgeTokenCache = new WeakMap<Knowledge, Set<string>>();\n\n/**\n * Tokenize string into lowercase terms for lexical similarity comparison.\n */\nfunction tokenize(text: string): Set<string> {\n return new Set(\n text\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, ' ')\n .split(/\\s+/)\n .filter((t) => t.length > 2)\n );\n}\n\nfunction getCachedTokens(k: Knowledge): Set<string> {\n let tokens = knowledgeTokenCache.get(k);\n if (!tokens) {\n tokens = tokenize(k.statement);\n knowledgeTokenCache.set(k, tokens);\n }\n return tokens;\n}\n\nfunction jaccardSimilarity(setA: Set<string>, setB: Set<string>): number {\n if (setA.size === 0 && setB.size === 0) return 1.0;\n let intersection = 0;\n for (const item of setA) {\n if (setB.has(item)) intersection++;\n }\n const union = setA.size + setB.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n/**\n * Known polar antonym word pairs.\n */\nconst POLAR_ANTONYMS: Array<[string, string]> = [\n ['enabled', 'disabled'],\n ['enable', 'disable'],\n ['active', 'inactive'],\n ['true', 'false'],\n ['on', 'off'],\n ['yes', 'no'],\n ['allowed', 'disallowed'],\n ['allowed', 'prohibited'],\n ['allowed', 'forbidden'],\n ['permit', 'deny'],\n ['allow', 'forbid'],\n ['allow', 'prohibit'],\n ['always', 'never'],\n ['should', 'should not'],\n ['must', 'must not'],\n ['required', 'optional'],\n ['required', 'forbidden'],\n ['valid', 'invalid'],\n ['safe', 'unsafe'],\n ['sync', 'async'],\n ['synchronous', 'asynchronous'],\n ['public', 'private']\n];\n\nconst NEGATION_PATTERNS = [\n /\\bnot\\b/i,\n /\\bnever\\b/i,\n /\\bno\\b/i,\n /\\bcannot\\b/i,\n /\\bwithout\\b/i,\n /\\bdisallow\\b/i,\n /\\bforbid\\b/i,\n /\\bprohibit\\b/i,\n /\\bavoid\\b/i\n];\n\n/**\n * Checks if two statements express opposite polarity or semantic contradiction.\n * Essential rule: High lexical similarity does NOT imply MERGE.\n * \"cache enabled in production\" vs \"cache disabled in production\" have opposite polarity.\n */\nexport function detectPolarContradiction(\n stmtA: string,\n stmtB: string\n): { contradicted: boolean; reason?: string } {\n const lowerA = stmtA.toLowerCase();\n const lowerB = stmtB.toLowerCase();\n\n // 1. Check known antonym pairs\n for (const [pos, neg] of POLAR_ANTONYMS) {\n const aHasPos = new RegExp(`\\\\b${pos}\\\\b`, 'i').test(lowerA);\n const aHasNeg = new RegExp(`\\\\b${neg}\\\\b`, 'i').test(lowerA);\n const bHasPos = new RegExp(`\\\\b${pos}\\\\b`, 'i').test(lowerB);\n const bHasNeg = new RegExp(`\\\\b${neg}\\\\b`, 'i').test(lowerB);\n\n if ((aHasPos && bHasNeg) || (aHasNeg && bHasPos)) {\n return {\n contradicted: true,\n reason: `Polar opposite terms detected: '${pos}' vs '${neg}'`\n };\n }\n }\n\n // 2. Check asymmetric negation (one statement has negation, the other does not)\n const aHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerA));\n const bHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerB));\n\n if (aHasNegation !== bHasNegation) {\n return {\n contradicted: true,\n reason: 'Asymmetric negation detected (one statement asserts, the other negates)'\n };\n }\n\n return { contradicted: false };\n}\n\n/**\n * Reconcile an incoming candidate lesson with existing knowledge.\n * Classifies the action as: NEW, MERGE, UPDATE, SUPPORT, CONTRADICT, SPECIALIZE, GENERALIZE, IGNORE.\n */\nexport function reconcileKnowledge(\n candidate: Partial<Knowledge> & { statement: string },\n existingList: Knowledge[]\n): ReconciliationResult {\n const candidateId = candidate.id?.trim();\n const candidateStmt = candidate.statement.trim();\n const candidateTokens = tokenize(candidateStmt);\n const candidatePos = new Set((candidate.positive ?? []).map((p) => p.trim().toLowerCase()));\n\n // 1. Direct ID match\n if (candidateId) {\n const directMatch = existingList.find((k) => k.id === candidateId);\n if (directMatch) {\n // Check for exact identical statement\n if (directMatch.statement.trim().toLowerCase() === candidateStmt.toLowerCase()) {\n // If evidence is identical and cues are identical, IGNORE to avoid duplicate bloat\n const hasNewEvidence = (candidate.evidence ?? []).some((e) => !directMatch.evidence.includes(e));\n if (!hasNewEvidence) {\n return {\n action: 'IGNORE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Identical statement and evidence already recorded'\n };\n }\n\n return {\n action: 'SUPPORT',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Confirms existing knowledge with new evidence source'\n };\n }\n\n // Check if candidate contradicts existing statement (e.g. contains negation or opposing cues)\n const opposingPositive = (candidate.negative ?? []).some((n) => directMatch.positive.includes(n));\n const opposingNegative = (candidate.positive ?? []).some((p) => directMatch.negative.includes(p));\n const polarCheck = detectPolarContradiction(candidateStmt, directMatch.statement);\n\n if (opposingPositive || opposingNegative || polarCheck.contradicted) {\n // Check if contexts differ (e.g. dev vs prod) -> SPECIALIZE\n const distinctCues = Array.from(candidatePos).filter((c) => !directMatch.positive.includes(c));\n const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);\n const mentionsEnvB = /(production|prod)/i.test(directMatch.statement);\n const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(directMatch.statement);\n const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);\n\n if (distinctCues.length > 0 || (mentionsEnvA && mentionsEnvB) || (mentionsEnvBDev && mentionsEnvAProd)) {\n return {\n action: 'SPECIALIZE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: `Conflicting claim applies under specialized context cues: [${distinctCues.join(', ')}] (${polarCheck.reason || 'opposing polarity'})`\n };\n }\n\n return {\n action: 'CONTRADICT',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: `Contradicts existing statement (${polarCheck.reason || 'opposing polarity'}) for ID \"${directMatch.id}\"`\n };\n }\n\n return {\n action: 'UPDATE',\n targetId: directMatch.id,\n knowledge: directMatch,\n reason: 'Refines or updates existing knowledge statement'\n };\n }\n }\n\n // 2. Search for nearby existing knowledge by statement similarity and contextual overlap\n let bestMatch: Knowledge | null = null;\n let bestSim = 0;\n let bestPolarCheck: { contradicted: boolean; reason?: string } = { contradicted: false };\n\n for (const existing of existingList) {\n const existingTokens = getCachedTokens(existing);\n const sim = jaccardSimilarity(candidateTokens, existingTokens);\n\n // Explicit polar contradiction check: opposing polar assertions on same topic\n const polarCheck = detectPolarContradiction(candidateStmt, existing.statement);\n if (polarCheck.contradicted) {\n let sharedCount = 0;\n for (const t of candidateTokens) {\n if (existingTokens.has(t)) sharedCount++;\n }\n const sharedCues = (candidate.positive ?? []).filter((p) => existing.positive.includes(p));\n if (sharedCount >= 2 || (sharedCues.length >= 1 && sim >= 0.35) || sim >= 0.40) {\n bestMatch = existing;\n bestSim = Math.max(sim, 0.85);\n bestPolarCheck = polarCheck;\n break;\n }\n }\n\n if (sim > bestSim) {\n bestSim = sim;\n bestMatch = existing;\n bestPolarCheck = polarCheck;\n }\n }\n\n if (bestMatch && bestSim >= 0.70) {\n // CRITICAL: First evaluate contradiction and polar polarity BEFORE any merge decision!\n const polarCheck = bestPolarCheck.contradicted\n ? bestPolarCheck\n : detectPolarContradiction(candidateStmt, bestMatch.statement);\n const hasOpposingCues =\n (candidate.negative ?? []).some((n) => bestMatch!.positive.includes(n)) ||\n (candidate.positive ?? []).some((p) => bestMatch!.negative.includes(p));\n\n if (polarCheck.contradicted || hasOpposingCues) {\n // Check if contexts differ (e.g. dev vs prod) -> SPECIALIZE\n const distinctCues = Array.from(candidatePos).filter((c) => !bestMatch!.positive.includes(c));\n const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);\n const mentionsEnvB = /(production|prod)/i.test(bestMatch.statement);\n const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(bestMatch.statement);\n const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);\n\n if (distinctCues.length > 0 || (mentionsEnvA && mentionsEnvB) || (mentionsEnvBDev && mentionsEnvAProd)) {\n return {\n action: 'SPECIALIZE',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `High textual similarity (${Math.round(bestSim * 100)}%) with polar distinction across contexts (${polarCheck.reason || 'opposing cues'})`,\n score: bestSim\n };\n }\n\n return {\n action: 'CONTRADICT',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Contradicting statement (${polarCheck.reason || 'opposing cues'}) despite high textual similarity (${Math.round(bestSim * 100)}%)`,\n score: bestSim\n };\n }\n\n // Only if polarity is compatible and similarity is nearly identical:\n if (bestSim >= 0.90) {\n return {\n action: 'MERGE',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Nearly identical statement (${Math.round(bestSim * 100)}% match) with compatible polarity; merging cues and evidence`,\n score: bestSim\n };\n }\n\n return {\n action: 'SUPPORT',\n targetId: bestMatch.id,\n knowledge: bestMatch,\n reason: `Supports existing knowledge (${Math.round(bestSim * 100)}% similarity) with compatible polarity`,\n score: bestSim\n };\n }\n\n // 3. Fallback: completely new knowledge\n return {\n action: 'NEW',\n reason: 'No conflicting or matching knowledge found; creating new knowledge entry'\n };\n}\n\n/**\n * Apply reconciliation to knowledge base.\n */\nexport function applyReconciliation(\n reconciliation: ReconciliationResult,\n candidate: Partial<Knowledge> & { statement: string },\n existingList: Knowledge[],\n storage?: { appendMetricEvent(event: MetricEvent): Promise<void> }\n): { updatedKnowledge: Knowledge; action: ReconciliationAction; createdId: string } {\n const now = new Date().toISOString();\n\n switch (reconciliation.action) {\n case 'SUPPORT': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.support_count++;\n target.metrics.last_used = now;\n storage?.appendMetricEvent({\n type: 'knowledge_strengthened',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n\n // Add new evidence refs\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) {\n target.evidence.push(e);\n }\n }\n }\n\n // Merge positive/negative cues if any new\n if (candidate.positive) {\n for (const p of candidate.positive) {\n const lp = p.trim().toLowerCase();\n if (!target.positive.includes(lp)) target.positive.push(lp);\n }\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n\n const promotion = evaluatePromotion(target);\n if (promotion.promoted && promotion.newStage) {\n target.stage = promotion.newStage;\n if (target.stage === 'active' && target.status === 'candidate') {\n target.status = 'active';\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: promotion.reason || `Promoted to ${target.stage}`,\n source: candidate.evidence?.[0]\n });\n } else {\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Confirmed with supporting evidence',\n source: candidate.evidence?.[0]\n });\n }\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n return { updatedKnowledge: target, action: 'SUPPORT', createdId: target.id };\n }\n\n case 'MERGE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.support_count++;\n target.metrics.last_used = now;\n storage?.appendMetricEvent({\n type: 'knowledge_merged',\n knowledgeId: target.id,\n targetId: reconciliation.targetId,\n timestamp: Date.now()\n });\n\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) target.evidence.push(e);\n }\n }\n\n if (candidate.positive) {\n for (const p of candidate.positive) {\n const lp = p.trim().toLowerCase();\n if (!target.positive.includes(lp)) target.positive.push(lp);\n }\n }\n\n if (candidate.negative) {\n for (const n of candidate.negative) {\n const ln = n.trim().toLowerCase();\n if (!target.negative.includes(ln)) target.negative.push(ln);\n }\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n\n const promotion = evaluatePromotion(target);\n if (promotion.promoted && promotion.newStage) {\n target.stage = promotion.newStage;\n if (target.stage === 'active' && target.status === 'candidate') {\n target.status = 'active';\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: promotion.reason || `Promoted to ${target.stage}`,\n source: candidate.evidence?.[0]\n });\n } else {\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Merged with compatible knowledge candidate',\n source: candidate.evidence?.[0]\n });\n }\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n return { updatedKnowledge: target, action: 'MERGE', createdId: target.id };\n }\n\n case 'UPDATE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.statement = candidate.statement;\n if (candidate.type) target.type = candidate.type;\n if (candidate.positive) {\n target.positive = Array.from(new Set([...target.positive, ...candidate.positive.map((p) => p.toLowerCase())]));\n }\n if (candidate.negative) {\n target.negative = Array.from(new Set([...target.negative, ...candidate.negative.map((n) => n.toLowerCase())]));\n }\n if (candidate.evidence) {\n target.evidence = Array.from(new Set([...target.evidence, ...candidate.evidence]));\n }\n\n target.temporal = {\n revision: (target.temporal?.revision ?? 1) + 1,\n valid_from: { timestamp: now }\n };\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Updated statement and context',\n source: candidate.evidence?.[0]\n });\n\n target.updatedAt = now;\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n storage?.appendMetricEvent({\n type: 'knowledge_strengthened',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'UPDATE', createdId: target.id };\n }\n\n case 'CONTRADICT': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n target.metrics.contradiction_count++;\n if (candidate.evidence) {\n for (const e of candidate.evidence) {\n if (!target.evidence.includes(e)) target.evidence.push(e);\n }\n }\n\n if (target.metrics.contradiction_count > target.metrics.support_count) {\n target.status = 'contradicted';\n }\n\n if (!target.history) {\n target.history = [\n {\n timestamp: target.createdAt || now,\n stage: target.stage,\n status: target.status,\n reason: 'Initial knowledge recorded',\n source: target.evidence?.[0]\n }\n ];\n }\n target.history.push({\n timestamp: now,\n stage: target.stage,\n status: target.status,\n reason: reconciliation.reason || 'Contradicted by opposing claim',\n source: candidate.evidence?.[0]\n });\n\n target.confidence = calculateEffectiveConfidence(target);\n target.value = calculateKnowledgeValue(target);\n target.updatedAt = now;\n storage?.appendMetricEvent({\n type: 'contradiction_detected',\n knowledgeId: target.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'CONTRADICT', createdId: target.id };\n }\n\n case 'SPECIALIZE': {\n // Create specialized branch\n const parent = existingList.find((k) => k.id === reconciliation.targetId);\n const subContext = candidate.positive?.[0] || 'specialized';\n const newId =\n candidate.id && candidate.id !== parent?.id\n ? candidate.id\n : `${parent ? parent.id : 'knowledge'}.${subContext}`;\n\n const created = createKnowledge({\n id: newId,\n type: candidate.type ?? parent?.type ?? 'lesson',\n statement: candidate.statement,\n positive: candidate.positive ?? [],\n negative: candidate.negative ?? [],\n relations: parent ? [parent.id, ...(candidate.relations ?? [])] : (candidate.relations ?? []),\n confidence: candidate.confidence ?? 0.85,\n evidence: candidate.evidence ?? [],\n scope: candidate.scope ?? parent?.scope ?? 'project',\n tags: candidate.tags ?? parent?.tags ?? []\n });\n\n if (parent) {\n if (!parent.relations.includes(newId)) {\n parent.relations.push(newId);\n }\n if (!parent.history) {\n parent.history = [\n {\n timestamp: parent.createdAt || now,\n stage: parent.stage,\n status: parent.status,\n reason: 'Initial knowledge recorded'\n }\n ];\n }\n parent.history.push({\n timestamp: now,\n stage: parent.stage,\n status: parent.status,\n reason: `Specialized into contextual branch ${newId}`\n });\n }\n\n storage?.appendMetricEvent({\n type: 'knowledge_specialized',\n knowledgeId: created.id,\n timestamp: Date.now()\n });\n\n return { updatedKnowledge: created, action: 'SPECIALIZE', createdId: created.id };\n }\n\n case 'IGNORE': {\n const target = existingList.find((k) => k.id === reconciliation.targetId)!;\n storage?.appendMetricEvent({\n type: 'duplicate_ignored',\n timestamp: Date.now()\n });\n return { updatedKnowledge: target, action: 'IGNORE', createdId: target.id };\n }\n\n case 'NEW':\n default: {\n const newId = candidate.id || `kn_${hashString(candidate.statement)}`;\n const created = createKnowledge({\n id: newId,\n type: candidate.type,\n statement: candidate.statement,\n positive: candidate.positive,\n negative: candidate.negative,\n relations: candidate.relations,\n confidence: candidate.confidence,\n evidence: candidate.evidence,\n scope: candidate.scope,\n tags: candidate.tags\n });\n storage?.appendMetricEvent({\n type: 'knowledge_created',\n knowledgeId: created.id,\n timestamp: Date.now()\n });\n return { updatedKnowledge: created, action: 'NEW', createdId: created.id };\n }\n }\n}\n","import type {\n RuleDefinition,\n RuleSet,\n EvaluatedRule,\n Observation,\n Knowledge\n} from '../core/types.js';\nimport { createObservation } from '../core/knowledge.js';\nimport type { ProjectContext } from '../scanner/filesystem.js';\n\n/**\n * Converts a simple glob-like pattern (e.g. *.sh, src/**\\/*.ts) into a regular expression.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = glob.trim().replace(/^\\//, '').replace(/\\/$/, '');\n pattern = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n pattern = pattern.replace(/\\*\\*/g, '.*');\n pattern = pattern.replace(/\\*/g, '[^/]*');\n pattern = pattern.replace(/\\?/g, '[^/]');\n return new RegExp(`(^|/)${pattern}$`, 'i');\n}\n\n/**\n * Evaluates a single rule condition against the project context.\n */\nexport async function evaluateRule(\n rule: RuleDefinition,\n context: ProjectContext\n): Promise<EvaluatedRule> {\n const cond = rule.when;\n\n // Unconditional rules are always matched\n if (!cond || Object.keys(cond).length === 0) {\n return {\n rule,\n matched: true,\n reason: 'Always active project rule'\n };\n }\n\n const matchedFiles: string[] = [];\n\n // 1. Check has_file condition\n if (cond.has_file) {\n const files = Array.isArray(cond.has_file) ? cond.has_file : [cond.has_file];\n for (const f of files) {\n if (!context.hasFile(f)) {\n return {\n rule,\n matched: false,\n reason: `Required file '${f}' not found in project`\n };\n }\n matchedFiles.push(f);\n }\n }\n\n // 2. Check missing_file condition\n if (cond.missing_file) {\n const files = Array.isArray(cond.missing_file) ? cond.missing_file : [cond.missing_file];\n for (const f of files) {\n if (context.hasFile(f)) {\n return {\n rule,\n matched: false,\n reason: `Prohibited file '${f}' is present in project`\n };\n }\n }\n }\n\n // 3. Check any_file condition\n if (cond.any_file && cond.any_file.length > 0) {\n let anyFound = false;\n for (const f of cond.any_file) {\n if (context.hasFile(f)) {\n anyFound = true;\n matchedFiles.push(f);\n break;\n }\n }\n if (!anyFound) {\n return {\n rule,\n matched: false,\n reason: `None of the expected files were found: ${cond.any_file.join(', ')}`\n };\n }\n }\n\n // 4. Check file_pattern condition\n if (cond.file_pattern) {\n const regex = globToRegex(cond.file_pattern);\n const matches = context.files.filter(\n (f) => regex.test(f.relativePath) || regex.test(f.name)\n );\n if (matches.length === 0) {\n return {\n rule,\n matched: false,\n reason: `No files matched pattern '${cond.file_pattern}'`\n };\n }\n matchedFiles.push(...matches.slice(0, 5).map((m) => m.relativePath));\n }\n\n // 5. Check content_match condition\n if (cond.content_match) {\n const { files: targetFile, pattern } = cond.content_match;\n const regex = new RegExp(pattern, 'm');\n\n if (targetFile) {\n const content = await context.readText(targetFile);\n if (!content || !regex.test(content)) {\n return {\n rule,\n matched: false,\n reason: `Pattern '${pattern}' was not found in file '${targetFile}'`\n };\n }\n matchedFiles.push(targetFile);\n } else {\n // If no specific file target, search across top candidate files\n let contentFound = false;\n for (const file of context.files.slice(0, 20)) {\n const content = await context.readText(file.relativePath);\n if (content && regex.test(content)) {\n contentFound = true;\n matchedFiles.push(file.relativePath);\n break;\n }\n }\n if (!contentFound) {\n return {\n rule,\n matched: false,\n reason: `Pattern '${pattern}' was not found in project files`\n };\n }\n }\n }\n\n return {\n rule,\n matched: true,\n reason: 'Rule condition matched repository structure',\n matchedFiles: matchedFiles.length > 0 ? Array.from(new Set(matchedFiles)) : undefined\n };\n}\n\n/**\n * Evaluates all rules within a RuleSet against the project context.\n */\nexport async function evaluateRuleSet(\n ruleSet: RuleSet,\n context: ProjectContext\n): Promise<EvaluatedRule[]> {\n const results: EvaluatedRule[] = [];\n for (const rule of ruleSet.rules) {\n results.push(await evaluateRule(rule, context));\n }\n return results;\n}\n\n/**\n * Converts a matched rule into a standard Observation.\n */\nexport function ruleToObservation(\n rule: RuleDefinition,\n evaluated: EvaluatedRule,\n sourceFile: string\n): Observation {\n const now = new Date().toISOString();\n const ruleType = rule.type ?? 'rule';\n\n const positive = [\n ...(rule.positive ?? []),\n rule.id,\n ruleType,\n ...(rule.tags ?? [])\n ];\n\n return createObservation({\n source: `rule:${rule.id}`,\n claim: rule.statement,\n confidence: rule.confidence ?? 0.95,\n provenance: {\n type: 'file',\n path: sourceFile,\n timestamp: now\n },\n positive: Array.from(new Set(positive)),\n negative: rule.negative ? [...rule.negative] : [],\n details: {\n ruleId: rule.id,\n ruleName: rule.name,\n ruleType,\n matchedReason: evaluated.reason,\n matchedFiles: evaluated.matchedFiles,\n scope: rule.scope ?? 'project',\n ...(rule.details ?? {})\n }\n });\n}\n\n/**\n * Converts a matched rule into a candidate Knowledge item.\n */\nexport function ruleToCandidateLesson(\n rule: RuleDefinition,\n evaluated: EvaluatedRule,\n obsSource: string\n): Partial<Knowledge> & { statement: string } {\n const ruleType = rule.type ?? 'rule';\n const positive = [\n ...(rule.positive ?? []),\n rule.id,\n ruleType,\n ...(rule.tags ?? [])\n ];\n\n return {\n id: `rule.${rule.id}`,\n type: ruleType,\n statement: rule.statement,\n positive: Array.from(new Set(positive)),\n negative: rule.negative ? [...rule.negative] : [],\n confidence: rule.confidence ?? 0.95,\n evidence: [obsSource],\n scope: rule.scope ?? 'project',\n tags: rule.tags ? [...rule.tags] : []\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/scanner/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { B as BUILTIN_DETECTORS, C as ConventionDetector, a as ConventionRegistry, D as DetectorCategory, P as ProjectContext, S as ScannedFileInfo, W as WalkOptions, b as analyzeProjectStructure, d as defaultRegistry, r as registerConventionDetector, w as walkDirectory } from '../filesystem-
|
|
2
|
-
import {
|
|
1
|
+
export { B as BUILTIN_DETECTORS, C as ConventionDetector, a as ConventionRegistry, D as DetectorCategory, P as ProjectContext, S as ScannedFileInfo, W as WalkOptions, b as analyzeProjectStructure, d as defaultRegistry, r as registerConventionDetector, w as walkDirectory } from '../filesystem-CrAkhr85.js';
|
|
2
|
+
import { q as Observation, f as EvidenceRelation } from '../types-DNlvHsj9.js';
|
|
3
3
|
|
|
4
4
|
interface MarkdownEvidenceResult {
|
|
5
5
|
observations: Observation[];
|
package/dist/scanner/index.js
CHANGED
|
@@ -148,7 +148,7 @@ function createKnowledge(params) {
|
|
|
148
148
|
type: params.type ?? "lesson",
|
|
149
149
|
status,
|
|
150
150
|
stage: initialStage,
|
|
151
|
-
statement: params.statement.trim(),
|
|
151
|
+
statement: (params.statement ?? params.question ?? params.id).trim(),
|
|
152
152
|
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
153
153
|
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
154
154
|
relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
|
|
@@ -178,6 +178,11 @@ function createKnowledge(params) {
|
|
|
178
178
|
},
|
|
179
179
|
history,
|
|
180
180
|
value: 0,
|
|
181
|
+
lesson: params.lesson,
|
|
182
|
+
failure_details: params.failure_details,
|
|
183
|
+
context: params.context,
|
|
184
|
+
contradicts: params.contradicts ? Array.from(new Set(params.contradicts)) : void 0,
|
|
185
|
+
blocking: params.blocking,
|
|
181
186
|
createdAt: now,
|
|
182
187
|
updatedAt: now
|
|
183
188
|
};
|