@kontextmind/kxm 0.7.105 → 0.7.107

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.
@@ -17313,7 +17313,7 @@ function sessionTokenFixHint(policy) {
17313
17313
  }
17314
17314
 
17315
17315
  // plugins/kxm/src/mcp-server.ts
17316
- var VERSION = "0.7.105";
17316
+ var VERSION = "0.7.107";
17317
17317
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
17318
17318
  var inbox = /* @__PURE__ */ new Map();
17319
17319
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -17151,7 +17151,7 @@ function validateModelReferences(models, issues) {
17151
17151
  };
17152
17152
  for (const id of models.keys()) walk(id, []);
17153
17153
  }
17154
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
17154
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, writerRole) {
17155
17155
  const issues = [];
17156
17156
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
17157
17157
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -17222,9 +17222,9 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17222
17222
  if (projectRoot) {
17223
17223
  const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
17224
17224
  const implementerAgent = agents.get("implementer") ?? agents.get("writer");
17225
- if (existsSync3(writerRolePath) && implementerAgent) {
17225
+ if ((writerRole !== void 0 || existsSync3(writerRolePath)) && implementerAgent) {
17226
17226
  try {
17227
- const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
17227
+ const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync(writerRolePath, "utf8"));
17228
17228
  const roleObj = objectValue(rawRole);
17229
17229
  const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17230
17230
  const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
@@ -17290,7 +17290,9 @@ function assertNoRegisteredGates(options) {
17290
17290
  function loadKxmProject(projectRoot, options = {}) {
17291
17291
  return loadProjectBundle(projectRoot, options);
17292
17292
  }
17293
- function loadProjectBundle(projectRoot, options, workflowCandidate) {
17293
+ function loadProjectBundle(projectRoot, options, candidate) {
17294
+ const workflowCandidate = candidate?.kind === "workflow" ? candidate : void 0;
17295
+ const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : void 0;
17294
17296
  assertNoRegisteredGates(options);
17295
17297
  const root = resolve(projectRoot);
17296
17298
  const legacyPresent = legacyConfigFilesAt(root);
@@ -17313,7 +17315,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17313
17315
  earlyIssues.push(issue2("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
17314
17316
  }
17315
17317
  }
17316
- const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate) => stringValue(objectValue(candidate)?.id)).filter((candidate) => candidate !== void 0));
17318
+ const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate2) => stringValue(objectValue(candidate2)?.id)).filter((candidate2) => candidate2 !== void 0));
17317
17319
  for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits3)) {
17318
17320
  if (!resourceIdentifier(repositoryId)) {
17319
17321
  earlyIssues.push(issue2("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
@@ -17329,7 +17331,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17329
17331
  const { id, document } = workflowCandidate;
17330
17332
  const logicalPath = `.kxm/workflows/${id}.yaml`;
17331
17333
  if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
17332
- const collision = [...workflows.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
17334
+ const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
17333
17335
  if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
17334
17336
  workflows.set(id, { kind: "workflow", id, file: join2(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
17335
17337
  }
@@ -17348,8 +17350,8 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17348
17350
  }
17349
17351
  }
17350
17352
  const seenBindings = /* @__PURE__ */ new Map();
17351
- for (const candidate of valuesOf(project.value, "repositories")) {
17352
- const entry = objectValue(candidate);
17353
+ for (const candidate2 of valuesOf(project.value, "repositories")) {
17354
+ const entry = objectValue(candidate2);
17353
17355
  const repositoryId = entry && stringValue(entry.id);
17354
17356
  const pathHint = entry && stringValue(entry.pathHint);
17355
17357
  const role = entry && stringValue(entry.role);
@@ -17446,7 +17448,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17446
17448
  }
17447
17449
  }
17448
17450
  if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
17449
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
17451
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
17450
17452
  if (issues.length > 0) throw new KxmConfigError(issues);
17451
17453
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
17452
17454
  return {
@@ -17321,7 +17321,7 @@ function validateModelReferences(models, issues) {
17321
17321
  };
17322
17322
  for (const id of models.keys()) walk(id, []);
17323
17323
  }
17324
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
17324
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, writerRole) {
17325
17325
  const issues = [];
17326
17326
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
17327
17327
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -17392,9 +17392,9 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17392
17392
  if (projectRoot) {
17393
17393
  const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
17394
17394
  const implementerAgent = agents.get("implementer") ?? agents.get("writer");
17395
- if (existsSync3(writerRolePath) && implementerAgent) {
17395
+ if ((writerRole !== void 0 || existsSync3(writerRolePath)) && implementerAgent) {
17396
17396
  try {
17397
- const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
17397
+ const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync(writerRolePath, "utf8"));
17398
17398
  const roleObj = objectValue(rawRole);
17399
17399
  const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17400
17400
  const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
@@ -17460,7 +17460,9 @@ function assertNoRegisteredGates(options) {
17460
17460
  function loadKxmProject(projectRoot, options = {}) {
17461
17461
  return loadProjectBundle(projectRoot, options);
17462
17462
  }
17463
- function loadProjectBundle(projectRoot, options, workflowCandidate) {
17463
+ function loadProjectBundle(projectRoot, options, candidate) {
17464
+ const workflowCandidate = candidate?.kind === "workflow" ? candidate : void 0;
17465
+ const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : void 0;
17464
17466
  assertNoRegisteredGates(options);
17465
17467
  const root = resolve(projectRoot);
17466
17468
  const legacyPresent = legacyConfigFilesAt(root);
@@ -17483,7 +17485,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17483
17485
  earlyIssues.push(issue2("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
17484
17486
  }
17485
17487
  }
17486
- const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate) => stringValue(objectValue(candidate)?.id)).filter((candidate) => candidate !== void 0));
17488
+ const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate2) => stringValue(objectValue(candidate2)?.id)).filter((candidate2) => candidate2 !== void 0));
17487
17489
  for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits3)) {
17488
17490
  if (!resourceIdentifier(repositoryId)) {
17489
17491
  earlyIssues.push(issue2("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
@@ -17499,7 +17501,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17499
17501
  const { id, document } = workflowCandidate;
17500
17502
  const logicalPath = `.kxm/workflows/${id}.yaml`;
17501
17503
  if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
17502
- const collision = [...workflows.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
17504
+ const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
17503
17505
  if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
17504
17506
  workflows.set(id, { kind: "workflow", id, file: join2(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
17505
17507
  }
@@ -17518,8 +17520,8 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17518
17520
  }
17519
17521
  }
17520
17522
  const seenBindings = /* @__PURE__ */ new Map();
17521
- for (const candidate of valuesOf(project.value, "repositories")) {
17522
- const entry = objectValue(candidate);
17523
+ for (const candidate2 of valuesOf(project.value, "repositories")) {
17524
+ const entry = objectValue(candidate2);
17523
17525
  const repositoryId = entry && stringValue(entry.id);
17524
17526
  const pathHint = entry && stringValue(entry.pathHint);
17525
17527
  const role = entry && stringValue(entry.role);
@@ -17616,7 +17618,7 @@ function loadProjectBundle(projectRoot, options, workflowCandidate) {
17616
17618
  }
17617
17619
  }
17618
17620
  if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
17619
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
17621
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
17620
17622
  if (issues.length > 0) throw new KxmConfigError(issues);
17621
17623
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
17622
17624
  return {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.105",
3
+ "version": "0.7.107",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,9 +15,10 @@ import {
15
15
  loadRoleHostsConfig,
16
16
  setRoleSeatHost,
17
17
  resolveRoleSeat,
18
+ parseRoleFile,
18
19
  type KxmRoleDefinition,
19
20
  } from "../role.ts";
20
- import { discoverKxmProjectRoot } from "../project-config.ts";
21
+ import { discoverKxmProjectRoot, kxmRoleWriteIssues } from "../project-config.ts";
21
22
  import { ensureKxmSupervisor, kxmRuntimeRequest } from "../runtime-supervisor.ts";
22
23
  import { projectRuntimeOwnsRun } from "../runtime-store.ts";
23
24
  import { resumeWorkflowFromRuling, type WorkflowRun } from "../workflow.ts";
@@ -156,6 +157,7 @@ export async function cmdRoleAdd(
156
157
  },
157
158
  ): Promise<number> {
158
159
  const scope = options.scope ?? "local";
160
+ let base: KxmRoleDefinition | undefined;
159
161
  if (!roleId || options.pick) {
160
162
  const candidates: PickCandidate[] = Object.values(DEFAULT_ROLES).map((r) => ({
161
163
  id: r.id,
@@ -166,8 +168,10 @@ export async function cmdRoleAdd(
166
168
  if (scope === "local") {
167
169
  const globalRoles = listRoles({ scope: "global", userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
168
170
  for (const gr of globalRoles) {
169
- if (!candidates.some((c) => c.id === gr.id)) {
170
- candidates.push({ id: gr.id, description: gr.description, label: "global", payload: gr });
171
+ // The listing is a summary; the copy needs the definition, read from the listed file (it may be `.yml`).
172
+ const definition = parseRoleFile(gr.filePath);
173
+ if (definition && !candidates.some((c) => c.id === gr.id)) {
174
+ candidates.push({ id: gr.id, description: gr.description, label: "global", payload: definition });
171
175
  }
172
176
  }
173
177
  }
@@ -179,63 +183,66 @@ export async function cmdRoleAdd(
179
183
  }
180
184
  } else {
181
185
  roleId = picked.id;
182
- if (!options.file && picked.payload && DEFAULT_ROLES[picked.id]) {
183
- const base = DEFAULT_ROLES[picked.id]!;
184
- const roleDef: KxmRoleDefinition = {
185
- ...base,
186
- description: options.description || base.description,
187
- skills: options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : base.skills,
188
- roster: options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : base.roster,
189
- };
190
- try {
191
- const res = addRole(roleDef, {
192
- scope,
193
- repoRoot: runtime.cwd,
194
- userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
195
- overwrite: options.overwrite,
196
- dryRun: runtime.dryRun,
197
- });
198
- if (runtime.dryRun) {
199
- printPlan(runtime, { command: "role add", roleId, ...res }, [{ action: "write", target: res.filePath }], `add role '${roleId}' to ${res.scope}`);
200
- return 0;
201
- }
202
- print(
203
- runtime.io,
204
- runtime.json,
205
- { ok: true, command: "role add", roleId, ...res },
206
- `Added role '${roleId}' to ${res.scope} (${res.filePath})\n`,
207
- );
208
- return 0;
209
- } catch (err: unknown) {
210
- runtime.io.stderr(`role add failed: ${(err as Error).message}\n`);
211
- return 1;
212
- }
213
- }
186
+ base = picked.payload as KxmRoleDefinition;
214
187
  }
215
188
  }
216
189
 
190
+ const skills = options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
191
+ const roster = options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : undefined;
217
192
  let roleDef: KxmRoleDefinition;
218
193
  if (options.file) {
219
194
  const filePath = resolve(runtime.cwd, options.file);
220
195
  const content = readFileSync(filePath, "utf8");
221
196
  roleDef = parseYaml(content) as KxmRoleDefinition;
222
197
  roleDef.id = roleId!;
198
+ } else if (base) {
199
+ roleDef = {
200
+ ...base,
201
+ description: options.description || base.description,
202
+ skills: skills ?? base.skills,
203
+ roster: roster ?? base.roster,
204
+ };
223
205
  } else {
224
- const skills = options.skills ? options.skills.split(",").map((s) => s.trim()).filter(Boolean) : [];
225
- const roster = options.model ? [{ harness: options.harness ?? "pi", model: options.model }] : [];
226
206
  roleDef = {
227
207
  schema: "kxm.role.v1",
228
208
  id: roleId!,
229
209
  description: options.description || `Role ${roleId}`,
230
- skills,
231
- roster,
210
+ skills: skills ?? [],
211
+ roster: roster ?? [],
232
212
  };
233
213
  }
234
214
 
215
+ // A local role file sits in the project the loader reads, and the loader refuses the
216
+ // whole project over a writer roster it rejects, so the role is checked before it lands.
217
+ let repoRoot = runtime.cwd;
218
+ if (scope === "local") {
219
+ const projectRoot = discoverKxmProjectRoot(runtime.cwd);
220
+ if (!projectRoot) {
221
+ print(
222
+ runtime.io,
223
+ runtime.json,
224
+ { ok: false, command: "role add", error: "project_not_found" },
225
+ "role add failed: local roles belong to a KXM project; run kxm init at the repository root, or pass --scope global",
226
+ );
227
+ return 2;
228
+ }
229
+ const issues = kxmRoleWriteIssues(projectRoot, roleDef.id, stringifyYaml(roleDef));
230
+ if (issues.length > 0) {
231
+ print(
232
+ runtime.io,
233
+ runtime.json,
234
+ { ok: false, command: "role add", error: "role_invalid", issues },
235
+ `role add failed: with this role the project would not load, so nothing was written\n${issues.map((entry) => ` ${entry.file}: ${entry.code}: ${entry.message}`).join("\n")}`,
236
+ );
237
+ return 2;
238
+ }
239
+ repoRoot = projectRoot;
240
+ }
241
+
235
242
  try {
236
243
  const res = addRole(roleDef, {
237
244
  scope,
238
- repoRoot: runtime.cwd,
245
+ repoRoot,
239
246
  userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
240
247
  overwrite: options.overwrite,
241
248
  dryRun: runtime.dryRun,
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
11
11
  import type { HubEvent, MessageRecord } from "./protocol.ts";
12
12
  import { sessionTokenFixHint } from "./session-token-hint.ts";
13
13
 
14
- const VERSION = "0.7.105";
14
+ const VERSION = "0.7.107";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();
@@ -1124,6 +1124,7 @@ function validateBundle(
1124
1124
  options: KxmConfigOptions,
1125
1125
  gateRegistry?: KxmResource,
1126
1126
  projectRoot?: string,
1127
+ writerRole?: string,
1127
1128
  ): KxmConfigIssue[] {
1128
1129
  const issues: KxmConfigIssue[] = [];
1129
1130
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate): candidate is JsonObject => Boolean(candidate));
@@ -1200,9 +1201,9 @@ function validateBundle(
1200
1201
  if (projectRoot) {
1201
1202
  const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
1202
1203
  const implementerAgent = agents.get("implementer") ?? agents.get("writer");
1203
- if (existsSync(writerRolePath) && implementerAgent) {
1204
+ if ((writerRole !== undefined || existsSync(writerRolePath)) && implementerAgent) {
1204
1205
  try {
1205
- const rawRole = parseRestrictedYaml(readFileSync(writerRolePath, "utf8"));
1206
+ const rawRole = parseRestrictedYaml(writerRole ?? readFileSync(writerRolePath, "utf8"));
1206
1207
  const roleObj = objectValue(rawRole);
1207
1208
  const rosterEntries = valuesOf(roleObj ?? {}, "roster")
1208
1209
  .map((candidate) => objectValue(candidate))
@@ -1304,8 +1305,32 @@ export function kxmWorkflowWriteIssues(
1304
1305
  document: string,
1305
1306
  options: KxmConfigOptions = {},
1306
1307
  ): readonly KxmConfigIssue[] {
1308
+ return candidateIssues(projectRoot, options, { kind: "workflow", id: workflowId, document });
1309
+ }
1310
+
1311
+ /**
1312
+ * What `loadKxmProject` would refuse once `.kxm/roles/<roleId>.yaml` held `document`,
1313
+ * found without writing it. The loader reads only the writer role, so for any other
1314
+ * id this is the project's load as it stands.
1315
+ */
1316
+ export function kxmRoleWriteIssues(
1317
+ projectRoot: string,
1318
+ roleId: string,
1319
+ document: string,
1320
+ options: KxmConfigOptions = {},
1321
+ ): readonly KxmConfigIssue[] {
1322
+ return candidateIssues(projectRoot, options, { kind: "role", id: roleId, document });
1323
+ }
1324
+
1325
+ interface WriteCandidate {
1326
+ kind: "workflow" | "role";
1327
+ id: string;
1328
+ document: string;
1329
+ }
1330
+
1331
+ function candidateIssues(projectRoot: string, options: KxmConfigOptions, candidate: WriteCandidate): readonly KxmConfigIssue[] {
1307
1332
  try {
1308
- loadProjectBundle(projectRoot, options, { id: workflowId, document });
1333
+ loadProjectBundle(projectRoot, options, candidate);
1309
1334
  return [];
1310
1335
  } catch (error) {
1311
1336
  if (error instanceof KxmConfigError) return error.issues;
@@ -1316,8 +1341,11 @@ export function kxmWorkflowWriteIssues(
1316
1341
  function loadProjectBundle(
1317
1342
  projectRoot: string,
1318
1343
  options: KxmConfigOptions,
1319
- workflowCandidate?: { id: string; document: string },
1344
+ candidate?: WriteCandidate,
1320
1345
  ): KxmProjectBundle {
1346
+ const workflowCandidate = candidate?.kind === "workflow" ? candidate : undefined;
1347
+ // A case-insensitive filesystem serves `Writer.yaml` to the loader's `writer.yaml` lookup.
1348
+ const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : undefined;
1321
1349
  assertNoRegisteredGates(options);
1322
1350
  const root = resolve(projectRoot);
1323
1351
  const legacyPresent = legacyConfigFilesAt(root);
@@ -1484,7 +1512,7 @@ function loadProjectBundle(
1484
1512
  }
1485
1513
  if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
1486
1514
 
1487
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
1515
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
1488
1516
  if (issues.length > 0) throw new KxmConfigError(issues);
1489
1517
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...(gateRegistry ? [gateRegistry] : [])]
1490
1518
  .sort((left, right) => compareCodeUnits(left.logicalPath, right.logicalPath));