@kontextmind/kxm 0.7.85 → 0.7.87
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/docs/configuration.md +2 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +898 -498
- package/plugins/kxm/dist/extension.js +70 -61
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +32 -10
- package/plugins/kxm/dist/runtime.js +76 -36
- package/plugins/kxm/dist/server.js +34 -23
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/context-skills.ts +51 -6
- package/plugins/kxm/src/cli/hub.ts +39 -14
- package/plugins/kxm/src/cli/project.ts +84 -23
- package/plugins/kxm/src/cli/roles.ts +48 -4
- package/plugins/kxm/src/cli/system.ts +18 -1
- package/plugins/kxm/src/cli/tasks.ts +34 -5
- package/plugins/kxm/src/cli/types.ts +30 -0
- package/plugins/kxm/src/cli/workflows.ts +20 -4
- package/plugins/kxm/src/cli.ts +57 -0
- package/plugins/kxm/src/config.ts +4 -3
- package/plugins/kxm/src/database.ts +72 -33
- package/plugins/kxm/src/local-snapshot.ts +4 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/memory.ts +10 -7
- package/plugins/kxm/src/role.ts +18 -6
- package/plugins/kxm/src/runtime-supervisor.ts +52 -14
- package/plugins/kxm/src/session-work.ts +4 -1
- package/plugins/kxm/src/skills.ts +29 -15
- package/plugins/kxm/src/sqlite.ts +18 -0
- package/plugins/kxm/src/task-manager.ts +20 -13
- package/plugins/kxm/src/workflow-manager.ts +12 -4
|
@@ -18372,7 +18372,7 @@ function discoverProjectStores(projectRoot, options = {}) {
|
|
|
18372
18372
|
}
|
|
18373
18373
|
return stores;
|
|
18374
18374
|
}
|
|
18375
|
-
function
|
|
18375
|
+
function planBackup(options = {}) {
|
|
18376
18376
|
const projectRoot = options.projectRoot ? resolve3(options.projectRoot) : process.cwd();
|
|
18377
18377
|
const stores = discoverProjectStores(projectRoot, {
|
|
18378
18378
|
...options.hubDataPath !== void 0 ? { hubDataPath: options.hubDataPath } : {}
|
|
@@ -18380,30 +18380,35 @@ function createBackup(options = {}) {
|
|
|
18380
18380
|
if (stores.length === 0) {
|
|
18381
18381
|
throw databaseError("backup_no_stores", projectRoot, "no existing SQLite stores found to backup");
|
|
18382
18382
|
}
|
|
18383
|
-
const
|
|
18384
|
-
const timestamp =
|
|
18385
|
-
const backupId = `bk_${randomBytes(8).toString("hex")}`;
|
|
18383
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18384
|
+
const timestamp = createdAt.replace(/[:.]/g, "-");
|
|
18386
18385
|
const outDir = options.outDir ? resolve3(options.outDir) : join4(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
|
|
18387
|
-
if (!existsSync4(outDir)) {
|
|
18388
|
-
mkdirSync(outDir, { recursive: true, mode: 448 });
|
|
18389
|
-
}
|
|
18390
|
-
const backedUpStores = [];
|
|
18391
18386
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
18392
|
-
|
|
18387
|
+
const planned = stores.map((store) => {
|
|
18393
18388
|
let filename = basename2(store.sourcePath);
|
|
18394
18389
|
if (usedFilenames.has(filename)) {
|
|
18395
18390
|
const sanitizedId = store.storeId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
18396
18391
|
filename = `${sanitizedId}-${filename}`;
|
|
18397
18392
|
}
|
|
18398
18393
|
usedFilenames.add(filename);
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18394
|
+
return { storeId: store.storeId, sourcePath: store.sourcePath, backupFile: filename };
|
|
18395
|
+
});
|
|
18396
|
+
return { projectRoot, outDir, createdAt, stores: planned };
|
|
18397
|
+
}
|
|
18398
|
+
function createBackup(options = {}) {
|
|
18399
|
+
const { projectRoot, outDir, createdAt, stores } = planBackup(options);
|
|
18400
|
+
const backupId = `bk_${randomBytes(8).toString("hex")}`;
|
|
18401
|
+
if (!existsSync4(outDir)) {
|
|
18402
|
+
mkdirSync(outDir, { recursive: true, mode: 448 });
|
|
18403
|
+
}
|
|
18404
|
+
const backedUpStores = [];
|
|
18405
|
+
for (const store of stores) {
|
|
18406
|
+
backedUpStores.push(backupDatabaseFile(store.sourcePath, join4(outDir, store.backupFile), store.storeId));
|
|
18402
18407
|
}
|
|
18403
18408
|
const manifest = {
|
|
18404
18409
|
schema: "kxm.backup-manifest.v1",
|
|
18405
18410
|
backupId,
|
|
18406
|
-
createdAt
|
|
18411
|
+
createdAt,
|
|
18407
18412
|
projectRoot,
|
|
18408
18413
|
stores: backedUpStores
|
|
18409
18414
|
};
|
|
@@ -18415,7 +18420,7 @@ function createBackup(options = {}) {
|
|
|
18415
18420
|
writeFileSync(manifestPath, finalJson, "utf8");
|
|
18416
18421
|
return { manifest, outDir };
|
|
18417
18422
|
}
|
|
18418
|
-
function
|
|
18423
|
+
function planRestore(manifestPathOrDir, options = {}) {
|
|
18419
18424
|
let manifestPath = resolve3(manifestPathOrDir);
|
|
18420
18425
|
const stat = lstatSync2(manifestPath, { throwIfNoEntry: false });
|
|
18421
18426
|
if (!stat) {
|
|
@@ -18438,7 +18443,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18438
18443
|
if (manifest.schema !== "kxm.backup-manifest.v1" || !Array.isArray(manifest.stores) || manifest.stores.length === 0) {
|
|
18439
18444
|
throw databaseError("restore_manifest_invalid", manifestPath, "manifest is not a valid kxm.backup-manifest.v1 document");
|
|
18440
18445
|
}
|
|
18441
|
-
const
|
|
18446
|
+
const stores = [];
|
|
18442
18447
|
for (const store of manifest.stores) {
|
|
18443
18448
|
const backupFilePath = join4(manifestDir, store.backupFile);
|
|
18444
18449
|
if (!existsSync4(backupFilePath)) {
|
|
@@ -18452,24 +18457,35 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18452
18457
|
`backup file ${store.backupFile} sha256 ${actualSha256} does not match manifest hash ${store.sha256}`
|
|
18453
18458
|
);
|
|
18454
18459
|
}
|
|
18455
|
-
const
|
|
18460
|
+
const maxSupportedVersion = kxmBackupCeiling(store.storeId);
|
|
18461
|
+
if (store.schemaVersion > maxSupportedVersion) {
|
|
18462
|
+
throw databaseError(
|
|
18463
|
+
"runtime_schema_newer",
|
|
18464
|
+
backupFilePath,
|
|
18465
|
+
`backup store ${store.storeId} schema version ${store.schemaVersion} is newer than supported maximum ${maxSupportedVersion}`
|
|
18466
|
+
);
|
|
18467
|
+
}
|
|
18456
18468
|
let targetPath = store.sourcePath;
|
|
18457
18469
|
if (options.projectRoot && manifest.projectRoot && targetPath.startsWith(manifest.projectRoot)) {
|
|
18458
18470
|
const rel = targetPath.slice(manifest.projectRoot.length).replace(/^[\\/]+/, "");
|
|
18459
18471
|
targetPath = join4(resolve3(options.projectRoot), rel);
|
|
18460
18472
|
}
|
|
18461
|
-
|
|
18462
|
-
backupFilePath,
|
|
18463
|
-
targetPath,
|
|
18464
|
-
store.storeId,
|
|
18465
|
-
store.schemaVersion,
|
|
18466
|
-
maxSupported
|
|
18467
|
-
);
|
|
18468
|
-
restoredStores.push(result);
|
|
18473
|
+
stores.push({ storeId: store.storeId, backupFilePath, targetPath, schemaVersion: store.schemaVersion, maxSupportedVersion });
|
|
18469
18474
|
}
|
|
18475
|
+
return { manifestPath, backupId: manifest.backupId, stores };
|
|
18476
|
+
}
|
|
18477
|
+
function restoreBackup(manifestPathOrDir, options = {}) {
|
|
18478
|
+
const plan = planRestore(manifestPathOrDir, options);
|
|
18479
|
+
const restoredStores = plan.stores.map((store) => restoreDatabaseFile(
|
|
18480
|
+
store.backupFilePath,
|
|
18481
|
+
store.targetPath,
|
|
18482
|
+
store.storeId,
|
|
18483
|
+
store.schemaVersion,
|
|
18484
|
+
store.maxSupportedVersion
|
|
18485
|
+
));
|
|
18470
18486
|
return {
|
|
18471
|
-
manifestPath,
|
|
18472
|
-
backupId:
|
|
18487
|
+
manifestPath: plan.manifestPath,
|
|
18488
|
+
backupId: plan.backupId,
|
|
18473
18489
|
restoredStores
|
|
18474
18490
|
};
|
|
18475
18491
|
}
|
|
@@ -29003,17 +29019,38 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
29003
29019
|
}
|
|
29004
29020
|
}, 1e3);
|
|
29005
29021
|
heartbeat.unref();
|
|
29006
|
-
|
|
29007
|
-
|
|
29008
|
-
|
|
29009
|
-
|
|
29010
|
-
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
29014
|
-
|
|
29022
|
+
const reopenRegisteredProjects = () => {
|
|
29023
|
+
for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
|
|
29024
|
+
const key = projectRuntimeKey(reg.projectRoot);
|
|
29025
|
+
if (contexts.has(key)) continue;
|
|
29026
|
+
try {
|
|
29027
|
+
contextFor(reg.projectRoot);
|
|
29028
|
+
syncStatuses.delete(key);
|
|
29029
|
+
} catch (error) {
|
|
29030
|
+
const reason = syncFailureText(error);
|
|
29031
|
+
const prior = syncStatuses.get(key);
|
|
29032
|
+
if (prior === void 0 || prior.lastError !== reason) {
|
|
29033
|
+
logger.warn({
|
|
29034
|
+
event: "runtime_sync_context_unavailable",
|
|
29035
|
+
projectId: reg.projectId,
|
|
29036
|
+
message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`,
|
|
29037
|
+
reason
|
|
29038
|
+
});
|
|
29039
|
+
}
|
|
29040
|
+
syncStatuses.set(key, {
|
|
29041
|
+
projectId: reg.projectId,
|
|
29042
|
+
projectRoot: reg.projectRoot,
|
|
29043
|
+
homeRuntimeId: activeRuntimeId,
|
|
29044
|
+
state: "blocked",
|
|
29045
|
+
outbox: { pending: 0, acked: 0, refused: 0, refusals: [] },
|
|
29046
|
+
storeReadable: false,
|
|
29047
|
+
consecutiveFailures: 1,
|
|
29048
|
+
lastError: reason
|
|
29049
|
+
});
|
|
29050
|
+
}
|
|
29015
29051
|
}
|
|
29016
|
-
}
|
|
29052
|
+
};
|
|
29053
|
+
reopenRegisteredProjects();
|
|
29017
29054
|
const recordSyncStatus = (context, next) => {
|
|
29018
29055
|
const key = projectRuntimeKey(context.projectRoot);
|
|
29019
29056
|
const previous = syncStatuses.get(key);
|
|
@@ -29042,6 +29079,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
29042
29079
|
if (syncing || stopping) return;
|
|
29043
29080
|
syncing = true;
|
|
29044
29081
|
void (async () => {
|
|
29082
|
+
reopenRegisteredProjects();
|
|
29045
29083
|
for (const context of [...contexts.values()]) {
|
|
29046
29084
|
const key = projectRuntimeKey(context.projectRoot);
|
|
29047
29085
|
const prior = syncStatuses.get(key);
|
|
@@ -31414,7 +31452,9 @@ export {
|
|
|
31414
31452
|
parsePiOneShotUsage,
|
|
31415
31453
|
parseSshConfig,
|
|
31416
31454
|
persistKxmRunState,
|
|
31455
|
+
planBackup,
|
|
31417
31456
|
planHarnessUpdate,
|
|
31457
|
+
planRestore,
|
|
31418
31458
|
probeHarnessAssignment,
|
|
31419
31459
|
probeHarnessAssignmentAsync,
|
|
31420
31460
|
probeHarnesses,
|
|
@@ -14783,7 +14783,7 @@ import { createHash as createHash5, createHmac } from "node:crypto";
|
|
|
14783
14783
|
import { existsSync as existsSync6 } from "node:fs";
|
|
14784
14784
|
import { createServer } from "node:http";
|
|
14785
14785
|
import { isIP } from "node:net";
|
|
14786
|
-
import { dirname as
|
|
14786
|
+
import { dirname as dirname6, join as join6, resolve as resolve6 } from "node:path";
|
|
14787
14787
|
|
|
14788
14788
|
// plugins/kxm/src/protocol.ts
|
|
14789
14789
|
import { randomUUID } from "node:crypto";
|
|
@@ -18213,7 +18213,7 @@ function boundedRefs(value, field) {
|
|
|
18213
18213
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
18214
18214
|
import { createHash as createHash4 } from "node:crypto";
|
|
18215
18215
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, rmSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18216
|
-
import { join as join4 } from "node:path";
|
|
18216
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
18217
18217
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
18218
18218
|
var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
|
|
18219
18219
|
var SKILL_DECISION_SCHEMA = "kxm.skill-decision.v1";
|
|
@@ -18301,10 +18301,22 @@ var SkillLifecycle = class {
|
|
|
18301
18301
|
root;
|
|
18302
18302
|
now;
|
|
18303
18303
|
allowOptimizationEvals;
|
|
18304
|
+
dryRun;
|
|
18305
|
+
/** What a `dryRun` lifecycle would have written or moved, in order. */
|
|
18306
|
+
planned = [];
|
|
18304
18307
|
constructor(root, options = {}) {
|
|
18305
18308
|
this.root = root;
|
|
18306
18309
|
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
18307
18310
|
this.allowOptimizationEvals = options.allowOptimizationEvals === true;
|
|
18311
|
+
this.dryRun = options.dryRun === true;
|
|
18312
|
+
}
|
|
18313
|
+
write(file, content) {
|
|
18314
|
+
if (this.dryRun) {
|
|
18315
|
+
this.planned.push({ action: "write", target: file });
|
|
18316
|
+
return;
|
|
18317
|
+
}
|
|
18318
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
18319
|
+
writeFileSync2(file, content);
|
|
18308
18320
|
}
|
|
18309
18321
|
dir(state) {
|
|
18310
18322
|
return join4(this.root, state === "candidate" ? "candidates" : `${state}s`.replace("rejecteds", "rejected").replace("promoteds", "promoted"));
|
|
@@ -18317,15 +18329,14 @@ var SkillLifecycle = class {
|
|
|
18317
18329
|
return { dir, metadata: join4(dir, "metadata.json"), skill: join4(dir, "SKILL.md") };
|
|
18318
18330
|
}
|
|
18319
18331
|
appendHistory(id, record) {
|
|
18320
|
-
mkdirSync2(join4(this.root, "history"), { recursive: true });
|
|
18321
18332
|
const line = `${JSON.stringify(record)}
|
|
18322
18333
|
`;
|
|
18323
18334
|
if (existsSync4(this.historyFile(id))) {
|
|
18324
18335
|
const existing = readFileSync3(this.historyFile(id), "utf8");
|
|
18325
18336
|
const lines = existing.split("\n").filter((entry) => entry.trim());
|
|
18326
|
-
|
|
18337
|
+
this.write(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
|
|
18327
18338
|
} else {
|
|
18328
|
-
|
|
18339
|
+
this.write(this.historyFile(id), line);
|
|
18329
18340
|
}
|
|
18330
18341
|
}
|
|
18331
18342
|
history(id) {
|
|
@@ -18346,6 +18357,10 @@ var SkillLifecycle = class {
|
|
|
18346
18357
|
if (!existsSync4(fromDir)) {
|
|
18347
18358
|
throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${from}`);
|
|
18348
18359
|
}
|
|
18360
|
+
if (this.dryRun) {
|
|
18361
|
+
this.planned.push({ action: "move", target: `${fromDir} -> ${toDir}` });
|
|
18362
|
+
return;
|
|
18363
|
+
}
|
|
18349
18364
|
mkdirSync2(this.dir(to), { recursive: true });
|
|
18350
18365
|
if (existsSync4(toDir)) rmSync(toDir, { recursive: true, force: true });
|
|
18351
18366
|
renameSync(fromDir, toDir);
|
|
@@ -18389,7 +18404,7 @@ var SkillLifecycle = class {
|
|
|
18389
18404
|
if (!harness) throw new SkillLifecycleError("invalid_skill_compatibility", "compatibility.harness is required");
|
|
18390
18405
|
const contentSha256 = skillContentSha256(content);
|
|
18391
18406
|
const id = skillIdFor(name, contentSha256);
|
|
18392
|
-
const {
|
|
18407
|
+
const { metadata, skill } = this.paths("candidate", id);
|
|
18393
18408
|
if (existsSync4(metadata)) {
|
|
18394
18409
|
throw new SkillLifecycleError(
|
|
18395
18410
|
"skill_candidate_exists",
|
|
@@ -18409,9 +18424,8 @@ var SkillLifecycle = class {
|
|
|
18409
18424
|
createdAt: this.now(),
|
|
18410
18425
|
...input.supersedes ? { supersedes: input.supersedes } : {}
|
|
18411
18426
|
};
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
writeFileSync2(metadata, `${JSON.stringify(record, null, 2)}
|
|
18427
|
+
this.write(skill, content);
|
|
18428
|
+
this.write(metadata, `${JSON.stringify(record, null, 2)}
|
|
18415
18429
|
`);
|
|
18416
18430
|
this.appendHistory(id, { schema: "kxm.skill-history-event.v1", event: "candidate_created", by: createdBy, supersedes: input.supersedes, at: record.createdAt });
|
|
18417
18431
|
return record;
|
|
@@ -18500,18 +18514,15 @@ var SkillLifecycle = class {
|
|
|
18500
18514
|
};
|
|
18501
18515
|
const candidatePaths = this.paths("candidate", candidateId);
|
|
18502
18516
|
const promotedPaths = this.paths("promoted", candidateId);
|
|
18503
|
-
mkdirSync2(promotedPaths.dir, { recursive: true });
|
|
18504
18517
|
const skillContent = readFileSync3(candidatePaths.skill, "utf8");
|
|
18505
18518
|
const metadataContent = readFileSync3(candidatePaths.metadata, "utf8");
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
const
|
|
18509
|
-
mkdirSync2(patchesDir, { recursive: true });
|
|
18510
|
-
const patchPath = join4(patchesDir, `${candidateId}.patch`);
|
|
18519
|
+
this.write(promotedPaths.skill, skillContent);
|
|
18520
|
+
this.write(promotedPaths.metadata, metadataContent);
|
|
18521
|
+
const patchPath = join4(this.root, "patches", `${candidateId}.patch`);
|
|
18511
18522
|
const relSkillPath = `.kxm/skills/promoted/${candidateId}/SKILL.md`;
|
|
18512
18523
|
const relMetaPath = `.kxm/skills/promoted/${candidateId}/metadata.json`;
|
|
18513
18524
|
const patch = `${createUnifiedPatch(relSkillPath, skillContent)}${createUnifiedPatch(relMetaPath, metadataContent)}`;
|
|
18514
|
-
|
|
18525
|
+
this.write(patchPath, patch);
|
|
18515
18526
|
this.appendHistory(candidateId, record);
|
|
18516
18527
|
return { ...metadata, patch, patchPath };
|
|
18517
18528
|
}
|
|
@@ -19031,7 +19042,7 @@ import {
|
|
|
19031
19042
|
unlinkSync,
|
|
19032
19043
|
writeFileSync as writeFileSync4
|
|
19033
19044
|
} from "node:fs";
|
|
19034
|
-
import { basename as basename3, dirname as
|
|
19045
|
+
import { basename as basename3, dirname as dirname5, join as join5, resolve as resolve4 } from "node:path";
|
|
19035
19046
|
|
|
19036
19047
|
// plugins/kxm/src/sqlite.ts
|
|
19037
19048
|
import { createRequire } from "node:module";
|
|
@@ -19078,7 +19089,7 @@ function databaseError(code, file, message) {
|
|
|
19078
19089
|
return new KxmConfigError([issue2]);
|
|
19079
19090
|
}
|
|
19080
19091
|
function checkedParent(path, description) {
|
|
19081
|
-
const parent =
|
|
19092
|
+
const parent = dirname5(path);
|
|
19082
19093
|
if (!existsSync5(parent)) mkdirSync4(parent, { recursive: true, mode: 448 });
|
|
19083
19094
|
const stat = lstatSync2(parent, { throwIfNoEntry: false });
|
|
19084
19095
|
if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -20359,7 +20370,7 @@ function createMeshHub(options = {}) {
|
|
|
20359
20370
|
const logger = options.logger ?? (() => void 0);
|
|
20360
20371
|
const hubNow = options.now ?? (() => Date.now());
|
|
20361
20372
|
const assetsDir2 = options.assetsDir;
|
|
20362
|
-
const hubRepoRoot = options.repoRoot ?? (options.dataPath && options.dataPath !== ":memory:" ? resolve6(
|
|
20373
|
+
const hubRepoRoot = options.repoRoot ?? (options.dataPath && options.dataPath !== ":memory:" ? resolve6(dirname6(dirname6(options.dataPath))) : process.cwd());
|
|
20363
20374
|
const store = new MeshStore(options.dataPath);
|
|
20364
20375
|
const agents = store.agents;
|
|
20365
20376
|
const messages = store.messages;
|
|
@@ -22556,11 +22567,11 @@ data: ${JSON.stringify({ agent: publicAgent(current, staleAfterMs) })}
|
|
|
22556
22567
|
|
|
22557
22568
|
// plugins/kxm/src/server.ts
|
|
22558
22569
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
22559
|
-
import { dirname as
|
|
22570
|
+
import { dirname as dirname8, join as join7, resolve as resolve7 } from "node:path";
|
|
22560
22571
|
|
|
22561
22572
|
// plugins/kxm/src/logger.ts
|
|
22562
22573
|
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, renameSync as renameSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
22563
|
-
import { dirname as
|
|
22574
|
+
import { dirname as dirname7 } from "node:path";
|
|
22564
22575
|
var LOG_LEVEL_PRIORITY = {
|
|
22565
22576
|
debug: 10,
|
|
22566
22577
|
info: 20,
|
|
@@ -22666,7 +22677,7 @@ function createLogger(options) {
|
|
|
22666
22677
|
currentSize = 0;
|
|
22667
22678
|
}
|
|
22668
22679
|
try {
|
|
22669
|
-
mkdirSync5(
|
|
22680
|
+
mkdirSync5(dirname7(filePath), { recursive: true });
|
|
22670
22681
|
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
22671
22682
|
currentSize += lineBytes;
|
|
22672
22683
|
} catch {
|
|
@@ -22729,7 +22740,7 @@ var rateLimitWindowMs = Number.parseInt(
|
|
|
22729
22740
|
process.env.KXM_RATE_LIMIT_WINDOW_MS ?? String(DEFAULT_RATE_LIMIT_WINDOW_MS),
|
|
22730
22741
|
10
|
|
22731
22742
|
);
|
|
22732
|
-
for (const directory of [configDir, logsDir, assetsDir, stateDir,
|
|
22743
|
+
for (const directory of [configDir, logsDir, assetsDir, stateDir, dirname8(logPath)]) {
|
|
22733
22744
|
mkdirSync6(directory, { recursive: true });
|
|
22734
22745
|
}
|
|
22735
22746
|
var structuredLog = createLogger({
|
package/plugins/kxm/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join, relative } from "node:path";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { redactSecrets } from "../redact.ts";
|
|
4
4
|
import {
|
|
5
5
|
createMemoryNote,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../memory.ts";
|
|
11
11
|
import { SkillLifecycle, type SkillEvaluationKind, type SkillState } from "../skills.ts";
|
|
12
12
|
import { writeCompiledWiki } from "../wiki.ts";
|
|
13
|
-
import { print, type Runtime } from "./types.ts";
|
|
13
|
+
import { print, printPlan, type Runtime } from "./types.ts";
|
|
14
14
|
|
|
15
15
|
/** Authenticated hub POST for context operations. The CLI operates as the
|
|
16
16
|
* control plane: the administrative token scopes one project per request. */
|
|
@@ -122,6 +122,15 @@ export async function cmdContextPromote(runtime: Runtime, project: string, propo
|
|
|
122
122
|
runtime.io.stderr("context promote --evidence must contain at least one durable evidence reference\n");
|
|
123
123
|
return 2;
|
|
124
124
|
}
|
|
125
|
+
if (runtime.dryRun) {
|
|
126
|
+
printPlan(
|
|
127
|
+
runtime,
|
|
128
|
+
{ command: "context promote", project, proposalId, evidence },
|
|
129
|
+
[{ action: "request", target: `POST ${runtime.serverUrl.replace(/\/$/, "")}/v1/context/state/promote` }],
|
|
130
|
+
`promote proposal ${proposalId} in ${project}`,
|
|
131
|
+
);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
125
134
|
const response = await hubContextPost({
|
|
126
135
|
serverUrl: runtime.serverUrl,
|
|
127
136
|
path: "/v1/context/state/promote",
|
|
@@ -158,6 +167,16 @@ export async function cmdContextWikiCompile(runtime: Runtime, project: string, o
|
|
|
158
167
|
return 1;
|
|
159
168
|
}
|
|
160
169
|
const compiled = response.body as { audit: { pages: string[]; contradictions: number }; pages: { path: string; content: string }[] };
|
|
170
|
+
if (options.out && runtime.dryRun) {
|
|
171
|
+
const outDir = options.out;
|
|
172
|
+
printPlan(
|
|
173
|
+
runtime,
|
|
174
|
+
{ command: "context wiki-compile", project, pages: compiled.audit.pages, openContradictions: compiled.audit.contradictions, outDir },
|
|
175
|
+
compiled.pages.map((page) => ({ action: "write", target: resolve(outDir, page.path) })),
|
|
176
|
+
`write ${compiled.pages.length} compiled wiki page(s) to ${outDir}`,
|
|
177
|
+
);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
161
180
|
let written: string[] = [];
|
|
162
181
|
if (options.out) {
|
|
163
182
|
const pages = new Map(compiled.pages.map((page) => [page.path, page.content]));
|
|
@@ -233,7 +252,7 @@ export async function cmdSkillsCreate(runtime: Runtime, options: { file: string;
|
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
export async function cmdSkillsEvaluate(runtime: Runtime, skillId: string, options: { kind: string; evaluator: string; fail?: boolean | undefined; score?: string | undefined; details?: string | undefined }): Promise<number> {
|
|
236
|
-
const lifecycle = new SkillLifecycle(skillsRoot(runtime));
|
|
255
|
+
const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
|
|
237
256
|
try {
|
|
238
257
|
const outcome = lifecycle.evaluate(skillId, {
|
|
239
258
|
kind: options.kind as SkillEvaluationKind,
|
|
@@ -242,6 +261,10 @@ export async function cmdSkillsEvaluate(runtime: Runtime, skillId: string, optio
|
|
|
242
261
|
...(options.score !== undefined ? { score: Number(options.score) } : {}),
|
|
243
262
|
...(options.details ? { details: options.details } : {}),
|
|
244
263
|
});
|
|
264
|
+
if (runtime.dryRun) {
|
|
265
|
+
printPlan(runtime, { command: "skills evaluate", skillId, quarantined: outcome.quarantined, evaluation: outcome.evaluation }, lifecycle.planned, `record ${options.kind} evaluation${outcome.quarantined ? " (candidate quarantined)" : ""}`);
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
245
268
|
print(runtime.io, runtime.json, { ok: true, command: "skills evaluate", skillId, quarantined: outcome.quarantined, evaluation: outcome.evaluation }, `recorded ${options.kind} evaluation${outcome.quarantined ? " (candidate quarantined)" : ""}`);
|
|
246
269
|
return 0;
|
|
247
270
|
} catch (error) {
|
|
@@ -252,13 +275,17 @@ export async function cmdSkillsEvaluate(runtime: Runtime, skillId: string, optio
|
|
|
252
275
|
}
|
|
253
276
|
|
|
254
277
|
export async function cmdSkillsPromote(runtime: Runtime, skillId: string, options: { decidedBy: string; evidence: string; reason?: string | undefined }): Promise<number> {
|
|
255
|
-
const lifecycle = new SkillLifecycle(skillsRoot(runtime));
|
|
278
|
+
const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
|
|
256
279
|
try {
|
|
257
280
|
const promoted = lifecycle.promote(skillId, {
|
|
258
281
|
decidedBy: options.decidedBy,
|
|
259
282
|
reason: options.reason ?? "passed protected evaluation",
|
|
260
283
|
evidenceRefs: csv(options.evidence) ?? [],
|
|
261
284
|
});
|
|
285
|
+
if (runtime.dryRun) {
|
|
286
|
+
printPlan(runtime, { command: "skills promote", skillId, metadata: promoted, patchPath: promoted.patchPath }, lifecycle.planned, `promote skill ${skillId}`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
262
289
|
print(runtime.io, runtime.json, { ok: true, command: "skills promote", skillId, metadata: promoted, patchPath: promoted.patchPath }, `promoted skill ${skillId} (patch: ${promoted.patchPath})`);
|
|
263
290
|
return 0;
|
|
264
291
|
} catch (error) {
|
|
@@ -269,9 +296,13 @@ export async function cmdSkillsPromote(runtime: Runtime, skillId: string, option
|
|
|
269
296
|
}
|
|
270
297
|
|
|
271
298
|
export async function cmdSkillsReject(runtime: Runtime, skillId: string, options: { decidedBy: string; reason?: string | undefined }): Promise<number> {
|
|
272
|
-
const lifecycle = new SkillLifecycle(skillsRoot(runtime));
|
|
299
|
+
const lifecycle = new SkillLifecycle(skillsRoot(runtime), { dryRun: runtime.dryRun });
|
|
273
300
|
try {
|
|
274
301
|
const metadata = lifecycle.reject(skillId, { decidedBy: options.decidedBy, reason: options.reason ?? "rejected" });
|
|
302
|
+
if (runtime.dryRun) {
|
|
303
|
+
printPlan(runtime, { command: "skills reject", skillId, metadata }, lifecycle.planned, `reject skill ${skillId} (history retained)`);
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
275
306
|
print(runtime.io, runtime.json, { ok: true, command: "skills reject", skillId, metadata }, `rejected skill ${skillId} (history retained)`);
|
|
276
307
|
return 0;
|
|
277
308
|
} catch (error) {
|
|
@@ -339,8 +370,13 @@ export async function cmdMemoryNote(
|
|
|
339
370
|
scope: (options.scope ?? "project") as MemoryScope,
|
|
340
371
|
...(options.kind !== undefined ? { kind: options.kind } : {}),
|
|
341
372
|
...(options.body !== undefined ? { body: options.body } : {}),
|
|
373
|
+
dryRun: runtime.dryRun,
|
|
342
374
|
});
|
|
343
375
|
const relPath = relative(runtime.cwd, path);
|
|
376
|
+
if (runtime.dryRun) {
|
|
377
|
+
printPlan(runtime, { command: "memory note", candidate: record, path: relPath }, [{ action: "write", target: path }], `record memory candidate in ${relPath} (the id is assigned when it is recorded)`);
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
344
380
|
print(
|
|
345
381
|
runtime.io,
|
|
346
382
|
runtime.json,
|
|
@@ -357,7 +393,16 @@ export async function cmdMemoryNote(
|
|
|
357
393
|
|
|
358
394
|
export async function cmdMemorySync(runtime: Runtime): Promise<number> {
|
|
359
395
|
try {
|
|
360
|
-
const result = syncHarnessMemory(runtime.cwd);
|
|
396
|
+
const result = syncHarnessMemory(runtime.cwd, { dryRun: runtime.dryRun });
|
|
397
|
+
if (runtime.dryRun) {
|
|
398
|
+
printPlan(
|
|
399
|
+
runtime,
|
|
400
|
+
{ command: "memory sync", ...result },
|
|
401
|
+
[...result.created, ...result.updated].map((file) => ({ action: "write", target: join(runtime.cwd, file) })),
|
|
402
|
+
"regenerate project memory blocks in AGENTS.md, CLAUDE.md, and GEMINI.md",
|
|
403
|
+
);
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
361
406
|
print(
|
|
362
407
|
runtime.io,
|
|
363
408
|
runtime.json,
|
|
@@ -6,7 +6,7 @@ import { defaultProjectName } from "../project-name.ts";
|
|
|
6
6
|
import { hasClientHubCredential, resolveClientHubAuthToken } from "../hub-env.ts";
|
|
7
7
|
import { agentWorker, type Worker } from "../envelope.ts";
|
|
8
8
|
import { MESH_TUI_PANELS, runMeshTui, type MeshTuiPanel } from "../tui.ts";
|
|
9
|
-
import { formatSessionBriefText, loadSessionBriefAsync, type SessionHubStatus } from "../session-work.ts";
|
|
9
|
+
import { formatSessionBriefText, loadSessionBriefAsync, readCachedSessionBrief, type SessionHubStatus } from "../session-work.ts";
|
|
10
10
|
import {
|
|
11
11
|
HUB_BINDING_SCHEMA,
|
|
12
12
|
HubBindingError,
|
|
@@ -47,11 +47,13 @@ import {
|
|
|
47
47
|
import { createSession, loadNamedWorkers, rosterNames, sessionAssetDirs, workflowAssetDirs, writeSession } from "../session.ts";
|
|
48
48
|
import {
|
|
49
49
|
print,
|
|
50
|
+
printPlan,
|
|
50
51
|
printWorker,
|
|
51
52
|
hostMode,
|
|
52
53
|
workspaceEnv,
|
|
53
54
|
spawnScript,
|
|
54
55
|
processExists,
|
|
56
|
+
type PlannedChange,
|
|
55
57
|
type Runtime,
|
|
56
58
|
} from "./types.ts";
|
|
57
59
|
|
|
@@ -140,7 +142,7 @@ export async function refreshKxmUpdateNotice(
|
|
|
140
142
|
}
|
|
141
143
|
const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
|
|
142
144
|
const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
|
|
143
|
-
writeUpdateCache(runtime.dirs.state, notice);
|
|
145
|
+
if (!runtime.dryRun) writeUpdateCache(runtime.dirs.state, notice);
|
|
144
146
|
return notice;
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -556,6 +558,12 @@ export async function cmdSessionStatus(runtime: Runtime): Promise<number> {
|
|
|
556
558
|
}
|
|
557
559
|
|
|
558
560
|
export async function cmdAuthToken(runtime: Runtime, options: { status?: boolean | undefined; clear?: boolean | undefined; issue?: boolean | undefined } = {}): Promise<number> {
|
|
561
|
+
const tokenFile = sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR);
|
|
562
|
+
if (options.clear && runtime.dryRun) {
|
|
563
|
+
const present = existsSync(tokenFile);
|
|
564
|
+
printPlan(runtime, { command: "auth token", cleared: present }, present ? [{ action: "delete", target: tokenFile }] : [], present ? "clear the session token from disk" : "no session token file to clear");
|
|
565
|
+
return 0;
|
|
566
|
+
}
|
|
559
567
|
if (options.clear) {
|
|
560
568
|
const cleared = clearSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
561
569
|
print(
|
|
@@ -615,18 +623,19 @@ export async function cmdAuthToken(runtime: Runtime, options: { status?: boolean
|
|
|
615
623
|
return 1;
|
|
616
624
|
}
|
|
617
625
|
|
|
626
|
+
const existingToken = options.issue ? undefined : readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
627
|
+
if (runtime.dryRun && !existingToken) {
|
|
628
|
+
// The token is only worth printing once it is on disk; a dry run never is.
|
|
629
|
+
printPlan(runtime, { command: "auth token" }, [{ action: "write", target: tokenFile }], "issue an operator session token and persist it");
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
|
|
618
633
|
let token: string;
|
|
619
|
-
if (
|
|
634
|
+
if (existingToken) {
|
|
635
|
+
token = existingToken.token;
|
|
636
|
+
} else {
|
|
620
637
|
token = mintSessionToken({ preset: "operator" });
|
|
621
638
|
persistSessionTokenToDisk(token, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
622
|
-
} else {
|
|
623
|
-
const existing = readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
624
|
-
if (existing) {
|
|
625
|
-
token = existing.token;
|
|
626
|
-
} else {
|
|
627
|
-
token = mintSessionToken({ preset: "operator" });
|
|
628
|
-
persistSessionTokenToDisk(token, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
629
|
-
}
|
|
630
639
|
}
|
|
631
640
|
|
|
632
641
|
print(runtime.io, runtime.json, { ok: true, command: "auth token", token }, token);
|
|
@@ -635,11 +644,17 @@ export async function cmdAuthToken(runtime: Runtime, options: { status?: boolean
|
|
|
635
644
|
|
|
636
645
|
export async function cmdSessionBrief(runtime: Runtime, options: { status?: boolean | undefined; token?: boolean | undefined } = {}): Promise<number> {
|
|
637
646
|
const existing = readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
638
|
-
|
|
639
|
-
|
|
647
|
+
// A dry run neither mints nor persists a token; it names the file it would write.
|
|
648
|
+
const planned: PlannedChange[] = existing ? [] : [{ action: "write", target: sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR) }];
|
|
649
|
+
const sessionToken = existing?.token ?? (runtime.dryRun ? undefined : mintSessionToken({ preset: "operator" }));
|
|
650
|
+
if (!existing && sessionToken) {
|
|
640
651
|
persistSessionTokenToDisk(sessionToken, { userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
641
652
|
}
|
|
642
653
|
if (options.token) {
|
|
654
|
+
if (!sessionToken) {
|
|
655
|
+
printPlan(runtime, { command: "session brief" }, planned, "issue an operator session token and persist it");
|
|
656
|
+
return 0;
|
|
657
|
+
}
|
|
643
658
|
print(runtime.io, runtime.json, { ok: true, command: "session brief", sessionToken }, sessionToken);
|
|
644
659
|
return 0;
|
|
645
660
|
}
|
|
@@ -668,9 +683,19 @@ export async function cmdSessionBrief(runtime: Runtime, options: { status?: bool
|
|
|
668
683
|
|
|
669
684
|
const brief = await loadSessionBriefAsync(runtime.dirs.workdir, env, undefined, hub, {
|
|
670
685
|
fetchImpl: runtime.fetchImpl,
|
|
671
|
-
sessionToken,
|
|
686
|
+
...(sessionToken ? { sessionToken } : {}),
|
|
687
|
+
writeCache: !runtime.dryRun,
|
|
672
688
|
});
|
|
673
689
|
|
|
690
|
+
if (runtime.dryRun) {
|
|
691
|
+
if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target: join(runtime.dirs.state, "session-brief.json") });
|
|
692
|
+
print(runtime.io, runtime.json, { ...brief, dryRun: true, planned }, [
|
|
693
|
+
options.status ? brief.statusLine : formatSessionBriefText(brief),
|
|
694
|
+
...planned.map((change) => `dry run: would ${change.action} ${change.target}`),
|
|
695
|
+
].join("\n"));
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
674
699
|
if (options.status) {
|
|
675
700
|
print(runtime.io, runtime.json, brief, brief.statusLine);
|
|
676
701
|
return 0;
|