@kontextmind/kxm 0.7.195 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +12 -1
- package/docs/operations/troubleshooting.md +5 -0
- package/docs/reference/cli-reference.md +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +15 -5
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/hub.ts +7 -1
- package/plugins/kxm/src/init.ts +2 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/suggest.ts +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -714,6 +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
|
+
|
|
723
|
+
- **A first run says what to do next when Git or the hub is missing.**
|
|
724
|
+
`kxm init` outside a Git worktree still exits 1 with `git_root_required` and
|
|
725
|
+
now says to run `git init`, then `kxm init` again. `kxm hub view` adds
|
|
726
|
+
`hint` when both probes are unreachable or time out: on loopback, start a
|
|
727
|
+
hub with `kxm hub start`; on a remote URL, check the address and
|
|
728
|
+
`kxm hub bind`.
|
|
717
729
|
- **`kxm vision assert --route` selects the Pi model, and an unadmitted route names the fix.**
|
|
718
730
|
The flag used to change only the admission check. Pi still ran
|
|
719
731
|
`zai-coding-cn/glm-5.3-flash`. Another admitted id is now `pi --model <id>`
|
|
@@ -721,7 +733,6 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
721
733
|
`route_not_admitted` includes the route id and `kxm routes admit --model`.
|
|
722
734
|
This checkout does not admit the default; pass
|
|
723
735
|
`--route openrouter/z-ai/glm-5.3-flash`.
|
|
724
|
-
|
|
725
736
|
- **The weekly model refresh opens its pull request with a GitHub App.**
|
|
726
737
|
`.github/workflows/refresh-models.yml` uses `actions/create-github-app-token`
|
|
727
738
|
with repository secrets `KXM_REFRESH_APP_ID` and
|
|
@@ -19,6 +19,7 @@ Work from the smallest boundary outward:
|
|
|
19
19
|
| Symptom | Cause | Fix |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| `kxm: command not found` | Only the Pi package or the Claude Code plugin was installed; neither installs the CLI | `npm install --global --omit=peer @kontextmind/kxm`; see [Install KXM](../start/install.md) |
|
|
22
|
+
| `git_root_required` from `kxm init` | The current directory is not inside a Git worktree | Run `git init` in that directory, then `kxm init` again. If Git is present, run `kxm init` from the worktree root |
|
|
22
23
|
| `kxm` works in one shell but not in another | npm's global bin directory is not on that shell's `PATH` | Run `kxm completion install` (bash and zsh also get a `PATH` entry), then open a new shell |
|
|
23
24
|
| Tab completion does nothing | Completion is not installed for this shell | `kxm completion install --shell bash`, `zsh` or `fish`; it is idempotent, and `--dry-run` previews it |
|
|
24
25
|
| Plugin tools fail on start | Node.js is older than 22.19 on the 22.x line, or older than 24 | Install a supported Node.js on the `PATH` the harness uses |
|
|
@@ -42,6 +43,10 @@ If it still reports `not_detected`, confirm that `%AppData%\Roaming\npm` is on t
|
|
|
42
43
|
|
|
43
44
|
## Hub and authentication
|
|
44
45
|
|
|
46
|
+
### Nothing is listening
|
|
47
|
+
|
|
48
|
+
`kxm hub view` prints `hub health=false ready=false` and, for a loopback URL, `Nothing is listening on this loopback URL. Start a local hub with kxm hub start.` A remote URL names `kxm hub bind` instead. An HTML login page or a redirect is a different failure: the status line says `auth proxy`, and the fix is the proxy or the hub URL, not `kxm hub start`.
|
|
49
|
+
|
|
45
50
|
### The hub refuses to start
|
|
46
51
|
|
|
47
52
|
| Message | Fix |
|
|
@@ -511,7 +511,7 @@ Calls `GET /health` and `GET /ready` on the target hub and reports whether the U
|
|
|
511
511
|
No command-specific options.
|
|
512
512
|
|
|
513
513
|
- Needs a hub to succeed. Reads only.
|
|
514
|
-
- JSON keys: `target` (`url`, `scope`, and `source: "env"` when `KXM_SERVER_URL` overrides a binding), `identity` (`mode`, `modeSource`, `url`, `urlSource`, `project`, `projectSource`, `keySource`), `health`, `ready
|
|
514
|
+
- JSON keys: `target` (`url`, `scope`, and `source: "env"` when `KXM_SERVER_URL` overrides a binding), `identity` (`mode`, `modeSource`, `url`, `urlSource`, `project`, `projectSource`, `keySource`), `health`, `ready`, and `hint` when both probes are unreachable or time out. An unreachable hub reports `{"error":"hub_unreachable"}` for both probes. On a loopback URL the hint is `Nothing is listening on this loopback URL. Start a local hub with kxm hub start.` A remote URL names `kxm hub bind`. An auth proxy or redirect reports `{"error":"auth_proxy"}` and has no hint.
|
|
515
515
|
- Exit 0 when both probes succeed, 1 otherwise. A cloud mode with no resolvable key exits 2 with `cloud_token_missing`.
|
|
516
516
|
|
|
517
517
|
```bash
|
package/package.json
CHANGED
|
@@ -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.
|
|
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",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -30893,7 +30893,7 @@ function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
|
|
|
30893
30893
|
};
|
|
30894
30894
|
}
|
|
30895
30895
|
if (plan.projectRoot !== gitRoot) {
|
|
30896
|
-
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run
|
|
30896
|
+
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run from the Git worktree root")]);
|
|
30897
30897
|
}
|
|
30898
30898
|
const projectId = generatedProjectId(options.projectId);
|
|
30899
30899
|
const projectName = normalizedProjectName(gitRoot, options.projectName);
|
|
@@ -30918,7 +30918,7 @@ function initializeKxmProject(start = process.cwd(), options = {}) {
|
|
|
30918
30918
|
assertNoRegisteredGates(options);
|
|
30919
30919
|
const gitRoot = discoverGitRoot(start);
|
|
30920
30920
|
if (!gitRoot) {
|
|
30921
|
-
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run inside
|
|
30921
|
+
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run inside a Git worktree. Run git init in this directory, then run kxm init again.")]);
|
|
30922
30922
|
}
|
|
30923
30923
|
if (options.projectId !== void 0) generatedProjectId(options.projectId);
|
|
30924
30924
|
if (options.projectName !== void 0) normalizedProjectName(gitRoot, options.projectName);
|
|
@@ -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) =>
|
|
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;
|
|
@@ -53997,6 +54003,8 @@ async function cmdStatus(runtime) {
|
|
|
53997
54003
|
const notHub = !proxyKind && (health.kind === "not_hub" || ready.kind === "not_hub");
|
|
53998
54004
|
const probeNote = proxyKind === "redirect" ? " \xB7 auth proxy or redirect" : proxyKind === "auth_proxy" ? " \xB7 auth proxy (HTML login page)" : notHub ? " \xB7 not a kxm hub" : "";
|
|
53999
54005
|
const effectiveScope = connection.scope;
|
|
54006
|
+
const hubDown = (health.kind === "unreachable" || health.kind === "timeout") && (ready.kind === "unreachable" || ready.kind === "timeout");
|
|
54007
|
+
const hint = !hubDown ? void 0 : effectiveScope === "loopback" ? "Nothing is listening on this loopback URL. Start a local hub with kxm hub start." : "Nothing answered this hub URL. Check the address, the tunnel, and kxm hub bind.";
|
|
54000
54008
|
const overridden = connection.urlSource === "env";
|
|
54001
54009
|
const identity = {
|
|
54002
54010
|
mode: connection.mode,
|
|
@@ -54013,13 +54021,15 @@ async function cmdStatus(runtime) {
|
|
|
54013
54021
|
target: { url: runtime.serverUrl, scope: effectiveScope, ...overridden ? { source: "env" } : {} },
|
|
54014
54022
|
identity,
|
|
54015
54023
|
health: health.body,
|
|
54016
|
-
ready: ready.body
|
|
54024
|
+
ready: ready.body,
|
|
54025
|
+
...hint ? { hint } : {}
|
|
54017
54026
|
};
|
|
54018
54027
|
print(
|
|
54019
54028
|
runtime.io,
|
|
54020
54029
|
runtime.json,
|
|
54021
54030
|
payload,
|
|
54022
|
-
`hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} \xB7 mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}${probeNote}`
|
|
54031
|
+
`hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} \xB7 mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}${probeNote}${hint ? `
|
|
54032
|
+
${hint}` : ""}`
|
|
54023
54033
|
);
|
|
54024
54034
|
return payload.ok ? 0 : 1;
|
|
54025
54035
|
}
|
|
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25462
25462
|
}
|
|
25463
25463
|
|
|
25464
25464
|
// plugins/kxm/src/mcp-server.ts
|
|
25465
|
-
var VERSION = "0.7.
|
|
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();
|
package/plugins/kxm/package.json
CHANGED
|
@@ -202,6 +202,11 @@ export async function cmdStatus(runtime: Runtime): Promise<number> {
|
|
|
202
202
|
// A cloud mode is remote when the contacted URL is that endpoint, including a
|
|
203
203
|
// loopback SSH forward.
|
|
204
204
|
const effectiveScope = connection.scope;
|
|
205
|
+
const hubDown = (health.kind === "unreachable" || health.kind === "timeout")
|
|
206
|
+
&& (ready.kind === "unreachable" || ready.kind === "timeout");
|
|
207
|
+
const hint = !hubDown ? undefined : effectiveScope === "loopback"
|
|
208
|
+
? "Nothing is listening on this loopback URL. Start a local hub with kxm hub start."
|
|
209
|
+
: "Nothing answered this hub URL. Check the address, the tunnel, and kxm hub bind.";
|
|
205
210
|
const overridden = connection.urlSource === "env";
|
|
206
211
|
const identity = {
|
|
207
212
|
mode: connection.mode,
|
|
@@ -219,9 +224,10 @@ export async function cmdStatus(runtime: Runtime): Promise<number> {
|
|
|
219
224
|
identity,
|
|
220
225
|
health: health.body,
|
|
221
226
|
ready: ready.body,
|
|
227
|
+
...(hint ? { hint } : {}),
|
|
222
228
|
};
|
|
223
229
|
print(runtime.io, runtime.json, payload,
|
|
224
|
-
`hub health=${health.ok} ready=${ready.ok} · ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} · mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}${probeNote}`);
|
|
230
|
+
`hub health=${health.ok} ready=${ready.ok} · ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} · mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}${probeNote}${hint ? `\n${hint}` : ""}`);
|
|
225
231
|
return payload.ok ? 0 : 1;
|
|
226
232
|
}
|
|
227
233
|
|
package/plugins/kxm/src/init.ts
CHANGED
|
@@ -390,7 +390,7 @@ function initializeKxmProjectAtGitRoot(
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
if (plan.projectRoot !== gitRoot) {
|
|
393
|
-
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run
|
|
393
|
+
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run from the Git worktree root")]);
|
|
394
394
|
}
|
|
395
395
|
const projectId = generatedProjectId(options.projectId);
|
|
396
396
|
const projectName = normalizedProjectName(gitRoot, options.projectName);
|
|
@@ -416,7 +416,7 @@ export function initializeKxmProject(start = process.cwd(), options: KxmInitOpti
|
|
|
416
416
|
assertNoRegisteredGates(options);
|
|
417
417
|
const gitRoot = discoverGitRoot(start);
|
|
418
418
|
if (!gitRoot) {
|
|
419
|
-
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run inside
|
|
419
|
+
throw new KxmConfigError([initIssue("git_root_required", ".", "kxm init must run inside a Git worktree. Run git init in this directory, then run kxm init again.")]);
|
|
420
420
|
}
|
|
421
421
|
if (options.projectId !== undefined) generatedProjectId(options.projectId);
|
|
422
422
|
if (options.projectName !== undefined) normalizedProjectName(gitRoot, options.projectName);
|
|
@@ -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.
|
|
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) =>
|
|
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;
|