@kontextmind/kxm 0.7.145 → 0.7.147

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.
Files changed (73) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/README.md +20 -2
  3. package/.kxm/agents/{coordinator.yaml → planner.yaml} +2 -0
  4. package/.kxm/agents/{critic-arch.yaml → reviewer-arch.yaml} +2 -0
  5. package/.kxm/agents/{critic-cli.yaml → reviewer-cli.yaml} +2 -0
  6. package/.kxm/agents/{implementer.yaml → writer.yaml} +2 -0
  7. package/.kxm/roles/planner.yaml +4 -2
  8. package/.kxm/roles/reviewer-arch.yaml +4 -2
  9. package/.kxm/roles/reviewer-cli.yaml +4 -2
  10. package/.kxm/roles/writer.yaml +6 -3
  11. package/.kxm/workflows/default.yaml +20 -12
  12. package/.kxm/workflows/land.yaml +1 -1
  13. package/.kxm/workflows/{review-arch-only.yaml → reviewer-arch-only.yaml} +7 -3
  14. package/.kxm/workflows/{review-cli-only.yaml → reviewer-cli-only.yaml} +7 -3
  15. package/.kxm/workflows/{implement-only.yaml → writer-only.yaml} +8 -4
  16. package/CHANGELOG.md +48 -8
  17. package/docs/README.md +1 -1
  18. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +8 -5
  19. package/docs/adr/ADR-0005-obscura-default-playwright.md +4 -3
  20. package/docs/adr/ADR-0006-machine-account-names.md +90 -0
  21. package/docs/adr/ADR-0007-steel-caddy-authentik.md +97 -0
  22. package/docs/adr/README.md +3 -1
  23. package/docs/contributing/harness-routing-internals.md +17 -11
  24. package/docs/contributing/operating-rules.md +13 -1
  25. package/docs/guides/agent-skills.md +1 -1
  26. package/docs/guides/browser-automation.md +44 -28
  27. package/docs/kb/how-credentials-retrieved-safely.md +22 -23
  28. package/docs/kb/how-to-connect-playwright-to-steel.md +8 -5
  29. package/docs/kb/how-to-recover-expired-session-or-orphan.md +11 -10
  30. package/docs/kb/why-automation-opened-different-browser.md +4 -3
  31. package/docs/operations/deploy.md +31 -0
  32. package/docs/operations/troubleshooting.md +23 -1
  33. package/docs/prompts/browser-diagnose-recover.md +2 -2
  34. package/docs/prompts/browser-start.md +1 -1
  35. package/docs/reference/cli-reference.md +23 -24
  36. package/docs/reference/config-reference.md +69 -62
  37. package/docs/reference/configuration.md +9 -7
  38. package/docs/reference/harness-routing.md +6 -5
  39. package/docs/reference/workflow-catalog.md +3 -3
  40. package/package.json +3 -3
  41. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  42. package/plugins/kxm/dist/cli.js +1145 -805
  43. package/plugins/kxm/dist/mcp-server.js +1 -1
  44. package/plugins/kxm/dist/runtime-supervisor.js +636 -308
  45. package/plugins/kxm/dist/runtime.js +710 -382
  46. package/plugins/kxm/dist/server.js +49 -5
  47. package/plugins/kxm/package.json +1 -1
  48. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +11 -12
  49. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -4
  50. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +4 -3
  51. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +5 -5
  52. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
  53. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -3
  54. package/plugins/kxm/src/browser.ts +12 -9
  55. package/plugins/kxm/src/cli/project.ts +2 -1
  56. package/plugins/kxm/src/cli/roles.ts +3 -4
  57. package/plugins/kxm/src/engine.ts +9 -6
  58. package/plugins/kxm/src/mcp-server.ts +1 -1
  59. package/plugins/kxm/src/modes.ts +1 -1
  60. package/plugins/kxm/src/policy-draft.mjs +11 -5
  61. package/plugins/kxm/src/project-config.ts +49 -11
  62. package/plugins/kxm/src/runtime-service.ts +2 -1
  63. package/plugins/kxm/src/template.ts +39 -10
  64. package/plugins/kxm/src/workflow-manager.ts +34 -28
  65. package/plugins/kxm/src/workforce-names.d.mts +38 -0
  66. package/plugins/kxm/src/workforce-names.mjs +317 -0
  67. package/schemas/agent.schema.json +7 -0
  68. package/schemas/model.schema.json +12 -0
  69. package/schemas/workflow.schema.json +14 -0
  70. package/scripts/harness-run.mjs +1 -1
  71. package/scripts/native-critic.mjs +5 -5
  72. package/scripts/roster-policy.mjs +9 -3
  73. package/scripts/workforce-lint.mjs +18 -0
@@ -11,6 +11,7 @@ import {
11
11
  } from "./restricted-yaml.mjs";
12
12
  import { pathToFileURL } from "node:url";
13
13
  import { validatePolicyDraft } from "./policy-draft.mjs";
14
+ import { findYamlBasename, lintWorkforce, lookupById, noteDeprecatedId, STEP_RENAMES } from "./workforce-names.mjs";
14
15
  import { resolveKxmTemplateBaseline } from "./template.ts";
15
16
  import { findKxmRepoRoot } from "./repo-root.ts";
16
17
  import { BUILTIN_HARNESS_IDS, DEFAULT_HARNESS } from "./harness.ts";
@@ -590,6 +591,20 @@ function names(value: JsonValue | undefined): string[] {
590
591
  return Array.isArray(value) ? value.filter((candidate): candidate is string => typeof candidate === "string") : [];
591
592
  }
592
593
 
594
+ /** Resolve an agent, workflow, or route, including a deprecated alias. */
595
+ export function lookupKxmResource<T extends { id?: string; value: JsonObject }>(
596
+ resources: Iterable<T>,
597
+ requested: string,
598
+ kind: "agent" | "workflow" | "route",
599
+ ): T | undefined {
600
+ const records = [...resources].map((resource) => ({
601
+ id: resource.id,
602
+ aliases: names(resource.value.aliases),
603
+ resource,
604
+ }));
605
+ return lookupById(records, requested, kind)?.record.resource;
606
+ }
607
+
593
608
  function mapResource(resource: KxmResource, map: Map<string, KxmResource>, issues: KxmConfigIssue[]): void {
594
609
  const id = resource.id;
595
610
  if (!id) return;
@@ -872,14 +887,31 @@ function validateWorkflow(
872
887
  ): void {
873
888
  const file = workflow.logicalPath;
874
889
  const coordinator = stringValue(workflow.value.coordinator) ?? "coordinator";
875
- if (!agents.has(coordinator)) issues.push(issue("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
890
+ if (!lookupKxmResource(agents.values(), coordinator, "agent")) issues.push(issue("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
876
891
  const steps = valuesOf(workflow.value, "steps").map((candidate) => objectValue(candidate)).filter((candidate): candidate is JsonObject => Boolean(candidate));
877
892
  const stepIndex = new Map<string, number>();
893
+ const primarySteps = new Set<string>();
878
894
  for (const [index, step] of steps.entries()) {
879
895
  const id = stringValue(step.id);
880
896
  if (!id) continue;
881
897
  if (stepIndex.has(id)) issues.push(issue("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
882
- else stepIndex.set(id, index);
898
+ else {
899
+ stepIndex.set(id, index);
900
+ primarySteps.add(id);
901
+ }
902
+ }
903
+ for (const [index, step] of steps.entries()) {
904
+ const id = stringValue(step.id);
905
+ if (!id) continue;
906
+ const aliases = new Set(names(step.aliases));
907
+ for (const [oldId, newId] of STEP_RENAMES) {
908
+ if (id === newId) aliases.add(oldId);
909
+ if (id === oldId) aliases.add(newId);
910
+ }
911
+ for (const alias of aliases) {
912
+ if (alias === id || primarySteps.has(alias) || stepIndex.has(alias)) continue;
913
+ stepIndex.set(alias, index);
914
+ }
883
915
  }
884
916
 
885
917
  for (const field of ["reproOracle", "planHash"] as const) {
@@ -910,7 +942,7 @@ function validateWorkflow(
910
942
  const kind = stringValue(step.kind);
911
943
  validateToolPolicy(workflow, objectValue(step.tools), `${stepId}.tools`, issues);
912
944
  const primaryAgentId = stringValue(step.agent);
913
- if ((kind === "agent" || kind === "moa") && primaryAgentId && !agents.has(primaryAgentId)) {
945
+ if ((kind === "agent" || kind === "moa") && primaryAgentId && !lookupKxmResource(agents.values(), primaryAgentId, "agent")) {
914
946
  issues.push(issue("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
915
947
  }
916
948
  const gate = stringValue(step.gate);
@@ -938,7 +970,7 @@ function validateWorkflow(
938
970
  const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
939
971
  const allowedAgents = declaredAgents.length > 0 ? declaredAgents : primaryAgentId ? [primaryAgentId] : [];
940
972
  for (const agentId of allowedAgents) {
941
- const agent = agents.get(agentId);
973
+ const agent = lookupKxmResource(agents.values(), agentId, "agent");
942
974
  if (!agent) issues.push(issue("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
943
975
  else {
944
976
  validateAgentScope(agent, step, repositories, file, stepId, issues);
@@ -1003,7 +1035,7 @@ function validateWorkflow(
1003
1035
  const producerMinimum = numberValue(policy.minimumProducers, 1);
1004
1036
  if (producerMinimum > target || producerMinimum > eligible.length) issues.push(issue("semantic", "producer_minimum_impossible", file, `${stepId}.${String(key)} producer minimum exceeds its eligible or target pool`));
1005
1037
  for (const agentId of eligible) {
1006
- if (!agents.has(agentId)) issues.push(issue("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
1038
+ if (!lookupKxmResource(agents.values(), agentId, "agent")) issues.push(issue("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
1007
1039
  if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
1008
1040
  }
1009
1041
  const degradation = objectValue(policy.degradation);
@@ -1026,6 +1058,10 @@ function validateWorkflow(
1026
1058
  if (parsed.terminalStatus === "completed") completedTerminalSources.add(index);
1027
1059
  continue;
1028
1060
  }
1061
+ if (parsed.target && parsed.target !== "$terminal" && !primarySteps.has(parsed.target) && stepIndex.has(parsed.target)) {
1062
+ const canonical = stringValue(steps[stepIndex.get(parsed.target) ?? -1]?.id);
1063
+ if (canonical) noteDeprecatedId("step", parsed.target, canonical);
1064
+ }
1029
1065
  const targetIndex = parsed.target === undefined ? undefined : stepIndex.get(parsed.target);
1030
1066
  if (targetIndex === undefined) {
1031
1067
  issues.push(issue("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
@@ -1195,7 +1231,7 @@ function validateBundle(
1195
1231
  validateModelReferences(models, issues);
1196
1232
 
1197
1233
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
1198
- if (!workflows.has(defaultWorkflow)) issues.push(issue("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
1234
+ if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
1199
1235
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
1200
1236
 
1201
1237
  // Developer policy is the role files under .kxm/roles/ (with the model
@@ -1220,8 +1256,9 @@ function rosterModelIssues(projectRoot: string, roles: ReadonlyMap<string, KxmRe
1220
1256
  for (const entry of valuesOf(role.value, "roster")) {
1221
1257
  const route = stringValue(objectValue(entry)?.route);
1222
1258
  if (!route) continue;
1223
- const logical = `.kxm/models/${route}.yaml`;
1224
- const file = join(projectRoot, ".kxm", "models", `${route}.yaml`);
1259
+ const resolved = findYamlBasename(join(projectRoot, ".kxm", "models"), route, "route") ?? route;
1260
+ const logical = `.kxm/models/${resolved}.yaml`;
1261
+ const file = join(projectRoot, ".kxm", "models", `${resolved}.yaml`);
1225
1262
  let harness: string | undefined;
1226
1263
  try {
1227
1264
  if (existsSync(file)) harness = stringValue(parseRestrictedYaml(readFileSync(file, "utf8"), logical).harness);
@@ -1305,6 +1342,7 @@ function developerRolePolicyIssues(projectRoot: string): KxmConfigIssue[] {
1305
1342
  // is this package's developer policy, so it runs when the loaded project
1306
1343
  // is that package.
1307
1344
  if (resolve(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
1345
+ const lintIssues = lintWorkforce(projectRoot).errors.map((entry) => issue("semantic", entry.code, entry.file, entry.message));
1308
1346
  const rolesDir = join(projectRoot, ".kxm", "roles");
1309
1347
  const modelsDir = join(projectRoot, ".kxm", "models");
1310
1348
  const roles: Record<string, JsonObject> = {};
@@ -1340,7 +1378,7 @@ function developerRolePolicyIssues(projectRoot: string): KxmConfigIssue[] {
1340
1378
  }
1341
1379
  const ceilings = developerCeilings();
1342
1380
  if (!ceilings.ok) {
1343
- return [...parseIssues, issue("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
1381
+ return [...parseIssues, ...lintIssues, issue("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
1344
1382
  }
1345
1383
  const result = validatePolicyDraft({ models, roles, evidence }, {
1346
1384
  ceilings: ceilings.ROUTES,
@@ -1349,8 +1387,8 @@ function developerRolePolicyIssues(projectRoot: string): KxmConfigIssue[] {
1349
1387
  piNativeVendorProviders: ceilings.PI_NATIVE_VENDOR_PROVIDERS,
1350
1388
  vendorAliases: { "x-ai": "xai", moonshotai: "moonshot", "google-ai": "google", qwen: "alibaba" },
1351
1389
  });
1352
- if (result.ok) return parseIssues;
1353
- return [...parseIssues, ...result.issues.map((entry) => issue(entry.phase, entry.code, entry.file, entry.message))];
1390
+ if (result.ok) return [...parseIssues, ...lintIssues];
1391
+ return [...parseIssues, ...lintIssues, ...result.issues.map((entry) => issue(entry.phase, entry.code, entry.file, entry.message))];
1354
1392
  }
1355
1393
 
1356
1394
  export function kxmCanonicalJson(value: JsonValue): string {
@@ -5,6 +5,7 @@ import type { ContextItem } from "./context.ts";
5
5
  import {
6
6
  KxmConfigError,
7
7
  loadKxmProject,
8
+ lookupKxmResource,
8
9
  kxmCanonicalJson,
9
10
  type JsonObject,
10
11
  type JsonValue,
@@ -402,7 +403,7 @@ export function acceptKxmRun(
402
403
  const now = options.now ?? new Date().toISOString();
403
404
  const monotonicNs = options.monotonicNs ?? kxmMonotonicNs();
404
405
 
405
- const workflow = bundle.workflows.get(request.workflowId);
406
+ const workflow = lookupKxmResource(bundle.workflows.values(), request.workflowId, "workflow");
406
407
  if (!workflow) {
407
408
  throw runtimeError("run_workflow_unknown", ".kxm/workflows", `workflow ${request.workflowId} does not exist in this project`);
408
409
  }
@@ -175,35 +175,64 @@ function coreTemplate(projectId: string, projectName: string, variant: KxmTempla
175
175
  ]);
176
176
  if (variant === "v4-registry") {
177
177
  const coordinator = files.get(".kxm/agents/coordinator.yaml");
178
- if (coordinator) files.set(".kxm/agents/coordinator.yaml", { ...coordinator, role: "planner" });
178
+ if (coordinator) {
179
+ files.delete(".kxm/agents/coordinator.yaml");
180
+ files.set(".kxm/agents/planner.yaml", { ...coordinator, role: "planner", aliases: ["coordinator"] });
181
+ }
179
182
  const implementer = files.get(".kxm/agents/implementer.yaml");
180
- if (implementer) files.set(".kxm/agents/implementer.yaml", { ...implementer, role: "writer" });
183
+ if (implementer) {
184
+ files.delete(".kxm/agents/implementer.yaml");
185
+ files.set(".kxm/agents/writer.yaml", { ...implementer, role: "writer", aliases: ["implementer"] });
186
+ }
181
187
  const workflow = files.get(".kxm/workflows/default.yaml");
182
188
  if (workflow) {
183
189
  const limits = { ...(workflow.limits as JsonObject) };
184
190
  delete limits.maxAgentTimeMs;
185
- files.set(".kxm/workflows/default.yaml", { ...workflow, limits });
191
+ const steps = (workflow.steps as JsonObject[]).map((step) => {
192
+ if (step.id === "plan") {
193
+ const on = { ...(step.on as JsonObject), passed: "writer" };
194
+ return { ...step, agent: "planner", on };
195
+ }
196
+ if (step.id === "implement") {
197
+ const assignments = { ...(step.assignments as JsonObject), allowedAgents: ["writer"] };
198
+ return { ...step, id: "writer", aliases: ["implement"], agent: "writer", assignments };
199
+ }
200
+ if (step.id === "verify") {
201
+ const on = { ...(step.on as JsonObject) };
202
+ const failure = on["implementation-failure"];
203
+ if (failure && typeof failure === "object" && !Array.isArray(failure)) {
204
+ on["implementation-failure"] = { ...(failure as JsonObject), target: "writer" };
205
+ }
206
+ return { ...step, on };
207
+ }
208
+ return step;
209
+ });
210
+ files.set(".kxm/workflows/default.yaml", { ...workflow, coordinator: "planner", limits, steps });
186
211
  }
187
212
  // The two models the template names, and nothing else. A fresh project can
188
213
  // be driven without falling through to an unadmitted default model.
214
+ // Route ids follow <harness>-<model-slug>. grok-default and fable-default
215
+ // remain aliases so an existing --route flag still resolves.
189
216
  files.set(".kxm/routes.yaml", {
190
217
  schema: "kxm.routes.v2",
191
- updatedAt: "2026-09-23T00:00:00.000Z",
218
+ updatedAt: "2026-09-27T00:00:00.000Z",
192
219
  admitted: ["anthropic/fable", "xai/grok-4.6"],
193
220
  disabled: [],
194
221
  });
195
- files.set(".kxm/models/grok-default.yaml", {
222
+ files.set(".kxm/models/grok-grok-4-6.yaml", {
196
223
  schema: "kxm.model.v2",
197
- id: "grok-default",
224
+ id: "grok-grok-4-6",
225
+ aliases: ["grok-default"],
198
226
  harness: "grok",
199
227
  model: "grok-4.6",
200
228
  vendor: "xai",
201
229
  status: "admitted",
202
230
  permissions: ["edit"],
203
231
  });
204
- files.set(".kxm/models/fable-default.yaml", {
232
+ files.set(".kxm/models/claude-fable.yaml", {
205
233
  schema: "kxm.model.v2",
206
- id: "fable-default",
234
+ id: "claude-fable",
235
+ aliases: ["fable-default"],
207
236
  harness: "claude",
208
237
  model: "fable",
209
238
  vendor: "anthropic",
@@ -216,7 +245,7 @@ function coreTemplate(projectId: string, projectName: string, variant: KxmTempla
216
245
  purpose: "writer",
217
246
  permission: "edit",
218
247
  description: "Primary implementation agent.",
219
- roster: [{ route: "grok-default" }],
248
+ roster: [{ route: "grok-grok-4-6", effort: "medium" }],
220
249
  });
221
250
  files.set(".kxm/roles/planner.yaml", {
222
251
  schema: "kxm.role.v2",
@@ -224,7 +253,7 @@ function coreTemplate(projectId: string, projectName: string, variant: KxmTempla
224
253
  purpose: "planner",
225
254
  permission: "read-only",
226
255
  description: "Plans the change before implementation.",
227
- roster: [{ route: "fable-default" }],
256
+ roster: [{ route: "claude-fable", effort: "medium" }],
228
257
  });
229
258
  files.set(".kxm/gates.yaml", {
230
259
  schema: "kxm.gate-registry.v1",
@@ -63,23 +63,25 @@ function verifyStep(retryStep: string): Record<string, unknown> {
63
63
  /**
64
64
  * Built-in `kxm workflow add --template` definitions. Each is a complete
65
65
  * kxm.workflow.v1 document (the file name is the workflow id) that uses only
66
- * what `kxm init` creates: the coordinator and implementer agents, the control
66
+ * what `kxm init` creates: the planner and writer agents, the control
67
67
  * repository, and the `test` gate. Agent steps declare `failed` because the
68
- * producer falls back to it.
68
+ * producer falls back to it. `coordinator` and `implementer` still resolve
69
+ * as aliases of `planner` and `writer`.
69
70
  */
70
71
  export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
71
72
  "implement-and-verify": {
72
73
  schema: "kxm.workflow.v1",
73
- description: "Implement a change, then run the project's test gate; a failing gate sends the work back to implement.",
74
- coordinator: "coordinator",
74
+ description: "Implement a change, then run the project's test gate; a failing gate sends the work back to writer.",
75
+ coordinator: "planner",
75
76
  limits: {
76
77
  maxTransitions: 8,
77
78
  },
78
79
  steps: [
79
80
  {
80
- id: "implement",
81
+ id: "writer",
82
+ aliases: ["implement"],
81
83
  kind: "agent",
82
- agent: "implementer",
84
+ agent: "writer",
83
85
  maxAttempts: 3,
84
86
  repositories: { control: "write" },
85
87
  on: {
@@ -87,57 +89,60 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
87
89
  failed: failed(),
88
90
  },
89
91
  },
90
- verifyStep("implement"),
92
+ verifyStep("writer"),
91
93
  ],
92
94
  },
93
95
  "dual-critic-review": {
94
96
  schema: "kxm.workflow.v1",
95
- description: "Implement, review twice, then run the project's test gate. Both reviews run as the coordinator agent; for independent critics, add agents under .kxm/agents and point review-arch and review-cli at them.",
96
- coordinator: "coordinator",
97
+ description: "Write, review twice, then run the project's test gate. Both reviews run as the planner agent; for independent reviewers, add reviewer-arch and reviewer-cli agents and point the review steps at them.",
98
+ coordinator: "planner",
97
99
  limits: {
98
100
  maxTransitions: 12,
99
101
  },
100
102
  steps: [
101
103
  {
102
- id: "implement",
104
+ id: "writer",
105
+ aliases: ["implement"],
103
106
  kind: "agent",
104
- agent: "implementer",
107
+ agent: "writer",
105
108
  maxAttempts: 3,
106
109
  repositories: { control: "write" },
107
110
  on: {
108
- passed: "review-arch",
111
+ passed: "reviewer-arch",
109
112
  failed: failed(),
110
113
  },
111
114
  },
112
115
  {
113
- id: "review-arch",
116
+ id: "reviewer-arch",
117
+ aliases: ["review-arch", "critic-arch"],
114
118
  kind: "agent",
115
- agent: "coordinator",
119
+ agent: "planner",
116
120
  maxAttempts: 3,
117
121
  repositories: { control: "read" },
118
122
  on: {
119
- passed: "review-cli",
120
- failed: { target: "implement", maxTransitions: 2 },
123
+ passed: "reviewer-cli",
124
+ failed: { target: "writer", maxTransitions: 2 },
121
125
  },
122
126
  },
123
127
  {
124
- id: "review-cli",
128
+ id: "reviewer-cli",
129
+ aliases: ["review-cli", "critic-cli"],
125
130
  kind: "agent",
126
- agent: "coordinator",
131
+ agent: "planner",
127
132
  maxAttempts: 3,
128
133
  repositories: { control: "read" },
129
134
  on: {
130
135
  passed: "verify",
131
- failed: { target: "implement", maxTransitions: 2 },
136
+ failed: { target: "writer", maxTransitions: 2 },
132
137
  },
133
138
  },
134
- verifyStep("implement"),
139
+ verifyStep("writer"),
135
140
  ],
136
141
  },
137
142
  "spec-and-plan": {
138
143
  schema: "kxm.workflow.v1",
139
- description: "Plan a change, then review the plan. Both steps run as the coordinator agent and only read the repository.",
140
- coordinator: "coordinator",
144
+ description: "Plan a change, then review the plan. Both steps run as the planner agent and only read the repository.",
145
+ coordinator: "planner",
141
146
  limits: {
142
147
  maxTransitions: 8,
143
148
  },
@@ -145,18 +150,19 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
145
150
  {
146
151
  id: "plan",
147
152
  kind: "agent",
148
- agent: "coordinator",
153
+ agent: "planner",
149
154
  maxAttempts: 3,
150
155
  repositories: { control: "read" },
151
156
  on: {
152
- passed: "review-arch",
157
+ passed: "reviewer-arch",
153
158
  failed: failed(),
154
159
  },
155
160
  },
156
161
  {
157
- id: "review-arch",
162
+ id: "reviewer-arch",
163
+ aliases: ["review-arch", "critic-arch"],
158
164
  kind: "agent",
159
- agent: "coordinator",
165
+ agent: "planner",
160
166
  maxAttempts: 3,
161
167
  repositories: { control: "read" },
162
168
  on: {
@@ -173,13 +179,13 @@ export function scaffoldWorkflowDefinition(description: string): Record<string,
173
179
  return {
174
180
  schema: "kxm.workflow.v1",
175
181
  description,
176
- coordinator: "coordinator",
182
+ coordinator: "planner",
177
183
  limits: { maxTransitions: 8 },
178
184
  steps: [
179
185
  {
180
186
  id: "step-1",
181
187
  kind: "agent",
182
- agent: "implementer",
188
+ agent: "writer",
183
189
  repositories: { control: "write" },
184
190
  on: { passed: completed(), failed: failed() },
185
191
  },
@@ -0,0 +1,38 @@
1
+ export const ROLE_IDS: readonly string[];
2
+ export const ROUTE_RENAMES: readonly (readonly [string, string])[];
3
+ export const AGENT_RENAMES: readonly (readonly [string, string])[];
4
+ export const WORKFLOW_RENAMES: readonly (readonly [string, string])[];
5
+ export const STEP_RENAMES: readonly (readonly [string, string])[];
6
+
7
+ export function noteDeprecatedId(kind: string, from: string, to: string): void;
8
+ export function resetDeprecatedIdWarnings(): void;
9
+ export function resolveRenamedId(
10
+ requested: string,
11
+ knownIds: ReadonlySet<string>,
12
+ pairs: readonly (readonly [string, string])[],
13
+ ): string;
14
+ export function canonicalRouteId(harness: string, model: string): string | undefined;
15
+
16
+ export function lookupById<T extends { id?: string | undefined; aliases?: readonly string[] | undefined }>(
17
+ records: Iterable<T>,
18
+ requested: string,
19
+ kind: "route" | "agent" | "workflow" | "step",
20
+ ): { record: T; viaAlias: boolean } | undefined;
21
+
22
+ export function findYamlBasename(
23
+ directory: string,
24
+ requested: string,
25
+ kind: "route" | "agent" | "workflow" | "step",
26
+ ): string | undefined;
27
+
28
+ export interface WorkforceLintIssue {
29
+ severity: "error" | "warning";
30
+ code: string;
31
+ file: string;
32
+ message: string;
33
+ }
34
+
35
+ export function lintWorkforce(root: string): {
36
+ errors: WorkforceLintIssue[];
37
+ warnings: WorkforceLintIssue[];
38
+ };