@kontextmind/kxm 0.7.92 → 0.7.93

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 (91) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/workflows/default.yaml +1 -1
  3. package/CHANGELOG.md +204 -0
  4. package/README.md +3 -0
  5. package/docs/README.md +3 -0
  6. package/docs/agent-skills.md +123 -60
  7. package/docs/architecture.md +5 -2
  8. package/docs/cli-reference.md +3527 -0
  9. package/docs/config-reference.md +1943 -0
  10. package/docs/configuration.md +29 -3
  11. package/docs/continuous-improvement.md +122 -10
  12. package/docs/contracts/routing.md +95 -11
  13. package/docs/harness-routing.md +616 -0
  14. package/docs/kxm-handbook.md +106 -19
  15. package/docs/templates/README.md +1 -1
  16. package/docs/test-matrix.md +12 -6
  17. package/docs/troubleshooting.md +2 -2
  18. package/examples/project/.kxm/workflows/fix.yaml +1 -1
  19. package/examples/project/.kxm/workflows/improve.yaml +1 -1
  20. package/package.json +1 -1
  21. package/plugins/kxm/.claude-plugin/plugin.json +9 -10
  22. package/plugins/kxm/README.md +238 -56
  23. package/plugins/kxm/dist/claude-hook.js +10083 -0
  24. package/plugins/kxm/dist/cli.js +3068 -2446
  25. package/plugins/kxm/dist/client.js +64 -0
  26. package/plugins/kxm/dist/core.js +102 -9
  27. package/plugins/kxm/dist/extension.js +210 -68
  28. package/plugins/kxm/dist/mcp-server.js +217 -40
  29. package/plugins/kxm/dist/runtime-supervisor.js +1628 -157
  30. package/plugins/kxm/dist/runtime.js +1874 -298
  31. package/plugins/kxm/dist/server.js +416 -82
  32. package/plugins/kxm/package.json +1 -1
  33. package/plugins/kxm/skills/hints.json +1 -1
  34. package/plugins/kxm/skills/kxm/SKILL.md +48 -24
  35. package/plugins/kxm/skills/kxm/references/protocol.md +3 -3
  36. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +61 -21
  37. package/plugins/kxm/skills/kxm-definitions/SKILL.md +9 -0
  38. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +82 -16
  39. package/plugins/kxm/skills/kxm-harvest/SKILL.md +1 -1
  40. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +55 -27
  41. package/plugins/kxm/skills/kxm-insights/SKILL.md +1 -1
  42. package/plugins/kxm/skills/kxm-mind/SKILL.md +2 -2
  43. package/plugins/kxm/skills/{kxm-setup → kxm-mind-setup}/SKILL.md +4 -4
  44. package/plugins/kxm/skills/kxm-peer/SKILL.md +68 -93
  45. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +156 -23
  46. package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
  47. package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
  48. package/plugins/kxm/skills/kxm-query/SKILL.md +1 -1
  49. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +74 -15
  50. package/plugins/kxm/skills/kxm-runs/SKILL.md +46 -17
  51. package/plugins/kxm/skills/kxm-session/SKILL.md +64 -36
  52. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +44 -15
  53. package/plugins/kxm/skills/kxm-tasks/SKILL.md +16 -4
  54. package/plugins/kxm/skills/kxm-triage/SKILL.md +1 -1
  55. package/plugins/kxm/skills/kxm-work/SKILL.md +1 -1
  56. package/plugins/kxm/skills/kxm-workflow/SKILL.md +60 -19
  57. package/plugins/kxm/src/arbiter.ts +67 -22
  58. package/plugins/kxm/src/autocomplete.ts +1 -1
  59. package/plugins/kxm/src/claude-hook.ts +192 -0
  60. package/plugins/kxm/src/cli/project.ts +11 -5
  61. package/plugins/kxm/src/cli/system.ts +85 -13
  62. package/plugins/kxm/src/cli/types.ts +4 -1
  63. package/plugins/kxm/src/cli/workflows.ts +18 -16
  64. package/plugins/kxm/src/cli.ts +23 -13
  65. package/plugins/kxm/src/client.ts +15 -4
  66. package/plugins/kxm/src/commands.ts +19 -9
  67. package/plugins/kxm/src/config.ts +42 -7
  68. package/plugins/kxm/src/context-packet.ts +14 -2
  69. package/plugins/kxm/src/context.ts +16 -5
  70. package/plugins/kxm/src/dispatch-context.ts +286 -0
  71. package/plugins/kxm/src/engine-plan.ts +40 -0
  72. package/plugins/kxm/src/engine.ts +138 -6
  73. package/plugins/kxm/src/hub-env.ts +17 -1
  74. package/plugins/kxm/src/hub.ts +92 -29
  75. package/plugins/kxm/src/improve-sources.ts +228 -0
  76. package/plugins/kxm/src/improve.ts +325 -140
  77. package/plugins/kxm/src/local-snapshot.ts +101 -42
  78. package/plugins/kxm/src/mcp-server.ts +129 -30
  79. package/plugins/kxm/src/project-config.ts +25 -0
  80. package/plugins/kxm/src/protocol.ts +11 -0
  81. package/plugins/kxm/src/relevance.ts +138 -0
  82. package/plugins/kxm/src/retrospective.ts +16 -10
  83. package/plugins/kxm/src/runtime-service.ts +8 -1
  84. package/plugins/kxm/src/runtime-supervisor.ts +16 -2
  85. package/plugins/kxm/src/session-token-hint.ts +17 -0
  86. package/plugins/kxm/src/suggest.ts +7 -7
  87. package/plugins/kxm/src/workflow-manager.ts +80 -78
  88. package/plugins/kxm/src/workflow.ts +202 -12
  89. package/scripts/build-runtime.mjs +7 -1
  90. package/scripts/check-generated.mjs +1 -0
  91. package/scripts/emit-codex-artifacts.mjs +1 -1
@@ -5,6 +5,7 @@ import {
5
5
  canonicalWorkflowEvidenceKey,
6
6
  journalPromotionState,
7
7
  normalizeVerifiedWorkflowEvidence,
8
+ rankImprovementSignals,
8
9
  type WorkflowJournalEntry,
9
10
  type WorkflowRun,
10
11
  } from "./workflow.ts";
@@ -248,12 +249,14 @@ export function buildRetrospective(
248
249
  const byCategory: Record<string, number> = {};
249
250
  const byArea: Record<string, number> = {};
250
251
  const byClass: Record<string, number> = {};
252
+ const byErrorClass: Record<string, number> = {};
251
253
  for (const entry of entries) {
252
254
  increment(byCategory, entry.category);
253
255
  increment(byArea, entry.area);
254
256
  increment(byClass, classFromEvidence(entry.evidence));
257
+ if (entry.category === "error") increment(byErrorClass, classFromEvidence(entry.evidence));
255
258
  }
256
- const recurringErrorClasses = Object.entries(byClass)
259
+ const recurringErrorClasses = Object.entries(byErrorClass)
257
260
  .map(([errorClass, count]) => ({ class: errorClass, count }))
258
261
  .sort((left, right) => right.count - left.count || left.class.localeCompare(right.class));
259
262
  const resolvedContradictions = new Set(entries.filter((entry) => entry.category === "decision" || entry.category === "lesson").flatMap((entry) => entry.relatedEntryIds));
@@ -263,15 +266,18 @@ export function buildRetrospective(
263
266
  const decisions = entries
264
267
  .filter((entry) => entry.category === "decision")
265
268
  .map((entry) => ({ id: entry.id, summary: entry.summary, area: entry.area }));
266
- const proposedImprovements = entries
267
- .filter((entry) => entry.category === "lesson" || entry.category === "error")
268
- .slice(0, 12)
269
- .map((entry) => ({
270
- area: entry.area,
271
- summary: entry.summary,
272
- successMeasure: "reduce recurrence of this class in the next comparable run",
273
- status: "proposed" as const,
274
- }));
269
+ // Ranked, merged error and lesson signals over this run's raw entries. The
270
+ // category filter runs before the limit so up to 12 of them survive.
271
+ const proposedImprovements = rankImprovementSignals(
272
+ journal.filter((entry) => entry.runId === run.id && (entry.category === "error" || entry.category === "lesson")),
273
+ new Map([[run.id, run]]),
274
+ 12,
275
+ ).map((signal) => ({
276
+ area: signal.area,
277
+ summary: signal.summary,
278
+ successMeasure: redactSecrets(`no recurrence of ${signal.key} in the next ${run.definitionId} run`),
279
+ status: "proposed" as const,
280
+ }));
275
281
  const evidenceAudit = buildEvidenceAudit(run);
276
282
  const degradedStageIds = run.stages
277
283
  .filter((stage) => stage.degraded)
@@ -117,7 +117,7 @@ function collectMemoryFiles(dir: string, baseDir: string, ignoreSubdirs: Set<str
117
117
  * the promoted-state snapshot.
118
118
  */
119
119
  export function computeKxmMemoryRevision(
120
- bundle: KxmProjectBundle,
120
+ bundle: Pick<KxmProjectBundle, "projectRoot">,
121
121
  options: KxmMemoryRevisionOptions = {},
122
122
  ): string {
123
123
  const hash = createHash("sha256");
@@ -248,6 +248,11 @@ export interface KxmRuntimeContext {
248
248
  * freeze a budget by supplying a stale clock.
249
249
  */
250
250
  readonly budgetClock?: () => string;
251
+ /**
252
+ * Optional structured logger (the Runtime supervisor's). The engine writes
253
+ * metadata-only lines here, ids and counts, never task or memory text.
254
+ */
255
+ readonly logger?: (entry: Record<string, unknown>) => void;
251
256
  }
252
257
 
253
258
  const closedRuntimeContexts = new WeakSet<KxmRuntimeContext>();
@@ -286,6 +291,7 @@ export function openKxmRuntimeContext(
286
291
  now?: string;
287
292
  homeRuntimeId: string;
288
293
  budgetClock?: () => string;
294
+ logger?: (entry: Record<string, unknown>) => void;
289
295
  },
290
296
  ): KxmRuntimeContext {
291
297
  const paths = kxmRuntimePaths(options.stateRoot !== undefined ? { stateRoot: options.stateRoot } : {});
@@ -310,6 +316,7 @@ export function openKxmRuntimeContext(
310
316
  eventStore,
311
317
  configRevision: bundle.configRevision,
312
318
  ...(options.budgetClock ? { budgetClock: options.budgetClock } : {}),
319
+ ...(options.logger ? { logger: options.logger } : {}),
313
320
  };
314
321
  } catch (error) {
315
322
  registry.close();
@@ -683,7 +683,7 @@ async function startKxmRuntimeSupervisorInner(
683
683
  const key = projectRuntimeKey(projectRoot);
684
684
  const existing = contexts.get(key);
685
685
  if (existing) return existing;
686
- const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot });
686
+ const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot, logger });
687
687
  registerSyncCredentials(context);
688
688
  contexts.set(key, context);
689
689
  return context;
@@ -1315,7 +1315,21 @@ export async function kxmRuntimeRequest(
1315
1315
  if (!response.ok) {
1316
1316
  const code = typeof payload.error === "string" ? payload.error : "runtime_request_failed";
1317
1317
  const message = typeof payload.message === "string" ? payload.message : `runtime request failed with HTTP ${response.status}`;
1318
- throw runtimeError(code, path, message);
1318
+ throw runtimeError(code, path, `${message}${handoffSuffix(payload.handoff)}`);
1319
1319
  }
1320
1320
  return payload;
1321
1321
  }
1322
+
1323
+ const HANDOFF_TEXT_MAX = 200;
1324
+
1325
+ /** Why the Runtime handed a run off, from a `run_handoff_required` body: the
1326
+ * reason, field and detail, each capped so a refusal stays one readable line. */
1327
+ function handoffSuffix(handoff: unknown): string {
1328
+ if (!handoff || typeof handoff !== "object" || Array.isArray(handoff)) return "";
1329
+ const parts: string[] = [];
1330
+ for (const key of ["reason", "field", "detail"] as const) {
1331
+ const value = (handoff as Record<string, unknown>)[key];
1332
+ if (typeof value === "string" && value.length > 0) parts.push(`${key} ${value.slice(0, HANDOFF_TEXT_MAX)}`);
1333
+ }
1334
+ return parts.length > 0 ? ` (handoff ${parts.join("; ")})` : "";
1335
+ }
@@ -0,0 +1,17 @@
1
+ /** User-directed fix for a `session_token_invalid` tool-policy denial.
2
+ *
3
+ * `enforceToolPolicy` (commands.ts) returns that error from three places: an
4
+ * invalid `KXM_SESSION_TOKEN` in the environment (detail starts with
5
+ * `KXM_SESSION_TOKEN`), and an unreadable or malformed/expired session token
6
+ * file on disk. The Claude SessionStart hook and the MCP server both use this
7
+ * text, so a session sees one explanation. Neither text prints or reissues a
8
+ * token: `kxm session token --issue` and bare `kxm session token` print one. */
9
+
10
+ const ENV_TEXT = "KXM_SESSION_TOKEN in the environment Claude Code was launched from is malformed or expired, so every kxm_* tool fails with tool_policy_denied. Ask the user to unset or replace KXM_SESSION_TOKEN in the environment Claude Code was launched from, then restart Claude Code.";
11
+
12
+ const DISK_TEXT = "The KXM session token file on this machine is expired, malformed or unreadable, so every kxm_* tool fails with tool_policy_denied. Ask the user to run `kxm session token --clear` in their own terminal. `kxm session token --status` reports No active session token found for an expired file even though the file still blocks tools. The kxm plugin no longer refreshes that 24-hour token.";
13
+
14
+ export function sessionTokenFixHint(policy: { error?: string | undefined; detail?: string | undefined }): string | undefined {
15
+ if (policy.error !== "session_token_invalid") return undefined;
16
+ return policy.detail?.startsWith("KXM_SESSION_TOKEN") ? ENV_TEXT : DISK_TEXT;
17
+ }
@@ -32,49 +32,49 @@ const WORKFLOW_PATTERNS: WorkflowPattern[] = [
32
32
  id: "software-engineering/bug-fix",
33
33
  area: "software-engineering",
34
34
  keywords: ["fix", "bug", "flaky", "failure", "timeout", "error", "repro", "crash", "broken", "hang"],
35
- skills: ["troubleshooting", "kxm", "kxm-query", "kxm-work"],
35
+ skills: ["kxm-workflow", "kxm-runs", "kxm-context-memory"],
36
36
  defaultCommand: (p) => `kxm run software-engineering/bug-fix "${p}"`,
37
37
  },
38
38
  {
39
39
  id: "software-engineering/feature-implementation",
40
40
  area: "software-engineering",
41
41
  keywords: ["feature", "implement", "add", "build", "create", "develop", "support", "endpoint", "ui", "tui"],
42
- skills: ["modern-web-guidance", "kxm", "kxm-mind", "kxm-query"],
42
+ skills: ["kxm-workflow", "kxm-peer", "kxm-context-memory"],
43
43
  defaultCommand: (p) => `kxm run software-engineering/feature-implementation "${p}"`,
44
44
  },
45
45
  {
46
46
  id: "software-engineering/refactoring",
47
47
  area: "software-engineering",
48
48
  keywords: ["refactor", "cleanup", "reorganize", "modularize", "deduplicate", "split", "simplify", "deprecate"],
49
- skills: ["kxm", "kxm-mind", "kxm-query"],
49
+ skills: ["kxm-workflow", "kxm-runs"],
50
50
  defaultCommand: (p) => `kxm run software-engineering/refactoring "${p}"`,
51
51
  },
52
52
  {
53
53
  id: "security-reliability/vulnerability-remediation",
54
54
  area: "security-reliability",
55
55
  keywords: ["cve", "vulnerability", "security", "exploit", "sanitize", "leak", "secret", "injection", "redact", "auth"],
56
- skills: ["kxm", "kxm-protocol", "kxm-triage"],
56
+ skills: ["kxm-workflow", "kxm-definitions"],
57
57
  defaultCommand: (p) => `kxm run security-reliability/vulnerability-remediation "${p}"`,
58
58
  },
59
59
  {
60
60
  id: "security-reliability/reliability-hardening",
61
61
  area: "security-reliability",
62
62
  keywords: ["idempotency", "retry", "circuit-breaker", "cas", "lock", "concurrency", "deadlock", "race", "crash-recovery"],
63
- skills: ["kxm", "kxm-protocol"],
63
+ skills: ["kxm-workflow", "kxm-peer"],
64
64
  defaultCommand: (p) => `kxm run security-reliability/reliability-hardening "${p}"`,
65
65
  },
66
66
  {
67
67
  id: "data-analytics/pipeline-migration",
68
68
  area: "data-analytics",
69
69
  keywords: ["database", "sqlite", "migration", "pipeline", "schema", "transform", "table", "wal", "foreign", "cascading"],
70
- skills: ["kxm", "kxm-query"],
70
+ skills: ["kxm-runs", "kxm-context-memory"],
71
71
  defaultCommand: (p) => `kxm run data-analytics/pipeline-migration "${p}"`,
72
72
  },
73
73
  {
74
74
  id: "research-strategy/architecture-spike",
75
75
  area: "research-strategy",
76
76
  keywords: ["spike", "investigate", "prototype", "research", "feasibility", "benchmark", "explore", "evaluate"],
77
- skills: ["kxm-session", "kxm-mind", "kxm-query", "kxm-insights"],
77
+ skills: ["kxm-session", "kxm-context-memory", "kxm-routing-improve"],
78
78
  defaultCommand: (p) => `kxm run research-strategy/architecture-spike "${p}"`,
79
79
  },
80
80
  ];
@@ -18,10 +18,40 @@ export interface WorkflowDefSummary {
18
18
  roles: string[];
19
19
  }
20
20
 
21
+ // Fresh objects per use: YAML stringify turns a repeated object into an
22
+ // anchor and alias, which the restricted project loader refuses.
23
+ const completed = () => ({ target: "$terminal", terminalStatus: "completed" });
24
+ const failed = () => ({ target: "$terminal", terminalStatus: "failed" });
25
+
26
+ /** The project's `test` gate from `kxm init`. A gate step settles on `passed` or
27
+ * `implementation-failure`, never `failed`. `maxAttempts` counts entries into a
28
+ * step, so every step a back-edge re-enters allows one entry per transition. */
29
+ function verifyStep(retryStep: string): Record<string, unknown> {
30
+ return {
31
+ id: "verify",
32
+ kind: "gate",
33
+ gate: "test",
34
+ expect: "pass",
35
+ maxAttempts: 3,
36
+ repositories: { control: "write" },
37
+ on: {
38
+ passed: completed(),
39
+ "implementation-failure": { target: retryStep, maxTransitions: 2 },
40
+ },
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Built-in `kxm workflow add --template` definitions. Each is a complete
46
+ * kxm.workflow.v1 document (the file name is the workflow id) that uses only
47
+ * what `kxm init` creates: the coordinator and implementer agents, the control
48
+ * repository, and the `test` gate. Agent steps declare `failed` because the
49
+ * producer falls back to it.
50
+ */
21
51
  export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
22
52
  "implement-and-verify": {
23
53
  schema: "kxm.workflow.v1",
24
- description: "Standard implement and verify workflow",
54
+ description: "Implement a change, then run the project's test gate; a failing gate sends the work back to implement.",
25
55
  coordinator: "coordinator",
26
56
  limits: {
27
57
  maxTransitions: 8,
@@ -30,38 +60,20 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
30
60
  {
31
61
  id: "implement",
32
62
  kind: "agent",
33
- role: "writer",
34
- maxAttempts: 2,
63
+ agent: "implementer",
64
+ maxAttempts: 3,
65
+ repositories: { control: "write" },
35
66
  on: {
36
67
  passed: "verify",
37
- failed: {
38
- target: "$terminal",
39
- terminalStatus: "failed",
40
- },
41
- },
42
- },
43
- {
44
- id: "verify",
45
- kind: "gate",
46
- gate: "verify-gate",
47
- expect: "pass",
48
- maxAttempts: 1,
49
- on: {
50
- passed: {
51
- target: "$terminal",
52
- terminalStatus: "completed",
53
- },
54
- failed: {
55
- target: "implement",
56
- maxTransitions: 2,
57
- },
68
+ failed: failed(),
58
69
  },
59
70
  },
71
+ verifyStep("implement"),
60
72
  ],
61
73
  },
62
74
  "dual-critic-review": {
63
75
  schema: "kxm.workflow.v1",
64
- description: "Dual-critic review workflow with independent Fable architecture and Sol CLI critics",
76
+ 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.",
65
77
  coordinator: "coordinator",
66
78
  limits: {
67
79
  maxTransitions: 12,
@@ -70,102 +82,92 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
70
82
  {
71
83
  id: "implement",
72
84
  kind: "agent",
73
- role: "writer",
74
- maxAttempts: 2,
85
+ agent: "implementer",
86
+ maxAttempts: 3,
87
+ repositories: { control: "write" },
75
88
  on: {
76
89
  passed: "review-arch",
77
- failed: {
78
- target: "$terminal",
79
- terminalStatus: "failed",
80
- },
90
+ failed: failed(),
81
91
  },
82
92
  },
83
93
  {
84
94
  id: "review-arch",
85
95
  kind: "agent",
86
- role: "critic-arch",
87
- maxAttempts: 2,
96
+ agent: "coordinator",
97
+ maxAttempts: 3,
98
+ repositories: { control: "read" },
88
99
  on: {
89
100
  passed: "review-cli",
90
- failed: {
91
- target: "implement",
92
- maxTransitions: 2,
93
- },
101
+ failed: { target: "implement", maxTransitions: 2 },
94
102
  },
95
103
  },
96
104
  {
97
105
  id: "review-cli",
98
106
  kind: "agent",
99
- role: "critic-cli",
100
- maxAttempts: 2,
107
+ agent: "coordinator",
108
+ maxAttempts: 3,
109
+ repositories: { control: "read" },
101
110
  on: {
102
111
  passed: "verify",
103
- failed: {
104
- target: "implement",
105
- maxTransitions: 2,
106
- },
107
- },
108
- },
109
- {
110
- id: "verify",
111
- kind: "gate",
112
- gate: "verify-gate",
113
- expect: "pass",
114
- maxAttempts: 1,
115
- on: {
116
- passed: {
117
- target: "$terminal",
118
- terminalStatus: "completed",
119
- },
120
- failed: {
121
- target: "implement",
122
- maxTransitions: 2,
123
- },
112
+ failed: { target: "implement", maxTransitions: 2 },
124
113
  },
125
114
  },
115
+ verifyStep("implement"),
126
116
  ],
127
117
  },
128
118
  "spec-and-plan": {
129
119
  schema: "kxm.workflow.v1",
130
- description: "Specification and architecture breakdown planning workflow",
120
+ description: "Plan a change, then review the plan. Both steps run as the coordinator agent and only read the repository.",
131
121
  coordinator: "coordinator",
132
122
  limits: {
133
- maxTransitions: 6,
123
+ maxTransitions: 8,
134
124
  },
135
125
  steps: [
136
126
  {
137
127
  id: "plan",
138
128
  kind: "agent",
139
- role: "planner",
140
- maxAttempts: 2,
129
+ agent: "coordinator",
130
+ maxAttempts: 3,
131
+ repositories: { control: "read" },
141
132
  on: {
142
133
  passed: "review-arch",
143
- failed: {
144
- target: "$terminal",
145
- terminalStatus: "failed",
146
- },
134
+ failed: failed(),
147
135
  },
148
136
  },
149
137
  {
150
138
  id: "review-arch",
151
139
  kind: "agent",
152
- role: "critic-arch",
153
- maxAttempts: 2,
140
+ agent: "coordinator",
141
+ maxAttempts: 3,
142
+ repositories: { control: "read" },
154
143
  on: {
155
- passed: {
156
- target: "$terminal",
157
- terminalStatus: "completed",
158
- },
159
- failed: {
160
- target: "plan",
161
- maxTransitions: 2,
162
- },
144
+ passed: completed(),
145
+ failed: { target: "plan", maxTransitions: 2 },
163
146
  },
164
147
  },
165
148
  ],
166
149
  },
167
150
  };
168
151
 
152
+ /** The one-step definition `kxm workflow add <id>` writes without a template or file. */
153
+ export function scaffoldWorkflowDefinition(description: string): Record<string, unknown> {
154
+ return {
155
+ schema: "kxm.workflow.v1",
156
+ description,
157
+ coordinator: "coordinator",
158
+ limits: { maxTransitions: 8 },
159
+ steps: [
160
+ {
161
+ id: "step-1",
162
+ kind: "agent",
163
+ agent: "implementer",
164
+ repositories: { control: "write" },
165
+ on: { passed: completed(), failed: failed() },
166
+ },
167
+ ],
168
+ };
169
+ }
170
+
169
171
  export const DEFAULT_WORKFLOW_TEMPLATE = WORKFLOW_TEMPLATES["implement-and-verify"]!;
170
172
 
171
173
  export function workflowsDirectory(