Skip to main content

wm_tools/
nlu.rs

1//! Embedding-based NLU router for the `wm` meta-tool.
2//!
3//! Replaces the 450-line keyword `if-else` chain with a data-driven TF-IDF
4//! cosine similarity router. Each tool has a weighted keyword profile. Input
5//! text is tokenized and compared against all profiles using cosine similarity.
6//!
7//! Advantages over the keyword chain:
8//! - Scales to hundreds of tools without code changes (just add profiles)
9//! - Handles partial matches and multi-word queries naturally
10//! - Confidence score reflects actual semantic overlap, not arbitrary 0.9/1.0
11//! - No ordering dependency — all profiles scored independently
12
13use ahash::AHashMap;
14
15/// A tool routing profile with weighted keywords.
16#[derive(Debug, Clone)]
17pub struct ToolProfile {
18    pub tool_name: &'static str,
19    /// Weighted keywords — (term, weight) pairs.
20    /// Multi-word phrases are split into individual tokens.
21    pub keywords: &'static [(&'static str, f64)],
22}
23
24/// All tool profiles, ordered roughly by specificity (most specific first
25/// for tie-breaking, though cosine similarity makes this less critical).
26pub static TOOL_PROFILES: &[ToolProfile] = &[
27    // ── Memory operations ──────────────────────────────────────────
28    ToolProfile {
29        tool_name: "memory.create",
30        keywords: &[
31            ("remember", 7.0),
32            ("store", 7.0),
33            ("save", 5.0),
34            ("memorize", 5.0),
35            ("record", 2.0),
36            ("persist", 2.0),
37            ("capture", 1.5),
38        ],
39    },
40    ToolProfile {
41        tool_name: "memory.read",
42        keywords: &[
43            ("recall", 3.0),
44            ("read", 2.5),
45            ("fetch", 2.5),
46            ("get", 1.5),
47            ("retrieve", 2.5),
48            ("memory", 1.0),
49            ("load", 2.0),
50            ("access", 1.5),
51            ("view", 1.5),
52            ("show", 1.5),
53        ],
54    },
55    ToolProfile {
56        tool_name: "memory.list",
57        keywords: &[
58            ("list", 3.0),
59            ("show", 2.0),
60            ("all", 1.5),
61            ("memories", 2.0),
62            ("browse", 2.0),
63            ("enumerate", 2.0),
64            ("display", 2.0),
65            ("view", 1.5),
66        ],
67    },
68    ToolProfile {
69        tool_name: "memory.delete",
70        keywords: &[
71            ("delete", 3.0),
72            ("remove", 2.5),
73            ("forget", 2.5),
74            ("erase", 2.5),
75            ("destroy", 2.0),
76            ("purge", 1.5),
77            ("drop", 2.0),
78            ("clear", 1.5),
79            ("discard", 2.0),
80        ],
81    },
82    ToolProfile {
83        tool_name: "memory.search",
84        keywords: &[
85            ("search", 3.5),
86            ("find", 2.0),
87            ("query", 1.0),
88            ("lookup", 2.0),
89            ("fulltext", 2.5),
90            ("full-text", 2.5),
91            ("seek", 1.5),
92            ("locate", 1.5),
93            ("grep", 2.0),
94        ],
95    },
96    ToolProfile {
97        tool_name: "memory.chat",
98        keywords: &[
99            ("chat", 3.5),
100            ("conversational", 3.0),
101            ("converse", 2.5),
102            ("talk", 2.0),
103            ("ask", 2.0),
104            ("discuss", 2.0),
105            ("explore", 1.5),
106            ("browse", 1.5),
107            ("hybrid", 2.0),
108        ],
109    },
110    ToolProfile {
111        tool_name: "memory.vector.search",
112        keywords: &[
113            ("vector", 3.0),
114            ("embedding", 3.0),
115            ("similar", 2.5),
116            ("similarity", 3.0),
117            ("semantic", 2.5),
118            ("nearest", 2.0),
119            ("neighbors", 1.5),
120            ("cosine", 2.0),
121            ("ann", 2.0),
122            ("alike", 2.0),
123            ("like", 1.0),
124            ("close", 1.5),
125        ],
126    },
127    ToolProfile {
128        tool_name: "memory.query",
129        keywords: &[
130            ("query", 3.0),
131            ("filter", 2.5),
132            ("where", 1.5),
133            ("select", 2.0),
134            ("condition", 2.0),
135            ("criteria", 2.0),
136            ("match", 1.5),
137            ("search", 1.0),
138        ],
139    },
140    ToolProfile {
141        tool_name: "memory.hybrid_recall",
142        keywords: &[
143            ("hybrid", 3.0),
144            ("smart", 2.5),
145            ("combined", 2.5),
146            ("recall", 1.5),
147            ("intelligent", 2.0),
148            ("fusion", 2.0),
149        ],
150    },
151    ToolProfile {
152        tool_name: "memory.associate",
153        keywords: &[
154            ("associate", 3.0),
155            ("link", 2.5),
156            ("connect", 2.0),
157            ("relate", 2.5),
158            ("tie", 1.5),
159            ("bind", 1.5),
160        ],
161    },
162    ToolProfile {
163        tool_name: "memory.associations",
164        keywords: &[
165            ("associations", 3.0),
166            ("links", 2.5),
167            ("related", 2.5),
168            ("connections", 2.0),
169            ("edges", 2.0),
170            ("neighbors", 1.5),
171        ],
172    },
173    ToolProfile {
174        tool_name: "memory.associate_mine",
175        keywords: &[
176            ("mine", 4.0),
177            ("discover", 2.0),
178            ("associations", 1.5),
179            ("uncover", 2.0),
180            ("excavate", 2.0),
181        ],
182    },
183    ToolProfile {
184        tool_name: "memory.consolidate",
185        keywords: &[
186            ("consolidate", 3.0),
187            ("deduplicate", 3.0),
188            ("dedup", 3.0),
189            ("merge", 2.0),
190            ("duplicate", 2.0),
191            ("combine", 1.5),
192        ],
193    },
194    ToolProfile {
195        tool_name: "memory.decay",
196        keywords: &[
197            ("decay", 3.0),
198            ("age", 2.0),
199            ("expire", 2.5),
200            ("stale", 2.0),
201            ("rot", 1.5),
202            ("degrade", 2.0),
203        ],
204    },
205    ToolProfile {
206        tool_name: "memory.batch_read",
207        keywords: &[
208            ("batch", 3.0),
209            ("multiple", 2.5),
210            ("bulk", 2.5),
211            ("read", 1.0),
212            ("many", 2.0),
213            ("several", 1.5),
214        ],
215    },
216    ToolProfile {
217        tool_name: "memory.update",
218        keywords: &[
219            ("update", 5.0),
220            ("modify", 2.0),
221            ("change", 2.0),
222            ("edit", 2.0),
223            ("alter", 1.5),
224            ("revise", 1.5),
225            ("memory", 1.0),
226            ("amend", 1.5),
227            ("patch", 1.5),
228        ],
229    },
230    ToolProfile {
231        tool_name: "memory.tag",
232        keywords: &[
233            ("tag", 5.0),
234            ("label", 2.0),
235            ("retag", 3.0),
236            ("categorize", 2.0),
237            ("mark", 1.5),
238            ("add", 1.5),
239        ],
240    },
241    ToolProfile {
242        tool_name: "memory.stats",
243        keywords: &[
244            ("stats", 3.0),
245            ("statistics", 3.0),
246            ("summary", 2.0),
247            ("memory", 1.0),
248            ("galaxy", 1.5),
249            ("count", 1.0),
250        ],
251    },
252    ToolProfile {
253        tool_name: "memory.count",
254        keywords: &[
255            ("count", 3.0),
256            ("how", 1.5),
257            ("many", 2.0),
258            ("number", 2.5),
259            ("total", 2.0),
260            ("memories", 1.5),
261        ],
262    },
263    ToolProfile {
264        tool_name: "memory.tags",
265        keywords: &[
266            ("tags", 4.0),
267            ("labels", 2.0),
268            ("categories", 1.5),
269            ("list", 1.0),
270        ],
271    },
272    ToolProfile {
273        tool_name: "memory.nearby",
274        keywords: &[
275            ("nearby", 3.0),
276            ("near", 2.5),
277            ("close", 2.0),
278            ("spatial", 2.5),
279            ("proximity", 2.5),
280            ("surrounding", 2.0),
281            ("adjacent", 2.0),
282        ],
283    },
284    // ── Session ────────────────────────────────────────────────────
285    ToolProfile {
286        tool_name: "session.start",
287        keywords: &[
288            ("start", 3.0),
289            ("new", 2.5),
290            ("begin", 2.5),
291            ("open", 2.0),
292            ("session", 2.0),
293            ("create", 1.5),
294        ],
295    },
296    ToolProfile {
297        tool_name: "session.checkpoint",
298        keywords: &[
299            ("checkpoint", 3.0),
300            ("snapshot", 3.0),
301            ("save", 1.5),
302            ("point", 2.0),
303            ("marker", 2.5),
304        ],
305    },
306    ToolProfile {
307        tool_name: "session.recall",
308        keywords: &[
309            ("recall", 2.0),
310            ("session", 3.0),
311            ("history", 2.5),
312            ("replay", 2.5),
313            ("previous", 1.5),
314        ],
315    },
316    ToolProfile {
317        tool_name: "session.end",
318        keywords: &[
319            ("end", 3.0),
320            ("close", 2.5),
321            ("stop", 2.0),
322            ("finish", 2.5),
323            ("terminate", 2.5),
324            ("session", 1.5),
325        ],
326    },
327    ToolProfile {
328        tool_name: "session.list",
329        keywords: &[
330            ("list", 2.5),
331            ("show", 2.0),
332            ("all", 1.5),
333            ("sessions", 3.0),
334            ("history", 1.5),
335        ],
336    },
337    // ── Consciousness ──────────────────────────────────────────────
338    ToolProfile {
339        tool_name: "citta.status",
340        keywords: &[
341            ("citta", 3.0),
342            ("consciousness", 2.5),
343            ("status", 1.5),
344            ("vector", 2.0),
345            ("awareness", 2.0),
346        ],
347    },
348    ToolProfile {
349        tool_name: "citta.reflect",
350        keywords: &[
351            ("reflect", 5.0),
352            ("introspect", 3.0),
353            ("meditate", 2.5),
354            ("contemplate", 2.5),
355            ("self", 1.5),
356            ("examine", 1.5),
357        ],
358    },
359    ToolProfile {
360        tool_name: "citta.coherence",
361        keywords: &[
362            ("coherence", 3.0),
363            ("coherent", 3.0),
364            ("write", 1.5),
365            ("permitted", 2.5),
366            ("allowed", 2.0),
367            ("can", 1.0),
368        ],
369    },
370    ToolProfile {
371        tool_name: "dream.trigger",
372        keywords: &[
373            ("trigger", 3.0),
374            ("start", 1.5),
375            ("initiate", 2.5),
376            ("dream", 3.0),
377            ("begin", 1.5),
378            ("sleep", 1.5),
379        ],
380    },
381    ToolProfile {
382        tool_name: "dream.status",
383        keywords: &[
384            ("dream", 3.0),
385            ("status", 2.0),
386            ("cycle", 2.5),
387            ("sleep", 2.0),
388            ("phase", 2.0),
389        ],
390    },
391    // ── Tools management ───────────────────────────────────────────
392    ToolProfile {
393        tool_name: "tools.effectiveness_report",
394        keywords: &[
395            ("effectiveness", 4.0),
396            ("performance", 1.5),
397            ("report", 2.0),
398            ("efficiency", 2.0),
399        ],
400    },
401    ToolProfile {
402        tool_name: "tools.retire",
403        keywords: &[
404            ("retire", 3.0),
405            ("decommission", 3.0),
406            ("remove", 1.5),
407            ("disable", 2.0),
408            ("tool", 1.0),
409            ("sunset", 2.5),
410        ],
411    },
412    ToolProfile {
413        tool_name: "tools.list",
414        keywords: &[
415            ("tools", 3.0),
416            ("list", 2.0),
417            ("available", 2.0),
418            ("catalog", 2.5),
419            ("inventory", 2.0),
420            ("discover", 1.5),
421        ],
422    },
423    // ── Patterns ───────────────────────────────────────────────────
424    ToolProfile {
425        tool_name: "pattern.search",
426        keywords: &[
427            ("pattern", 4.0),
428            ("recurring", 3.0),
429            ("repeating", 2.5),
430            ("cycle", 2.0),
431            ("frequency", 2.0),
432            ("regularity", 2.0),
433        ],
434    },
435    ToolProfile {
436        tool_name: "salience.spotlight",
437        keywords: &[
438            ("salience", 4.0),
439            ("spotlight", 4.0),
440            ("important", 1.0),
441            ("prominent", 2.0),
442            ("notable", 2.0),
443            ("highlight", 2.0),
444        ],
445    },
446    ToolProfile {
447        tool_name: "serendipity.surface",
448        keywords: &[
449            ("serendipity", 3.0),
450            ("serendipit", 3.0),
451            ("unexpected", 2.5),
452            ("surprising", 2.5),
453            ("connection", 1.5),
454            ("surface", 1.5),
455        ],
456    },
457    // ── Constellation ──────────────────────────────────────────────
458    ToolProfile {
459        tool_name: "constellation.detect",
460        keywords: &[
461            ("constellation", 3.0),
462            ("detect", 4.0),
463            ("cluster", 3.0),
464            ("grouping", 2.0),
465            ("density", 2.0),
466        ],
467    },
468    ToolProfile {
469        tool_name: "constellation.list",
470        keywords: &[
471            ("constellation", 3.0),
472            ("list", 4.0),
473            ("show", 2.0),
474            ("clusters", 2.0),
475        ],
476    },
477    // ── Autonomous Cycles (Phase E) ────────────────────────────────
478    ToolProfile {
479        tool_name: "consolidation.connect",
480        keywords: &[
481            ("connect", 2.5),
482            ("disconnected", 3.0),
483            ("link", 2.0),
484            ("propose", 2.0),
485            ("connection", 2.0),
486            ("bridge", 2.0),
487            ("unlinked", 2.5),
488        ],
489    },
490    ToolProfile {
491        tool_name: "consolidation.compress",
492        keywords: &[
493            ("compress", 3.0),
494            ("merge", 2.5),
495            ("redundancy", 3.0),
496            ("overlapping", 2.5),
497            ("reduce", 2.0),
498            ("deduplicate", 2.0),
499        ],
500    },
501    ToolProfile {
502        tool_name: "emergence.scan",
503        keywords: &[
504            ("emergence", 3.0),
505            ("emerging", 3.0),
506            ("scan", 2.0),
507            ("tag", 1.5),
508            ("topic", 2.5),
509            ("trend", 2.5),
510            ("pattern", 1.5),
511        ],
512    },
513    ToolProfile {
514        tool_name: "retention.prune",
515        keywords: &[
516            ("prune", 3.0),
517            ("forget", 2.0),
518            ("ready", 2.0),
519            ("forgettable", 3.0),
520            ("retention", 2.5),
521            ("candidate", 2.0),
522            ("cleanup", 1.5),
523        ],
524    },
525    // ── Spiral Report (Phase F) ────────────────────────────────────
526    ToolProfile {
527        tool_name: "spiral.report",
528        keywords: &[
529            ("spiral", 3.0),
530            ("autonomy", 2.5),
531            ("circular", 2.5),
532            ("thinking", 2.0),
533            ("expansion", 2.5),
534            ("novelty", 3.0),
535            ("report", 1.5),
536        ],
537    },
538    // ── Galaxy ─────────────────────────────────────────────────────
539    ToolProfile {
540        tool_name: "galaxy.stats",
541        keywords: &[
542            ("galaxy", 3.0),
543            ("stats", 3.0),
544            ("count", 1.5),
545            ("overview", 2.0),
546        ],
547    },
548    ToolProfile {
549        tool_name: "galaxy.export",
550        keywords: &[
551            ("export", 3.0),
552            ("backup", 2.5),
553            ("dump", 2.0),
554            ("galaxy", 2.0),
555            ("save", 1.5),
556        ],
557    },
558    ToolProfile {
559        tool_name: "galaxy.import",
560        keywords: &[
561            ("import", 3.0),
562            ("restore", 3.0),
563            ("load", 2.0),
564            ("galaxy", 2.0),
565            ("ingest", 2.5),
566        ],
567    },
568    // ── Karma ──────────────────────────────────────────────────────
569    ToolProfile {
570        tool_name: "karma.report",
571        keywords: &[
572            ("karma", 3.0),
573            ("debt", 2.5),
574            ("ledger", 2.5),
575            ("balance", 2.0),
576            ("report", 1.5),
577        ],
578    },
579    ToolProfile {
580        tool_name: "karma.history",
581        keywords: &[
582            ("karma", 2.5),
583            ("history", 3.0),
584            ("log", 2.5),
585            ("entries", 2.5),
586            ("record", 1.5),
587            ("past", 2.0),
588        ],
589    },
590    ToolProfile {
591        tool_name: "karma.clear",
592        keywords: &[
593            ("clear", 3.0),
594            ("wipe", 3.0),
595            ("reset", 2.5),
596            ("purge", 2.5),
597            ("karma", 2.0),
598            ("clean", 2.0),
599        ],
600    },
601    // ── Dharma ─────────────────────────────────────────────────────
602    ToolProfile {
603        tool_name: "dharma.status",
604        keywords: &[
605            ("dharma", 3.0),
606            ("governance", 2.5),
607            ("ethics", 2.5),
608            ("status", 1.5),
609            ("rules", 1.0),
610        ],
611    },
612    ToolProfile {
613        tool_name: "dharma.rules",
614        keywords: &[
615            ("dharma", 2.5),
616            ("rules", 3.0),
617            ("governance", 2.0),
618            ("ethics", 2.0),
619            ("laws", 2.5),
620            ("principles", 2.0),
621        ],
622    },
623    ToolProfile {
624        tool_name: "dharma.audit",
625        keywords: &[
626            ("dharma", 2.0),
627            ("audit", 3.0),
628            ("governance", 2.0),
629            ("ethics", 1.5),
630            ("inspect", 2.5),
631            ("review", 2.0),
632        ],
633    },
634    ToolProfile {
635        tool_name: "dharma.profiles",
636        keywords: &[
637            ("dharma", 2.0),
638            ("profiles", 3.0),
639            ("governance", 1.5),
640            ("ethics", 1.5),
641            ("modes", 2.0),
642            ("configurations", 2.0),
643        ],
644    },
645    // ── Harmony / Substrate ────────────────────────────────────────
646    ToolProfile {
647        tool_name: "harmony.vector",
648        keywords: &[
649            ("harmony", 3.0),
650            ("substrate", 3.0),
651            ("hardware", 2.5),
652            ("resource", 2.0),
653            ("cpu", 2.5),
654            ("memory", 1.5),
655            ("pressure", 2.5),
656            ("thermal", 2.5),
657            ("battery", 2.5),
658            ("system", 1.0),
659            ("vector", 2.0),
660            ("status", 1.5),
661        ],
662    },
663    ToolProfile {
664        tool_name: "harmony.history",
665        keywords: &[
666            ("harmony", 2.5),
667            ("history", 3.0),
668            ("substrate", 2.0),
669            ("hardware", 2.0),
670            ("resource", 1.5),
671            ("past", 1.5),
672            ("timeline", 2.0),
673        ],
674    },
675    // ── Gnosis / Transparency ──────────────────────────────────────
676    ToolProfile {
677        tool_name: "gnosis.status",
678        keywords: &[
679            ("gnosis", 3.0),
680            ("transparency", 3.0),
681            ("governance", 2.0),
682            ("status", 2.0),
683            ("overview", 2.0),
684            ("full", 1.5),
685            ("layers", 2.0),
686        ],
687    },
688    ToolProfile {
689        tool_name: "gnosis.history",
690        keywords: &[
691            ("gnosis", 2.5),
692            ("history", 3.0),
693            ("transparency", 2.0),
694            ("governance", 1.5),
695            ("audit", 2.0),
696            ("past", 1.5),
697        ],
698    },
699    ToolProfile {
700        tool_name: "gnosis.explain",
701        keywords: &[
702            ("gnosis", 2.0),
703            ("explain", 3.0),
704            ("why", 3.0),
705            ("blocked", 2.5),
706            ("allowed", 2.5),
707            ("verdict", 3.0),
708            ("reason", 2.5),
709            ("governance", 1.5),
710        ],
711    },
712    // ── Agents ─────────────────────────────────────────────────────
713    ToolProfile {
714        tool_name: "agent.register",
715        keywords: &[
716            ("register", 3.0),
717            ("agent", 3.0),
718            ("new", 2.0),
719            ("create", 2.0),
720            ("add", 2.0),
721            ("enroll", 2.5),
722        ],
723    },
724    ToolProfile {
725        tool_name: "agent.list",
726        keywords: &[
727            ("list", 2.5),
728            ("show", 2.0),
729            ("all", 1.5),
730            ("agents", 3.0),
731            ("roster", 2.5),
732        ],
733    },
734    ToolProfile {
735        tool_name: "agent.heartbeat",
736        keywords: &[
737            ("heartbeat", 3.0),
738            ("alive", 2.5),
739            ("ping", 2.5),
740            ("agent", 2.0),
741            ("status", 1.5),
742            ("check", 1.5),
743        ],
744    },
745    // ── Agent management (Tier 6) ────────────────────────────────
746    ToolProfile {
747        tool_name: "agent.trust",
748        keywords: &[
749            ("trust", 4.0),
750            ("reliability", 3.0),
751            ("confidence", 2.5),
752            ("agent", 2.0),
753            ("score", 2.0),
754            ("rating", 2.0),
755        ],
756    },
757    ToolProfile {
758        tool_name: "agent.descriptions",
759        keywords: &[
760            ("description", 4.0),
761            ("describe", 3.5),
762            ("agent", 2.0),
763            ("info", 2.0),
764            ("about", 2.0),
765            ("profile", 2.5),
766        ],
767    },
768    ToolProfile {
769        tool_name: "agent.capabilities",
770        keywords: &[
771            ("capabilities", 4.0),
772            ("capability", 3.5),
773            ("skills", 3.0),
774            ("agent", 2.0),
775            ("abilities", 2.5),
776            ("features", 2.0),
777        ],
778    },
779    ToolProfile {
780        tool_name: "agent.heartbeat.history",
781        keywords: &[
782            ("heartbeat", 3.5),
783            ("history", 3.5),
784            ("agent", 2.0),
785            ("log", 2.5),
786            ("record", 2.0),
787            ("past", 2.0),
788        ],
789    },
790    ToolProfile {
791        tool_name: "agent.deregister",
792        keywords: &[
793            ("deregister", 4.0),
794            ("unregister", 4.0),
795            ("remove", 3.0),
796            ("delete", 2.5),
797            ("agent", 2.0),
798            ("revoke", 3.0),
799        ],
800    },
801    // ── Tasks ──────────────────────────────────────────────────────
802    ToolProfile {
803        tool_name: "task.distribute",
804        keywords: &[
805            ("distribute", 3.0),
806            ("assign", 2.5),
807            ("dispatch", 2.0),
808            ("task", 3.0),
809            ("delegate", 2.5),
810            ("allocate", 2.0),
811        ],
812    },
813    ToolProfile {
814        tool_name: "task.status",
815        keywords: &[
816            ("task", 2.5),
817            ("status", 3.0),
818            ("progress", 2.5),
819            ("check", 2.0),
820            ("track", 2.0),
821        ],
822    },
823    // ── System ─────────────────────────────────────────────────────
824    ToolProfile {
825        tool_name: "system.health",
826        keywords: &[
827            ("system", 2.5),
828            ("health", 3.0),
829            ("check", 2.0),
830            ("diagnostic", 3.0),
831            ("doctor", 2.5),
832            ("status", 1.5),
833        ],
834    },
835    ToolProfile {
836        tool_name: "system.config",
837        keywords: &[
838            ("system", 2.0),
839            ("config", 3.0),
840            ("configuration", 3.0),
841            ("settings", 2.5),
842            ("info", 2.0),
843            ("setup", 2.0),
844        ],
845    },
846    ToolProfile {
847        tool_name: "system.flush",
848        keywords: &[
849            ("flush", 3.0),
850            ("garbage", 2.5),
851            ("collect", 2.0),
852            ("gc", 3.0),
853            ("cleanup", 2.5),
854            ("purge", 1.5),
855            ("clear", 1.5),
856        ],
857    },
858    // ── Knowledge graph ───────────────────────────────────────────
859    ToolProfile {
860        tool_name: "kg.extract",
861        keywords: &[
862            ("extract", 3.0),
863            ("entity", 3.0),
864            ("entities", 2.5),
865            ("relationship", 2.5),
866            ("triple", 2.5),
867            ("knowledge", 2.0),
868            ("graph", 2.0),
869            ("ner", 3.0),
870        ],
871    },
872    ToolProfile {
873        tool_name: "kg.query",
874        keywords: &[
875            ("knowledge", 2.0),
876            ("graph", 2.0),
877            ("relationship", 2.5),
878            ("entity", 2.0),
879            ("connected", 2.0),
880            ("subgraph", 3.0),
881            ("neighborhood", 2.0),
882        ],
883    },
884    ToolProfile {
885        tool_name: "kg.top",
886        keywords: &[
887            ("hub", 3.0),
888            ("god", 2.5),
889            ("node", 2.0),
890            ("top", 2.5),
891            ("ranked", 2.0),
892            ("central", 2.5),
893            ("important", 1.5),
894            ("knowledge", 1.5),
895            ("graph", 1.5),
896        ],
897    },
898    // ── Graph traversal ───────────────────────────────────────────
899    ToolProfile {
900        tool_name: "graph.walk",
901        keywords: &[
902            ("walk", 3.0),
903            ("traverse", 3.0),
904            ("bfs", 3.0),
905            ("explore", 2.0),
906            ("path", 2.0),
907            ("hop", 2.5),
908            ("follow", 2.0),
909            ("graph", 1.5),
910        ],
911    },
912    ToolProfile {
913        tool_name: "graph.community",
914        keywords: &[
915            ("community", 3.0),
916            ("cluster", 3.0),
917            ("communities", 2.5),
918            ("label", 2.0),
919            ("propagation", 2.5),
920            ("group", 2.0),
921            ("modularity", 2.5),
922        ],
923    },
924    ToolProfile {
925        tool_name: "graph.propagate",
926        keywords: &[
927            ("propagate", 3.0),
928            ("activation", 3.0),
929            ("spread", 2.5),
930            ("ripple", 2.5),
931            ("diffuse", 2.0),
932            ("energy", 2.0),
933            ("signal", 2.0),
934        ],
935    },
936    // ── Galaxy management ─────────────────────────────────────────
937    ToolProfile {
938        tool_name: "galaxy.transfer",
939        keywords: &[
940            ("transfer", 4.0),
941            ("move", 3.0),
942            ("relocate", 3.0),
943            ("migrate", 2.5),
944            ("galaxy", 1.5),
945        ],
946    },
947    ToolProfile {
948        tool_name: "galaxy.merge",
949        keywords: &[
950            ("merge", 4.0),
951            ("combine", 2.5),
952            ("unify", 2.5),
953            ("galaxy", 1.5),
954            ("absorb", 2.0),
955        ],
956    },
957    ToolProfile {
958        tool_name: "galaxy.snapshot",
959        keywords: &[
960            ("snapshot", 4.0),
961            ("backup", 3.0),
962            ("checkpoint", 2.5),
963            ("capture", 2.0),
964            ("galaxy", 1.5),
965            ("preserve", 2.0),
966        ],
967    },
968    ToolProfile {
969        tool_name: "galaxy.restore",
970        keywords: &[
971            ("restore", 4.0),
972            ("recover", 3.0),
973            ("rollback", 3.0),
974            ("revert", 2.5),
975            ("galaxy", 1.5),
976            ("undo", 2.0),
977        ],
978    },
979    // ── Galaxy management (Tier 6) ───────────────────────────────
980    ToolProfile {
981        tool_name: "galaxy.dashboard",
982        keywords: &[
983            ("dashboard", 4.0),
984            ("overview", 3.5),
985            ("summary", 3.0),
986            ("galaxy", 2.0),
987            ("panel", 2.5),
988            ("report", 2.0),
989        ],
990    },
991    ToolProfile {
992        tool_name: "galaxy.backup",
993        keywords: &[
994            ("backup", 4.0),
995            ("archive", 3.0),
996            ("dump", 3.0),
997            ("galaxy", 2.0),
998            ("save", 2.0),
999            ("copy", 2.0),
1000        ],
1001    },
1002    ToolProfile {
1003        tool_name: "galaxy.taxonomy",
1004        keywords: &[
1005            ("taxonomy", 4.0),
1006            ("classification", 3.0),
1007            ("categories", 2.5),
1008            ("galaxy", 2.0),
1009            ("list", 1.5),
1010            ("types", 2.0),
1011        ],
1012    },
1013    ToolProfile {
1014        tool_name: "galaxy.purge",
1015        keywords: &[
1016            ("purge", 4.0),
1017            ("clear", 3.0),
1018            ("wipe", 3.5),
1019            ("empty", 2.5),
1020            ("galaxy", 2.0),
1021            ("clean", 2.5),
1022        ],
1023    },
1024    ToolProfile {
1025        tool_name: "galaxy.health",
1026        keywords: &[
1027            ("health", 4.0),
1028            ("diagnostic", 3.0),
1029            ("checkup", 3.0),
1030            ("galaxy", 2.0),
1031            ("status", 2.0),
1032            ("integrity", 2.5),
1033        ],
1034    },
1035    // ── Archaeology & learning ────────────────────────────────────
1036    ToolProfile {
1037        tool_name: "archaeology.search",
1038        keywords: &[
1039            ("archaeology", 4.0),
1040            ("excavate", 3.5),
1041            ("strata", 3.0),
1042            ("layer", 2.5),
1043            ("depth", 2.0),
1044            ("history", 2.0),
1045            ("timeline", 2.5),
1046            ("evolution", 2.0),
1047            ("oldest", 2.0),
1048            ("newest", 2.0),
1049        ],
1050    },
1051    ToolProfile {
1052        tool_name: "learning.pattern",
1053        keywords: &[
1054            ("learning", 3.0),
1055            ("pattern", 3.0),
1056            ("recurring", 2.5),
1057            ("theme", 2.5),
1058            ("frequency", 2.0),
1059            ("co-occurrence", 3.0),
1060            ("trends", 2.5),
1061            ("repeated", 2.0),
1062            ("common", 1.5),
1063        ],
1064    },
1065    ToolProfile {
1066        tool_name: "learning.suggest",
1067        keywords: &[
1068            ("suggest", 3.5),
1069            ("suggestion", 3.5),
1070            ("learn", 2.5),
1071            ("gap", 3.0),
1072            ("missing", 2.0),
1073            ("explore", 2.0),
1074            ("next", 2.0),
1075            ("recommend", 2.5),
1076            ("path", 2.0),
1077            ("advice", 2.0),
1078        ],
1079    },
1080    // ── Reasoning ─────────────────────────────────────────────────
1081    ToolProfile {
1082        tool_name: "bicameral.reason",
1083        keywords: &[
1084            ("bicameral", 5.0),
1085            ("hemisphere", 4.0),
1086            ("debate", 4.0),
1087            ("consensus", 3.5),
1088            ("deliberate", 3.5),
1089            ("pros", 3.0),
1090            ("cons", 3.0),
1091            ("dual", 2.5),
1092            ("perspective", 2.5),
1093            ("callosum", 4.0),
1094        ],
1095    },
1096    ToolProfile {
1097        tool_name: "bicameral.status",
1098        keywords: &[
1099            ("bicameral", 3.0),
1100            ("hemisphere", 3.5),
1101            ("callosum", 3.0),
1102            ("left", 1.5),
1103            ("right", 1.5),
1104            ("status", 2.0),
1105        ],
1106    },
1107    ToolProfile {
1108        tool_name: "reasoning.bicameral",
1109        keywords: &[
1110            ("bicameral", 3.0),
1111            ("pros", 3.5),
1112            ("cons", 3.5),
1113            ("debate", 2.5),
1114            ("perspective", 2.5),
1115            ("argument", 2.5),
1116            ("supporting", 2.0),
1117            ("opposing", 2.5),
1118            ("evidence", 2.0),
1119            ("analyze", 1.5),
1120        ],
1121    },
1122    // ── Drive & Emotion (R7) ───────────────────────────────────────
1123    ToolProfile {
1124        tool_name: "drive.snapshot",
1125        keywords: &[
1126            ("drive", 4.0),
1127            ("emotion", 4.0),
1128            ("motivation", 3.5),
1129            ("curiosity", 3.0),
1130            ("satisfaction", 3.0),
1131            ("caution", 2.5),
1132            ("energy", 2.0),
1133            ("mood", 3.0),
1134            ("feeling", 2.5),
1135        ],
1136    },
1137    ToolProfile {
1138        tool_name: "drive.event",
1139        keywords: &[
1140            ("drive", 3.0),
1141            ("emotion", 3.0),
1142            ("inject", 3.5),
1143            ("trigger", 2.5),
1144            ("reward", 3.0),
1145            ("frustration", 3.0),
1146            ("novelty", 2.5),
1147        ],
1148    },
1149    ToolProfile {
1150        tool_name: "think",
1151        keywords: &[
1152            ("think", 4.0),
1153            ("analyze", 3.0),
1154            ("reason", 2.5),
1155            ("consider", 2.0),
1156            ("reflect", 2.5),
1157            ("ponder", 3.0),
1158            ("contemplate", 3.0),
1159            ("insight", 2.0),
1160            ("thought", 3.0),
1161        ],
1162    },
1163    ToolProfile {
1164        tool_name: "explain",
1165        keywords: &[
1166            ("explain", 4.0),
1167            ("explanation", 3.5),
1168            ("clarify", 3.0),
1169            ("describe", 2.5),
1170            ("context", 2.0),
1171            ("related", 2.0),
1172            ("understand", 2.0),
1173            ("elaborate", 2.5),
1174            ("meaning", 2.0),
1175        ],
1176    },
1177    // ── Pipeline & skills ─────────────────────────────────────────
1178    ToolProfile {
1179        tool_name: "pipeline.create",
1180        keywords: &[
1181            ("pipeline", 4.0),
1182            ("create", 2.5),
1183            ("build", 2.0),
1184            ("workflow", 3.0),
1185            ("steps", 2.0),
1186            ("chain", 2.0),
1187            ("sequence", 2.0),
1188        ],
1189    },
1190    ToolProfile {
1191        tool_name: "pipeline.list",
1192        keywords: &[
1193            ("pipeline", 3.5),
1194            ("list", 3.0),
1195            ("workflows", 2.5),
1196            ("show", 1.5),
1197            ("available", 2.0),
1198        ],
1199    },
1200    ToolProfile {
1201        tool_name: "pipeline.status",
1202        keywords: &[
1203            ("pipeline", 3.0),
1204            ("status", 3.0),
1205            ("check", 2.0),
1206            ("state", 2.0),
1207            ("progress", 2.5),
1208            ("running", 2.0),
1209        ],
1210    },
1211    ToolProfile {
1212        tool_name: "skill.invoke",
1213        keywords: &[
1214            ("skill", 4.0),
1215            ("invoke", 3.5),
1216            ("execute", 2.5),
1217            ("run", 2.0),
1218            ("call", 2.0),
1219            ("trigger", 2.0),
1220            ("ability", 2.5),
1221        ],
1222    },
1223    ToolProfile {
1224        tool_name: "skill.list",
1225        keywords: &[
1226            ("skill", 3.5),
1227            ("list", 3.0),
1228            ("abilities", 2.5),
1229            ("available", 2.0),
1230            ("show", 1.5),
1231            ("capabilities", 2.0),
1232        ],
1233    },
1234    // ── Anomaly & state ───────────────────────────────────────────
1235    ToolProfile {
1236        tool_name: "anomaly.detect",
1237        keywords: &[
1238            ("anomaly", 4.0),
1239            ("detect", 3.0),
1240            ("outlier", 3.5),
1241            ("unusual", 2.5),
1242            ("abnormal", 3.0),
1243            ("strange", 2.0),
1244            ("irregular", 2.5),
1245            ("z-score", 3.0),
1246        ],
1247    },
1248    ToolProfile {
1249        tool_name: "state.snapshot",
1250        keywords: &[
1251            ("snapshot", 4.0),
1252            ("capture", 2.5),
1253            ("state", 2.5),
1254            ("checkpoint", 3.0),
1255            ("preserve", 2.0),
1256            ("record", 2.0),
1257        ],
1258    },
1259    ToolProfile {
1260        tool_name: "state.revert",
1261        keywords: &[
1262            ("revert", 4.0),
1263            ("rollback", 3.5),
1264            ("restore", 3.0),
1265            ("previous", 2.5),
1266            ("undo", 2.5),
1267            ("go back", 2.0),
1268            ("state", 2.0),
1269        ],
1270    },
1271    // ── Correlation & god nodes ──────────────────────────────────
1272    ToolProfile {
1273        tool_name: "correlation.analyze",
1274        keywords: &[
1275            ("correlation", 4.0),
1276            ("analyze", 2.0),
1277            ("co-occurrence", 3.0),
1278            ("phi", 2.5),
1279            ("relationship", 2.0),
1280            ("statistical", 2.5),
1281            ("connect", 1.5),
1282            ("associate", 1.5),
1283        ],
1284    },
1285    ToolProfile {
1286        tool_name: "god.nodes",
1287        keywords: &[
1288            ("god", 3.5),
1289            ("nodes", 3.0),
1290            ("hub", 3.0),
1291            ("central", 2.5),
1292            ("important", 2.0),
1293            ("connector", 3.0),
1294            ("cross-galaxy", 3.0),
1295            ("entity", 2.0),
1296        ],
1297    },
1298    // ── Anti-loop & boundary ──────────────────────────────────────
1299    ToolProfile {
1300        tool_name: "anti_loop.check",
1301        keywords: &[
1302            ("loop", 4.0),
1303            ("anti", 2.5),
1304            ("repetitive", 3.0),
1305            ("duplicate", 3.0),
1306            ("stuck", 3.0),
1307            ("cycle", 2.5),
1308            ("repeated", 2.5),
1309            ("burst", 2.0),
1310        ],
1311    },
1312    ToolProfile {
1313        tool_name: "boundary.enforce",
1314        keywords: &[
1315            ("boundary", 4.0),
1316            ("enforce", 3.5),
1317            ("limit", 3.0),
1318            ("violation", 3.0),
1319            ("overflow", 3.0),
1320            ("constraint", 2.5),
1321            ("check", 2.0),
1322            ("resource", 2.0),
1323            ("sprawl", 2.5),
1324        ],
1325    },
1326    // ── Tier 5: Net tools ──────────────────────────────────────────
1327    ToolProfile {
1328        tool_name: "association.mine",
1329        keywords: &[
1330            ("cross", 5.0),
1331            ("galaxy", 4.0),
1332            ("association", 3.0),
1333            ("mine", 3.0),
1334            ("overlap", 2.5),
1335            ("keyword", 2.0),
1336            ("propose", 2.0),
1337            ("link", 2.0),
1338        ],
1339    },
1340    ToolProfile {
1341        tool_name: "pattern.detect",
1342        keywords: &[
1343            ("pattern", 4.0),
1344            ("detect", 3.5),
1345            ("structural", 3.0),
1346            ("hub", 3.0),
1347            ("bridge", 3.0),
1348            ("chain", 2.5),
1349            ("graph", 2.0),
1350            ("topology", 2.5),
1351        ],
1352    },
1353    ToolProfile {
1354        tool_name: "emergence.report",
1355        keywords: &[
1356            ("emergence", 4.0),
1357            ("report", 3.0),
1358            ("tag", 2.5),
1359            ("frequency", 2.5),
1360            ("distribution", 2.0),
1361            ("trend", 2.0),
1362            ("emerging", 3.0),
1363            ("dominant", 2.0),
1364        ],
1365    },
1366    ToolProfile {
1367        tool_name: "network.stats",
1368        keywords: &[
1369            ("network", 4.0),
1370            ("stats", 3.0),
1371            ("density", 3.0),
1372            ("degree", 2.5),
1373            ("edge", 2.0),
1374            ("node", 2.0),
1375            ("global", 2.0),
1376            ("graph", 2.0),
1377        ],
1378    },
1379    ToolProfile {
1380        tool_name: "network.centrality",
1381        keywords: &[
1382            ("centrality", 4.0),
1383            ("central", 3.0),
1384            ("degree", 3.0),
1385            ("important", 2.0),
1386            ("influential", 2.5),
1387            ("hub", 2.0),
1388            ("rank", 2.0),
1389            ("top", 2.0),
1390        ],
1391    },
1392    ToolProfile {
1393        tool_name: "network.clusters",
1394        keywords: &[
1395            ("cluster", 4.0),
1396            ("clusters", 3.0),
1397            ("component", 3.0),
1398            ("connected", 2.5),
1399            ("group", 2.0),
1400            ("isolate", 2.0),
1401            ("subgraph", 2.5),
1402        ],
1403    },
1404    // ── Tier 5: Ghost tools ────────────────────────────────────────
1405    ToolProfile {
1406        tool_name: "smarana.status",
1407        keywords: &[
1408            ("smarana", 5.0),
1409            ("retention", 4.0),
1410            ("recall", 3.0),
1411            ("score", 2.0),
1412            ("memory", 1.5),
1413            ("forgetting", 2.5),
1414        ],
1415    },
1416    ToolProfile {
1417        tool_name: "smarana.trace",
1418        keywords: &[
1419            ("smarana", 4.0),
1420            ("trace", 3.5),
1421            ("decay", 3.0),
1422            ("retention", 3.0),
1423            ("over", 2.0),
1424            ("time", 1.5),
1425            ("history", 2.0),
1426        ],
1427    },
1428    ToolProfile {
1429        tool_name: "apotheosis.check",
1430        keywords: &[
1431            ("apotheosis", 5.0),
1432            ("self", 2.5),
1433            ("improvement", 3.5),
1434            ("trend", 3.0),
1435            ("progress", 2.5),
1436            ("check", 2.0),
1437            ("score", 2.0),
1438        ],
1439    },
1440    ToolProfile {
1441        tool_name: "citta.history",
1442        keywords: &[
1443            ("citta", 4.0),
1444            ("history", 3.5),
1445            ("heartbeat", 3.0),
1446            ("valence", 2.5),
1447            ("past", 2.0),
1448            ("recent", 2.0),
1449            ("consciousness", 2.5),
1450        ],
1451    },
1452    ToolProfile {
1453        tool_name: "dream.analyze",
1454        keywords: &[
1455            ("dream", 4.0),
1456            ("analyze", 3.5),
1457            ("analysis", 3.0),
1458            ("consolidation", 2.5),
1459            ("quality", 2.5),
1460            ("sleep", 2.0),
1461            ("cycle", 2.0),
1462        ],
1463    },
1464    ToolProfile {
1465        tool_name: "consciousness.depth",
1466        keywords: &[
1467            ("consciousness", 4.0),
1468            ("depth", 4.0),
1469            ("deep", 3.0),
1470            ("measure", 2.5),
1471            ("awareness", 2.5),
1472            ("state", 2.0),
1473            ("level", 2.0),
1474        ],
1475    },
1476    // ── Tier 7: WinnowingBasket tools ──────────────────────────────
1477    ToolProfile {
1478        tool_name: "memory.sort",
1479        keywords: &[
1480            ("sort", 4.0),
1481            ("order", 3.5),
1482            ("arrange", 3.0),
1483            ("rank", 2.5),
1484            ("by", 1.5),
1485            ("importance", 2.0),
1486            ("recency", 2.0),
1487            ("memory", 1.5),
1488        ],
1489    },
1490    ToolProfile {
1491        tool_name: "memory.filter",
1492        keywords: &[
1493            ("filter", 4.0),
1494            ("where", 2.5),
1495            ("match", 2.0),
1496            ("criteria", 3.0),
1497            ("condition", 2.5),
1498            ("tag", 2.0),
1499            ("importance", 1.5),
1500            ("memory", 1.5),
1501        ],
1502    },
1503    ToolProfile {
1504        tool_name: "memory.deduplicate",
1505        keywords: &[
1506            ("deduplicate", 4.0),
1507            ("dedup", 4.0),
1508            ("duplicate", 3.5),
1509            ("unique", 2.5),
1510            ("distinct", 3.0),
1511            ("remove", 2.0),
1512            ("redundant", 3.0),
1513            ("memory", 1.5),
1514        ],
1515    },
1516    ToolProfile {
1517        tool_name: "memory.export",
1518        keywords: &[
1519            ("export", 4.0),
1520            ("download", 3.0),
1521            ("dump", 3.0),
1522            ("extract", 2.5),
1523            ("format", 2.0),
1524            ("csv", 3.5),
1525            ("markdown", 3.0),
1526            ("memory", 1.5),
1527        ],
1528    },
1529    // ── Tier 7: Dipper tools ───────────────────────────────────────
1530    ToolProfile {
1531        tool_name: "homeostasis.check",
1532        keywords: &[
1533            ("homeostasis", 5.0),
1534            ("check", 3.0),
1535            ("balance", 3.5),
1536            ("equilibrium", 3.0),
1537            ("health", 2.5),
1538            ("metrics", 3.0),
1539            ("vitals", 3.0),
1540            ("system", 1.5),
1541        ],
1542    },
1543    ToolProfile {
1544        tool_name: "homeostasis.adjust",
1545        keywords: &[
1546            ("homeostasis", 4.0),
1547            ("adjust", 3.5),
1548            ("tune", 3.0),
1549            ("rebalance", 3.5),
1550            ("weight", 3.0),
1551            ("simulate", 2.5),
1552            ("recalibrate", 3.0),
1553        ],
1554    },
1555    ToolProfile {
1556        tool_name: "homeostasis.history",
1557        keywords: &[
1558            ("homeostasis", 4.0),
1559            ("history", 3.5),
1560            ("past", 2.5),
1561            ("trend", 3.0),
1562            ("samples", 3.0),
1563            ("readings", 2.5),
1564            ("timeline", 2.0),
1565        ],
1566    },
1567    ToolProfile {
1568        tool_name: "homeostasis.alerts",
1569        keywords: &[
1570            ("homeostasis", 4.0),
1571            ("alert", 4.0),
1572            ("alerts", 4.0),
1573            ("warning", 3.5),
1574            ("critical", 3.0),
1575            ("notify", 2.5),
1576            ("threshold", 2.5),
1577            ("triggered", 2.0),
1578        ],
1579    },
1580    // ── v4: Reflex tools ──────────────────────────────────────────
1581    ToolProfile {
1582        tool_name: "reflex.dispatch",
1583        keywords: &[
1584            ("reflex", 5.0),
1585            ("dispatch", 5.0),
1586            ("trigger", 4.0),
1587            ("invoke", 3.0),
1588            ("fire", 3.0),
1589            ("handler", 2.5),
1590            ("emergency", 2.0),
1591            ("e_stop", 3.5),
1592            ("estop", 3.5),
1593            ("safety", 2.0),
1594            ("actuator", 2.0),
1595        ],
1596    },
1597    ToolProfile {
1598        tool_name: "reflex.status",
1599        keywords: &[
1600            ("reflex", 5.0),
1601            ("status", 4.0),
1602            ("table", 3.0),
1603            ("handler", 2.5),
1604            ("registered", 2.5),
1605            ("safety_mask", 3.0),
1606            ("dispatch_count", 3.0),
1607            ("builtins", 2.0),
1608        ],
1609    },
1610    // ── v4: Workspace tools ───────────────────────────────────────
1611    ToolProfile {
1612        tool_name: "workspace.spotlight",
1613        keywords: &[
1614            ("spotlight", 5.0),
1615            ("attention", 4.0),
1616            ("focus", 3.0),
1617            ("arbitration", 3.5),
1618            ("workspace", 3.0),
1619            ("current", 2.0),
1620            ("holder", 2.5),
1621            ("salience", 2.5),
1622            ("who", 1.5),
1623            ("winning", 2.5),
1624        ],
1625    },
1626    ToolProfile {
1627        tool_name: "workspace.events",
1628        keywords: &[
1629            ("workspace", 4.0),
1630            ("events", 5.0),
1631            ("backlog", 4.0),
1632            ("history", 2.5),
1633            ("log", 2.0),
1634        ],
1635    },
1636    ToolProfile {
1637        tool_name: "workspace.publish",
1638        keywords: &[
1639            ("workspace", 4.0),
1640            ("publish", 5.0),
1641            ("broadcast", 4.0),
1642            ("emit", 3.5),
1643            ("send", 2.5),
1644            ("event", 3.0),
1645            ("post", 2.5),
1646            ("notify", 2.0),
1647            ("submit", 2.5),
1648        ],
1649    },
1650    ToolProfile {
1651        tool_name: "workspace.stats",
1652        keywords: &[
1653            ("workspace", 4.0),
1654            ("stats", 5.0),
1655            ("statistics", 4.5),
1656            ("transfers", 3.0),
1657            ("arbitration", 2.5),
1658            ("published", 3.0),
1659            ("count", 2.0),
1660            ("summary", 2.5),
1661        ],
1662    },
1663    // ── v4: Timescale tools ───────────────────────────────────────
1664    ToolProfile {
1665        tool_name: "timescale.status",
1666        keywords: &[
1667            ("timescale", 5.0),
1668            ("status", 4.0),
1669            ("tier", 3.0),
1670            ("tiers", 3.0),
1671            ("bus", 2.5),
1672            ("brain_wave", 3.0),
1673            ("active", 2.5),
1674            ("hooks", 2.0),
1675            ("interval", 2.0),
1676            ("budget", 2.0),
1677        ],
1678    },
1679    ToolProfile {
1680        tool_name: "timescale.hooks",
1681        keywords: &[
1682            ("timescale", 4.0),
1683            ("hooks", 5.0),
1684            ("hook", 4.0),
1685            ("list", 2.5),
1686            ("stats", 2.5),
1687            ("performance", 2.5),
1688            ("tick", 3.0),
1689            ("timeout", 2.5),
1690            ("duration", 2.0),
1691            ("callback", 2.0),
1692        ],
1693    },
1694    // ── Self-model (R4) ─────────────────────────────────────────────
1695    ToolProfile {
1696        tool_name: "selfmodel.forecast",
1697        keywords: &[
1698            ("forecast", 5.0),
1699            ("predict", 4.0),
1700            ("prediction", 4.0),
1701            ("project", 3.0),
1702            ("projection", 3.0),
1703            ("extrapolate", 4.0),
1704            ("trend", 3.0),
1705            ("outlook", 3.0),
1706            ("selfmodel", 5.0),
1707            ("introspect", 3.0),
1708            ("horizon", 2.5),
1709            ("metric", 2.0),
1710        ],
1711    },
1712    ToolProfile {
1713        tool_name: "selfmodel.alerts",
1714        keywords: &[
1715            ("alert", 5.0),
1716            ("alerts", 5.0),
1717            ("warning", 3.5),
1718            ("warnings", 3.5),
1719            ("critical", 3.5),
1720            ("threshold", 3.0),
1721            ("breach", 3.0),
1722            ("exceed", 2.5),
1723            ("danger", 3.0),
1724            ("selfmodel", 5.0),
1725            ("introspect", 3.0),
1726        ],
1727    },
1728    ToolProfile {
1729        tool_name: "selfmodel.snapshot",
1730        keywords: &[
1731            ("snapshot", 5.0),
1732            ("selfmodel", 5.0),
1733            ("introspect", 4.0),
1734            ("introspection", 4.0),
1735            ("overview", 3.0),
1736            ("confidence", 3.0),
1737            ("conservative", 2.5),
1738        ],
1739    },
1740    // ── RSI: Friction & Improvement ───────────────────────────────
1741    ToolProfile {
1742        tool_name: "friction.log",
1743        keywords: &[
1744            ("friction", 5.0),
1745            ("log", 3.0),
1746            ("report", 2.5),
1747            ("issue", 3.5),
1748            ("problem", 3.0),
1749            ("bug", 3.0),
1750            ("complaint", 3.0),
1751            ("annoying", 2.5),
1752            ("broken", 2.5),
1753            ("wrong", 2.0),
1754        ],
1755    },
1756    ToolProfile {
1757        tool_name: "friction.review",
1758        keywords: &[
1759            ("friction", 4.5),
1760            ("review", 4.0),
1761            ("issues", 3.5),
1762            ("problems", 3.0),
1763            ("patterns", 2.5),
1764            ("summary", 2.5),
1765            ("analyze", 2.0),
1766            ("frictions", 4.0),
1767        ],
1768    },
1769    ToolProfile {
1770        tool_name: "improve.proposals",
1771        keywords: &[
1772            ("improve", 5.0),
1773            ("improvement", 5.0),
1774            ("improvements", 5.0),
1775            ("proposal", 4.0),
1776            ("proposals", 4.0),
1777            ("suggest", 3.0),
1778            ("suggestions", 3.0),
1779            ("fix", 2.5),
1780            ("friction", 2.0),
1781            ("upgrade", 3.0),
1782            ("enhance", 2.5),
1783            ("better", 2.0),
1784        ],
1785    },
1786    ToolProfile {
1787        tool_name: "redteam.proposals",
1788        keywords: &[
1789            ("redteam", 5.0),
1790            ("red", 2.0),
1791            ("team", 2.0),
1792            ("adversarial", 5.0),
1793            ("attack", 4.5),
1794            ("vulnerability", 4.5),
1795            ("security", 4.0),
1796            ("exploit", 4.0),
1797            ("breach", 3.5),
1798            ("pentest", 4.5),
1799            ("penetrate", 3.5),
1800            ("break", 3.0),
1801            ("threat", 3.5),
1802            ("probe", 3.0),
1803            ("audit", 2.5),
1804        ],
1805    },
1806    // ── Sensorimotor / Embodiment I/O ───────────────────────────────
1807    ToolProfile {
1808        tool_name: "sensor.list",
1809        keywords: &[
1810            ("sensor", 5.0),
1811            ("sensors", 5.0),
1812            ("list", 3.0),
1813            ("hardware", 3.0),
1814            ("devices", 2.5),
1815            ("thermal", 2.0),
1816            ("battery", 2.0),
1817        ],
1818    },
1819    ToolProfile {
1820        tool_name: "sensor.read",
1821        keywords: &[
1822            ("read", 4.0),
1823            ("sensor", 4.0),
1824            ("temperature", 3.5),
1825            ("value", 3.0),
1826            ("measure", 3.0),
1827            ("probe", 2.5),
1828        ],
1829    },
1830    ToolProfile {
1831        tool_name: "sensor.poll",
1832        keywords: &[
1833            ("poll", 5.0),
1834            ("sample", 4.0),
1835            ("all", 2.5),
1836            ("sensors", 3.0),
1837            ("readings", 3.5),
1838            ("collect", 2.5),
1839        ],
1840    },
1841    ToolProfile {
1842        tool_name: "sensor.history",
1843        keywords: &[
1844            ("history", 5.0),
1845            ("past", 3.0),
1846            ("readings", 3.5),
1847            ("recent", 3.0),
1848            ("log", 2.5),
1849            ("timeseries", 3.5),
1850        ],
1851    },
1852    ToolProfile {
1853        tool_name: "actuator.list",
1854        keywords: &[
1855            ("actuator", 5.0),
1856            ("actuators", 5.0),
1857            ("motor", 3.0),
1858            ("relay", 3.0),
1859            ("output", 2.5),
1860        ],
1861    },
1862    ToolProfile {
1863        tool_name: "actuator.command",
1864        keywords: &[
1865            ("command", 4.5),
1866            ("send", 3.5),
1867            ("actuator", 4.0),
1868            ("motor", 3.0),
1869            ("drive", 3.0),
1870            ("control", 3.5),
1871            ("set", 2.0),
1872        ],
1873    },
1874    ToolProfile {
1875        tool_name: "actuator.estop",
1876        keywords: &[
1877            ("estop", 5.0),
1878            ("emergency", 5.0),
1879            ("stop", 4.0),
1880            ("halt", 4.0),
1881            ("abort", 3.5),
1882            ("shutdown", 3.0),
1883        ],
1884    },
1885    ToolProfile {
1886        tool_name: "reflex.list",
1887        keywords: &[
1888            ("reflex", 5.0),
1889            ("reflexes", 5.0),
1890            ("rules", 3.0),
1891            ("trigger", 2.5),
1892        ],
1893    },
1894    ToolProfile {
1895        tool_name: "reflex.add",
1896        keywords: &[
1897            ("add", 3.5),
1898            ("reflex", 4.5),
1899            ("rule", 4.0),
1900            ("create", 3.0),
1901            ("threshold", 3.5),
1902            ("trigger", 3.0),
1903        ],
1904    },
1905    ToolProfile {
1906        tool_name: "reflex.evaluate",
1907        keywords: &[
1908            ("evaluate", 5.0),
1909            ("check", 3.0),
1910            ("reflex", 4.0),
1911            ("trigger", 3.5),
1912            ("fire", 3.0),
1913            ("respond", 2.5),
1914        ],
1915    },
1916    ToolProfile {
1917        tool_name: "sensorimotor.scan",
1918        keywords: &[
1919            ("sensorimotor", 6.0),
1920            ("scan", 4.0),
1921            ("poll", 3.5),
1922            ("reflex", 3.0),
1923            ("autonomous", 3.0),
1924            ("embodiment", 4.0),
1925            ("cycle", 2.5),
1926            ("self-regulate", 3.0),
1927        ],
1928    },
1929    // ── Gnosis fallback ────────────────────────────────────────────
1930    ToolProfile {
1931        tool_name: "gnosis",
1932        keywords: &[
1933            ("help", 2.0),
1934            ("discover", 2.0),
1935            ("what", 1.5),
1936            ("can", 1.0),
1937            ("do", 1.0),
1938            ("status", 1.5),
1939            ("overview", 2.0),
1940            ("system", 1.0),
1941        ],
1942    },
1943    // ── Speculative decoding ──────────────────────────────────────
1944    ToolProfile {
1945        tool_name: "speculative.decode",
1946        keywords: &[
1947            ("speculative", 5.0),
1948            ("decode", 4.0),
1949            ("draft", 3.0),
1950            ("verify", 2.5),
1951            ("accelerate", 3.0),
1952            ("speedup", 3.0),
1953            ("fast", 2.0),
1954            ("infer", 2.0),
1955            ("generate", 1.5),
1956        ],
1957    },
1958    ToolProfile {
1959        tool_name: "speculative.stats",
1960        keywords: &[
1961            ("speculative", 4.0),
1962            ("acceptance", 3.0),
1963            ("speedup", 3.0),
1964            ("draft", 2.0),
1965            ("latency", 2.0),
1966            ("tokens", 2.0),
1967        ],
1968    },
1969    // ── Meta-harness ───────────────────────────────────────────────
1970    ToolProfile {
1971        tool_name: "meta.enhance",
1972        keywords: &[
1973            ("enhance", 5.0),
1974            ("grounding", 4.0),
1975            ("grounded", 4.0),
1976            ("rag", 4.0),
1977            ("self-correct", 4.0),
1978            ("selfcorrect", 4.0),
1979            ("ensemble", 3.5),
1980            ("improve", 3.0),
1981            ("cognitive", 3.0),
1982            ("meta", 2.5),
1983            ("harness", 3.0),
1984            ("augment", 2.5),
1985            ("refine", 2.0),
1986        ],
1987    },
1988    ToolProfile {
1989        tool_name: "meta.stats",
1990        keywords: &[
1991            ("meta", 3.0),
1992            ("harness", 3.0),
1993            ("enhancement", 3.0),
1994            ("improvement", 3.0),
1995            ("enhance", 2.0),
1996            ("stats", 2.0),
1997        ],
1998    },
1999    // ── Dense encoding ──────────────────────────────────────────────
2000    ToolProfile {
2001        tool_name: "dense.encode",
2002        keywords: &[
2003            ("dense", 5.0),
2004            ("compress", 4.0),
2005            ("compression", 4.0),
2006            ("encode", 3.5),
2007            ("encoding", 3.5),
2008            ("token", 2.5),
2009            ("compact", 3.0),
2010            ("shrink", 2.5),
2011            ("cjk", 3.0),
2012        ],
2013    },
2014    ToolProfile {
2015        tool_name: "dense.decode",
2016        keywords: &[
2017            ("decode", 4.0),
2018            ("decompress", 4.0),
2019            ("expand", 3.0),
2020            ("restore", 2.5),
2021            ("dense", 2.0),
2022        ],
2023    },
2024    // ── Transaction tools ──────────────────────────────────────────
2025    ToolProfile {
2026        tool_name: "transaction.begin",
2027        keywords: &[
2028            ("transaction", 5.0),
2029            ("begin", 4.0),
2030            ("start", 3.0),
2031            ("snapshot", 3.5),
2032            ("checkpoint", 3.0),
2033            ("atomic", 3.0),
2034        ],
2035    },
2036    ToolProfile {
2037        tool_name: "transaction.commit",
2038        keywords: &[
2039            ("transaction", 5.0),
2040            ("commit", 5.0),
2041            ("finalize", 3.5),
2042            ("confirm", 3.0),
2043            ("keep", 2.5),
2044            ("persist", 3.0),
2045        ],
2046    },
2047    ToolProfile {
2048        tool_name: "transaction.rollback",
2049        keywords: &[
2050            ("transaction", 5.0),
2051            ("rollback", 5.0),
2052            ("revert", 4.0),
2053            ("undo", 4.0),
2054            ("restore", 3.5),
2055            ("abort", 3.5),
2056            ("discard", 3.0),
2057        ],
2058    },
2059    // ── Imagination Engine ─────────────────────────────────────────
2060    ToolProfile {
2061        tool_name: "imagine.scenario",
2062        keywords: &[
2063            ("imagine", 5.0),
2064            ("scenario", 5.0),
2065            ("scenarios", 4.5),
2066            ("plan", 3.0),
2067            ("contingency", 4.0),
2068            ("what-if", 4.0),
2069            ("possibility", 3.5),
2070            ("options", 2.5),
2071            ("alternatives", 3.0),
2072            ("brainstorm", 3.5),
2073            ("envision", 3.5),
2074        ],
2075    },
2076    ToolProfile {
2077        tool_name: "imagine.predict",
2078        keywords: &[
2079            ("predict", 5.0),
2080            ("outcome", 4.0),
2081            ("consequence", 4.0),
2082            ("forecast", 3.0),
2083            ("expect", 3.0),
2084            ("result", 2.5),
2085            ("happen", 3.5),
2086            ("would", 2.5),
2087            ("if", 1.5),
2088            ("imagine", 2.0),
2089        ],
2090    },
2091    ToolProfile {
2092        tool_name: "imagine.reflect",
2093        keywords: &[
2094            ("reflect", 5.0),
2095            ("counterfactual", 5.0),
2096            ("regret", 4.0),
2097            ("alternative", 3.5),
2098            ("should", 3.0),
2099            ("instead", 3.0),
2100            ("what-if", 3.0),
2101            ("reconsider", 4.0),
2102            ("retrospect", 3.5),
2103            ("lesson", 3.0),
2104            ("counter", 2.5),
2105            ("factual", 2.5),
2106        ],
2107    },
2108    // ── NLU observability ───────────────────────────────────────────
2109    ToolProfile {
2110        tool_name: "nlu.shadow_report",
2111        keywords: &[
2112            ("shadow", 4.0),
2113            ("disagreement", 4.0),
2114            ("nlu", 3.0),
2115            ("router", 3.0),
2116            ("embedding", 2.5),
2117            ("tfidf", 2.5),
2118            ("tf-idf", 2.5),
2119            ("oats", 3.0),
2120            ("promotion", 2.5),
2121            ("routing", 2.0),
2122        ],
2123    },
2124];
2125
2126/// Common English stopwords that don't contribute to tool routing.
2127/// These are filtered out during tokenization to improve cosine similarity.
2128const STOPWORDS: &[&str] = &[
2129    // Articles
2130    "a", "an", "the", // Demonstratives
2131    "this", "that", "these", "those", // Pronouns
2132    "i", "me", "my", "you", "your", "yours", "it", "its", "we", "our", "ours", "they", "them",
2133    "their", "theirs", "he", "him", "his", "she", "her", "hers", // Auxiliary verbs
2134    "is", "are", "was", "were", "be", "been", "being", "am", "have", "has", "had", "will", "would",
2135    "could", "should", "shall", "must", // Prepositions
2136    "in", "on", "at", "to", "for", "of", "with", "by", "from", "into", "about", "over", "under",
2137    "through", "between", "among", "during", "before", "after", "above", "below",
2138    // Conjunctions
2139    "and", "but", "or", "nor", "so", "yet", // Negation/affirmation
2140    "not", "no", "yes", // Conditionals
2141    "if", "else", "because", "as", "until", "while", "although", "though", "since", "unless",
2142    "whether", // Direction/position
2143    "up", "down", "out", "off", "again", "further", "then", "once", "here", "there", "when",
2144    "where", "why", "how", // Quantifiers (non-routing)
2145    "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "only", "own",
2146    "same", "than", "too", "very", // Time/manner
2147    "just", "also", "now",
2148];
2149
2150/// Bases of common English verbs that drop a trailing 'e' before -ing/-ed suffixes.
2151/// When stemming removes -ing or -ed and the base is in this set, 'e' is restored.
2152/// Sorted for binary search.
2153const E_DROPPING_BASES: &[&str] = &[
2154    "activat",
2155    "allocat",
2156    "arrang",
2157    "associat",
2158    "becom",
2159    "calculat",
2160    "chang",
2161    "clos",
2162    "cit",
2163    "configur",
2164    "consolidat",
2165    "continu",
2166    "creat",
2167    "delegat",
2168    "delet",
2169    "demonstrat",
2170    "downgrad",
2171    "encourag",
2172    "engag",
2173    "ensur",
2174    "enumerat",
2175    "evaluat",
2176    "exchang",
2177    "exclud",
2178    "explor",
2179    "fac",
2180    "generat",
2181    "giv",
2182    "improv",
2183    "includ",
2184    "leav",
2185    "lik",
2186    "mak",
2187    "manag",
2188    "measur",
2189    "merg",
2190    "mov",
2191    "navigat",
2192    "notic",
2193    "operat",
2194    "practic",
2195    "relat",
2196    "remov",
2197    "restor",
2198    "sav",
2199    "simulat",
2200    "stor",
2201    "tak",
2202    "updat",
2203    "upgrad",
2204    "us",
2205    "validat",
2206    "writ",
2207];
2208
2209/// Simple English stemmer for common suffixes.
2210/// Reduces words to their root form to improve matching.
2211/// Examples: "memories" → "memory", "searching" → "search", "stored" → "store"
2212fn stem(word: &str) -> String {
2213    let w = word.to_lowercase();
2214
2215    // Handle -ies → -y (categories → category, memories → memory)
2216    if w.ends_with("ies") && w.len() > 3 {
2217        let base = &w[..w.len() - 3];
2218        return format!("{base}y");
2219    }
2220
2221    // Handle -ing (searching → search, storing → store)
2222    if w.ends_with("ing") && w.len() > 4 {
2223        let base = &w[..w.len() - 3];
2224        // Double consonant check: running → run (character-aware — the old
2225        // byte-index comparison could panic on non-ASCII words like "xéing"
2226        // and "éing", which slice mid-character).
2227        if let Some(last) = base.chars().last() {
2228            let is_double_consonant = base.chars().rev().nth(1) == Some(last);
2229            if is_double_consonant {
2230                return base[..base.len() - last.len_utf8()].to_string();
2231            }
2232        }
2233        // Check if this base needs 'e' restoration (e-dropping verb)
2234        if E_DROPPING_BASES.binary_search(&base).is_ok() {
2235            return format!("{base}e");
2236        }
2237        return base.to_string();
2238    }
2239
2240    // Handle -ed (stored → store, searched → search)
2241    if w.ends_with("ed") && w.len() > 3 {
2242        let base = &w[..w.len() - 2];
2243        // Check if this base needs 'e' restoration (e-dropping verb)
2244        if E_DROPPING_BASES.binary_search(&base).is_ok() {
2245            return format!("{base}e");
2246        }
2247        return base.to_string();
2248    }
2249
2250    // Handle -es (searches → search, batches → batch)
2251    if w.ends_with("es") && w.len() > 3 {
2252        let base = &w[..w.len() - 2];
2253        // ch/sh/s/x/z endings: searches → search
2254        if base.ends_with("ch")
2255            || base.ends_with("sh")
2256            || base.ends_with('s')
2257            || base.ends_with('x')
2258            || base.ends_with('z')
2259        {
2260            return base.to_string();
2261        }
2262        return w[..w.len() - 1].to_string();
2263    }
2264
2265    // Handle -s (simple plural: tags → tag, lists → list)
2266    if w.ends_with('s') && w.len() > 2 && !w.ends_with("ss") {
2267        return w[..w.len() - 1].to_string();
2268    }
2269
2270    w
2271}
2272
2273/// Tokenize text into lowercase terms, filtering out stopwords and applying stemming.
2274/// Splits on non-alphanumeric characters (simple but effective for routing).
2275fn tokenize(text: &str) -> Vec<String> {
2276    text.split(|c: char| !c.is_alphanumeric())
2277        .filter(|s| !s.is_empty())
2278        .map(str::to_lowercase)
2279        .filter(|s| !STOPWORDS.contains(&s.as_str()))
2280        .map(|s| stem(&s))
2281        .collect()
2282}
2283
2284/// Build a term-frequency map from tokens.
2285fn term_frequencies(tokens: &[String]) -> AHashMap<String, f64> {
2286    let mut tf = AHashMap::new();
2287    for token in tokens {
2288        *tf.entry(token.clone()).or_insert(0.0) += 1.0;
2289    }
2290    tf
2291}
2292
2293/// Compute cosine similarity between an input TF vector and a tool profile.
2294///
2295/// The profile's keywords form a weighted vector. The input is a TF vector.
2296/// Both input tokens and profile keywords are stemmed before comparison.
2297/// Cosine similarity = dot(input, profile) / (|input| * |profile|).
2298fn cosine_similarity(input_tf: &AHashMap<String, f64>, profile: &ToolProfile) -> f64 {
2299    let mut dot_product = 0.0;
2300    let mut profile_norm_sq = 0.0;
2301
2302    for (term, weight) in profile.keywords {
2303        profile_norm_sq += weight * weight;
2304        let stemmed_term = stem(term);
2305        if let Some(&freq) = input_tf.get(&stemmed_term) {
2306            dot_product += freq * weight;
2307        }
2308    }
2309
2310    if profile_norm_sq == 0.0 {
2311        return 0.0;
2312    }
2313
2314    let input_norm: f64 = input_tf.values().map(|v| v * v).sum::<f64>().sqrt();
2315    if input_norm == 0.0 {
2316        return 0.0;
2317    }
2318
2319    dot_product / (input_norm * profile_norm_sq.sqrt())
2320}
2321
2322/// Classify natural language input into (tool_name, confidence) using
2323/// TF-IDF cosine similarity against all tool profiles.
2324///
2325/// Returns the best-matching tool name and its similarity score (0.0–1.0).
2326/// Command verbs that strongly indicate a specific tool when they appear
2327/// as the first word of the input. This helps counteract cosine similarity's
2328/// bias toward profiles with fewer keywords (smaller norm).
2329pub const PREFIX_ROUTES: &[(&str, &str, f64)] = &[
2330    ("remember", "memory.create", 1.5),
2331    ("store", "memory.create", 1.5),
2332    ("save", "memory.create", 1.5),
2333    ("memorize", "memory.create", 1.5),
2334    ("recall", "memory.read", 1.5),
2335    ("search", "memory.search", 1.3),
2336    ("list", "memory.list", 1.3),
2337    ("delete", "memory.delete", 1.5),
2338    ("remove", "memory.delete", 1.3),
2339    ("forget", "memory.delete", 1.5),
2340    ("count", "memory.count", 1.5),
2341    ("show", "gnosis", 1.0),
2342    ("spotlight", "workspace.spotlight", 1.5),
2343    ("publish", "workspace.publish", 1.5),
2344    ("broadcast", "workspace.publish", 1.4),
2345    ("forecast", "selfmodel.forecast", 1.5),
2346    ("deliberate", "bicameral.reason", 1.3),
2347    ("drive", "drive.snapshot", 1.5),
2348    ("emotion", "drive.snapshot", 1.4),
2349    ("adversarial", "redteam.proposals", 1.5),
2350    ("redteam", "redteam.proposals", 1.5),
2351    ("pentest", "redteam.proposals", 1.5),
2352    ("friction", "friction.log", 1.3),
2353    ("log", "friction.log", 1.4),
2354    ("sensor", "sensor.list", 1.5),
2355    ("actuator", "actuator.list", 1.5),
2356    ("estop", "actuator.estop", 1.5),
2357    ("emergency", "actuator.estop", 1.3),
2358    ("sensorimotor", "sensorimotor.scan", 1.5),
2359    ("imagine", "imagine.scenario", 1.5),
2360    ("envision", "imagine.scenario", 1.4),
2361    ("brainstorm", "imagine.scenario", 1.3),
2362    ("counterfactual", "imagine.reflect", 1.5),
2363];
2364
2365/// If no profile scores above the minimum threshold, falls back to "gnosis"
2366/// with confidence 0.0.
2367#[must_use]
2368pub fn classify(text: &str) -> (&'static str, f64) {
2369    let lower = text.to_lowercase();
2370    if lower.trim().is_empty() {
2371        return ("gnosis", 0.0);
2372    }
2373
2374    let tokens = tokenize(&lower);
2375    if tokens.is_empty() {
2376        return ("gnosis", 0.0);
2377    }
2378
2379    let input_tf = term_frequencies(&tokens);
2380
2381    // Check for prefix-based routing bonus
2382    let first_word = lower.split_whitespace().next().unwrap_or("");
2383    let prefix_bonus: Option<(&str, f64)> = PREFIX_ROUTES
2384        .iter()
2385        .find(|(verb, _, _)| *verb == first_word)
2386        .map(|(_, tool, bonus)| (*tool, *bonus));
2387
2388    let mut best_tool = "gnosis";
2389    let mut best_score = 0.0;
2390
2391    for profile in TOOL_PROFILES {
2392        let mut score = cosine_similarity(&input_tf, profile);
2393        // Apply prefix routing: bonus to matching tool, penalty to non-matching
2394        if let Some((bonus_tool, bonus)) = prefix_bonus {
2395            if profile.tool_name == bonus_tool {
2396                score *= bonus;
2397            } else {
2398                // Dampen non-matching tools to respect prefix intent
2399                score /= bonus;
2400            }
2401        }
2402        if score > best_score {
2403            best_score = score;
2404            best_tool = profile.tool_name;
2405        }
2406    }
2407
2408    // Minimum confidence threshold — below this, fall back to gnosis
2409    const MIN_THRESHOLD: f64 = 0.10;
2410    if best_score < MIN_THRESHOLD {
2411        return ("gnosis", 0.0);
2412    }
2413
2414    (best_tool, best_score)
2415}
2416
2417#[cfg(test)]
2418fn profiled_tools() -> Vec<&'static str> {
2419    TOOL_PROFILES.iter().map(|p| p.tool_name).collect()
2420}
2421
2422#[cfg(test)]
2423fn profile_count() -> usize {
2424    TOOL_PROFILES.len()
2425}
2426
2427#[cfg(test)]
2428mod tests {
2429    use super::*;
2430    use std::collections::HashSet;
2431
2432    #[test]
2433    fn classify_empty_returns_gnosis() {
2434        let (tool, conf) = classify("");
2435        assert_eq!(tool, "gnosis");
2436        assert_eq!(conf, 0.0);
2437    }
2438
2439    #[test]
2440    fn classify_whitespace_returns_gnosis() {
2441        let (tool, conf) = classify("   ");
2442        assert_eq!(tool, "gnosis");
2443        assert_eq!(conf, 0.0);
2444    }
2445
2446    #[test]
2447    fn classify_unknown_returns_gnosis() {
2448        let (tool, conf) = classify("xyzzy frobnicate");
2449        assert_eq!(tool, "gnosis");
2450        assert_eq!(conf, 0.0);
2451    }
2452
2453    #[test]
2454    fn classify_remember_routes_to_memory_create() {
2455        let (tool, _conf) = classify("remember that the sky is blue");
2456        assert_eq!(tool, "memory.create");
2457    }
2458
2459    #[test]
2460    fn classify_store_routes_to_memory_create() {
2461        let (tool, _conf) = classify("store this important fact");
2462        assert_eq!(tool, "memory.create");
2463    }
2464
2465    #[test]
2466    fn classify_recall_routes_to_memory_read() {
2467        let (tool, _conf) = classify("recall the last memory");
2468        assert_eq!(tool, "memory.read");
2469    }
2470
2471    #[test]
2472    fn classify_search_routes_to_memory_search() {
2473        let (tool, _conf) = classify("search for rust");
2474        assert_eq!(tool, "memory.search");
2475    }
2476
2477    #[test]
2478    fn classify_list_memories_routes_to_memory_list() {
2479        let (tool, _conf) = classify("list memories in codex");
2480        assert_eq!(tool, "memory.list");
2481    }
2482
2483    #[test]
2484    fn classify_delete_memory_routes_to_memory_delete() {
2485        let (tool, _conf) = classify("delete memory abc-123");
2486        assert_eq!(tool, "memory.delete");
2487    }
2488
2489    #[test]
2490    fn classify_karma_routes_to_karma_report() {
2491        let (tool, _conf) = classify("show me the karma report");
2492        assert_eq!(tool, "karma.report");
2493    }
2494
2495    #[test]
2496    fn classify_karma_history_routes_correctly() {
2497        let (tool, _conf) = classify("karma history");
2498        assert_eq!(tool, "karma.history");
2499    }
2500
2501    #[test]
2502    fn classify_dharma_status_routes_correctly() {
2503        let (tool, _conf) = classify("dharma status");
2504        assert_eq!(tool, "dharma.status");
2505    }
2506
2507    #[test]
2508    fn classify_dharma_rules_routes_correctly() {
2509        let (tool, _conf) = classify("show dharma rules");
2510        assert_eq!(tool, "dharma.rules");
2511    }
2512
2513    #[test]
2514    fn classify_harmony_routes_to_harmony_vector() {
2515        let (tool, _conf) = classify("harmony vector status");
2516        assert_eq!(tool, "harmony.vector");
2517    }
2518
2519    #[test]
2520    fn classify_gnosis_explain_routes_correctly() {
2521        let (tool, _conf) = classify("why was my action blocked");
2522        assert_eq!(tool, "gnosis.explain");
2523    }
2524
2525    #[test]
2526    fn classify_session_start_routes_correctly() {
2527        let (tool, _conf) = classify("start session research");
2528        assert_eq!(tool, "session.start");
2529    }
2530
2531    #[test]
2532    fn classify_session_end_routes_correctly() {
2533        let (tool, _conf) = classify("end session abc-123");
2534        assert_eq!(tool, "session.end");
2535    }
2536
2537    #[test]
2538    fn classify_citta_status_routes_correctly() {
2539        let (tool, _conf) = classify("citta status");
2540        assert_eq!(tool, "citta.status");
2541    }
2542
2543    #[test]
2544    fn classify_dream_trigger_routes_correctly() {
2545        let (tool, _conf) = classify("trigger dream cycle");
2546        assert_eq!(tool, "dream.trigger");
2547    }
2548
2549    #[test]
2550    fn classify_consolidate_routes_correctly() {
2551        let (tool, _conf) = classify("consolidate duplicate memories");
2552        assert_eq!(tool, "memory.consolidate");
2553    }
2554
2555    #[test]
2556    fn classify_emergence_scan_routes_correctly() {
2557        let (tool, _conf) = classify("emergence scan for trending tags");
2558        assert_eq!(tool, "emergence.scan");
2559    }
2560
2561    #[test]
2562    fn classify_spiral_report_routes_correctly() {
2563        let (tool, _conf) = classify("spiral report for autonomy");
2564        assert_eq!(tool, "spiral.report");
2565    }
2566
2567    #[test]
2568    fn classify_retention_prune_routes_correctly() {
2569        let (tool, _conf) = classify("prune memories ready to forget");
2570        assert_eq!(tool, "retention.prune");
2571    }
2572
2573    #[test]
2574    fn classify_tools_list_routes_correctly() {
2575        let (tool, _conf) = classify("list tools");
2576        assert_eq!(tool, "tools.list");
2577    }
2578
2579    #[test]
2580    fn classify_system_health_routes_correctly() {
2581        let (tool, _conf) = classify("system health check");
2582        assert_eq!(tool, "system.health");
2583    }
2584
2585    #[test]
2586    fn classify_agent_register_routes_correctly() {
2587        let (tool, _conf) = classify("register agent worker-1");
2588        assert_eq!(tool, "agent.register");
2589    }
2590
2591    #[test]
2592    fn classify_task_distribute_routes_correctly() {
2593        let (tool, _conf) = classify("distribute task analyze data");
2594        assert_eq!(tool, "task.distribute");
2595    }
2596
2597    #[test]
2598    fn classify_nearby_memories_routes_correctly() {
2599        let (tool, _conf) = classify("find nearby memories");
2600        assert_eq!(tool, "memory.nearby");
2601    }
2602
2603    #[test]
2604    fn classify_hybrid_recall_routes_correctly() {
2605        let (tool, _conf) = classify("hybrid recall for rust");
2606        assert_eq!(tool, "memory.hybrid_recall");
2607    }
2608
2609    #[test]
2610    fn classify_galaxy_stats_routes_correctly() {
2611        let (tool, _conf) = classify("galaxy stats overview");
2612        assert_eq!(tool, "galaxy.stats");
2613    }
2614
2615    #[test]
2616    fn classify_galaxy_export_routes_correctly() {
2617        let (tool, _conf) = classify("export galaxy backup");
2618        assert_eq!(tool, "galaxy.export");
2619    }
2620
2621    #[test]
2622    fn classify_kg_extract_routes_correctly() {
2623        let (tool, _conf) = classify("extract entities knowledge graph");
2624        assert_eq!(tool, "kg.extract");
2625    }
2626
2627    #[test]
2628    fn classify_kg_query_routes_correctly() {
2629        let (tool, _conf) = classify("knowledge graph query relationships");
2630        assert_eq!(tool, "kg.query");
2631    }
2632
2633    #[test]
2634    fn classify_kg_top_routes_correctly() {
2635        let (tool, _conf) = classify("top hub nodes knowledge graph");
2636        assert_eq!(tool, "kg.top");
2637    }
2638
2639    #[test]
2640    fn classify_graph_walk_routes_correctly() {
2641        let (tool, _conf) = classify("traverse graph walk bfs");
2642        assert_eq!(tool, "graph.walk");
2643    }
2644
2645    #[test]
2646    fn classify_graph_community_routes_correctly() {
2647        let (tool, _conf) = classify("detect communities clusters in graph");
2648        assert_eq!(tool, "graph.community");
2649    }
2650
2651    #[test]
2652    fn classify_graph_propagate_routes_correctly() {
2653        let (tool, _conf) = classify("propagate activation spread ripple");
2654        assert_eq!(tool, "graph.propagate");
2655    }
2656
2657    #[test]
2658    fn classify_galaxy_transfer_routes_correctly() {
2659        let (tool, _conf) = classify("transfer move memories galaxy");
2660        assert_eq!(tool, "galaxy.transfer");
2661    }
2662
2663    #[test]
2664    fn classify_galaxy_merge_routes_correctly() {
2665        let (tool, _conf) = classify("merge combine galaxies");
2666        assert_eq!(tool, "galaxy.merge");
2667    }
2668
2669    #[test]
2670    fn classify_galaxy_snapshot_routes_correctly() {
2671        let (tool, _conf) = classify("snapshot backup galaxy");
2672        assert_eq!(tool, "galaxy.snapshot");
2673    }
2674
2675    #[test]
2676    fn classify_galaxy_restore_routes_correctly() {
2677        let (tool, _conf) = classify("restore recover galaxy snapshot");
2678        assert_eq!(tool, "galaxy.restore");
2679    }
2680
2681    #[test]
2682    fn classify_agent_trust_routes_correctly() {
2683        let (tool, _conf) = classify("trust reliability agent score");
2684        assert_eq!(tool, "agent.trust");
2685    }
2686
2687    #[test]
2688    fn classify_agent_descriptions_routes_correctly() {
2689        let (tool, _conf) = classify("describe agent profile info");
2690        assert_eq!(tool, "agent.descriptions");
2691    }
2692
2693    #[test]
2694    fn classify_agent_capabilities_routes_correctly() {
2695        let (tool, _conf) = classify("agent capabilities skills abilities");
2696        assert_eq!(tool, "agent.capabilities");
2697    }
2698
2699    #[test]
2700    fn classify_agent_heartbeat_history_routes_correctly() {
2701        let (tool, _conf) = classify("heartbeat history log agent");
2702        assert_eq!(tool, "agent.heartbeat.history");
2703    }
2704
2705    #[test]
2706    fn classify_agent_deregister_routes_correctly() {
2707        let (tool, _conf) = classify("deregister unregister remove agent");
2708        assert_eq!(tool, "agent.deregister");
2709    }
2710
2711    #[test]
2712    fn classify_galaxy_dashboard_routes_correctly() {
2713        let (tool, _conf) = classify("galaxy dashboard overview panel");
2714        assert_eq!(tool, "galaxy.dashboard");
2715    }
2716
2717    #[test]
2718    fn classify_galaxy_backup_routes_correctly() {
2719        let (tool, _conf) = classify("backup archive galaxy dump");
2720        assert_eq!(tool, "galaxy.backup");
2721    }
2722
2723    #[test]
2724    fn classify_galaxy_taxonomy_routes_correctly() {
2725        let (tool, _conf) = classify("galaxy taxonomy classification categories");
2726        assert_eq!(tool, "galaxy.taxonomy");
2727    }
2728
2729    #[test]
2730    fn classify_galaxy_purge_routes_correctly() {
2731        let (tool, _conf) = classify("purge wipe clear galaxy");
2732        assert_eq!(tool, "galaxy.purge");
2733    }
2734
2735    #[test]
2736    fn classify_galaxy_health_routes_correctly() {
2737        let (tool, _conf) = classify("galaxy health diagnostic checkup");
2738        assert_eq!(tool, "galaxy.health");
2739    }
2740
2741    #[test]
2742    fn classify_memory_sort_routes_correctly() {
2743        let (tool, _conf) = classify("sort memories by importance");
2744        assert_eq!(tool, "memory.sort");
2745    }
2746
2747    #[test]
2748    fn classify_memory_filter_routes_correctly() {
2749        let (tool, _conf) = classify("filter memories by tag criteria");
2750        assert_eq!(tool, "memory.filter");
2751    }
2752
2753    #[test]
2754    fn classify_memory_deduplicate_routes_correctly() {
2755        let (tool, _conf) = classify("deduplicate memories redundant duplicate");
2756        assert_eq!(tool, "memory.deduplicate");
2757    }
2758
2759    #[test]
2760    fn classify_memory_export_routes_correctly() {
2761        let (tool, _conf) = classify("export memories csv format download");
2762        assert_eq!(tool, "memory.export");
2763    }
2764
2765    #[test]
2766    fn classify_homeostasis_check_routes_correctly() {
2767        let (tool, _conf) = classify("homeostasis check balance vitals metrics");
2768        assert_eq!(tool, "homeostasis.check");
2769    }
2770
2771    #[test]
2772    fn classify_homeostasis_adjust_routes_correctly() {
2773        let (tool, _conf) = classify("homeostasis adjust rebalance weight tune");
2774        assert_eq!(tool, "homeostasis.adjust");
2775    }
2776
2777    #[test]
2778    fn classify_homeostasis_history_routes_correctly() {
2779        let (tool, _conf) = classify("homeostasis history trend past samples");
2780        assert_eq!(tool, "homeostasis.history");
2781    }
2782
2783    #[test]
2784    fn classify_homeostasis_alerts_routes_correctly() {
2785        let (tool, _conf) = classify("homeostasis alerts warning critical threshold");
2786        assert_eq!(tool, "homeostasis.alerts");
2787    }
2788
2789    #[test]
2790    fn classify_reflex_dispatch_routes_correctly() {
2791        let (tool, _conf) = classify("dispatch reflex e_stop emergency handler");
2792        assert_eq!(tool, "reflex.dispatch");
2793    }
2794
2795    #[test]
2796    fn classify_reflex_status_routes_correctly() {
2797        let (tool, _conf) = classify("reflex status table registered handlers");
2798        assert_eq!(tool, "reflex.status");
2799    }
2800
2801    #[test]
2802    fn classify_workspace_spotlight_routes_correctly() {
2803        let (tool, _conf) = classify("workspace spotlight attention arbitration");
2804        assert_eq!(tool, "workspace.spotlight");
2805    }
2806
2807    #[test]
2808    fn classify_workspace_events_routes_correctly() {
2809        let (tool, _conf) = classify("workspace recent events backlog history");
2810        assert_eq!(tool, "workspace.events");
2811    }
2812
2813    #[test]
2814    fn classify_workspace_publish_routes_correctly() {
2815        let (tool, _conf) = classify("publish broadcast workspace event emit");
2816        assert_eq!(tool, "workspace.publish");
2817    }
2818
2819    #[test]
2820    fn classify_workspace_stats_routes_correctly() {
2821        let (tool, _conf) = classify("workspace stats statistics transfers count");
2822        assert_eq!(tool, "workspace.stats");
2823    }
2824
2825    #[test]
2826    fn classify_timescale_status_routes_correctly() {
2827        let (tool, _conf) = classify("timescale status tier bus brain_wave active");
2828        assert_eq!(tool, "timescale.status");
2829    }
2830
2831    #[test]
2832    fn classify_timescale_hooks_routes_correctly() {
2833        let (tool, _conf) = classify("timescale hooks list tick timeout performance");
2834        assert_eq!(tool, "timescale.hooks");
2835    }
2836
2837    #[test]
2838    fn classify_confidence_is_reasonable() {
2839        let (_tool, conf) = classify("remember that rust is fast");
2840        assert!(
2841            conf > 0.15,
2842            "confidence should be > 0.15 for clear match, got {conf}"
2843        );
2844    }
2845
2846    #[test]
2847    fn classify_case_insensitive() {
2848        let (tool1, _) = classify("REMEMBER THAT");
2849        let (tool2, _) = classify("remember that");
2850        assert_eq!(tool1, tool2);
2851    }
2852
2853    #[test]
2854    fn classify_partial_match_works() {
2855        let (tool, conf) = classify("search rust");
2856        assert_eq!(tool, "memory.search");
2857        assert!(conf > 0.0);
2858    }
2859
2860    #[test]
2861    fn classify_multi_word_query() {
2862        let (tool, _conf) = classify("show me the effectiveness report for tools");
2863        assert_eq!(tool, "tools.effectiveness_report");
2864    }
2865
2866    #[test]
2867    fn profile_count_is_reasonable() {
2868        // Should have 50+ profiles
2869        assert!(
2870            profile_count() >= 60,
2871            "expected 60+ profiles, got {}",
2872            profile_count()
2873        );
2874    }
2875
2876    #[test]
2877    fn profiled_tools_are_unique() {
2878        let tools = profiled_tools();
2879        let set: HashSet<&str> = tools.iter().copied().collect();
2880        assert_eq!(tools.len(), set.len(), "duplicate tool names in profiles");
2881    }
2882
2883    #[test]
2884    fn classify_unique_patterns_count() {
2885        let inputs = [
2886            "remember",
2887            "recall",
2888            "list memories",
2889            "delete memory",
2890            "search",
2891            "query",
2892            "associate",
2893            "associations",
2894            "consolidate",
2895            "decay",
2896            "batch read",
2897            "update memory",
2898            "tag memory",
2899            "memory stats",
2900            "hybrid recall",
2901            "count memories",
2902            "list tags",
2903            "mine associations",
2904            "start session",
2905            "checkpoint",
2906            "recall session",
2907            "end session",
2908            "list sessions",
2909            "citta status",
2910            "reflect",
2911            "coherence",
2912            "dream status",
2913            "trigger dream",
2914            "effectiveness",
2915            "retire tool",
2916            "pattern search",
2917            "salience",
2918            "serendipity",
2919            "detect clusters",
2920            "list constellations",
2921            "galaxy stats",
2922            "export galaxy",
2923            "import galaxy",
2924            "karma",
2925            "karma history",
2926            "clear karma",
2927            "dharma rules",
2928            "dharma audit",
2929            "dharma profiles",
2930            "dharma",
2931            "register agent",
2932            "list agents",
2933            "heartbeat",
2934            "distribute task",
2935            "task status",
2936            "system health",
2937            "system config",
2938            "flush",
2939            "tools",
2940            "nearby memories",
2941            "vector search",
2942            "find similar",
2943            "extract entities knowledge graph",
2944            "knowledge graph query",
2945            "top hub nodes",
2946            "traverse graph walk",
2947            "detect communities",
2948            "propagate activation",
2949            "transfer galaxy",
2950            "merge galaxies",
2951            "snapshot galaxy",
2952            "restore galaxy",
2953            "sort memories",
2954            "filter memories",
2955            "deduplicate memories",
2956            "export memories csv",
2957            "homeostasis check",
2958            "homeostasis adjust",
2959            "homeostasis history",
2960            "homeostasis alerts",
2961            "dispatch reflex e_stop",
2962            "reflex status table",
2963            "workspace spotlight attention",
2964            "workspace recent events",
2965            "publish workspace event",
2966            "workspace stats summary",
2967            "timescale status tiers",
2968            "timescale hooks tick",
2969        ];
2970        let mut tools: HashSet<&str> = HashSet::new();
2971        for input in &inputs {
2972            let (tool, _) = classify(input);
2973            tools.insert(tool);
2974        }
2975        assert!(
2976            tools.len() >= 30,
2977            "Expected 30+ unique NLU targets, got {}",
2978            tools.len()
2979        );
2980    }
2981
2982    #[test]
2983    fn classify_vector_search_routes_correctly() {
2984        let (tool, conf) = classify("vector search similar memories");
2985        assert_eq!(tool, "memory.vector.search");
2986        assert!(conf > 0.0);
2987    }
2988
2989    #[test]
2990    fn classify_embedding_search_routes_correctly() {
2991        let (tool, conf) = classify("embedding similarity lookup");
2992        assert_eq!(tool, "memory.vector.search");
2993        assert!(conf > 0.0);
2994    }
2995
2996    #[test]
2997    fn classify_semantic_search_routes_correctly() {
2998        let (tool, conf) = classify("semantic similarity search");
2999        assert_eq!(tool, "memory.vector.search");
3000        assert!(conf > 0.0);
3001    }
3002
3003    #[test]
3004    fn classify_stemming_handles_morphological_variants() {
3005        // -ing form should route same as base
3006        let (tool1, _) = classify("searching for rust");
3007        let (tool2, _) = classify("search for rust");
3008        assert_eq!(tool1, tool2);
3009
3010        // -ed form
3011        let (tool3, _) = classify("stored important fact");
3012        let (tool4, _) = classify("store important fact");
3013        assert_eq!(tool3, tool4);
3014
3015        // plural → singular
3016        let (tool5, _) = classify("list memories");
3017        let (tool6, _) = classify("list memory");
3018        assert_eq!(tool5, tool6);
3019    }
3020
3021    #[test]
3022    fn stem_handles_unicode_without_panicking() {
3023        // Regression: the -ing double-consonant check sliced by byte index,
3024        // panicking on words whose base ends in a multi-byte character.
3025        assert_eq!(stem("xéing"), "xé");
3026        assert_eq!(stem("éing"), "é");
3027        assert_eq!(stem("caféing"), "café");
3028        // ASCII behavior unchanged
3029        assert_eq!(stem("running"), "run");
3030        assert_eq!(stem("swimming"), "swim");
3031        assert_eq!(stem("searching"), "search");
3032        assert_eq!(stem("typing"), "typ");
3033        assert_eq!(stem("memories"), "memory");
3034        assert_eq!(stem("stored"), "store");
3035    }
3036
3037    #[test]
3038    fn classify_handles_unicode_thoughts_without_panicking() {
3039        // Full pipeline: tokenize → stem on multibyte input must not panic.
3040        let (tool, _conf) = classify("caféing sur les mémoires");
3041        assert!(!tool.is_empty());
3042        let (tool2, _conf2) = classify("mémoire éing recherche");
3043        assert!(!tool2.is_empty());
3044    }
3045
3046    #[test]
3047    fn classify_confidence_improved_with_stopwords() {
3048        // With stopwords filtered, confidence should be higher
3049        let (_, conf) = classify("remember that rust is fast");
3050        assert!(
3051            conf > 0.20,
3052            "confidence should be > 0.20 with stopword filtering, got {conf}"
3053        );
3054    }
3055
3056    // ── Self-model (R4) NLU routing tests ───────────────────────────
3057
3058    #[test]
3059    fn classify_forecast_routes_to_selfmodel_forecast() {
3060        let (tool, _conf) = classify("forecast cpu load for next 5 samples");
3061        assert_eq!(tool, "selfmodel.forecast");
3062    }
3063
3064    #[test]
3065    fn classify_predict_routes_to_selfmodel_forecast() {
3066        let (tool, _conf) = classify("predict memory pressure trend");
3067        assert_eq!(tool, "selfmodel.forecast");
3068    }
3069
3070    #[test]
3071    fn classify_alerts_routes_to_selfmodel_alerts() {
3072        let (tool, _conf) = classify("selfmodel alerts");
3073        assert_eq!(tool, "selfmodel.alerts");
3074    }
3075
3076    #[test]
3077    fn classify_warning_routes_to_selfmodel_alerts() {
3078        let (tool, _conf) = classify("selfmodel critical warnings");
3079        assert_eq!(tool, "selfmodel.alerts");
3080    }
3081
3082    #[test]
3083    fn classify_snapshot_routes_to_selfmodel_snapshot() {
3084        let (tool, _conf) = classify("selfmodel snapshot");
3085        assert_eq!(tool, "selfmodel.snapshot");
3086    }
3087
3088    #[test]
3089    fn classify_introspection_routes_to_selfmodel_snapshot() {
3090        let (tool, _conf) = classify("show introspection state overview");
3091        assert_eq!(tool, "selfmodel.snapshot");
3092    }
3093
3094    // ── Bicameral (R5) NLU routing tests ────────────────────────────
3095
3096    #[test]
3097    fn classify_bicameral_debate_routes_to_bicameral_reason() {
3098        let (tool, _conf) = classify("bicameral debate on rust vs python");
3099        assert_eq!(tool, "bicameral.reason");
3100    }
3101
3102    #[test]
3103    fn classify_hemisphere_consensus_routes_to_bicameral_reason() {
3104        let (tool, _conf) = classify("dual hemisphere consensus deliberation");
3105        assert_eq!(tool, "bicameral.reason");
3106    }
3107
3108    #[test]
3109    fn classify_bicameral_status_routes_correctly() {
3110        let (tool, _conf) = classify("bicameral hemisphere status");
3111        assert_eq!(tool, "bicameral.status");
3112    }
3113
3114    #[test]
3115    fn classify_callosum_routes_to_bicameral_reason() {
3116        let (tool, _conf) = classify("corpus callosum debate perspectives");
3117        assert_eq!(tool, "bicameral.reason");
3118    }
3119
3120    // ── Drive & Emotion (R7) NLU routing tests ──────────────────────
3121
3122    #[test]
3123    fn classify_drive_snapshot_routes_correctly() {
3124        let (tool, _conf) = classify("drive snapshot current motivation state");
3125        assert_eq!(tool, "drive.snapshot");
3126    }
3127
3128    #[test]
3129    fn classify_emotion_routes_to_drive_snapshot() {
3130        let (tool, _conf) = classify("show current emotion and mood");
3131        assert_eq!(tool, "drive.snapshot");
3132    }
3133
3134    #[test]
3135    fn classify_drive_event_routes_correctly() {
3136        let (tool, _conf) = classify("inject drive event reward for success");
3137        assert_eq!(tool, "drive.event");
3138    }
3139
3140    #[test]
3141    fn classify_curiosity_routes_to_drive_snapshot() {
3142        let (tool, _conf) = classify("curiosity satisfaction caution levels");
3143        assert_eq!(tool, "drive.snapshot");
3144    }
3145
3146    // ── Adversarial NLU routing tests ───────────────────────────────
3147
3148    #[test]
3149    fn adversarial_remember_in_redteam_query_doesnt_misroute() {
3150        // "remember" embedded in a redteam query should not route to memory.create
3151        let (tool, _conf) = classify("redteam scan to remember uncovered vectors");
3152        assert_ne!(
3153            tool, "memory.create",
3154            "redteam query should not route to memory.create even with 'remember' embedded"
3155        );
3156    }
3157
3158    #[test]
3159    fn adversarial_delete_in_search_query_doesnt_misroute() {
3160        // "delete" embedded in a search query should not route to memory.delete
3161        let (tool, _conf) = classify("search for memories about delete operations");
3162        assert_ne!(
3163            tool, "memory.delete",
3164            "search query should not route to memory.delete even with 'delete' embedded"
3165        );
3166    }
3167
3168    #[test]
3169    fn adversarial_store_in_gnosis_query_doesnt_misroute() {
3170        // "store" embedded in a gnosis query should not route to memory.create
3171        let (tool, _conf) = classify("explain why the store blocked my action");
3172        assert_ne!(
3173            tool, "memory.create",
3174            "gnosis query should not route to memory.create even with 'store' embedded"
3175        );
3176    }
3177
3178    #[test]
3179    fn adversarial_repeated_keyword_doesnt_inflate_score() {
3180        // Repeating a keyword many times should not artificially inflate the score
3181        let (tool, conf) = classify("remember remember remember remember remember remember");
3182        assert_eq!(tool, "memory.create");
3183        // Confidence should be reasonable, not artificially high from repetition
3184        assert!(
3185            conf <= 1.0,
3186            "repeated keywords should not inflate confidence beyond 1.0: got {conf}"
3187        );
3188    }
3189
3190    #[test]
3191    fn adversarial_keyword_stuffing_doesnt_misroute() {
3192        // Stuffing multiple tool keywords should not cause misrouting
3193        let (tool, _conf) = classify("remember delete search list recall store");
3194        // Should route to one of the memory tools, not error out
3195        assert!(
3196            tool.starts_with("memory."),
3197            "keyword stuffing should still route to a memory tool, got {tool}"
3198        );
3199    }
3200
3201    #[test]
3202    fn adversarial_redteam_with_memory_keyword_doesnt_misroute() {
3203        // "memory" embedded in a redteam query should not route to memory tools
3204        let (tool, _conf) = classify("redteam proposals for memory poisoning attack");
3205        assert_eq!(
3206            tool, "redteam.proposals",
3207            "redteam query should route to redteam.proposals even with 'memory' embedded"
3208        );
3209    }
3210
3211    #[test]
3212    fn adversarial_friction_with_delete_keyword_doesnt_misroute() {
3213        // "friction" with "delete" should route to friction.log, not memory.delete
3214        let (tool, _conf) = classify("log friction about delete operations failing");
3215        // Should route to friction.log due to prefix route, not memory.delete
3216        assert_ne!(
3217            tool, "memory.delete",
3218            "friction query should not route to memory.delete even with 'delete' embedded"
3219        );
3220    }
3221
3222    #[test]
3223    fn adversarial_long_input_doesnt_cascade_misroute() {
3224        // Very long input with many keywords should not cascade into wrong routing
3225        let input = "remember to search for delete and list and recall and store and save \
3226                     and memorize and retrieve and fetch and get and load and access and \
3227                     query and find and look and check and count and purge and forget \
3228                     and remove and drop and clear and wipe and erase and destroy";
3229        let (tool, _conf) = classify(input);
3230        // Should route to some memory tool, not panic or return gnosis
3231        assert!(
3232            tool.starts_with("memory.") || tool == "gnosis",
3233            "long input should route to memory tool or gnosis, got {tool}"
3234        );
3235    }
3236
3237    #[test]
3238    fn adversarial_empty_words_between_keywords() {
3239        // Empty words between keywords should not affect routing
3240        let (tool1, _) = classify("remember the important fact");
3241        let (tool2, _) = classify("remember    the    important    fact");
3242        assert_eq!(tool1, tool2, "extra whitespace should not change routing");
3243    }
3244
3245    #[test]
3246    fn adversarial_unicode_homoglyph_doesnt_misroute() {
3247        // Unicode characters that look like ASCII should not cause misrouting
3248        let (tool, _conf) = classify("rеmеmbеr this fact"); // Cyrillic 'е' chars
3249        // Should NOT route to memory.create because the keywords don't match
3250        // (Cyrillic е ≠ Latin e after tokenization)
3251        assert_ne!(
3252            tool, "memory.create",
3253            "unicode homoglyphs should not trick the router into memory.create"
3254        );
3255    }
3256
3257    // ── Imagination Engine NLU routing tests ─────────────────────────
3258
3259    #[test]
3260    fn classify_imagine_scenarios_routes_to_imagine_scenario() {
3261        let (tool, _conf) = classify("imagine scenarios for improving performance");
3262        assert_eq!(tool, "imagine.scenario");
3263    }
3264
3265    #[test]
3266    fn classify_brainstorm_routes_to_imagine_scenario() {
3267        let (tool, _conf) = classify("brainstorm contingency plans for deployment");
3268        assert_eq!(tool, "imagine.scenario");
3269    }
3270
3271    #[test]
3272    fn classify_envision_routes_to_imagine_scenario() {
3273        let (tool, _conf) = classify("envision what-if possibilities for the system");
3274        assert_eq!(tool, "imagine.scenario");
3275    }
3276
3277    #[test]
3278    fn classify_reflect_routes_to_imagine_reflect() {
3279        let (tool, _conf) =
3280            classify("counterfactual reflect on what should have been done instead");
3281        assert_eq!(tool, "imagine.reflect");
3282    }
3283
3284    #[test]
3285    fn classify_counterfactual_routes_to_imagine_reflect() {
3286        let (tool, _conf) = classify("counterfactual analysis of the decision");
3287        assert_eq!(tool, "imagine.reflect");
3288    }
3289
3290    // ── Property-based tests (proptest) ─────────────────────────────
3291
3292    use proptest::prelude::*;
3293
3294    proptest! {
3295        /// classify() must never panic on arbitrary UTF-8 strings.
3296        #[test]
3297        fn classify_never_panics(text in ".*") {
3298            let (tool, conf) = classify(&text);
3299            prop_assert!(!tool.is_empty(), "tool name must be non-empty");
3300            prop_assert!(
3301                (0.0..=1.0).contains(&conf),
3302                "confidence must be in [0,1], got {conf}"
3303            );
3304        }
3305
3306        /// classify() must never panic on arbitrary bytes (lossy UTF-8).
3307        #[test]
3308        fn classify_never_panics_bytes(data in proptest::collection::vec(any::<u8>(), 0..256)) {
3309            let text = String::from_utf8_lossy(&data);
3310            let (tool, conf) = classify(&text);
3311            prop_assert!(!tool.is_empty());
3312            prop_assert!((0.0..=1.0).contains(&conf));
3313        }
3314
3315        /// classify() is deterministic — same input always yields same output.
3316        #[test]
3317        fn classify_is_deterministic(text in ".*") {
3318            let (tool1, conf1) = classify(&text);
3319            let (tool2, conf2) = classify(&text);
3320            prop_assert_eq!(tool1, tool2);
3321            prop_assert!((conf1 - conf2).abs() < f64::EPSILON);
3322        }
3323
3324        /// Empty or whitespace-only input always returns gnosis with 0.0 confidence.
3325        #[test]
3326        fn classify_empty_returns_gnosis_prop(ws in r"[ \t\n\r]*") {
3327            let (tool, conf) = classify(&ws);
3328            prop_assert_eq!(tool, "gnosis");
3329            prop_assert_eq!(conf, 0.0);
3330        }
3331
3332        /// Confidence is always finite (not NaN or infinity).
3333        #[test]
3334        fn classify_confidence_is_finite(text in ".*") {
3335            let (_, conf) = classify(&text);
3336            prop_assert!(conf.is_finite(), "confidence must be finite, got {conf}");
3337        }
3338    }
3339}