@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/README.md +20 -2
- package/.kxm/agents/{coordinator.yaml → planner.yaml} +2 -0
- package/.kxm/agents/{critic-arch.yaml → reviewer-arch.yaml} +2 -0
- package/.kxm/agents/{critic-cli.yaml → reviewer-cli.yaml} +2 -0
- package/.kxm/agents/{implementer.yaml → writer.yaml} +2 -0
- package/.kxm/roles/planner.yaml +4 -2
- package/.kxm/roles/reviewer-arch.yaml +4 -2
- package/.kxm/roles/reviewer-cli.yaml +4 -2
- package/.kxm/roles/writer.yaml +6 -3
- package/.kxm/workflows/default.yaml +20 -12
- package/.kxm/workflows/land.yaml +1 -1
- package/.kxm/workflows/{review-arch-only.yaml → reviewer-arch-only.yaml} +7 -3
- package/.kxm/workflows/{review-cli-only.yaml → reviewer-cli-only.yaml} +7 -3
- package/.kxm/workflows/{implement-only.yaml → writer-only.yaml} +8 -4
- package/CHANGELOG.md +48 -8
- package/docs/README.md +1 -1
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +8 -5
- package/docs/adr/ADR-0005-obscura-default-playwright.md +4 -3
- package/docs/adr/ADR-0006-machine-account-names.md +90 -0
- package/docs/adr/ADR-0007-steel-caddy-authentik.md +97 -0
- package/docs/adr/README.md +3 -1
- package/docs/contributing/harness-routing-internals.md +17 -11
- package/docs/contributing/operating-rules.md +13 -1
- package/docs/guides/agent-skills.md +1 -1
- package/docs/guides/browser-automation.md +44 -28
- package/docs/kb/how-credentials-retrieved-safely.md +22 -23
- package/docs/kb/how-to-connect-playwright-to-steel.md +8 -5
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +11 -10
- package/docs/kb/why-automation-opened-different-browser.md +4 -3
- package/docs/operations/deploy.md +31 -0
- package/docs/operations/troubleshooting.md +23 -1
- package/docs/prompts/browser-diagnose-recover.md +2 -2
- package/docs/prompts/browser-start.md +1 -1
- package/docs/reference/cli-reference.md +23 -24
- package/docs/reference/config-reference.md +69 -62
- package/docs/reference/configuration.md +9 -7
- package/docs/reference/harness-routing.md +6 -5
- package/docs/reference/workflow-catalog.md +3 -3
- package/package.json +3 -3
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +1145 -805
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +636 -308
- package/plugins/kxm/dist/runtime.js +710 -382
- package/plugins/kxm/dist/server.js +49 -5
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +11 -12
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -4
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +4 -3
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +5 -5
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -3
- package/plugins/kxm/src/browser.ts +12 -9
- package/plugins/kxm/src/cli/project.ts +2 -1
- package/plugins/kxm/src/cli/roles.ts +3 -4
- package/plugins/kxm/src/engine.ts +9 -6
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/modes.ts +1 -1
- package/plugins/kxm/src/policy-draft.mjs +11 -5
- package/plugins/kxm/src/project-config.ts +49 -11
- package/plugins/kxm/src/runtime-service.ts +2 -1
- package/plugins/kxm/src/template.ts +39 -10
- package/plugins/kxm/src/workflow-manager.ts +34 -28
- package/plugins/kxm/src/workforce-names.d.mts +38 -0
- package/plugins/kxm/src/workforce-names.mjs +317 -0
- package/schemas/agent.schema.json +7 -0
- package/schemas/model.schema.json +12 -0
- package/schemas/workflow.schema.json +14 -0
- package/scripts/harness-run.mjs +1 -1
- package/scripts/native-critic.mjs +5 -5
- package/scripts/roster-policy.mjs +9 -3
- 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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
1224
|
-
const
|
|
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.
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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-
|
|
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-
|
|
222
|
+
files.set(".kxm/models/grok-grok-4-6.yaml", {
|
|
196
223
|
schema: "kxm.model.v2",
|
|
197
|
-
id: "grok-
|
|
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
|
|
232
|
+
files.set(".kxm/models/claude-fable.yaml", {
|
|
205
233
|
schema: "kxm.model.v2",
|
|
206
|
-
id: "fable
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
74
|
-
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: "
|
|
81
|
+
id: "writer",
|
|
82
|
+
aliases: ["implement"],
|
|
81
83
|
kind: "agent",
|
|
82
|
-
agent: "
|
|
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("
|
|
92
|
+
verifyStep("writer"),
|
|
91
93
|
],
|
|
92
94
|
},
|
|
93
95
|
"dual-critic-review": {
|
|
94
96
|
schema: "kxm.workflow.v1",
|
|
95
|
-
description: "
|
|
96
|
-
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: "
|
|
104
|
+
id: "writer",
|
|
105
|
+
aliases: ["implement"],
|
|
103
106
|
kind: "agent",
|
|
104
|
-
agent: "
|
|
107
|
+
agent: "writer",
|
|
105
108
|
maxAttempts: 3,
|
|
106
109
|
repositories: { control: "write" },
|
|
107
110
|
on: {
|
|
108
|
-
passed: "
|
|
111
|
+
passed: "reviewer-arch",
|
|
109
112
|
failed: failed(),
|
|
110
113
|
},
|
|
111
114
|
},
|
|
112
115
|
{
|
|
113
|
-
id: "
|
|
116
|
+
id: "reviewer-arch",
|
|
117
|
+
aliases: ["review-arch", "critic-arch"],
|
|
114
118
|
kind: "agent",
|
|
115
|
-
agent: "
|
|
119
|
+
agent: "planner",
|
|
116
120
|
maxAttempts: 3,
|
|
117
121
|
repositories: { control: "read" },
|
|
118
122
|
on: {
|
|
119
|
-
passed: "
|
|
120
|
-
failed: { target: "
|
|
123
|
+
passed: "reviewer-cli",
|
|
124
|
+
failed: { target: "writer", maxTransitions: 2 },
|
|
121
125
|
},
|
|
122
126
|
},
|
|
123
127
|
{
|
|
124
|
-
id: "
|
|
128
|
+
id: "reviewer-cli",
|
|
129
|
+
aliases: ["review-cli", "critic-cli"],
|
|
125
130
|
kind: "agent",
|
|
126
|
-
agent: "
|
|
131
|
+
agent: "planner",
|
|
127
132
|
maxAttempts: 3,
|
|
128
133
|
repositories: { control: "read" },
|
|
129
134
|
on: {
|
|
130
135
|
passed: "verify",
|
|
131
|
-
failed: { target: "
|
|
136
|
+
failed: { target: "writer", maxTransitions: 2 },
|
|
132
137
|
},
|
|
133
138
|
},
|
|
134
|
-
verifyStep("
|
|
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
|
|
140
|
-
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: "
|
|
153
|
+
agent: "planner",
|
|
149
154
|
maxAttempts: 3,
|
|
150
155
|
repositories: { control: "read" },
|
|
151
156
|
on: {
|
|
152
|
-
passed: "
|
|
157
|
+
passed: "reviewer-arch",
|
|
153
158
|
failed: failed(),
|
|
154
159
|
},
|
|
155
160
|
},
|
|
156
161
|
{
|
|
157
|
-
id: "
|
|
162
|
+
id: "reviewer-arch",
|
|
163
|
+
aliases: ["review-arch", "critic-arch"],
|
|
158
164
|
kind: "agent",
|
|
159
|
-
agent: "
|
|
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: "
|
|
182
|
+
coordinator: "planner",
|
|
177
183
|
limits: { maxTransitions: 8 },
|
|
178
184
|
steps: [
|
|
179
185
|
{
|
|
180
186
|
id: "step-1",
|
|
181
187
|
kind: "agent",
|
|
182
|
-
agent: "
|
|
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
|
+
};
|