@kontextmind/kxm 0.7.86 → 0.7.88
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 +897 -500
- package/plugins/kxm/dist/core.js +2 -2
- package/plugins/kxm/dist/extension.js +72 -63
- package/plugins/kxm/dist/mcp-server.js +3 -3
- package/plugins/kxm/dist/runtime.js +44 -26
- package/plugins/kxm/dist/server.js +56 -31
- 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 +78 -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/commands.ts +2 -2
- package/plugins/kxm/src/config.ts +4 -3
- package/plugins/kxm/src/context/providers.ts +4 -0
- package/plugins/kxm/src/database.ts +72 -33
- package/plugins/kxm/src/hub.ts +26 -8
- 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/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/state.ts +4 -1
- package/plugins/kxm/src/task-manager.ts +20 -13
- package/plugins/kxm/src/workflow-manager.ts +12 -4
|
@@ -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";
|
|
@@ -17303,8 +17303,8 @@ var AGENT_COMMANDS = [
|
|
|
17303
17303
|
summary: { type: "string", description: "Promotion summary" },
|
|
17304
17304
|
authority: {
|
|
17305
17305
|
type: "string",
|
|
17306
|
-
enum: ["
|
|
17307
|
-
description: "Authority class"
|
|
17306
|
+
enum: ["evidence", "hypothesis"],
|
|
17307
|
+
description: "Authority class; an agent's proposal is peer origin, so evidence at most"
|
|
17308
17308
|
},
|
|
17309
17309
|
confidence: {
|
|
17310
17310
|
type: "string",
|
|
@@ -18082,6 +18082,9 @@ var NativeStateProvider = class {
|
|
|
18082
18082
|
if (change?.schema !== "kxm.state-change-proposal.v1") {
|
|
18083
18083
|
throw new ProtocolError(400, "invalid state change proposal schema", "invalid_state_proposal");
|
|
18084
18084
|
}
|
|
18085
|
+
if (change.origin !== "peer" && change.origin !== "human") {
|
|
18086
|
+
throw new ProtocolError(400, "state change proposal origin must be peer or human", "invalid_state_proposal");
|
|
18087
|
+
}
|
|
18085
18088
|
const project = requireNonEmpty(change.project, "proposal project");
|
|
18086
18089
|
const key = requireNonEmpty(change.key, "proposal key");
|
|
18087
18090
|
const evidenceRefs = boundedRefs(change.evidenceRefs, "proposal evidenceRefs");
|
|
@@ -18091,7 +18094,7 @@ var NativeStateProvider = class {
|
|
|
18091
18094
|
project,
|
|
18092
18095
|
summary: change.summary,
|
|
18093
18096
|
provenance: {
|
|
18094
|
-
sourceType: change.
|
|
18097
|
+
sourceType: change.origin,
|
|
18095
18098
|
sourceRef: `proposed-by:${change.proposedBy}`
|
|
18096
18099
|
},
|
|
18097
18100
|
authority: change.authority,
|
|
@@ -18213,7 +18216,7 @@ function boundedRefs(value, field) {
|
|
|
18213
18216
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
18214
18217
|
import { createHash as createHash4 } from "node:crypto";
|
|
18215
18218
|
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";
|
|
18219
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
18217
18220
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
18218
18221
|
var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
|
|
18219
18222
|
var SKILL_DECISION_SCHEMA = "kxm.skill-decision.v1";
|
|
@@ -18301,10 +18304,22 @@ var SkillLifecycle = class {
|
|
|
18301
18304
|
root;
|
|
18302
18305
|
now;
|
|
18303
18306
|
allowOptimizationEvals;
|
|
18307
|
+
dryRun;
|
|
18308
|
+
/** What a `dryRun` lifecycle would have written or moved, in order. */
|
|
18309
|
+
planned = [];
|
|
18304
18310
|
constructor(root, options = {}) {
|
|
18305
18311
|
this.root = root;
|
|
18306
18312
|
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
18307
18313
|
this.allowOptimizationEvals = options.allowOptimizationEvals === true;
|
|
18314
|
+
this.dryRun = options.dryRun === true;
|
|
18315
|
+
}
|
|
18316
|
+
write(file, content) {
|
|
18317
|
+
if (this.dryRun) {
|
|
18318
|
+
this.planned.push({ action: "write", target: file });
|
|
18319
|
+
return;
|
|
18320
|
+
}
|
|
18321
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
18322
|
+
writeFileSync2(file, content);
|
|
18308
18323
|
}
|
|
18309
18324
|
dir(state) {
|
|
18310
18325
|
return join4(this.root, state === "candidate" ? "candidates" : `${state}s`.replace("rejecteds", "rejected").replace("promoteds", "promoted"));
|
|
@@ -18317,15 +18332,14 @@ var SkillLifecycle = class {
|
|
|
18317
18332
|
return { dir, metadata: join4(dir, "metadata.json"), skill: join4(dir, "SKILL.md") };
|
|
18318
18333
|
}
|
|
18319
18334
|
appendHistory(id, record) {
|
|
18320
|
-
mkdirSync2(join4(this.root, "history"), { recursive: true });
|
|
18321
18335
|
const line = `${JSON.stringify(record)}
|
|
18322
18336
|
`;
|
|
18323
18337
|
if (existsSync4(this.historyFile(id))) {
|
|
18324
18338
|
const existing = readFileSync3(this.historyFile(id), "utf8");
|
|
18325
18339
|
const lines = existing.split("\n").filter((entry) => entry.trim());
|
|
18326
|
-
|
|
18340
|
+
this.write(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
|
|
18327
18341
|
} else {
|
|
18328
|
-
|
|
18342
|
+
this.write(this.historyFile(id), line);
|
|
18329
18343
|
}
|
|
18330
18344
|
}
|
|
18331
18345
|
history(id) {
|
|
@@ -18346,6 +18360,10 @@ var SkillLifecycle = class {
|
|
|
18346
18360
|
if (!existsSync4(fromDir)) {
|
|
18347
18361
|
throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${from}`);
|
|
18348
18362
|
}
|
|
18363
|
+
if (this.dryRun) {
|
|
18364
|
+
this.planned.push({ action: "move", target: `${fromDir} -> ${toDir}` });
|
|
18365
|
+
return;
|
|
18366
|
+
}
|
|
18349
18367
|
mkdirSync2(this.dir(to), { recursive: true });
|
|
18350
18368
|
if (existsSync4(toDir)) rmSync(toDir, { recursive: true, force: true });
|
|
18351
18369
|
renameSync(fromDir, toDir);
|
|
@@ -18389,7 +18407,7 @@ var SkillLifecycle = class {
|
|
|
18389
18407
|
if (!harness) throw new SkillLifecycleError("invalid_skill_compatibility", "compatibility.harness is required");
|
|
18390
18408
|
const contentSha256 = skillContentSha256(content);
|
|
18391
18409
|
const id = skillIdFor(name, contentSha256);
|
|
18392
|
-
const {
|
|
18410
|
+
const { metadata, skill } = this.paths("candidate", id);
|
|
18393
18411
|
if (existsSync4(metadata)) {
|
|
18394
18412
|
throw new SkillLifecycleError(
|
|
18395
18413
|
"skill_candidate_exists",
|
|
@@ -18409,9 +18427,8 @@ var SkillLifecycle = class {
|
|
|
18409
18427
|
createdAt: this.now(),
|
|
18410
18428
|
...input.supersedes ? { supersedes: input.supersedes } : {}
|
|
18411
18429
|
};
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
writeFileSync2(metadata, `${JSON.stringify(record, null, 2)}
|
|
18430
|
+
this.write(skill, content);
|
|
18431
|
+
this.write(metadata, `${JSON.stringify(record, null, 2)}
|
|
18415
18432
|
`);
|
|
18416
18433
|
this.appendHistory(id, { schema: "kxm.skill-history-event.v1", event: "candidate_created", by: createdBy, supersedes: input.supersedes, at: record.createdAt });
|
|
18417
18434
|
return record;
|
|
@@ -18500,18 +18517,15 @@ var SkillLifecycle = class {
|
|
|
18500
18517
|
};
|
|
18501
18518
|
const candidatePaths = this.paths("candidate", candidateId);
|
|
18502
18519
|
const promotedPaths = this.paths("promoted", candidateId);
|
|
18503
|
-
mkdirSync2(promotedPaths.dir, { recursive: true });
|
|
18504
18520
|
const skillContent = readFileSync3(candidatePaths.skill, "utf8");
|
|
18505
18521
|
const metadataContent = readFileSync3(candidatePaths.metadata, "utf8");
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
const
|
|
18509
|
-
mkdirSync2(patchesDir, { recursive: true });
|
|
18510
|
-
const patchPath = join4(patchesDir, `${candidateId}.patch`);
|
|
18522
|
+
this.write(promotedPaths.skill, skillContent);
|
|
18523
|
+
this.write(promotedPaths.metadata, metadataContent);
|
|
18524
|
+
const patchPath = join4(this.root, "patches", `${candidateId}.patch`);
|
|
18511
18525
|
const relSkillPath = `.kxm/skills/promoted/${candidateId}/SKILL.md`;
|
|
18512
18526
|
const relMetaPath = `.kxm/skills/promoted/${candidateId}/metadata.json`;
|
|
18513
18527
|
const patch = `${createUnifiedPatch(relSkillPath, skillContent)}${createUnifiedPatch(relMetaPath, metadataContent)}`;
|
|
18514
|
-
|
|
18528
|
+
this.write(patchPath, patch);
|
|
18515
18529
|
this.appendHistory(candidateId, record);
|
|
18516
18530
|
return { ...metadata, patch, patchPath };
|
|
18517
18531
|
}
|
|
@@ -19031,7 +19045,7 @@ import {
|
|
|
19031
19045
|
unlinkSync,
|
|
19032
19046
|
writeFileSync as writeFileSync4
|
|
19033
19047
|
} from "node:fs";
|
|
19034
|
-
import { basename as basename3, dirname as
|
|
19048
|
+
import { basename as basename3, dirname as dirname5, join as join5, resolve as resolve4 } from "node:path";
|
|
19035
19049
|
|
|
19036
19050
|
// plugins/kxm/src/sqlite.ts
|
|
19037
19051
|
import { createRequire } from "node:module";
|
|
@@ -19078,7 +19092,7 @@ function databaseError(code, file, message) {
|
|
|
19078
19092
|
return new KxmConfigError([issue2]);
|
|
19079
19093
|
}
|
|
19080
19094
|
function checkedParent(path, description) {
|
|
19081
|
-
const parent =
|
|
19095
|
+
const parent = dirname5(path);
|
|
19082
19096
|
if (!existsSync5(parent)) mkdirSync4(parent, { recursive: true, mode: 448 });
|
|
19083
19097
|
const stat = lstatSync2(parent, { throwIfNoEntry: false });
|
|
19084
19098
|
if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -20359,7 +20373,7 @@ function createMeshHub(options = {}) {
|
|
|
20359
20373
|
const logger = options.logger ?? (() => void 0);
|
|
20360
20374
|
const hubNow = options.now ?? (() => Date.now());
|
|
20361
20375
|
const assetsDir2 = options.assetsDir;
|
|
20362
|
-
const hubRepoRoot = options.repoRoot ?? (options.dataPath && options.dataPath !== ":memory:" ? resolve6(
|
|
20376
|
+
const hubRepoRoot = options.repoRoot ?? (options.dataPath && options.dataPath !== ":memory:" ? resolve6(dirname6(dirname6(options.dataPath))) : process.cwd());
|
|
20363
20377
|
const store = new MeshStore(options.dataPath);
|
|
20364
20378
|
const agents = store.agents;
|
|
20365
20379
|
const messages = store.messages;
|
|
@@ -20470,12 +20484,12 @@ function createMeshHub(options = {}) {
|
|
|
20470
20484
|
if (agent.project !== project) {
|
|
20471
20485
|
throw new ProtocolError(403, "context requests are limited to the agent's project", "context_isolation_violation");
|
|
20472
20486
|
}
|
|
20473
|
-
return { project, caller: agent.id };
|
|
20487
|
+
return { project, caller: agent.id, credential: "agent" };
|
|
20474
20488
|
}
|
|
20475
20489
|
requireAdminAuth(request);
|
|
20476
20490
|
const callerHeader = request.headers["x-kxm-caller-id"];
|
|
20477
20491
|
const caller = typeof callerHeader === "string" && callerHeader.trim() ? callerHeader.trim() : "kxm-admin";
|
|
20478
|
-
return { project, caller };
|
|
20492
|
+
return { project, caller, credential: "admin" };
|
|
20479
20493
|
}
|
|
20480
20494
|
function requireAdminAuth(request) {
|
|
20481
20495
|
if (!authToken2 && isLoopback(host2)) return;
|
|
@@ -21582,7 +21596,17 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
|
|
|
21582
21596
|
const contextStateProposeMatch = url.pathname.match(/^\/v1\/context\/state\/propose$/);
|
|
21583
21597
|
if (method === "POST" && contextStateProposeMatch) {
|
|
21584
21598
|
const body = await readJson(request);
|
|
21585
|
-
const { project: callerProject, caller: callerId } = contextCallerProject(request, body.project);
|
|
21599
|
+
const { project: callerProject, caller: callerId, credential } = contextCallerProject(request, body.project);
|
|
21600
|
+
if (credential === "admin") requireConfiguredAdminAuth(request, "human-origin state proposals");
|
|
21601
|
+
if (body.proposedBy !== void 0 && body.proposedBy !== callerId) {
|
|
21602
|
+
logger({ event: "security_alert", alert: "state_proposer_mismatch", project: callerProject, callerId, credential });
|
|
21603
|
+
throw new ProtocolError(
|
|
21604
|
+
403,
|
|
21605
|
+
"proposedBy must name the authenticated caller",
|
|
21606
|
+
"state_proposer_mismatch"
|
|
21607
|
+
);
|
|
21608
|
+
}
|
|
21609
|
+
const origin = credential === "agent" ? "peer" : "human";
|
|
21586
21610
|
const proposalId = await stateProvider.propose({
|
|
21587
21611
|
schema: "kxm.state-change-proposal.v1",
|
|
21588
21612
|
project: callerProject,
|
|
@@ -21591,11 +21615,12 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
|
|
|
21591
21615
|
authority: parseContextAuthority(body.authority),
|
|
21592
21616
|
confidence: parseContextConfidence(body.confidence),
|
|
21593
21617
|
evidenceRefs: boundedStringList(body.evidenceRefs, "evidenceRefs", 32),
|
|
21594
|
-
proposedBy:
|
|
21618
|
+
proposedBy: callerId,
|
|
21619
|
+
origin
|
|
21595
21620
|
});
|
|
21596
21621
|
counters.contextRequests += 1;
|
|
21597
21622
|
publishOps(callerProject, "workflows");
|
|
21598
|
-
logger({ event: "context_state_proposed", project: callerProject, proposalId, proposedBy: callerId });
|
|
21623
|
+
logger({ event: "context_state_proposed", project: callerProject, proposalId, proposedBy: callerId, origin });
|
|
21599
21624
|
json(response, 201, { proposalId });
|
|
21600
21625
|
return;
|
|
21601
21626
|
}
|
|
@@ -22556,11 +22581,11 @@ data: ${JSON.stringify({ agent: publicAgent(current, staleAfterMs) })}
|
|
|
22556
22581
|
|
|
22557
22582
|
// plugins/kxm/src/server.ts
|
|
22558
22583
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
22559
|
-
import { dirname as
|
|
22584
|
+
import { dirname as dirname8, join as join7, resolve as resolve7 } from "node:path";
|
|
22560
22585
|
|
|
22561
22586
|
// plugins/kxm/src/logger.ts
|
|
22562
22587
|
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, renameSync as renameSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
22563
|
-
import { dirname as
|
|
22588
|
+
import { dirname as dirname7 } from "node:path";
|
|
22564
22589
|
var LOG_LEVEL_PRIORITY = {
|
|
22565
22590
|
debug: 10,
|
|
22566
22591
|
info: 20,
|
|
@@ -22666,7 +22691,7 @@ function createLogger(options) {
|
|
|
22666
22691
|
currentSize = 0;
|
|
22667
22692
|
}
|
|
22668
22693
|
try {
|
|
22669
|
-
mkdirSync5(
|
|
22694
|
+
mkdirSync5(dirname7(filePath), { recursive: true });
|
|
22670
22695
|
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
22671
22696
|
currentSize += lineBytes;
|
|
22672
22697
|
} catch {
|
|
@@ -22729,7 +22754,7 @@ var rateLimitWindowMs = Number.parseInt(
|
|
|
22729
22754
|
process.env.KXM_RATE_LIMIT_WINDOW_MS ?? String(DEFAULT_RATE_LIMIT_WINDOW_MS),
|
|
22730
22755
|
10
|
|
22731
22756
|
);
|
|
22732
|
-
for (const directory of [configDir, logsDir, assetsDir, stateDir,
|
|
22757
|
+
for (const directory of [configDir, logsDir, assetsDir, stateDir, dirname8(logPath)]) {
|
|
22733
22758
|
mkdirSync6(directory, { recursive: true });
|
|
22734
22759
|
}
|
|
22735
22760
|
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;
|