UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

380 lines (293 loc) • 17.7 kB
# Develop KXM Set up a source checkout, learn where each part of KXM lives, and run the same gate CI runs before you push. This page is for contributors who change the CLI, the [hub](../glossary.md#hub), the [Runtime](../glossary.md#runtime), the Pi extension, the Claude Code plugin, or the bundled skills. ## Before you begin - Node.js 22.19 or newer on the 22 line, or Node.js 24 or newer. The `engines` field in `package.json` is the authority. - npm and Git. - Optional tools, needed only for the matching task: - [Pi](https://pi.dev) to load the extension from source. - The Claude Code CLI to validate the plugin manifests locally. - [`just`](https://github.com/casey/just) for the remaining [assignment runner](assignment-runner.md) recipes only: `assign`, `witness`, `accept`, `observe-cost`, `attribute`, `change-report`, and `plan-current`. Those recipes are harness transport for the issue 127 runner. They are not how a unit is dispatched. Do not rename or delete them. - `kxm lane run` to dispatch a unit. A writer unit uses `kxm lane run <unit> --brief <file> --workflow implement-only`. The read-only critic workflows on the same command are `review-arch-only` and `review-cli-only`. - Docker for the clean-container install smoke. ## Set up a checkout ```bash git clone https://github.com/kontextmind/kxm.git cd kxm npm ci node scripts/kxm.mjs --help ``` `node scripts/kxm.mjs` is the `kxm` binary from `package.json` `bin`. It runs the committed bundle `plugins/kxm/dist/cli.js`, not the TypeScript source. A source change is invisible until you rebuild: ```bash npm run build ``` Use `node scripts/kxm.mjs` wherever the user docs show `kxm`. ### Keep experiments away from real state A source build uses the same default hub (`http://127.0.0.1:7331`), user state root, configuration and telemetry directories as an installed `kxm`. Point every experiment at throwaway directories and a hub you start yourself on another port: ```bash # Run in a scratch Git repository, not in your KXM checkout. export KXM_STATE_HOME="$(mktemp -d)" export KXM_USER_CONFIG_DIR="$(mktemp -d)" export KXM_USER_TELEMETRY_DIR="$(mktemp -d)" export KXM_PORT=47331 export KXM_SERVER_URL="http://127.0.0.1:$KXM_PORT" node /path/to/kxm/scripts/kxm.mjs hub start ``` In a second terminal with the same variables: ```bash node /path/to/kxm/scripts/kxm.mjs hub view node /path/to/kxm/scripts/kxm.mjs hub stop ``` Expected output of `hub view`: ```text hub health=true ready=true · loopback hub ``` `KXM_STATE_HOME` must be an absolute path. The hub writes its database and logs under `.kxm/` in the directory where you start it. ### Load the Pi extension from source Pi loads the TypeScript extension directly, so it needs no build step: ```bash pi --no-extensions -e ./plugins/kxm/src/extension.ts ``` `--no-extensions` turns off Pi's package discovery, so only the listed extensions load. Add another `-e` for each provider extension your models need. ### Validate the Claude Code plugin With the Claude Code CLI installed, run the same strict manifest validation as CI. Validation reads the manifests only; the plugin itself runs the committed `plugins/kxm/dist/mcp-server.js`, so rebuild before you try it in Claude Code. ```bash npm run validate:claude ``` ## Repository layout The tree below lists what is tracked in Git. Directories marked "not shipped" are excluded from the npm package. ```text . ├── plugins/kxm/ The product: CLI, hub, Runtime, Pi extension, MCP server │ ├── src/ TypeScript source (cli/, context/, providers/ inside) │ ├── dist/ Generated, committed bundles (see Generated artifacts) │ ├── skills/ Bundled Agent Skills, one <name>/SKILL.md each │ ├── skill-suite.json Skill manifest: names, paths, owned commands │ ├── .claude-plugin/ Claude Code plugin manifest (plugin.json) │ └── .mcp.json MCP server launch for the plugin ├── .claude-plugin/ Claude marketplace catalog (marketplace.json) ├── packages/core/tui/ @kontextmind/tui workspace package ├── scripts/ kxm.mjs, hub and worker wrappers, build, release, runners ├── schemas/ JSON Schemas for YAML resources, events and records ├── docs/ User, operator and contributor documentation ├── examples/ Runnable transport examples and project fixtures ├── test/ core/, simulations/, fixtures/, helpers/ (not shipped) ├── .kxm/ This repository's own KXM project definition ├── .agents/skills/ Generated mirror of the bundled skills (not shipped) ├── .github/ CI, release, issue and PR templates (not shipped) ├── .claude/ Developer harness notes and commands (not shipped) ├── plans/ Internal planning and tracking (not shipped) ├── justfile Remaining issue 127 recipes (assign, witness, accept, observe-cost, attribute, change-report, plan-current) └── AGENTS.md, CLAUDE.md, GEMINI.md Agent instructions with generated blocks ``` The workspace layout for `packages/` is described in [Packages and workspaces](packages.md). The `.kxm/` layout is described in [`.kxm/README.md`](../../.kxm/README.md). ## Packaging standards KXM ships one product through four native package formats. Each format reads its own manifest, and `npm run check:versions` keeps every version field equal. ### npm package `@kontextmind/kxm` is published publicly. | Field | What it declares | |---|---| | `bin` | `kxm` → `scripts/kxm.mjs` | | `exports` | `./core`, `./runtime`, `./client`, `./extension`, `./mcp` from `plugins/kxm/dist`, and `./tui` | | `files` | `plugins/kxm/{dist,src,skills}`, `scripts`, `docs`, `examples`, `schemas`, the plugin manifests, and selected `.kxm` files | | `engines` | Node `^22.19.0 \|\| >=24.0.0` | | `peerDependencies` | Pi and `typebox`, both optional | Everything under `docs/` ships in the tarball, so a link from `docs/` into `plans/` is dead for npm readers. `.npmignore` drops logs, state and generated assets even inside listed directories. Inspect the result with `npm pack --dry-run`. ### Claude Code plugin and marketplace | File | Role | |---|---| | `.claude-plugin/marketplace.json` | Marketplace catalog; its `kxm` entry points at `./plugins/kxm` | | `plugins/kxm/.claude-plugin/plugin.json` | Plugin manifest: `userConfig`, the `SessionStart` hook, the channel, and `mcpServers` | | `plugins/kxm/.mcp.json` | Launches `dist/mcp-server.js` over stdio and maps `userConfig` to `KXM_*` variables | | `plugins/kxm/dist/claude-hook.js` | The bundled, read-only `SessionStart` hook script | A marketplace install clones the repository, so the plugin can use only committed files. That is why the bundles in `dist/` are tracked. For what the plugin does, see the [plugin README](../../plugins/kxm/README.md). ### Pi package The root `package.json` carries the `pi-package` keyword and a `pi` block: `pi.extensions` lists `./plugins/kxm/src/extension.ts` and `pi.skills` lists `./plugins/kxm/skills`. Pi loads the TypeScript source itself. ### Agent Skills Each bundled skill is a directory `plugins/kxm/skills/<name>/` with a `SKILL.md`. `plugins/kxm/skill-suite.json` declares every skill and the top-level `kxm` commands it owns. `test/core/skill-suite.test.ts` enforces the rules: - The directory name equals the frontmatter `name` and the manifest `name`. - The frontmatter parses as strict YAML. Quote any value that contains a colon followed by a space. - The `description` is at most 1,024 characters. - Every top-level `kxm` command is owned by exactly one skill. - `.agents/skills/` matches the authored skills byte for byte. For what each skill covers, see [Agent skills](../guides/agent-skills.md). ## The development loop 1. Branch from `main`. Keep one concern per branch and one clear concern per commit. 2. Change the source and the test that proves the change. Update [the test matrix](test-matrix.md) when you add a behavior. 3. Rebuild with `npm run build` when you touched anything that is bundled. 4. Run the focused test file while you iterate (see [Test conventions](#test-conventions)). 5. Update the user docs and `CHANGELOG.md` (under `## Unreleased`) for any user-visible change. 6. Stage the source change together with the regenerated files. The commit gate compares generated files with the staged copy. 7. Run the commit gate, `npm run verify`, fix what it reports, then commit. 8. Push and open a pull request. The template asks for the slice issue and the `npm run verify` result. Maintainers who delegate a unit dispatch it with `kxm lane run <unit> --brief <file> --workflow implement-only`. Read-only critics use the same command with `review-arch-only` or `review-cli-only`. The [assignment runner](assignment-runner.md) recipes (`assign`, `witness`, `accept`, `observe-cost`, `attribute`, `change-report`, `plan-current`) stay on `just`. They are harness transport for the issue 127 runner, not the unit transport, and they cover steps 2 to 7 after a lane run. ## Generated artifacts `npm run build` regenerates the files below. They are committed because the Claude marketplace installs from Git and npm consumers install without dev dependencies. Neither can run a build or strip TypeScript at install time. | Artifact | Produced by | Consumers | |---|---|---| | `plugins/kxm/dist/cli.js`, `server.js`, `runtime-supervisor.js`, `claude-hook.js` | `scripts/build-runtime.mjs` | `kxm`, the hub, the Runtime supervisor, the plugin hook | | `plugins/kxm/dist/core.js`, `runtime.js`, `client.js`, `extension.js` | `scripts/build-runtime.mjs` | The package `exports` | | `plugins/kxm/dist/mcp-server.js` | `npm run build:mcp` | The Claude plugin and `@kontextmind/kxm/mcp` | | `packages/core/tui/dist/index.js` | `npm run build:packages` | `@kontextmind/kxm/tui` | | `.agents/skills/<name>/…` | `scripts/emit-codex-artifacts.mjs` | Harnesses that read `.agents/skills` | | `AGENTS.md` block between the `kxm:codex:commands` markers | `scripts/emit-codex-artifacts.mjs` | Codex and other `AGENTS.md` readers | | `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` blocks between the `kxm:memory` markers | `syncHarnessMemory` (same as `kxm memory sync`) | Every harness, from `.kxm/memory/*.md` | Never edit a generated file by hand. Edit its source and rebuild. `npm run check:generated` rebuilds, then fails if any artifact is missing, not tracked, or different from the staged copy. Stage the rebuilt files (`git add plugins/kxm/dist packages/core/tui/dist .agents AGENTS.md CLAUDE.md GEMINI.md`) before you run it. ## The commit gate Run `npm run verify` before every commit you push. It is the same gate for people and for agents. ```bash npm run verify ``` It runs three scripts in order: | Step | Script | What it checks | |---|---|---| | 1 | `npm test` | Build, then `test/core/*.test.ts` and `packages/core/*/tests/unit/*.test.ts`. The script passes `--test-force-exit` so a finished run leaves the process even if a handle is still open; a failing test is still reported before that exit | | 2 | `npm run check` | `tsc --noEmit`, `markdownlint-cli2`, and version surfaces | | 3 | `npm run check:generated` | Generated artifacts are tracked and match the staged copy | Every suite script passes `--test-force-exit`, and `scripts/run-bounded.mjs` stops the run if the suite is still going after its wall-clock limit (20 minutes, or 40 for the complete and coverage suites). The bound is that wall clock per script. A per-test timeout is not used because under `node --test` each file is itself a test, so a per-test timeout also bounds each file. Other scripts you will use: | Script | Use it for | |---|---| | `npm run test:core` | The core suite only (CI runs a smaller contract set; see [CI and release](ci-and-release.md)) | | `npm run test:simulations` | `test/simulations/*.test.ts` | | `npm run test:coverage` | Core suite with coverage floors | | `npm run test:coverage:complete` | Core plus simulations with the higher nightly floors | | `npm run lint:docs` | Markdown lint only | | `npm run e2e` | Start Obscura if needed, then Playwright in `test/e2e/` (`node --test` does not collect that directory) | | `npm run validate:pr` | The three-minute CI gate; see [CI and release](ci-and-release.md) | | `npm run validate:ci` | Coverage suite, `check` and a package dry run (not run by CI today) | | `npm run validate:claude` | Strict Claude plugin and marketplace validation | Plugin validation needs the Claude Code CLI, so it is a CI job rather than part of `verify`. Do not add a third gate script: `test/core/ci-contract.test.ts` refuses one. ### Coverage floors | Run | Lines | Branches | Functions | |---|---:|---:|---:| | `test:coverage:core` (pushes to `main`, releases) | 91% | 80% | 92% | | `test:coverage:complete` (nightly) | 93% | 80% | 93% | Coverage measures `plugins/kxm/src/**/*.ts` and `packages/core/*/src/**/*.ts`. The floors only move up. A pull request may raise a floor. No pull request may lower one: when coverage drops, add tests. Three files are excluded because they run only as spawned bundles, so coverage would attribute their lines to `dist`, never to the source. Each is exercised as a child process instead: - `plugins/kxm/src/server.ts`, the hub entry; - `plugins/kxm/src/mcp-server.ts`, the MCP stdio entry; - `plugins/kxm/src/runtime-supervisor.ts`, the Runtime supervisor entry. An exclusion needs a reason other than "hard to test". ## Test conventions Tests use the built-in `node:test` runner and run TypeScript through `--experimental-strip-types`. There is no Jest or Vitest. ### Write one focused test per behavior - Name each test after the behavior it proves, as a sentence. For example, `"MCP server never registers with the persisted admin token"`. - Assert the observable contract: an exit code, a JSON field, an error code, a file on disk. Do not assert log wording unless the wording is the contract. - Prove the refusal as well as the success. Fail-closed paths need their own test. - Put the test next to its peers in `test/core/<area>.test.ts`, and add a row to [the test matrix](test-matrix.md). ### Run one file or one test Build once, then run the file directly: ```bash npm run build node --disable-warning=ExperimentalWarning --experimental-strip-types \ --test test/core/improve.test.ts # Narrow to tests whose names match a pattern. node --disable-warning=ExperimentalWarning --experimental-strip-types \ --test --test-name-pattern="same-ask" test/core/improve.test.ts ``` Several suites spawn the built bundles, so a stale `dist` fails them. Rebuild before you trust a failure. ### Isolate all state A test must never read or write the operator's hub, state root, configuration or credentials. Use the shared helpers: | Helper | What it isolates | |---|---| | `isolateSessionEnvironment()` in `test/helpers/session-env.ts` | Points `KXM_STATE_HOME` and `KXM_USER_CONFIG_DIR` at a temp directory and clears session tokens | | `createTestMesh(context)` in `test/helpers.ts` | Starts an in-memory hub on port `0` with a test token, closed when the test ends | | `committedKxmProject(prefix, …)` in `test/helpers/project.ts` | Creates a committed KXM project and a separate state root in temp directories | | `test/helpers/harness-fake.ts` | Fake spawns and auth fixtures for `scripts/harness-run.mjs`, so no real harness or login is needed | Other rules: - Create files only under `mkdtempSync(join(tmpdir(), …))` and remove them when the test ends. - Bind servers to port `0`. Never assume `7331` is free. - Make no network calls outside loopback, and never call a paid model. Real-model checks are opt-in smoke tests (see [CI and release](ci-and-release.md#smoke-tests)). - Tests must pass on Node 22.19 and Node 24. ## Change the protocol Routes, request fields, status transitions, delivery modes, limits and authentication are the protocol. A change to any of them needs: - integration tests; - an update to `plugins/kxm/skills/kxm/references/protocol.md`; - an update to the matching page in `docs/`; - a `CHANGELOG.md` entry; - a compatibility note when existing clients could break. Prefer additive changes. KXM does not keep backward-compatible aliases for retired names: an old name fails closed instead. ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| | `generated artifacts changed after build` | `dist`, a skill mirror or a generated block differs from the index | Run `npm run build`, stage the listed files, rerun | | `generated artifacts are not tracked by git` | A new bundle or skill file was never added | `git add` the listed paths | | `… version … does not match …` from `check:versions` | A version surface was edited by hand | Revert it; releases stamp versions (see [CI and release](ci-and-release.md#versions)) | | A doc test fails after a docs move | A test or code path reads that doc | See [pinned paths](writing-docs.md#pinned-paths) | | A CLI change has no effect | `node scripts/kxm.mjs` runs the old bundle | `npm run build` | ## Next steps - Understand what CI runs on your pull request: [CI and release](ci-and-release.md) - Write or update a docs page: [Write KXM documentation](writing-docs.md) - Find the test that proves a behavior: [Test matrix](test-matrix.md) - Add a workspace package: [Packages and workspaces](packages.md) - Look up a command: [CLI reference](../reference/cli-reference.md)