@kontextmind/kxm 0.7.40 → 0.7.43
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 +41 -7
- package/docs/README.md +5 -3
- package/docs/agent-skills.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/configuration.md +28 -5
- package/docs/{vnext → contracts}/README.md +7 -7
- package/docs/{vnext → contracts}/architecture.md +2 -2
- package/docs/{vnext → contracts}/migration.md +25 -25
- package/docs/{vnext → contracts}/routing.md +2 -2
- package/docs/{vnext → contracts}/synchronization.md +1 -1
- package/docs/{vnext → contracts}/validation.md +7 -7
- package/docs/getting-started.md +1 -1
- package/docs/kb/qa-authentik-authentication.md +47 -0
- package/docs/kb/qa-extension-install-and-hub-bootstrap.md +68 -0
- package/docs/kb/qa-hub-on-a-public-host.md +31 -0
- package/docs/kb/qa-sqlite-vs-duckdb.md +18 -0
- package/docs/kb/qa-what-the-hub-stores.md +47 -0
- package/docs/packages.md +91 -0
- package/docs/templates/architecture.md +1 -1
- package/docs/test-matrix.md +6 -6
- package/docs/tui-components.md +131 -0
- package/examples/README.md +2 -2
- package/examples/{vnext → project}/.kxm/agents/critic-3.yaml +1 -1
- package/examples/{vnext → project}/README.md +4 -4
- package/package.json +18 -8
- package/packages/core/tui/CHANGELOG.md +19 -0
- package/packages/core/tui/LICENSE +21 -0
- package/packages/core/tui/README.md +45 -0
- package/packages/core/tui/dist/index.js +1439 -0
- package/packages/core/tui/src/adapters/pi.ts +73 -0
- package/packages/core/tui/src/adapters/terminal.ts +106 -0
- package/packages/core/tui/src/exports/index.ts +30 -0
- package/packages/core/tui/src/services/registry.ts +274 -0
- package/packages/core/tui/src/tui/keys.ts +130 -0
- package/packages/core/tui/src/tui/layout.ts +117 -0
- package/packages/core/tui/src/tui/panel.ts +435 -0
- package/packages/core/tui/src/tui/panelComponent.ts +208 -0
- package/packages/core/tui/src/tui/render.ts +309 -0
- package/packages/core/tui/src/tui/theme.ts +145 -0
- package/packages/core/tui/src/types/surface.ts +407 -0
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +681 -553
- package/plugins/kxm/dist/extension.js +18 -18
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/{vnext-runtime-supervisor.js → runtime-supervisor.js} +553 -553
- package/plugins/kxm/dist/runtime.js +608 -608
- package/plugins/kxm/dist/server.js +13 -13
- package/plugins/kxm/package.json +2 -2
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +2 -2
- package/plugins/kxm/skills/kxm-runs/SKILL.md +3 -3
- package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +1 -1
- package/plugins/kxm/src/autocomplete.ts +4 -4
- package/plugins/kxm/src/{vnext-bindings.ts → bindings.ts} +48 -48
- package/plugins/kxm/src/cli/hub.ts +3 -3
- package/plugins/kxm/src/cli/{vnext.ts → project.ts} +106 -106
- package/plugins/kxm/src/cli/roles.ts +9 -9
- package/plugins/kxm/src/cli/system.ts +2 -2
- package/plugins/kxm/src/cli/tasks.ts +8 -8
- package/plugins/kxm/src/cli/workflows.ts +8 -8
- package/plugins/kxm/src/cli.ts +47 -47
- package/plugins/kxm/src/config.ts +69 -14
- package/plugins/kxm/src/database.ts +4 -4
- package/plugins/kxm/src/{vnext-engine-artifacts.ts → engine-artifacts.ts} +7 -7
- package/plugins/kxm/src/{vnext-engine-command.ts → engine-command.ts} +40 -40
- package/plugins/kxm/src/{vnext-engine-compile.ts → engine-compile.ts} +64 -64
- package/plugins/kxm/src/{vnext-engine-evidence.ts → engine-evidence.ts} +26 -26
- package/plugins/kxm/src/{vnext-engine-fold.ts → engine-fold.ts} +99 -99
- package/plugins/kxm/src/{vnext-engine-gate-records.ts → engine-gate-records.ts} +105 -105
- package/plugins/kxm/src/{vnext-engine-plan.ts → engine-plan.ts} +69 -69
- package/plugins/kxm/src/{vnext-engine.ts → engine.ts} +502 -502
- package/plugins/kxm/src/gate-hash.ts +10 -0
- package/plugins/kxm/src/{vnext-harness.ts → harness.ts} +1 -1
- package/plugins/kxm/src/hub.ts +1 -1
- package/plugins/kxm/src/init-guide-setup.ts +3 -3
- package/plugins/kxm/src/{vnext-init.ts → init.ts} +93 -93
- package/plugins/kxm/src/kxm-install-kind.ts +1 -1
- package/plugins/kxm/src/kxm-update-config.ts +2 -2
- package/plugins/kxm/src/local-snapshot.ts +23 -23
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/{vnext-migrate.ts → migrate.ts} +123 -123
- package/plugins/kxm/src/model-inventory.ts +1 -1
- package/plugins/kxm/src/{vnext-oneshot-evidence.ts → oneshot-evidence.ts} +2 -2
- package/plugins/kxm/src/{vnext-oneshot-process.ts → oneshot-process.ts} +4 -4
- package/plugins/kxm/src/{vnext-oneshot-producer.ts → oneshot-producer.ts} +24 -24
- package/plugins/kxm/src/{vnext-permission.ts → permission.ts} +86 -86
- package/plugins/kxm/src/{vnext-pi-producer.ts → pi-producer.ts} +13 -13
- package/plugins/kxm/src/{vnext-config.ts → project-config.ts} +177 -177
- package/plugins/kxm/src/{vnext-repair.ts → repair.ts} +159 -159
- package/plugins/kxm/src/restricted-yaml.d.mts +3 -3
- package/plugins/kxm/src/restricted-yaml.mjs +4 -4
- package/plugins/kxm/src/{vnext-runtime-owner.ts → runtime-owner.ts} +35 -35
- package/plugins/kxm/src/{vnext-runtime.ts → runtime-service.ts} +136 -136
- package/plugins/kxm/src/{vnext-runtime-store.ts → runtime-store.ts} +145 -145
- package/plugins/kxm/src/{vnext-runtime-supervisor.ts → runtime-supervisor.ts} +80 -80
- package/plugins/kxm/src/runtime.ts +6 -6
- package/plugins/kxm/src/session-work.ts +3 -3
- package/plugins/kxm/src/studio-layout.ts +6 -6
- package/plugins/kxm/src/{vnext-template.ts → template.ts} +33 -33
- package/plugins/kxm/src/tui.ts +10 -24
- package/schemas/{vnext/README.md → README.md} +3 -3
- package/schemas/{vnext/agent.schema.json → agent.schema.json} +1 -1
- package/schemas/{vnext/assignment-result.schema.json → assignment-result.schema.json} +1 -1
- package/schemas/{vnext/backup-manifest.schema.json → backup-manifest.schema.json} +1 -1
- package/schemas/{vnext/candidate.schema.json → candidate.schema.json} +1 -1
- package/schemas/{vnext/common.schema.json → common.schema.json} +2 -2
- package/schemas/{vnext/context-candidate.schema.json → context-candidate.schema.json} +1 -1
- package/schemas/{vnext/context-packet.schema.json → context-packet.schema.json} +1 -1
- package/schemas/{vnext/delivery-manifest.schema.json → delivery-manifest.schema.json} +1 -1
- package/schemas/{vnext/drive-receipt.schema.json → drive-receipt.schema.json} +2 -2
- package/schemas/{vnext/environment.schema.json → environment.schema.json} +1 -1
- package/schemas/{vnext/gate-registry.schema.json → gate-registry.schema.json} +1 -1
- package/schemas/{vnext/handoff-manifest.schema.json → handoff-manifest.schema.json} +1 -1
- package/schemas/{vnext/init-operation.schema.json → init-operation.schema.json} +1 -1
- package/schemas/{vnext/local-repository-bindings.schema.json → local-repository-bindings.schema.json} +1 -1
- package/schemas/{vnext/memory-record.schema.json → memory-record.schema.json} +1 -1
- package/schemas/{vnext/migration-decision.schema.json → migration-decision.schema.json} +1 -1
- package/schemas/{vnext/migration-plan.schema.json → migration-plan.schema.json} +1 -1
- package/schemas/{vnext/migration-receipt.schema.json → migration-receipt.schema.json} +1 -1
- package/schemas/{vnext/model.schema.json → model.schema.json} +1 -1
- package/schemas/{vnext/modes.schema.json → modes.schema.json} +1 -1
- package/schemas/{vnext/permission-diff.schema.json → permission-diff.schema.json} +1 -1
- package/schemas/policy-draft/README.md +2 -2
- package/schemas/{vnext/prices.schema.json → prices.schema.json} +1 -1
- package/schemas/{vnext/project.schema.json → project.schema.json} +1 -1
- package/schemas/{vnext/repository.schema.json → repository.schema.json} +1 -1
- package/schemas/{vnext/role.schema.json → role.schema.json} +1 -1
- package/schemas/{vnext/run-event.schema.json → run-event.schema.json} +1 -1
- package/schemas/{vnext/session-brief.schema.json → session-brief.schema.json} +2 -2
- package/schemas/{vnext/sync-event.schema.json → sync-event.schema.json} +1 -1
- package/schemas/{vnext/template-provenance.schema.json → template-provenance.schema.json} +1 -1
- package/schemas/{vnext/workflow.schema.json → workflow.schema.json} +1 -1
- package/scripts/build-package.mjs +59 -0
- package/scripts/build-runtime.mjs +3 -2
- package/scripts/check-generated.mjs +2 -1
- package/scripts/check-versions.mjs +12 -1
- package/scripts/docker-install-smoke.mjs +268 -0
- package/scripts/emit-codex-artifacts.mjs +1 -1
- package/scripts/kxm-bump-version.mjs +53 -3
- package/scripts/kxm-runtime-supervisor.mjs +3 -3
- package/scripts/package-surfaces.mjs +39 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +0 -10
- /package/docs/{vnext → contracts}/effects-and-recovery.md +0 -0
- /package/docs/{vnext → contracts}/lifecycles.md +0 -0
- /package/docs/{vnext → contracts}/terminology.md +0 -0
- /package/examples/{vnext → project}/.kxm/agents/coordinator.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-1.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-2.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/implementer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/planner.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reproducer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reviewer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/gates.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-claude.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-gemini.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-grok.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/implementation.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/primary.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/prices.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project/env.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/default.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/fix.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/improve.yaml +0 -0
- /package/examples/{vnext → project}/records/assignment-result-recorded.json +0 -0
- /package/examples/{vnext → project}/records/assignment-result.json +0 -0
- /package/examples/{vnext → project}/records/context-candidate.json +0 -0
- /package/examples/{vnext → project}/records/delivery-manifest.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved-sync.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved.json +0 -0
- /package/examples/{vnext → project}/records/run-created.json +0 -0
- /package/examples/{vnext → project}/records/sync-event.json +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/env.yaml +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/repositories/web/.kxm/repo/repo.yaml +0 -0
|
@@ -10966,12 +10966,12 @@ var DatabaseSync = class {
|
|
|
10966
10966
|
}
|
|
10967
10967
|
};
|
|
10968
10968
|
|
|
10969
|
-
// plugins/kxm/src/
|
|
10969
|
+
// plugins/kxm/src/project-config.ts
|
|
10970
10970
|
import { basename as basename2, dirname as dirname3, extname as extname2, isAbsolute, join as join4, relative, resolve as resolve3, sep } from "node:path";
|
|
10971
10971
|
|
|
10972
10972
|
// plugins/kxm/src/restricted-yaml.mjs
|
|
10973
10973
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
10974
|
-
var
|
|
10974
|
+
var KXM_YAML_LIMITS = Object.freeze({
|
|
10975
10975
|
maxDocumentBytes: 256 * 1024,
|
|
10976
10976
|
maxDepth: 32,
|
|
10977
10977
|
maxScalarBytes: 64 * 1024,
|
|
@@ -10980,7 +10980,7 @@ var VNEXT_YAML_LIMITS = Object.freeze({
|
|
|
10980
10980
|
maxKeys: 8192
|
|
10981
10981
|
});
|
|
10982
10982
|
|
|
10983
|
-
// plugins/kxm/src/
|
|
10983
|
+
// plugins/kxm/src/template.ts
|
|
10984
10984
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
10985
10985
|
|
|
10986
10986
|
// plugins/kxm/src/repo-root.ts
|
|
@@ -11002,10 +11002,10 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
|
|
|
11002
11002
|
);
|
|
11003
11003
|
}
|
|
11004
11004
|
|
|
11005
|
-
// plugins/kxm/src/
|
|
11005
|
+
// plugins/kxm/src/oneshot-process.ts
|
|
11006
11006
|
var OUTPUT_LIMIT = 8 * 1024 * 1024;
|
|
11007
11007
|
|
|
11008
|
-
// plugins/kxm/src/
|
|
11008
|
+
// plugins/kxm/src/harness.ts
|
|
11009
11009
|
var NATIVE_HARNESS_PROVIDERS = Object.freeze({
|
|
11010
11010
|
claude: "anthropic",
|
|
11011
11011
|
codex: "openai",
|
|
@@ -11370,20 +11370,20 @@ var WIN_NPM_INNER_EXE = Object.freeze({
|
|
|
11370
11370
|
claude: Object.freeze(["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"])
|
|
11371
11371
|
});
|
|
11372
11372
|
|
|
11373
|
-
// plugins/kxm/src/
|
|
11374
|
-
var
|
|
11373
|
+
// plugins/kxm/src/project-config.ts
|
|
11374
|
+
var KxmConfigError = class extends Error {
|
|
11375
11375
|
issues;
|
|
11376
11376
|
constructor(issues) {
|
|
11377
11377
|
const sorted = sortIssues(issues);
|
|
11378
11378
|
super(sorted.map((issue) => `${issue.file}: ${issue.code}: ${issue.message}`).join("\n"));
|
|
11379
|
-
this.name = "
|
|
11379
|
+
this.name = "KxmConfigError";
|
|
11380
11380
|
this.issues = sorted;
|
|
11381
11381
|
}
|
|
11382
11382
|
};
|
|
11383
|
-
function
|
|
11384
|
-
return join4(findKxmRepoRoot(import.meta.url), "schemas"
|
|
11383
|
+
function defaultKxmSchemaDir() {
|
|
11384
|
+
return join4(findKxmRepoRoot(import.meta.url), "schemas");
|
|
11385
11385
|
}
|
|
11386
|
-
var DEFAULT_SCHEMA_DIR =
|
|
11386
|
+
var DEFAULT_SCHEMA_DIR = defaultKxmSchemaDir();
|
|
11387
11387
|
var RESOURCE_SCHEMA = Object.freeze({
|
|
11388
11388
|
project: { identity: "kxm.project.v1", file: "project.schema.json" },
|
|
11389
11389
|
repository: { identity: "kxm.repository.v1", file: "repository.schema.json" },
|
|
@@ -11403,7 +11403,7 @@ function sortIssues(issues) {
|
|
|
11403
11403
|
// plugins/kxm/src/database.ts
|
|
11404
11404
|
function databaseError(code, file, message) {
|
|
11405
11405
|
const issue = { phase: "semantic", code, file, message };
|
|
11406
|
-
return new
|
|
11406
|
+
return new KxmConfigError([issue]);
|
|
11407
11407
|
}
|
|
11408
11408
|
function checkedParent(path, description) {
|
|
11409
11409
|
const parent = dirname4(path);
|
|
@@ -13957,7 +13957,7 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
|
|
|
13957
13957
|
const current = requireAgent(request, agentId);
|
|
13958
13958
|
requireProjectAuth(request, current.project);
|
|
13959
13959
|
const presenceOnly = url.searchParams.get("presenceOnly") === "true";
|
|
13960
|
-
if (presenceOnly && current.model !== "
|
|
13960
|
+
if (presenceOnly && current.model !== "tui") {
|
|
13961
13961
|
throw new ProtocolError(403, "presence-only streams are reserved for metadata observers", "presence_stream_forbidden");
|
|
13962
13962
|
}
|
|
13963
13963
|
response.writeHead(200, {
|
package/plugins/kxm/package.json
CHANGED
|
@@ -12,8 +12,8 @@ invent `force`, domain-trust, or extra migrate verbs.
|
|
|
12
12
|
|
|
13
13
|
| Command | Purpose | Options / arguments |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `kxm init` | Create, validate, or plan migration of a
|
|
16
|
-
| `kxm migrate plan` | Compute the legacy-to-
|
|
15
|
+
| `kxm init` | Create, validate, or plan migration of a KXM project | `--json`, `--dry-run`, `--name`, `--project-id`, `--repository <id=absolute-path>` |
|
|
16
|
+
| `kxm migrate plan` | Compute the legacy-to-KXM plan without writes | `--json` |
|
|
17
17
|
| `kxm migrate apply` | Install a reviewed migration with a hash-linked receipt | `--decisions <file>`, `--project-id`, `--name` |
|
|
18
18
|
| `kxm migrate verify` | Verify a migration receipt | `--json` |
|
|
19
19
|
| `kxm trust diff` | Structured permission diff against a Git revision | `--base <revision>` |
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kxm-runs
|
|
3
|
-
description: Create and inspect local
|
|
3
|
+
description: Create and inspect local KXM runs while preserving current execution-status boundaries.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# KXM Runs
|
|
7
7
|
|
|
8
|
-
`kxm run` creates a
|
|
8
|
+
`kxm run` creates a KXM run (offline-first; steps do not execute until the
|
|
9
9
|
run engine lands). Inspect with `kxm runs`. Do not invent get/create/logs
|
|
10
10
|
verbs under `runs`.
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ verbs under `runs`.
|
|
|
13
13
|
|
|
14
14
|
| Command | Purpose | Options / arguments |
|
|
15
15
|
|---|---|---|
|
|
16
|
-
| `kxm run [workflow] [prompt...]` | Create a
|
|
16
|
+
| `kxm run [workflow] [prompt...]` | Create a KXM run (prompt is hashed, never stored raw) | `--json` |
|
|
17
17
|
| `kxm runs list` | List recent runs for the current project | `--json` |
|
|
18
18
|
| `kxm runs status <runId>` | Show projected run status | `--json` |
|
|
19
19
|
| `kxm runs receipt <runId>` | Print the newest drive receipt for a run | `--json`, `--all` |
|
|
@@ -15,7 +15,7 @@ MVP is a **local hub** on this machine. Work is agents and workflows. Do not con
|
|
|
15
15
|
| **Hub local (MVP)** | `kxm hub bind <url>` | Bind this host to a running loopback hub; session brief + status line |
|
|
16
16
|
| **SSH / HTTPS remote** | After MVP | Fail closed. Not available |
|
|
17
17
|
|
|
18
|
-
In-harness Pi can attach to a local hub. Local Runtime workflow insights (`kxm dash` from the
|
|
18
|
+
In-harness Pi can attach to a local hub. Local Runtime workflow insights (`kxm dash` from the KXM event store) are after MVP.
|
|
19
19
|
|
|
20
20
|
## First run (hub local)
|
|
21
21
|
|
|
@@ -17,7 +17,7 @@ in `--evidence-refs`. Caller-authored text never satisfies peer quorum.
|
|
|
17
17
|
| `kxm workflow checkpoint [runId] [stageId] [status] [summary]` | Record a stage result | `--run-id`, `--stage-id`, `--status passed\|warning\|failed`, `--summary`, `--evidence`, `--evidence-refs` |
|
|
18
18
|
| `kxm workflow record [runId] [category] [area] [summary]` | Journal plan/decision/contradiction/error/lesson | `--category`, `--area`, `--severity`, `--details`, `--evidence` |
|
|
19
19
|
| `kxm workflow wait [runId] [stageId] [signalKey] [summary]` | Wait for a signed callback | `--signal-key`, `--timeout-ms`, `--evidence`, `--evidence-refs` |
|
|
20
|
-
| `kxm workflow signal <runId> <signalKey> <status> <summary>` | Resume a wait or
|
|
20
|
+
| `kxm workflow signal <runId> <signalKey> <status> <summary>` | Resume a wait or KXM run | `[evidence...]`, `--delivery-id` |
|
|
21
21
|
| `kxm workflow start [definitionId]` | POST a signed workflow-start webhook | `--payload`, `--delivery-id`, `--event` |
|
|
22
22
|
| `kxm workflow export <runId>` | Export a proposed retrospective | `--input`, `--out-dir` |
|
|
23
23
|
| `kxm workflow definitions` | List definitions | `--scope all\|global\|local` |
|
|
@@ -131,15 +131,15 @@ _kxm() {
|
|
|
131
131
|
|
|
132
132
|
local -a commands
|
|
133
133
|
commands=(
|
|
134
|
-
'init:Create or validate a
|
|
134
|
+
'init:Create or validate a KXM project'
|
|
135
135
|
'migrate:Plan, apply, and verify legacy configuration migration'
|
|
136
136
|
'backup:Create a verified SQLite backup manifest'
|
|
137
137
|
'restore:Restore SQLite stores from a backup manifest'
|
|
138
|
-
'run:Create a
|
|
139
|
-
'runs:Inspect
|
|
138
|
+
'run:Create a KXM workflow run'
|
|
139
|
+
'runs:Inspect KXM runs'
|
|
140
140
|
'harness:Detect coding-agent harnesses and auth'
|
|
141
141
|
'update:Update kxm, harness CLIs, and model catalogs'
|
|
142
|
-
'runtime:Manage the
|
|
142
|
+
'runtime:Manage the KXM Runtime supervisor'
|
|
143
143
|
'trust:Permission-diff trust review'
|
|
144
144
|
'agent:Run and supervise agents'
|
|
145
145
|
'session:Create manifests and brief recent hub work'
|
|
@@ -18,57 +18,57 @@ import { homedir } from "node:os";
|
|
|
18
18
|
import { DatabaseSync } from "./sqlite.ts";
|
|
19
19
|
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
KxmConfigError,
|
|
22
|
+
KxmSchemaRegistry,
|
|
23
23
|
parseRestrictedYaml,
|
|
24
24
|
type JsonObject,
|
|
25
|
-
type
|
|
26
|
-
} from "./
|
|
25
|
+
type KxmConfigIssue,
|
|
26
|
+
} from "./project-config.ts";
|
|
27
27
|
|
|
28
28
|
const MAX_BINDING_RECORD_BYTES = 256 * 1024;
|
|
29
29
|
const BINDING_LABEL = "Runtime-local repository bindings";
|
|
30
30
|
|
|
31
|
-
export interface
|
|
31
|
+
export interface KxmLocalBindingRecord {
|
|
32
32
|
schema: "kxm.local-repository-bindings.v1";
|
|
33
33
|
projectId: string;
|
|
34
34
|
projectRoot: string;
|
|
35
35
|
repositories: Readonly<Record<string, string>>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export interface
|
|
38
|
+
export interface LoadedKxmLocalBindings extends KxmLocalBindingRecord {
|
|
39
39
|
file: string;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export interface
|
|
42
|
+
export interface KxmLocalBindingPathOptions {
|
|
43
43
|
stateRoot?: string;
|
|
44
44
|
env?: NodeJS.ProcessEnv;
|
|
45
45
|
homeDir?: string;
|
|
46
46
|
platform?: NodeJS.Platform;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export interface
|
|
49
|
+
export interface KxmLocalBindingStoreOptions extends KxmLocalBindingPathOptions {
|
|
50
50
|
schemasDir?: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export interface
|
|
53
|
+
export interface KxmLocalBindingWriteResult {
|
|
54
54
|
file: string;
|
|
55
55
|
written: boolean;
|
|
56
|
-
record:
|
|
56
|
+
record: KxmLocalBindingRecord;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export interface
|
|
59
|
+
export interface KxmLocalBindingLock {
|
|
60
60
|
readonly projectRoot: string;
|
|
61
61
|
readonly file: string;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const activeLocks = new WeakSet<
|
|
64
|
+
const activeLocks = new WeakSet<KxmLocalBindingLock>();
|
|
65
65
|
|
|
66
|
-
function bindingIssue(phase:
|
|
66
|
+
function bindingIssue(phase: KxmConfigIssue["phase"], code: string, message: string): KxmConfigIssue {
|
|
67
67
|
return { phase, code, file: BINDING_LABEL, message };
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function bindingError(phase:
|
|
71
|
-
throw new
|
|
70
|
+
function bindingError(phase: KxmConfigIssue["phase"], code: string, message: string): never {
|
|
71
|
+
throw new KxmConfigError([bindingIssue(phase, code, message)]);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export function canonicalHostPath(path: string): string {
|
|
@@ -89,7 +89,7 @@ export function sameHostPath(left: string, right: string, platform = process.pla
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/** Resolve the per-user state root without consulting any legacy KXM_* workspace override. */
|
|
92
|
-
export function
|
|
92
|
+
export function kxmUserStateRoot(options: KxmLocalBindingPathOptions = {}): string {
|
|
93
93
|
const env = options.env ?? process.env;
|
|
94
94
|
const platform = options.platform ?? process.platform;
|
|
95
95
|
const home = options.homeDir ?? homedir();
|
|
@@ -115,8 +115,8 @@ function projectBindingKey(projectRoot: string, platform = process.platform): st
|
|
|
115
115
|
return createHash("sha256").update(keyInput, "utf8").digest("hex");
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export function
|
|
119
|
-
const stateRoot =
|
|
118
|
+
export function kxmLocalBindingFile(projectRoot: string, options: KxmLocalBindingPathOptions = {}): string {
|
|
119
|
+
const stateRoot = kxmUserStateRoot(options);
|
|
120
120
|
return join(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -137,10 +137,10 @@ function existingBindingDirectory(file: string, stateRoot: string): boolean {
|
|
|
137
137
|
return checkedDirectory(project, "local project binding directory");
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function asRecord(value: JsonObject, file: string, projectRoot: string, schemasDir?: string):
|
|
141
|
-
const registry = new
|
|
140
|
+
function asRecord(value: JsonObject, file: string, projectRoot: string, schemasDir?: string): LoadedKxmLocalBindings {
|
|
141
|
+
const registry = new KxmSchemaRegistry(schemasDir);
|
|
142
142
|
const issues = registry.validateLocalBindings(value, BINDING_LABEL);
|
|
143
|
-
if (issues.length > 0) throw new
|
|
143
|
+
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
144
144
|
const recordRoot = String(value.projectRoot);
|
|
145
145
|
if (!sameHostPath(recordRoot, projectRoot)) {
|
|
146
146
|
bindingError("semantic", "local_binding_project_root_mismatch", "binding record belongs to a different control worktree");
|
|
@@ -157,13 +157,13 @@ function asRecord(value: JsonObject, file: string, projectRoot: string, schemasD
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/** Read one exact, bounded Runtime-local binding record. Missing state is not an error. */
|
|
160
|
-
export function
|
|
160
|
+
export function readKxmLocalBindings(
|
|
161
161
|
projectRoot: string,
|
|
162
|
-
options:
|
|
163
|
-
):
|
|
162
|
+
options: KxmLocalBindingStoreOptions = {},
|
|
163
|
+
): LoadedKxmLocalBindings | undefined {
|
|
164
164
|
const root = resolve(projectRoot);
|
|
165
|
-
const stateRoot =
|
|
166
|
-
const file =
|
|
165
|
+
const stateRoot = kxmUserStateRoot(options);
|
|
166
|
+
const file = kxmLocalBindingFile(root, options);
|
|
167
167
|
if (!existingBindingDirectory(file, stateRoot) || !existsSync(file)) return undefined;
|
|
168
168
|
const stat = lstatSync(file);
|
|
169
169
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -181,7 +181,7 @@ function normalizedRecord(
|
|
|
181
181
|
projectId: string,
|
|
182
182
|
repositories: Readonly<Record<string, string>>,
|
|
183
183
|
schemasDir?: string,
|
|
184
|
-
):
|
|
184
|
+
): KxmLocalBindingRecord {
|
|
185
185
|
const normalizedRepositories = Object.fromEntries(Object.entries(repositories)
|
|
186
186
|
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
187
187
|
.map(([repositoryId, path]) => {
|
|
@@ -194,12 +194,12 @@ function normalizedRecord(
|
|
|
194
194
|
projectRoot: canonicalHostPath(projectRoot),
|
|
195
195
|
repositories: normalizedRepositories,
|
|
196
196
|
};
|
|
197
|
-
const issues = new
|
|
198
|
-
if (issues.length > 0) throw new
|
|
199
|
-
return value as unknown as
|
|
197
|
+
const issues = new KxmSchemaRegistry(schemasDir).validateLocalBindings(value, BINDING_LABEL);
|
|
198
|
+
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
199
|
+
return value as unknown as KxmLocalBindingRecord;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
function recordsEqual(left:
|
|
202
|
+
function recordsEqual(left: KxmLocalBindingRecord, right: KxmLocalBindingRecord): boolean {
|
|
203
203
|
if (left.projectId !== right.projectId || !sameHostPath(left.projectRoot, right.projectRoot)) return false;
|
|
204
204
|
const leftEntries = Object.entries(left.repositories);
|
|
205
205
|
const rightEntries = Object.entries(right.repositories);
|
|
@@ -309,10 +309,10 @@ function projectOperationLockFile(projectRoot: string): string {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
/** Serialize one project mutation with a process-death-released SQLite write lock. */
|
|
312
|
-
export function
|
|
312
|
+
export function withKxmLocalBindingLock<T>(
|
|
313
313
|
projectRoot: string,
|
|
314
|
-
options:
|
|
315
|
-
callback: (lock:
|
|
314
|
+
options: KxmLocalBindingStoreOptions,
|
|
315
|
+
callback: (lock: KxmLocalBindingLock) => T,
|
|
316
316
|
): T {
|
|
317
317
|
const root = resolve(projectRoot);
|
|
318
318
|
const file = projectOperationLockFile(root);
|
|
@@ -333,7 +333,7 @@ export function withVnextLocalBindingLock<T>(
|
|
|
333
333
|
}
|
|
334
334
|
throw error;
|
|
335
335
|
}
|
|
336
|
-
const lock:
|
|
336
|
+
const lock: KxmLocalBindingLock = Object.freeze({ projectRoot: canonicalHostPath(root), file });
|
|
337
337
|
activeLocks.add(lock);
|
|
338
338
|
try {
|
|
339
339
|
return callback(lock);
|
|
@@ -346,16 +346,16 @@ export function withVnextLocalBindingLock<T>(
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
export function
|
|
349
|
+
export function planKxmLocalBindings(
|
|
350
350
|
projectRoot: string,
|
|
351
351
|
projectId: string,
|
|
352
352
|
repositories: Readonly<Record<string, string>>,
|
|
353
|
-
options:
|
|
354
|
-
):
|
|
353
|
+
options: KxmLocalBindingStoreOptions = {},
|
|
354
|
+
): KxmLocalBindingWriteResult {
|
|
355
355
|
const root = resolve(projectRoot);
|
|
356
|
-
const file =
|
|
356
|
+
const file = kxmLocalBindingFile(root, options);
|
|
357
357
|
const record = normalizedRecord(root, projectId, repositories, options.schemasDir);
|
|
358
|
-
const existing =
|
|
358
|
+
const existing = readKxmLocalBindings(root, options);
|
|
359
359
|
if (existing?.projectId !== undefined && existing.projectId !== projectId) {
|
|
360
360
|
bindingError("semantic", "local_binding_project_id_mismatch", "binding record belongs to a different project identity");
|
|
361
361
|
}
|
|
@@ -363,24 +363,24 @@ export function planVnextLocalBindings(
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
/** Atomically persist validated host paths outside the Git-tracked project. */
|
|
366
|
-
export function
|
|
366
|
+
export function writeKxmLocalBindings(
|
|
367
367
|
projectRoot: string,
|
|
368
368
|
projectId: string,
|
|
369
369
|
repositories: Readonly<Record<string, string>>,
|
|
370
|
-
options:
|
|
371
|
-
lock?:
|
|
372
|
-
):
|
|
370
|
+
options: KxmLocalBindingStoreOptions = {},
|
|
371
|
+
lock?: KxmLocalBindingLock,
|
|
372
|
+
): KxmLocalBindingWriteResult {
|
|
373
373
|
const root = resolve(projectRoot);
|
|
374
374
|
if (!lock) {
|
|
375
|
-
return
|
|
375
|
+
return withKxmLocalBindingLock(root, options, (acquired) => writeKxmLocalBindings(root, projectId, repositories, options, acquired));
|
|
376
376
|
}
|
|
377
377
|
if (!activeLocks.has(lock) || !sameHostPath(lock.projectRoot, root)) {
|
|
378
378
|
bindingError("semantic", "local_binding_lock_invalid", "binding update does not hold the active project lock");
|
|
379
379
|
}
|
|
380
|
-
const planned =
|
|
380
|
+
const planned = planKxmLocalBindings(root, projectId, repositories, options);
|
|
381
381
|
const { file, record } = planned;
|
|
382
382
|
if (!planned.written) return planned;
|
|
383
|
-
ensureBindingDirectory(file,
|
|
383
|
+
ensureBindingDirectory(file, kxmUserStateRoot(options));
|
|
384
384
|
const temporary = join(dirname(file), `.repository-bindings-${process.pid}-${randomUUID()}.tmp`);
|
|
385
385
|
let descriptor: number | undefined;
|
|
386
386
|
try {
|
|
@@ -395,7 +395,7 @@ export function writeVnextLocalBindings(
|
|
|
395
395
|
if (descriptor !== undefined) closeSync(descriptor);
|
|
396
396
|
rmSync(temporary, { force: true });
|
|
397
397
|
}
|
|
398
|
-
const verified =
|
|
398
|
+
const verified = readKxmLocalBindings(root, options);
|
|
399
399
|
if (!verified || !recordsEqual(verified, record)) {
|
|
400
400
|
bindingError("semantic", "local_binding_install_verification_failed", "installed binding record does not match the validated input");
|
|
401
401
|
}
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
classifyInstallRoot,
|
|
23
23
|
type InstallProbe,
|
|
24
24
|
} from "../kxm-install-kind.ts";
|
|
25
|
-
import {
|
|
25
|
+
import { kxmUserStateRoot } from "../bindings.ts";
|
|
26
26
|
import {
|
|
27
27
|
fetchLatestKxmVersion,
|
|
28
28
|
kxmReleaseAssetName,
|
|
@@ -88,7 +88,7 @@ import { homedir } from "node:os";
|
|
|
88
88
|
export function warnIgnoredProjectUpdateYaml(runtime: Runtime): void {
|
|
89
89
|
const projectFile = join(runtime.dirs.workspace, "update.yaml");
|
|
90
90
|
if (!existsSync(projectFile)) return;
|
|
91
|
-
const userFile = join(
|
|
91
|
+
const userFile = join(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
92
92
|
runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}\n`);
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -162,7 +162,7 @@ export async function cmdHub(runtime: Runtime): Promise<number> {
|
|
|
162
162
|
config = loadKxmUpdateConfig(runtime.env);
|
|
163
163
|
} catch (error) {
|
|
164
164
|
if (error instanceof KxmUpdateConfigError) {
|
|
165
|
-
const yamlPath = join(
|
|
165
|
+
const yamlPath = join(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
166
166
|
runtime.io.stderr(`kxm: ${error.message}; update check skipped; fix or remove ${yamlPath}\n`);
|
|
167
167
|
} else {
|
|
168
168
|
throw error;
|