@kontextmind/kxm 0.6.0

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 (227) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.kxm/README.md +14 -0
  3. package/.kxm/assets/README.md +5 -0
  4. package/.kxm/assets/retrospectives/README.md +5 -0
  5. package/.kxm/config/README.md +5 -0
  6. package/.kxm/config/agents.json +43 -0
  7. package/.kxm/config/env.example +56 -0
  8. package/.kxm/config/update.example.yaml +9 -0
  9. package/.kxm/config/workflows/fix.json +160 -0
  10. package/.kxm/config/workflows/jira-development.json +116 -0
  11. package/.kxm/config/workflows/provenance-quorum.json +150 -0
  12. package/.kxm/config/workflows/v04-dogfood.json +72 -0
  13. package/CHANGELOG.md +465 -0
  14. package/LICENSE +21 -0
  15. package/README.md +306 -0
  16. package/SECURITY.md +72 -0
  17. package/docs/README.md +48 -0
  18. package/docs/agent-communication-envelopes-and-gates.md +553 -0
  19. package/docs/architecture.md +242 -0
  20. package/docs/assignment-runner.md +241 -0
  21. package/docs/configuration.md +361 -0
  22. package/docs/continuous-improvement.md +114 -0
  23. package/docs/getting-started.md +253 -0
  24. package/docs/kxm-handbook.md +1090 -0
  25. package/docs/operations.md +205 -0
  26. package/docs/provenance-gates.md +291 -0
  27. package/docs/skills.md +45 -0
  28. package/docs/templates/README.md +95 -0
  29. package/docs/templates/adr.md +88 -0
  30. package/docs/templates/architecture.md +120 -0
  31. package/docs/templates/bug-fix.md +109 -0
  32. package/docs/templates/feature.md +108 -0
  33. package/docs/templates/handoff.md +72 -0
  34. package/docs/templates/postmortem.md +77 -0
  35. package/docs/templates/research.md +100 -0
  36. package/docs/templates/review.md +85 -0
  37. package/docs/templates/runbook.md +73 -0
  38. package/docs/templates/test-plan.md +87 -0
  39. package/docs/templates/test-report.md +72 -0
  40. package/docs/test-matrix.md +121 -0
  41. package/docs/troubleshooting.md +249 -0
  42. package/docs/vnext/README.md +62 -0
  43. package/docs/vnext/architecture.md +185 -0
  44. package/docs/vnext/effects-and-recovery.md +172 -0
  45. package/docs/vnext/lifecycles.md +235 -0
  46. package/docs/vnext/migration.md +220 -0
  47. package/docs/vnext/routing.md +184 -0
  48. package/docs/vnext/synchronization.md +172 -0
  49. package/docs/vnext/terminology.md +240 -0
  50. package/docs/vnext/validation.md +335 -0
  51. package/docs/webhook-workflows.md +240 -0
  52. package/docs/workflow-guide.md +1150 -0
  53. package/examples/README.md +102 -0
  54. package/examples/provenance-workflow.json +40 -0
  55. package/examples/requester.ts +30 -0
  56. package/examples/reviewer-agent.ts +29 -0
  57. package/examples/roundtrip.ts +46 -0
  58. package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
  59. package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
  60. package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
  61. package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
  62. package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
  63. package/examples/vnext/.kxm/agents/planner.yaml +13 -0
  64. package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
  65. package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
  66. package/examples/vnext/.kxm/gates.yaml +8 -0
  67. package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
  68. package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
  69. package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
  70. package/examples/vnext/.kxm/models/implementation.yaml +14 -0
  71. package/examples/vnext/.kxm/models/primary.yaml +17 -0
  72. package/examples/vnext/.kxm/prices.yaml +111 -0
  73. package/examples/vnext/.kxm/project/env.yaml +7 -0
  74. package/examples/vnext/.kxm/project.yaml +32 -0
  75. package/examples/vnext/.kxm/repo/repo.yaml +8 -0
  76. package/examples/vnext/.kxm/workflows/default.yaml +92 -0
  77. package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
  78. package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
  79. package/examples/vnext/README.md +53 -0
  80. package/examples/vnext/records/assignment-result-recorded.json +63 -0
  81. package/examples/vnext/records/assignment-result.json +46 -0
  82. package/examples/vnext/records/context-candidate.json +42 -0
  83. package/examples/vnext/records/delivery-manifest.json +66 -0
  84. package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
  85. package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
  86. package/examples/vnext/records/run-created.json +54 -0
  87. package/examples/vnext/records/sync-event.json +65 -0
  88. package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
  89. package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
  90. package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
  91. package/examples/workflow-signal.ts +63 -0
  92. package/package.json +129 -0
  93. package/plugins/kxm/.claude-plugin/plugin.json +73 -0
  94. package/plugins/kxm/.mcp.json +19 -0
  95. package/plugins/kxm/README.md +93 -0
  96. package/plugins/kxm/dist/cli.js +42853 -0
  97. package/plugins/kxm/dist/client.js +416 -0
  98. package/plugins/kxm/dist/core.js +1823 -0
  99. package/plugins/kxm/dist/extension.js +3797 -0
  100. package/plugins/kxm/dist/mcp-server.js +17104 -0
  101. package/plugins/kxm/dist/runtime.js +23361 -0
  102. package/plugins/kxm/dist/server.js +13640 -0
  103. package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
  104. package/plugins/kxm/package.json +12 -0
  105. package/plugins/kxm/skills/kxm/SKILL.md +97 -0
  106. package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
  107. package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
  108. package/plugins/kxm/src/arbiter.ts +355 -0
  109. package/plugins/kxm/src/artifacts-exist.ts +62 -0
  110. package/plugins/kxm/src/autocomplete.ts +236 -0
  111. package/plugins/kxm/src/cli.ts +3707 -0
  112. package/plugins/kxm/src/client.ts +614 -0
  113. package/plugins/kxm/src/commands.ts +1063 -0
  114. package/plugins/kxm/src/config.ts +290 -0
  115. package/plugins/kxm/src/context/providers.ts +101 -0
  116. package/plugins/kxm/src/context-packet.ts +332 -0
  117. package/plugins/kxm/src/context.ts +499 -0
  118. package/plugins/kxm/src/core.ts +6 -0
  119. package/plugins/kxm/src/database.ts +563 -0
  120. package/plugins/kxm/src/diagnostics.ts +184 -0
  121. package/plugins/kxm/src/envelope.ts +118 -0
  122. package/plugins/kxm/src/extension.ts +895 -0
  123. package/plugins/kxm/src/external-effects.ts +299 -0
  124. package/plugins/kxm/src/github-watch.ts +255 -0
  125. package/plugins/kxm/src/hub-binding.ts +160 -0
  126. package/plugins/kxm/src/hub.ts +2502 -0
  127. package/plugins/kxm/src/improve.ts +383 -0
  128. package/plugins/kxm/src/inbox.ts +10 -0
  129. package/plugins/kxm/src/kxm-install-kind.ts +113 -0
  130. package/plugins/kxm/src/kxm-update-config.ts +39 -0
  131. package/plugins/kxm/src/kxm-update.ts +238 -0
  132. package/plugins/kxm/src/local-snapshot.ts +406 -0
  133. package/plugins/kxm/src/logger.ts +198 -0
  134. package/plugins/kxm/src/mcp-server.ts +143 -0
  135. package/plugins/kxm/src/memory.ts +385 -0
  136. package/plugins/kxm/src/nous-pi.ts +287 -0
  137. package/plugins/kxm/src/nous-provider.ts +729 -0
  138. package/plugins/kxm/src/price-calc.ts +87 -0
  139. package/plugins/kxm/src/prices.ts +121 -0
  140. package/plugins/kxm/src/protocol.ts +172 -0
  141. package/plugins/kxm/src/recovery.ts +211 -0
  142. package/plugins/kxm/src/redact.ts +26 -0
  143. package/plugins/kxm/src/retrospective.ts +400 -0
  144. package/plugins/kxm/src/routing.ts +830 -0
  145. package/plugins/kxm/src/runtime.ts +9 -0
  146. package/plugins/kxm/src/server.ts +117 -0
  147. package/plugins/kxm/src/session-work.ts +571 -0
  148. package/plugins/kxm/src/session.ts +184 -0
  149. package/plugins/kxm/src/skills.ts +535 -0
  150. package/plugins/kxm/src/state.ts +326 -0
  151. package/plugins/kxm/src/store.ts +637 -0
  152. package/plugins/kxm/src/studio-layout.ts +268 -0
  153. package/plugins/kxm/src/suggest.ts +162 -0
  154. package/plugins/kxm/src/task-manager.ts +244 -0
  155. package/plugins/kxm/src/telemetry.ts +116 -0
  156. package/plugins/kxm/src/tui.ts +1046 -0
  157. package/plugins/kxm/src/vnext-bindings.ts +403 -0
  158. package/plugins/kxm/src/vnext-config.ts +1646 -0
  159. package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
  160. package/plugins/kxm/src/vnext-engine-command.ts +533 -0
  161. package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
  162. package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
  163. package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
  164. package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
  165. package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
  166. package/plugins/kxm/src/vnext-engine.ts +2458 -0
  167. package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
  168. package/plugins/kxm/src/vnext-harness.ts +1142 -0
  169. package/plugins/kxm/src/vnext-init.ts +430 -0
  170. package/plugins/kxm/src/vnext-migrate.ts +1848 -0
  171. package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
  172. package/plugins/kxm/src/vnext-permission.ts +936 -0
  173. package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
  174. package/plugins/kxm/src/vnext-repair.ts +1094 -0
  175. package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
  176. package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
  177. package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
  178. package/plugins/kxm/src/vnext-runtime.ts +663 -0
  179. package/plugins/kxm/src/vnext-template.ts +247 -0
  180. package/plugins/kxm/src/wiki.ts +313 -0
  181. package/plugins/kxm/src/workflow.ts +1548 -0
  182. package/schemas/vnext/README.md +46 -0
  183. package/schemas/vnext/agent.schema.json +40 -0
  184. package/schemas/vnext/assignment-result.schema.json +66 -0
  185. package/schemas/vnext/backup-manifest.schema.json +89 -0
  186. package/schemas/vnext/candidate.schema.json +109 -0
  187. package/schemas/vnext/common.schema.json +422 -0
  188. package/schemas/vnext/context-candidate.schema.json +76 -0
  189. package/schemas/vnext/context-packet.schema.json +192 -0
  190. package/schemas/vnext/delivery-manifest.schema.json +159 -0
  191. package/schemas/vnext/environment.schema.json +66 -0
  192. package/schemas/vnext/gate-registry.schema.json +109 -0
  193. package/schemas/vnext/handoff-manifest.schema.json +146 -0
  194. package/schemas/vnext/init-operation.schema.json +61 -0
  195. package/schemas/vnext/local-repository-bindings.schema.json +30 -0
  196. package/schemas/vnext/memory-record.schema.json +45 -0
  197. package/schemas/vnext/migration-decision.schema.json +26 -0
  198. package/schemas/vnext/migration-plan.schema.json +123 -0
  199. package/schemas/vnext/migration-receipt.schema.json +52 -0
  200. package/schemas/vnext/model.schema.json +42 -0
  201. package/schemas/vnext/permission-diff.schema.json +57 -0
  202. package/schemas/vnext/prices.schema.json +115 -0
  203. package/schemas/vnext/project.schema.json +85 -0
  204. package/schemas/vnext/repository.schema.json +24 -0
  205. package/schemas/vnext/run-event.schema.json +460 -0
  206. package/schemas/vnext/session-brief.schema.json +153 -0
  207. package/schemas/vnext/sync-event.schema.json +234 -0
  208. package/schemas/vnext/template-provenance.schema.json +38 -0
  209. package/schemas/vnext/workflow.schema.json +248 -0
  210. package/scripts/assignment-run.d.mts +354 -0
  211. package/scripts/assignment-run.mjs +4451 -0
  212. package/scripts/build-runtime.mjs +56 -0
  213. package/scripts/check-generated.mjs +77 -0
  214. package/scripts/check-versions.mjs +34 -0
  215. package/scripts/emit-codex-artifacts.d.mts +9 -0
  216. package/scripts/emit-codex-artifacts.mjs +91 -0
  217. package/scripts/harness-run.d.mts +83 -0
  218. package/scripts/harness-run.mjs +2095 -0
  219. package/scripts/kxm-hub.mjs +105 -0
  220. package/scripts/kxm-publish-npm.mjs +327 -0
  221. package/scripts/kxm-release-github.mjs +472 -0
  222. package/scripts/kxm-runtime-supervisor.mjs +7 -0
  223. package/scripts/kxm-worker.mjs +1127 -0
  224. package/scripts/kxm.mjs +27 -0
  225. package/scripts/roster-policy.d.mts +20 -0
  226. package/scripts/roster-policy.mjs +161 -0
  227. package/scripts/smoke-multi-pi.mjs +479 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * KXM Web Studio Layout Engine (Phase 10 / Decision D14)
3
+ * Generates form/stepper metadata, ELK/React Flow-derived DAG nodes/edges,
4
+ * and Temporal swimlane activity timelines from compiled plans and run states.
5
+ */
6
+
7
+ import type { VnextCompiledPlan, VnextCompiledStep } from "./vnext-engine-compile.ts";
8
+ import type { VnextRunState } from "./vnext-engine-fold.ts";
9
+
10
+ export const STUDIO_LAYOUT_SCHEMA = "kxm.studio-layout.v1" as const;
11
+
12
+ export interface StudioDagNode {
13
+ id: string;
14
+ type: "agentStep" | "gateStep" | "approvalStep" | "waitStep";
15
+ data: {
16
+ label: string;
17
+ kind: string;
18
+ role?: string | undefined;
19
+ gate?: string | undefined;
20
+ status: "pending" | "preparing" | "running" | "passed" | "failed" | "waiting" | "cancelled";
21
+ stepAttempt?: number | undefined;
22
+ description?: string | undefined;
23
+ outcomes: readonly string[];
24
+ };
25
+ position: { x: number; y: number };
26
+ }
27
+
28
+ export interface StudioDagEdge {
29
+ id: string;
30
+ source: string;
31
+ target: string;
32
+ label?: string | undefined;
33
+ animated?: boolean | undefined;
34
+ style?: { stroke?: string } | undefined;
35
+ }
36
+
37
+ export interface TemporalSwimlaneActivity {
38
+ stepId: string;
39
+ attemptId?: string | undefined;
40
+ status: string;
41
+ startOffsetMs: number;
42
+ durationMs: number;
43
+ stepAttempt: number;
44
+ }
45
+
46
+ export interface TemporalSwimlane {
47
+ role: string;
48
+ agentId?: string | undefined;
49
+ activities: TemporalSwimlaneActivity[];
50
+ }
51
+
52
+ export interface StudioStepperStage {
53
+ id: string;
54
+ label: string;
55
+ kind: string;
56
+ status: "pending" | "active" | "completed" | "failed" | "skipped";
57
+ }
58
+
59
+ export interface StudioLayoutPayload {
60
+ schema: typeof STUDIO_LAYOUT_SCHEMA;
61
+ workflowId: string;
62
+ runId?: string | undefined;
63
+ generatedAt: string;
64
+ stepper: StudioStepperStage[];
65
+ dag: {
66
+ nodes: StudioDagNode[];
67
+ edges: StudioDagEdge[];
68
+ };
69
+ temporalSwimlanes: TemporalSwimlane[];
70
+ }
71
+
72
+ /**
73
+ * Computes deterministic layered DAG coordinates (ELK-style hierarchical ranking)
74
+ * without requiring any manual layout coordinates in the YAML workflow file.
75
+ */
76
+ export function generateStudioLayout(
77
+ plan: VnextCompiledPlan,
78
+ state?: VnextRunState | undefined,
79
+ ): StudioLayoutPayload {
80
+ const generatedAt = new Date().toISOString();
81
+ const stepIds = Object.keys(plan.steps);
82
+
83
+ // 1. Calculate topological rank/level for each step
84
+ const inDegree: Record<string, number> = {};
85
+ const adj: Record<string, string[]> = {};
86
+ for (const id of stepIds) {
87
+ inDegree[id] = 0;
88
+ adj[id] = [];
89
+ }
90
+
91
+ for (const [sourceId, step] of Object.entries(plan.steps)) {
92
+ for (const transition of Object.values(step.transitions)) {
93
+ if (transition.to && plan.steps[transition.to]) {
94
+ adj[sourceId]!.push(transition.to);
95
+ inDegree[transition.to] = (inDegree[transition.to] ?? 0) + 1;
96
+ }
97
+ }
98
+ }
99
+
100
+ // Assign levels (x coordinate columns)
101
+ const levels: Record<string, number> = {};
102
+ const queue: string[] = [];
103
+
104
+ if (plan.entryStepId && plan.steps[plan.entryStepId]) {
105
+ queue.push(plan.entryStepId);
106
+ levels[plan.entryStepId] = 0;
107
+ } else {
108
+ for (const id of stepIds) {
109
+ if ((inDegree[id] ?? 0) === 0) {
110
+ queue.push(id);
111
+ levels[id] = 0;
112
+ }
113
+ }
114
+ }
115
+
116
+ while (queue.length > 0) {
117
+ const current = queue.shift()!;
118
+ const curLevel = levels[current] ?? 0;
119
+ for (const neighbor of adj[current] ?? []) {
120
+ const nextLevel = Math.max(levels[neighbor] ?? 0, curLevel + 1);
121
+ levels[neighbor] = nextLevel;
122
+ queue.push(neighbor);
123
+ }
124
+ }
125
+
126
+ // Default any unassigned step to 0
127
+ for (const id of stepIds) {
128
+ if (levels[id] === undefined) levels[id] = 0;
129
+ }
130
+
131
+ // Group steps by level to determine y positions
132
+ const byLevel: Record<number, string[]> = {};
133
+ for (const [id, lvl] of Object.entries(levels)) {
134
+ if (!byLevel[lvl]) byLevel[lvl] = [];
135
+ byLevel[lvl]!.push(id);
136
+ }
137
+
138
+ const nodes: StudioDagNode[] = [];
139
+ const edges: StudioDagEdge[] = [];
140
+ const X_SPACING = 240;
141
+ const Y_SPACING = 120;
142
+
143
+ for (const [lvlStr, idsInLevel] of Object.entries(byLevel)) {
144
+ const lvl = Number(lvlStr);
145
+ const x = lvl * X_SPACING;
146
+ const startY = -(idsInLevel.length - 1) * (Y_SPACING / 2);
147
+
148
+ idsInLevel.forEach((stepId, index) => {
149
+ const step = plan.steps[stepId]!;
150
+ const y = startY + index * Y_SPACING;
151
+
152
+ let status: StudioDagNode["data"]["status"] = "pending";
153
+ let stepAttempt: number | undefined = undefined;
154
+
155
+ if (state) {
156
+ if (state.currentStep?.stepId === stepId) {
157
+ status = state.currentStep.status === "running" ? "running" : "preparing";
158
+ stepAttempt = state.currentStep.stepAttempt;
159
+ } else if (state.stepAttempts[stepId] !== undefined) {
160
+ status = "passed";
161
+ stepAttempt = state.stepAttempts[stepId];
162
+ }
163
+ }
164
+
165
+ let type: StudioDagNode["type"] = "agentStep";
166
+ let role: string | undefined = undefined;
167
+ let gate: string | undefined = undefined;
168
+
169
+ if (step.kind === "agent" || step.kind === "moa") {
170
+ type = "agentStep";
171
+ role = step.agent;
172
+ } else if (step.kind === "gate") {
173
+ type = "gateStep";
174
+ gate = step.gate;
175
+ } else if (step.kind === "approval") {
176
+ type = "approvalStep";
177
+ } else if (step.kind === "wait") {
178
+ type = "waitStep";
179
+ }
180
+
181
+ nodes.push({
182
+ id: stepId,
183
+ type,
184
+ data: {
185
+ label: step.description ?? stepId,
186
+ kind: step.kind,
187
+ role,
188
+ gate,
189
+ status,
190
+ stepAttempt,
191
+ description: step.instructions,
192
+ outcomes: step.outcomes,
193
+ },
194
+ position: { x, y },
195
+ });
196
+
197
+ // Build edges
198
+ for (const [outcome, transition] of Object.entries(step.transitions)) {
199
+ if (transition.to && plan.steps[transition.to]) {
200
+ edges.push({
201
+ id: `e_${stepId}_to_${transition.to}_${outcome}`,
202
+ source: stepId,
203
+ target: transition.to,
204
+ label: outcome !== "passed" && outcome !== "completed" ? outcome : undefined,
205
+ animated: status === "running",
206
+ style: {
207
+ stroke: outcome === "failed" ? "#ef4444" : outcome === "warning" ? "#f59e0b" : "#64748b",
208
+ },
209
+ });
210
+ }
211
+ }
212
+ });
213
+ }
214
+
215
+ // 2. Stepper stages (linear path)
216
+ const stepper: StudioStepperStage[] = plan.order.map((stepId) => {
217
+ const step = plan.steps[stepId];
218
+ let status: StudioStepperStage["status"] = "pending";
219
+ if (state) {
220
+ if (state.currentStep?.stepId === stepId) {
221
+ status = "active";
222
+ } else if (state.stepAttempts[stepId] !== undefined) {
223
+ status = "completed";
224
+ }
225
+ }
226
+ return {
227
+ id: stepId,
228
+ label: step?.description ?? stepId,
229
+ kind: step?.kind ?? "unknown",
230
+ status,
231
+ };
232
+ });
233
+
234
+ // 3. Temporal Swimlanes
235
+ const swimlanesByRole: Record<string, TemporalSwimlaneActivity[]> = {};
236
+ let currentOffset = 0;
237
+
238
+ for (const stepId of plan.order) {
239
+ const step = plan.steps[stepId];
240
+ const role = step && (step.kind === "agent" || step.kind === "moa") ? step.agent : "engine-coordinator";
241
+ if (!swimlanesByRole[role]) swimlanesByRole[role] = [];
242
+
243
+ const duration = step?.timeoutMs ? Math.min(step.timeoutMs, 5000) : 3000;
244
+ swimlanesByRole[role]!.push({
245
+ stepId,
246
+ status: state?.currentStep?.stepId === stepId ? "running" : "completed",
247
+ startOffsetMs: currentOffset,
248
+ durationMs: duration,
249
+ stepAttempt: state?.stepAttempts[stepId] ?? 1,
250
+ });
251
+ currentOffset += duration;
252
+ }
253
+
254
+ const temporalSwimlanes: TemporalSwimlane[] = Object.entries(swimlanesByRole).map(([role, activities]) => ({
255
+ role,
256
+ activities,
257
+ }));
258
+
259
+ return {
260
+ schema: STUDIO_LAYOUT_SCHEMA,
261
+ workflowId: plan.workflowId,
262
+ runId: state ? "active-run" : undefined,
263
+ generatedAt,
264
+ stepper,
265
+ dag: { nodes, edges },
266
+ temporalSwimlanes,
267
+ };
268
+ }
@@ -0,0 +1,162 @@
1
+ export interface RoleSpec {
2
+ harness: string;
3
+ model: string;
4
+ role: string;
5
+ }
6
+
7
+ export interface SuggestionResult {
8
+ workflowId: string;
9
+ area: string;
10
+ confidence: number;
11
+ reasons: string[];
12
+ suggestedSkills: string[];
13
+ roles: {
14
+ planner: RoleSpec;
15
+ writer: RoleSpec;
16
+ critics: RoleSpec[];
17
+ verifier: { kind: "witness"; command: string };
18
+ };
19
+ suggestedCommand: string;
20
+ }
21
+
22
+ interface WorkflowPattern {
23
+ id: string;
24
+ area: string;
25
+ keywords: string[];
26
+ skills: string[];
27
+ defaultCommand: (prompt: string) => string;
28
+ }
29
+
30
+ const WORKFLOW_PATTERNS: WorkflowPattern[] = [
31
+ {
32
+ id: "software-engineering/bug-fix",
33
+ area: "software-engineering",
34
+ keywords: ["fix", "bug", "flaky", "failure", "timeout", "error", "repro", "crash", "broken", "hang"],
35
+ skills: ["troubleshooting", "memory-leak-debugging"],
36
+ defaultCommand: (p) => `kxm run software-engineering/bug-fix "${p}"`,
37
+ },
38
+ {
39
+ id: "software-engineering/feature-implementation",
40
+ area: "software-engineering",
41
+ keywords: ["feature", "implement", "add", "build", "create", "develop", "support", "endpoint", "ui", "tui"],
42
+ skills: ["modern-web-guidance", "kxm"],
43
+ defaultCommand: (p) => `kxm run software-engineering/feature-implementation "${p}"`,
44
+ },
45
+ {
46
+ id: "software-engineering/refactoring",
47
+ area: "software-engineering",
48
+ keywords: ["refactor", "cleanup", "reorganize", "modularize", "deduplicate", "split", "simplify", "deprecate"],
49
+ skills: ["kxm"],
50
+ defaultCommand: (p) => `kxm run software-engineering/refactoring "${p}"`,
51
+ },
52
+ {
53
+ id: "security-reliability/vulnerability-remediation",
54
+ area: "security-reliability",
55
+ keywords: ["cve", "vulnerability", "security", "exploit", "sanitize", "leak", "secret", "injection", "redact", "auth"],
56
+ skills: ["kxm"],
57
+ defaultCommand: (p) => `kxm run security-reliability/vulnerability-remediation "${p}"`,
58
+ },
59
+ {
60
+ id: "security-reliability/reliability-hardening",
61
+ area: "security-reliability",
62
+ keywords: ["idempotency", "retry", "circuit-breaker", "cas", "lock", "concurrency", "deadlock", "race", "crash-recovery"],
63
+ skills: ["kxm"],
64
+ defaultCommand: (p) => `kxm run security-reliability/reliability-hardening "${p}"`,
65
+ },
66
+ {
67
+ id: "data-analytics/pipeline-migration",
68
+ area: "data-analytics",
69
+ keywords: ["database", "sqlite", "migration", "pipeline", "schema", "transform", "table", "wal", "foreign", "cascading"],
70
+ skills: ["kxm"],
71
+ defaultCommand: (p) => `kxm run data-analytics/pipeline-migration "${p}"`,
72
+ },
73
+ {
74
+ id: "research-strategy/architecture-spike",
75
+ area: "research-strategy",
76
+ keywords: ["spike", "investigate", "prototype", "research", "feasibility", "benchmark", "explore", "evaluate"],
77
+ skills: ["kxm-session"],
78
+ defaultCommand: (p) => `kxm run research-strategy/architecture-spike "${p}"`,
79
+ },
80
+ ];
81
+
82
+ export function suggestWorkflowAndRoles(
83
+ prompt: string,
84
+ options: {
85
+ availableHarnesses?: Array<{ harness: string; auth?: string | undefined }> | undefined;
86
+ } = {},
87
+ ): SuggestionResult {
88
+ const normalized = prompt.toLowerCase();
89
+ const words = normalized.split(/\W+/).filter(Boolean);
90
+
91
+ let bestPattern: WorkflowPattern = WORKFLOW_PATTERNS[0]!;
92
+ let bestScore = -1;
93
+ const matchedReasons: string[] = [];
94
+
95
+ for (const pattern of WORKFLOW_PATTERNS) {
96
+ let score = 0;
97
+ const matchedWords: string[] = [];
98
+ for (const keyword of pattern.keywords) {
99
+ if (words.includes(keyword) || normalized.includes(keyword)) {
100
+ score += 1;
101
+ matchedWords.push(keyword);
102
+ }
103
+ }
104
+ if (score > bestScore) {
105
+ bestScore = score;
106
+ bestPattern = pattern;
107
+ matchedReasons.length = 0;
108
+ matchedReasons.push(`Matched keywords: ${matchedWords.join(", ")}`);
109
+ }
110
+ }
111
+
112
+ if (bestScore <= 0) {
113
+ // Default fallback to feature-implementation
114
+ bestPattern = WORKFLOW_PATTERNS[1]!;
115
+ matchedReasons.push("Default fallback: general feature implementation workflow");
116
+ }
117
+
118
+ // Determine active harnesses
119
+ const activeHarnesses = new Set(
120
+ (options.availableHarnesses ?? [])
121
+ .filter((h) => h.auth === "active" || h.auth === "ready" || h.auth === "configured")
122
+ .map((h) => h.harness.toLowerCase()),
123
+ );
124
+
125
+ // If none specified or active, assume standard default rotation
126
+ const hasClaude = activeHarnesses.size === 0 || activeHarnesses.has("claude");
127
+ const hasGrok = activeHarnesses.size === 0 || activeHarnesses.has("grok");
128
+ const hasCodex = activeHarnesses.size === 0 || activeHarnesses.has("codex");
129
+
130
+ // Planner
131
+ const planner: RoleSpec = hasClaude
132
+ ? { harness: "claude", model: "fable", role: "planner" }
133
+ : { harness: "codex", model: "gpt-5.6-sol", role: "planner" };
134
+
135
+ // Writer (Starting rotation: Grok 4.6 headless)
136
+ const writer: RoleSpec = hasGrok
137
+ ? { harness: "grok", model: "grok-4.6", role: "writer" }
138
+ : (activeHarnesses.has("agy")
139
+ ? { harness: "agy", model: "gemini-2.5-pro", role: "writer" }
140
+ : { harness: "pi", model: "qwen/qwen3-coder-plus", role: "writer" });
141
+
142
+ // Critics (Vendor independent: Fable Arch + Codex CLI)
143
+ const critics: RoleSpec[] = [
144
+ { harness: "claude", model: "fable", role: "reviewer-arch" },
145
+ { harness: "codex", model: "gpt-5.6-sol", role: "reviewer-cli" },
146
+ ];
147
+
148
+ return {
149
+ workflowId: bestPattern.id,
150
+ area: bestPattern.area,
151
+ confidence: bestScore > 0 ? Math.min(1, 0.5 + bestScore * 0.15) : 0.5,
152
+ reasons: matchedReasons,
153
+ suggestedSkills: bestPattern.skills,
154
+ roles: {
155
+ planner,
156
+ writer,
157
+ critics,
158
+ verifier: { kind: "witness", command: "npm run verify" },
159
+ },
160
+ suggestedCommand: bestPattern.defaultCommand(prompt.replace(/"/g, '\\"')),
161
+ };
162
+ }
@@ -0,0 +1,244 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { parse, stringify } from "yaml";
5
+
6
+ export const GOAL_SCHEMA = "kxm.goal.v1" as const;
7
+ export const TASK_SCHEMA = "kxm.task.v1" as const;
8
+
9
+ export type GoalStatus = "active" | "achieved" | "abandoned";
10
+ export type TaskStatus = "todo" | "in_progress" | "blocked" | "in_review" | "done";
11
+ export type TrackerType = "github" | "jira" | "gitlab" | "none";
12
+
13
+ export interface GoalRecord {
14
+ schema: typeof GOAL_SCHEMA;
15
+ id: string;
16
+ title: string;
17
+ area: string;
18
+ status: GoalStatus;
19
+ successMetrics: string[];
20
+ targetDate?: string;
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ }
24
+
25
+ export interface TaskAcceptanceCriterion {
26
+ id?: string;
27
+ description: string;
28
+ required: boolean;
29
+ }
30
+
31
+ export interface TaskTrackerSync {
32
+ tracker: TrackerType;
33
+ issueKey: string;
34
+ syncStatus: "synced" | "pending" | "failed";
35
+ lastSyncedAt?: string;
36
+ }
37
+
38
+ export interface TaskRecord {
39
+ schema: typeof TASK_SCHEMA;
40
+ id: string;
41
+ goalId?: string;
42
+ title: string;
43
+ objective: string;
44
+ acceptanceCriteria: TaskAcceptanceCriterion[];
45
+ status: TaskStatus;
46
+ assignedWorkflow?: string;
47
+ workflowRunId?: string;
48
+ trackerSync?: TaskTrackerSync;
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ }
52
+
53
+ function goalsDirectory(repoRoot: string): string {
54
+ return resolve(repoRoot, ".kxm", "goals");
55
+ }
56
+
57
+ function tasksDirectory(repoRoot: string): string {
58
+ return resolve(repoRoot, ".kxm", "tasks");
59
+ }
60
+
61
+ export function createGoal(
62
+ repoRoot: string,
63
+ input: {
64
+ title: string;
65
+ area?: string | undefined;
66
+ successMetrics?: string[] | undefined;
67
+ targetDate?: string | undefined;
68
+ },
69
+ ): GoalRecord {
70
+ const dir = goalsDirectory(repoRoot);
71
+ mkdirSync(dir, { recursive: true });
72
+
73
+ const id = `goal_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
74
+ const now = new Date().toISOString();
75
+ const record: GoalRecord = {
76
+ schema: GOAL_SCHEMA,
77
+ id,
78
+ title: input.title,
79
+ area: input.area ?? "software-engineering",
80
+ status: "active",
81
+ successMetrics: input.successMetrics ?? [],
82
+ ...(input.targetDate ? { targetDate: input.targetDate } : {}),
83
+ createdAt: now,
84
+ updatedAt: now,
85
+ };
86
+
87
+ const filePath = join(dir, `${id}.yaml`);
88
+ writeFileSync(filePath, stringify(record).trim() + "\n", "utf8");
89
+ return record;
90
+ }
91
+
92
+ export function listGoals(repoRoot: string): GoalRecord[] {
93
+ const dir = goalsDirectory(repoRoot);
94
+ if (!existsSync(dir)) return [];
95
+
96
+ const goals: GoalRecord[] = [];
97
+ const entries = readdirSync(dir, { withFileTypes: true });
98
+ for (const entry of entries) {
99
+ if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
100
+ try {
101
+ const text = readFileSync(join(dir, entry.name), "utf8");
102
+ const doc = parse(text) as GoalRecord;
103
+ if (doc && doc.schema === GOAL_SCHEMA) goals.push(doc);
104
+ } catch {
105
+ // skip invalid
106
+ }
107
+ }
108
+ }
109
+
110
+ return goals.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
111
+ }
112
+
113
+ export function createTask(
114
+ repoRoot: string,
115
+ input: {
116
+ goalId?: string | undefined;
117
+ title: string;
118
+ objective: string;
119
+ acceptanceCriteria?: TaskAcceptanceCriterion[] | undefined;
120
+ assignedWorkflow?: string | undefined;
121
+ trackerSync?: {
122
+ tracker: TrackerType;
123
+ issueKey: string;
124
+ } | undefined;
125
+ },
126
+ ): TaskRecord {
127
+ const dir = tasksDirectory(repoRoot);
128
+ mkdirSync(dir, { recursive: true });
129
+
130
+ const id = `task_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
131
+ const now = new Date().toISOString();
132
+ const record: TaskRecord = {
133
+ schema: TASK_SCHEMA,
134
+ id,
135
+ ...(input.goalId ? { goalId: input.goalId } : {}),
136
+ title: input.title,
137
+ objective: input.objective,
138
+ acceptanceCriteria: input.acceptanceCriteria ?? [],
139
+ status: "todo",
140
+ ...(input.assignedWorkflow ? { assignedWorkflow: input.assignedWorkflow } : {}),
141
+ ...(input.trackerSync
142
+ ? {
143
+ trackerSync: {
144
+ tracker: input.trackerSync.tracker,
145
+ issueKey: input.trackerSync.issueKey,
146
+ syncStatus: "pending",
147
+ },
148
+ }
149
+ : {}),
150
+ createdAt: now,
151
+ updatedAt: now,
152
+ };
153
+
154
+ const filePath = join(dir, `${id}.yaml`);
155
+ writeFileSync(filePath, stringify(record).trim() + "\n", "utf8");
156
+ return record;
157
+ }
158
+
159
+ export function listTasks(
160
+ repoRoot: string,
161
+ options: { goalId?: string | undefined; status?: TaskStatus | undefined } = {},
162
+ ): TaskRecord[] {
163
+ const dir = tasksDirectory(repoRoot);
164
+ if (!existsSync(dir)) return [];
165
+
166
+ const tasks: TaskRecord[] = [];
167
+ const entries = readdirSync(dir, { withFileTypes: true });
168
+ for (const entry of entries) {
169
+ if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
170
+ try {
171
+ const text = readFileSync(join(dir, entry.name), "utf8");
172
+ const doc = parse(text) as TaskRecord;
173
+ if (doc && doc.schema === TASK_SCHEMA) {
174
+ if (options.goalId && doc.goalId !== options.goalId) continue;
175
+ if (options.status && doc.status !== options.status) continue;
176
+ tasks.push(doc);
177
+ }
178
+ } catch {
179
+ // skip invalid
180
+ }
181
+ }
182
+ }
183
+
184
+ return tasks.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
185
+ }
186
+
187
+ export function getTask(repoRoot: string, taskId: string): TaskRecord | undefined {
188
+ const dir = tasksDirectory(repoRoot);
189
+ const filePath = join(dir, `${taskId}.yaml`);
190
+ if (!existsSync(filePath)) return undefined;
191
+ try {
192
+ const text = readFileSync(filePath, "utf8");
193
+ const doc = parse(text) as TaskRecord;
194
+ return doc && doc.schema === TASK_SCHEMA ? doc : undefined;
195
+ } catch {
196
+ return undefined;
197
+ }
198
+ }
199
+
200
+ export function updateTaskStatus(
201
+ repoRoot: string,
202
+ taskId: string,
203
+ status: TaskStatus,
204
+ options: { workflowRunId?: string } = {},
205
+ ): TaskRecord {
206
+ const task = getTask(repoRoot, taskId);
207
+ if (!task) {
208
+ throw new Error(`Task ${taskId} not found`);
209
+ }
210
+
211
+ task.status = status;
212
+ if (options.workflowRunId) task.workflowRunId = options.workflowRunId;
213
+ task.updatedAt = new Date().toISOString();
214
+
215
+ const filePath = join(tasksDirectory(repoRoot), `${taskId}.yaml`);
216
+ writeFileSync(filePath, stringify(task).trim() + "\n", "utf8");
217
+ return task;
218
+ }
219
+
220
+ export function syncTaskWithTracker(
221
+ repoRoot: string,
222
+ taskId: string,
223
+ options: {
224
+ mockRemoteState?: string;
225
+ commentReceipt?: string;
226
+ } = {},
227
+ ): TaskRecord {
228
+ const task = getTask(repoRoot, taskId);
229
+ if (!task) {
230
+ throw new Error(`Task ${taskId} not found`);
231
+ }
232
+ if (!task.trackerSync) {
233
+ throw new Error(`Task ${taskId} does not have an associated issue board tracker`);
234
+ }
235
+
236
+ const now = new Date().toISOString();
237
+ task.trackerSync.syncStatus = "synced";
238
+ task.trackerSync.lastSyncedAt = now;
239
+ task.updatedAt = now;
240
+
241
+ const filePath = join(tasksDirectory(repoRoot), `${taskId}.yaml`);
242
+ writeFileSync(filePath, stringify(task).trim() + "\n", "utf8");
243
+ return task;
244
+ }