@kontextmind/kxm 0.7.104 → 0.7.106
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 +23 -0
- package/docs/contributing/test-matrix.md +6 -1
- package/docs/reference/cli-reference.md +45 -36
- package/docs/reference/config-reference.md +1 -1
- package/docs/reference/workflow-definitions.md +4 -4
- package/docs/start/first-workflow.md +6 -2
- package/docs/start/quickstart-claude-code.md +10 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +1421 -760
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +12 -10
- package/plugins/kxm/dist/runtime.js +14 -12
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/project.ts +66 -18
- package/plugins/kxm/src/cli/roles.ts +47 -40
- package/plugins/kxm/src/cli/system.ts +19 -1
- package/plugins/kxm/src/cli/tasks.ts +55 -31
- package/plugins/kxm/src/cli/workflows.ts +52 -54
- package/plugins/kxm/src/cli.ts +1 -1
- package/plugins/kxm/src/engine.ts +79 -3
- package/plugins/kxm/src/harness.ts +6 -5
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/project-config.ts +33 -5
- package/plugins/kxm/src/suggest.ts +132 -79
- package/plugins/kxm/src/workflow-manager.ts +42 -19
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -2987,7 +2987,7 @@ var require_compile = __commonJS({
|
|
|
2987
2987
|
const schOrFunc = root.refs[ref];
|
|
2988
2988
|
if (schOrFunc)
|
|
2989
2989
|
return schOrFunc;
|
|
2990
|
-
let _sch =
|
|
2990
|
+
let _sch = resolve34.call(this, root, ref);
|
|
2991
2991
|
if (_sch === void 0) {
|
|
2992
2992
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2993
2993
|
const { schemaId } = this.opts;
|
|
@@ -3014,7 +3014,7 @@ var require_compile = __commonJS({
|
|
|
3014
3014
|
function sameSchemaEnv(s1, s2) {
|
|
3015
3015
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3016
3016
|
}
|
|
3017
|
-
function
|
|
3017
|
+
function resolve34(root, ref) {
|
|
3018
3018
|
let sch;
|
|
3019
3019
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3020
3020
|
ref = sch;
|
|
@@ -3835,11 +3835,11 @@ var require_fast_uri = __commonJS({
|
|
|
3835
3835
|
normalizeString(uri, options);
|
|
3836
3836
|
} else if (typeof uri === "object") {
|
|
3837
3837
|
uri = /** @type {T} */
|
|
3838
|
-
|
|
3838
|
+
parse11(serialize(uri, options), options);
|
|
3839
3839
|
}
|
|
3840
3840
|
return uri;
|
|
3841
3841
|
}
|
|
3842
|
-
function
|
|
3842
|
+
function resolve34(baseURI, relativeURI, options) {
|
|
3843
3843
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3844
3844
|
const {
|
|
3845
3845
|
parsed: baseParsed,
|
|
@@ -3875,8 +3875,8 @@ var require_fast_uri = __commonJS({
|
|
|
3875
3875
|
function resolveComponent(base, relative8, options, skipNormalization) {
|
|
3876
3876
|
const target = {};
|
|
3877
3877
|
if (!skipNormalization) {
|
|
3878
|
-
base =
|
|
3879
|
-
relative8 =
|
|
3878
|
+
base = parse11(serialize(base, options), options);
|
|
3879
|
+
relative8 = parse11(serialize(relative8, options), options);
|
|
3880
3880
|
}
|
|
3881
3881
|
options = options || {};
|
|
3882
3882
|
if (!options.tolerant && relative8.scheme) {
|
|
@@ -4168,7 +4168,7 @@ var require_fast_uri = __commonJS({
|
|
|
4168
4168
|
}
|
|
4169
4169
|
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
|
|
4170
4170
|
}
|
|
4171
|
-
function
|
|
4171
|
+
function parse11(uri, opts) {
|
|
4172
4172
|
return parseWithStatus(uri, opts).parsed;
|
|
4173
4173
|
}
|
|
4174
4174
|
function normalizeString(uri, opts) {
|
|
@@ -4201,11 +4201,11 @@ var require_fast_uri = __commonJS({
|
|
|
4201
4201
|
var fastUri = {
|
|
4202
4202
|
SCHEMES,
|
|
4203
4203
|
normalize,
|
|
4204
|
-
resolve:
|
|
4204
|
+
resolve: resolve34,
|
|
4205
4205
|
resolveComponent,
|
|
4206
4206
|
equal,
|
|
4207
4207
|
serialize,
|
|
4208
|
-
parse:
|
|
4208
|
+
parse: parse11
|
|
4209
4209
|
};
|
|
4210
4210
|
module.exports = fastUri;
|
|
4211
4211
|
module.exports.default = fastUri;
|
|
@@ -14678,7 +14678,7 @@ var require_public_api = __commonJS({
|
|
|
14678
14678
|
}
|
|
14679
14679
|
return doc;
|
|
14680
14680
|
}
|
|
14681
|
-
function
|
|
14681
|
+
function parse11(src, reviver, options) {
|
|
14682
14682
|
let _reviver = void 0;
|
|
14683
14683
|
if (typeof reviver === "function") {
|
|
14684
14684
|
_reviver = reviver;
|
|
@@ -14719,7 +14719,7 @@ var require_public_api = __commonJS({
|
|
|
14719
14719
|
return value.toString(options);
|
|
14720
14720
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
14721
14721
|
}
|
|
14722
|
-
exports.parse =
|
|
14722
|
+
exports.parse = parse11;
|
|
14723
14723
|
exports.parseAllDocuments = parseAllDocuments;
|
|
14724
14724
|
exports.parseDocument = parseDocument2;
|
|
14725
14725
|
exports.stringify = stringify12;
|
|
@@ -14779,7 +14779,7 @@ var require_dist = __commonJS({
|
|
|
14779
14779
|
});
|
|
14780
14780
|
|
|
14781
14781
|
// plugins/kxm/src/cli.ts
|
|
14782
|
-
import { resolve as
|
|
14782
|
+
import { resolve as resolve33 } from "node:path";
|
|
14783
14783
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
14784
14784
|
|
|
14785
14785
|
// node_modules/commander/lib/error.js
|
|
@@ -19751,7 +19751,7 @@ var HubClient = class {
|
|
|
19751
19751
|
if (signal?.aborted) throw new MeshWaitError("aborted", messageId);
|
|
19752
19752
|
const message = await this.getMessage(messageId);
|
|
19753
19753
|
if (["replied", "cancelled", "expired", "error"].includes(message.status)) return message;
|
|
19754
|
-
await new Promise((
|
|
19754
|
+
await new Promise((resolve34, reject) => {
|
|
19755
19755
|
const onAbort = () => {
|
|
19756
19756
|
signal?.removeEventListener("abort", onAbort);
|
|
19757
19757
|
clearTimeout(timer);
|
|
@@ -19759,7 +19759,7 @@ var HubClient = class {
|
|
|
19759
19759
|
};
|
|
19760
19760
|
const timer = setTimeout(() => {
|
|
19761
19761
|
signal?.removeEventListener("abort", onAbort);
|
|
19762
|
-
|
|
19762
|
+
resolve34();
|
|
19763
19763
|
}, Math.min(500, Math.max(1, deadline - Date.now())));
|
|
19764
19764
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
19765
19765
|
if (signal?.aborted) onAbort();
|
|
@@ -19814,7 +19814,7 @@ var HubClient = class {
|
|
|
19814
19814
|
} catch (error) {
|
|
19815
19815
|
if (this.stopped || error instanceof Error && error.name === "AbortError") return;
|
|
19816
19816
|
}
|
|
19817
|
-
if (!this.stopped) await new Promise((
|
|
19817
|
+
if (!this.stopped) await new Promise((resolve34) => setTimeout(resolve34, this.options.reconnectMs));
|
|
19818
19818
|
}
|
|
19819
19819
|
}
|
|
19820
19820
|
headers(includeIdentity = true) {
|
|
@@ -21350,7 +21350,7 @@ function defaultSpawn(command, args, options) {
|
|
|
21350
21350
|
if (options.signal?.aborted) {
|
|
21351
21351
|
return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
|
|
21352
21352
|
}
|
|
21353
|
-
return new Promise((
|
|
21353
|
+
return new Promise((resolve34) => {
|
|
21354
21354
|
const stdout = [];
|
|
21355
21355
|
const stderr = [];
|
|
21356
21356
|
let outputBytes = 0;
|
|
@@ -21381,7 +21381,7 @@ function defaultSpawn(command, args, options) {
|
|
|
21381
21381
|
const started = Boolean(child?.pid);
|
|
21382
21382
|
if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
|
|
21383
21383
|
const unverifiedDescendants = stopping || started && !observedChildExit;
|
|
21384
|
-
|
|
21384
|
+
resolve34({
|
|
21385
21385
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
21386
21386
|
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
21387
21387
|
code,
|
|
@@ -21790,6 +21790,12 @@ var WRITER_ONESHOT_ARGS = Object.freeze({
|
|
|
21790
21790
|
pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]),
|
|
21791
21791
|
grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"])
|
|
21792
21792
|
});
|
|
21793
|
+
function oneShotWriterArgs(harness) {
|
|
21794
|
+
return Object.hasOwn(WRITER_ONESHOT_ARGS, harness) ? WRITER_ONESHOT_ARGS[harness] : void 0;
|
|
21795
|
+
}
|
|
21796
|
+
function oneShotPermissionArgs(harness, permission) {
|
|
21797
|
+
return permission === "edit" ? oneShotWriterArgs(harness) : oneShotReadOnlyArgs(harness);
|
|
21798
|
+
}
|
|
21793
21799
|
var BUILTIN_HARNESSES = Object.freeze([
|
|
21794
21800
|
{
|
|
21795
21801
|
id: "pi",
|
|
@@ -22167,7 +22173,7 @@ function probeEntry(entry, runCommand, timeoutMs, platform, probe = {}) {
|
|
|
22167
22173
|
return {
|
|
22168
22174
|
id: entry.id,
|
|
22169
22175
|
label: entry.label,
|
|
22170
|
-
default: entry.
|
|
22176
|
+
default: entry.id === (probe.defaultHarness ?? DEFAULT_HARNESS),
|
|
22171
22177
|
mode: entry.mode,
|
|
22172
22178
|
detected,
|
|
22173
22179
|
authenticated,
|
|
@@ -22188,7 +22194,7 @@ function probeHarnesses(options = {}) {
|
|
|
22188
22194
|
const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
|
|
22189
22195
|
const platform = options.platform ?? process.platform;
|
|
22190
22196
|
return {
|
|
22191
|
-
defaultHarness: DEFAULT_HARNESS,
|
|
22197
|
+
defaultHarness: options.defaultHarness ?? DEFAULT_HARNESS,
|
|
22192
22198
|
harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs, platform, options))
|
|
22193
22199
|
};
|
|
22194
22200
|
}
|
|
@@ -22432,7 +22438,7 @@ function formatHarnessInventory(inventory) {
|
|
|
22432
22438
|
].join(" ");
|
|
22433
22439
|
});
|
|
22434
22440
|
return [
|
|
22435
|
-
`default harness: ${inventory.defaultHarness} (
|
|
22441
|
+
`default harness: ${inventory.defaultHarness} (used when an agent omits harness:)`,
|
|
22436
22442
|
"enable/disable = Git YAML (.kxm/agents, .kxm/models) or the harness's own plugin CLI",
|
|
22437
22443
|
"governed kxm skills are not auto-updated",
|
|
22438
22444
|
header,
|
|
@@ -23094,8 +23100,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
23094
23100
|
const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
|
|
23095
23101
|
if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
|
|
23096
23102
|
}
|
|
23097
|
-
const
|
|
23098
|
-
const minimumPassed =
|
|
23103
|
+
const join52 = objectValue(step.join);
|
|
23104
|
+
const minimumPassed = join52 && typeof join52.minimumPassed === "number" ? join52.minimumPassed : void 0;
|
|
23099
23105
|
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
|
|
23100
23106
|
const distinctBy = names(assignment?.distinctBy);
|
|
23101
23107
|
if (distinctBy.length > 0) {
|
|
@@ -23240,7 +23246,7 @@ function validateModelReferences(models, issues) {
|
|
|
23240
23246
|
};
|
|
23241
23247
|
for (const id of models.keys()) walk(id, []);
|
|
23242
23248
|
}
|
|
23243
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
|
|
23249
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, writerRole) {
|
|
23244
23250
|
const issues = [];
|
|
23245
23251
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
23246
23252
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -23311,9 +23317,9 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
23311
23317
|
if (projectRoot) {
|
|
23312
23318
|
const writerRolePath = join6(projectRoot, ".kxm", "roles", "writer.yaml");
|
|
23313
23319
|
const implementerAgent = agents.get("implementer") ?? agents.get("writer");
|
|
23314
|
-
if (existsSync6(writerRolePath) && implementerAgent) {
|
|
23320
|
+
if ((writerRole !== void 0 || existsSync6(writerRolePath)) && implementerAgent) {
|
|
23315
23321
|
try {
|
|
23316
|
-
const rawRole = parseRestrictedYaml2(readFileSync5(writerRolePath, "utf8"));
|
|
23322
|
+
const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync5(writerRolePath, "utf8"));
|
|
23317
23323
|
const roleObj = objectValue(rawRole);
|
|
23318
23324
|
const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
|
|
23319
23325
|
const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m2) => Boolean(m2));
|
|
@@ -23384,15 +23390,23 @@ function loadKxmProject(projectRoot, options = {}) {
|
|
|
23384
23390
|
return loadProjectBundle(projectRoot, options);
|
|
23385
23391
|
}
|
|
23386
23392
|
function kxmWorkflowWriteIssues(projectRoot, workflowId, document, options = {}) {
|
|
23393
|
+
return candidateIssues(projectRoot, options, { kind: "workflow", id: workflowId, document });
|
|
23394
|
+
}
|
|
23395
|
+
function kxmRoleWriteIssues(projectRoot, roleId, document, options = {}) {
|
|
23396
|
+
return candidateIssues(projectRoot, options, { kind: "role", id: roleId, document });
|
|
23397
|
+
}
|
|
23398
|
+
function candidateIssues(projectRoot, options, candidate) {
|
|
23387
23399
|
try {
|
|
23388
|
-
loadProjectBundle(projectRoot, options,
|
|
23400
|
+
loadProjectBundle(projectRoot, options, candidate);
|
|
23389
23401
|
return [];
|
|
23390
23402
|
} catch (error) {
|
|
23391
23403
|
if (error instanceof KxmConfigError) return error.issues;
|
|
23392
23404
|
throw error;
|
|
23393
23405
|
}
|
|
23394
23406
|
}
|
|
23395
|
-
function loadProjectBundle(projectRoot, options,
|
|
23407
|
+
function loadProjectBundle(projectRoot, options, candidate) {
|
|
23408
|
+
const workflowCandidate = candidate?.kind === "workflow" ? candidate : void 0;
|
|
23409
|
+
const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : void 0;
|
|
23396
23410
|
assertNoRegisteredGates(options);
|
|
23397
23411
|
const root = resolve3(projectRoot);
|
|
23398
23412
|
const legacyPresent = legacyConfigFilesAt(root);
|
|
@@ -23415,7 +23429,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
|
|
|
23415
23429
|
earlyIssues.push(issue2("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
|
|
23416
23430
|
}
|
|
23417
23431
|
}
|
|
23418
|
-
const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((
|
|
23432
|
+
const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate2) => stringValue(objectValue(candidate2)?.id)).filter((candidate2) => candidate2 !== void 0));
|
|
23419
23433
|
for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits3)) {
|
|
23420
23434
|
if (!resourceIdentifier(repositoryId)) {
|
|
23421
23435
|
earlyIssues.push(issue2("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
|
|
@@ -23431,7 +23445,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
|
|
|
23431
23445
|
const { id, document } = workflowCandidate;
|
|
23432
23446
|
const logicalPath = `.kxm/workflows/${id}.yaml`;
|
|
23433
23447
|
if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
|
|
23434
|
-
const collision = [...workflows.keys()].find((
|
|
23448
|
+
const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
23435
23449
|
if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
|
|
23436
23450
|
workflows.set(id, { kind: "workflow", id, file: join6(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
|
|
23437
23451
|
}
|
|
@@ -23450,8 +23464,8 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
|
|
|
23450
23464
|
}
|
|
23451
23465
|
}
|
|
23452
23466
|
const seenBindings = /* @__PURE__ */ new Map();
|
|
23453
|
-
for (const
|
|
23454
|
-
const entry = objectValue(
|
|
23467
|
+
for (const candidate2 of valuesOf(project.value, "repositories")) {
|
|
23468
|
+
const entry = objectValue(candidate2);
|
|
23455
23469
|
const repositoryId = entry && stringValue(entry.id);
|
|
23456
23470
|
const pathHint = entry && stringValue(entry.pathHint);
|
|
23457
23471
|
const role = entry && stringValue(entry.role);
|
|
@@ -23548,7 +23562,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
|
|
|
23548
23562
|
}
|
|
23549
23563
|
}
|
|
23550
23564
|
if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
|
|
23551
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
|
|
23565
|
+
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
|
|
23552
23566
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
23553
23567
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
|
|
23554
23568
|
return {
|
|
@@ -23659,9 +23673,9 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
|
|
|
23659
23673
|
|
|
23660
23674
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
23661
23675
|
import { spawn as spawn2 } from "node:child_process";
|
|
23662
|
-
import { createHash as
|
|
23663
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
23664
|
-
import { dirname as dirname11, isAbsolute as isAbsolute7, join as
|
|
23676
|
+
import { createHash as createHash14, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
23677
|
+
import { chmodSync as chmodSync4, existsSync as existsSync17, lstatSync as lstatSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync15, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "node:fs";
|
|
23678
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join17 } from "node:path";
|
|
23665
23679
|
|
|
23666
23680
|
// plugins/kxm/src/runtime-store.ts
|
|
23667
23681
|
import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
@@ -25062,6 +25076,9 @@ var EVENT_STORE_TABLES = {
|
|
|
25062
25076
|
};
|
|
25063
25077
|
var KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
|
|
25064
25078
|
|
|
25079
|
+
// plugins/kxm/src/engine-plan.ts
|
|
25080
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
25081
|
+
|
|
25065
25082
|
// plugins/kxm/src/engine-compile.ts
|
|
25066
25083
|
var KXM_COMPILED_WORKFLOW_SCHEMA = "kxm.compiled-workflow.v1";
|
|
25067
25084
|
var WORKFLOW_SCHEMA = "kxm.workflow.v1";
|
|
@@ -25210,7 +25227,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
25210
25227
|
const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
|
|
25211
25228
|
const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
|
|
25212
25229
|
const assignments = compileAssignments(step, id, agent, sink);
|
|
25213
|
-
const
|
|
25230
|
+
const join52 = compileJoin(step, id, sink);
|
|
25214
25231
|
const requiredEvidence = compileEvidence(step, id, sink);
|
|
25215
25232
|
const transitions = compileTransitions(step, id, index, stepIndex, sink);
|
|
25216
25233
|
const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
|
|
@@ -25238,7 +25255,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
25238
25255
|
transitions: orderedTransitions,
|
|
25239
25256
|
requiresPlanHash: requirePlanHash.includes(id),
|
|
25240
25257
|
assignments,
|
|
25241
|
-
join:
|
|
25258
|
+
join: join52
|
|
25242
25259
|
};
|
|
25243
25260
|
if (kind === "agent" || kind === "moa") {
|
|
25244
25261
|
if (!agent) return void 0;
|
|
@@ -25288,15 +25305,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
|
|
|
25288
25305
|
};
|
|
25289
25306
|
}
|
|
25290
25307
|
function compileJoin(step, stepId, sink) {
|
|
25291
|
-
const
|
|
25292
|
-
if (!
|
|
25293
|
-
const declared = stringValue2(
|
|
25308
|
+
const join52 = objectValue2(step.join);
|
|
25309
|
+
if (!join52) return { strategy: "all" };
|
|
25310
|
+
const declared = stringValue2(join52.strategy);
|
|
25294
25311
|
const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
|
|
25295
|
-
const minimumPassed = compileOptionalCount(
|
|
25312
|
+
const minimumPassed = compileOptionalCount(join52.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
|
|
25296
25313
|
const compiled = {
|
|
25297
25314
|
strategy,
|
|
25298
25315
|
...minimumPassed !== void 0 ? { minimumPassed } : {},
|
|
25299
|
-
...typeof
|
|
25316
|
+
...typeof join52.cancelRemaining === "boolean" ? { cancelRemaining: join52.cancelRemaining } : {}
|
|
25300
25317
|
};
|
|
25301
25318
|
return compiled;
|
|
25302
25319
|
}
|
|
@@ -25565,7 +25582,110 @@ function pushIssue(sink, issue3) {
|
|
|
25565
25582
|
sink.issues.push({ workflowId: sink.workflowId, ...issue3 });
|
|
25566
25583
|
}
|
|
25567
25584
|
|
|
25585
|
+
// plugins/kxm/src/engine-plan.ts
|
|
25586
|
+
function kxmSha256(input) {
|
|
25587
|
+
return `sha256:${createHash11("sha256").update(input, "utf8").digest("hex")}`;
|
|
25588
|
+
}
|
|
25589
|
+
function parseGateDefinition(raw, gateId, runId) {
|
|
25590
|
+
const value = asObject(raw, runId, `gates.definitions.${gateId}`);
|
|
25591
|
+
const kind = asString(value.kind, runId, `gates.definitions.${gateId}.kind`);
|
|
25592
|
+
if (kind === "command") {
|
|
25593
|
+
assertExactKeys(value, ["kind", "argv", "timeoutMs"], ["cwd"], runId, `gates.definitions.${gateId}`);
|
|
25594
|
+
const argv = asStringArray(value.argv, runId, `gates.definitions.${gateId}.argv`);
|
|
25595
|
+
if (argv.length < 1 || argv.length > 64) {
|
|
25596
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.argv length is invalid`);
|
|
25597
|
+
}
|
|
25598
|
+
for (const [index, entry] of argv.entries()) {
|
|
25599
|
+
if (entry.length === 0 || entry.length > 4096 || entry.includes("\0")) {
|
|
25600
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.argv[${index}] is invalid`);
|
|
25601
|
+
}
|
|
25602
|
+
}
|
|
25603
|
+
const executable = argv[0];
|
|
25604
|
+
if (executable === "." || executable === ".." || executable.includes("\\") || !executable.startsWith("/") && executable.includes("/")) {
|
|
25605
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId} argv[0] must be a bare executable or absolute POSIX path`);
|
|
25606
|
+
}
|
|
25607
|
+
const timeoutMs = value.timeoutMs;
|
|
25608
|
+
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647) {
|
|
25609
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.timeoutMs is invalid`);
|
|
25610
|
+
}
|
|
25611
|
+
if (value.cwd !== void 0 && value.cwd !== "control") {
|
|
25612
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.cwd is invalid`);
|
|
25613
|
+
}
|
|
25614
|
+
return {
|
|
25615
|
+
kind: "command",
|
|
25616
|
+
argv,
|
|
25617
|
+
timeoutMs,
|
|
25618
|
+
...value.cwd === "control" ? { cwd: "control" } : {}
|
|
25619
|
+
};
|
|
25620
|
+
}
|
|
25621
|
+
if (kind === "artifacts-exist") {
|
|
25622
|
+
assertExactKeys(value, ["kind", "paths"], [], runId, `gates.definitions.${gateId}`);
|
|
25623
|
+
const paths = asStringArray(value.paths, runId, `gates.definitions.${gateId}.paths`);
|
|
25624
|
+
if (paths.length < 1 || paths.length > 64) {
|
|
25625
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.paths length is invalid`);
|
|
25626
|
+
}
|
|
25627
|
+
if (new Set(paths).size !== paths.length) {
|
|
25628
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId}.paths are not unique`);
|
|
25629
|
+
}
|
|
25630
|
+
for (const path4 of paths) {
|
|
25631
|
+
if (path4 === "." || !kxmPortablePath(path4) || path4.startsWith("/") || path4.includes("\\")) {
|
|
25632
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId} path is not a portable relative path`);
|
|
25633
|
+
}
|
|
25634
|
+
}
|
|
25635
|
+
return { kind: "artifacts-exist", paths };
|
|
25636
|
+
}
|
|
25637
|
+
if (kind === "reserved") {
|
|
25638
|
+
assertExactKeys(value, ["kind"], [], runId, `gates.definitions.${gateId}`);
|
|
25639
|
+
return { kind: "reserved" };
|
|
25640
|
+
}
|
|
25641
|
+
throw runtimeError("run_plan_corrupt", runId, `gates.definitions.${gateId} kind is invalid`);
|
|
25642
|
+
}
|
|
25643
|
+
function asObject(value, runId, label) {
|
|
25644
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
25645
|
+
throw runtimeError("run_plan_corrupt", runId, `${label} is not an object`);
|
|
25646
|
+
}
|
|
25647
|
+
return value;
|
|
25648
|
+
}
|
|
25649
|
+
function assertExactKeys(value, required, optional, runId, label) {
|
|
25650
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
25651
|
+
for (const key of required) {
|
|
25652
|
+
if (!(key in value)) throw runtimeError("run_plan_corrupt", runId, `${label} is missing ${key}`);
|
|
25653
|
+
}
|
|
25654
|
+
for (const key of Object.keys(value)) {
|
|
25655
|
+
if (!allowed.has(key)) throw runtimeError("run_plan_corrupt", runId, `${label} has unexpected key ${key}`);
|
|
25656
|
+
}
|
|
25657
|
+
}
|
|
25658
|
+
function asString(value, runId, label) {
|
|
25659
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
25660
|
+
throw runtimeError("run_plan_corrupt", runId, `${label} is not a non-empty string`);
|
|
25661
|
+
}
|
|
25662
|
+
return value;
|
|
25663
|
+
}
|
|
25664
|
+
function asStringArray(value, runId, label) {
|
|
25665
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
25666
|
+
throw runtimeError("run_plan_corrupt", runId, `${label} is not a string array`);
|
|
25667
|
+
}
|
|
25668
|
+
return [...value];
|
|
25669
|
+
}
|
|
25670
|
+
|
|
25671
|
+
// plugins/kxm/src/gate-hash.ts
|
|
25672
|
+
function gateRegistryHash(registryValue) {
|
|
25673
|
+
return kxmSha256(kxmCanonicalJson(registryValue));
|
|
25674
|
+
}
|
|
25675
|
+
|
|
25568
25676
|
// plugins/kxm/src/runtime-service.ts
|
|
25677
|
+
function objectValue3(value) {
|
|
25678
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
25679
|
+
}
|
|
25680
|
+
function kxmProjectAdmissionLimits(bundle) {
|
|
25681
|
+
const limits = objectValue3(bundle.project.value.limits);
|
|
25682
|
+
const maxConcurrentRuns = typeof limits?.maxConcurrentRuns === "number" && Number.isInteger(limits.maxConcurrentRuns) && limits.maxConcurrentRuns >= 1 ? limits.maxConcurrentRuns : 1;
|
|
25683
|
+
return {
|
|
25684
|
+
maxConcurrentRuns,
|
|
25685
|
+
...typeof limits?.maxRunDurationMs === "number" ? { maxRunDurationMs: limits.maxRunDurationMs } : {},
|
|
25686
|
+
...typeof limits?.maxAgentTimeMs === "number" ? { maxAgentTimeMs: limits.maxAgentTimeMs } : {}
|
|
25687
|
+
};
|
|
25688
|
+
}
|
|
25569
25689
|
var RUNTIME_EPOCH_NS = process.hrtime.bigint();
|
|
25570
25690
|
|
|
25571
25691
|
// plugins/kxm/src/oneshot-evidence.ts
|
|
@@ -25575,7 +25695,7 @@ var RECORD_LIMIT = 16 * 1024 * 1024;
|
|
|
25575
25695
|
|
|
25576
25696
|
// plugins/kxm/src/prices.ts
|
|
25577
25697
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
25578
|
-
import { createHash as
|
|
25698
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
25579
25699
|
import { existsSync as existsSync11, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
25580
25700
|
import { join as join11 } from "node:path";
|
|
25581
25701
|
|
|
@@ -25638,7 +25758,7 @@ function hashPriceCatalog(catalog) {
|
|
|
25638
25758
|
}))
|
|
25639
25759
|
})).sort((a, b2) => a.id.localeCompare(b2.id))
|
|
25640
25760
|
};
|
|
25641
|
-
return
|
|
25761
|
+
return createHash12("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
|
|
25642
25762
|
}
|
|
25643
25763
|
function parsePriceCatalog(text) {
|
|
25644
25764
|
const parsed = (0, import_yaml3.parse)(text);
|
|
@@ -25776,6 +25896,8 @@ function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date())
|
|
|
25776
25896
|
|
|
25777
25897
|
// plugins/kxm/src/engine.ts
|
|
25778
25898
|
var import_yaml7 = __toESM(require_dist(), 1);
|
|
25899
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
25900
|
+
import { join as join15 } from "node:path";
|
|
25779
25901
|
|
|
25780
25902
|
// plugins/kxm/src/routes.ts
|
|
25781
25903
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
@@ -25835,6 +25957,10 @@ function listRoleBindings(root) {
|
|
|
25835
25957
|
}
|
|
25836
25958
|
return result;
|
|
25837
25959
|
}
|
|
25960
|
+
function isRouteAdmitted(root, modelId2) {
|
|
25961
|
+
const policy = loadRoutePolicy(root);
|
|
25962
|
+
return policy.admitted.includes(modelId2) && !policy.disabled.includes(modelId2);
|
|
25963
|
+
}
|
|
25838
25964
|
function listInventoryModels(root) {
|
|
25839
25965
|
const path4 = join12(root, ".kxm", "models", "inventory.yaml");
|
|
25840
25966
|
if (!existsSync12(path4)) return [];
|
|
@@ -26120,7 +26246,7 @@ function syncHarnessMemory(repoRoot, options = {}) {
|
|
|
26120
26246
|
|
|
26121
26247
|
// plugins/kxm/src/skills.ts
|
|
26122
26248
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
26123
|
-
import { createHash as
|
|
26249
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
26124
26250
|
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
26125
26251
|
import { dirname as dirname9, join as join14 } from "node:path";
|
|
26126
26252
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
@@ -26145,7 +26271,7 @@ var SkillLifecycleError = class extends Error {
|
|
|
26145
26271
|
}
|
|
26146
26272
|
};
|
|
26147
26273
|
function skillContentSha256(content) {
|
|
26148
|
-
return
|
|
26274
|
+
return createHash13("sha256").update(content, "utf8").digest("hex");
|
|
26149
26275
|
}
|
|
26150
26276
|
function skillIdFor(name, contentSha256) {
|
|
26151
26277
|
const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -27159,13 +27285,418 @@ function formatRoutingReport(report2, options = {}) {
|
|
|
27159
27285
|
}
|
|
27160
27286
|
|
|
27161
27287
|
// plugins/kxm/src/engine.ts
|
|
27288
|
+
function resolveProducerRoute(projectRoot, step, agentId) {
|
|
27289
|
+
let agentModel;
|
|
27290
|
+
const agentFile = join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
|
|
27291
|
+
if (existsSync15(agentFile)) {
|
|
27292
|
+
try {
|
|
27293
|
+
const parsed = (0, import_yaml7.parse)(readFileSync13(agentFile, "utf8"));
|
|
27294
|
+
if (typeof parsed?.model === "string") {
|
|
27295
|
+
agentModel = parsed.model;
|
|
27296
|
+
} else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
|
|
27297
|
+
const declared = parsed.model;
|
|
27298
|
+
const providerOk = typeof declared.provider === "string" && declared.provider.length > 0 && !declared.provider.includes("/");
|
|
27299
|
+
const modelOk = typeof declared.model === "string" && declared.model.length > 0 && !declared.model.startsWith("/") && !declared.model.endsWith("/");
|
|
27300
|
+
if (providerOk && modelOk) {
|
|
27301
|
+
agentModel = `${declared.provider}/${declared.model}`;
|
|
27302
|
+
} else {
|
|
27303
|
+
return {
|
|
27304
|
+
error: {
|
|
27305
|
+
reason: "step_unsupported",
|
|
27306
|
+
field: "model",
|
|
27307
|
+
detail: "producer_route_unsupported: invalid model declaration"
|
|
27308
|
+
}
|
|
27309
|
+
};
|
|
27310
|
+
}
|
|
27311
|
+
}
|
|
27312
|
+
} catch {
|
|
27313
|
+
}
|
|
27314
|
+
}
|
|
27315
|
+
let selector = typeof step.model === "string" ? step.model : agentModel;
|
|
27316
|
+
if (!selector) {
|
|
27317
|
+
if (agentId === "implementer") {
|
|
27318
|
+
selector = "xai/grok-4.6";
|
|
27319
|
+
}
|
|
27320
|
+
}
|
|
27321
|
+
if (!selector) {
|
|
27322
|
+
return {
|
|
27323
|
+
error: {
|
|
27324
|
+
reason: "step_unsupported",
|
|
27325
|
+
field: "model",
|
|
27326
|
+
detail: "producer_route_unsupported: agent or step has no model declared"
|
|
27327
|
+
}
|
|
27328
|
+
};
|
|
27329
|
+
}
|
|
27330
|
+
const slash = selector.indexOf("/");
|
|
27331
|
+
if (slash <= 0 || slash === selector.length - 1) {
|
|
27332
|
+
return {
|
|
27333
|
+
error: {
|
|
27334
|
+
reason: "step_unsupported",
|
|
27335
|
+
field: "model",
|
|
27336
|
+
detail: `producer_route_unsupported: invalid selector '${selector}'`
|
|
27337
|
+
}
|
|
27338
|
+
};
|
|
27339
|
+
}
|
|
27340
|
+
if (!isRouteAdmitted(projectRoot, selector)) {
|
|
27341
|
+
return {
|
|
27342
|
+
error: {
|
|
27343
|
+
reason: "step_unsupported",
|
|
27344
|
+
field: "model",
|
|
27345
|
+
detail: `producer_route_unsupported: model '${selector}' is not admitted`
|
|
27346
|
+
}
|
|
27347
|
+
};
|
|
27348
|
+
}
|
|
27349
|
+
const role = agentId === "implementer" ? "writer" : agentId;
|
|
27350
|
+
const roleBindings = listRoleBindings(projectRoot);
|
|
27351
|
+
const roster = roleBindings[role];
|
|
27352
|
+
if (roster && !roster.includes(selector)) {
|
|
27353
|
+
return {
|
|
27354
|
+
error: {
|
|
27355
|
+
reason: "step_unsupported",
|
|
27356
|
+
field: "model",
|
|
27357
|
+
detail: `producer_route_unsupported: model '${selector}' not in role '${role}' roster`
|
|
27358
|
+
}
|
|
27359
|
+
};
|
|
27360
|
+
}
|
|
27361
|
+
const provider = selector.slice(0, slash);
|
|
27362
|
+
const model = selector.slice(slash + 1);
|
|
27363
|
+
return { provider, model, selector };
|
|
27364
|
+
}
|
|
27162
27365
|
var ENGINE_ROUTING_METADATA_KEYS = /* @__PURE__ */ new Set(["workflowId", "askSha256", "objectiveSha256", "stepWrites"]);
|
|
27163
27366
|
var MAX_PRODUCER_ROUTING_METADATA_FIELDS = MAX_PROVIDER_METADATA_FIELDS - ENGINE_ROUTING_METADATA_KEYS.size;
|
|
27367
|
+
function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
|
|
27368
|
+
const workflow = bundle.workflows.get(workflowId);
|
|
27369
|
+
if (!workflow) throw runtimeError("run_workflow_unknown", workflowId, `workflow ${workflowId} does not exist in this project`);
|
|
27370
|
+
const plan = compileKxmWorkflow({ id: workflowId, value: workflow.value, logicalPath: workflow.logicalPath });
|
|
27371
|
+
const envelope = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
|
|
27372
|
+
const prerequisites = [];
|
|
27373
|
+
const limit = unsupportedLimit(envelope);
|
|
27374
|
+
if (limit) prerequisites.push(limit);
|
|
27375
|
+
const visited = /* @__PURE__ */ new Set();
|
|
27376
|
+
const pending = [plan.entryStepId];
|
|
27377
|
+
while (pending.length > 0) {
|
|
27378
|
+
const stepId = pending.pop();
|
|
27379
|
+
if (visited.has(stepId)) continue;
|
|
27380
|
+
visited.add(stepId);
|
|
27381
|
+
const step = plan.steps[stepId];
|
|
27382
|
+
for (const transition2 of Object.values(step.transitions)) {
|
|
27383
|
+
if (transition2.to === "step") pending.push(transition2.target);
|
|
27384
|
+
}
|
|
27385
|
+
const unsupported = step.kind === "gate" ? unsupportedGateStep(plan, step, envelope, { projectRoot }) ?? starterGatePrerequisite(step, envelope.gates, projectRoot) : unsupportedStep(plan, step, "oneshot");
|
|
27386
|
+
if (unsupported) {
|
|
27387
|
+
prerequisites.push({ ...unsupported, stepId });
|
|
27388
|
+
continue;
|
|
27389
|
+
}
|
|
27390
|
+
if (step.kind !== "agent" && step.kind !== "moa") continue;
|
|
27391
|
+
const agentIds = step.assignments.allowedAgents.length > 0 ? step.assignments.allowedAgents : [step.agent];
|
|
27392
|
+
for (const agentId of agentIds) {
|
|
27393
|
+
const agent = bundle.agents.get(agentId);
|
|
27394
|
+
const harness = String(agent?.value.harness ?? bundle.project.value.defaultHarness ?? "pi");
|
|
27395
|
+
const permission = Object.values(step.repositories).includes("write") ? "edit" : "read-only";
|
|
27396
|
+
if (!BUILTIN_HARNESSES.some((entry) => entry.id === harness && entry.oneShot) || !oneShotPermissionArgs(harness, permission)) {
|
|
27397
|
+
prerequisites.push({ reason: "step_unsupported", stepId, field: "harness", detail: `${agentId}: ${harness} has no audited ${permission} one-shot profile; use a supported workflow or execute this work directly in ${harness}, without substituting another harness` });
|
|
27398
|
+
continue;
|
|
27399
|
+
}
|
|
27400
|
+
const route = resolveProducerRoute(projectRoot, step, agentId);
|
|
27401
|
+
if ("error" in route) {
|
|
27402
|
+
prerequisites.push({ ...route.error, stepId, detail: `${agentId}: ${route.error.detail}; configure its model in .kxm/agents/${agentId}.yaml and admit the installed model with kxm routes admit --model <provider/model>` });
|
|
27403
|
+
continue;
|
|
27404
|
+
}
|
|
27405
|
+
const writeRefusal = unsupportedLiveWrite(projectRoot, step, agentId, route.selector, envelope.projectLimits.maxConcurrentRuns);
|
|
27406
|
+
if (writeRefusal) {
|
|
27407
|
+
prerequisites.push({ ...writeRefusal, stepId });
|
|
27408
|
+
continue;
|
|
27409
|
+
}
|
|
27410
|
+
const compatible = validateHarnessModelPair(harness, route);
|
|
27411
|
+
if (!compatible.valid) {
|
|
27412
|
+
prerequisites.push({ reason: "step_unsupported", stepId, field: "model", detail: `${agentId}: ${compatible.message ?? `${harness} cannot run ${route.selector}`}; configure a model supported by ${harness} in .kxm/agents/${agentId}.yaml` });
|
|
27413
|
+
}
|
|
27414
|
+
}
|
|
27415
|
+
}
|
|
27416
|
+
return prerequisites;
|
|
27417
|
+
}
|
|
27418
|
+
function starterGatePrerequisite(step, gates, projectRoot) {
|
|
27419
|
+
const definition = gates.definitions[step.gate];
|
|
27420
|
+
if (!definition || definition.kind !== "command" || definition.argv.length !== 2 || definition.argv[0] !== "npm" || definition.argv[1] !== "test") return void 0;
|
|
27421
|
+
let testScript;
|
|
27422
|
+
try {
|
|
27423
|
+
const manifest = JSON.parse(readFileSync13(join15(projectRoot, "package.json"), "utf8"));
|
|
27424
|
+
if (manifest && typeof manifest === "object" && "scripts" in manifest) {
|
|
27425
|
+
const scripts = manifest.scripts;
|
|
27426
|
+
if (scripts && typeof scripts === "object" && "test" in scripts) testScript = scripts.test;
|
|
27427
|
+
}
|
|
27428
|
+
} catch {
|
|
27429
|
+
}
|
|
27430
|
+
if (typeof testScript !== "string" || testScript.trim().length === 0) {
|
|
27431
|
+
return { reason: "gate_unsupported", field: `gates.${step.gate}.argv`, detail: `gate ${step.gate} runs npm test but this repository has no readable package.json with scripts.test; configure .kxm/gates.yaml gates.${step.gate}.argv for the repository's actual test runner before driving this workflow` };
|
|
27432
|
+
}
|
|
27433
|
+
return void 0;
|
|
27434
|
+
}
|
|
27435
|
+
function unsupportedLimit(envelope) {
|
|
27436
|
+
if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
|
|
27437
|
+
return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
27438
|
+
}
|
|
27439
|
+
if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
|
|
27440
|
+
return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
27441
|
+
}
|
|
27442
|
+
return void 0;
|
|
27443
|
+
}
|
|
27444
|
+
function unsupportedStep(plan, step, producerId) {
|
|
27445
|
+
if (step.kind === "gate") throw runtimeError("run_plan_corrupt", plan.workflowId, `unsupportedStep called on gate without envelope; use unsupportedGateStep instead`);
|
|
27446
|
+
if (step.kind !== "agent" && step.kind !== "moa" && step.kind !== "approval" && step.kind !== "wait") {
|
|
27447
|
+
return { reason: "step_unsupported", field: "kind", detail: `step kind ${step.kind} is not executed in this slice` };
|
|
27448
|
+
}
|
|
27449
|
+
if (step.assignments.maxAttemptsPerAssignment > 2) {
|
|
27450
|
+
return { reason: "step_unsupported", field: "assignments.maxAttemptsPerAssignment", detail: "only up to two physical attempts are supported in this slice" };
|
|
27451
|
+
}
|
|
27452
|
+
if (step.join.strategy !== "all" && step.join.strategy !== "all-settled") {
|
|
27453
|
+
return { reason: "step_unsupported", field: "join.strategy", detail: "only join all or all-settled is supported" };
|
|
27454
|
+
}
|
|
27455
|
+
if (step.join.strategy === "all" && step.join.minimumPassed !== void 0) {
|
|
27456
|
+
return { reason: "step_unsupported", field: "join.minimumPassed", detail: "minimumPassed requires join strategy all-settled" };
|
|
27457
|
+
}
|
|
27458
|
+
if (step.join.cancelRemaining) {
|
|
27459
|
+
return { reason: "step_unsupported", field: "join.cancelRemaining", detail: "cancelRemaining is not executed in this slice" };
|
|
27460
|
+
}
|
|
27461
|
+
if (step.assignments.distinctBy.some((d2) => d2 !== "provider")) {
|
|
27462
|
+
return { reason: "step_unsupported", field: "assignments.distinctBy", detail: "only distinctBy provider is supported in this slice" };
|
|
27463
|
+
}
|
|
27464
|
+
if (step.assignments.maxWriteRepositories !== void 0 && step.assignments.maxWriteRepositories > 1) {
|
|
27465
|
+
return { reason: "step_unsupported", field: "assignments.maxWriteRepositories", detail: "at most one write repository is supported in this slice" };
|
|
27466
|
+
}
|
|
27467
|
+
for (const evidence of step.requiredEvidence) {
|
|
27468
|
+
if (!evidence || !["artifact", "assignment-result", "gate", "approval", "receipt"].includes(evidence.kind)) {
|
|
27469
|
+
return { reason: "step_unsupported", field: "requiredEvidence", detail: `evidence kind '${evidence?.kind}' is not supported in this slice` };
|
|
27470
|
+
}
|
|
27471
|
+
}
|
|
27472
|
+
if (step.timeoutMs !== void 0 && step.timeoutMs <= 0) {
|
|
27473
|
+
return { reason: "step_unsupported", field: "timeoutMs", detail: "timeoutMs must be positive" };
|
|
27474
|
+
}
|
|
27475
|
+
if (step.safeSpeculation) return { reason: "step_unsupported", field: "safeSpeculation", detail: "speculation is not executed in this slice" };
|
|
27476
|
+
if (step.tools) return { reason: "step_unsupported", field: "tools", detail: "tools are not executed in this slice" };
|
|
27477
|
+
if (step.secrets.length > 0) return { reason: "step_unsupported", field: "secrets", detail: "secrets are not executed in this slice" };
|
|
27478
|
+
for (const [repoId, access] of Object.entries(step.repositories)) {
|
|
27479
|
+
if (access !== "read" && access !== "write" && access !== "none") {
|
|
27480
|
+
return { reason: "step_unsupported", field: "repositories", detail: `invalid repository access '${access}' on ${repoId}` };
|
|
27481
|
+
}
|
|
27482
|
+
}
|
|
27483
|
+
return void 0;
|
|
27484
|
+
}
|
|
27485
|
+
function readYamlRecord(path4) {
|
|
27486
|
+
if (!existsSync15(path4)) return void 0;
|
|
27487
|
+
try {
|
|
27488
|
+
const parsed = (0, import_yaml7.parse)(readFileSync13(path4, "utf8"));
|
|
27489
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
27490
|
+
} catch {
|
|
27491
|
+
return void 0;
|
|
27492
|
+
}
|
|
27493
|
+
return void 0;
|
|
27494
|
+
}
|
|
27495
|
+
function agentHarness(projectRoot, agentId) {
|
|
27496
|
+
const harness = readYamlRecord(join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`))?.harness;
|
|
27497
|
+
return typeof harness === "string" && harness.length > 0 ? harness : void 0;
|
|
27498
|
+
}
|
|
27499
|
+
function projectDefaultHarness(projectRoot) {
|
|
27500
|
+
const harness = readYamlRecord(join15(projectRoot, ".kxm", "project.yaml"))?.defaultHarness;
|
|
27501
|
+
return typeof harness === "string" && harness.length > 0 ? harness : "pi";
|
|
27502
|
+
}
|
|
27503
|
+
function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurrentRuns) {
|
|
27504
|
+
if (!Object.values(step.repositories).some((access) => access === "write")) return void 0;
|
|
27505
|
+
if (step.assignments.maximum !== 1) {
|
|
27506
|
+
return {
|
|
27507
|
+
reason: "step_unsupported",
|
|
27508
|
+
field: "assignments.maximum",
|
|
27509
|
+
detail: "live write steps run a single assignment; the checkout witness cannot attribute edits between writers"
|
|
27510
|
+
};
|
|
27511
|
+
}
|
|
27512
|
+
if (maxConcurrentRuns !== 1) {
|
|
27513
|
+
return {
|
|
27514
|
+
reason: "step_unsupported",
|
|
27515
|
+
field: "limits.maxConcurrentRuns",
|
|
27516
|
+
detail: "live write steps require limits.maxConcurrentRuns of 1; concurrent runs share one checkout"
|
|
27517
|
+
};
|
|
27518
|
+
}
|
|
27519
|
+
const harness = agentHarness(projectRoot, agentId) ?? projectDefaultHarness(projectRoot);
|
|
27520
|
+
if (!oneShotWriterArgs(harness)) {
|
|
27521
|
+
return {
|
|
27522
|
+
reason: "step_unsupported",
|
|
27523
|
+
field: "repositories",
|
|
27524
|
+
detail: `live write steps require an audited writer profile; ${harness} has none`
|
|
27525
|
+
};
|
|
27526
|
+
}
|
|
27527
|
+
const rosterPath = join15(projectRoot, ".kxm", "roster.yaml");
|
|
27528
|
+
if (!existsSync15(rosterPath)) return void 0;
|
|
27529
|
+
const roster = readYamlRecord(rosterPath);
|
|
27530
|
+
if (!roster || roster.schema !== "kxm.developer-roster.v1") {
|
|
27531
|
+
return { reason: "step_unsupported", field: "model", detail: "live write steps require a readable kxm.developer-roster.v1" };
|
|
27532
|
+
}
|
|
27533
|
+
const routes = roster.routes;
|
|
27534
|
+
const lineup = roster.lineup;
|
|
27535
|
+
const writerIds = lineup && typeof lineup === "object" && !Array.isArray(lineup) ? lineup.writer : void 0;
|
|
27536
|
+
if (!routes || typeof routes !== "object" || Array.isArray(routes) || !Array.isArray(writerIds)) {
|
|
27537
|
+
return { reason: "step_unsupported", field: "model", detail: "developer roster has no writer lineup" };
|
|
27538
|
+
}
|
|
27539
|
+
const allowed = writerIds.some((id) => {
|
|
27540
|
+
if (typeof id !== "string") return false;
|
|
27541
|
+
const route = routes[id];
|
|
27542
|
+
if (!route || typeof route !== "object" || Array.isArray(route)) return false;
|
|
27543
|
+
const record = route;
|
|
27544
|
+
if (record.harness !== harness || record.status !== "admitted") return false;
|
|
27545
|
+
if (!Array.isArray(record.permissions) || !record.permissions.includes("edit")) return false;
|
|
27546
|
+
const model = typeof record.model === "string" ? record.model : "";
|
|
27547
|
+
const vendor = typeof record.vendor === "string" ? record.vendor : "";
|
|
27548
|
+
return model === selector || vendor.length > 0 && `${vendor}/${model}` === selector;
|
|
27549
|
+
});
|
|
27550
|
+
if (!allowed) {
|
|
27551
|
+
return {
|
|
27552
|
+
reason: "step_unsupported",
|
|
27553
|
+
field: "model",
|
|
27554
|
+
detail: `live write route ${selector} on ${harness} is not on the developer roster writer lineup`
|
|
27555
|
+
};
|
|
27556
|
+
}
|
|
27557
|
+
return void 0;
|
|
27558
|
+
}
|
|
27559
|
+
function unsupportedGateStep(plan, step, envelope, context) {
|
|
27560
|
+
if (!step.gate) return { reason: "step_unsupported", field: "gate", detail: "gate step is missing gate id" };
|
|
27561
|
+
if (envelope.gates.registry === null) {
|
|
27562
|
+
return { reason: "step_unsupported", field: "gate", detail: `step ${step.id} refers to a gate but registry is null` };
|
|
27563
|
+
}
|
|
27564
|
+
const definition = envelope.gates.definitions[step.gate];
|
|
27565
|
+
if (!definition) {
|
|
27566
|
+
return { reason: "step_unsupported", field: "gate", detail: `gate definition ${step.gate} not found in registry` };
|
|
27567
|
+
}
|
|
27568
|
+
if (definition.kind === "reserved") {
|
|
27569
|
+
return { reason: "gate_unsupported", field: "gate", detail: `gate ${step.gate} is reserved and cannot be executed` };
|
|
27570
|
+
}
|
|
27571
|
+
if (step.assignments.allowedAgents && step.assignments.allowedAgents.length > 0) {
|
|
27572
|
+
return { reason: "step_unsupported", field: "assignments.allowedAgents", detail: "gate steps do not support allowedAgents" };
|
|
27573
|
+
}
|
|
27574
|
+
if (step.assignments.minimum !== 1 || step.assignments.target !== 1 || step.assignments.maximum !== 1) {
|
|
27575
|
+
return { reason: "step_unsupported", field: "assignments", detail: "gate steps require exactly 1 agent assignment" };
|
|
27576
|
+
}
|
|
27577
|
+
if (step.assignments.maxParallel !== 1) {
|
|
27578
|
+
return { reason: "step_unsupported", field: "assignments.maxParallel", detail: "gate steps require maxParallel of 1" };
|
|
27579
|
+
}
|
|
27580
|
+
if (step.assignments.maxAttemptsPerAssignment !== 1) {
|
|
27581
|
+
return { reason: "step_unsupported", field: "assignments.maxAttemptsPerAssignment", detail: "gate steps require maxAttemptsPerAssignment of 1" };
|
|
27582
|
+
}
|
|
27583
|
+
if (step.assignments.distinctBy && step.assignments.distinctBy.length > 0) {
|
|
27584
|
+
return { reason: "step_unsupported", field: "assignments.distinctBy", detail: "gate steps do not support distinctBy" };
|
|
27585
|
+
}
|
|
27586
|
+
if (step.assignments.maxWriteRepositories) {
|
|
27587
|
+
return { reason: "step_unsupported", field: "assignments.maxWriteRepositories", detail: "gate steps do not support maxWriteRepositories" };
|
|
27588
|
+
}
|
|
27589
|
+
if (step.join.strategy !== "all") {
|
|
27590
|
+
return { reason: "step_unsupported", field: "join", detail: `gate steps require join strategy 'all', not '${step.join.strategy}'` };
|
|
27591
|
+
}
|
|
27592
|
+
if (step.join.minimumPassed !== void 0) {
|
|
27593
|
+
return { reason: "step_unsupported", field: "join", detail: "gate steps do not support minimumPassed" };
|
|
27594
|
+
}
|
|
27595
|
+
if (step.join.cancelRemaining) {
|
|
27596
|
+
return { reason: "step_unsupported", field: "join", detail: "gate steps do not support cancelRemaining" };
|
|
27597
|
+
}
|
|
27598
|
+
for (const evidence of step.requiredEvidence) {
|
|
27599
|
+
if (!evidence || !["gate", "artifact", "receipt", "assignment-result", "approval"].includes(evidence.kind)) {
|
|
27600
|
+
return { reason: "step_unsupported", field: "requiredEvidence", detail: `gate steps do not support evidence kind '${evidence?.kind}'` };
|
|
27601
|
+
}
|
|
27602
|
+
}
|
|
27603
|
+
if (step.safeSpeculation) {
|
|
27604
|
+
return { reason: "step_unsupported", field: "safeSpeculation", detail: "gate steps do not support safeSpeculation" };
|
|
27605
|
+
}
|
|
27606
|
+
if (step.tools) {
|
|
27607
|
+
return { reason: "step_unsupported", field: "tools", detail: "gate steps do not support tools" };
|
|
27608
|
+
}
|
|
27609
|
+
if (step.secrets && step.secrets.length > 0) {
|
|
27610
|
+
return { reason: "step_unsupported", field: "secrets", detail: "gate steps do not support secrets" };
|
|
27611
|
+
}
|
|
27612
|
+
if (step.model) {
|
|
27613
|
+
return { reason: "step_unsupported", field: "model", detail: "gate steps do not support model" };
|
|
27614
|
+
}
|
|
27615
|
+
if (plan.reproOracle?.stageId === step.id) {
|
|
27616
|
+
return { reason: "step_unsupported", field: "reproOracle", detail: "gate steps cannot be repro oracle" };
|
|
27617
|
+
}
|
|
27618
|
+
if (plan.planHash?.stageId === step.id) {
|
|
27619
|
+
return { reason: "step_unsupported", field: "planHash", detail: "gate steps cannot be plan hash oracle" };
|
|
27620
|
+
}
|
|
27621
|
+
if (step.timeoutMs !== void 0) {
|
|
27622
|
+
if (definition.kind === "artifacts-exist") {
|
|
27623
|
+
return { reason: "step_unsupported", field: "timeoutMs", detail: "artifacts-exist gates do not support timeoutMs" };
|
|
27624
|
+
}
|
|
27625
|
+
if (definition.kind === "command" && definition.timeoutMs !== void 0 && step.timeoutMs < definition.timeoutMs) {
|
|
27626
|
+
return { reason: "step_unsupported", field: "timeoutMs", detail: "command step timeoutMs narrower than the registry timeout is refused" };
|
|
27627
|
+
}
|
|
27628
|
+
}
|
|
27629
|
+
const repositories = step.repositories;
|
|
27630
|
+
if (!repositories || Object.keys(repositories).length === 0) {
|
|
27631
|
+
return { reason: "step_unsupported", field: "repositories", detail: "gate steps require repository declarations" };
|
|
27632
|
+
}
|
|
27633
|
+
if (definition.kind === "artifacts-exist") {
|
|
27634
|
+
const controlAccess = repositories.control;
|
|
27635
|
+
if (controlAccess !== "read" && controlAccess !== "write") {
|
|
27636
|
+
return { reason: "step_unsupported", field: "repositories", detail: "artifacts-exist gates require control repository access as read or write" };
|
|
27637
|
+
}
|
|
27638
|
+
for (const [repoId, access] of Object.entries(repositories)) {
|
|
27639
|
+
if (repoId !== "control" && access !== "none") {
|
|
27640
|
+
return { reason: "step_unsupported", field: "repositories", detail: "artifacts-exist gates require other repositories to have access 'none'" };
|
|
27641
|
+
}
|
|
27642
|
+
}
|
|
27643
|
+
} else if (definition.kind === "command") {
|
|
27644
|
+
for (const [repoId, access] of Object.entries(repositories)) {
|
|
27645
|
+
if (access !== "read" && access !== "write" && access !== "none") {
|
|
27646
|
+
return { reason: "step_unsupported", field: "repositories", detail: `invalid repository access '${access}' on ${repoId}` };
|
|
27647
|
+
}
|
|
27648
|
+
}
|
|
27649
|
+
const hasWrite = Object.values(repositories).some((access) => access === "write");
|
|
27650
|
+
if (!hasWrite) {
|
|
27651
|
+
return { reason: "step_unsupported", field: "repositories", detail: "command gates require at least one repository with write access" };
|
|
27652
|
+
}
|
|
27653
|
+
}
|
|
27654
|
+
const expectPass = step.expect === "pass";
|
|
27655
|
+
if (expectPass) {
|
|
27656
|
+
if (!step.outcomes.includes("passed")) {
|
|
27657
|
+
return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects pass but missing required outcome passed; spell it as 'passed'" };
|
|
27658
|
+
}
|
|
27659
|
+
if (!step.outcomes.includes("implementation-failure") && !step.outcomes.includes("failed")) {
|
|
27660
|
+
return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects pass but missing required failure outcome; declare implementation-failure or failed" };
|
|
27661
|
+
}
|
|
27662
|
+
} else {
|
|
27663
|
+
if (!step.outcomes.includes("repro-missing")) {
|
|
27664
|
+
return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects fail but missing required outcome repro-missing; spell it as 'repro-missing'" };
|
|
27665
|
+
}
|
|
27666
|
+
if (!step.outcomes.includes("implementation-failure") && !step.outcomes.includes("failed")) {
|
|
27667
|
+
return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects fail but missing required failure outcome; declare implementation-failure or failed" };
|
|
27668
|
+
}
|
|
27669
|
+
}
|
|
27670
|
+
if (context && projectRuntimeKey(context.projectRoot) !== envelope.gates.controlRoot.projectKey) {
|
|
27671
|
+
throw runtimeError("run_owner_mismatch", plan.workflowId, `gate control root does not match project`);
|
|
27672
|
+
}
|
|
27673
|
+
if (definition.kind === "artifacts-exist" && step.expect === "fail") {
|
|
27674
|
+
return { reason: "step_unsupported", field: "expect", detail: "artifacts-exist gates cannot expect fail" };
|
|
27675
|
+
}
|
|
27676
|
+
return void 0;
|
|
27677
|
+
}
|
|
27678
|
+
function pinnedGatesForCompiledPlan(plan, bundle, projectRoot) {
|
|
27679
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
27680
|
+
for (const step of Object.values(plan.steps)) {
|
|
27681
|
+
if (step.kind === "gate") referenced.add(step.gate);
|
|
27682
|
+
}
|
|
27683
|
+
const definitions = /* @__PURE__ */ Object.create(null);
|
|
27684
|
+
const registryValue = bundle.gateRegistry?.value;
|
|
27685
|
+
const gatesObject = registryValue && typeof registryValue.gates === "object" && registryValue.gates && !Array.isArray(registryValue.gates) ? registryValue.gates : {};
|
|
27686
|
+
for (const gateId of referenced) {
|
|
27687
|
+
definitions[gateId] = parseGateDefinition(gatesObject[gateId], gateId, plan.workflowId);
|
|
27688
|
+
}
|
|
27689
|
+
return {
|
|
27690
|
+
registry: referenced.size > 0 && bundle.gateRegistry ? { schema: "kxm.gate-registry.v1", hash: gateRegistryHash(bundle.gateRegistry.value) } : bundle.gateRegistry && referenced.size === 0 ? { schema: "kxm.gate-registry.v1", hash: gateRegistryHash(bundle.gateRegistry.value) } : null,
|
|
27691
|
+
definitions,
|
|
27692
|
+
controlRoot: { repositoryId: "control", projectKey: projectRuntimeKey(projectRoot) }
|
|
27693
|
+
};
|
|
27694
|
+
}
|
|
27164
27695
|
|
|
27165
27696
|
// plugins/kxm/src/hub-binding.ts
|
|
27166
|
-
import { existsSync as
|
|
27697
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync14, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
27167
27698
|
import { homedir as homedir4 } from "node:os";
|
|
27168
|
-
import { dirname as dirname10, isAbsolute as isAbsolute6, join as
|
|
27699
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join16, resolve as resolve11 } from "node:path";
|
|
27169
27700
|
var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
27170
27701
|
var HUB_HEALTH_PROBE_MS = 300;
|
|
27171
27702
|
var HubBindingError = class extends Error {
|
|
@@ -27182,16 +27713,16 @@ function resolveUserStateRoot2(env) {
|
|
|
27182
27713
|
}
|
|
27183
27714
|
if (process.platform === "win32") {
|
|
27184
27715
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
27185
|
-
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData :
|
|
27716
|
+
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join16(homedir4(), "AppData", "Local");
|
|
27186
27717
|
return resolve11(base2, "KXM");
|
|
27187
27718
|
}
|
|
27188
27719
|
if (process.platform === "darwin") return resolve11(homedir4(), "Library", "Application Support", "KXM");
|
|
27189
27720
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
27190
|
-
const base = xdgState && isAbsolute6(xdgState) ? xdgState :
|
|
27721
|
+
const base = xdgState && isAbsolute6(xdgState) ? xdgState : join16(homedir4(), ".local", "state");
|
|
27191
27722
|
return resolve11(base, "kxm");
|
|
27192
27723
|
}
|
|
27193
27724
|
function hubBindingFile(env = process.env) {
|
|
27194
|
-
return
|
|
27725
|
+
return join16(resolveUserStateRoot2(env), "hub-binding.json");
|
|
27195
27726
|
}
|
|
27196
27727
|
function validateHubUrl(raw) {
|
|
27197
27728
|
let parsed;
|
|
@@ -27227,10 +27758,10 @@ function isAbortError(error) {
|
|
|
27227
27758
|
}
|
|
27228
27759
|
function readHubBinding(env = process.env) {
|
|
27229
27760
|
const file = hubBindingFile(env);
|
|
27230
|
-
if (!
|
|
27761
|
+
if (!existsSync16(file)) return void 0;
|
|
27231
27762
|
let parsed;
|
|
27232
27763
|
try {
|
|
27233
|
-
parsed = JSON.parse(
|
|
27764
|
+
parsed = JSON.parse(readFileSync14(file, "utf8"));
|
|
27234
27765
|
} catch {
|
|
27235
27766
|
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
27236
27767
|
}
|
|
@@ -27253,7 +27784,7 @@ function readHubBinding(env = process.env) {
|
|
|
27253
27784
|
function writeHubBinding(record, env = process.env) {
|
|
27254
27785
|
const file = hubBindingFile(env);
|
|
27255
27786
|
mkdirSync10(dirname10(file), { recursive: true, mode: 448 });
|
|
27256
|
-
const temporary =
|
|
27787
|
+
const temporary = join16(dirname10(file), `.hub-binding-${process.pid}.tmp`);
|
|
27257
27788
|
try {
|
|
27258
27789
|
writeFileSync11(temporary, `${JSON.stringify(record, null, 2)}
|
|
27259
27790
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -27302,7 +27833,7 @@ var DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
27302
27833
|
|
|
27303
27834
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
27304
27835
|
function kxmSupervisorTokenFile(paths) {
|
|
27305
|
-
return
|
|
27836
|
+
return join17(paths.runtimeDir, "supervisor.token");
|
|
27306
27837
|
}
|
|
27307
27838
|
function readKxmSupervisorToken(paths) {
|
|
27308
27839
|
const file = kxmSupervisorTokenFile(paths);
|
|
@@ -27311,7 +27842,7 @@ function readKxmSupervisorToken(paths) {
|
|
|
27311
27842
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
27312
27843
|
throw runtimeError("runtime_path_invalid", file, "supervisor token file must be a regular file, not a link");
|
|
27313
27844
|
}
|
|
27314
|
-
const token =
|
|
27845
|
+
const token = readFileSync15(file, "utf8").trim();
|
|
27315
27846
|
return token.length >= 32 ? token : void 0;
|
|
27316
27847
|
}
|
|
27317
27848
|
function processAlive(pid) {
|
|
@@ -27325,11 +27856,11 @@ function processAlive(pid) {
|
|
|
27325
27856
|
var HEARTBEAT_STALE_MS = 15e3;
|
|
27326
27857
|
var SUPERVISOR_ERROR_MAX_AGE_MS = 3e4;
|
|
27327
27858
|
function supervisorErrorFile(paths) {
|
|
27328
|
-
return
|
|
27859
|
+
return join17(paths.runtimeDir, "supervisor.error");
|
|
27329
27860
|
}
|
|
27330
27861
|
function clearSupervisorError(paths) {
|
|
27331
27862
|
const file = supervisorErrorFile(paths);
|
|
27332
|
-
if (
|
|
27863
|
+
if (existsSync17(file)) rmSync5(file, { force: true });
|
|
27333
27864
|
}
|
|
27334
27865
|
function readRecentSupervisorError(paths) {
|
|
27335
27866
|
const file = supervisorErrorFile(paths);
|
|
@@ -27338,7 +27869,7 @@ function readRecentSupervisorError(paths) {
|
|
|
27338
27869
|
const ageMs = Date.now() - stat.mtimeMs;
|
|
27339
27870
|
if (ageMs > SUPERVISOR_ERROR_MAX_AGE_MS) return void 0;
|
|
27340
27871
|
try {
|
|
27341
|
-
return
|
|
27872
|
+
return readFileSync15(file, "utf8").trim();
|
|
27342
27873
|
} catch {
|
|
27343
27874
|
return void 0;
|
|
27344
27875
|
}
|
|
@@ -27359,7 +27890,7 @@ function supervisorStatusOf(record) {
|
|
|
27359
27890
|
};
|
|
27360
27891
|
}
|
|
27361
27892
|
function kxmSupervisorStatus(paths, options = {}) {
|
|
27362
|
-
if (!
|
|
27893
|
+
if (!existsSync17(paths.registryDb)) return { running: false };
|
|
27363
27894
|
if (options.readOnly) {
|
|
27364
27895
|
const database = openReadOnlyDatabase(paths.registryDb);
|
|
27365
27896
|
try {
|
|
@@ -27425,7 +27956,7 @@ async function ensureKxmSupervisor(options = {}) {
|
|
|
27425
27956
|
throw runtimeError("runtime_supervisor_unreachable", paths.registryDb, `runtime supervisor pid ${status.pid} is registered as running but cannot be probed`);
|
|
27426
27957
|
}
|
|
27427
27958
|
clearSupervisorError(paths);
|
|
27428
|
-
const scriptPath =
|
|
27959
|
+
const scriptPath = join17(findKxmRepoRoot(import.meta.url), "scripts", "kxm-runtime-supervisor.mjs");
|
|
27429
27960
|
const spawnImpl = options.spawnImpl ?? ((script, env) => {
|
|
27430
27961
|
const child = spawn2(process.execPath, [script], {
|
|
27431
27962
|
detached: true,
|
|
@@ -27489,11 +28020,11 @@ function handoffSuffix(handoff) {
|
|
|
27489
28020
|
|
|
27490
28021
|
// plugins/kxm/src/cli/types.ts
|
|
27491
28022
|
import { spawn as spawn3 } from "node:child_process";
|
|
27492
|
-
import { join as
|
|
28023
|
+
import { join as join19, resolve as resolve12 } from "node:path";
|
|
27493
28024
|
|
|
27494
28025
|
// plugins/kxm/src/telemetry.ts
|
|
27495
|
-
import { appendFileSync, mkdirSync as mkdirSync12, readFileSync as
|
|
27496
|
-
import { dirname as dirname12, join as
|
|
28026
|
+
import { appendFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16 } from "node:fs";
|
|
28027
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
27497
28028
|
var TELEMETRY_SCHEMA = "kxm.telemetry.v1";
|
|
27498
28029
|
function inferImprovementTarget(input) {
|
|
27499
28030
|
const explicit = input.env?.KXM_IMPROVE_TARGET?.trim();
|
|
@@ -27508,7 +28039,7 @@ function appendTelemetry(path4, event) {
|
|
|
27508
28039
|
`, { encoding: "utf8", mode: 384 });
|
|
27509
28040
|
}
|
|
27510
28041
|
function telemetryPath(logsDir) {
|
|
27511
|
-
return
|
|
28042
|
+
return join18(logsDir, "telemetry.jsonl");
|
|
27512
28043
|
}
|
|
27513
28044
|
function makeTelemetryEvent(input) {
|
|
27514
28045
|
return {
|
|
@@ -27523,7 +28054,7 @@ function makeTelemetryEvent(input) {
|
|
|
27523
28054
|
function readRoutingRecords(path4) {
|
|
27524
28055
|
const records = [];
|
|
27525
28056
|
try {
|
|
27526
|
-
const raw =
|
|
28057
|
+
const raw = readFileSync16(path4, "utf8");
|
|
27527
28058
|
for (const line of raw.split(/\r?\n/)) {
|
|
27528
28059
|
if (!line.trim()) continue;
|
|
27529
28060
|
try {
|
|
@@ -27602,7 +28133,7 @@ function workerResult(worker, payload) {
|
|
|
27602
28133
|
// plugins/kxm/src/cli/types.ts
|
|
27603
28134
|
function spawnScript(scriptName, extraEnv = {}) {
|
|
27604
28135
|
return new Promise((resolveExit) => {
|
|
27605
|
-
const child = spawn3(process.execPath, [
|
|
28136
|
+
const child = spawn3(process.execPath, [join19(findKxmRepoRoot(import.meta.url), "scripts", scriptName)], {
|
|
27606
28137
|
stdio: "inherit",
|
|
27607
28138
|
env: { ...process.env, ...extraEnv }
|
|
27608
28139
|
});
|
|
@@ -27703,10 +28234,10 @@ function workspaceDirs(cwd, workspaceFlag, env) {
|
|
|
27703
28234
|
return {
|
|
27704
28235
|
workdir,
|
|
27705
28236
|
workspace,
|
|
27706
|
-
config: derive ?
|
|
27707
|
-
logs: derive ?
|
|
27708
|
-
assets: derive ?
|
|
27709
|
-
state: derive ?
|
|
28237
|
+
config: derive ? join19(workspace, "config") : resolve12(workdir, env.KXM_CONFIG_DIR?.trim() || join19(workspace, "config")),
|
|
28238
|
+
logs: derive ? join19(workspace, "logs") : resolve12(workdir, env.KXM_LOGS_DIR?.trim() || join19(workspace, "logs")),
|
|
28239
|
+
assets: derive ? join19(workspace, "assets") : resolve12(workdir, env.KXM_ASSETS_DIR?.trim() || join19(workspace, "assets")),
|
|
28240
|
+
state: derive ? join19(workspace, "state") : resolve12(workdir, env.KXM_STATE_DIR?.trim() || join19(workspace, "state"))
|
|
27710
28241
|
};
|
|
27711
28242
|
}
|
|
27712
28243
|
function maskEnvName(name) {
|
|
@@ -27774,20 +28305,20 @@ function workspaceEnv(runtime) {
|
|
|
27774
28305
|
// plugins/kxm/src/cli/roles.ts
|
|
27775
28306
|
var import_yaml10 = __toESM(require_dist(), 1);
|
|
27776
28307
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
27777
|
-
import { existsSync as
|
|
27778
|
-
import { join as
|
|
28308
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
|
|
28309
|
+
import { join as join22, resolve as resolve15 } from "node:path";
|
|
27779
28310
|
import { createInterface } from "node:readline";
|
|
27780
28311
|
|
|
27781
28312
|
// plugins/kxm/src/role.ts
|
|
27782
28313
|
var import_yaml9 = __toESM(require_dist(), 1);
|
|
27783
|
-
import { existsSync as
|
|
27784
|
-
import { join as
|
|
28314
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readdirSync as readdirSync6, readFileSync as readFileSync18, rmSync as rmSync6, writeFileSync as writeFileSync14 } from "node:fs";
|
|
28315
|
+
import { join as join21 } from "node:path";
|
|
27785
28316
|
|
|
27786
28317
|
// plugins/kxm/src/config.ts
|
|
27787
28318
|
var import_yaml8 = __toESM(require_dist(), 1);
|
|
27788
|
-
import { existsSync as
|
|
28319
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
|
|
27789
28320
|
import { homedir as homedir5 } from "node:os";
|
|
27790
|
-
import { dirname as dirname13, join as
|
|
28321
|
+
import { dirname as dirname13, join as join20, resolve as resolve13 } from "node:path";
|
|
27791
28322
|
var KXM_CONFIG_SCHEMA = "kxm.config.v1";
|
|
27792
28323
|
var IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
|
|
27793
28324
|
var DEFAULT_KXM_CONFIG = {
|
|
@@ -27842,7 +28373,7 @@ var DEFAULT_KXM_CONFIG = {
|
|
|
27842
28373
|
};
|
|
27843
28374
|
function userConfigDirectory(overrideDir) {
|
|
27844
28375
|
if (overrideDir) return resolve13(overrideDir);
|
|
27845
|
-
return resolve13(process.env.KXM_USER_CONFIG_DIR?.trim() ||
|
|
28376
|
+
return resolve13(process.env.KXM_USER_CONFIG_DIR?.trim() || join20(homedir5(), ".config", "kxm"));
|
|
27846
28377
|
}
|
|
27847
28378
|
function repoConfigDirectory(repoRoot) {
|
|
27848
28379
|
return resolve13(repoRoot, ".kxm");
|
|
@@ -27889,14 +28420,14 @@ function normalizeImprovementConfig(raw) {
|
|
|
27889
28420
|
}
|
|
27890
28421
|
function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
27891
28422
|
const userDir = userConfigDirectory(options.userConfigDir);
|
|
27892
|
-
const userConfigFile =
|
|
28423
|
+
const userConfigFile = join20(userDir, "config.yaml");
|
|
27893
28424
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
27894
|
-
const repoConfigFile =
|
|
28425
|
+
const repoConfigFile = join20(repoDir, "config.yaml");
|
|
27895
28426
|
let userRaw = {};
|
|
27896
28427
|
let userLoadedPath;
|
|
27897
|
-
if (
|
|
28428
|
+
if (existsSync18(userConfigFile)) {
|
|
27898
28429
|
try {
|
|
27899
|
-
const text =
|
|
28430
|
+
const text = readFileSync17(userConfigFile, "utf8");
|
|
27900
28431
|
userRaw = (0, import_yaml8.parse)(text) ?? {};
|
|
27901
28432
|
userLoadedPath = userConfigFile;
|
|
27902
28433
|
} catch (error) {
|
|
@@ -27905,9 +28436,9 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
27905
28436
|
}
|
|
27906
28437
|
let repoRaw = {};
|
|
27907
28438
|
let repoLoadedPath;
|
|
27908
|
-
if (
|
|
28439
|
+
if (existsSync18(repoConfigFile)) {
|
|
27909
28440
|
try {
|
|
27910
|
-
const text =
|
|
28441
|
+
const text = readFileSync17(repoConfigFile, "utf8");
|
|
27911
28442
|
repoRaw = (0, import_yaml8.parse)(text) ?? {};
|
|
27912
28443
|
repoLoadedPath = repoConfigFile;
|
|
27913
28444
|
} catch (error) {
|
|
@@ -27943,7 +28474,7 @@ function getKxmConfigValue(config, keyPath) {
|
|
|
27943
28474
|
return current;
|
|
27944
28475
|
}
|
|
27945
28476
|
function kxmConfigFileForScope(repoRoot, scope, userConfigDir) {
|
|
27946
|
-
return scope === "user" ?
|
|
28477
|
+
return scope === "user" ? join20(userConfigDirectory(userConfigDir), "config.yaml") : join20(repoConfigDirectory(repoRoot), "config.yaml");
|
|
27947
28478
|
}
|
|
27948
28479
|
function configKeyParts(keyPath) {
|
|
27949
28480
|
const parts = keyPath.split(".");
|
|
@@ -27954,9 +28485,9 @@ function configKeyParts(keyPath) {
|
|
|
27954
28485
|
}
|
|
27955
28486
|
function readConfigFile(targetFile2) {
|
|
27956
28487
|
let existing = {};
|
|
27957
|
-
if (
|
|
28488
|
+
if (existsSync18(targetFile2)) {
|
|
27958
28489
|
try {
|
|
27959
|
-
existing = (0, import_yaml8.parse)(
|
|
28490
|
+
existing = (0, import_yaml8.parse)(readFileSync17(targetFile2, "utf8")) ?? {};
|
|
27960
28491
|
} catch {
|
|
27961
28492
|
existing = {};
|
|
27962
28493
|
}
|
|
@@ -28074,21 +28605,21 @@ var DEFAULT_ROLES = {
|
|
|
28074
28605
|
};
|
|
28075
28606
|
function rolesDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
|
|
28076
28607
|
if (scope === "global") {
|
|
28077
|
-
return
|
|
28608
|
+
return join21(userConfigDirectory(userConfigDir), "roles");
|
|
28078
28609
|
}
|
|
28079
|
-
return
|
|
28610
|
+
return join21(repoConfigDirectory(repoRoot), "roles");
|
|
28080
28611
|
}
|
|
28081
28612
|
function ensureRolesDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
|
|
28082
28613
|
const dir = rolesDirectory(scope, repoRoot, userConfigDir);
|
|
28083
|
-
if (!
|
|
28614
|
+
if (!existsSync19(dir)) {
|
|
28084
28615
|
mkdirSync14(dir, { recursive: true });
|
|
28085
28616
|
}
|
|
28086
28617
|
return dir;
|
|
28087
28618
|
}
|
|
28088
28619
|
function parseRoleFile(filePath) {
|
|
28089
|
-
if (!
|
|
28620
|
+
if (!existsSync19(filePath)) return void 0;
|
|
28090
28621
|
try {
|
|
28091
|
-
const raw =
|
|
28622
|
+
const raw = readFileSync18(filePath, "utf8");
|
|
28092
28623
|
const parsed = (0, import_yaml9.parse)(raw);
|
|
28093
28624
|
if (!parsed || typeof parsed !== "object" || parsed.schema !== KXM_ROLE_SCHEMA) {
|
|
28094
28625
|
return void 0;
|
|
@@ -28115,10 +28646,10 @@ function listRoles(options = {}) {
|
|
|
28115
28646
|
const globalDir = rolesDirectory("global", repoRoot, options.userConfigDir);
|
|
28116
28647
|
const localDir = rolesDirectory("local", repoRoot, options.userConfigDir);
|
|
28117
28648
|
const localRoles = /* @__PURE__ */ new Map();
|
|
28118
|
-
if (scopeFilter !== "global" &&
|
|
28649
|
+
if (scopeFilter !== "global" && existsSync19(localDir)) {
|
|
28119
28650
|
for (const entry of readdirSync6(localDir)) {
|
|
28120
28651
|
if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
|
|
28121
|
-
const filePath =
|
|
28652
|
+
const filePath = join21(localDir, entry);
|
|
28122
28653
|
const parsed = parseRoleFile(filePath);
|
|
28123
28654
|
if (parsed) {
|
|
28124
28655
|
localRoles.set(parsed.id, { role: parsed, filePath });
|
|
@@ -28127,10 +28658,10 @@ function listRoles(options = {}) {
|
|
|
28127
28658
|
}
|
|
28128
28659
|
}
|
|
28129
28660
|
const globalRoles = /* @__PURE__ */ new Map();
|
|
28130
|
-
if (scopeFilter !== "local" &&
|
|
28661
|
+
if (scopeFilter !== "local" && existsSync19(globalDir)) {
|
|
28131
28662
|
for (const entry of readdirSync6(globalDir)) {
|
|
28132
28663
|
if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
|
|
28133
|
-
const filePath =
|
|
28664
|
+
const filePath = join21(globalDir, entry);
|
|
28134
28665
|
const parsed = parseRoleFile(filePath);
|
|
28135
28666
|
if (parsed) {
|
|
28136
28667
|
globalRoles.set(parsed.id, { role: parsed, filePath });
|
|
@@ -28178,13 +28709,13 @@ function getRole(roleId, options = {}) {
|
|
|
28178
28709
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
28179
28710
|
if (scope !== "global") {
|
|
28180
28711
|
const localDir = rolesDirectory("local", repoRoot, options.userConfigDir);
|
|
28181
|
-
const localFile =
|
|
28712
|
+
const localFile = join21(localDir, `${roleId}.yaml`);
|
|
28182
28713
|
const parsed = parseRoleFile(localFile);
|
|
28183
28714
|
if (parsed) return { role: parsed, scope: "local", filePath: localFile };
|
|
28184
28715
|
}
|
|
28185
28716
|
if (scope !== "local") {
|
|
28186
28717
|
const globalDir = rolesDirectory("global", repoRoot, options.userConfigDir);
|
|
28187
|
-
const globalFile =
|
|
28718
|
+
const globalFile = join21(globalDir, `${roleId}.yaml`);
|
|
28188
28719
|
const parsed = parseRoleFile(globalFile);
|
|
28189
28720
|
if (parsed) return { role: parsed, scope: "global", filePath: globalFile };
|
|
28190
28721
|
}
|
|
@@ -28194,8 +28725,8 @@ function addRole(role, options = {}) {
|
|
|
28194
28725
|
const scope = options.scope ?? "local";
|
|
28195
28726
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
28196
28727
|
const dir = options.dryRun ? rolesDirectory(scope, repoRoot, options.userConfigDir) : ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
|
|
28197
|
-
const filePath =
|
|
28198
|
-
if (
|
|
28728
|
+
const filePath = join21(dir, `${role.id}.yaml`);
|
|
28729
|
+
if (existsSync19(filePath) && !options.overwrite) {
|
|
28199
28730
|
throw new Error(`role_already_exists: role '${role.id}' already exists at ${filePath}`);
|
|
28200
28731
|
}
|
|
28201
28732
|
if (!options.dryRun) writeFileSync14(filePath, (0, import_yaml9.stringify)(role), "utf8");
|
|
@@ -28205,8 +28736,8 @@ function removeRole(roleId, options = {}) {
|
|
|
28205
28736
|
const scope = options.scope ?? "local";
|
|
28206
28737
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
28207
28738
|
const dir = rolesDirectory(scope, repoRoot, options.userConfigDir);
|
|
28208
|
-
const filePath =
|
|
28209
|
-
if (!
|
|
28739
|
+
const filePath = join21(dir, `${roleId}.yaml`);
|
|
28740
|
+
if (!existsSync19(filePath)) {
|
|
28210
28741
|
throw new Error(`role_not_found: role '${roleId}' not found in ${scope} directory (${filePath})`);
|
|
28211
28742
|
}
|
|
28212
28743
|
if (options.dryRun) return { id: roleId, removed: false, filePath, scope };
|
|
@@ -28228,7 +28759,7 @@ function modifyRole(roleId, updates, options = {}) {
|
|
|
28228
28759
|
const scope = options.scope ?? target.scope;
|
|
28229
28760
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
28230
28761
|
const dir = options.dryRun ? rolesDirectory(scope, repoRoot, options.userConfigDir) : ensureRolesDirectory(scope, repoRoot, options.userConfigDir);
|
|
28231
|
-
const filePath =
|
|
28762
|
+
const filePath = join21(dir, `${roleId}.yaml`);
|
|
28232
28763
|
if (!options.dryRun) writeFileSync14(filePath, (0, import_yaml9.stringify)(updated), "utf8");
|
|
28233
28764
|
return { id: roleId, role: updated, filePath, scope };
|
|
28234
28765
|
}
|
|
@@ -28277,18 +28808,18 @@ var DEFAULT_ROLE_SEATS = {
|
|
|
28277
28808
|
};
|
|
28278
28809
|
function findExistingRoleHostsFile(scope, repoRoot = process.cwd(), userConfigDir) {
|
|
28279
28810
|
const dir = scope === "global" ? userConfigDirectory(userConfigDir) : repoConfigDirectory(repoRoot);
|
|
28280
|
-
const yamlPath =
|
|
28281
|
-
const ymlPath =
|
|
28282
|
-
const jsonPath =
|
|
28283
|
-
if (
|
|
28284
|
-
if (
|
|
28285
|
-
if (
|
|
28811
|
+
const yamlPath = join21(dir, "role-hosts.yaml");
|
|
28812
|
+
const ymlPath = join21(dir, "role-hosts.yml");
|
|
28813
|
+
const jsonPath = join21(dir, "role-hosts.json");
|
|
28814
|
+
if (existsSync19(yamlPath)) return { filePath: yamlPath, exists: true, format: "yaml" };
|
|
28815
|
+
if (existsSync19(ymlPath)) return { filePath: ymlPath, exists: true, format: "yaml" };
|
|
28816
|
+
if (existsSync19(jsonPath)) return { filePath: jsonPath, exists: true, format: "json" };
|
|
28286
28817
|
return { filePath: yamlPath, exists: false, format: "yaml" };
|
|
28287
28818
|
}
|
|
28288
28819
|
function parseRoleHostsFile(filePath) {
|
|
28289
|
-
if (!
|
|
28820
|
+
if (!existsSync19(filePath)) return void 0;
|
|
28290
28821
|
try {
|
|
28291
|
-
const raw =
|
|
28822
|
+
const raw = readFileSync18(filePath, "utf8");
|
|
28292
28823
|
const parsed = (0, import_yaml9.parse)(raw);
|
|
28293
28824
|
if (!parsed || typeof parsed !== "object") return void 0;
|
|
28294
28825
|
if (parsed.schema && parsed.schema !== KXM_ROLE_HOSTS_SCHEMA) return void 0;
|
|
@@ -28361,9 +28892,9 @@ function saveRoleHostsConfig(config, options = {}) {
|
|
|
28361
28892
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
28362
28893
|
const dir = scope === "global" ? userConfigDirectory(options.userConfigDir) : repoConfigDirectory(repoRoot);
|
|
28363
28894
|
const format = options.format ?? "yaml";
|
|
28364
|
-
const filePath =
|
|
28895
|
+
const filePath = join21(dir, format === "json" ? "role-hosts.json" : "role-hosts.yaml");
|
|
28365
28896
|
if (options.dryRun) return { filePath, scope };
|
|
28366
|
-
if (!
|
|
28897
|
+
if (!existsSync19(dir)) {
|
|
28367
28898
|
mkdirSync14(dir, { recursive: true });
|
|
28368
28899
|
}
|
|
28369
28900
|
const payload = {
|
|
@@ -28551,6 +29082,7 @@ async function cmdRoleGet(runtime, roleId, options) {
|
|
|
28551
29082
|
}
|
|
28552
29083
|
async function cmdRoleAdd(runtime, roleId, options) {
|
|
28553
29084
|
const scope = options.scope ?? "local";
|
|
29085
|
+
let base;
|
|
28554
29086
|
if (!roleId || options.pick) {
|
|
28555
29087
|
const candidates = Object.values(DEFAULT_ROLES).map((r) => ({
|
|
28556
29088
|
id: r.id,
|
|
@@ -28561,8 +29093,9 @@ async function cmdRoleAdd(runtime, roleId, options) {
|
|
|
28561
29093
|
if (scope === "local") {
|
|
28562
29094
|
const globalRoles = listRoles({ scope: "global", userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
28563
29095
|
for (const gr of globalRoles) {
|
|
28564
|
-
|
|
28565
|
-
|
|
29096
|
+
const definition = parseRoleFile(gr.filePath);
|
|
29097
|
+
if (definition && !candidates.some((c) => c.id === gr.id)) {
|
|
29098
|
+
candidates.push({ id: gr.id, description: gr.description, label: "global", payload: definition });
|
|
28566
29099
|
}
|
|
28567
29100
|
}
|
|
28568
29101
|
}
|
|
@@ -28574,63 +29107,62 @@ async function cmdRoleAdd(runtime, roleId, options) {
|
|
|
28574
29107
|
}
|
|
28575
29108
|
} else {
|
|
28576
29109
|
roleId = picked.id;
|
|
28577
|
-
|
|
28578
|
-
const base = DEFAULT_ROLES[picked.id];
|
|
28579
|
-
const roleDef2 = {
|
|
28580
|
-
...base,
|
|
28581
|
-
description: options.description || base.description,
|
|
28582
|
-
skills: options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : base.skills,
|
|
28583
|
-
roster: options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : base.roster
|
|
28584
|
-
};
|
|
28585
|
-
try {
|
|
28586
|
-
const res = addRole(roleDef2, {
|
|
28587
|
-
scope,
|
|
28588
|
-
repoRoot: runtime.cwd,
|
|
28589
|
-
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
|
|
28590
|
-
overwrite: options.overwrite,
|
|
28591
|
-
dryRun: runtime.dryRun
|
|
28592
|
-
});
|
|
28593
|
-
if (runtime.dryRun) {
|
|
28594
|
-
printPlan(runtime, { command: "role add", roleId, ...res }, [{ action: "write", target: res.filePath }], `add role '${roleId}' to ${res.scope}`);
|
|
28595
|
-
return 0;
|
|
28596
|
-
}
|
|
28597
|
-
print(
|
|
28598
|
-
runtime.io,
|
|
28599
|
-
runtime.json,
|
|
28600
|
-
{ ok: true, command: "role add", roleId, ...res },
|
|
28601
|
-
`Added role '${roleId}' to ${res.scope} (${res.filePath})
|
|
28602
|
-
`
|
|
28603
|
-
);
|
|
28604
|
-
return 0;
|
|
28605
|
-
} catch (err) {
|
|
28606
|
-
runtime.io.stderr(`role add failed: ${err.message}
|
|
28607
|
-
`);
|
|
28608
|
-
return 1;
|
|
28609
|
-
}
|
|
28610
|
-
}
|
|
29110
|
+
base = picked.payload;
|
|
28611
29111
|
}
|
|
28612
29112
|
}
|
|
29113
|
+
const skills = options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
29114
|
+
const roster = options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : void 0;
|
|
28613
29115
|
let roleDef;
|
|
28614
29116
|
if (options.file) {
|
|
28615
29117
|
const filePath = resolve15(runtime.cwd, options.file);
|
|
28616
|
-
const content =
|
|
29118
|
+
const content = readFileSync19(filePath, "utf8");
|
|
28617
29119
|
roleDef = (0, import_yaml10.parse)(content);
|
|
28618
29120
|
roleDef.id = roleId;
|
|
29121
|
+
} else if (base) {
|
|
29122
|
+
roleDef = {
|
|
29123
|
+
...base,
|
|
29124
|
+
description: options.description || base.description,
|
|
29125
|
+
skills: skills ?? base.skills,
|
|
29126
|
+
roster: roster ?? base.roster
|
|
29127
|
+
};
|
|
28619
29128
|
} else {
|
|
28620
|
-
const skills = options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
28621
|
-
const roster = options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : [];
|
|
28622
29129
|
roleDef = {
|
|
28623
29130
|
schema: "kxm.role.v1",
|
|
28624
29131
|
id: roleId,
|
|
28625
29132
|
description: options.description || `Role ${roleId}`,
|
|
28626
|
-
skills,
|
|
28627
|
-
roster
|
|
29133
|
+
skills: skills ?? [],
|
|
29134
|
+
roster: roster ?? []
|
|
28628
29135
|
};
|
|
28629
29136
|
}
|
|
29137
|
+
let repoRoot = runtime.cwd;
|
|
29138
|
+
if (scope === "local") {
|
|
29139
|
+
const projectRoot = discoverKxmProjectRoot(runtime.cwd);
|
|
29140
|
+
if (!projectRoot) {
|
|
29141
|
+
print(
|
|
29142
|
+
runtime.io,
|
|
29143
|
+
runtime.json,
|
|
29144
|
+
{ ok: false, command: "role add", error: "project_not_found" },
|
|
29145
|
+
"role add failed: local roles belong to a KXM project; run kxm init at the repository root, or pass --scope global"
|
|
29146
|
+
);
|
|
29147
|
+
return 2;
|
|
29148
|
+
}
|
|
29149
|
+
const issues = kxmRoleWriteIssues(projectRoot, roleDef.id, (0, import_yaml10.stringify)(roleDef));
|
|
29150
|
+
if (issues.length > 0) {
|
|
29151
|
+
print(
|
|
29152
|
+
runtime.io,
|
|
29153
|
+
runtime.json,
|
|
29154
|
+
{ ok: false, command: "role add", error: "role_invalid", issues },
|
|
29155
|
+
`role add failed: with this role the project would not load, so nothing was written
|
|
29156
|
+
${issues.map((entry) => ` ${entry.file}: ${entry.code}: ${entry.message}`).join("\n")}`
|
|
29157
|
+
);
|
|
29158
|
+
return 2;
|
|
29159
|
+
}
|
|
29160
|
+
repoRoot = projectRoot;
|
|
29161
|
+
}
|
|
28630
29162
|
try {
|
|
28631
29163
|
const res = addRole(roleDef, {
|
|
28632
29164
|
scope,
|
|
28633
|
-
repoRoot
|
|
29165
|
+
repoRoot,
|
|
28634
29166
|
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
|
|
28635
29167
|
overwrite: options.overwrite,
|
|
28636
29168
|
dryRun: runtime.dryRun
|
|
@@ -28942,8 +29474,8 @@ async function cmdRoleResume(runtime, runId, ruling) {
|
|
|
28942
29474
|
return 1;
|
|
28943
29475
|
}
|
|
28944
29476
|
}
|
|
28945
|
-
const dbPath =
|
|
28946
|
-
if (
|
|
29477
|
+
const dbPath = join22(runtime.cwd, ".kxm", "state", "kxm.db");
|
|
29478
|
+
if (existsSync20(dbPath)) {
|
|
28947
29479
|
try {
|
|
28948
29480
|
const database = runtime.dryRun ? openReadOnlyDatabase(dbPath) : new DatabaseSync(dbPath);
|
|
28949
29481
|
try {
|
|
@@ -29015,8 +29547,8 @@ async function cmdRoleResume(runtime, runId, ruling) {
|
|
|
29015
29547
|
// plugins/kxm/src/cli/workflows.ts
|
|
29016
29548
|
var import_yaml12 = __toESM(require_dist(), 1);
|
|
29017
29549
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
29018
|
-
import { existsSync as
|
|
29019
|
-
import { join as
|
|
29550
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
|
|
29551
|
+
import { join as join24, resolve as resolve17 } from "node:path";
|
|
29020
29552
|
|
|
29021
29553
|
// plugins/kxm/src/retrospective.ts
|
|
29022
29554
|
import { mkdirSync as mkdirSync15, renameSync as renameSync6, writeFileSync as writeFileSync15 } from "node:fs";
|
|
@@ -29344,7 +29876,7 @@ async function watchGithubChecks(input) {
|
|
|
29344
29876
|
}
|
|
29345
29877
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
29346
29878
|
const now = input.now ?? Date.now;
|
|
29347
|
-
const sleep = input.sleep ?? ((ms) => new Promise((
|
|
29879
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve34) => setTimeout(resolve34, ms)));
|
|
29348
29880
|
const deadline = now() + input.timeoutMs;
|
|
29349
29881
|
const headers = {
|
|
29350
29882
|
authorization: `Bearer ${token}`,
|
|
@@ -29443,8 +29975,22 @@ async function watchGithubChecks(input) {
|
|
|
29443
29975
|
|
|
29444
29976
|
// plugins/kxm/src/workflow-manager.ts
|
|
29445
29977
|
var import_yaml11 = __toESM(require_dist(), 1);
|
|
29446
|
-
import { existsSync as
|
|
29447
|
-
import { join as
|
|
29978
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "node:fs";
|
|
29979
|
+
import { join as join23 } from "node:path";
|
|
29980
|
+
function assertWorkflowId(workflowId) {
|
|
29981
|
+
if (!kxmResourceIdentifier(workflowId)) {
|
|
29982
|
+
throw new Error(`workflow_id_invalid: '${workflowId}' must be a flat lowercase slug of at most 64 characters, starting with a letter and using letters, digits, single hyphens or underscores; paths and platform-reserved names are not allowed (use 'bug-fix', not 'software-engineering/bug-fix')`);
|
|
29983
|
+
}
|
|
29984
|
+
}
|
|
29985
|
+
var workflowSchemaRegistry;
|
|
29986
|
+
function workflowPayload(workflowId, content, filePath) {
|
|
29987
|
+
const payload = typeof content === "string" ? content : (0, import_yaml11.stringify)(content);
|
|
29988
|
+
const value = parseRestrictedYaml2(payload, filePath);
|
|
29989
|
+
const issues = (workflowSchemaRegistry ??= new KxmSchemaRegistry()).validate("workflow", value, filePath);
|
|
29990
|
+
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
29991
|
+
compileKxmWorkflow({ id: workflowId, value, logicalPath: filePath });
|
|
29992
|
+
return payload;
|
|
29993
|
+
}
|
|
29448
29994
|
var completed = () => ({ target: "$terminal", terminalStatus: "completed" });
|
|
29449
29995
|
var failed = () => ({ target: "$terminal", terminalStatus: "failed" });
|
|
29450
29996
|
function verifyStep(retryStep) {
|
|
@@ -29581,24 +30127,22 @@ function scaffoldWorkflowDefinition(description) {
|
|
|
29581
30127
|
var DEFAULT_WORKFLOW_TEMPLATE = WORKFLOW_TEMPLATES["implement-and-verify"];
|
|
29582
30128
|
function workflowsDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
|
|
29583
30129
|
if (scope === "global") {
|
|
29584
|
-
return
|
|
30130
|
+
return join23(userConfigDirectory(userConfigDir), "workflows");
|
|
29585
30131
|
}
|
|
29586
|
-
return
|
|
30132
|
+
return join23(repoConfigDirectory(repoRoot), "workflows");
|
|
29587
30133
|
}
|
|
29588
30134
|
function ensureWorkflowsDirectory(scope, repoRoot = process.cwd(), userConfigDir) {
|
|
29589
30135
|
const dir = workflowsDirectory(scope, repoRoot, userConfigDir);
|
|
29590
|
-
if (!
|
|
30136
|
+
if (!existsSync21(dir)) {
|
|
29591
30137
|
mkdirSync16(dir, { recursive: true });
|
|
29592
30138
|
}
|
|
29593
30139
|
return dir;
|
|
29594
30140
|
}
|
|
29595
30141
|
function parseWorkflowFile(filePath) {
|
|
29596
|
-
if (!
|
|
30142
|
+
if (!existsSync21(filePath)) return void 0;
|
|
29597
30143
|
try {
|
|
29598
|
-
const raw =
|
|
29599
|
-
|
|
29600
|
-
if (!parsed || typeof parsed !== "object") return void 0;
|
|
29601
|
-
return parsed;
|
|
30144
|
+
const raw = readFileSync20(filePath, "utf8");
|
|
30145
|
+
return parseRestrictedYaml2(raw, filePath);
|
|
29602
30146
|
} catch {
|
|
29603
30147
|
return void 0;
|
|
29604
30148
|
}
|
|
@@ -29609,26 +30153,26 @@ function listWorkflowDefinitions(options = {}) {
|
|
|
29609
30153
|
const globalDir = workflowsDirectory("global", repoRoot, options.userConfigDir);
|
|
29610
30154
|
const localDir = workflowsDirectory("local", repoRoot, options.userConfigDir);
|
|
29611
30155
|
const localDefs = /* @__PURE__ */ new Map();
|
|
29612
|
-
if (scopeFilter !== "global" &&
|
|
30156
|
+
if (scopeFilter !== "global" && existsSync21(localDir)) {
|
|
29613
30157
|
for (const entry of readdirSync7(localDir)) {
|
|
29614
|
-
|
|
29615
|
-
|
|
30158
|
+
const id = entry.endsWith(".yaml") ? entry.slice(0, -5) : entry.endsWith(".yml") ? entry.slice(0, -4) : void 0;
|
|
30159
|
+
if (id && kxmResourceIdentifier(id)) {
|
|
30160
|
+
const filePath = join23(localDir, entry);
|
|
29616
30161
|
const def = parseWorkflowFile(filePath);
|
|
29617
30162
|
if (def) {
|
|
29618
|
-
const id = def.id || entry.replace(/\.ya?ml$/i, "");
|
|
29619
30163
|
localDefs.set(id, { def, filePath });
|
|
29620
30164
|
}
|
|
29621
30165
|
}
|
|
29622
30166
|
}
|
|
29623
30167
|
}
|
|
29624
30168
|
const globalDefs = /* @__PURE__ */ new Map();
|
|
29625
|
-
if (scopeFilter !== "local" &&
|
|
30169
|
+
if (scopeFilter !== "local" && existsSync21(globalDir)) {
|
|
29626
30170
|
for (const entry of readdirSync7(globalDir)) {
|
|
29627
|
-
|
|
29628
|
-
|
|
30171
|
+
const id = entry.endsWith(".yaml") ? entry.slice(0, -5) : entry.endsWith(".yml") ? entry.slice(0, -4) : void 0;
|
|
30172
|
+
if (id && kxmResourceIdentifier(id)) {
|
|
30173
|
+
const filePath = join23(globalDir, entry);
|
|
29629
30174
|
const def = parseWorkflowFile(filePath);
|
|
29630
30175
|
if (def) {
|
|
29631
|
-
const id = def.id || entry.replace(/\.ya?ml$/i, "");
|
|
29632
30176
|
globalDefs.set(id, { def, filePath });
|
|
29633
30177
|
}
|
|
29634
30178
|
}
|
|
@@ -29639,7 +30183,7 @@ function listWorkflowDefinitions(options = {}) {
|
|
|
29639
30183
|
const steps = Array.isArray(def.steps) ? def.steps : [];
|
|
29640
30184
|
for (const step of steps) {
|
|
29641
30185
|
if (step && typeof step === "object") {
|
|
29642
|
-
const role = step.
|
|
30186
|
+
const role = step.agent;
|
|
29643
30187
|
if (typeof role === "string" && !roles.includes(role)) {
|
|
29644
30188
|
roles.push(role);
|
|
29645
30189
|
}
|
|
@@ -29677,40 +30221,46 @@ function listWorkflowDefinitions(options = {}) {
|
|
|
29677
30221
|
return result;
|
|
29678
30222
|
}
|
|
29679
30223
|
function getWorkflowDefinition(workflowId, options = {}) {
|
|
30224
|
+
assertWorkflowId(workflowId);
|
|
29680
30225
|
const scope = options.scope ?? "all";
|
|
29681
30226
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
29682
30227
|
if (scope !== "global") {
|
|
29683
30228
|
const localDir = workflowsDirectory("local", repoRoot, options.userConfigDir);
|
|
29684
|
-
const localFile =
|
|
30229
|
+
const localFile = join23(localDir, `${workflowId}.yaml`);
|
|
29685
30230
|
const parsed = parseWorkflowFile(localFile);
|
|
29686
30231
|
if (parsed) return { workflow: parsed, scope: "local", filePath: localFile };
|
|
29687
30232
|
}
|
|
29688
30233
|
if (scope !== "local") {
|
|
29689
30234
|
const globalDir = workflowsDirectory("global", repoRoot, options.userConfigDir);
|
|
29690
|
-
const globalFile =
|
|
30235
|
+
const globalFile = join23(globalDir, `${workflowId}.yaml`);
|
|
29691
30236
|
const parsed = parseWorkflowFile(globalFile);
|
|
29692
30237
|
if (parsed) return { workflow: parsed, scope: "global", filePath: globalFile };
|
|
29693
30238
|
}
|
|
29694
30239
|
return void 0;
|
|
29695
30240
|
}
|
|
29696
30241
|
function addWorkflowDefinition(workflowId, content = DEFAULT_WORKFLOW_TEMPLATE, options = {}) {
|
|
30242
|
+
assertWorkflowId(workflowId);
|
|
29697
30243
|
const scope = options.scope ?? "local";
|
|
29698
30244
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
29699
|
-
const dir =
|
|
29700
|
-
const filePath =
|
|
29701
|
-
if (
|
|
30245
|
+
const dir = workflowsDirectory(scope, repoRoot, options.userConfigDir);
|
|
30246
|
+
const filePath = join23(dir, `${workflowId}.yaml`);
|
|
30247
|
+
if (existsSync21(filePath) && !options.overwrite) {
|
|
29702
30248
|
throw new Error(`workflow_already_exists: workflow '${workflowId}' already exists at ${filePath}`);
|
|
29703
30249
|
}
|
|
29704
|
-
const payload =
|
|
29705
|
-
if (!options.dryRun)
|
|
30250
|
+
const payload = workflowPayload(workflowId, content, filePath);
|
|
30251
|
+
if (!options.dryRun) {
|
|
30252
|
+
ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
|
|
30253
|
+
writeFileSync16(filePath, payload, "utf8");
|
|
30254
|
+
}
|
|
29706
30255
|
return { id: workflowId, filePath, scope };
|
|
29707
30256
|
}
|
|
29708
30257
|
function removeWorkflowDefinition(workflowId, options = {}) {
|
|
30258
|
+
assertWorkflowId(workflowId);
|
|
29709
30259
|
const scope = options.scope ?? "local";
|
|
29710
30260
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
29711
30261
|
const dir = workflowsDirectory(scope, repoRoot, options.userConfigDir);
|
|
29712
|
-
const filePath =
|
|
29713
|
-
if (!
|
|
30262
|
+
const filePath = join23(dir, `${workflowId}.yaml`);
|
|
30263
|
+
if (!existsSync21(filePath)) {
|
|
29714
30264
|
throw new Error(`workflow_not_found: workflow '${workflowId}' not found in ${scope} directory (${filePath})`);
|
|
29715
30265
|
}
|
|
29716
30266
|
if (options.dryRun) return { id: workflowId, removed: false, filePath, scope };
|
|
@@ -29728,16 +30278,20 @@ function modifyWorkflowDefinition(workflowId, updates, options = {}) {
|
|
|
29728
30278
|
};
|
|
29729
30279
|
const scope = options.scope ?? target.scope;
|
|
29730
30280
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
29731
|
-
const dir =
|
|
29732
|
-
const filePath =
|
|
29733
|
-
|
|
30281
|
+
const dir = workflowsDirectory(scope, repoRoot, options.userConfigDir);
|
|
30282
|
+
const filePath = join23(dir, `${workflowId}.yaml`);
|
|
30283
|
+
const payload = workflowPayload(workflowId, updated, filePath);
|
|
30284
|
+
if (!options.dryRun) {
|
|
30285
|
+
ensureWorkflowsDirectory(scope, repoRoot, options.userConfigDir);
|
|
30286
|
+
writeFileSync16(filePath, payload, "utf8");
|
|
30287
|
+
}
|
|
29734
30288
|
return { id: workflowId, workflow: updated, filePath, scope };
|
|
29735
30289
|
}
|
|
29736
30290
|
|
|
29737
30291
|
// plugins/kxm/src/cli/workflows.ts
|
|
29738
30292
|
var CLI_NAME = "kxm";
|
|
29739
30293
|
function localWorkflowSnapshot(dataPath, runId) {
|
|
29740
|
-
if (!
|
|
30294
|
+
if (!existsSync22(dataPath)) throw new Error("state_database_not_found");
|
|
29741
30295
|
const database = openReadOnlyDatabase(dataPath);
|
|
29742
30296
|
try {
|
|
29743
30297
|
const rows = runId ? database.prepare("SELECT record FROM workflow_runs WHERE id = ?").all(runId) : database.prepare("SELECT record FROM workflow_runs ORDER BY rowid DESC LIMIT 200").all();
|
|
@@ -29817,7 +30371,7 @@ function activeWorkflowDefinition(runtime, definitionId) {
|
|
|
29817
30371
|
let raw;
|
|
29818
30372
|
if (file) {
|
|
29819
30373
|
try {
|
|
29820
|
-
raw =
|
|
30374
|
+
raw = readFileSync21(resolve17(runtime.cwd, file), "utf8");
|
|
29821
30375
|
} catch {
|
|
29822
30376
|
throw new Error("workflow definition file is unavailable");
|
|
29823
30377
|
}
|
|
@@ -29865,60 +30419,56 @@ async function cmdWorkflowDefinitions(runtime, options) {
|
|
|
29865
30419
|
}
|
|
29866
30420
|
async function cmdWorkflowAdd(runtime, workflowId, options) {
|
|
29867
30421
|
const scope = options.scope ?? "local";
|
|
29868
|
-
const refuse = (error,
|
|
29869
|
-
print(runtime.io, runtime.json, { ok: false, command: "workflow add", error }, `workflow add failed: ${
|
|
29870
|
-
return
|
|
30422
|
+
const refuse = (error, message, code = 2) => {
|
|
30423
|
+
print(runtime.io, runtime.json, { ok: false, command: "workflow add", error, message }, `workflow add failed: ${message}`);
|
|
30424
|
+
return code;
|
|
29871
30425
|
};
|
|
29872
|
-
|
|
29873
|
-
|
|
29874
|
-
if (options.
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
|
|
29881
|
-
|
|
29882
|
-
|
|
29883
|
-
|
|
29884
|
-
|
|
29885
|
-
id,
|
|
29886
|
-
|
|
29887
|
-
|
|
29888
|
-
|
|
29889
|
-
|
|
29890
|
-
|
|
29891
|
-
|
|
29892
|
-
|
|
29893
|
-
const
|
|
29894
|
-
|
|
29895
|
-
|
|
30426
|
+
try {
|
|
30427
|
+
let content;
|
|
30428
|
+
if (options.template !== void 0) {
|
|
30429
|
+
if (options.file !== void 0 || options.pick !== void 0) {
|
|
30430
|
+
return refuse("workflow_add_conflict", "--template cannot be combined with --file or --pick");
|
|
30431
|
+
}
|
|
30432
|
+
if (!workflowId) return refuse("workflow_id_required", "usage: kxm workflow add <workflowId> --template <name>");
|
|
30433
|
+
const template = Object.hasOwn(WORKFLOW_TEMPLATES, options.template) ? WORKFLOW_TEMPLATES[options.template] : void 0;
|
|
30434
|
+
if (!template) {
|
|
30435
|
+
return refuse("workflow_template_unknown", `unknown template ${options.template}; choose ${Object.keys(WORKFLOW_TEMPLATES).join(", ")}`);
|
|
30436
|
+
}
|
|
30437
|
+
content = { ...template, ...options.description !== void 0 ? { description: options.description } : {} };
|
|
30438
|
+
} else if (!workflowId || options.pick) {
|
|
30439
|
+
const candidates = Object.entries(WORKFLOW_TEMPLATES).map(([id, tmpl]) => ({
|
|
30440
|
+
id,
|
|
30441
|
+
description: String(tmpl.description ?? id),
|
|
30442
|
+
label: "template",
|
|
30443
|
+
payload: tmpl
|
|
30444
|
+
}));
|
|
30445
|
+
if (scope === "local") {
|
|
30446
|
+
const globalDefs = listWorkflowDefinitions({ scope: "global", userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
30447
|
+
for (const gd of globalDefs) {
|
|
30448
|
+
if (!candidates.some((c) => c.id === gd.id)) {
|
|
30449
|
+
candidates.push({ id: gd.id, description: gd.description, label: "global", payload: gd.filePath });
|
|
30450
|
+
}
|
|
29896
30451
|
}
|
|
29897
30452
|
}
|
|
29898
|
-
|
|
29899
|
-
|
|
29900
|
-
|
|
29901
|
-
if (!workflowId) {
|
|
29902
|
-
runtime.io.stderr("workflow add failed: missing workflowId or pick selection\n");
|
|
29903
|
-
return 1;
|
|
30453
|
+
const picked = await resolvePickItem(runtime.io, `Select a workflow template to add (${scope})`, candidates, options.pick, runtime.env);
|
|
30454
|
+
if (!picked) {
|
|
30455
|
+
return refuse("workflow_id_required", "provide a workflowId or select an available workflow with --pick <id>", 1);
|
|
29904
30456
|
}
|
|
29905
|
-
|
|
29906
|
-
|
|
29907
|
-
|
|
29908
|
-
content = {
|
|
29909
|
-
...picked.payload,
|
|
29910
|
-
|
|
29911
|
-
};
|
|
30457
|
+
workflowId ??= picked.id;
|
|
30458
|
+
if (options.file === void 0) {
|
|
30459
|
+
const pickedContent = typeof picked.payload === "string" ? readFileSync21(picked.payload, "utf8") : picked.payload;
|
|
30460
|
+
content = options.description !== void 0 ? {
|
|
30461
|
+
...typeof pickedContent === "string" ? parseRestrictedYaml2(pickedContent, String(picked.payload)) : pickedContent,
|
|
30462
|
+
description: options.description
|
|
30463
|
+
} : pickedContent;
|
|
29912
30464
|
}
|
|
29913
30465
|
}
|
|
29914
|
-
|
|
29915
|
-
|
|
29916
|
-
|
|
29917
|
-
|
|
29918
|
-
|
|
29919
|
-
|
|
29920
|
-
}
|
|
29921
|
-
try {
|
|
30466
|
+
if (options.file !== void 0) {
|
|
30467
|
+
const filePath = resolve17(runtime.cwd, options.file);
|
|
30468
|
+
content = readFileSync21(filePath, "utf8");
|
|
30469
|
+
} else if (!content) {
|
|
30470
|
+
content = scaffoldWorkflowDefinition(options.description || `Workflow ${workflowId}`);
|
|
30471
|
+
}
|
|
29922
30472
|
const document = typeof content === "string" ? content : (0, import_yaml12.stringify)(content);
|
|
29923
30473
|
let repoRoot = runtime.cwd;
|
|
29924
30474
|
if (scope === "local") {
|
|
@@ -29931,7 +30481,7 @@ async function cmdWorkflowAdd(runtime, workflowId, options) {
|
|
|
29931
30481
|
print(
|
|
29932
30482
|
runtime.io,
|
|
29933
30483
|
runtime.json,
|
|
29934
|
-
{ ok: false, command: "workflow add", error: "workflow_invalid", issues },
|
|
30484
|
+
{ ok: false, command: "workflow add", error: "workflow_invalid", message: "with this workflow the project would not load, so nothing was written", issues },
|
|
29935
30485
|
`workflow add failed: with this workflow the project would not load, so nothing was written
|
|
29936
30486
|
${issues.map((entry) => ` ${entry.file}: ${entry.code}: ${entry.message}`).join("\n")}`
|
|
29937
30487
|
);
|
|
@@ -29959,9 +30509,7 @@ ${issues.map((entry) => ` ${entry.file}: ${entry.code}: ${entry.message}`).join
|
|
|
29959
30509
|
);
|
|
29960
30510
|
return 0;
|
|
29961
30511
|
} catch (err) {
|
|
29962
|
-
|
|
29963
|
-
`);
|
|
29964
|
-
return 1;
|
|
30512
|
+
return refuse("workflow_add_failed", err instanceof Error ? err.message : String(err), 1);
|
|
29965
30513
|
}
|
|
29966
30514
|
}
|
|
29967
30515
|
async function cmdWorkflowRemove(runtime, workflowId, options) {
|
|
@@ -30093,7 +30641,7 @@ async function cmdWorkflowStart(runtime, definitionIdArg, options) {
|
|
|
30093
30641
|
}
|
|
30094
30642
|
let payload;
|
|
30095
30643
|
try {
|
|
30096
|
-
const raw = payloadFlag.startsWith("@") ?
|
|
30644
|
+
const raw = payloadFlag.startsWith("@") ? readFileSync21(resolve17(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
|
|
30097
30645
|
const value = JSON.parse(raw);
|
|
30098
30646
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("object required");
|
|
30099
30647
|
payload = value;
|
|
@@ -30160,7 +30708,7 @@ async function cmdWorkflowDegrade(runtime, runId, stageId, options) {
|
|
|
30160
30708
|
}
|
|
30161
30709
|
}
|
|
30162
30710
|
async function cmdWorkflowInspect(runtime, action, runId) {
|
|
30163
|
-
const dataPath =
|
|
30711
|
+
const dataPath = resolve17(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join24(runtime.dirs.state, "kxm.db"));
|
|
30164
30712
|
if (action === "get" && !runId) {
|
|
30165
30713
|
runtime.io.stderr(`Usage: ${CLI_NAME} workflow get <runId>
|
|
30166
30714
|
`);
|
|
@@ -30329,14 +30877,14 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
|
|
|
30329
30877
|
return 2;
|
|
30330
30878
|
}
|
|
30331
30879
|
const snapshotFlag = String(options.input || "");
|
|
30332
|
-
const snapshotPath = snapshotFlag ?
|
|
30880
|
+
const snapshotPath = snapshotFlag ? resolve17(runtime.cwd, snapshotFlag) : "";
|
|
30333
30881
|
let snapshot;
|
|
30334
30882
|
try {
|
|
30335
30883
|
if (snapshotPath) {
|
|
30336
|
-
if (!
|
|
30337
|
-
snapshot = JSON.parse(
|
|
30884
|
+
if (!existsSync22(snapshotPath)) throw new Error("snapshot_missing");
|
|
30885
|
+
snapshot = JSON.parse(readFileSync21(snapshotPath, "utf8"));
|
|
30338
30886
|
} else {
|
|
30339
|
-
const dataPath =
|
|
30887
|
+
const dataPath = resolve17(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join24(runtime.dirs.state, "kxm.db"));
|
|
30340
30888
|
const local = localWorkflowSnapshot(dataPath, runId);
|
|
30341
30889
|
if (!local.runs[0]) throw new Error("workflow_not_found");
|
|
30342
30890
|
snapshot = { run: local.runs[0], journal: local.journal };
|
|
@@ -30348,8 +30896,8 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
|
|
|
30348
30896
|
return 1;
|
|
30349
30897
|
}
|
|
30350
30898
|
const doc = buildRetrospective(snapshot.run, snapshot.journal);
|
|
30351
|
-
const outDir =
|
|
30352
|
-
const assetsRoot =
|
|
30899
|
+
const outDir = resolve17(runtime.cwd, String(options.outDir || join24(runtime.dirs.assets, "retrospectives")));
|
|
30900
|
+
const assetsRoot = resolve17(runtime.dirs.assets);
|
|
30353
30901
|
const assetsPrefix = `${assetsRoot}${process.platform === "win32" ? "\\" : "/"}`;
|
|
30354
30902
|
if (outDir !== assetsRoot && !outDir.startsWith(assetsPrefix)) {
|
|
30355
30903
|
print(runtime.io, runtime.json, { ok: false, command: "retrospective export", error: "output_outside_workspace_assets" }, "retrospectives must stay under the workspace assets directory");
|
|
@@ -30366,133 +30914,174 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
|
|
|
30366
30914
|
|
|
30367
30915
|
// plugins/kxm/src/cli/tasks.ts
|
|
30368
30916
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
30369
|
-
import { existsSync as
|
|
30370
|
-
import { basename as basename7, join as
|
|
30917
|
+
import { existsSync as existsSync28, readFileSync as readFileSync27, readdirSync as readdirSync10 } from "node:fs";
|
|
30918
|
+
import { basename as basename7, join as join32, resolve as resolve23 } from "node:path";
|
|
30371
30919
|
|
|
30372
30920
|
// plugins/kxm/src/suggest.ts
|
|
30373
30921
|
var WORKFLOW_PATTERNS = [
|
|
30374
30922
|
{
|
|
30375
|
-
id: "
|
|
30923
|
+
id: "bug-fix",
|
|
30924
|
+
template: "implement-and-verify",
|
|
30376
30925
|
area: "software-engineering",
|
|
30377
30926
|
keywords: ["fix", "bug", "flaky", "failure", "timeout", "error", "repro", "crash", "broken", "hang"],
|
|
30378
|
-
skills: ["kxm-workflow", "kxm-runs", "kxm-context-memory"]
|
|
30379
|
-
defaultCommand: (p) => `kxm run software-engineering/bug-fix "${p}"`
|
|
30927
|
+
skills: ["kxm-workflow", "kxm-runs", "kxm-context-memory"]
|
|
30380
30928
|
},
|
|
30381
30929
|
{
|
|
30382
|
-
id: "
|
|
30930
|
+
id: "feature-implementation",
|
|
30931
|
+
template: "implement-and-verify",
|
|
30383
30932
|
area: "software-engineering",
|
|
30384
30933
|
keywords: ["feature", "implement", "add", "build", "create", "develop", "support", "endpoint", "ui", "tui"],
|
|
30385
|
-
skills: ["kxm-workflow", "kxm-peer", "kxm-context-memory"]
|
|
30386
|
-
defaultCommand: (p) => `kxm run software-engineering/feature-implementation "${p}"`
|
|
30934
|
+
skills: ["kxm-workflow", "kxm-peer", "kxm-context-memory"]
|
|
30387
30935
|
},
|
|
30388
30936
|
{
|
|
30389
|
-
id: "
|
|
30937
|
+
id: "refactoring",
|
|
30938
|
+
template: "dual-critic-review",
|
|
30390
30939
|
area: "software-engineering",
|
|
30391
30940
|
keywords: ["refactor", "cleanup", "reorganize", "modularize", "deduplicate", "split", "simplify", "deprecate"],
|
|
30392
|
-
skills: ["kxm-workflow", "kxm-runs"]
|
|
30393
|
-
defaultCommand: (p) => `kxm run software-engineering/refactoring "${p}"`
|
|
30941
|
+
skills: ["kxm-workflow", "kxm-runs"]
|
|
30394
30942
|
},
|
|
30395
30943
|
{
|
|
30396
|
-
id: "
|
|
30944
|
+
id: "vulnerability-remediation",
|
|
30945
|
+
template: "dual-critic-review",
|
|
30397
30946
|
area: "security-reliability",
|
|
30398
30947
|
keywords: ["cve", "vulnerability", "security", "exploit", "sanitize", "leak", "secret", "injection", "redact", "auth"],
|
|
30399
|
-
skills: ["kxm-workflow", "kxm-definitions"]
|
|
30400
|
-
defaultCommand: (p) => `kxm run security-reliability/vulnerability-remediation "${p}"`
|
|
30948
|
+
skills: ["kxm-workflow", "kxm-definitions"]
|
|
30401
30949
|
},
|
|
30402
30950
|
{
|
|
30403
|
-
id: "
|
|
30951
|
+
id: "reliability-hardening",
|
|
30952
|
+
template: "dual-critic-review",
|
|
30404
30953
|
area: "security-reliability",
|
|
30405
30954
|
keywords: ["idempotency", "retry", "circuit-breaker", "cas", "lock", "concurrency", "deadlock", "race", "crash-recovery"],
|
|
30406
|
-
skills: ["kxm-workflow", "kxm-peer"]
|
|
30407
|
-
defaultCommand: (p) => `kxm run security-reliability/reliability-hardening "${p}"`
|
|
30955
|
+
skills: ["kxm-workflow", "kxm-peer"]
|
|
30408
30956
|
},
|
|
30409
30957
|
{
|
|
30410
|
-
id: "
|
|
30958
|
+
id: "pipeline-migration",
|
|
30959
|
+
template: "implement-and-verify",
|
|
30411
30960
|
area: "data-analytics",
|
|
30412
30961
|
keywords: ["database", "sqlite", "migration", "pipeline", "schema", "transform", "table", "wal", "foreign", "cascading"],
|
|
30413
|
-
skills: ["kxm-runs", "kxm-context-memory"]
|
|
30414
|
-
defaultCommand: (p) => `kxm run data-analytics/pipeline-migration "${p}"`
|
|
30962
|
+
skills: ["kxm-runs", "kxm-context-memory"]
|
|
30415
30963
|
},
|
|
30416
30964
|
{
|
|
30417
|
-
id: "
|
|
30965
|
+
id: "architecture-spike",
|
|
30966
|
+
template: "spec-and-plan",
|
|
30418
30967
|
area: "research-strategy",
|
|
30419
30968
|
keywords: ["spike", "investigate", "prototype", "research", "feasibility", "benchmark", "explore", "evaluate"],
|
|
30420
|
-
skills: ["kxm-session", "kxm-context-memory", "kxm-routing-improve"]
|
|
30421
|
-
defaultCommand: (p) => `kxm run research-strategy/architecture-spike "${p}"`
|
|
30969
|
+
skills: ["kxm-session", "kxm-context-memory", "kxm-routing-improve"]
|
|
30422
30970
|
}
|
|
30423
30971
|
];
|
|
30424
30972
|
function suggestWorkflowAndRoles(prompt, options = {}) {
|
|
30425
30973
|
const normalized = prompt.toLowerCase();
|
|
30426
30974
|
const words = normalized.split(/\W+/).filter(Boolean);
|
|
30427
|
-
|
|
30428
|
-
let
|
|
30429
|
-
|
|
30975
|
+
const claudeOnly = /\bclaude(?:\s+code)?(?:[\s-]+only|\s+exclusively)\b|\b(?:only|exclusively)(?:\s+(?:use|using|with|via))?\s+claude(?:\s+code)?\b/i.test(prompt);
|
|
30976
|
+
let bestPattern = WORKFLOW_PATTERNS[1];
|
|
30977
|
+
let bestScore = 0;
|
|
30978
|
+
const reasons = [];
|
|
30430
30979
|
for (const pattern of WORKFLOW_PATTERNS) {
|
|
30431
|
-
|
|
30432
|
-
|
|
30433
|
-
|
|
30434
|
-
if (words.includes(keyword) || normalized.includes(keyword)) {
|
|
30435
|
-
score += 1;
|
|
30436
|
-
matchedWords.push(keyword);
|
|
30437
|
-
}
|
|
30438
|
-
}
|
|
30439
|
-
if (score > bestScore) {
|
|
30440
|
-
bestScore = score;
|
|
30980
|
+
const matchedWords = pattern.keywords.filter((keyword) => words.includes(keyword) || normalized.includes(keyword));
|
|
30981
|
+
if (matchedWords.length > bestScore) {
|
|
30982
|
+
bestScore = matchedWords.length;
|
|
30441
30983
|
bestPattern = pattern;
|
|
30442
|
-
|
|
30443
|
-
|
|
30984
|
+
reasons.length = 0;
|
|
30985
|
+
reasons.push(`Matched keywords: ${matchedWords.join(", ")}`);
|
|
30444
30986
|
}
|
|
30445
30987
|
}
|
|
30446
|
-
if (bestScore
|
|
30447
|
-
|
|
30448
|
-
|
|
30449
|
-
|
|
30450
|
-
const
|
|
30451
|
-
(options.availableHarnesses ?? []).filter((h) => h.auth === "active" || h.auth === "ready" || h.auth === "configured").map((h) => h.harness.toLowerCase())
|
|
30452
|
-
);
|
|
30453
|
-
const hasClaude = activeHarnesses.size === 0 || activeHarnesses.has("claude");
|
|
30454
|
-
const hasGrok = activeHarnesses.size === 0 || activeHarnesses.has("grok");
|
|
30455
|
-
const hasCodex = activeHarnesses.size === 0 || activeHarnesses.has("codex");
|
|
30456
|
-
const planner = hasClaude ? { harness: "claude", model: "fable", role: "planner" } : { harness: "codex", model: "gpt-5.6-sol", role: "planner" };
|
|
30457
|
-
const writer = hasGrok ? { harness: "grok", model: "grok-4.6", role: "writer" } : activeHarnesses.has("agy") ? { harness: "agy", model: "gemini-2.5-pro", role: "writer" } : { harness: "pi", model: "qwen/qwen3-coder-plus", role: "writer" };
|
|
30458
|
-
const critics = [
|
|
30459
|
-
{ harness: "claude", model: "fable", role: "reviewer-arch" },
|
|
30460
|
-
{ harness: "codex", model: "gpt-5.6-sol", role: "reviewer-cli" }
|
|
30461
|
-
];
|
|
30462
|
-
return {
|
|
30988
|
+
if (bestScore === 0) reasons.push("Default fallback: general feature implementation workflow");
|
|
30989
|
+
if (claudeOnly) reasons.push("Explicit Claude-only constraint; other harnesses are excluded");
|
|
30990
|
+
const template = WORKFLOW_TEMPLATES[bestPattern.template];
|
|
30991
|
+
const steps = template.steps;
|
|
30992
|
+
const base = {
|
|
30463
30993
|
workflowId: bestPattern.id,
|
|
30994
|
+
template: bestPattern.template,
|
|
30464
30995
|
area: bestPattern.area,
|
|
30465
30996
|
confidence: bestScore > 0 ? Math.min(1, 0.5 + bestScore * 0.15) : 0.5,
|
|
30466
|
-
reasons
|
|
30997
|
+
reasons,
|
|
30467
30998
|
suggestedSkills: bestPattern.skills,
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
|
|
30999
|
+
suggestedCommand: `kxm workflow add ${bestPattern.id} --template ${bestPattern.template}`
|
|
31000
|
+
};
|
|
31001
|
+
const writeStep = steps.find((step) => step.kind === "agent" && Object.values(step.repositories ?? {}).includes("write"));
|
|
31002
|
+
const available = (options.availableHarnesses ?? []).filter((entry) => BUILTIN_HARNESS_IDS.includes(entry.id) && entry.detected && entry.authenticated === true && entry.dispatch?.supported === true && entry.dispatch.status === "yes" && (!claudeOnly || entry.id === "claude"));
|
|
31003
|
+
const selected = available.find((entry) => entry.id === "claude") ?? available[0];
|
|
31004
|
+
if (!selected) {
|
|
31005
|
+
const relevant = (options.availableHarnesses ?? []).filter((entry) => !claudeOnly || entry.id === "claude");
|
|
31006
|
+
const detail = relevant.map((entry) => `${entry.id}: ${entry.dispatch?.reason ?? (!entry.detected ? "not_detected" : entry.authenticated !== true ? "authentication_unverified" : "dispatch_unavailable")}`).join("; ");
|
|
31007
|
+
return {
|
|
31008
|
+
...base,
|
|
31009
|
+
roles: [],
|
|
31010
|
+
execution: {
|
|
31011
|
+
supported: false,
|
|
31012
|
+
error: "harness_unavailable",
|
|
31013
|
+
reason: `${claudeOnly ? "Claude-only execution requires Claude Code" : "Execution requires a supported harness"} detected, authenticated, and ready for dispatch. ${detail || "No verified harness inventory is available."}`,
|
|
31014
|
+
nextSteps: claudeOnly ? ["Install Claude Code if missing, authenticate with claude auth login, then confirm claude auth status and kxm harness list. Other harnesses will not be substituted."] : ["Install and authenticate a supported harness, then run kxm harness list to check detection, authentication, and dispatch readiness. Pi requires a configured model/provider auth context."]
|
|
31015
|
+
}
|
|
31016
|
+
};
|
|
31017
|
+
}
|
|
31018
|
+
if (writeStep && !oneShotWriterArgs(selected.id)) {
|
|
31019
|
+
return {
|
|
31020
|
+
...base,
|
|
31021
|
+
roles: [],
|
|
31022
|
+
execution: {
|
|
31023
|
+
supported: false,
|
|
31024
|
+
error: "live_write_unsupported",
|
|
31025
|
+
reason: `${bestPattern.template} requires repository writes at step ${writeStep.id}, but the selected ${selected.id} harness has no audited live writer profile. No other harness will be substituted.`,
|
|
31026
|
+
nextSteps: [
|
|
31027
|
+
`Run the implementation directly in ${claudeOnly ? "Claude Code" : selected.id} in the target worktree, and run the repository's actual verification command.`,
|
|
31028
|
+
`For read-only KXM planning, try kxm suggest "Investigate an architecture spike${claudeOnly ? " using Claude only" : ""}"; the shipped spec-and-plan template does not implement changes.`,
|
|
31029
|
+
"Installing a definition or creating a run does not execute it. Simulation is not evidence of a bug fix or workflow completion."
|
|
31030
|
+
]
|
|
31031
|
+
}
|
|
31032
|
+
};
|
|
31033
|
+
}
|
|
31034
|
+
const roles = [];
|
|
31035
|
+
for (const step of steps) {
|
|
31036
|
+
if (!step.agent) continue;
|
|
31037
|
+
const role = roles.find((entry) => entry.agent === step.agent);
|
|
31038
|
+
if (role) role.role += `, ${step.id}`;
|
|
31039
|
+
else roles.push({ agent: step.agent, harness: selected.id, role: step.id });
|
|
31040
|
+
}
|
|
31041
|
+
const shell = process.platform === "win32" ? "powershell" : "posix";
|
|
31042
|
+
const quotedPrompt = shell === "powershell" ? `'${prompt.replace(/['\u2018-\u201b]/g, "$&$&")}'` : `'${prompt.replace(/'/g, "'\\''")}'`;
|
|
31043
|
+
return {
|
|
31044
|
+
...base,
|
|
31045
|
+
roles,
|
|
31046
|
+
execution: {
|
|
31047
|
+
supported: true,
|
|
31048
|
+
prerequisites: [
|
|
31049
|
+
"Run kxm init in the target repository if needed, then install the exact template with the suggested command. If that workflow ID already exists, stop and review it; the commands below apply only to a newly installed template, not an existing definition with potentially different agents or permissions.",
|
|
31050
|
+
...roles.map((role) => `Set harness: ${role.harness} and model: <your authenticated compatible model selector> in .kxm/agents/${role.agent}.yaml. Admit that exact selector in .kxm/routes.yaml and ensure it is not disabled; this recommendation does not choose a model or change routing.`),
|
|
31051
|
+
...writeStep ? [
|
|
31052
|
+
`Keep each writer step at assignments.maximum: 1 and set limits.maxConcurrentRuns: 1 in .kxm/project.yaml; the live ${selected.id} writer profile requires a lone writer in the checkout.`,
|
|
31053
|
+
`If .kxm/roster.yaml exists, it must be readable kxm.developer-roster.v1 with a lineup.writer route matching harness: ${selected.id} and the selected writer model (model or vendor/model), status: admitted, and permissions containing edit.`,
|
|
31054
|
+
"Configure the test gate in .kxm/gates.yaml with kind: command and argv for this repository's actual verification command. A scaffold/example command or simulation is not evidence that the implementation works."
|
|
31055
|
+
] : [],
|
|
31056
|
+
`Validate the installed workflow with kxm gate validate --file .kxm/workflows/${bestPattern.id}.yaml and the project configuration with kxm init --dry-run before creating a run.`
|
|
31057
|
+
],
|
|
31058
|
+
shell,
|
|
31059
|
+
createCommand: `kxm run ${bestPattern.id} -- ${quotedPrompt}`,
|
|
31060
|
+
driveCommand: "kxm runs drive <runId> --wait",
|
|
31061
|
+
statusCommand: "kxm runs status <runId>",
|
|
31062
|
+
receiptCommand: "kxm runs receipt <runId>"
|
|
31063
|
+
}
|
|
30475
31064
|
};
|
|
30476
31065
|
}
|
|
30477
31066
|
|
|
30478
31067
|
// plugins/kxm/src/task-manager.ts
|
|
30479
31068
|
var import_yaml13 = __toESM(require_dist(), 1);
|
|
30480
31069
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
30481
|
-
import { existsSync as
|
|
30482
|
-
import { join as
|
|
31070
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync22, readdirSync as readdirSync8, writeFileSync as writeFileSync17 } from "node:fs";
|
|
31071
|
+
import { join as join25, resolve as resolve18 } from "node:path";
|
|
30483
31072
|
var GOAL_SCHEMA = "kxm.goal.v1";
|
|
30484
31073
|
var TASK_SCHEMA = "kxm.task.v1";
|
|
30485
31074
|
function goalsDirectory(repoRoot) {
|
|
30486
|
-
return
|
|
31075
|
+
return resolve18(repoRoot, ".kxm", "goals");
|
|
30487
31076
|
}
|
|
30488
31077
|
function tasksDirectory(repoRoot) {
|
|
30489
|
-
return
|
|
31078
|
+
return resolve18(repoRoot, ".kxm", "tasks");
|
|
30490
31079
|
}
|
|
30491
31080
|
function goalFilePath(repoRoot, goalId) {
|
|
30492
|
-
return
|
|
31081
|
+
return join25(goalsDirectory(repoRoot), `${goalId}.yaml`);
|
|
30493
31082
|
}
|
|
30494
31083
|
function taskFilePath(repoRoot, taskId) {
|
|
30495
|
-
return
|
|
31084
|
+
return join25(tasksDirectory(repoRoot), `${taskId}.yaml`);
|
|
30496
31085
|
}
|
|
30497
31086
|
function createGoal(repoRoot, input, options = {}) {
|
|
30498
31087
|
const id = `goal_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
|
|
@@ -30515,13 +31104,13 @@ function createGoal(repoRoot, input, options = {}) {
|
|
|
30515
31104
|
}
|
|
30516
31105
|
function listGoals(repoRoot) {
|
|
30517
31106
|
const dir = goalsDirectory(repoRoot);
|
|
30518
|
-
if (!
|
|
31107
|
+
if (!existsSync23(dir)) return [];
|
|
30519
31108
|
const goals = [];
|
|
30520
31109
|
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
30521
31110
|
for (const entry of entries) {
|
|
30522
31111
|
if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
|
|
30523
31112
|
try {
|
|
30524
|
-
const text =
|
|
31113
|
+
const text = readFileSync22(join25(dir, entry.name), "utf8");
|
|
30525
31114
|
const doc = (0, import_yaml13.parse)(text);
|
|
30526
31115
|
if (doc && doc.schema === GOAL_SCHEMA) goals.push(doc);
|
|
30527
31116
|
} catch {
|
|
@@ -30559,13 +31148,13 @@ function createTask(repoRoot, input, options = {}) {
|
|
|
30559
31148
|
}
|
|
30560
31149
|
function listTasks(repoRoot, options = {}) {
|
|
30561
31150
|
const dir = tasksDirectory(repoRoot);
|
|
30562
|
-
if (!
|
|
31151
|
+
if (!existsSync23(dir)) return [];
|
|
30563
31152
|
const tasks = [];
|
|
30564
31153
|
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
30565
31154
|
for (const entry of entries) {
|
|
30566
31155
|
if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
|
|
30567
31156
|
try {
|
|
30568
|
-
const text =
|
|
31157
|
+
const text = readFileSync22(join25(dir, entry.name), "utf8");
|
|
30569
31158
|
const doc = (0, import_yaml13.parse)(text);
|
|
30570
31159
|
if (doc && doc.schema === TASK_SCHEMA) {
|
|
30571
31160
|
if (options.goalId && doc.goalId !== options.goalId) continue;
|
|
@@ -30580,27 +31169,16 @@ function listTasks(repoRoot, options = {}) {
|
|
|
30580
31169
|
}
|
|
30581
31170
|
function getTask(repoRoot, taskId) {
|
|
30582
31171
|
const dir = tasksDirectory(repoRoot);
|
|
30583
|
-
const filePath =
|
|
30584
|
-
if (!
|
|
31172
|
+
const filePath = join25(dir, `${taskId}.yaml`);
|
|
31173
|
+
if (!existsSync23(filePath)) return void 0;
|
|
30585
31174
|
try {
|
|
30586
|
-
const text =
|
|
31175
|
+
const text = readFileSync22(filePath, "utf8");
|
|
30587
31176
|
const doc = (0, import_yaml13.parse)(text);
|
|
30588
31177
|
return doc && doc.schema === TASK_SCHEMA ? doc : void 0;
|
|
30589
31178
|
} catch {
|
|
30590
31179
|
return void 0;
|
|
30591
31180
|
}
|
|
30592
31181
|
}
|
|
30593
|
-
function updateTaskStatus(repoRoot, taskId, status, options = {}) {
|
|
30594
|
-
const task = getTask(repoRoot, taskId);
|
|
30595
|
-
if (!task) {
|
|
30596
|
-
throw new Error(`Task ${taskId} not found`);
|
|
30597
|
-
}
|
|
30598
|
-
task.status = status;
|
|
30599
|
-
if (options.workflowRunId) task.workflowRunId = options.workflowRunId;
|
|
30600
|
-
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30601
|
-
if (!options.dryRun) writeFileSync17(taskFilePath(repoRoot, taskId), (0, import_yaml13.stringify)(task).trim() + "\n", "utf8");
|
|
30602
|
-
return task;
|
|
30603
|
-
}
|
|
30604
31182
|
function syncTaskWithTracker(repoRoot, taskId, options = {}) {
|
|
30605
31183
|
const task = getTask(repoRoot, taskId);
|
|
30606
31184
|
if (!task) {
|
|
@@ -31380,13 +31958,13 @@ function createStudioServer(options = {}) {
|
|
|
31380
31958
|
}
|
|
31381
31959
|
|
|
31382
31960
|
// plugins/kxm/src/cli/project.ts
|
|
31383
|
-
import { existsSync as
|
|
31384
|
-
import { dirname as
|
|
31961
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
31962
|
+
import { dirname as dirname17, join as join31, resolve as resolve22 } from "node:path";
|
|
31385
31963
|
import { createInterface as createInterface2 } from "node:readline";
|
|
31386
31964
|
|
|
31387
31965
|
// plugins/kxm/src/hub-autostart.ts
|
|
31388
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync18, openSync as openSync2, readFileSync as
|
|
31389
|
-
import { join as
|
|
31966
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync18, openSync as openSync2, readFileSync as readFileSync23 } from "node:fs";
|
|
31967
|
+
import { join as join26, resolve as resolve19 } from "node:path";
|
|
31390
31968
|
function defaultProcessExists(pid) {
|
|
31391
31969
|
try {
|
|
31392
31970
|
process.kill(pid, 0);
|
|
@@ -31398,7 +31976,7 @@ function defaultProcessExists(pid) {
|
|
|
31398
31976
|
function readLiveHubClaim(stateDir, processExists3 = defaultProcessExists) {
|
|
31399
31977
|
let parsed;
|
|
31400
31978
|
try {
|
|
31401
|
-
parsed = JSON.parse(
|
|
31979
|
+
parsed = JSON.parse(readFileSync23(join26(stateDir, "hub.pid"), "utf8"));
|
|
31402
31980
|
} catch {
|
|
31403
31981
|
return void 0;
|
|
31404
31982
|
}
|
|
@@ -31416,7 +31994,7 @@ function readLiveHubClaim(stateDir, processExists3 = defaultProcessExists) {
|
|
|
31416
31994
|
// plugins/kxm/src/model-inventory.ts
|
|
31417
31995
|
var import_yaml14 = __toESM(require_dist(), 1);
|
|
31418
31996
|
import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync18 } from "node:fs";
|
|
31419
|
-
import { join as
|
|
31997
|
+
import { join as join27 } from "node:path";
|
|
31420
31998
|
var OR_URL = "https://openrouter.ai/api/v1/models";
|
|
31421
31999
|
var NOUS_URL = "https://inference-api.nousresearch.com/v1/models";
|
|
31422
32000
|
function price(value) {
|
|
@@ -31502,41 +32080,41 @@ async function refreshModelInventory(options) {
|
|
|
31502
32080
|
sources.nous = { url: nousUrl, ok: !nous.error, ...nous.error ? { error: nous.error } : {} };
|
|
31503
32081
|
for (const item of nous.data) add(item, "nous", "discount");
|
|
31504
32082
|
const inventory = { schema: "kxm.model-inventory.v1", fetchedAt, currency: "USD", sources, models: [...byId.values()].sort((a, b2) => a.id.localeCompare(b2.id)) };
|
|
31505
|
-
mkdirSync19(
|
|
31506
|
-
writeFileSync18(
|
|
32083
|
+
mkdirSync19(join27(options.outputRoot, ".kxm", "models"), { recursive: true });
|
|
32084
|
+
writeFileSync18(join27(options.outputRoot, ".kxm", "models", "inventory.yaml"), (0, import_yaml14.stringify)(inventory), "utf8");
|
|
31507
32085
|
return inventory;
|
|
31508
32086
|
}
|
|
31509
32087
|
|
|
31510
32088
|
// plugins/kxm/src/init.ts
|
|
31511
32089
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
31512
|
-
import { existsSync as
|
|
31513
|
-
import { basename as basename6, join as
|
|
32090
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
32091
|
+
import { basename as basename6, join as join30 } from "node:path";
|
|
31514
32092
|
|
|
31515
32093
|
// plugins/kxm/src/repair.ts
|
|
31516
32094
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
31517
32095
|
import {
|
|
31518
32096
|
chmodSync as chmodSync5,
|
|
31519
32097
|
closeSync as closeSync3,
|
|
31520
|
-
existsSync as
|
|
32098
|
+
existsSync as existsSync25,
|
|
31521
32099
|
fsyncSync as fsyncSync2,
|
|
31522
32100
|
linkSync,
|
|
31523
32101
|
lstatSync as lstatSync7,
|
|
31524
32102
|
mkdirSync as mkdirSync21,
|
|
31525
32103
|
openSync as openSync3,
|
|
31526
|
-
readFileSync as
|
|
32104
|
+
readFileSync as readFileSync25,
|
|
31527
32105
|
readdirSync as readdirSync9,
|
|
31528
32106
|
renameSync as renameSync7,
|
|
31529
32107
|
rmSync as rmSync9,
|
|
31530
32108
|
writeFileSync as writeFileSync20
|
|
31531
32109
|
} from "node:fs";
|
|
31532
|
-
import { dirname as
|
|
32110
|
+
import { dirname as dirname16, isAbsolute as isAbsolute8, join as join29, relative as relative5, resolve as resolve21 } from "node:path";
|
|
31533
32111
|
|
|
31534
32112
|
// plugins/kxm/src/permission.ts
|
|
31535
32113
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
31536
|
-
import { createHash as
|
|
31537
|
-
import { existsSync as
|
|
32114
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
32115
|
+
import { existsSync as existsSync24, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync24, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
|
|
31538
32116
|
import { tmpdir } from "node:os";
|
|
31539
|
-
import { dirname as
|
|
32117
|
+
import { dirname as dirname15, join as join28, resolve as resolve20, sep as sep4 } from "node:path";
|
|
31540
32118
|
var PROSE_FIELDS = {
|
|
31541
32119
|
project: /* @__PURE__ */ new Set(["name", "description"]),
|
|
31542
32120
|
repository: /* @__PURE__ */ new Set(["description"]),
|
|
@@ -31552,7 +32130,7 @@ var UNTRACKED_RANK = { "tracked-only": 0, ask: 1, bounded: 2 };
|
|
|
31552
32130
|
function canonical(value) {
|
|
31553
32131
|
return kxmCanonicalJson(value === void 0 ? null : value);
|
|
31554
32132
|
}
|
|
31555
|
-
function
|
|
32133
|
+
function asObject2(value) {
|
|
31556
32134
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
31557
32135
|
}
|
|
31558
32136
|
function valuesOf3(value, key) {
|
|
@@ -31568,12 +32146,12 @@ function pointerEscape(segment) {
|
|
|
31568
32146
|
function secretGrantKeys(grants) {
|
|
31569
32147
|
const counts = /* @__PURE__ */ new Map();
|
|
31570
32148
|
for (const grant of grants) {
|
|
31571
|
-
const ref = stringValue3(
|
|
32149
|
+
const ref = stringValue3(asObject2(grant)?.ref);
|
|
31572
32150
|
if (ref) counts.set(ref, (counts.get(ref) ?? 0) + 1);
|
|
31573
32151
|
}
|
|
31574
32152
|
const keys = /* @__PURE__ */ new Map();
|
|
31575
32153
|
for (const [index, grant] of grants.entries()) {
|
|
31576
|
-
const ref = stringValue3(
|
|
32154
|
+
const ref = stringValue3(asObject2(grant)?.ref);
|
|
31577
32155
|
keys.set(index, ref && counts.get(ref) === 1 ? `by-ref/${pointerEscape(ref)}` : String(index));
|
|
31578
32156
|
}
|
|
31579
32157
|
return keys;
|
|
@@ -31593,21 +32171,21 @@ function kxmAuthorityEntries(resource) {
|
|
|
31593
32171
|
const declarations = valuesOf3(value, "repositories");
|
|
31594
32172
|
const declarationIds = /* @__PURE__ */ new Map();
|
|
31595
32173
|
for (const entry of declarations) {
|
|
31596
|
-
const id = stringValue3(
|
|
32174
|
+
const id = stringValue3(asObject2(entry)?.id);
|
|
31597
32175
|
if (id) declarationIds.set(id, (declarationIds.get(id) ?? 0) + 1);
|
|
31598
32176
|
}
|
|
31599
32177
|
for (const [index, repository] of declarations.entries()) {
|
|
31600
|
-
const entry =
|
|
32178
|
+
const entry = asObject2(repository);
|
|
31601
32179
|
if (!entry) continue;
|
|
31602
32180
|
const id = stringValue3(entry.id);
|
|
31603
32181
|
const key = id && declarationIds.get(id) === 1 ? `by-id/${pointerEscape(id)}` : String(index);
|
|
31604
32182
|
push(`/repositories/${key}`, "resource-shape", entry);
|
|
31605
32183
|
}
|
|
31606
|
-
const workspace =
|
|
32184
|
+
const workspace = asObject2(value.workspace);
|
|
31607
32185
|
if (workspace) push("/workspace", "snapshot-policy", workspace);
|
|
31608
|
-
const sync =
|
|
32186
|
+
const sync = asObject2(value.sync);
|
|
31609
32187
|
if (sync) push("/sync", "sync-policy", sync);
|
|
31610
|
-
const limits =
|
|
32188
|
+
const limits = asObject2(value.limits);
|
|
31611
32189
|
if (limits) push("/limits", "budget", limits);
|
|
31612
32190
|
break;
|
|
31613
32191
|
}
|
|
@@ -31625,7 +32203,7 @@ function kxmAuthorityEntries(resource) {
|
|
|
31625
32203
|
push("/harness", "harness", value.harness ?? "pi");
|
|
31626
32204
|
push("/tools", "tools", value.tools);
|
|
31627
32205
|
push("/defaultRepositoryAccess", "repository-access", value.defaultRepositoryAccess);
|
|
31628
|
-
const repositories =
|
|
32206
|
+
const repositories = asObject2(value.repositories);
|
|
31629
32207
|
for (const repositoryId of Object.keys(repositories ?? {}).sort()) {
|
|
31630
32208
|
push(`/repositories/${pointerEscape(repositoryId)}`, "repository-access", repositories?.[repositoryId]);
|
|
31631
32209
|
}
|
|
@@ -31635,7 +32213,7 @@ function kxmAuthorityEntries(resource) {
|
|
|
31635
32213
|
}
|
|
31636
32214
|
push("/network", "network", value.network);
|
|
31637
32215
|
push("/resultSchema", "delivery", value.resultSchema);
|
|
31638
|
-
const session =
|
|
32216
|
+
const session = asObject2(value.session);
|
|
31639
32217
|
if (session) push("/session", "resource-shape", session);
|
|
31640
32218
|
break;
|
|
31641
32219
|
}
|
|
@@ -31647,15 +32225,15 @@ function kxmAuthorityEntries(resource) {
|
|
|
31647
32225
|
push("/capabilities", "model", value.capabilities);
|
|
31648
32226
|
push("/priority", "model", value.priority);
|
|
31649
32227
|
push("/fallbacks", "model", value.fallbacks);
|
|
31650
|
-
const limits =
|
|
32228
|
+
const limits = asObject2(value.limits);
|
|
31651
32229
|
if (limits) push("/limits", "budget", limits);
|
|
31652
32230
|
break;
|
|
31653
32231
|
}
|
|
31654
32232
|
case "environment": {
|
|
31655
32233
|
push("/path", "environment", value.path);
|
|
31656
|
-
const envValues =
|
|
32234
|
+
const envValues = asObject2(value.values);
|
|
31657
32235
|
for (const key of Object.keys(envValues ?? {}).sort()) {
|
|
31658
|
-
const entry =
|
|
32236
|
+
const entry = asObject2(envValues?.[key]);
|
|
31659
32237
|
const projected = entry ? { ...entry, value: entry.value === void 0 ? void 0 : "<redacted>" } : envValues?.[key];
|
|
31660
32238
|
push(`/values/${pointerEscape(key)}`, "environment", projected);
|
|
31661
32239
|
}
|
|
@@ -31667,9 +32245,9 @@ function kxmAuthorityEntries(resource) {
|
|
|
31667
32245
|
}
|
|
31668
32246
|
case "gate-registry": {
|
|
31669
32247
|
push("/schema", "resource-shape", value.schema);
|
|
31670
|
-
const gates =
|
|
32248
|
+
const gates = asObject2(value.gates) ?? {};
|
|
31671
32249
|
for (const id of Object.keys(gates).sort()) {
|
|
31672
|
-
const definition =
|
|
32250
|
+
const definition = asObject2(gates[id]);
|
|
31673
32251
|
const path4 = `/gates/${pointerEscape(id)}`;
|
|
31674
32252
|
if (definition?.kind === "command") {
|
|
31675
32253
|
const { timeoutMs, ...authority } = definition;
|
|
@@ -31681,13 +32259,13 @@ function kxmAuthorityEntries(resource) {
|
|
|
31681
32259
|
}
|
|
31682
32260
|
case "workflow": {
|
|
31683
32261
|
push("/coordinator", "resource-shape", value.coordinator);
|
|
31684
|
-
const limits =
|
|
32262
|
+
const limits = asObject2(value.limits);
|
|
31685
32263
|
if (limits) push("/limits", "budget", limits);
|
|
31686
32264
|
if (value.reproOracle !== void 0) push("/reproOracle", "evidence-quorum", value.reproOracle);
|
|
31687
32265
|
if (value.planHash !== void 0) push("/planHash", "evidence-quorum", value.planHash);
|
|
31688
32266
|
if (value.requirePlanHash !== void 0) push("/requirePlanHash", "evidence-quorum", value.requirePlanHash);
|
|
31689
32267
|
for (const [index, stepEntry] of valuesOf3(value, "steps").entries()) {
|
|
31690
|
-
const step =
|
|
32268
|
+
const step = asObject2(stepEntry);
|
|
31691
32269
|
if (!step) continue;
|
|
31692
32270
|
const stepPath = `/steps/${index}`;
|
|
31693
32271
|
push(`${stepPath}/id`, "resource-shape", step.id);
|
|
@@ -31699,7 +32277,7 @@ function kxmAuthorityEntries(resource) {
|
|
|
31699
32277
|
push(`${stepPath}/model`, "model", step.model);
|
|
31700
32278
|
push(`${stepPath}/maxAttempts`, "budget", step.maxAttempts);
|
|
31701
32279
|
push(`${stepPath}/timeoutMs`, "budget", step.timeoutMs);
|
|
31702
|
-
const repositories =
|
|
32280
|
+
const repositories = asObject2(step.repositories);
|
|
31703
32281
|
for (const repositoryId of Object.keys(repositories ?? {}).sort()) {
|
|
31704
32282
|
push(`${stepPath}/repositories/${pointerEscape(repositoryId)}`, "repository-access", repositories?.[repositoryId]);
|
|
31705
32283
|
}
|
|
@@ -31729,7 +32307,7 @@ function kxmProseEntries(resource) {
|
|
|
31729
32307
|
}
|
|
31730
32308
|
if (resource.kind === "workflow") {
|
|
31731
32309
|
for (const [index, stepEntry] of valuesOf3(resource.value, "steps").entries()) {
|
|
31732
|
-
const step =
|
|
32310
|
+
const step = asObject2(stepEntry);
|
|
31733
32311
|
if (!step) continue;
|
|
31734
32312
|
for (const field of ["description", "instructions"]) {
|
|
31735
32313
|
if (step[field] !== void 0) entries.set(`/steps/${index}/${field}`, canonical(step[field]));
|
|
@@ -31739,7 +32317,7 @@ function kxmProseEntries(resource) {
|
|
|
31739
32317
|
return entries;
|
|
31740
32318
|
}
|
|
31741
32319
|
function valueHash(canonicalValue) {
|
|
31742
|
-
return `sha256:${
|
|
32320
|
+
return `sha256:${createHash15("sha256").update(canonicalValue, "utf8").digest("hex")}`;
|
|
31743
32321
|
}
|
|
31744
32322
|
function rankDirection(rank, baseValue, candidateValue) {
|
|
31745
32323
|
const base = JSON.parse(baseValue);
|
|
@@ -31760,8 +32338,8 @@ function numericBoundDirection(baseValue, candidateValue) {
|
|
|
31760
32338
|
return "neutral";
|
|
31761
32339
|
}
|
|
31762
32340
|
function budgetDirection(baseValue, candidateValue) {
|
|
31763
|
-
const base =
|
|
31764
|
-
const candidate =
|
|
32341
|
+
const base = asObject2(JSON.parse(baseValue));
|
|
32342
|
+
const candidate = asObject2(JSON.parse(candidateValue));
|
|
31765
32343
|
if (!base || !candidate) return "expansion";
|
|
31766
32344
|
let sawExpansion = false;
|
|
31767
32345
|
let sawNarrowing = false;
|
|
@@ -31774,8 +32352,8 @@ function budgetDirection(baseValue, candidateValue) {
|
|
|
31774
32352
|
return sawNarrowing ? "narrowing" : "neutral";
|
|
31775
32353
|
}
|
|
31776
32354
|
function quorumDirection(baseValue, candidateValue) {
|
|
31777
|
-
const base =
|
|
31778
|
-
const candidate =
|
|
32355
|
+
const base = asObject2(JSON.parse(baseValue));
|
|
32356
|
+
const candidate = asObject2(JSON.parse(candidateValue));
|
|
31779
32357
|
if (!base || !candidate) return "expansion";
|
|
31780
32358
|
if (base.strategy !== void 0 || candidate.strategy !== void 0) return "expansion";
|
|
31781
32359
|
if (base.stageId !== void 0 || candidate.stageId !== void 0) return "expansion";
|
|
@@ -31789,13 +32367,13 @@ function quorumDirection(baseValue, candidateValue) {
|
|
|
31789
32367
|
const CLASSIFIED = /* @__PURE__ */ new Set(["key", "kind", "minimum", "reusableAcrossAttempts", "strategy", "stageId", "evidenceKey"]);
|
|
31790
32368
|
const CLASSIFIED_POLICY = /* @__PURE__ */ new Set(["minimumProducers", "eligibleAgents", "degradation"]);
|
|
31791
32369
|
const residualOf = (value) => {
|
|
31792
|
-
const parsed =
|
|
32370
|
+
const parsed = asObject2(JSON.parse(value));
|
|
31793
32371
|
if (!parsed) return null;
|
|
31794
32372
|
const residual = {};
|
|
31795
32373
|
for (const [key, entry] of Object.entries(parsed)) {
|
|
31796
32374
|
if (CLASSIFIED.has(key)) continue;
|
|
31797
32375
|
if (key === "producerPolicy") {
|
|
31798
|
-
const policy =
|
|
32376
|
+
const policy = asObject2(entry);
|
|
31799
32377
|
if (!policy) {
|
|
31800
32378
|
residual[key] = entry;
|
|
31801
32379
|
continue;
|
|
@@ -31808,8 +32386,8 @@ function quorumDirection(baseValue, candidateValue) {
|
|
|
31808
32386
|
return residual;
|
|
31809
32387
|
};
|
|
31810
32388
|
if (kxmCanonicalJson(residualOf(baseValue)) !== kxmCanonicalJson(residualOf(candidateValue))) return "expansion";
|
|
31811
|
-
const basePolicy =
|
|
31812
|
-
const candidatePolicy =
|
|
32389
|
+
const basePolicy = asObject2(base.producerPolicy);
|
|
32390
|
+
const candidatePolicy = asObject2(candidate.producerPolicy);
|
|
31813
32391
|
if (!basePolicy && !candidatePolicy) return sawNarrowing ? "narrowing" : "neutral";
|
|
31814
32392
|
if (!basePolicy || !candidatePolicy) return "expansion";
|
|
31815
32393
|
const basePolicyMinimum = typeof basePolicy.minimumProducers === "number" ? basePolicy.minimumProducers : 1;
|
|
@@ -31818,8 +32396,8 @@ function quorumDirection(baseValue, candidateValue) {
|
|
|
31818
32396
|
const baseEligible = new Set((Array.isArray(basePolicy.eligibleAgents) ? basePolicy.eligibleAgents : []).map(String));
|
|
31819
32397
|
const candidateEligible = new Set((Array.isArray(candidatePolicy.eligibleAgents) ? candidatePolicy.eligibleAgents : []).map(String));
|
|
31820
32398
|
for (const agent of baseEligible) if (!candidateEligible.has(agent)) return "expansion";
|
|
31821
|
-
const baseDegradation =
|
|
31822
|
-
const candidateDegradation =
|
|
32399
|
+
const baseDegradation = asObject2(basePolicy.degradation);
|
|
32400
|
+
const candidateDegradation = asObject2(candidatePolicy.degradation);
|
|
31823
32401
|
if (!baseDegradation && candidateDegradation) return "expansion";
|
|
31824
32402
|
if (baseDegradation && candidateDegradation) {
|
|
31825
32403
|
const baseFloor = typeof baseDegradation.minimumProducers === "number" ? baseDegradation.minimumProducers : 1;
|
|
@@ -31833,11 +32411,11 @@ function quorumDirection(baseValue, candidateValue) {
|
|
|
31833
32411
|
return sawNarrowing ? "narrowing" : "neutral";
|
|
31834
32412
|
}
|
|
31835
32413
|
function snapshotDirection(baseValue, candidateValue) {
|
|
31836
|
-
const base =
|
|
31837
|
-
const candidate =
|
|
32414
|
+
const base = asObject2(JSON.parse(baseValue));
|
|
32415
|
+
const candidate = asObject2(JSON.parse(candidateValue));
|
|
31838
32416
|
if (!base || !candidate) return "expansion";
|
|
31839
|
-
const baseSnapshot =
|
|
31840
|
-
const candidateSnapshot =
|
|
32417
|
+
const baseSnapshot = asObject2(base.dirtySnapshot);
|
|
32418
|
+
const candidateSnapshot = asObject2(candidate.dirtySnapshot);
|
|
31841
32419
|
if (!baseSnapshot || !candidateSnapshot) return "expansion";
|
|
31842
32420
|
const untracked = rankDirection(UNTRACKED_RANK, canonical(baseSnapshot.untracked), canonical(candidateSnapshot.untracked));
|
|
31843
32421
|
if (untracked === "expansion") return "expansion";
|
|
@@ -31891,8 +32469,8 @@ function classifyChange(field, baseValue, candidateValue, context) {
|
|
|
31891
32469
|
case "snapshot-policy":
|
|
31892
32470
|
return snapshotDirection(baseValue, candidateValue);
|
|
31893
32471
|
case "secrets": {
|
|
31894
|
-
const base =
|
|
31895
|
-
const candidate =
|
|
32472
|
+
const base = asObject2(JSON.parse(baseValue));
|
|
32473
|
+
const candidate = asObject2(JSON.parse(candidateValue));
|
|
31896
32474
|
if (base && candidate && base.ref === candidate.ref) {
|
|
31897
32475
|
const { required: _b, ...baseRest } = base;
|
|
31898
32476
|
const { required: _c, ...candidateRest } = candidate;
|
|
@@ -32057,9 +32635,9 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32057
32635
|
const tree = resolveTreeRevision(root, revision);
|
|
32058
32636
|
const listing = gitBuffer(root, ["ls-tree", "-r", "-z", "--name-only", tree]).toString("utf8");
|
|
32059
32637
|
const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
|
|
32060
|
-
const shadow = mkdtempSync(
|
|
32061
|
-
const shadowResolved =
|
|
32062
|
-
const shadowMembersDir = `.kxm-shadow-members-${
|
|
32638
|
+
const shadow = mkdtempSync(join28(tmpdir(), "kxm-permission-base-"));
|
|
32639
|
+
const shadowResolved = resolve20(shadow);
|
|
32640
|
+
const shadowMembersDir = `.kxm-shadow-members-${createHash15("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
|
|
32063
32641
|
try {
|
|
32064
32642
|
for (const path4 of paths) {
|
|
32065
32643
|
const segments = path4.split("/");
|
|
@@ -32071,8 +32649,8 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32071
32649
|
message: "Git tree entry contains a traversal or non-portable segment"
|
|
32072
32650
|
}]);
|
|
32073
32651
|
}
|
|
32074
|
-
const absolute =
|
|
32075
|
-
const resolved =
|
|
32652
|
+
const absolute = join28(shadowResolved, ...segments);
|
|
32653
|
+
const resolved = resolve20(absolute);
|
|
32076
32654
|
if (!resolved.startsWith(`${shadowResolved}${sep4}`)) {
|
|
32077
32655
|
throw new KxmConfigError([{
|
|
32078
32656
|
phase: "path",
|
|
@@ -32082,11 +32660,11 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32082
32660
|
}]);
|
|
32083
32661
|
}
|
|
32084
32662
|
const content = gitBuffer(root, ["cat-file", "blob", `${tree}:${path4}`]);
|
|
32085
|
-
mkdirSync20(
|
|
32663
|
+
mkdirSync20(dirname15(absolute), { recursive: true });
|
|
32086
32664
|
writeFileSync19(absolute, content);
|
|
32087
32665
|
}
|
|
32088
32666
|
initShadowGitRoot(shadowResolved);
|
|
32089
|
-
const baseProjectFile =
|
|
32667
|
+
const baseProjectFile = join28(shadowResolved, ".kxm", "project.yaml");
|
|
32090
32668
|
let baseProject;
|
|
32091
32669
|
try {
|
|
32092
32670
|
baseProject = loadBaseProjectDeclarations(baseProjectFile);
|
|
@@ -32116,8 +32694,8 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32116
32694
|
}]);
|
|
32117
32695
|
}
|
|
32118
32696
|
const boundPath = options.repositoryBindings?.[member.repositoryId];
|
|
32119
|
-
const memberWorktree = boundPath ?
|
|
32120
|
-
if (!memberWorktree || !
|
|
32697
|
+
const memberWorktree = boundPath ? resolve20(boundPath) : member.pathHint ? resolve20(root, ...member.pathHint.split("/")) : void 0;
|
|
32698
|
+
if (!memberWorktree || !existsSync24(memberWorktree)) {
|
|
32121
32699
|
if (member.required === false) continue;
|
|
32122
32700
|
throw new KxmConfigError([{
|
|
32123
32701
|
phase: "discovery",
|
|
@@ -32164,8 +32742,8 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32164
32742
|
const memberTree = git(memberWorktree, ["rev-parse", "--verify", "--quiet", `${pinnedCommit}^{tree}`]).trim();
|
|
32165
32743
|
const memberListing = gitBuffer(memberWorktree, ["ls-tree", "-r", "-z", "--name-only", memberTree]).toString("utf8");
|
|
32166
32744
|
const memberPaths = memberListing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
|
|
32167
|
-
const memberShadowRoot =
|
|
32168
|
-
if (!
|
|
32745
|
+
const memberShadowRoot = join28(shadowResolved, shadowMembersDir, member.repositoryId);
|
|
32746
|
+
if (!resolve20(memberShadowRoot).startsWith(`${resolve20(shadowResolved, shadowMembersDir)}${sep4}`)) {
|
|
32169
32747
|
throw new KxmConfigError([{
|
|
32170
32748
|
phase: "path",
|
|
32171
32749
|
code: "git_tree_path_invalid",
|
|
@@ -32178,13 +32756,13 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
32178
32756
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".." || /[\\:]/.test(segment))) {
|
|
32179
32757
|
throw new KxmConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry contains a traversal or non-portable segment" }]);
|
|
32180
32758
|
}
|
|
32181
|
-
const absolute =
|
|
32182
|
-
const resolved =
|
|
32183
|
-
if (!resolved.startsWith(`${
|
|
32759
|
+
const absolute = join28(memberShadowRoot, ...segments);
|
|
32760
|
+
const resolved = resolve20(absolute);
|
|
32761
|
+
if (!resolved.startsWith(`${resolve20(memberShadowRoot)}${sep4}`)) {
|
|
32184
32762
|
throw new KxmConfigError([{ phase: "path", code: "git_tree_path_invalid", file: path4, message: "member Git tree entry escapes the shadow root" }]);
|
|
32185
32763
|
}
|
|
32186
32764
|
const content = gitBuffer(memberWorktree, ["cat-file", "blob", `${memberTree}:${path4}`]);
|
|
32187
|
-
mkdirSync20(
|
|
32765
|
+
mkdirSync20(dirname15(absolute), { recursive: true });
|
|
32188
32766
|
writeFileSync19(absolute, content);
|
|
32189
32767
|
}
|
|
32190
32768
|
initShadowGitRoot(memberShadowRoot);
|
|
@@ -32206,8 +32784,8 @@ function portableMemberPathHint(pathHint) {
|
|
|
32206
32784
|
return pathHint !== "." && kxmPortablePath(pathHint);
|
|
32207
32785
|
}
|
|
32208
32786
|
function loadBaseProjectDeclarations(projectFile) {
|
|
32209
|
-
if (!
|
|
32210
|
-
const value = parseRestrictedYaml2(
|
|
32787
|
+
if (!existsSync24(projectFile)) return [];
|
|
32788
|
+
const value = parseRestrictedYaml2(readFileSync24(projectFile, "utf8"), ".kxm/project.yaml");
|
|
32211
32789
|
const repositories = Array.isArray(value?.repositories) ? value.repositories : [];
|
|
32212
32790
|
const members = [];
|
|
32213
32791
|
for (const entry of repositories) {
|
|
@@ -32310,7 +32888,7 @@ function writeDurableNew(file, bytes, mode) {
|
|
|
32310
32888
|
} finally {
|
|
32311
32889
|
if (descriptor !== void 0) closeSync3(descriptor);
|
|
32312
32890
|
}
|
|
32313
|
-
syncDirectory2(
|
|
32891
|
+
syncDirectory2(dirname16(file));
|
|
32314
32892
|
}
|
|
32315
32893
|
function writeDurable(file, bytes, mode) {
|
|
32316
32894
|
let descriptor;
|
|
@@ -32327,27 +32905,27 @@ function writeDurable(file, bytes, mode) {
|
|
|
32327
32905
|
} finally {
|
|
32328
32906
|
if (descriptor !== void 0) closeSync3(descriptor);
|
|
32329
32907
|
}
|
|
32330
|
-
syncDirectory2(
|
|
32908
|
+
syncDirectory2(dirname16(file));
|
|
32331
32909
|
}
|
|
32332
32910
|
function sameHostPath3(left, right) {
|
|
32333
|
-
const first =
|
|
32334
|
-
const second =
|
|
32911
|
+
const first = resolve21(left);
|
|
32912
|
+
const second = resolve21(right);
|
|
32335
32913
|
return process.platform === "win32" ? first.toLocaleLowerCase("en-US") === second.toLocaleLowerCase("en-US") : first === second;
|
|
32336
32914
|
}
|
|
32337
32915
|
function transactionRoot(projectRoot) {
|
|
32338
|
-
return
|
|
32916
|
+
return join29(resolve21(projectRoot), TRANSACTION_NAME);
|
|
32339
32917
|
}
|
|
32340
32918
|
function operationFile(projectRoot) {
|
|
32341
|
-
return
|
|
32919
|
+
return join29(transactionRoot(projectRoot), OPERATION_FILE);
|
|
32342
32920
|
}
|
|
32343
32921
|
function targetFile(projectRoot, portablePath2) {
|
|
32344
|
-
return
|
|
32922
|
+
return join29(transactionRoot(projectRoot), "targets", ...portablePath2.split("/"));
|
|
32345
32923
|
}
|
|
32346
32924
|
function shadowRoot(projectRoot) {
|
|
32347
|
-
return
|
|
32925
|
+
return join29(transactionRoot(projectRoot), "shadow");
|
|
32348
32926
|
}
|
|
32349
32927
|
function backupFile(projectRoot, portablePath2) {
|
|
32350
|
-
return
|
|
32928
|
+
return join29(transactionRoot(projectRoot), "backups", ...portablePath2.split("/"));
|
|
32351
32929
|
}
|
|
32352
32930
|
function portableManagedPath(path4) {
|
|
32353
32931
|
return path4.startsWith(".kxm/") && path4 !== KXM_TEMPLATE_PROVENANCE_PATH && path4.length <= 1024 && !path4.includes("\\") && !isAbsolute8(path4) && PORTABLE_PATH.test(path4) && !/[<>:"|?*]/.test(path4);
|
|
@@ -32356,24 +32934,24 @@ function readRegularBounded(file, label) {
|
|
|
32356
32934
|
const stat = lstatSync7(file);
|
|
32357
32935
|
if (stat.isSymbolicLink() || !stat.isFile()) fail3("repair_file_invalid", label, "managed file must be a regular file, not a link or directory", "path");
|
|
32358
32936
|
if (stat.size > MAX_MANAGED_FILE_BYTES) fail3("repair_file_too_large", label, `managed file exceeds ${MAX_MANAGED_FILE_BYTES} bytes`, "parse");
|
|
32359
|
-
return
|
|
32937
|
+
return readFileSync25(file);
|
|
32360
32938
|
}
|
|
32361
32939
|
function readOptionalManaged(projectRoot, path4) {
|
|
32362
|
-
const file =
|
|
32363
|
-
if (!
|
|
32364
|
-
let parent =
|
|
32365
|
-
const boundary =
|
|
32940
|
+
const file = join29(projectRoot, ...path4.split("/"));
|
|
32941
|
+
if (!existsSync25(file)) return void 0;
|
|
32942
|
+
let parent = dirname16(file);
|
|
32943
|
+
const boundary = resolve21(projectRoot);
|
|
32366
32944
|
while (parent !== boundary) {
|
|
32367
32945
|
const stat = lstatSync7(parent);
|
|
32368
32946
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_parent_invalid", path4, "managed file parents must be regular directories", "path");
|
|
32369
|
-
parent =
|
|
32947
|
+
parent = dirname16(parent);
|
|
32370
32948
|
}
|
|
32371
32949
|
return readRegularBounded(file, path4);
|
|
32372
32950
|
}
|
|
32373
32951
|
function readProjectAndProvenance(projectRoot, schemasDir) {
|
|
32374
|
-
const projectFile =
|
|
32375
|
-
const provenanceFile =
|
|
32376
|
-
if (!
|
|
32952
|
+
const projectFile = join29(projectRoot, ".kxm", "project.yaml");
|
|
32953
|
+
const provenanceFile = join29(projectRoot, ...KXM_TEMPLATE_PROVENANCE_PATH.split("/"));
|
|
32954
|
+
if (!existsSync25(projectFile) || !existsSync25(provenanceFile)) return void 0;
|
|
32377
32955
|
const projectBytes = readOptionalManaged(projectRoot, ".kxm/project.yaml");
|
|
32378
32956
|
const provenanceBytes = readOptionalManaged(projectRoot, KXM_TEMPLATE_PROVENANCE_PATH);
|
|
32379
32957
|
if (!projectBytes || !provenanceBytes) return void 0;
|
|
@@ -32409,7 +32987,7 @@ function readProjectAndProvenance(projectRoot, schemasDir) {
|
|
|
32409
32987
|
}
|
|
32410
32988
|
function planKxmTemplateRepair(projectRoot, options = {}) {
|
|
32411
32989
|
assertNoRegisteredGates(options);
|
|
32412
|
-
const root =
|
|
32990
|
+
const root = resolve21(projectRoot);
|
|
32413
32991
|
const source = readProjectAndProvenance(root, options.schemasDir);
|
|
32414
32992
|
if (!source) return void 0;
|
|
32415
32993
|
const target = renderKxmTemplate(
|
|
@@ -32537,7 +33115,7 @@ function operationPlanSha(operation) {
|
|
|
32537
33115
|
function writeOperation(projectRoot, operation) {
|
|
32538
33116
|
const root = transactionRoot(projectRoot);
|
|
32539
33117
|
const file = operationFile(projectRoot);
|
|
32540
|
-
const temporary =
|
|
33118
|
+
const temporary = join29(root, `.operation-${operation.operationId}-${randomUUID10()}.tmp`);
|
|
32541
33119
|
try {
|
|
32542
33120
|
writeDurableNew(temporary, Buffer.from(`${JSON.stringify(operation, null, 2)}
|
|
32543
33121
|
`, "utf8"), 384);
|
|
@@ -32554,11 +33132,11 @@ function updatePhase(projectRoot, operation, phase) {
|
|
|
32554
33132
|
}
|
|
32555
33133
|
function readOperation(projectRoot, schemasDir) {
|
|
32556
33134
|
const root = transactionRoot(projectRoot);
|
|
32557
|
-
if (!
|
|
33135
|
+
if (!existsSync25(root)) return void 0;
|
|
32558
33136
|
const stat = lstatSync7(root);
|
|
32559
33137
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("init_transaction_invalid", TRANSACTION_NAME, "initialization transaction must be a regular directory", "path");
|
|
32560
33138
|
const file = operationFile(projectRoot);
|
|
32561
|
-
if (!
|
|
33139
|
+
if (!existsSync25(file)) {
|
|
32562
33140
|
const entries = readdirSync9(root, { withFileTypes: true });
|
|
32563
33141
|
const recoverable = entries.every((entry) => entry.isFile() && /^\.operation-op_[a-f0-9]{32}-[a-f0-9-]{36}\.tmp$/.test(entry.name));
|
|
32564
33142
|
if (recoverable) return void 0;
|
|
@@ -32586,15 +33164,15 @@ function readOperation(projectRoot, schemasDir) {
|
|
|
32586
33164
|
return operation;
|
|
32587
33165
|
}
|
|
32588
33166
|
function hasKxmInitTransaction(projectRoot) {
|
|
32589
|
-
return
|
|
33167
|
+
return existsSync25(transactionRoot(projectRoot));
|
|
32590
33168
|
}
|
|
32591
33169
|
function readTarget(projectRoot, path4) {
|
|
32592
|
-
return readOptionalManaged(
|
|
33170
|
+
return readOptionalManaged(join29(transactionRoot(projectRoot), "targets"), path4);
|
|
32593
33171
|
}
|
|
32594
33172
|
function writeTarget(projectRoot, path4, bytes) {
|
|
32595
33173
|
if (bytes.byteLength > MAX_MANAGED_FILE_BYTES) fail3("template_target_too_large", path4, `target exceeds ${MAX_MANAGED_FILE_BYTES} bytes`);
|
|
32596
33174
|
const file = targetFile(projectRoot, path4);
|
|
32597
|
-
mkdirSync21(
|
|
33175
|
+
mkdirSync21(dirname16(file), { recursive: true, mode: 448 });
|
|
32598
33176
|
writeDurableNew(file, bytes, 384);
|
|
32599
33177
|
}
|
|
32600
33178
|
function writeAndVerifyBackups(projectRoot, operation) {
|
|
@@ -32606,7 +33184,7 @@ function writeAndVerifyBackups(projectRoot, operation) {
|
|
|
32606
33184
|
fail3("repair_preimage_changed", entry.path, "destination changed before transaction preparation; local bytes were preserved");
|
|
32607
33185
|
}
|
|
32608
33186
|
const file = backupFile(projectRoot, entry.path);
|
|
32609
|
-
mkdirSync21(
|
|
33187
|
+
mkdirSync21(dirname16(file), { recursive: true, mode: 448 });
|
|
32610
33188
|
writeDurableNew(file, current, 384);
|
|
32611
33189
|
}
|
|
32612
33190
|
}
|
|
@@ -32621,7 +33199,7 @@ function writeKnownBackups(projectRoot, operation) {
|
|
|
32621
33199
|
fail3("init_transaction_backup_source_invalid", entry.path, "supported source template cannot reproduce the pinned preimage");
|
|
32622
33200
|
}
|
|
32623
33201
|
const file = backupFile(projectRoot, entry.path);
|
|
32624
|
-
mkdirSync21(
|
|
33202
|
+
mkdirSync21(dirname16(file), { recursive: true, mode: 448 });
|
|
32625
33203
|
writeDurableNew(file, bytes, 384);
|
|
32626
33204
|
}
|
|
32627
33205
|
}
|
|
@@ -32629,7 +33207,7 @@ function verifyBackups(projectRoot, operation) {
|
|
|
32629
33207
|
if (operation.kind !== "repair") return;
|
|
32630
33208
|
for (const entry of operation.files) {
|
|
32631
33209
|
if (entry.observedSha256 === null) continue;
|
|
32632
|
-
const bytes = readOptionalManaged(
|
|
33210
|
+
const bytes = readOptionalManaged(join29(transactionRoot(projectRoot), "backups"), entry.path);
|
|
32633
33211
|
if (!bytes || kxmContentSha256(bytes) !== entry.observedSha256) {
|
|
32634
33212
|
fail3("init_transaction_backup_invalid", entry.path, "pinned preimage backup is missing or corrupt");
|
|
32635
33213
|
}
|
|
@@ -32663,10 +33241,10 @@ function sourceConfigFiles(projectRoot) {
|
|
|
32663
33241
|
".kxm/repo/repo.yaml",
|
|
32664
33242
|
".kxm/repo/env.yaml"
|
|
32665
33243
|
];
|
|
32666
|
-
const files = fixed.filter((path4) =>
|
|
33244
|
+
const files = fixed.filter((path4) => existsSync25(join29(projectRoot, ...path4.split("/"))));
|
|
32667
33245
|
for (const directory of ["agents", "models", "workflows"]) {
|
|
32668
|
-
const absolute =
|
|
32669
|
-
if (!
|
|
33246
|
+
const absolute = join29(projectRoot, ".kxm", directory);
|
|
33247
|
+
if (!existsSync25(absolute)) continue;
|
|
32670
33248
|
const stat = lstatSync7(absolute);
|
|
32671
33249
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_directory_invalid", `.kxm/${directory}`, "configuration directory must be regular", "path");
|
|
32672
33250
|
for (const entry of readdirSync9(absolute, { withFileTypes: true })) {
|
|
@@ -32687,8 +33265,8 @@ function absoluteMemberBindings(projectRoot, project, explicit = {}) {
|
|
|
32687
33265
|
const repository = candidate;
|
|
32688
33266
|
if (repository.role !== "member" || typeof repository.id !== "string" || Object.hasOwn(bindings, repository.id)) continue;
|
|
32689
33267
|
if (typeof repository.pathHint === "string") {
|
|
32690
|
-
const candidate2 =
|
|
32691
|
-
if (
|
|
33268
|
+
const candidate2 = resolve21(projectRoot, ...repository.pathHint.split("/"));
|
|
33269
|
+
if (existsSync25(candidate2)) bindings[repository.id] = candidate2;
|
|
32692
33270
|
}
|
|
32693
33271
|
}
|
|
32694
33272
|
return bindings;
|
|
@@ -32702,9 +33280,9 @@ function prepareShadow(projectRoot, operation, options) {
|
|
|
32702
33280
|
const bytes = readOptionalManaged(projectRoot, path4);
|
|
32703
33281
|
if (!bytes) fail3("repair_snapshot_file_missing", path4, "configuration changed during shadow preparation");
|
|
32704
33282
|
total += bytes.byteLength;
|
|
32705
|
-
const source =
|
|
32706
|
-
const destination =
|
|
32707
|
-
mkdirSync21(
|
|
33283
|
+
const source = join29(projectRoot, ...path4.split("/"));
|
|
33284
|
+
const destination = join29(shadow, ...path4.split("/"));
|
|
33285
|
+
mkdirSync21(dirname16(destination), { recursive: true, mode: 448 });
|
|
32708
33286
|
writeDurableNew(destination, bytes, lstatSync7(source).mode & 511);
|
|
32709
33287
|
if (total > MAX_MANAGED_TOTAL_BYTES) fail3("repair_snapshot_too_large", ".kxm", `configuration snapshot exceeds ${MAX_MANAGED_TOTAL_BYTES} bytes`, "parse");
|
|
32710
33288
|
}
|
|
@@ -32712,12 +33290,12 @@ function prepareShadow(projectRoot, operation, options) {
|
|
|
32712
33290
|
for (const entry of operation.files) {
|
|
32713
33291
|
const bytes = readTarget(projectRoot, entry.path);
|
|
32714
33292
|
if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
|
|
32715
|
-
const destination =
|
|
32716
|
-
mkdirSync21(
|
|
32717
|
-
const mode =
|
|
33293
|
+
const destination = join29(shadow, ...entry.path.split("/"));
|
|
33294
|
+
mkdirSync21(dirname16(destination), { recursive: true, mode: 448 });
|
|
33295
|
+
const mode = existsSync25(destination) ? lstatSync7(destination).mode & 511 : 420;
|
|
32718
33296
|
writeDurable(destination, bytes, mode);
|
|
32719
33297
|
}
|
|
32720
|
-
const project = parseRestrictedYaml2(
|
|
33298
|
+
const project = parseRestrictedYaml2(readFileSync25(join29(shadow, ".kxm", "project.yaml")), ".kxm/project.yaml");
|
|
32721
33299
|
const bound = absoluteMemberBindings(projectRoot, project, options.repositoryBindings);
|
|
32722
33300
|
loadKxmProject(shadow, { ...loaderOptions(options), repositoryBindings: bound });
|
|
32723
33301
|
const provenance = readProjectAndProvenance(shadow, options.schemasDir)?.provenance;
|
|
@@ -32726,7 +33304,7 @@ function prepareShadow(projectRoot, operation, options) {
|
|
|
32726
33304
|
}
|
|
32727
33305
|
}
|
|
32728
33306
|
function createOperation(projectRoot, kind, rendered, files, sourceTemplateRevision) {
|
|
32729
|
-
const root =
|
|
33307
|
+
const root = resolve21(projectRoot);
|
|
32730
33308
|
const operationWithoutHash = {
|
|
32731
33309
|
schema: "kxm.init-operation.v1",
|
|
32732
33310
|
operationId: `op_${randomUUID10().replaceAll("-", "")}`,
|
|
@@ -32755,7 +33333,7 @@ function prepareOperation(projectRoot, operation, rendered, options) {
|
|
|
32755
33333
|
const transaction = transactionRoot(projectRoot);
|
|
32756
33334
|
validateOperationRecord(operation, options.schemasDir);
|
|
32757
33335
|
validateOperationIntent(projectRoot, operation);
|
|
32758
|
-
if (
|
|
33336
|
+
if (existsSync25(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
|
|
32759
33337
|
mkdirSync21(transaction, { mode: 448 });
|
|
32760
33338
|
syncDirectory2(projectRoot);
|
|
32761
33339
|
writeOperation(projectRoot, operation);
|
|
@@ -32777,25 +33355,25 @@ function prepareOperation(projectRoot, operation, rendered, options) {
|
|
|
32777
33355
|
}
|
|
32778
33356
|
}
|
|
32779
33357
|
function ensureDestinationParents(projectRoot, path4) {
|
|
32780
|
-
const boundary =
|
|
33358
|
+
const boundary = resolve21(projectRoot);
|
|
32781
33359
|
const segments = path4.split("/").slice(0, -1);
|
|
32782
33360
|
let current = boundary;
|
|
32783
33361
|
for (const segment of segments) {
|
|
32784
|
-
current =
|
|
32785
|
-
if (!
|
|
33362
|
+
current = join29(current, segment);
|
|
33363
|
+
if (!existsSync25(current)) mkdirSync21(current, { mode: 448 });
|
|
32786
33364
|
const stat = lstatSync7(current);
|
|
32787
33365
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_destination_parent_invalid", path4, "destination parent must be a regular directory", "path");
|
|
32788
33366
|
}
|
|
32789
33367
|
}
|
|
32790
33368
|
function destinationSha(projectRoot, path4) {
|
|
32791
|
-
const file =
|
|
32792
|
-
if (!
|
|
33369
|
+
const file = join29(projectRoot, ...path4.split("/"));
|
|
33370
|
+
if (!existsSync25(file)) return void 0;
|
|
32793
33371
|
return kxmContentSha256(readRegularBounded(file, path4));
|
|
32794
33372
|
}
|
|
32795
33373
|
function requireConditionalLinkSupport(directory, operationId, path4) {
|
|
32796
33374
|
const nonce = randomUUID10();
|
|
32797
|
-
const source =
|
|
32798
|
-
const target =
|
|
33375
|
+
const source = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-source`);
|
|
33376
|
+
const target = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-target`);
|
|
32799
33377
|
try {
|
|
32800
33378
|
writeDurableNew(source, Buffer.from("kxm-link-probe\n", "utf8"), 384);
|
|
32801
33379
|
try {
|
|
@@ -32810,19 +33388,19 @@ function requireConditionalLinkSupport(directory, operationId, path4) {
|
|
|
32810
33388
|
}
|
|
32811
33389
|
}
|
|
32812
33390
|
function atomicInstallTarget(projectRoot, operation, entry) {
|
|
32813
|
-
const destination =
|
|
33391
|
+
const destination = join29(projectRoot, ...entry.path.split("/"));
|
|
32814
33392
|
ensureDestinationParents(projectRoot, entry.path);
|
|
32815
|
-
const directory =
|
|
33393
|
+
const directory = dirname16(destination);
|
|
32816
33394
|
const temporaryPrefix = `.kxm-repair-${operation.operationId}-`;
|
|
32817
33395
|
const pathKey = kxmContentSha256(entry.path).slice("sha256:".length, "sha256:".length + 16);
|
|
32818
|
-
const displaced =
|
|
32819
|
-
if (
|
|
33396
|
+
const displaced = join29(directory, `${temporaryPrefix}${pathKey}.preimage`);
|
|
33397
|
+
if (existsSync25(displaced)) {
|
|
32820
33398
|
const stat = lstatSync7(displaced);
|
|
32821
33399
|
if (stat.isSymbolicLink() || !stat.isFile()) fail3("repair_preimage_temporary_invalid", entry.path, "stale displaced preimage is not a regular file", "path");
|
|
32822
|
-
if (entry.observedSha256 === null || kxmContentSha256(
|
|
33400
|
+
if (entry.observedSha256 === null || kxmContentSha256(readFileSync25(displaced)) !== entry.observedSha256) {
|
|
32823
33401
|
fail3("repair_preimage_temporary_mismatch", entry.path, "stale displaced preimage does not match the pinned operation");
|
|
32824
33402
|
}
|
|
32825
|
-
if (!
|
|
33403
|
+
if (!existsSync25(destination)) {
|
|
32826
33404
|
try {
|
|
32827
33405
|
linkSync(displaced, destination);
|
|
32828
33406
|
} catch {
|
|
@@ -32837,7 +33415,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
32837
33415
|
const owned = candidate.name.startsWith(temporaryPrefix) && (candidate.name.endsWith(".tmp") || candidate.name.endsWith(".link-source") || candidate.name.endsWith(".link-target"));
|
|
32838
33416
|
if (!owned) continue;
|
|
32839
33417
|
if (!candidate.isFile()) fail3("repair_temporary_invalid", entry.path, "stale operation temporary is not a regular file", "path");
|
|
32840
|
-
rmSync9(
|
|
33418
|
+
rmSync9(join29(directory, candidate.name), { force: true });
|
|
32841
33419
|
}
|
|
32842
33420
|
const current = destinationSha(projectRoot, entry.path);
|
|
32843
33421
|
if (current === entry.targetSha256) return;
|
|
@@ -32847,15 +33425,15 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
32847
33425
|
requireConditionalLinkSupport(directory, operation.operationId, entry.path);
|
|
32848
33426
|
const bytes = readTarget(projectRoot, entry.path);
|
|
32849
33427
|
if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
|
|
32850
|
-
const temporary =
|
|
32851
|
-
const mode =
|
|
33428
|
+
const temporary = join29(directory, `${temporaryPrefix}${randomUUID10()}.tmp`);
|
|
33429
|
+
const mode = existsSync25(destination) ? lstatSync7(destination).mode & 511 : 420;
|
|
32852
33430
|
try {
|
|
32853
33431
|
writeDurableNew(temporary, bytes, mode);
|
|
32854
33432
|
if (entry.observedSha256 !== null) {
|
|
32855
33433
|
renameSync7(destination, displaced);
|
|
32856
33434
|
syncDirectory2(directory);
|
|
32857
|
-
if (kxmContentSha256(
|
|
32858
|
-
if (!
|
|
33435
|
+
if (kxmContentSha256(readFileSync25(displaced)) !== entry.observedSha256) {
|
|
33436
|
+
if (!existsSync25(destination)) {
|
|
32859
33437
|
try {
|
|
32860
33438
|
linkSync(displaced, destination);
|
|
32861
33439
|
} catch {
|
|
@@ -32871,7 +33449,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
32871
33449
|
try {
|
|
32872
33450
|
linkSync(temporary, destination);
|
|
32873
33451
|
} catch {
|
|
32874
|
-
if (!
|
|
33452
|
+
if (!existsSync25(destination) && existsSync25(displaced)) {
|
|
32875
33453
|
try {
|
|
32876
33454
|
linkSync(displaced, destination);
|
|
32877
33455
|
} catch {
|
|
@@ -32893,12 +33471,12 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
32893
33471
|
if (destinationSha(projectRoot, entry.path) !== entry.targetSha256) fail3("repair_install_verification_failed", entry.path, "installed target hash does not match the pinned operation");
|
|
32894
33472
|
}
|
|
32895
33473
|
function installedCreateMatches(projectRoot, operation) {
|
|
32896
|
-
const configRoot =
|
|
32897
|
-
if (!
|
|
33474
|
+
const configRoot = join29(projectRoot, ".kxm");
|
|
33475
|
+
if (!existsSync25(configRoot)) return false;
|
|
32898
33476
|
const actual = [];
|
|
32899
33477
|
const visit2 = (directory) => {
|
|
32900
33478
|
for (const entry of readdirSync9(directory, { withFileTypes: true })) {
|
|
32901
|
-
const absolute =
|
|
33479
|
+
const absolute = join29(directory, entry.name);
|
|
32902
33480
|
if (entry.isSymbolicLink()) fail3("create_resume_link", relativeConfigPath(projectRoot, absolute), "created configuration must not contain links", "path");
|
|
32903
33481
|
if (entry.isDirectory()) visit2(absolute);
|
|
32904
33482
|
else if (entry.isFile()) actual.push(relativeConfigPath(projectRoot, absolute));
|
|
@@ -32911,15 +33489,15 @@ function installedCreateMatches(projectRoot, operation) {
|
|
|
32911
33489
|
return actual.length === expected.length && actual.every((path4, index) => path4 === expected[index]) && operation.files.every((entry) => destinationSha(projectRoot, entry.path) === entry.targetSha256);
|
|
32912
33490
|
}
|
|
32913
33491
|
function relativeConfigPath(projectRoot, file) {
|
|
32914
|
-
return relative5(
|
|
33492
|
+
return relative5(resolve21(projectRoot), file).replaceAll("\\", "/");
|
|
32915
33493
|
}
|
|
32916
33494
|
function syncTreeDirectories(root) {
|
|
32917
|
-
if (!
|
|
33495
|
+
if (!existsSync25(root)) return;
|
|
32918
33496
|
const directories = [];
|
|
32919
33497
|
const visit2 = (directory) => {
|
|
32920
33498
|
directories.push(directory);
|
|
32921
33499
|
for (const entry of readdirSync9(directory, { withFileTypes: true })) {
|
|
32922
|
-
if (entry.isDirectory()) visit2(
|
|
33500
|
+
if (entry.isDirectory()) visit2(join29(directory, entry.name));
|
|
32923
33501
|
}
|
|
32924
33502
|
};
|
|
32925
33503
|
visit2(root);
|
|
@@ -32933,7 +33511,7 @@ function rebuildCreateShadow(projectRoot, operation, options) {
|
|
|
32933
33511
|
if (stat.isSymbolicLink() || !stat.isDirectory() || !installedCreateMatches(shadow, operation)) {
|
|
32934
33512
|
fail3("create_shadow_invalid", TRANSACTION_NAME, "reconstructed create shadow does not exactly match pinned target files", "path");
|
|
32935
33513
|
}
|
|
32936
|
-
syncTreeDirectories(
|
|
33514
|
+
syncTreeDirectories(join29(shadow, ".kxm"));
|
|
32937
33515
|
}
|
|
32938
33516
|
function applyOperation(projectRoot, original, options, resumed) {
|
|
32939
33517
|
assertNoRegisteredGates(options);
|
|
@@ -32943,10 +33521,10 @@ function applyOperation(projectRoot, original, options, resumed) {
|
|
|
32943
33521
|
verifyBackups(projectRoot, operation);
|
|
32944
33522
|
operation = updatePhase(projectRoot, operation, "applying");
|
|
32945
33523
|
if (operation.kind === "create") {
|
|
32946
|
-
const destination =
|
|
32947
|
-
if (!
|
|
33524
|
+
const destination = join29(projectRoot, ".kxm");
|
|
33525
|
+
if (!existsSync25(destination)) {
|
|
32948
33526
|
rebuildCreateShadow(projectRoot, operation, effectiveOptions);
|
|
32949
|
-
renameSync7(
|
|
33527
|
+
renameSync7(join29(shadowRoot(projectRoot), ".kxm"), destination);
|
|
32950
33528
|
syncDirectory2(projectRoot);
|
|
32951
33529
|
} else if (!installedCreateMatches(projectRoot, operation)) {
|
|
32952
33530
|
fail3("create_resume_conflict", ".kxm", "existing project configuration does not match the pinned create operation");
|
|
@@ -32973,7 +33551,7 @@ function applyOperation(projectRoot, original, options, resumed) {
|
|
|
32973
33551
|
operation = updatePhase(projectRoot, operation, "verified");
|
|
32974
33552
|
if (options.testFaultAt === "verified") throw new Error("injected init fault after verification");
|
|
32975
33553
|
const files = operation.files.map((entry) => entry.path);
|
|
32976
|
-
syncTreeDirectories(
|
|
33554
|
+
syncTreeDirectories(join29(projectRoot, ".kxm"));
|
|
32977
33555
|
return { kind: operation.kind, resumed, bundle, files };
|
|
32978
33556
|
}
|
|
32979
33557
|
function prepareAndApplyKxmCreate(projectRoot, rendered, options = {}) {
|
|
@@ -33088,7 +33666,7 @@ function finishPreparingOperation(projectRoot, operation, options) {
|
|
|
33088
33666
|
const rendered = rendererForOperation(operation);
|
|
33089
33667
|
if (!rendered) fail3("init_transaction_renderer_unavailable", TRANSACTION_NAME, "this binary cannot reproduce the pinned pre-application target");
|
|
33090
33668
|
for (const directory of ["targets", "backups", "shadow"]) {
|
|
33091
|
-
rmSync9(
|
|
33669
|
+
rmSync9(join29(transactionRoot(projectRoot), directory), { recursive: true, force: true });
|
|
33092
33670
|
}
|
|
33093
33671
|
for (const entry of operation.files) {
|
|
33094
33672
|
const bytes = rendered.files.get(entry.path);
|
|
@@ -33107,7 +33685,7 @@ function resumeKxmInitTransaction(projectRoot, options = {}) {
|
|
|
33107
33685
|
let operation = readOperation(projectRoot, options.schemasDir);
|
|
33108
33686
|
if (!operation) {
|
|
33109
33687
|
const root = transactionRoot(projectRoot);
|
|
33110
|
-
if (
|
|
33688
|
+
if (existsSync25(root)) {
|
|
33111
33689
|
rmSync9(root, { recursive: true, force: true });
|
|
33112
33690
|
syncDirectory2(projectRoot);
|
|
33113
33691
|
}
|
|
@@ -33123,7 +33701,7 @@ function commitKxmInitTransaction(projectRoot, schemasDir) {
|
|
|
33123
33701
|
fail3("init_transaction_not_verified", TRANSACTION_NAME, "initialization transaction cannot commit before installed state is verified");
|
|
33124
33702
|
}
|
|
33125
33703
|
const transaction = transactionRoot(projectRoot);
|
|
33126
|
-
const retired =
|
|
33704
|
+
const retired = join29(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID10()}`);
|
|
33127
33705
|
renameSync7(transaction, retired);
|
|
33128
33706
|
syncDirectory2(projectRoot);
|
|
33129
33707
|
rmSync9(retired, { recursive: true, force: true });
|
|
@@ -33232,7 +33810,7 @@ function validateBindingIdentities(bundle, persisted, explicit) {
|
|
|
33232
33810
|
}
|
|
33233
33811
|
function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
|
|
33234
33812
|
const storeOptions = bindingStoreOptions(options);
|
|
33235
|
-
const persisted =
|
|
33813
|
+
const persisted = existsSync26(join30(gitRoot, ".kxm", "project.yaml")) ? readKxmLocalBindings(gitRoot, storeOptions) : void 0;
|
|
33236
33814
|
const repositoryBindings = Object.fromEntries([
|
|
33237
33815
|
...Object.entries(persisted?.repositories ?? {}),
|
|
33238
33816
|
...Object.entries(options.repositoryBindings ?? {})
|
|
@@ -33420,7 +33998,7 @@ function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
|
|
|
33420
33998
|
const files = [...rendered.files.keys()];
|
|
33421
33999
|
if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, files };
|
|
33422
34000
|
if (!mutationLock) throw new Error("project mutation lock is required to create configuration");
|
|
33423
|
-
if (
|
|
34001
|
+
if (existsSync26(join30(gitRoot, ".kxm"))) {
|
|
33424
34002
|
throw new KxmConfigError([initIssue("workspace_changed", ".kxm", "workspace changed after planning; existing .kxm state was not overwritten")]);
|
|
33425
34003
|
}
|
|
33426
34004
|
const created = prepareAndApplyKxmCreate(gitRoot, rendered, transactionOptions);
|
|
@@ -33457,7 +34035,7 @@ function hubSourceReason(status) {
|
|
|
33457
34035
|
if (status === 404) return "hub_not_found";
|
|
33458
34036
|
return `hub_http_${status}`;
|
|
33459
34037
|
}
|
|
33460
|
-
function
|
|
34038
|
+
function asString2(value) {
|
|
33461
34039
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
33462
34040
|
}
|
|
33463
34041
|
function isObject(value) {
|
|
@@ -33505,37 +34083,37 @@ async function assembleTenantStatus(input) {
|
|
|
33505
34083
|
project: body.project,
|
|
33506
34084
|
fetchedAt: body.fetchedAt,
|
|
33507
34085
|
agents: body.agents.filter(isObject).map((agent) => ({
|
|
33508
|
-
id:
|
|
33509
|
-
name:
|
|
34086
|
+
id: asString2(agent.id) ?? "",
|
|
34087
|
+
name: asString2(agent.name) ?? "",
|
|
33510
34088
|
online: agent.online === true
|
|
33511
34089
|
})),
|
|
33512
34090
|
openMessageTotal: body.openMessageTotal,
|
|
33513
34091
|
runTotal: body.runTotal,
|
|
33514
34092
|
runs: runRows.map((run) => {
|
|
33515
34093
|
const stages = Array.isArray(run.stages) ? run.stages.filter(isObject).map((stage) => ({
|
|
33516
|
-
id:
|
|
33517
|
-
status:
|
|
34094
|
+
id: asString2(stage.id) ?? "",
|
|
34095
|
+
status: asString2(stage.status) ?? "unknown",
|
|
33518
34096
|
...typeof stage.attempts === "number" ? { attempts: stage.attempts } : {}
|
|
33519
34097
|
})) : void 0;
|
|
33520
34098
|
const done = stages?.filter((stage) => stage.status === "passed" || stage.status === "failed" || stage.status === "warning").length;
|
|
33521
34099
|
return {
|
|
33522
|
-
id:
|
|
33523
|
-
status:
|
|
33524
|
-
definitionId:
|
|
33525
|
-
...
|
|
33526
|
-
...
|
|
33527
|
-
...
|
|
34100
|
+
id: asString2(run.id) ?? "",
|
|
34101
|
+
status: asString2(run.status) ?? "unknown",
|
|
34102
|
+
definitionId: asString2(run.definitionId) ?? "",
|
|
34103
|
+
...asString2(run.currentStage) ? { currentStage: asString2(run.currentStage) } : {},
|
|
34104
|
+
...asString2(run.targetAgentName) ? { targetAgentName: asString2(run.targetAgentName) } : {},
|
|
34105
|
+
...asString2(run.updatedAt) ? { updatedAt: asString2(run.updatedAt) } : {},
|
|
33528
34106
|
...stages !== void 0 ? { progress: { done: done ?? 0, total: stages.length }, stages } : {},
|
|
33529
34107
|
source: "hub-projection"
|
|
33530
34108
|
};
|
|
33531
34109
|
}),
|
|
33532
34110
|
plans: (Array.isArray(body.plans) ? body.plans : []).filter(isObject).map((plan) => ({
|
|
33533
|
-
id:
|
|
33534
|
-
runId:
|
|
33535
|
-
summary:
|
|
33536
|
-
createdAt:
|
|
33537
|
-
...
|
|
33538
|
-
...
|
|
34111
|
+
id: asString2(plan.id) ?? "",
|
|
34112
|
+
runId: asString2(plan.runId) ?? "",
|
|
34113
|
+
summary: asString2(plan.summary) ?? "",
|
|
34114
|
+
createdAt: asString2(plan.createdAt) ?? "",
|
|
34115
|
+
...asString2(plan.stageId) ? { stageId: asString2(plan.stageId) } : {},
|
|
34116
|
+
...asString2(plan.severity) ? { severity: asString2(plan.severity) } : {}
|
|
33539
34117
|
}))
|
|
33540
34118
|
};
|
|
33541
34119
|
return { state: "ok", observedAt: attemptedAt, value };
|
|
@@ -33676,6 +34254,11 @@ async function cmdKxmInit(runtime, options, postHooks) {
|
|
|
33676
34254
|
localStateRoot: kxmUserStateRoot({ env: runtime.env }),
|
|
33677
34255
|
dryRun: runtime.dryRun
|
|
33678
34256
|
});
|
|
34257
|
+
const starterGuidance = initialized.action === "created" || initialized.action === "planned" && initialized.plan.mode === "create" ? [
|
|
34258
|
+
"defaultHarness: pi and the npm test gate are generic starter settings, not repository detection.",
|
|
34259
|
+
"For Claude, set defaultHarness: claude in .kxm/project.yaml, update any explicit harness overrides in .kxm/agents/*.yaml, and configure compatible agent models.",
|
|
34260
|
+
"For .NET or other non-npm repositories, set gates.test.argv in .kxm/gates.yaml to the repository's actual test runner before driving a workflow."
|
|
34261
|
+
] : [];
|
|
33679
34262
|
const payload = {
|
|
33680
34263
|
ok: initialized.action !== "planned" || runtime.dryRun,
|
|
33681
34264
|
command: "init",
|
|
@@ -33688,10 +34271,11 @@ async function cmdKxmInit(runtime, options, postHooks) {
|
|
|
33688
34271
|
...initialized.repairPlan === void 0 ? {} : { repairPlan: initialized.repairPlan },
|
|
33689
34272
|
...initialized.resumePending === void 0 ? {} : { resumePending: initialized.resumePending },
|
|
33690
34273
|
...initialized.transactionKind === void 0 ? {} : { transactionKind: initialized.transactionKind },
|
|
33691
|
-
plannedOnly: initialized.action === "planned"
|
|
34274
|
+
plannedOnly: initialized.action === "planned",
|
|
34275
|
+
...starterGuidance.length > 0 ? { guidance: starterGuidance } : {}
|
|
33692
34276
|
};
|
|
33693
34277
|
const finishInit = (code, text) => {
|
|
33694
|
-
print(runtime.io, runtime.json, payload, text);
|
|
34278
|
+
print(runtime.io, runtime.json, payload, [text, ...starterGuidance].join("\n"));
|
|
33695
34279
|
return code;
|
|
33696
34280
|
};
|
|
33697
34281
|
if (initialized.action === "created") {
|
|
@@ -33745,7 +34329,7 @@ async function cmdBackup(runtime, options) {
|
|
|
33745
34329
|
projectRoot: backupProjectRoot(runtime),
|
|
33746
34330
|
env: runtime.env,
|
|
33747
34331
|
...options.allProjects === true ? { allProjects: true } : {},
|
|
33748
|
-
...options.out ? { outDir:
|
|
34332
|
+
...options.out ? { outDir: resolve22(runtime.cwd, options.out) } : {}
|
|
33749
34333
|
};
|
|
33750
34334
|
if (runtime.dryRun) {
|
|
33751
34335
|
const plan = planBackup(backupOptions);
|
|
@@ -33753,8 +34337,8 @@ async function cmdBackup(runtime, options) {
|
|
|
33753
34337
|
runtime,
|
|
33754
34338
|
{ command: "backup", scope: plan.scope, outDir: plan.outDir, stores: plan.stores, files: plan.files },
|
|
33755
34339
|
[
|
|
33756
|
-
...[...plan.stores, ...plan.files].map((entry) => ({ action: "write", target:
|
|
33757
|
-
{ action: "write", target:
|
|
34340
|
+
...[...plan.stores, ...plan.files].map((entry) => ({ action: "write", target: join31(plan.outDir, entry.backupFile) })),
|
|
34341
|
+
{ action: "write", target: join31(plan.outDir, "manifest.json") }
|
|
33758
34342
|
],
|
|
33759
34343
|
`back up ${plan.stores.length} store(s) and ${plan.files.length} file(s) (${plan.scope} scope) to ${plan.outDir} (sources are not opened, so their WAL is not checkpointed)`
|
|
33760
34344
|
);
|
|
@@ -33773,7 +34357,7 @@ async function cmdBackup(runtime, options) {
|
|
|
33773
34357
|
complete ? `Created SQLite backup with ${manifest.stores.length} store(s) (${manifest.scope ?? "project"} scope):` : `Backup is incomplete (${manifest.omitted?.length ?? 0} omitted); not ok:`,
|
|
33774
34358
|
...manifest.stores.map((s) => ` - ${s.storeId}: ${s.sourcePath} -> ${s.backupFile} (schema v${s.schemaVersion}, ${s.bytes} bytes, sha256 ${s.sha256.slice(0, 12)}...)`),
|
|
33775
34359
|
...(manifest.omitted ?? []).map((id) => ` - omitted ${id}`),
|
|
33776
|
-
`Manifest: ${
|
|
34360
|
+
`Manifest: ${join31(outDir, "manifest.json")}`
|
|
33777
34361
|
].join("\n");
|
|
33778
34362
|
print(runtime.io, runtime.json, payload, summary);
|
|
33779
34363
|
return complete ? 0 : 1;
|
|
@@ -33807,7 +34391,7 @@ function assertRestoreTargetsStopped(runtime, plan) {
|
|
|
33807
34391
|
}
|
|
33808
34392
|
for (const store of plan.stores) {
|
|
33809
34393
|
if (store.storeId !== "hub-store") continue;
|
|
33810
|
-
const claim = readLiveHubClaim(
|
|
34394
|
+
const claim = readLiveHubClaim(dirname17(store.targetPath));
|
|
33811
34395
|
if (claim) {
|
|
33812
34396
|
throw databaseError(
|
|
33813
34397
|
"restore_hub_running",
|
|
@@ -33819,7 +34403,7 @@ function assertRestoreTargetsStopped(runtime, plan) {
|
|
|
33819
34403
|
}
|
|
33820
34404
|
async function cmdRestore(runtime, manifestArg, options = {}) {
|
|
33821
34405
|
try {
|
|
33822
|
-
const plan = planRestore(
|
|
34406
|
+
const plan = planRestore(resolve22(runtime.cwd, manifestArg), {
|
|
33823
34407
|
projectRoot: backupProjectRoot(runtime),
|
|
33824
34408
|
env: runtime.env,
|
|
33825
34409
|
...options.allProjects === true ? { allProjects: true } : {}
|
|
@@ -33839,7 +34423,7 @@ async function cmdRestore(runtime, manifestArg, options = {}) {
|
|
|
33839
34423
|
[
|
|
33840
34424
|
...plan.stores.flatMap((store) => [
|
|
33841
34425
|
{ action: "write", target: store.targetPath },
|
|
33842
|
-
...[`${store.targetPath}-wal`, `${store.targetPath}-shm`].filter((file) =>
|
|
34426
|
+
...[`${store.targetPath}-wal`, `${store.targetPath}-shm`].filter((file) => existsSync27(file)).map((file) => ({ action: "delete", target: file }))
|
|
33843
34427
|
]),
|
|
33844
34428
|
...plan.files.map((file) => ({ action: "write", target: file.targetPath }))
|
|
33845
34429
|
],
|
|
@@ -33920,11 +34504,17 @@ async function readSupervisor(runtime, command) {
|
|
|
33920
34504
|
const attached = await attachKxmSupervisor({ env: runtime.env });
|
|
33921
34505
|
return attached ?? refuseDryRun(runtime.io, runtime.json, command, "the Runtime supervisor is not running and --dry-run will not start it");
|
|
33922
34506
|
}
|
|
33923
|
-
|
|
33924
|
-
|
|
33925
|
-
|
|
34507
|
+
function runEngineNotice(runId, defaultHarness, prerequisites) {
|
|
34508
|
+
return [
|
|
34509
|
+
`No steps executed. Project default harness: ${defaultHarness}; per-agent harness settings take precedence.`,
|
|
34510
|
+
...prerequisites.map((item) => `Live prerequisite${item.stepId ? ` (${item.stepId})` : ""}: ${item.detail}`),
|
|
34511
|
+
"Live execution uses one-shot harness calls; no hub or Pi worker is required. Check installation/authentication with kxm harness list.",
|
|
34512
|
+
`${prerequisites.length > 0 ? "Resolve the prerequisites above, then execute" : "Execute"}: kxm runs drive ${runId} --wait`,
|
|
34513
|
+
`Inspect: kxm runs status ${runId} --json; receipt: kxm runs receipt ${runId} --json; cancel: kxm runs cancel ${runId}`,
|
|
34514
|
+
"These are local Runtime runs, not webhook workflows; use kxm runs, not kxm workflow get."
|
|
34515
|
+
].join("\n");
|
|
33926
34516
|
}
|
|
33927
|
-
function resolveKxmRunTarget(runtime, workflow) {
|
|
34517
|
+
function resolveKxmRunTarget(runtime, workflow, forTask = false) {
|
|
33928
34518
|
if (runtime.workspaceFlag !== void 0) {
|
|
33929
34519
|
print(runtime.io, runtime.json, {
|
|
33930
34520
|
ok: false,
|
|
@@ -33933,7 +34523,7 @@ function resolveKxmRunTarget(runtime, workflow) {
|
|
|
33933
34523
|
}, "kxm run discovers the authoritative project from the current directory; --workspace is not supported");
|
|
33934
34524
|
return 2;
|
|
33935
34525
|
}
|
|
33936
|
-
if (!workflow) {
|
|
34526
|
+
if (!workflow && !forTask) {
|
|
33937
34527
|
print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required" }, "usage: kxm run <workflow> [prompt]");
|
|
33938
34528
|
return 2;
|
|
33939
34529
|
}
|
|
@@ -33943,15 +34533,30 @@ function resolveKxmRunTarget(runtime, workflow) {
|
|
|
33943
34533
|
return 1;
|
|
33944
34534
|
}
|
|
33945
34535
|
const bundle = loadKxmProject(projectRoot, {});
|
|
33946
|
-
|
|
33947
|
-
|
|
34536
|
+
const workflowId = workflow ?? String(bundle.project.value.defaultWorkflow ?? "default");
|
|
34537
|
+
if (!bundle.workflows.has(workflowId)) {
|
|
34538
|
+
print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId }, `workflow ${workflowId} does not exist in this project`);
|
|
33948
34539
|
return 1;
|
|
33949
34540
|
}
|
|
33950
|
-
|
|
34541
|
+
const defaultHarness = String(bundle.project.value.defaultHarness ?? "pi");
|
|
34542
|
+
const prerequisites = kxmLiveRunPrerequisites(bundle, workflowId, projectRoot);
|
|
34543
|
+
if (forTask && prerequisites.length > 0) {
|
|
34544
|
+
print(runtime.io, runtime.json, {
|
|
34545
|
+
ok: false,
|
|
34546
|
+
command: "task run",
|
|
34547
|
+
error: "run_execution_unavailable",
|
|
34548
|
+
workflowId,
|
|
34549
|
+
defaultHarness,
|
|
34550
|
+
execution: { status: "not_started", mode: "live", prerequisites }
|
|
34551
|
+
}, `task run refused; no run created or task changed (default harness: ${defaultHarness}).
|
|
34552
|
+
${prerequisites.map((item) => `${item.stepId ?? workflowId}: ${item.detail}`).join("\n")}`);
|
|
34553
|
+
return 1;
|
|
34554
|
+
}
|
|
34555
|
+
return { projectRoot, workflowId, configRevision: bundle.configRevision, defaultHarness, prerequisites };
|
|
33951
34556
|
}
|
|
33952
|
-
async function cmdKxmRun(runtime, workflow, promptParts) {
|
|
34557
|
+
async function cmdKxmRun(runtime, workflow, promptParts, forTask = false) {
|
|
33953
34558
|
try {
|
|
33954
|
-
const target = resolveKxmRunTarget(runtime, workflow);
|
|
34559
|
+
const target = resolveKxmRunTarget(runtime, workflow, forTask);
|
|
33955
34560
|
if (typeof target === "number") return target;
|
|
33956
34561
|
const { projectRoot } = target;
|
|
33957
34562
|
if (runtime.dryRun) {
|
|
@@ -33974,12 +34579,24 @@ async function cmdKxmRun(runtime, workflow, promptParts) {
|
|
|
33974
34579
|
print(runtime.io, runtime.json, {
|
|
33975
34580
|
ok: true,
|
|
33976
34581
|
command: "run",
|
|
33977
|
-
|
|
34582
|
+
execution: {
|
|
34583
|
+
status: "not_started",
|
|
34584
|
+
mode: "live",
|
|
34585
|
+
defaultHarness: target.defaultHarness,
|
|
34586
|
+
authentication: "not_checked",
|
|
34587
|
+
prerequisites: target.prerequisites,
|
|
34588
|
+
nextSteps: {
|
|
34589
|
+
harnesses: "kxm harness list",
|
|
34590
|
+
drive: `kxm runs drive ${run.runId} --wait`,
|
|
34591
|
+
status: `kxm runs status ${run.runId} --json`,
|
|
34592
|
+
receipt: `kxm runs receipt ${run.runId} --json`
|
|
34593
|
+
}
|
|
34594
|
+
},
|
|
33978
34595
|
idempotent: acceptance.idempotent === true,
|
|
33979
34596
|
run,
|
|
33980
34597
|
supervisor: { runtimeId: supervisor.runtimeId, port: supervisor.port, started: supervisor.started }
|
|
33981
34598
|
}, `run ${run.status}: ${run.runId} (home ${run.homeRuntimeId.slice(0, 12)}\u2026, config ${run.configRevision.slice(0, 19)}\u2026)
|
|
33982
|
-
${runEngineNotice(run.runId)}`);
|
|
34599
|
+
${runEngineNotice(run.runId, target.defaultHarness, target.prerequisites)}`);
|
|
33983
34600
|
return 0;
|
|
33984
34601
|
} catch (error) {
|
|
33985
34602
|
if (error instanceof KxmConfigError) {
|
|
@@ -34063,7 +34680,7 @@ function parseDriveWaitTimeoutMs(raw) {
|
|
|
34063
34680
|
return value;
|
|
34064
34681
|
}
|
|
34065
34682
|
function sleepMs(ms) {
|
|
34066
|
-
return new Promise((
|
|
34683
|
+
return new Promise((resolve34) => setTimeout(resolve34, ms));
|
|
34067
34684
|
}
|
|
34068
34685
|
function driveWaitCompleted(drive) {
|
|
34069
34686
|
return drive?.verified === true && drive.receipt?.settlement?.kind === "terminal" && drive.receipt.settlement.status === "completed";
|
|
@@ -34260,9 +34877,20 @@ async function cmdTenantStatus(runtime) {
|
|
|
34260
34877
|
}
|
|
34261
34878
|
}
|
|
34262
34879
|
async function cmdHarnessList(runtime) {
|
|
34263
|
-
|
|
34264
|
-
|
|
34265
|
-
|
|
34880
|
+
try {
|
|
34881
|
+
const projectRoot = discoverKxmProjectRoot(runtime.cwd);
|
|
34882
|
+
const defaultHarness = projectRoot ? String(loadKxmProject(projectRoot).project.value.defaultHarness ?? "pi") : void 0;
|
|
34883
|
+
const inventory = await probeHarnessesAsync({ env: runtime.env, defaultHarness });
|
|
34884
|
+
print(runtime.io, runtime.json, { ok: true, command: "harness list", ...inventory }, formatHarnessInventory(inventory));
|
|
34885
|
+
return 0;
|
|
34886
|
+
} catch (error) {
|
|
34887
|
+
if (error instanceof KxmConfigError) {
|
|
34888
|
+
print(runtime.io, runtime.json, { ok: false, command: "harness list", error: "harness_list_failed", issues: error.issues }, `harness list failed: ${error.message}`);
|
|
34889
|
+
return 1;
|
|
34890
|
+
}
|
|
34891
|
+
print(runtime.io, runtime.json, { ok: false, command: "harness list", error: "harness_list_io_failed" }, "harness list failed because a local operation did not complete");
|
|
34892
|
+
return 1;
|
|
34893
|
+
}
|
|
34266
34894
|
}
|
|
34267
34895
|
async function selectInventoryModel(runtime, requested) {
|
|
34268
34896
|
const models = listInventoryModels(runtime.dirs.workdir);
|
|
@@ -34270,7 +34898,7 @@ async function selectInventoryModel(runtime, requested) {
|
|
|
34270
34898
|
if (runtime.json || !process.stdin.isTTY || models.length === 0) return void 0;
|
|
34271
34899
|
runtime.io.stdout(models.slice(0, 100).map((model, index2) => `${index2 + 1}. ${model}`).join("\n") + "\nSelect model number: ");
|
|
34272
34900
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
34273
|
-
const answer = await new Promise((
|
|
34901
|
+
const answer = await new Promise((resolve34) => rl.once("line", resolve34));
|
|
34274
34902
|
rl.close();
|
|
34275
34903
|
const index = Number.parseInt(answer.trim(), 10) - 1;
|
|
34276
34904
|
return Number.isInteger(index) && index >= 0 && index < models.length ? models[index] : void 0;
|
|
@@ -34296,7 +34924,7 @@ async function cmdModelsScreen(runtime) {
|
|
|
34296
34924
|
return 2;
|
|
34297
34925
|
}
|
|
34298
34926
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
34299
|
-
const ask = (q2) => new Promise((
|
|
34927
|
+
const ask = (q2) => new Promise((resolve34) => rl.question(q2, resolve34));
|
|
34300
34928
|
try {
|
|
34301
34929
|
while (true) {
|
|
34302
34930
|
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
@@ -34473,39 +35101,64 @@ async function cmdKxmRunReceipt(runtime, runId, options = {}) {
|
|
|
34473
35101
|
}
|
|
34474
35102
|
|
|
34475
35103
|
// plugins/kxm/src/cli/tasks.ts
|
|
34476
|
-
async function cmdSuggest(runtime, promptParts) {
|
|
35104
|
+
async function cmdSuggest(runtime, promptParts, probeHarnesses2 = probeHarnessesAsync) {
|
|
34477
35105
|
try {
|
|
34478
35106
|
const prompt = promptParts.join(" ").trim();
|
|
34479
35107
|
if (!prompt) {
|
|
34480
|
-
runtime.io.
|
|
35108
|
+
print(runtime.io, runtime.json, { ok: false, command: "suggest", error: "prompt_required" }, "prompt must be non-empty");
|
|
34481
35109
|
return 2;
|
|
34482
35110
|
}
|
|
34483
|
-
const inventory = await
|
|
34484
|
-
const
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
|
|
35111
|
+
const inventory = runtime.dryRun ? void 0 : await probeHarnesses2({ env: runtime.env });
|
|
35112
|
+
const suggestion = suggestWorkflowAndRoles(prompt, { availableHarnesses: inventory?.harnesses });
|
|
35113
|
+
if (suggestion.execution.supported && [".yaml", ".yml"].some((extension) => existsSync28(join32(runtime.cwd, ".kxm", "workflows", `${suggestion.workflowId}${extension}`)))) {
|
|
35114
|
+
suggestion.roles = [];
|
|
35115
|
+
suggestion.execution = {
|
|
35116
|
+
supported: false,
|
|
35117
|
+
error: "workflow_already_exists",
|
|
35118
|
+
reason: `Workflow ${suggestion.workflowId} already exists; its agents and permissions may differ from the recommended template.`,
|
|
35119
|
+
nextSteps: ["Review the existing definition and every agent's harness/model before running it, or install the recommended template under a fresh flat ID. No existing workflow is overwritten or recommended for execution."]
|
|
35120
|
+
};
|
|
35121
|
+
}
|
|
35122
|
+
const execution = suggestion.execution;
|
|
34489
35123
|
const text = [
|
|
34490
35124
|
`Suggested Workflow: ${suggestion.workflowId} (${suggestion.area})`,
|
|
35125
|
+
`Template: ${suggestion.template}`,
|
|
34491
35126
|
`Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`,
|
|
34492
35127
|
`Reasons: ${suggestion.reasons.join("; ")}`,
|
|
34493
35128
|
`Suggested Skills: ${suggestion.suggestedSkills.join(", ") || "none"}`,
|
|
34494
|
-
|
|
34495
|
-
|
|
34496
|
-
`
|
|
34497
|
-
|
|
34498
|
-
|
|
34499
|
-
|
|
34500
|
-
|
|
34501
|
-
|
|
35129
|
+
"",
|
|
35130
|
+
"Install definition only (requires kxm init; does not execute):",
|
|
35131
|
+
` ${suggestion.suggestedCommand}`,
|
|
35132
|
+
"",
|
|
35133
|
+
...execution.supported ? [
|
|
35134
|
+
"Suggested agent routing (not applied):",
|
|
35135
|
+
...suggestion.roles.map((role) => ` ${role.agent}: ${role.harness} (${role.role}; use a configured compatible model)`),
|
|
35136
|
+
"Prerequisites:",
|
|
35137
|
+
...execution.prerequisites.map((step) => ` ${step}`),
|
|
35138
|
+
`Create a run only (${execution.shell}; does not execute steps):`,
|
|
35139
|
+
` ${execution.createCommand}`,
|
|
35140
|
+
"Then drive the returned run ID with live calls and inspect its result:",
|
|
35141
|
+
` ${execution.driveCommand}`,
|
|
35142
|
+
` ${execution.statusCommand}`,
|
|
35143
|
+
` ${execution.receiptCommand}`
|
|
35144
|
+
] : [
|
|
35145
|
+
`Execution unavailable: ${execution.reason}`,
|
|
35146
|
+
...execution.nextSteps.map((step) => ` ${step}`),
|
|
35147
|
+
...runtime.dryRun ? ["Harness authentication was not probed during --dry-run."] : []
|
|
35148
|
+
]
|
|
34502
35149
|
].join("\n");
|
|
34503
|
-
print(runtime.io, runtime.json, {
|
|
34504
|
-
|
|
35150
|
+
print(runtime.io, runtime.json, {
|
|
35151
|
+
ok: execution.supported,
|
|
35152
|
+
command: "suggest",
|
|
35153
|
+
prompt,
|
|
35154
|
+
...suggestion,
|
|
35155
|
+
...runtime.dryRun ? { dryRun: true } : {},
|
|
35156
|
+
...!execution.supported ? { error: execution.error } : {}
|
|
35157
|
+
}, text);
|
|
35158
|
+
return execution.supported ? 0 : 1;
|
|
34505
35159
|
} catch (error) {
|
|
34506
35160
|
const message = error instanceof Error ? error.message : String(error);
|
|
34507
|
-
runtime.io.
|
|
34508
|
-
`);
|
|
35161
|
+
print(runtime.io, runtime.json, { ok: false, command: "suggest", error: "suggest_failed", detail: message }, `suggest failed: ${message}`);
|
|
34509
35162
|
return 1;
|
|
34510
35163
|
}
|
|
34511
35164
|
}
|
|
@@ -34625,27 +35278,21 @@ async function cmdTaskRun(runtime, taskId) {
|
|
|
34625
35278
|
`);
|
|
34626
35279
|
return 1;
|
|
34627
35280
|
}
|
|
34628
|
-
const workflow = task.assignedWorkflow
|
|
35281
|
+
const workflow = task.assignedWorkflow;
|
|
34629
35282
|
if (runtime.dryRun) {
|
|
34630
|
-
const target = resolveKxmRunTarget(runtime, workflow);
|
|
35283
|
+
const target = resolveKxmRunTarget(runtime, workflow, true);
|
|
34631
35284
|
if (typeof target === "number") return target;
|
|
34632
|
-
const started = updateTaskStatus(runtime.cwd, taskId, "in_progress", { dryRun: true });
|
|
34633
35285
|
printPlan(
|
|
34634
35286
|
runtime,
|
|
34635
|
-
{ command: "task run", taskId, ...target, status:
|
|
35287
|
+
{ command: "task run", taskId, ...target, status: task.status, execution: { status: "not_started", mode: "live" } },
|
|
34636
35288
|
[
|
|
34637
|
-
{ action: "request", target: "POST kxm-runtime /v1/runs (starts the Runtime supervisor if it is not running)" }
|
|
34638
|
-
{ action: "write", target: taskFilePath(runtime.cwd, taskId) }
|
|
35289
|
+
{ action: "request", target: "POST kxm-runtime /v1/runs (starts the Runtime supervisor if it is not running)" }
|
|
34639
35290
|
],
|
|
34640
|
-
`
|
|
35291
|
+
`create workflow ${target.workflowId} for task ${taskId}; task status stays ${task.status} until work actually starts`
|
|
34641
35292
|
);
|
|
34642
35293
|
return 0;
|
|
34643
35294
|
}
|
|
34644
|
-
|
|
34645
|
-
if (exitCode === 0) {
|
|
34646
|
-
updateTaskStatus(runtime.cwd, taskId, "in_progress");
|
|
34647
|
-
}
|
|
34648
|
-
return exitCode;
|
|
35295
|
+
return await cmdKxmRun(runtime, workflow, [task.objective], true);
|
|
34649
35296
|
} catch (error) {
|
|
34650
35297
|
const message = error instanceof Error ? error.message : String(error);
|
|
34651
35298
|
runtime.io.stderr(`task run failed: ${message}
|
|
@@ -34679,12 +35326,12 @@ async function cmdStudioLayout(runtime, workflowPath) {
|
|
|
34679
35326
|
const projectRoot = discoverKxmProjectRoot(runtime.cwd) ?? runtime.cwd;
|
|
34680
35327
|
let filePath = workflowPath;
|
|
34681
35328
|
if (!filePath) {
|
|
34682
|
-
filePath =
|
|
35329
|
+
filePath = resolve23(projectRoot, ".kxm", "workflows", "default.yaml");
|
|
34683
35330
|
}
|
|
34684
35331
|
let yamlContent;
|
|
34685
35332
|
let workflowId = "default";
|
|
34686
|
-
if (
|
|
34687
|
-
yamlContent =
|
|
35333
|
+
if (existsSync28(filePath)) {
|
|
35334
|
+
yamlContent = readFileSync27(filePath, "utf8");
|
|
34688
35335
|
} else {
|
|
34689
35336
|
yamlContent = `schema: kxm.workflow.v1
|
|
34690
35337
|
description: Feature implementation workflow
|
|
@@ -34757,15 +35404,15 @@ async function cmdStudioServe(runtime, options) {
|
|
|
34757
35404
|
sessionToken,
|
|
34758
35405
|
planProvider: () => {
|
|
34759
35406
|
try {
|
|
34760
|
-
const wfDir =
|
|
34761
|
-
const defaultPath =
|
|
34762
|
-
let targetPath =
|
|
34763
|
-
if (!targetPath &&
|
|
35407
|
+
const wfDir = join32(runtime.cwd, ".kxm", "workflows");
|
|
35408
|
+
const defaultPath = join32(wfDir, "default.yaml");
|
|
35409
|
+
let targetPath = existsSync28(defaultPath) ? defaultPath : void 0;
|
|
35410
|
+
if (!targetPath && existsSync28(wfDir)) {
|
|
34764
35411
|
const yml = readdirSync10(wfDir).find((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
34765
|
-
if (yml) targetPath =
|
|
35412
|
+
if (yml) targetPath = join32(wfDir, yml);
|
|
34766
35413
|
}
|
|
34767
|
-
if (targetPath &&
|
|
34768
|
-
const raw =
|
|
35414
|
+
if (targetPath && existsSync28(targetPath)) {
|
|
35415
|
+
const raw = readFileSync27(targetPath, "utf8");
|
|
34769
35416
|
const parsed = (0, import_yaml15.parse)(raw);
|
|
34770
35417
|
const wfId = basename7(targetPath).replace(/\.(yaml|yml)$/, "");
|
|
34771
35418
|
return compileKxmWorkflow({ id: wfId, value: parsed });
|
|
@@ -34815,16 +35462,16 @@ Press Ctrl+C to stop.
|
|
|
34815
35462
|
}
|
|
34816
35463
|
|
|
34817
35464
|
// plugins/kxm/src/cli/context-skills.ts
|
|
34818
|
-
import { readFileSync as
|
|
34819
|
-
import { join as
|
|
35465
|
+
import { readFileSync as readFileSync28 } from "node:fs";
|
|
35466
|
+
import { join as join34, relative as relative6, resolve as resolve24 } from "node:path";
|
|
34820
35467
|
|
|
34821
35468
|
// plugins/kxm/src/wiki.ts
|
|
34822
35469
|
import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync21 } from "node:fs";
|
|
34823
|
-
import { join as
|
|
35470
|
+
import { join as join33 } from "node:path";
|
|
34824
35471
|
function writeCompiledWiki(root, wiki) {
|
|
34825
35472
|
const written = [];
|
|
34826
35473
|
for (const [relativePath, content] of [...wiki.pages].sort(([left], [right]) => left.localeCompare(right))) {
|
|
34827
|
-
const absolute =
|
|
35474
|
+
const absolute = join33(root, relativePath);
|
|
34828
35475
|
mkdirSync23(absolute.slice(0, absolute.lastIndexOf("/")), { recursive: true });
|
|
34829
35476
|
writeFileSync21(absolute, content);
|
|
34830
35477
|
written.push(relativePath);
|
|
@@ -34976,7 +35623,7 @@ async function cmdContextWikiCompile(runtime, project, options) {
|
|
|
34976
35623
|
printPlan(
|
|
34977
35624
|
runtime,
|
|
34978
35625
|
{ command: "context wiki-compile", project, pages: compiled.audit.pages, openContradictions: compiled.audit.contradictions, outDir },
|
|
34979
|
-
compiled.pages.map((page) => ({ action: "write", target:
|
|
35626
|
+
compiled.pages.map((page) => ({ action: "write", target: resolve24(outDir, page.path) })),
|
|
34980
35627
|
`write ${compiled.pages.length} compiled wiki page(s) to ${outDir}`
|
|
34981
35628
|
);
|
|
34982
35629
|
return 0;
|
|
@@ -35011,7 +35658,7 @@ function skillStateFromFlag(value) {
|
|
|
35011
35658
|
throw new Error(`invalid skill state ${value}`);
|
|
35012
35659
|
}
|
|
35013
35660
|
function skillsRoot(runtime) {
|
|
35014
|
-
return
|
|
35661
|
+
return join34(runtime.dirs.workdir, ".kxm", "skills");
|
|
35015
35662
|
}
|
|
35016
35663
|
function csv(value) {
|
|
35017
35664
|
if (!value) return void 0;
|
|
@@ -35028,7 +35675,7 @@ async function cmdSkillsCreate(runtime, options) {
|
|
|
35028
35675
|
const metadata = lifecycle.create({
|
|
35029
35676
|
name: options.name,
|
|
35030
35677
|
description: options.description ?? "",
|
|
35031
|
-
content:
|
|
35678
|
+
content: readFileSync28(options.file, "utf8"),
|
|
35032
35679
|
createdBy: options.createdBy,
|
|
35033
35680
|
sources: {
|
|
35034
35681
|
runIds: csv(options.run) ?? [],
|
|
@@ -35190,7 +35837,7 @@ async function cmdMemorySync(runtime) {
|
|
|
35190
35837
|
printPlan(
|
|
35191
35838
|
runtime,
|
|
35192
35839
|
{ command: "memory sync", ...result },
|
|
35193
|
-
result.updated.map((file) => ({ action: "write", target:
|
|
35840
|
+
result.updated.map((file) => ({ action: "write", target: join34(runtime.cwd, file) })),
|
|
35194
35841
|
"regenerate the project memory block in the instruction files this project already has"
|
|
35195
35842
|
);
|
|
35196
35843
|
return 0;
|
|
@@ -35218,8 +35865,8 @@ ${lines.join("\n")}`
|
|
|
35218
35865
|
|
|
35219
35866
|
// plugins/kxm/src/cli/hub.ts
|
|
35220
35867
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
35221
|
-
import { existsSync as
|
|
35222
|
-
import { join as
|
|
35868
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync27, readFileSync as readFileSync34, readdirSync as readdirSync12, rmSync as rmSync11, writeFileSync as writeFileSync24 } from "node:fs";
|
|
35869
|
+
import { join as join44, resolve as resolve27 } from "node:path";
|
|
35223
35870
|
|
|
35224
35871
|
// node_modules/marked/lib/marked.esm.js
|
|
35225
35872
|
function M() {
|
|
@@ -39381,10 +40028,10 @@ var TuiBase = class _TuiBase extends Container {
|
|
|
39381
40028
|
* @returns Promise containing the parsed RGB color, or undefined if it times out or fails to parse.
|
|
39382
40029
|
*/
|
|
39383
40030
|
queryTerminalBackgroundColor({ timeoutMs }) {
|
|
39384
|
-
return new Promise((
|
|
40031
|
+
return new Promise((resolve34) => {
|
|
39385
40032
|
const query = {
|
|
39386
40033
|
settled: false,
|
|
39387
|
-
resolve:
|
|
40034
|
+
resolve: resolve34,
|
|
39388
40035
|
timer: void 0
|
|
39389
40036
|
};
|
|
39390
40037
|
query.timer = setTimeout(() => {
|
|
@@ -39407,7 +40054,7 @@ var TuiBase = class _TuiBase extends Container {
|
|
|
39407
40054
|
* `CSI ? 997 ; 1 n` for dark or `CSI ? 997 ; 2 n` for light.
|
|
39408
40055
|
*/
|
|
39409
40056
|
queryTerminalColorScheme({ timeoutMs }) {
|
|
39410
|
-
return new Promise((
|
|
40057
|
+
return new Promise((resolve34) => {
|
|
39411
40058
|
let settled = false;
|
|
39412
40059
|
let timer;
|
|
39413
40060
|
let unsubscribe = () => {
|
|
@@ -39421,7 +40068,7 @@ var TuiBase = class _TuiBase extends Container {
|
|
|
39421
40068
|
timer = void 0;
|
|
39422
40069
|
}
|
|
39423
40070
|
unsubscribe();
|
|
39424
|
-
|
|
40071
|
+
resolve34(scheme);
|
|
39425
40072
|
};
|
|
39426
40073
|
unsubscribe = this.onTerminalColorSchemeChange(settle);
|
|
39427
40074
|
timer = setTimeout(() => settle(void 0), timeoutMs);
|
|
@@ -41152,19 +41799,19 @@ import * as path2 from "node:path";
|
|
|
41152
41799
|
|
|
41153
41800
|
// node_modules/@earendil-works/pi-tui/dist/native-module-path.js
|
|
41154
41801
|
import { createRequire as createRequire2 } from "node:module";
|
|
41155
|
-
import { dirname as
|
|
41802
|
+
import { dirname as dirname18, join as join35 } from "node:path";
|
|
41156
41803
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
41157
41804
|
var moduleRequire = createRequire2(import.meta.url);
|
|
41158
41805
|
var TUI_PACKAGE_NAME = "@earendil-works/pi-tui";
|
|
41159
41806
|
function getNativeModuleCandidates(nativePath, options = {}) {
|
|
41160
|
-
const moduleDir =
|
|
41807
|
+
const moduleDir = dirname18(fileURLToPath2(options.moduleUrl ?? import.meta.url));
|
|
41161
41808
|
const candidates = [];
|
|
41162
41809
|
try {
|
|
41163
41810
|
const packageEntry = (options.resolvePackage ?? moduleRequire.resolve)(TUI_PACKAGE_NAME);
|
|
41164
|
-
candidates.push(
|
|
41811
|
+
candidates.push(join35(dirname18(packageEntry), "..", nativePath));
|
|
41165
41812
|
} catch {
|
|
41166
41813
|
}
|
|
41167
|
-
candidates.push(
|
|
41814
|
+
candidates.push(join35(moduleDir, "..", nativePath), join35(moduleDir, nativePath), join35(dirname18(options.execPath ?? process.execPath), nativePath));
|
|
41168
41815
|
return Array.from(new Set(candidates));
|
|
41169
41816
|
}
|
|
41170
41817
|
|
|
@@ -41505,7 +42152,7 @@ var ProcessTerminal = class {
|
|
|
41505
42152
|
break;
|
|
41506
42153
|
if (now - lastDataTime >= idleMs)
|
|
41507
42154
|
break;
|
|
41508
|
-
await new Promise((
|
|
42155
|
+
await new Promise((resolve34) => setTimeout(resolve34, Math.min(idleMs, timeLeft)));
|
|
41509
42156
|
}
|
|
41510
42157
|
} finally {
|
|
41511
42158
|
process.stdin.removeListener("data", onData);
|
|
@@ -43621,7 +44268,7 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
|
|
|
43621
44268
|
|
|
43622
44269
|
// plugins/kxm/src/tui.ts
|
|
43623
44270
|
import { mkdirSync as mkdirSync24 } from "node:fs";
|
|
43624
|
-
import { join as
|
|
44271
|
+
import { join as join39, resolve as resolve26, dirname as dirname19 } from "node:path";
|
|
43625
44272
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
43626
44273
|
|
|
43627
44274
|
// packages/core/tui/src/types/surface.ts
|
|
@@ -43762,9 +44409,9 @@ var SHARED_EFFECT_KINDS = Object.freeze([
|
|
|
43762
44409
|
]);
|
|
43763
44410
|
|
|
43764
44411
|
// plugins/kxm/src/local-snapshot.ts
|
|
43765
|
-
import { existsSync as
|
|
44412
|
+
import { existsSync as existsSync29, readdirSync as readdirSync11, readFileSync as readFileSync29 } from "node:fs";
|
|
43766
44413
|
import { homedir as homedir6 } from "node:os";
|
|
43767
|
-
import { isAbsolute as isAbsolute9, join as
|
|
44414
|
+
import { isAbsolute as isAbsolute9, join as join38, resolve as resolve25 } from "node:path";
|
|
43768
44415
|
var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
|
|
43769
44416
|
var RUNTIME_PROJECT_KEY = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
43770
44417
|
function processExists2(pid) {
|
|
@@ -43889,36 +44536,36 @@ function readOpenMessageMetadata(database, where = OPEN_MESSAGE_WHERE, params =
|
|
|
43889
44536
|
return messages;
|
|
43890
44537
|
}
|
|
43891
44538
|
function resolveKxmSnapshotPaths(cwd, env = process.env) {
|
|
43892
|
-
const stateDir = env.KXM_STATE_DIR?.trim() ||
|
|
44539
|
+
const stateDir = env.KXM_STATE_DIR?.trim() || join38(cwd, ".kxm", "state");
|
|
43893
44540
|
const configured = env.KXM_DATA_PATH?.trim();
|
|
43894
|
-
const dataPath = configured ?
|
|
44541
|
+
const dataPath = configured ? resolve25(cwd, configured) : join38(stateDir, "kxm.db");
|
|
43895
44542
|
return { dataPath, stateDir };
|
|
43896
44543
|
}
|
|
43897
44544
|
function resolveKxmStateRoot(stateDir, options) {
|
|
43898
|
-
if (options?.kxmStateRoot &&
|
|
43899
|
-
return
|
|
44545
|
+
if (options?.kxmStateRoot && existsSync29(options.kxmStateRoot)) {
|
|
44546
|
+
return resolve25(options.kxmStateRoot);
|
|
43900
44547
|
}
|
|
43901
|
-
if (
|
|
44548
|
+
if (existsSync29(join38(stateDir, "runtime", "registry.db")) || existsSync29(join38(stateDir, "runtime", "projects"))) {
|
|
43902
44549
|
return stateDir;
|
|
43903
44550
|
}
|
|
43904
44551
|
const env = options?.env ?? process.env;
|
|
43905
44552
|
const explicit = env.KXM_STATE_HOME?.trim() || env.KXM_USER_STATE_DIR?.trim() || env.KXM_STATE_ROOT?.trim();
|
|
43906
|
-
if (explicit && isAbsolute9(explicit) &&
|
|
43907
|
-
return
|
|
44553
|
+
if (explicit && isAbsolute9(explicit) && existsSync29(resolve25(explicit))) {
|
|
44554
|
+
return resolve25(explicit);
|
|
43908
44555
|
}
|
|
43909
44556
|
let base;
|
|
43910
44557
|
if (process.platform === "win32") {
|
|
43911
44558
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
43912
|
-
base = localAppData && isAbsolute9(localAppData) ? localAppData :
|
|
43913
|
-
base =
|
|
44559
|
+
base = localAppData && isAbsolute9(localAppData) ? localAppData : join38(homedir6(), "AppData", "Local");
|
|
44560
|
+
base = resolve25(base, "KXM");
|
|
43914
44561
|
} else if (process.platform === "darwin") {
|
|
43915
|
-
base =
|
|
44562
|
+
base = resolve25(homedir6(), "Library", "Application Support", "KXM");
|
|
43916
44563
|
} else {
|
|
43917
44564
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
43918
|
-
base = xdgState && isAbsolute9(xdgState) ? xdgState :
|
|
43919
|
-
base =
|
|
44565
|
+
base = xdgState && isAbsolute9(xdgState) ? xdgState : join38(homedir6(), ".local", "state");
|
|
44566
|
+
base = resolve25(base, "kxm");
|
|
43920
44567
|
}
|
|
43921
|
-
if (
|
|
44568
|
+
if (existsSync29(base)) return base;
|
|
43922
44569
|
return void 0;
|
|
43923
44570
|
}
|
|
43924
44571
|
function readRuntimeRuns(eventDb, projectId) {
|
|
@@ -43951,7 +44598,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
43951
44598
|
let legacyRuns = [];
|
|
43952
44599
|
let legacyRunTotal = 0;
|
|
43953
44600
|
let plans = [];
|
|
43954
|
-
if (
|
|
44601
|
+
if (existsSync29(dataPath)) {
|
|
43955
44602
|
hasLegacy = true;
|
|
43956
44603
|
const database = openReadOnlyDatabase(dataPath);
|
|
43957
44604
|
try {
|
|
@@ -43982,11 +44629,11 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
43982
44629
|
let kxmRunTotal = 0;
|
|
43983
44630
|
const kxmStateRoot = scope && !scope.runtimeProjectId ? void 0 : resolveKxmStateRoot(stateDir, options);
|
|
43984
44631
|
if (kxmStateRoot) {
|
|
43985
|
-
const runtimeDir =
|
|
43986
|
-
const registryDbPath =
|
|
43987
|
-
const projectsDir =
|
|
44632
|
+
const runtimeDir = join38(kxmStateRoot, "runtime");
|
|
44633
|
+
const registryDbPath = join38(runtimeDir, "registry.db");
|
|
44634
|
+
const projectsDir = join38(runtimeDir, "projects");
|
|
43988
44635
|
const projectKeys = /* @__PURE__ */ new Set();
|
|
43989
|
-
if (
|
|
44636
|
+
if (existsSync29(registryDbPath)) {
|
|
43990
44637
|
hasKxm = true;
|
|
43991
44638
|
try {
|
|
43992
44639
|
const regDb = openReadOnlyDatabase(registryDbPath);
|
|
@@ -44002,7 +44649,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
44002
44649
|
} catch {
|
|
44003
44650
|
}
|
|
44004
44651
|
}
|
|
44005
|
-
if (!scope &&
|
|
44652
|
+
if (!scope && existsSync29(projectsDir)) {
|
|
44006
44653
|
try {
|
|
44007
44654
|
for (const entry of readdirSync11(projectsDir, { withFileTypes: true })) {
|
|
44008
44655
|
if (entry.isDirectory()) {
|
|
@@ -44014,8 +44661,8 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
44014
44661
|
}
|
|
44015
44662
|
for (const key of projectKeys) {
|
|
44016
44663
|
if (scope && !RUNTIME_PROJECT_KEY.test(key)) continue;
|
|
44017
|
-
const eventDbPath =
|
|
44018
|
-
if (
|
|
44664
|
+
const eventDbPath = join38(projectsDir, key, "run-events.db");
|
|
44665
|
+
if (existsSync29(eventDbPath)) {
|
|
44019
44666
|
hasKxm = true;
|
|
44020
44667
|
try {
|
|
44021
44668
|
const eventDb = openReadOnlyDatabase(eventDbPath);
|
|
@@ -44033,10 +44680,10 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
44033
44680
|
}
|
|
44034
44681
|
}
|
|
44035
44682
|
const pids = [];
|
|
44036
|
-
if (
|
|
44683
|
+
if (existsSync29(stateDir)) {
|
|
44037
44684
|
for (const file of readdirSync11(stateDir).filter((name) => name.endsWith(".pid"))) {
|
|
44038
44685
|
try {
|
|
44039
|
-
const record = JSON.parse(
|
|
44686
|
+
const record = JSON.parse(readFileSync29(join38(stateDir, file), "utf8"));
|
|
44040
44687
|
pids.push({
|
|
44041
44688
|
file,
|
|
44042
44689
|
...record.role ? { role: record.role } : {},
|
|
@@ -44075,8 +44722,8 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
44075
44722
|
} else {
|
|
44076
44723
|
source = "legacy";
|
|
44077
44724
|
}
|
|
44078
|
-
const telemetryFile =
|
|
44079
|
-
const spend =
|
|
44725
|
+
const telemetryFile = join38(stateDir, "telemetry.jsonl");
|
|
44726
|
+
const spend = existsSync29(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
|
|
44080
44727
|
return {
|
|
44081
44728
|
source,
|
|
44082
44729
|
agents: agents.sort((a, b2) => Number(b2.online) - Number(a.online) || a.name.localeCompare(b2.name)),
|
|
@@ -44210,11 +44857,11 @@ function spawnDegradeWorktree(repoRoot, runId, options) {
|
|
|
44210
44857
|
const cleanRunId = runId.replace(/^run_/, "");
|
|
44211
44858
|
const desc = options?.description ? slugifyBranchPart(options.description, 30) : "degrade";
|
|
44212
44859
|
const branchName = `kxm/run-${cleanRunId}-${desc}`;
|
|
44213
|
-
const relativeWtPath =
|
|
44214
|
-
const absoluteWtPath =
|
|
44860
|
+
const relativeWtPath = join39(".kxm", "worktrees", `run-${cleanRunId}-${desc}`);
|
|
44861
|
+
const absoluteWtPath = resolve26(repoRoot, relativeWtPath);
|
|
44215
44862
|
const jumpCommand = `cd "${absoluteWtPath}"`;
|
|
44216
44863
|
try {
|
|
44217
|
-
mkdirSync24(
|
|
44864
|
+
mkdirSync24(dirname19(absoluteWtPath), { recursive: true });
|
|
44218
44865
|
const branchCheck = runner("git", ["rev-parse", "--verify", `refs/heads/${branchName}`]);
|
|
44219
44866
|
let wtRes;
|
|
44220
44867
|
if (branchCheck.status === 0) {
|
|
@@ -44612,11 +45259,11 @@ async function readJson(response) {
|
|
|
44612
45259
|
}
|
|
44613
45260
|
async function waitForReconnect(signal, milliseconds = 1e3) {
|
|
44614
45261
|
if (signal.aborted) return;
|
|
44615
|
-
await new Promise((
|
|
45262
|
+
await new Promise((resolve34) => {
|
|
44616
45263
|
const done = () => {
|
|
44617
45264
|
clearTimeout(timer);
|
|
44618
45265
|
signal.removeEventListener("abort", done);
|
|
44619
|
-
|
|
45266
|
+
resolve34();
|
|
44620
45267
|
};
|
|
44621
45268
|
const timer = setTimeout(done, milliseconds);
|
|
44622
45269
|
timer.unref();
|
|
@@ -44979,8 +45626,8 @@ async function runMeshTui(input) {
|
|
|
44979
45626
|
// plugins/kxm/src/session-work.ts
|
|
44980
45627
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
44981
45628
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
44982
|
-
import { existsSync as
|
|
44983
|
-
import { join as
|
|
45629
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync25, readFileSync as readFileSync30, renameSync as renameSync8, writeFileSync as writeFileSync22 } from "node:fs";
|
|
45630
|
+
import { join as join40 } from "node:path";
|
|
44984
45631
|
var MAX_SESSION_BRIEF_TASKS = 5;
|
|
44985
45632
|
var MAX_SESSION_BRIEF_PLANS = 5;
|
|
44986
45633
|
var SESSION_BRIEF_SCHEMA = "kxm.session-brief.v1";
|
|
@@ -45137,10 +45784,10 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
45137
45784
|
};
|
|
45138
45785
|
}
|
|
45139
45786
|
function readCachedSessionBrief(stateDir) {
|
|
45140
|
-
const file =
|
|
45787
|
+
const file = join40(stateDir, "session-brief.json");
|
|
45141
45788
|
try {
|
|
45142
|
-
if (!
|
|
45143
|
-
const raw =
|
|
45789
|
+
if (!existsSync30(file)) return void 0;
|
|
45790
|
+
const raw = readFileSync30(file, "utf8");
|
|
45144
45791
|
const parsed = JSON.parse(raw);
|
|
45145
45792
|
if (parsed && parsed.schema === "kxm.session-brief.v1" && typeof parsed.generatedAt === "string") {
|
|
45146
45793
|
const ageMs = Date.now() - Date.parse(parsed.generatedAt);
|
|
@@ -45156,7 +45803,7 @@ function readCachedSessionBrief(stateDir) {
|
|
|
45156
45803
|
function writeCachedSessionBrief(stateDir, brief) {
|
|
45157
45804
|
try {
|
|
45158
45805
|
mkdirSync25(stateDir, { recursive: true, mode: 448 });
|
|
45159
|
-
const file =
|
|
45806
|
+
const file = join40(stateDir, "session-brief.json");
|
|
45160
45807
|
const tmp = `${file}.tmp.${randomUUID12().slice(0, 8)}`;
|
|
45161
45808
|
writeFileSync22(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
|
|
45162
45809
|
renameSync8(tmp, file);
|
|
@@ -45165,8 +45812,8 @@ function writeCachedSessionBrief(stateDir, brief) {
|
|
|
45165
45812
|
}
|
|
45166
45813
|
function estimateSessionCost(stateDir) {
|
|
45167
45814
|
try {
|
|
45168
|
-
const telemetryFile =
|
|
45169
|
-
if (!
|
|
45815
|
+
const telemetryFile = join40(stateDir, "telemetry.jsonl");
|
|
45816
|
+
if (!existsSync30(telemetryFile)) return void 0;
|
|
45170
45817
|
const records = readRoutingRecords(telemetryFile);
|
|
45171
45818
|
if (records.length === 0) return void 0;
|
|
45172
45819
|
let totalCost = 0;
|
|
@@ -45334,11 +45981,11 @@ function formatSessionBriefText(brief) {
|
|
|
45334
45981
|
}
|
|
45335
45982
|
|
|
45336
45983
|
// plugins/kxm/src/kxm-install-kind.ts
|
|
45337
|
-
import { existsSync as
|
|
45338
|
-
import { dirname as
|
|
45984
|
+
import { existsSync as existsSync31, readFileSync as readFileSync31 } from "node:fs";
|
|
45985
|
+
import { dirname as dirname20, join as join41 } from "node:path";
|
|
45339
45986
|
function packageIsKxm(root) {
|
|
45340
45987
|
try {
|
|
45341
|
-
const pkg = JSON.parse(
|
|
45988
|
+
const pkg = JSON.parse(readFileSync31(join41(root, "package.json"), "utf8"));
|
|
45342
45989
|
return pkg.name === "@kontextmind/kxm";
|
|
45343
45990
|
} catch {
|
|
45344
45991
|
return false;
|
|
@@ -45387,12 +46034,12 @@ function report(kind, root) {
|
|
|
45387
46034
|
return { kind, root, instruction: installKindInstruction(kind, root) };
|
|
45388
46035
|
}
|
|
45389
46036
|
function classifyInstallRoot(probe) {
|
|
45390
|
-
const piGitRoot =
|
|
46037
|
+
const piGitRoot = join41(probe.homeDir, ".pi", "agent", "git");
|
|
45391
46038
|
if (isUnder(probe.repoRoot, piGitRoot, probe.platform)) return report("pi-git", probe.repoRoot);
|
|
45392
|
-
const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() ||
|
|
45393
|
-
const pluginsRoot =
|
|
46039
|
+
const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() || join41(probe.homeDir, ".claude");
|
|
46040
|
+
const pluginsRoot = join41(claudeHome, "plugins");
|
|
45394
46041
|
if (isUnder(probe.moduleDir, pluginsRoot, probe.platform)) return report("claude-marketplace", probe.moduleDir);
|
|
45395
|
-
const hasGit =
|
|
46042
|
+
const hasGit = existsSync31(join41(probe.repoRoot, ".git"));
|
|
45396
46043
|
if (hasGit && packageIsKxm(probe.repoRoot)) return report("source", probe.repoRoot);
|
|
45397
46044
|
if (isNpmPackageLayout(probe.repoRoot, probe.platform) && packageIsKxm(probe.repoRoot) && !hasGit) {
|
|
45398
46045
|
return report("npm-package", probe.repoRoot);
|
|
@@ -45406,21 +46053,21 @@ function resolveInstallKind(probe, npmGlobalRoot) {
|
|
|
45406
46053
|
if (typeof globalRoot !== "string" || globalRoot.trim() === "") {
|
|
45407
46054
|
return report("unknown", classified.root);
|
|
45408
46055
|
}
|
|
45409
|
-
const expected =
|
|
46056
|
+
const expected = dirname20(dirname20(classified.root));
|
|
45410
46057
|
if (sameHostPath2(globalRoot.trim(), expected, probe.platform)) return report("npm-global", classified.root);
|
|
45411
46058
|
return report("npm-local", classified.root);
|
|
45412
46059
|
}
|
|
45413
46060
|
|
|
45414
46061
|
// plugins/kxm/src/kxm-update-config.ts
|
|
45415
46062
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
45416
|
-
import { existsSync as
|
|
45417
|
-
import { join as
|
|
46063
|
+
import { existsSync as existsSync32, readFileSync as readFileSync32 } from "node:fs";
|
|
46064
|
+
import { join as join42 } from "node:path";
|
|
45418
46065
|
function loadKxmUpdateConfig(env = process.env) {
|
|
45419
|
-
const path4 =
|
|
45420
|
-
if (!
|
|
46066
|
+
const path4 = join42(kxmUserStateRoot({ env }), "update.yaml");
|
|
46067
|
+
if (!existsSync32(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
|
|
45421
46068
|
let parsed;
|
|
45422
46069
|
try {
|
|
45423
|
-
parsed = (0, import_yaml16.parse)(
|
|
46070
|
+
parsed = (0, import_yaml16.parse)(readFileSync32(path4, "utf8"));
|
|
45424
46071
|
} catch {
|
|
45425
46072
|
throw new KxmUpdateConfigError("update.yaml is not valid YAML");
|
|
45426
46073
|
}
|
|
@@ -45446,8 +46093,8 @@ function loadKxmUpdateConfig(env = process.env) {
|
|
|
45446
46093
|
}
|
|
45447
46094
|
|
|
45448
46095
|
// plugins/kxm/src/session.ts
|
|
45449
|
-
import { existsSync as
|
|
45450
|
-
import { join as
|
|
46096
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync23 } from "node:fs";
|
|
46097
|
+
import { join as join43 } from "node:path";
|
|
45451
46098
|
var SESSION_SCHEMA = "kxm.session.v1";
|
|
45452
46099
|
var SessionConfigError = class extends Error {
|
|
45453
46100
|
code = "session_config_invalid";
|
|
@@ -45457,12 +46104,12 @@ var SessionConfigError = class extends Error {
|
|
|
45457
46104
|
}
|
|
45458
46105
|
};
|
|
45459
46106
|
function workflowAssetDirs(assetsDir, workflowId) {
|
|
45460
|
-
const root =
|
|
45461
|
-
return [root,
|
|
46107
|
+
const root = join43(assetsDir, "workflows", workflowId);
|
|
46108
|
+
return [root, join43(root, "inputs"), join43(root, "outputs"), join43(root, "generated")];
|
|
45462
46109
|
}
|
|
45463
46110
|
function sessionAssetDirs(assetsDir, sessionId) {
|
|
45464
|
-
const root =
|
|
45465
|
-
return [root,
|
|
46111
|
+
const root = join43(assetsDir, "sessions", sessionId);
|
|
46112
|
+
return [root, join43(root, "inputs"), join43(root, "outputs")];
|
|
45466
46113
|
}
|
|
45467
46114
|
function rosterNames(configDir) {
|
|
45468
46115
|
return [...loadRosterMap(configDir).values()].map((row) => String(row.name));
|
|
@@ -45512,7 +46159,7 @@ function workerFromRosterRow(name, row, project) {
|
|
|
45512
46159
|
function loadRosterMap(configDir) {
|
|
45513
46160
|
const byName = /* @__PURE__ */ new Map();
|
|
45514
46161
|
for (const [file, key] of [["agents.json", "agents"], ["gates.json", "gates"]]) {
|
|
45515
|
-
const path4 =
|
|
46162
|
+
const path4 = join43(configDir, file);
|
|
45516
46163
|
for (const row of loadRoster(path4, key)) {
|
|
45517
46164
|
const name = String(row.name).trim();
|
|
45518
46165
|
const lowered = name.toLowerCase();
|
|
@@ -45526,10 +46173,10 @@ function loadRosterMap(configDir) {
|
|
|
45526
46173
|
return byName;
|
|
45527
46174
|
}
|
|
45528
46175
|
function loadRoster(path4, key) {
|
|
45529
|
-
if (!
|
|
46176
|
+
if (!existsSync33(path4)) return [];
|
|
45530
46177
|
let parsed;
|
|
45531
46178
|
try {
|
|
45532
|
-
parsed = JSON.parse(
|
|
46179
|
+
parsed = JSON.parse(readFileSync33(path4, "utf8"));
|
|
45533
46180
|
} catch (error) {
|
|
45534
46181
|
throw new SessionConfigError(
|
|
45535
46182
|
`${key} roster at ${path4} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -45554,7 +46201,7 @@ function loadRoster(path4, key) {
|
|
|
45554
46201
|
});
|
|
45555
46202
|
}
|
|
45556
46203
|
function createSession(input) {
|
|
45557
|
-
const assetDir = input.mode === "workflow" && input.workflowId ?
|
|
46204
|
+
const assetDir = input.mode === "workflow" && input.workflowId ? join43("assets", "workflows", input.workflowId) : join43("assets", "sessions", input.id);
|
|
45558
46205
|
return {
|
|
45559
46206
|
schema: SESSION_SCHEMA,
|
|
45560
46207
|
id: input.id,
|
|
@@ -45567,8 +46214,8 @@ function createSession(input) {
|
|
|
45567
46214
|
};
|
|
45568
46215
|
}
|
|
45569
46216
|
function writeSession(assetsDir, session, dryRun = false) {
|
|
45570
|
-
const dir =
|
|
45571
|
-
const path4 =
|
|
46217
|
+
const dir = join43(assetsDir, "sessions", session.id);
|
|
46218
|
+
const path4 = join43(dir, "session.json");
|
|
45572
46219
|
if (!dryRun) {
|
|
45573
46220
|
mkdirSync26(dir, { recursive: true });
|
|
45574
46221
|
writeFileSync23(path4, `${JSON.stringify(session, null, 2)}
|
|
@@ -45579,7 +46226,7 @@ function writeSession(assetsDir, session, dryRun = false) {
|
|
|
45579
46226
|
|
|
45580
46227
|
// plugins/kxm/src/cli/hub.ts
|
|
45581
46228
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
45582
|
-
import { dirname as
|
|
46229
|
+
import { dirname as dirname21 } from "node:path";
|
|
45583
46230
|
import { homedir as homedir7 } from "node:os";
|
|
45584
46231
|
async function hubGet(url, fetchImpl) {
|
|
45585
46232
|
try {
|
|
@@ -45597,7 +46244,7 @@ async function hubGet(url, fetchImpl) {
|
|
|
45597
46244
|
}
|
|
45598
46245
|
function installProbeFrom(runtime) {
|
|
45599
46246
|
const partial = runtime.io.installProbe ?? {};
|
|
45600
|
-
const moduleDir = partial.moduleDir ??
|
|
46247
|
+
const moduleDir = partial.moduleDir ?? dirname21(fileURLToPath3(import.meta.url));
|
|
45601
46248
|
return {
|
|
45602
46249
|
moduleDir,
|
|
45603
46250
|
// Classify the install that is **running**, never the directory the caller
|
|
@@ -45618,13 +46265,13 @@ function moduleInstallRoot() {
|
|
|
45618
46265
|
try {
|
|
45619
46266
|
return findKxmRepoRoot(import.meta.url);
|
|
45620
46267
|
} catch {
|
|
45621
|
-
return
|
|
46268
|
+
return dirname21(fileURLToPath3(import.meta.url));
|
|
45622
46269
|
}
|
|
45623
46270
|
}
|
|
45624
46271
|
function warnIgnoredProjectUpdateYaml(runtime) {
|
|
45625
|
-
const projectFile =
|
|
45626
|
-
if (!
|
|
45627
|
-
const userFile =
|
|
46272
|
+
const projectFile = join44(runtime.dirs.workspace, "update.yaml");
|
|
46273
|
+
if (!existsSync34(projectFile)) return;
|
|
46274
|
+
const userFile = join44(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
45628
46275
|
runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
|
|
45629
46276
|
`);
|
|
45630
46277
|
}
|
|
@@ -45672,7 +46319,7 @@ async function cmdDash(runtime, options = {}) {
|
|
|
45672
46319
|
return 2;
|
|
45673
46320
|
}
|
|
45674
46321
|
const screen = requested;
|
|
45675
|
-
const dataPath =
|
|
46322
|
+
const dataPath = resolve27(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join44(runtime.dirs.state, "kxm.db"));
|
|
45676
46323
|
if (runtime.dryRun) {
|
|
45677
46324
|
print(runtime.io, runtime.json, {
|
|
45678
46325
|
ok: true,
|
|
@@ -45718,7 +46365,7 @@ async function cmdHub(runtime) {
|
|
|
45718
46365
|
config = loadKxmUpdateConfig(runtime.env);
|
|
45719
46366
|
} catch (error) {
|
|
45720
46367
|
if (error instanceof KxmUpdateConfigError) {
|
|
45721
|
-
const yamlPath =
|
|
46368
|
+
const yamlPath = join44(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
45722
46369
|
runtime.io.stderr(`kxm: ${error.message}; update check skipped; fix or remove ${yamlPath}
|
|
45723
46370
|
`);
|
|
45724
46371
|
} else {
|
|
@@ -45905,7 +46552,7 @@ async function cmdWorker(runtime, options) {
|
|
|
45905
46552
|
return await (runtime.io.spawnWorker ?? ((launchEnv) => spawnScript("kxm-worker.mjs", launchEnv)))(extraEnv);
|
|
45906
46553
|
}
|
|
45907
46554
|
async function cmdStop(runtime, waitMsFlag) {
|
|
45908
|
-
const pids =
|
|
46555
|
+
const pids = existsSync34(runtime.dirs.state) ? readdirSync12(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
|
|
45909
46556
|
if (runtime.dryRun) {
|
|
45910
46557
|
print(runtime.io, runtime.json, { ok: true, command: "stop", dryRun: true, pidFiles: pids }, "would signal pid files");
|
|
45911
46558
|
return 0;
|
|
@@ -45920,7 +46567,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45920
46567
|
const orphans = [];
|
|
45921
46568
|
for (const file of pids) {
|
|
45922
46569
|
try {
|
|
45923
|
-
const record = JSON.parse(
|
|
46570
|
+
const record = JSON.parse(readFileSync34(join44(runtime.dirs.state, file), "utf8"));
|
|
45924
46571
|
const expectedControl = file === "hub.pid" ? "hub.stop" : file.startsWith("worker-") ? `${file.slice(0, -4)}.stop` : void 0;
|
|
45925
46572
|
const expectedRole = file === "hub.pid" ? "hub" : file.startsWith("worker-") ? "worker" : void 0;
|
|
45926
46573
|
if (record.version !== 1 || !Number.isInteger(record.pid) || record.pid <= 0 || !record.startedAt || !expectedControl || record.controlFile !== expectedControl || record.role !== expectedRole) {
|
|
@@ -45940,7 +46587,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45940
46587
|
ignored.push(file);
|
|
45941
46588
|
continue;
|
|
45942
46589
|
}
|
|
45943
|
-
writeFileSync24(
|
|
46590
|
+
writeFileSync24(join44(runtime.dirs.state, record.controlFile), `${JSON.stringify({ startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {}, requestedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
45944
46591
|
`, { encoding: "utf8", mode: 384 });
|
|
45945
46592
|
requested.push(file);
|
|
45946
46593
|
records.set(file, { pid: record.pid, startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {} });
|
|
@@ -45958,7 +46605,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45958
46605
|
while (Date.now() <= deadline && stopped.size < requested.length) {
|
|
45959
46606
|
for (const [file, record] of records) {
|
|
45960
46607
|
try {
|
|
45961
|
-
const current = JSON.parse(
|
|
46608
|
+
const current = JSON.parse(readFileSync34(join44(runtime.dirs.state, file), "utf8"));
|
|
45962
46609
|
if (current.pid !== record.pid || current.startedAt !== record.startedAt || current.generation !== record.generation || !processExists(record.pid)) stopped.add(file);
|
|
45963
46610
|
} catch {
|
|
45964
46611
|
stopped.add(file);
|
|
@@ -45971,7 +46618,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45971
46618
|
for (const file of orphans) {
|
|
45972
46619
|
while (Date.now() <= deadlineOrphans) {
|
|
45973
46620
|
try {
|
|
45974
|
-
const current = JSON.parse(
|
|
46621
|
+
const current = JSON.parse(readFileSync34(join44(runtime.dirs.state, file), "utf8"));
|
|
45975
46622
|
if (!Number.isInteger(current.serverPid) || !processExists(current.serverPid)) break;
|
|
45976
46623
|
} catch {
|
|
45977
46624
|
break;
|
|
@@ -45979,14 +46626,14 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45979
46626
|
await (runtime.io.sleep ?? ((ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))))(100);
|
|
45980
46627
|
}
|
|
45981
46628
|
try {
|
|
45982
|
-
const current = JSON.parse(
|
|
46629
|
+
const current = JSON.parse(readFileSync34(join44(runtime.dirs.state, file), "utf8"));
|
|
45983
46630
|
if (Number.isInteger(current.serverPid) && processExists(current.serverPid)) {
|
|
45984
46631
|
try {
|
|
45985
46632
|
process.kill(current.serverPid, "SIGKILL");
|
|
45986
46633
|
} catch {
|
|
45987
46634
|
}
|
|
45988
46635
|
}
|
|
45989
|
-
rmSync11(
|
|
46636
|
+
rmSync11(join44(runtime.dirs.state, file), { force: true });
|
|
45990
46637
|
} catch {
|
|
45991
46638
|
}
|
|
45992
46639
|
stopped.add(file);
|
|
@@ -45997,11 +46644,11 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
45997
46644
|
}
|
|
45998
46645
|
async function cmdSessionStatus(runtime) {
|
|
45999
46646
|
const stateDir = runtime.dirs.state;
|
|
46000
|
-
const names3 =
|
|
46647
|
+
const names3 = existsSync34(stateDir) ? readdirSync12(stateDir) : [];
|
|
46001
46648
|
const claims = [];
|
|
46002
46649
|
for (const file of names3.filter((name) => name.endsWith(".pid"))) {
|
|
46003
46650
|
try {
|
|
46004
|
-
const record = JSON.parse(
|
|
46651
|
+
const record = JSON.parse(readFileSync34(join44(stateDir, file), "utf8"));
|
|
46005
46652
|
claims.push({
|
|
46006
46653
|
file,
|
|
46007
46654
|
role: record.role,
|
|
@@ -46016,7 +46663,7 @@ async function cmdSessionStatus(runtime) {
|
|
|
46016
46663
|
const recoveries = [];
|
|
46017
46664
|
for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
|
|
46018
46665
|
try {
|
|
46019
|
-
const envelope = JSON.parse(
|
|
46666
|
+
const envelope = JSON.parse(readFileSync34(join44(stateDir, file), "utf8"));
|
|
46020
46667
|
recoveries.push({
|
|
46021
46668
|
file,
|
|
46022
46669
|
reason: envelope.reason,
|
|
@@ -46042,7 +46689,7 @@ async function cmdSessionStatus(runtime) {
|
|
|
46042
46689
|
async function cmdAuthToken(runtime, options = {}) {
|
|
46043
46690
|
const tokenFile = sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR);
|
|
46044
46691
|
if (options.clear && runtime.dryRun) {
|
|
46045
|
-
const present =
|
|
46692
|
+
const present = existsSync34(tokenFile);
|
|
46046
46693
|
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");
|
|
46047
46694
|
return 0;
|
|
46048
46695
|
}
|
|
@@ -46131,7 +46778,7 @@ async function cmdSessionBrief(runtime, options = {}) {
|
|
|
46131
46778
|
print(runtime.io, runtime.json, { ok: true, command: "session brief", sessionToken }, sessionToken);
|
|
46132
46779
|
return 0;
|
|
46133
46780
|
}
|
|
46134
|
-
const dataPath =
|
|
46781
|
+
const dataPath = resolve27(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join44(runtime.dirs.state, "kxm.db"));
|
|
46135
46782
|
const env = {
|
|
46136
46783
|
...runtime.env,
|
|
46137
46784
|
KXM_STATE_DIR: runtime.dirs.state,
|
|
@@ -46157,7 +46804,7 @@ async function cmdSessionBrief(runtime, options = {}) {
|
|
|
46157
46804
|
writeCache: !runtime.dryRun
|
|
46158
46805
|
});
|
|
46159
46806
|
if (runtime.dryRun) {
|
|
46160
|
-
if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target:
|
|
46807
|
+
if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target: join44(runtime.dirs.state, "session-brief.json") });
|
|
46161
46808
|
print(runtime.io, runtime.json, { ...brief, dryRun: true, planned }, [
|
|
46162
46809
|
options.status ? brief.statusLine : formatSessionBriefText(brief),
|
|
46163
46810
|
...planned.map((change) => `dry run: would ${change.action} ${change.target}`)
|
|
@@ -46232,16 +46879,16 @@ async function cmdSessionStart(runtime, options) {
|
|
|
46232
46879
|
|
|
46233
46880
|
// plugins/kxm/src/cli/system.ts
|
|
46234
46881
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
46235
|
-
import { createHash as
|
|
46236
|
-
import { existsSync as
|
|
46882
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
46883
|
+
import { existsSync as existsSync41, mkdtempSync as mkdtempSync3, readFileSync as readFileSync39, rmSync as rmSync13 } from "node:fs";
|
|
46237
46884
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
46238
|
-
import { join as
|
|
46885
|
+
import { basename as basename10, extname as extname3, join as join50, resolve as resolve32 } from "node:path";
|
|
46239
46886
|
import { createInterface as createInterface3 } from "node:readline";
|
|
46240
46887
|
|
|
46241
46888
|
// plugins/kxm/src/improve.ts
|
|
46242
|
-
import { createHash as
|
|
46243
|
-
import { existsSync as
|
|
46244
|
-
import { join as
|
|
46889
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
46890
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync28, writeFileSync as writeFileSync25 } from "node:fs";
|
|
46891
|
+
import { join as join45, relative as relative7, resolve as resolve28 } from "node:path";
|
|
46245
46892
|
var CANDIDATE_SCHEMA = "kxm.candidate.v1";
|
|
46246
46893
|
var IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2";
|
|
46247
46894
|
function classifyCandidateKind(stepId, agentRole) {
|
|
@@ -46456,7 +47103,7 @@ function groupRoutingRecords(records, options = {}) {
|
|
|
46456
47103
|
}
|
|
46457
47104
|
const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
|
|
46458
47105
|
const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
|
|
46459
|
-
const keyHash =
|
|
47106
|
+
const keyHash = createHash16("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
|
|
46460
47107
|
const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
|
|
46461
47108
|
keyed.push({
|
|
46462
47109
|
key: group.key,
|
|
@@ -46496,8 +47143,8 @@ function groupRoutingRecords(records, options = {}) {
|
|
|
46496
47143
|
}).map((entry) => entry.row);
|
|
46497
47144
|
}
|
|
46498
47145
|
function buildImprovementReport(records, options = {}) {
|
|
46499
|
-
const projectRoot = options.projectRoot ?
|
|
46500
|
-
const candidatesDir = options.candidatesDir ?
|
|
47146
|
+
const projectRoot = options.projectRoot ? resolve28(options.projectRoot) : process.cwd();
|
|
47147
|
+
const candidatesDir = options.candidatesDir ? resolve28(options.candidatesDir) : join45(projectRoot, ".kxm", "candidates");
|
|
46501
47148
|
const promotionPolicy = options.promotionPolicy ?? "manual_pr";
|
|
46502
47149
|
const groups = groupRoutingRecords(records, options);
|
|
46503
47150
|
const candidates = [];
|
|
@@ -46511,7 +47158,7 @@ function buildImprovementReport(records, options = {}) {
|
|
|
46511
47158
|
group.workflowId
|
|
46512
47159
|
);
|
|
46513
47160
|
const diffFileName = `${group.candidateId}.diff`;
|
|
46514
|
-
const diffFilePath =
|
|
47161
|
+
const diffFilePath = join45(candidatesDir, diffFileName);
|
|
46515
47162
|
const relDiffPath = relative7(projectRoot, diffFilePath).replace(/\\/g, "/");
|
|
46516
47163
|
const candidate = {
|
|
46517
47164
|
schema: CANDIDATE_SCHEMA,
|
|
@@ -46533,11 +47180,11 @@ function buildImprovementReport(records, options = {}) {
|
|
|
46533
47180
|
createdAt: nowIso()
|
|
46534
47181
|
};
|
|
46535
47182
|
if (!options.dryRun) {
|
|
46536
|
-
if (!
|
|
47183
|
+
if (!existsSync35(candidatesDir)) {
|
|
46537
47184
|
mkdirSync28(candidatesDir, { recursive: true });
|
|
46538
47185
|
}
|
|
46539
47186
|
writeFileSync25(diffFilePath, diff, "utf8");
|
|
46540
|
-
const jsonFilePath =
|
|
47187
|
+
const jsonFilePath = join45(candidatesDir, `${group.candidateId}.json`);
|
|
46541
47188
|
writeFileSync25(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
|
|
46542
47189
|
}
|
|
46543
47190
|
candidates.push(candidate);
|
|
@@ -46566,7 +47213,7 @@ function buildImprovementReport(records, options = {}) {
|
|
|
46566
47213
|
}
|
|
46567
47214
|
function writeImprovementReport(improvementsDir, report2, dryRun = false) {
|
|
46568
47215
|
const stamp = report2.createdAt.replace(/[:.]/g, "-");
|
|
46569
|
-
const path4 =
|
|
47216
|
+
const path4 = join45(improvementsDir, `${stamp}.json`);
|
|
46570
47217
|
if (!dryRun) {
|
|
46571
47218
|
mkdirSync28(improvementsDir, { recursive: true });
|
|
46572
47219
|
writeFileSync25(path4, `${JSON.stringify(report2, null, 2)}
|
|
@@ -46645,8 +47292,8 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options = {})
|
|
|
46645
47292
|
}
|
|
46646
47293
|
|
|
46647
47294
|
// plugins/kxm/src/improve-sources.ts
|
|
46648
|
-
import { existsSync as
|
|
46649
|
-
import { resolve as
|
|
47295
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
47296
|
+
import { resolve as resolve29 } from "node:path";
|
|
46650
47297
|
var ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events WHERE event_type IN ('routing.attempt.recorded','step.entered','run.status_changed') ORDER BY run_id, sequence";
|
|
46651
47298
|
var SIMULATED_HARNESS = "driver-simulated";
|
|
46652
47299
|
function parsePayload(text) {
|
|
@@ -46668,7 +47315,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
46668
47315
|
undecided: 0,
|
|
46669
47316
|
duplicatesDropped: 0
|
|
46670
47317
|
};
|
|
46671
|
-
if (!
|
|
47318
|
+
if (!existsSync36(path4)) return { records: [], source };
|
|
46672
47319
|
source.exists = true;
|
|
46673
47320
|
let rows;
|
|
46674
47321
|
let database;
|
|
@@ -46744,7 +47391,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
46744
47391
|
};
|
|
46745
47392
|
}
|
|
46746
47393
|
function readJsonlSource(kind, path4) {
|
|
46747
|
-
const exists =
|
|
47394
|
+
const exists = existsSync36(path4);
|
|
46748
47395
|
const records = exists ? readRoutingRecords(path4).map((entry) => entry.routing) : [];
|
|
46749
47396
|
return { records, source: { kind, path: path4, exists, records: records.length, duplicatesDropped: 0 } };
|
|
46750
47397
|
}
|
|
@@ -46757,7 +47404,7 @@ function loadRoutingSources(options) {
|
|
|
46757
47404
|
const projectRoot = discoverKxmProjectRoot(options.cwd);
|
|
46758
47405
|
const reads = [];
|
|
46759
47406
|
if (options.file !== void 0) {
|
|
46760
|
-
reads.push(readJsonlSource("file",
|
|
47407
|
+
reads.push(readJsonlSource("file", resolve29(options.cwd, options.file)));
|
|
46761
47408
|
} else {
|
|
46762
47409
|
if (projectRoot !== void 0) reads.push(readEngineRoutingRecords(kxmProjectRunEventsPath(projectRoot, options.env)));
|
|
46763
47410
|
reads.push(readJsonlSource("telemetry", telemetryPath(options.logsDir)));
|
|
@@ -46788,8 +47435,8 @@ function loadRoutingSources(options) {
|
|
|
46788
47435
|
|
|
46789
47436
|
// plugins/kxm/src/modes.ts
|
|
46790
47437
|
var import_yaml17 = __toESM(require_dist(), 1);
|
|
46791
|
-
import { existsSync as
|
|
46792
|
-
import { join as
|
|
47438
|
+
import { existsSync as existsSync37, readFileSync as readFileSync36 } from "node:fs";
|
|
47439
|
+
import { join as join46 } from "node:path";
|
|
46793
47440
|
var DEFAULT_MODES_CONFIG = Object.freeze({
|
|
46794
47441
|
schema: "kxm.modes.v1",
|
|
46795
47442
|
majorModes: {
|
|
@@ -46852,12 +47499,12 @@ function loadModesConfig(projectRoot) {
|
|
|
46852
47499
|
if (!projectRoot) {
|
|
46853
47500
|
return DEFAULT_MODES_CONFIG;
|
|
46854
47501
|
}
|
|
46855
|
-
const modesPath =
|
|
46856
|
-
if (!
|
|
47502
|
+
const modesPath = join46(projectRoot, ".kxm", "modes.yaml");
|
|
47503
|
+
if (!existsSync37(modesPath)) {
|
|
46857
47504
|
return DEFAULT_MODES_CONFIG;
|
|
46858
47505
|
}
|
|
46859
47506
|
try {
|
|
46860
|
-
const raw =
|
|
47507
|
+
const raw = readFileSync36(modesPath, "utf8");
|
|
46861
47508
|
const parsed = (0, import_yaml17.parse)(raw);
|
|
46862
47509
|
if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
|
|
46863
47510
|
return {
|
|
@@ -46943,11 +47590,11 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
|
|
|
46943
47590
|
estimatedTokens: estimateTokens(baseSystemPromptChars)
|
|
46944
47591
|
});
|
|
46945
47592
|
for (const relPath of resolved.contextFiles) {
|
|
46946
|
-
const fullPath =
|
|
47593
|
+
const fullPath = join46(projectRoot, relPath);
|
|
46947
47594
|
let chars = 0;
|
|
46948
|
-
if (
|
|
47595
|
+
if (existsSync37(fullPath)) {
|
|
46949
47596
|
try {
|
|
46950
|
-
chars =
|
|
47597
|
+
chars = readFileSync36(fullPath, "utf8").length;
|
|
46951
47598
|
} catch {
|
|
46952
47599
|
chars = 0;
|
|
46953
47600
|
}
|
|
@@ -47079,9 +47726,9 @@ ${divider}
|
|
|
47079
47726
|
|
|
47080
47727
|
// plugins/kxm/src/ssh-remote.ts
|
|
47081
47728
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
47082
|
-
import { existsSync as
|
|
47729
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync29, readFileSync as readFileSync37, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync5 } from "node:fs";
|
|
47083
47730
|
import { homedir as homedir8 } from "node:os";
|
|
47084
|
-
import { join as
|
|
47731
|
+
import { join as join47, resolve as resolve30 } from "node:path";
|
|
47085
47732
|
var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
|
|
47086
47733
|
var MAX_SSH_OUTPUT_LINES = 2e3;
|
|
47087
47734
|
var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
|
|
@@ -47107,12 +47754,12 @@ function truncateSshOutput(raw) {
|
|
|
47107
47754
|
return { text, truncated };
|
|
47108
47755
|
}
|
|
47109
47756
|
function parseSshConfig(configPath) {
|
|
47110
|
-
const targetPath = configPath ??
|
|
47111
|
-
if (!
|
|
47757
|
+
const targetPath = configPath ?? join47(homedir8(), ".ssh", "config");
|
|
47758
|
+
if (!existsSync38(targetPath)) {
|
|
47112
47759
|
return [];
|
|
47113
47760
|
}
|
|
47114
47761
|
try {
|
|
47115
|
-
const content =
|
|
47762
|
+
const content = readFileSync37(targetPath, "utf-8");
|
|
47116
47763
|
const lines = content.split("\n");
|
|
47117
47764
|
const hosts = [];
|
|
47118
47765
|
let currentHosts = [];
|
|
@@ -47189,15 +47836,15 @@ function resolveSshHostG(host, execFn = spawnSync7) {
|
|
|
47189
47836
|
}
|
|
47190
47837
|
}
|
|
47191
47838
|
function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
|
|
47192
|
-
const resolved =
|
|
47193
|
-
if (!
|
|
47839
|
+
const resolved = resolve30(socketDir);
|
|
47840
|
+
if (!existsSync38(resolved)) {
|
|
47194
47841
|
mkdirSync29(resolved, { recursive: true, mode: 448 });
|
|
47195
47842
|
}
|
|
47196
47843
|
return resolved;
|
|
47197
47844
|
}
|
|
47198
47845
|
function buildSshArgs(options) {
|
|
47199
47846
|
const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
|
|
47200
|
-
const controlPath =
|
|
47847
|
+
const controlPath = join47(socketDir, "%C");
|
|
47201
47848
|
const persist = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
|
|
47202
47849
|
const args = [
|
|
47203
47850
|
"-o",
|
|
@@ -47222,7 +47869,7 @@ function buildSshArgs(options) {
|
|
|
47222
47869
|
}
|
|
47223
47870
|
function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
|
|
47224
47871
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
47225
|
-
const controlPath =
|
|
47872
|
+
const controlPath = join47(resolvedDir, "%C");
|
|
47226
47873
|
try {
|
|
47227
47874
|
const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
|
|
47228
47875
|
encoding: "utf-8"
|
|
@@ -47234,7 +47881,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
|
|
|
47234
47881
|
}
|
|
47235
47882
|
function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
|
|
47236
47883
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
47237
|
-
const controlPath =
|
|
47884
|
+
const controlPath = join47(resolvedDir, "%C");
|
|
47238
47885
|
try {
|
|
47239
47886
|
const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
|
|
47240
47887
|
encoding: "utf-8"
|
|
@@ -47679,9 +48326,9 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
|
|
|
47679
48326
|
}
|
|
47680
48327
|
|
|
47681
48328
|
// plugins/kxm/src/completion-install.ts
|
|
47682
|
-
import { existsSync as
|
|
48329
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync26 } from "node:fs";
|
|
47683
48330
|
import { homedir as homedir9 } from "node:os";
|
|
47684
|
-
import { basename as basename9, delimiter, dirname as
|
|
48331
|
+
import { basename as basename9, delimiter, dirname as dirname23, isAbsolute as isAbsolute10, join as join48, resolve as resolve31 } from "node:path";
|
|
47685
48332
|
var COMPLETION_MARKER = "# kxm completion";
|
|
47686
48333
|
var PATH_MARKER = "# kxm path";
|
|
47687
48334
|
function detectShell(env = process.env, platform = process.platform) {
|
|
@@ -47697,31 +48344,31 @@ function effectiveHome(options) {
|
|
|
47697
48344
|
const env = options.env ?? process.env;
|
|
47698
48345
|
if (options.homeDir) return options.homeDir;
|
|
47699
48346
|
const envHome = env.HOME?.trim() ?? env.USERPROFILE?.trim();
|
|
47700
|
-
return envHome && envHome.length > 0 ?
|
|
48347
|
+
return envHome && envHome.length > 0 ? resolve31(envHome) : homedir9();
|
|
47701
48348
|
}
|
|
47702
48349
|
function completionScriptPath(shell, options = {}) {
|
|
47703
48350
|
const env = options.env ?? process.env;
|
|
47704
48351
|
const explicit = options.configDir ?? env.KXM_USER_CONFIG_DIR?.trim();
|
|
47705
|
-
const base = explicit && explicit.length > 0 ?
|
|
47706
|
-
return
|
|
48352
|
+
const base = explicit && explicit.length > 0 ? resolve31(explicit) : resolve31(effectiveHome(options), ".config", "kxm");
|
|
48353
|
+
return join48(base, "completions", `kxm.${shell}`);
|
|
47707
48354
|
}
|
|
47708
48355
|
function bashRcCandidate(options) {
|
|
47709
48356
|
const home = effectiveHome(options);
|
|
47710
|
-
const candidates = [
|
|
47711
|
-
const existing = candidates.find((candidate) =>
|
|
48357
|
+
const candidates = [join48(home, ".bashrc"), join48(home, ".bash_profile")];
|
|
48358
|
+
const existing = candidates.find((candidate) => existsSync39(candidate));
|
|
47712
48359
|
return existing ?? candidates[0];
|
|
47713
48360
|
}
|
|
47714
48361
|
function zshRcCandidate(options) {
|
|
47715
48362
|
const env = options.env ?? process.env;
|
|
47716
48363
|
const home = effectiveHome(options);
|
|
47717
|
-
if (env.ZDOTDIR?.trim()) return
|
|
47718
|
-
return
|
|
48364
|
+
if (env.ZDOTDIR?.trim()) return join48(resolve31(env.ZDOTDIR.trim()), ".zshrc");
|
|
48365
|
+
return join48(home, ".zshrc");
|
|
47719
48366
|
}
|
|
47720
48367
|
function fishCompletionTarget(shell, options) {
|
|
47721
48368
|
const env = options.env ?? process.env;
|
|
47722
48369
|
const home = effectiveHome(options);
|
|
47723
|
-
if (env.XDG_CONFIG_HOME?.trim()) return
|
|
47724
|
-
return
|
|
48370
|
+
if (env.XDG_CONFIG_HOME?.trim()) return join48(resolve31(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
|
|
48371
|
+
return join48(home, ".config", "fish", "completions", "kxm.fish");
|
|
47725
48372
|
}
|
|
47726
48373
|
function completionRcTarget(shell, options = {}) {
|
|
47727
48374
|
if (shell === "fish") {
|
|
@@ -47753,16 +48400,16 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
47753
48400
|
const scriptPath = completionScriptPath(shell, options);
|
|
47754
48401
|
const { rcFile } = completionRcTarget(shell, options);
|
|
47755
48402
|
const script = generateShellCompletion(shell);
|
|
47756
|
-
const existingScript =
|
|
47757
|
-
const existingScriptMatches = existingScript &&
|
|
48403
|
+
const existingScript = existsSync39(scriptPath);
|
|
48404
|
+
const existingScriptMatches = existingScript && readFileSync38(scriptPath, "utf8") === script;
|
|
47758
48405
|
let rcModified = false;
|
|
47759
48406
|
let alreadyInstalled = false;
|
|
47760
48407
|
if (shell === "fish") {
|
|
47761
48408
|
const fishTarget = fishCompletionTarget(shell, options);
|
|
47762
|
-
const fishInstalled =
|
|
48409
|
+
const fishInstalled = existsSync39(fishTarget) && readFileSync38(fishTarget, "utf8") === script;
|
|
47763
48410
|
alreadyInstalled = fishInstalled;
|
|
47764
48411
|
if (!options.dryRun && (!fishInstalled || options.overwrite)) {
|
|
47765
|
-
mkdirSync30(
|
|
48412
|
+
mkdirSync30(resolve31(fishTarget, ".."), { recursive: true });
|
|
47766
48413
|
writeFileSync26(fishTarget, script, { encoding: "utf8" });
|
|
47767
48414
|
}
|
|
47768
48415
|
return {
|
|
@@ -47776,12 +48423,12 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
47776
48423
|
}
|
|
47777
48424
|
if (rcFile) {
|
|
47778
48425
|
const line = sourceLine(shell, scriptPath);
|
|
47779
|
-
const rcContent =
|
|
48426
|
+
const rcContent = existsSync39(rcFile) ? readFileSync38(rcFile, "utf8") : "";
|
|
47780
48427
|
alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
|
|
47781
48428
|
}
|
|
47782
48429
|
if (!options.dryRun) {
|
|
47783
48430
|
if (!existingScriptMatches || options.overwrite) {
|
|
47784
|
-
mkdirSync30(
|
|
48431
|
+
mkdirSync30(resolve31(scriptPath, ".."), { recursive: true });
|
|
47785
48432
|
writeFileSync26(scriptPath, script, { encoding: "utf8" });
|
|
47786
48433
|
}
|
|
47787
48434
|
if (rcFile && !alreadyInstalled) {
|
|
@@ -47791,8 +48438,8 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
47791
48438
|
${marker}
|
|
47792
48439
|
${line}
|
|
47793
48440
|
`;
|
|
47794
|
-
mkdirSync30(
|
|
47795
|
-
writeFileSync26(rcFile, (
|
|
48441
|
+
mkdirSync30(resolve31(rcFile, ".."), { recursive: true });
|
|
48442
|
+
writeFileSync26(rcFile, (existsSync39(rcFile) ? readFileSync38(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
|
|
47796
48443
|
rcModified = true;
|
|
47797
48444
|
}
|
|
47798
48445
|
}
|
|
@@ -47808,14 +48455,14 @@ ${line}
|
|
|
47808
48455
|
function kxmBinDir(env = process.env) {
|
|
47809
48456
|
const argv1 = env.KXM_ENTRY ?? process.argv[1];
|
|
47810
48457
|
if (argv1 && isAbsolute10(argv1)) {
|
|
47811
|
-
const dir =
|
|
47812
|
-
if (
|
|
48458
|
+
const dir = dirname23(argv1);
|
|
48459
|
+
if (existsSync39(join48(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
|
|
47813
48460
|
}
|
|
47814
48461
|
const pathEnv = env.PATH ?? "";
|
|
47815
48462
|
for (const part of pathEnv.split(delimiter)) {
|
|
47816
48463
|
if (!part) continue;
|
|
47817
|
-
const candidate =
|
|
47818
|
-
if (
|
|
48464
|
+
const candidate = resolve31(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
|
|
48465
|
+
if (existsSync39(candidate)) return resolve31(part);
|
|
47819
48466
|
}
|
|
47820
48467
|
return void 0;
|
|
47821
48468
|
}
|
|
@@ -47833,10 +48480,10 @@ function installPathEntry(shellInput, options = {}) {
|
|
|
47833
48480
|
}
|
|
47834
48481
|
const { rcFile } = completionRcTarget(shell, options);
|
|
47835
48482
|
const line = pathLine(binDir);
|
|
47836
|
-
const rcContent =
|
|
48483
|
+
const rcContent = existsSync39(rcFile ?? "") ? readFileSync38(rcFile, "utf8") : "";
|
|
47837
48484
|
const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
|
|
47838
48485
|
if (!options.dryRun && rcFile && !alreadyInstalled) {
|
|
47839
|
-
mkdirSync30(
|
|
48486
|
+
mkdirSync30(dirname23(rcFile), { recursive: true });
|
|
47840
48487
|
writeFileSync26(rcFile, rcContent + `
|
|
47841
48488
|
${PATH_MARKER}
|
|
47842
48489
|
${line}
|
|
@@ -47847,8 +48494,8 @@ ${line}
|
|
|
47847
48494
|
|
|
47848
48495
|
// plugins/kxm/src/init-guide-setup.ts
|
|
47849
48496
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
47850
|
-
import { existsSync as
|
|
47851
|
-
import { dirname as
|
|
48497
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync31, writeFileSync as writeFileSync27 } from "node:fs";
|
|
48498
|
+
import { dirname as dirname24, join as join49 } from "node:path";
|
|
47852
48499
|
var ADMITTED_GUIDE_BINDINGS = Object.freeze({
|
|
47853
48500
|
"anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
47854
48501
|
"anthropic/fable": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
@@ -48239,13 +48886,13 @@ function renderGuideSetupFiles(projectRoot, plan) {
|
|
|
48239
48886
|
const stage = roleStages.get(role);
|
|
48240
48887
|
if (!stage) continue;
|
|
48241
48888
|
files.push({
|
|
48242
|
-
path:
|
|
48889
|
+
path: join49(projectRoot, ".kxm", "agents", `${role}.yaml`),
|
|
48243
48890
|
content: (0, import_yaml18.stringify)(agentDocument(role, stage, binding))
|
|
48244
48891
|
});
|
|
48245
48892
|
}
|
|
48246
48893
|
for (const workflow of plan.workflows) {
|
|
48247
48894
|
files.push({
|
|
48248
|
-
path:
|
|
48895
|
+
path: join49(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
|
|
48249
48896
|
content: (0, import_yaml18.stringify)(workflowDocument(workflow))
|
|
48250
48897
|
});
|
|
48251
48898
|
}
|
|
@@ -48262,8 +48909,8 @@ function mergeGuideRouteAdmission(projectRoot, plan) {
|
|
|
48262
48909
|
}
|
|
48263
48910
|
if (added.length === 0) return added;
|
|
48264
48911
|
policy.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
48265
|
-
const path4 =
|
|
48266
|
-
mkdirSync31(
|
|
48912
|
+
const path4 = join49(projectRoot, ".kxm", "routes.yaml");
|
|
48913
|
+
mkdirSync31(dirname24(path4), { recursive: true });
|
|
48267
48914
|
writeFileSync27(path4, (0, import_yaml18.stringify)(policy), "utf8");
|
|
48268
48915
|
return added;
|
|
48269
48916
|
}
|
|
@@ -48271,11 +48918,11 @@ function writeGuideSetupFiles(files) {
|
|
|
48271
48918
|
const written = [];
|
|
48272
48919
|
const existed = [];
|
|
48273
48920
|
for (const file of files) {
|
|
48274
|
-
if (
|
|
48921
|
+
if (existsSync40(file.path)) {
|
|
48275
48922
|
existed.push(file.path);
|
|
48276
48923
|
continue;
|
|
48277
48924
|
}
|
|
48278
|
-
mkdirSync31(
|
|
48925
|
+
mkdirSync31(dirname24(file.path), { recursive: true });
|
|
48279
48926
|
writeFileSync27(file.path, file.content, "utf8");
|
|
48280
48927
|
written.push(file.path);
|
|
48281
48928
|
}
|
|
@@ -48339,7 +48986,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
48339
48986
|
detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
|
|
48340
48987
|
};
|
|
48341
48988
|
}
|
|
48342
|
-
const releaseDir = mkdtempSync3(
|
|
48989
|
+
const releaseDir = mkdtempSync3(join50(tmpdir2(), "kxm-pkg-update-"));
|
|
48343
48990
|
try {
|
|
48344
48991
|
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
|
|
48345
48992
|
if (runtime.dryRun) {
|
|
@@ -48348,7 +48995,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
48348
48995
|
for (const step of planned) {
|
|
48349
48996
|
if (step.kind === "verify") {
|
|
48350
48997
|
if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
|
|
48351
|
-
const actual =
|
|
48998
|
+
const actual = existsSync41(step.path) ? createHash17("sha256").update(readFileSync39(step.path)).digest("hex") : "missing";
|
|
48352
48999
|
return {
|
|
48353
49000
|
ok: false,
|
|
48354
49001
|
error: "release_digest_mismatch",
|
|
@@ -48625,13 +49272,27 @@ async function cmdValidate(runtime, fileFlag) {
|
|
|
48625
49272
|
return 2;
|
|
48626
49273
|
}
|
|
48627
49274
|
const selectedFile = explicitFile || configuredFile;
|
|
48628
|
-
const file = selectedFile ?
|
|
48629
|
-
if (file && !
|
|
49275
|
+
const file = selectedFile ? resolve32(runtime.cwd, selectedFile) : void 0;
|
|
49276
|
+
if (file && !existsSync41(file)) {
|
|
48630
49277
|
printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
|
|
48631
49278
|
return 1;
|
|
48632
49279
|
}
|
|
48633
49280
|
try {
|
|
48634
|
-
const raw = file ?
|
|
49281
|
+
const raw = file ? readFileSync39(file, "utf8") : inline;
|
|
49282
|
+
if (explicitFile && file && !raw.trimStart().startsWith("[")) {
|
|
49283
|
+
const value = parseRestrictedYaml2(raw, file);
|
|
49284
|
+
const issues = new KxmSchemaRegistry().validate("workflow", value, file);
|
|
49285
|
+
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
49286
|
+
const id = basename10(file, extname3(file));
|
|
49287
|
+
compileKxmWorkflow({ id, value, logicalPath: file });
|
|
49288
|
+
printWorker(
|
|
49289
|
+
runtime,
|
|
49290
|
+
worker,
|
|
49291
|
+
{ ok: true, command: "validate", source: "file", file, workflows: [{ id, schema: value.schema }], warnings: [] },
|
|
49292
|
+
`validated local workflow ${id} (schema and transitions); kxm init validates project references`
|
|
49293
|
+
);
|
|
49294
|
+
return 0;
|
|
49295
|
+
}
|
|
48635
49296
|
const warnings = [];
|
|
48636
49297
|
const definitions = parseWorkflowDefinitions(raw, runtime.env, (message) => warnings.push(message));
|
|
48637
49298
|
const secretEnvs = definitions.map((definition) => ({
|
|
@@ -48656,7 +49317,7 @@ async function cmdValidate(runtime, fileFlag) {
|
|
|
48656
49317
|
}
|
|
48657
49318
|
async function cmdArtifactsExist(runtime, pathFlag) {
|
|
48658
49319
|
const worker = gateOf(runtime, "artifacts-exist");
|
|
48659
|
-
const checked = verifyArtifactExists(runtime.dirs.assets,
|
|
49320
|
+
const checked = verifyArtifactExists(runtime.dirs.assets, resolve32(runtime.cwd, pathFlag));
|
|
48660
49321
|
if (!checked.ok) {
|
|
48661
49322
|
printWorker(
|
|
48662
49323
|
runtime,
|
|
@@ -48712,7 +49373,7 @@ async function cmdImprove(runtime, options = {}) {
|
|
|
48712
49373
|
print(runtime.io, runtime.json, { ok: false, command: "improve", error: "config_invalid", detail: message }, `config_invalid: ${message}`);
|
|
48713
49374
|
return 1;
|
|
48714
49375
|
}
|
|
48715
|
-
const candidatesDir = options.outDir ?
|
|
49376
|
+
const candidatesDir = options.outDir ? resolve32(runtime.cwd, options.outDir) : join50(root, ".kxm", "candidates");
|
|
48716
49377
|
const report2 = buildImprovementReport(loaded.records, {
|
|
48717
49378
|
candidatesDir,
|
|
48718
49379
|
projectRoot: root,
|
|
@@ -48721,7 +49382,7 @@ async function cmdImprove(runtime, options = {}) {
|
|
|
48721
49382
|
autoThreshold: config.improvement.autoThreshold,
|
|
48722
49383
|
halfLifeDays: config.improvement.telemetryHalfLifeDays
|
|
48723
49384
|
});
|
|
48724
|
-
const reportDir =
|
|
49385
|
+
const reportDir = join50(runtime.dirs.assets, "improvements");
|
|
48725
49386
|
const reportPath = writeImprovementReport(reportDir, report2, runtime.dryRun);
|
|
48726
49387
|
const text = [
|
|
48727
49388
|
"Sources:",
|
|
@@ -48865,9 +49526,9 @@ async function maybeOfferCompletionInstall(runtime) {
|
|
|
48865
49526
|
if (!pathNeeded) {
|
|
48866
49527
|
const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
48867
49528
|
const { rcFile } = completionRcTarget(shell, { env: runtime.env });
|
|
48868
|
-
if (rcFile &&
|
|
49529
|
+
if (rcFile && existsSync41(rcFile)) {
|
|
48869
49530
|
try {
|
|
48870
|
-
if (
|
|
49531
|
+
if (readFileSync39(rcFile, "utf8").includes(scriptPath)) return;
|
|
48871
49532
|
} catch {
|
|
48872
49533
|
}
|
|
48873
49534
|
}
|
|
@@ -49030,7 +49691,7 @@ async function cmdRoutingReport(runtime, options) {
|
|
|
49030
49691
|
let catalog;
|
|
49031
49692
|
if (includeEquivalentListCost) {
|
|
49032
49693
|
try {
|
|
49033
|
-
const pricesPath = options.prices ?
|
|
49694
|
+
const pricesPath = options.prices ? resolve32(runtime.cwd, options.prices) : join50(runtime.dirs.workspace, "prices.yaml");
|
|
49034
49695
|
catalog = loadPriceCatalog(pricesPath);
|
|
49035
49696
|
} catch {
|
|
49036
49697
|
}
|
|
@@ -49414,7 +50075,7 @@ function createProgram(ctx, result) {
|
|
|
49414
50075
|
addGlobalOptions(program2.command("restore <manifest>").description("Restore this project's SQLite stores from a verified backup manifest; refuses while the Runtime or hub is running")).option("--all-projects", "Also restore the shared Runtime registry and other projects' event stores the backup holds").action(async function restoreAction(manifest, options) {
|
|
49415
50076
|
result.code = await cmdRestore(runtimeFrom(ctx, this), manifest, options);
|
|
49416
50077
|
});
|
|
49417
|
-
addGlobalOptions(program2.command("run").description("Create a KXM run
|
|
50078
|
+
addGlobalOptions(program2.command("run").description("Create a KXM run without executing steps; follow its prerequisites, then kxm runs drive <runId> --wait").argument("[workflow]", "Workflow id to run").argument("[prompt...]", "Run prompt (events keep its hash; the full text is kept in a local 0600 sidecar file)").action(async function runAction(workflow2, promptParts) {
|
|
49418
50079
|
result.code = await cmdKxmRun(runtimeFrom(ctx, this), workflow2, promptParts);
|
|
49419
50080
|
}));
|
|
49420
50081
|
const runCmd = addGlobalOptions(program2.command("runs").description("Inspect KXM runs"));
|
|
@@ -49879,7 +50540,7 @@ async function runCli(argv, env = process.env, io = { stdout: (text) => process.
|
|
|
49879
50540
|
throw error;
|
|
49880
50541
|
}
|
|
49881
50542
|
}
|
|
49882
|
-
if (process.argv[1] &&
|
|
50543
|
+
if (process.argv[1] && resolve33(process.argv[1]) === fileURLToPath4(import.meta.url)) {
|
|
49883
50544
|
const code = await runCli(process.argv.slice(2));
|
|
49884
50545
|
process.exitCode = code;
|
|
49885
50546
|
}
|