codehere 0.3.0-rc.6 → 0.3.0-rc.8
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.
|
@@ -65,36 +65,99 @@ const ROUTING_RULES = [
|
|
|
65
65
|
];
|
|
66
66
|
/** Default specialist when no routing rule matches. */
|
|
67
67
|
const DEFAULT_SPECIALIST = 'claude-code';
|
|
68
|
+
/**
|
|
69
|
+
* Tokenize a prompt for trigger matching. Splits on any non-alphanumeric
|
|
70
|
+
* boundary (whitespace, hyphens, punctuation), lowercases everything. So:
|
|
71
|
+
*
|
|
72
|
+
* "Bug fix" → ["bug", "fix"]
|
|
73
|
+
* "set-up auth" → ["set", "up", "auth"] (trigger "set up" still matches via phrase below)
|
|
74
|
+
* "unit-test it" → ["unit", "test", "it"]
|
|
75
|
+
* "prefix matter" → ["prefix", "matter"] (does NOT match `fix`)
|
|
76
|
+
* "hotfixes" → ["hotfixes"] (does NOT match `fix`)
|
|
77
|
+
*
|
|
78
|
+
* Multi-word triggers ("add tests", "create new") are matched against the
|
|
79
|
+
* normalized prompt directly using word boundaries — so they catch
|
|
80
|
+
* "add tests" but not "added testing harness".
|
|
81
|
+
*/
|
|
82
|
+
function tokenize(prompt) {
|
|
83
|
+
const tokens = new Set(prompt
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.split(/[^a-z0-9]+/)
|
|
86
|
+
.filter(Boolean));
|
|
87
|
+
// Normalized form for multi-word phrase matching: ANY non-alphanumeric char
|
|
88
|
+
// (hyphens, underscores, slashes, dots, colons, commas, etc.) becomes a space,
|
|
89
|
+
// then collapse runs of whitespace. This matches the tokenizer's split rule
|
|
90
|
+
// exactly, so multi-word triggers like "add tests" catch all of:
|
|
91
|
+
// "add tests" "add-tests" "add_tests" "add/tests" "add:tests" "add.tests"
|
|
92
|
+
// Note: must NOT collapse the leading/trailing spaces — they're load-bearing
|
|
93
|
+
// for the boundary check below (`.includes(' ' + t + ' ')`).
|
|
94
|
+
const normalized = ' ' + prompt.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim() + ' ';
|
|
95
|
+
return { tokens, normalized };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Test whether a routing trigger matches a prompt. Single-word triggers must
|
|
99
|
+
* appear as whole tokens (no substring matches). Multi-word triggers must
|
|
100
|
+
* appear as a phrase in the normalized prompt (with leading/trailing spaces
|
|
101
|
+
* to enforce word boundaries on the ends).
|
|
102
|
+
*/
|
|
103
|
+
function triggerMatches(trigger, tokens, normalized) {
|
|
104
|
+
const t = trigger.toLowerCase().trim();
|
|
105
|
+
if (!t)
|
|
106
|
+
return false;
|
|
107
|
+
if (t.includes(' ')) {
|
|
108
|
+
return normalized.includes(' ' + t + ' ');
|
|
109
|
+
}
|
|
110
|
+
return tokens.has(t);
|
|
111
|
+
}
|
|
68
112
|
/**
|
|
69
113
|
* Pick the right specialist for a given prompt.
|
|
70
114
|
*
|
|
71
115
|
* Algorithm:
|
|
72
|
-
* 1.
|
|
73
|
-
* 2.
|
|
74
|
-
* 3.
|
|
75
|
-
* 4. If
|
|
76
|
-
*
|
|
77
|
-
*
|
|
116
|
+
* 1. Tokenize the prompt (whole-word matching, no substring false-positives).
|
|
117
|
+
* 2. Walk routing rules in order. Stop at the first rule whose triggers match.
|
|
118
|
+
* 3. Within that rule, return the first preferred adapter that's installed.
|
|
119
|
+
* 4. If that rule had a match but none of its preferred adapters are
|
|
120
|
+
* installed, return DEFAULT_SPECIALIST (or first installed) with a
|
|
121
|
+
* HONEST reason that names the matched rule and the fallback.
|
|
122
|
+
* 5. If no rule matched at all, return DEFAULT_SPECIALIST (or first
|
|
123
|
+
* installed) with a "no intent matched" reason.
|
|
78
124
|
*
|
|
79
125
|
* Returns null if NO specialist is installed (caller must handle).
|
|
126
|
+
*
|
|
127
|
+
* Known limitation: a prompt with multiple intents (e.g. "fix the bug and
|
|
128
|
+
* add tests") routes by the first matching rule, not the dominant intent.
|
|
129
|
+
* The v0.4 LLM classifier inherently handles dominance via confidence
|
|
130
|
+
* scoring; the rule-based path here keeps the deterministic-priority
|
|
131
|
+
* contract instead.
|
|
80
132
|
*/
|
|
81
133
|
export function pickSpecialist(prompt, installedAgentIds) {
|
|
82
134
|
if (installedAgentIds.length === 0) {
|
|
83
135
|
return null;
|
|
84
136
|
}
|
|
85
|
-
const
|
|
137
|
+
const installedSet = new Set(installedAgentIds);
|
|
138
|
+
const { tokens, normalized } = tokenize(prompt);
|
|
86
139
|
for (const rule of ROUTING_RULES) {
|
|
87
|
-
const matches = rule.triggers.some((t) =>
|
|
140
|
+
const matches = rule.triggers.some((t) => triggerMatches(t, tokens, normalized));
|
|
88
141
|
if (!matches)
|
|
89
142
|
continue;
|
|
143
|
+
// Rule matched. Walk preferred specialists in order.
|
|
90
144
|
for (const candidate of rule.preferred) {
|
|
91
|
-
if (
|
|
145
|
+
if (installedSet.has(candidate)) {
|
|
92
146
|
return { agentId: candidate, reason: rule.reason };
|
|
93
147
|
}
|
|
94
148
|
}
|
|
149
|
+
// Rule matched but none of its preferred specialists are installed.
|
|
150
|
+
// Fall through with an HONEST reason that names the matched rule + fallback,
|
|
151
|
+
// so the audit log records what actually happened (Codex review caught
|
|
152
|
+
// the prior version saying "default routing" here, which was a lie).
|
|
153
|
+
const fallbackId = installedSet.has(DEFAULT_SPECIALIST) ? DEFAULT_SPECIALIST : installedAgentIds[0];
|
|
154
|
+
return {
|
|
155
|
+
agentId: fallbackId,
|
|
156
|
+
reason: `${rule.reason} — but none of [${rule.preferred.join(', ')}] installed; falling back to ${fallbackId}`,
|
|
157
|
+
};
|
|
95
158
|
}
|
|
96
|
-
// No rule matched
|
|
97
|
-
if (
|
|
159
|
+
// No rule matched. Default routing.
|
|
160
|
+
if (installedSet.has(DEFAULT_SPECIALIST)) {
|
|
98
161
|
return { agentId: DEFAULT_SPECIALIST, reason: 'default routing (no specific intent matched)' };
|
|
99
162
|
}
|
|
100
163
|
return { agentId: installedAgentIds[0], reason: 'fallback to first available specialist' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codehere",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.8",
|
|
4
4
|
"description": "A PM for your AI coding agents. Delegate, orchestrate, and audit Claude Code, Codex, Aider, and OpenCode from one local web UI — every AI action traced, every file scored.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|