@kontextmind/kxm 0.7.196 → 0.7.197

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.196",
14
+ "version": "0.7.197",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -714,13 +714,18 @@ All notable user-facing changes are documented here. The project follows [Semant
714
714
 
715
715
  ### Fixed
716
716
 
717
+ - **`kxm suggest` matches workflow keywords as whole words.**
718
+ A substring match sent "guide" and "build" to the feature workflow because
719
+ they contain `ui`, "author" to vulnerability remediation because it contains
720
+ `auth`, and "change" to bug-fix because it contains `hang`. Hyphenated
721
+ keywords such as `circuit-breaker` still match as one token.
722
+
717
723
  - **A first run says what to do next when Git or the hub is missing.**
718
724
  `kxm init` outside a Git worktree still exits 1 with `git_root_required` and
719
725
  now says to run `git init`, then `kxm init` again. `kxm hub view` adds
720
726
  `hint` when both probes are unreachable or time out: on loopback, start a
721
727
  hub with `kxm hub start`; on a remote URL, check the address and
722
728
  `kxm hub bind`.
723
-
724
729
  - **`kxm vision assert --route` selects the Pi model, and an unadmitted route names the fix.**
725
730
  The flag used to change only the admission check. Pi still ran
726
731
  `zai-coding-cn/glm-5.3-flash`. Another admitted id is now `pi --model <id>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.196",
3
+ "version": "0.7.197",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.196",
5
+ "version": "0.7.197",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -38624,6 +38624,12 @@ var WORKFLOW_PATTERNS = [
38624
38624
  skills: ["kxm-session", "kxm-context-memory", "kxm-routing-improve"]
38625
38625
  }
38626
38626
  ];
38627
+ function keywordHit(normalized, words, keyword) {
38628
+ if (words.includes(keyword)) return true;
38629
+ if (!keyword.includes("-")) return false;
38630
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38631
+ return new RegExp(`(?:^|[^a-z0-9])${escaped}(?:$|[^a-z0-9])`).test(normalized);
38632
+ }
38627
38633
  function suggestWorkflowAndRoles(prompt, options = {}) {
38628
38634
  const normalized = prompt.toLowerCase();
38629
38635
  const words = normalized.split(/\W+/).filter(Boolean);
@@ -38632,7 +38638,7 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
38632
38638
  let bestScore = 0;
38633
38639
  const reasons = [];
38634
38640
  for (const pattern of WORKFLOW_PATTERNS) {
38635
- const matchedWords = pattern.keywords.filter((keyword) => words.includes(keyword) || normalized.includes(keyword));
38641
+ const matchedWords = pattern.keywords.filter((keyword) => keywordHit(normalized, words, keyword));
38636
38642
  if (matchedWords.length > bestScore) {
38637
38643
  bestScore = matchedWords.length;
38638
38644
  bestPattern = pattern;
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
25462
25462
  }
25463
25463
 
25464
25464
  // plugins/kxm/src/mcp-server.ts
25465
- var VERSION = "0.7.196";
25465
+ var VERSION = "0.7.197";
25466
25466
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
25467
25467
  var inbox = /* @__PURE__ */ new Map();
25468
25468
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.196",
3
+ "version": "0.7.197",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
11
11
  import type { HubEvent, MessageRecord } from "./protocol.ts";
12
12
  import { sessionTokenFixHint } from "./session-token-hint.ts";
13
13
 
14
- const VERSION = "0.7.196";
14
+ const VERSION = "0.7.197";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();
@@ -96,6 +96,17 @@ const WORKFLOW_PATTERNS: WorkflowPattern[] = [
96
96
  ];
97
97
 
98
98
  type TemplateStep = { id: string; kind: string; agent?: string; repositories?: Record<string, string> };
99
+
100
+ /** Whole tokens only. A substring match made `ui` hit `build` and `guide`,
101
+ * `auth` hit `author`, and `hang` hit `change`, so those prompts took the
102
+ * wrong workflow. Hyphenated keywords stay one token (`circuit-breaker`). */
103
+ function keywordHit(normalized: string, words: readonly string[], keyword: string): boolean {
104
+ if (words.includes(keyword)) return true;
105
+ if (!keyword.includes("-")) return false;
106
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
107
+ return new RegExp(`(?:^|[^a-z0-9])${escaped}(?:$|[^a-z0-9])`).test(normalized);
108
+ }
109
+
99
110
  type SuggestionHarness = Pick<HarnessStatus, "id" | "detected" | "authenticated" | "dispatch">;
100
111
 
101
112
  export function suggestWorkflowAndRoles(
@@ -110,7 +121,7 @@ export function suggestWorkflowAndRoles(
110
121
  const reasons: string[] = [];
111
122
 
112
123
  for (const pattern of WORKFLOW_PATTERNS) {
113
- const matchedWords = pattern.keywords.filter((keyword) => words.includes(keyword) || normalized.includes(keyword));
124
+ const matchedWords = pattern.keywords.filter((keyword) => keywordHit(normalized, words, keyword));
114
125
  if (matchedWords.length > bestScore) {
115
126
  bestScore = matchedWords.length;
116
127
  bestPattern = pattern;