feat(core): add command, intent, and routing primitives

This commit is contained in:
William Valentin
2026-02-12 22:47:22 -08:00
parent 7ae0fb51c2
commit 6e8984f788
25 changed files with 1469 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
export { ComponentRegistry } from './registry.js';
export type { IntentRule, IntentTarget, IntentTargetType, IntentMatch, ComponentRegistryConfig } from './registry.js';
+74
View File
@@ -0,0 +1,74 @@
import { describe, it, expect } from 'vitest';
import { ComponentRegistry } from './registry.js';
describe('ComponentRegistry', () => {
it('matches exact rules deterministically', () => {
const registry = new ComponentRegistry({ matchThreshold: 0.5 });
registry.loadRules([
{
name: 'status-help',
patterns: ['status'],
target: { type: 'agent', name: 'assistant' },
priority: 1,
enabled: true,
},
]);
const match = registry.match('status');
expect(match?.rule.name).toBe('status-help');
expect(match?.matchedPattern).toBe('status');
expect(match?.score).toBe(1);
});
it('matches wildcard patterns', () => {
const registry = new ComponentRegistry({ matchThreshold: 0.5 });
registry.register({
name: 'deploy-route',
patterns: ['deploy *'],
target: { type: 'agent', name: 'coder' },
priority: 1,
enabled: true,
});
const match = registry.match('deploy api service');
expect(match?.rule.name).toBe('deploy-route');
expect(match?.score).toBeGreaterThan(0.5);
expect(match?.score).toBeLessThanOrEqual(1);
});
it('resolves ties by priority then specificity', () => {
const registry = new ComponentRegistry({ matchThreshold: 0.5 });
registry.loadRules([
{
name: 'low-priority',
patterns: ['deploy *'],
target: { type: 'agent', name: 'assistant' },
priority: 1,
enabled: true,
},
{
name: 'high-priority',
patterns: ['deploy *'],
target: { type: 'agent', name: 'coder' },
priority: 10,
enabled: true,
},
]);
const match = registry.match('deploy dashboard');
expect(match?.rule.name).toBe('high-priority');
});
it('returns null when no rule meets threshold', () => {
const registry = new ComponentRegistry({ matchThreshold: 0.95 });
registry.register({
name: 'weak',
patterns: ['deploy *'],
target: { type: 'agent', name: 'coder' },
priority: 1,
enabled: true,
});
expect(registry.match('deploy dashboard')).toBeNull();
});
});
+142
View File
@@ -0,0 +1,142 @@
export type IntentTargetType = 'agent' | 'skill';
export interface IntentTarget {
type: IntentTargetType;
name: string;
}
export interface IntentRule {
name: string;
patterns: string[];
target: IntentTarget;
priority: number;
enabled: boolean;
}
export interface IntentMatch {
rule: IntentRule;
score: number;
matchedPattern: string;
}
export interface ComponentRegistryConfig {
matchThreshold: number;
}
export class ComponentRegistry {
private readonly matchThreshold: number;
private readonly rules: IntentRule[] = [];
constructor(config: ComponentRegistryConfig) {
this.matchThreshold = config.matchThreshold;
}
register(rule: IntentRule): void {
this.rules.push(rule);
}
loadRules(rules: IntentRule[]): void {
for (const rule of rules) {
this.register(rule);
}
}
list(): IntentRule[] {
return [...this.rules];
}
match(input: string): IntentMatch | null {
const normalizedInput = input.trim().toLowerCase();
if (!normalizedInput) {
return null;
}
let best: { match: IntentMatch; specificity: number } | null = null;
for (const rule of this.rules) {
if (!rule.enabled) {
continue;
}
let bestPatternScore = 0;
let bestPattern = '';
let bestSpecificity = 0;
for (const pattern of rule.patterns) {
const patternResult = this.scorePattern(pattern, normalizedInput);
if (!patternResult) {
continue;
}
if (patternResult.score > bestPatternScore) {
bestPatternScore = patternResult.score;
bestPattern = pattern;
bestSpecificity = patternResult.specificity;
}
}
if (bestPatternScore < this.matchThreshold) {
continue;
}
const match: IntentMatch = {
rule,
score: bestPatternScore,
matchedPattern: bestPattern,
};
if (!best) {
best = { match, specificity: bestSpecificity };
continue;
}
if (match.score > best.match.score) {
best = { match, specificity: bestSpecificity };
continue;
}
if (match.score === best.match.score) {
if (rule.priority > best.match.rule.priority) {
best = { match, specificity: bestSpecificity };
continue;
}
if (rule.priority === best.match.rule.priority && bestSpecificity > best.specificity) {
best = { match, specificity: bestSpecificity };
}
}
}
return best?.match ?? null;
}
private scorePattern(pattern: string, normalizedInput: string): { score: number; specificity: number } | null {
const normalizedPattern = pattern.trim().toLowerCase();
if (!normalizedPattern) {
return null;
}
const wildcardCount = (normalizedPattern.match(/\*/g) ?? []).length;
const literalLength = normalizedPattern.replace(/\*/g, '').length;
const specificity = literalLength / Math.max(normalizedInput.length, 1);
if (wildcardCount === 0) {
if (normalizedInput === normalizedPattern) {
return { score: 1, specificity };
}
if (normalizedInput.includes(normalizedPattern)) {
return { score: Math.min(0.9 + specificity * 0.1, 1), specificity };
}
return null;
}
const escaped = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
const regexPattern = escaped.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
if (!regex.test(normalizedInput)) {
return null;
}
return { score: Math.min(0.8 + specificity * 0.15, 0.99), specificity };
}
}