mudra-skills 1.2.0 → 3.0.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Mudra Companion - AI Agent Protocol",
3
- "version": "2.0",
3
+ "version": "3.0",
4
4
  "role": {
5
5
  "description": "You are a creative collaborator for gesture-controlled apps. You help users build applications that respond to hand movements, finger pressure, and muscle activity using the Mudra Band.",
6
6
  "approach": "Read what the user wants, fill gaps with smart defaults, and propose a brief concept before building. Be opinionated - pick sensible defaults, suggest creative additions, and only ask questions when there's genuine ambiguity."
@@ -137,7 +137,8 @@
137
137
  "forbidden_proposals": [
138
138
  "Never propose pressure to complement gesture — they are mutually exclusive",
139
139
  "Never propose gesture to complement pressure — they are mutually exclusive",
140
- "Never propose snc alone — it must always come with imu_acc and imu_gyro"
140
+ "Never propose snc alone — it must always come with imu_acc and imu_gyro",
141
+ "Never propose battery as a subscribable signal — battery is read-only via get_status"
141
142
  ]
142
143
  },
143
144
  "signals": {
@@ -154,7 +155,6 @@
154
155
  "type": "gesture",
155
156
  "data": {
156
157
  "type": "tap",
157
- "confidence": 0.95,
158
158
  "timestamp": 1234567890
159
159
  },
160
160
  "timestamp": 1234567890
@@ -164,7 +164,8 @@
164
164
  "double_tap": "Secondary action (like, favorite, details)",
165
165
  "twist": "Back, undo, cancel",
166
166
  "double_twist": "Reset, clear all"
167
- }
167
+ },
168
+ "note": "No confidence field — treat every gesture frame as a valid trigger."
168
169
  },
169
170
  "pressure": {
170
171
  "description": "Finger pressure (0-100%)",
@@ -202,13 +203,10 @@
202
203
  "data_format": {
203
204
  "type": "imu_acc",
204
205
  "data": {
205
- "timestamp": 1234567890,
206
- "values": [
207
- 0.1,
208
- -0.05,
209
- 9.81
210
- ],
211
- "frequency": 100
206
+ "values": [0.1, -0.05, 9.81],
207
+ "frequency": 100,
208
+ "frequency_std": 0.3,
209
+ "timestamp": 1234567890
212
210
  },
213
211
  "timestamp": 1234567890
214
212
  },
@@ -229,13 +227,10 @@
229
227
  "data_format": {
230
228
  "type": "imu_gyro",
231
229
  "data": {
232
- "timestamp": 1234567890,
233
- "values": [
234
- 5.2,
235
- -2.1,
236
- 0.8
237
- ],
238
- "frequency": 100
230
+ "values": [5.2, -2.1, 0.8],
231
+ "frequency": 100,
232
+ "frequency_std": 0.3,
233
+ "timestamp": 1234567890
239
234
  },
240
235
  "timestamp": 1234567890
241
236
  },
@@ -270,6 +265,28 @@
270
265
  "Accumulate for absolute position"
271
266
  ]
272
267
  },
268
+ "nav_direction": {
269
+ "description": "Discrete directional wrist gestures",
270
+ "use_for": "Menu navigation, discrete swiping, page turning",
271
+ "examples": [
272
+ "Next/previous page",
273
+ "Menu up/down",
274
+ "Swipe navigation"
275
+ ],
276
+ "data_format": {
277
+ "type": "nav_direction",
278
+ "data": {
279
+ "direction": "Right",
280
+ "timestamp": 1234567890
281
+ },
282
+ "timestamp": 1234567890
283
+ },
284
+ "notes": [
285
+ "direction values: None, Right, Left, Up, Down, Roll Left, Roll Right",
286
+ "Title-cased with spaces — e.g. 'Roll Left' not 'roll_left'",
287
+ "Mutually exclusive with navigation (continuous) and the IMU+Biometric bundle"
288
+ ]
289
+ },
273
290
  "snc": {
274
291
  "description": "Muscle activity (EMG)",
275
292
  "use_for": "Advanced biometrics",
@@ -281,46 +298,24 @@
281
298
  "data_format": {
282
299
  "type": "snc",
283
300
  "data": {
284
- "values": [
285
- 0.1,
286
- -0.05,
287
- 0.2
288
- ],
301
+ "values": [[0.1, -0.05, 0.2], [0.0, 0.1, -0.1], [0.05, 0.08, 0.0]],
289
302
  "frequency": 1000,
303
+ "frequency_std": 1.5,
290
304
  "timestamp": 1234567890
291
305
  },
292
306
  "timestamp": 1234567890
293
307
  },
294
308
  "notes": [
295
309
  "SNC (Sensorized Neuromusculature Complex) and EMG refer to the same signal",
296
- "Values range from -1 to 1",
297
- "High frequency (1000 Hz)",
298
- "Data comes from 3 sensors, one for each nerve: ulnar, median, and radial",
299
- "The data array is size 3, each cell corresponds to one sensor"
310
+ "Values: array of 3 channel arrays [[ch1_samples], [ch2_samples], [ch3_samples]], floats -1 to 1",
311
+ "High frequency (~1000 Hz)",
312
+ "Data comes from 3 sensors: ulnar, median, and radial nerves"
300
313
  ]
301
314
  },
302
- "battery": {
303
- "description": "Battery level",
304
- "data_format": {
305
- "type": "battery",
306
- "data": {
307
- "level": 85,
308
- "charging": false,
309
- "timestamp": 1234567890
310
- },
311
- "timestamp": 1234567890
312
- }
313
- },
314
315
  "button": {
315
316
  "description": "Air touch button events",
316
- "states": [
317
- "pressed",
318
- "released"
319
- ],
320
- "state_synonyms": [
321
- "press/release",
322
- "hold/release"
323
- ],
317
+ "states": ["pressed", "released"],
318
+ "state_synonyms": ["press/release", "hold/release"],
324
319
  "use_for": "Button-like interactions, discrete on/off triggers, air touch detection",
325
320
  "examples": [
326
321
  "Push-to-talk",
@@ -358,7 +353,8 @@
358
353
  "imu_acc, imu_gyro, and snc are always subscribed together — using any one requires all three",
359
354
  "gesture and pressure are mutually exclusive — never combine them",
360
355
  "navigation and nav_direction are mutually exclusive — never combine them",
361
- "The imu_biometric bundle (imu_acc + imu_gyro + snc) and navigation are mutually exclusive"
356
+ "The imu_biometric bundle (imu_acc + imu_gyro + snc) and navigation are mutually exclusive",
357
+ "The imu_biometric bundle (imu_acc + imu_gyro + snc) and nav_direction are mutually exclusive"
362
358
  ],
363
359
  "valid_combinations": [
364
360
  "gesture + button",
@@ -381,11 +377,11 @@
381
377
  },
382
378
  {
383
379
  "signals": ["navigation", "imu_acc"],
384
- "reason": "Different firmware targets - hardware limitation; also imu_acc belongs to the imu_biometric bundle which is incompatible with navigation"
380
+ "reason": "Different firmware targets - hardware limitation; imu_acc belongs to the imu_biometric bundle which is incompatible with navigation"
385
381
  },
386
382
  {
387
383
  "signals": ["navigation", "imu_gyro"],
388
- "reason": "Different firmware targets - hardware limitation; also imu_gyro belongs to the imu_biometric bundle which is incompatible with navigation"
384
+ "reason": "Different firmware targets - hardware limitation; imu_gyro belongs to the imu_biometric bundle which is incompatible with navigation"
389
385
  },
390
386
  {
391
387
  "signals": ["navigation", "snc"],
@@ -408,112 +404,164 @@
408
404
  },
409
405
  "websocket_api": {
410
406
  "url": "ws://127.0.0.1:8766",
411
- "connection_message": {
412
- "type": "connection_status",
413
- "data": {
414
- "status": "connected",
415
- "message": "Mudra Companion ready"
416
- },
417
- "timestamp": 1234567890
418
- },
407
+ "notes": [
408
+ "Single client only — a second connection is rejected with client_already_connected error then closed.",
409
+ "No auth required.",
410
+ "Server sends NO unsolicited frames on connect. Client must request state explicitly.",
411
+ "No HTTP fallback. ws://127.0.0.1:8766 is the only endpoint."
412
+ ],
419
413
  "commands": {
420
414
  "subscribe": {
421
415
  "description": "Start receiving a signal",
422
- "format": {
423
- "command": "subscribe",
424
- "signal": "<signal_name>"
425
- },
426
- "example": {
427
- "command": "subscribe",
428
- "signal": "gesture"
429
- },
416
+ "format": { "command": "subscribe", "signal": "<signal_name>" },
417
+ "example": { "command": "subscribe", "signal": "gesture" },
430
418
  "critical_note": "CRITICAL: One command per signal - send separately, not as an array. Parameter is 'signal' (singular), NOT 'signals' or 'data.signals'"
431
419
  },
432
420
  "unsubscribe": {
433
421
  "description": "Stop receiving a signal",
434
- "format": {
435
- "command": "unsubscribe",
436
- "signal": "<signal_name>"
437
- },
438
- "example": {
439
- "command": "unsubscribe",
440
- "signal": "pressure"
441
- }
422
+ "format": { "command": "unsubscribe", "signal": "<signal_name>" },
423
+ "example": { "command": "unsubscribe", "signal": "pressure" }
442
424
  },
443
425
  "get_subscriptions": {
444
- "description": "Get current subscriptions",
445
- "format": {
446
- "command": "get_subscriptions"
447
- }
448
- },
449
- "enable": {
450
- "description": "Enable a feature on device",
451
- "format": {
452
- "command": "enable",
453
- "feature": "<feature_name>"
454
- },
455
- "example": {
456
- "command": "enable",
457
- "feature": "pressure"
458
- }
459
- },
460
- "disable": {
461
- "description": "Disable a feature on device",
462
- "format": {
463
- "command": "disable",
464
- "feature": "<feature_name>"
465
- },
466
- "example": {
467
- "command": "disable",
468
- "feature": "pressure"
469
- }
426
+ "description": "Get current subscriptions (8-key boolean map — no battery key)",
427
+ "format": { "command": "get_subscriptions" }
470
428
  },
471
429
  "get_status": {
472
- "description": "Get device status",
473
- "format": {
474
- "command": "get_status"
475
- }
430
+ "description": "Get full device + subscription state",
431
+ "format": { "command": "get_status" },
432
+ "response_shape": {
433
+ "type": "status",
434
+ "data": {
435
+ "device": {
436
+ "name": "string|null",
437
+ "address": "string|null",
438
+ "battery": "number|null",
439
+ "charging": "boolean|null",
440
+ "firmware": "string|null",
441
+ "serial_number": "number|null",
442
+ "hand": "RIGHT|LEFT|null",
443
+ "state": "connected|disconnected",
444
+ "firmware_config": { "target": "string|null", "active": "boolean" }
445
+ },
446
+ "subscriptions": {
447
+ "snc": "boolean",
448
+ "imu_acc": "boolean",
449
+ "imu_gyro": "boolean",
450
+ "pressure": "boolean",
451
+ "gesture": "boolean",
452
+ "navigation": "boolean",
453
+ "nav_direction": "boolean",
454
+ "button": "boolean"
455
+ }
456
+ },
457
+ "timestamp": 1234567890
458
+ },
459
+ "band_connected_rule": "Band is connected if AND ONLY IF device.firmware is non-null AND device.serial_number is non-null. Use this rule — not device.state alone."
460
+ },
461
+ "status": {
462
+ "description": "Alias for get_status",
463
+ "format": { "command": "status" }
476
464
  },
477
- "get_docs": {
478
- "description": "Get full API documentation",
479
- "format": {
480
- "command": "get_docs"
481
- }
465
+ "get_device_info": {
466
+ "description": "Get just the device block (subset of get_status)",
467
+ "format": { "command": "get_device_info" }
482
468
  },
483
469
  "trigger_gesture": {
484
- "description": "Simulate a gesture (for testing without physical device)",
485
- "format": {
486
- "command": "trigger_gesture",
487
- "data": {
488
- "type": "<gesture_type>"
489
- }
490
- },
491
- "example": {
492
- "command": "trigger_gesture",
493
- "data": {
494
- "type": "tap"
495
- }
496
- }
470
+ "description": "Simulate a gesture event for testing without physical device",
471
+ "format": { "command": "trigger_gesture", "data": { "type": "<gesture_type>" } },
472
+ "example": { "command": "trigger_gesture", "data": { "type": "tap" } }
497
473
  }
498
474
  },
499
475
  "message_format": {
500
476
  "structure": {
501
- "type": "<signal_type>",
477
+ "type": "<signal_type or frame_type>",
502
478
  "data": {},
503
479
  "timestamp": 1234567890
504
480
  }
505
481
  },
482
+ "error_frames": {
483
+ "description": "All server-side errors share the same envelope: {type:'error', data:{error:'<code>', message:'...'}, timestamp}",
484
+ "codes": {
485
+ "invalid_json": "Client sent malformed JSON. Never expected from this skill's apps.",
486
+ "missing_command": "Client JSON has no 'command' field. Never expected.",
487
+ "unknown_command": "Command not in the supported list. Never expected after this update.",
488
+ "missing_signal": "subscribe/unsubscribe without a 'signal' field. Never expected.",
489
+ "invalid_signal": "subscribe/unsubscribe with an unrecognised signal name. Never expected after this update — do NOT send 'battery'.",
490
+ "client_already_connected": "CRITICAL: Server already has another connected client. Show a terminal 'close the other tab' message. Do NOT retry the connection."
491
+ }
492
+ },
493
+ "connection_lifecycle": {
494
+ "description": "Required sequence every generated app must follow on Manual→Mudra toggle",
495
+ "steps": [
496
+ "1. new WebSocket('ws://127.0.0.1:8766')",
497
+ "2. ws.onopen: reset backoff counter; immediately send {command:'get_status'}; subscribe to active signals",
498
+ "3. On first 'status' reply: evaluate band-connected predicate (device.firmware && device.serial_number); update connection label; if band connected, replay subscription record",
499
+ "4. Poll get_status every 2s while WS is open",
500
+ "5. On 'error' with data.error === 'client_already_connected': transition to terminal 'Already in use' state; set suppress-reconnect flag; do NOT retry",
501
+ "6. On ws.onclose (suppress flag NOT set): transition to 'Reconnecting…'; backoff [1,2,5,10]s then retry",
502
+ "7. On ws.onclose (suppress flag IS set): do nothing — leave 'Already in use' label showing",
503
+ "8. On Mudra→Manual toggle: close WS cleanly; cancel backoff timer; clear suppress flag"
504
+ ]
505
+ },
506
+ "connection_label_states": {
507
+ "Manual": "Mode toggle is Manual. No WS open. Simulator panel drives signals.",
508
+ "Connecting…": "WS handshake in progress or initial get_status not yet returned.",
509
+ "Mudra connected": "WS open AND device.firmware && device.serial_number are both non-null. Show hand chip: LEFT/RIGHT.",
510
+ "WebSocket only": "WS open BUT band not connected (null firmware or serial_number). Orange (#eab308). Hand chip shows 'None'.",
511
+ "Reconnecting…": "WS closed for non-conflict reason. Backoff retry loop active.",
512
+ "Already in use by another tab": "client_already_connected received. Terminal until page reload. No retry."
513
+ },
514
+ "subscription_record": {
515
+ "description": "Apps must maintain a client-side Set<string> of active subscriptions to enable auto re-subscribe on band reconnect and WS reconnect.",
516
+ "rules": [
517
+ "Add to record when subscribe() is called; remove when unsubscribe() is called",
518
+ "On 'WebSocket only' → 'Mudra connected' transition: replay the Set (re-send subscribe for each entry)",
519
+ "On WS reconnect (Reconnecting… → WS open): replay the Set after sending get_status",
520
+ "In-memory only — no localStorage persistence"
521
+ ]
522
+ },
523
+ "reconnect_backoff": {
524
+ "schedule_ms": [1000, 2000, 5000, 10000],
525
+ "cap": "10000ms indefinitely",
526
+ "reset": "Reset to index 0 on every successful ws.onopen",
527
+ "suppressed_when": "client_already_connected error frame received (FR-017 path)"
528
+ },
506
529
  "common_mistakes": [
507
530
  {
508
- "wrong": "{command: 'enable', data: {signals: ['gesture', 'pressure']}}",
509
- "correct": "{command: 'subscribe', signal: 'gesture'} then {command: 'subscribe', signal: 'pressure'}",
510
- "reason": "Send separate commands, one per signal"
531
+ "wrong": "Subscribing to 'battery': {command: 'subscribe', signal: 'battery'}",
532
+ "correct": "Read device.battery and device.charging from get_status response. Battery is NOT a subscribable signal.",
533
+ "reason": "New server returns error: invalid_signal for 'battery'"
534
+ },
535
+ {
536
+ "wrong": "{command: 'enable', data: {feature: 'pressure'}}",
537
+ "correct": "Use subscribe/unsubscribe commands only. 'enable' and 'disable' do not exist in the new server.",
538
+ "reason": "New server returns error: unknown_command"
539
+ },
540
+ {
541
+ "wrong": "{command: 'get_docs'} or {command: 'help'}",
542
+ "correct": "Read agent_protocol.json and promt.md — documentation lives in the skill, not in the wire protocol.",
543
+ "reason": "New server returns error: unknown_command"
544
+ },
545
+ {
546
+ "wrong": "Waiting for a server-sent 'connection_status' frame before subscribing or updating UI",
547
+ "correct": "Send {command:'get_status'} immediately in ws.onopen. The new server sends NO unsolicited frames on connect.",
548
+ "reason": "The new Dart server never sends an unsolicited connection_status frame. Apps waiting for it will hang forever."
549
+ },
550
+ {
551
+ "wrong": "Reading msg.data.confidence from gesture frames",
552
+ "correct": "Treat every gesture frame as a valid trigger. There is no confidence field.",
553
+ "reason": "New server does not emit confidence. Reads return undefined; threshold checks silently fail."
511
554
  },
512
555
  {
513
556
  "wrong": "{command: 'subscribe', signals: ['gesture', 'pressure']}",
514
557
  "correct": "Send separate subscribe commands - one per signal",
515
558
  "reason": "Parameter is 'signal' (singular), not 'signals'"
516
559
  },
560
+ {
561
+ "wrong": "Retrying after client_already_connected error",
562
+ "correct": "Show 'Mudra Companion is already in use by another tab — please close it before continuing.' Do not retry.",
563
+ "reason": "The single-client lock is held by the other tab. Retrying creates a tight reconnect loop that never succeeds."
564
+ },
517
565
  {
518
566
  "wrong": "Forgetting to subscribe (no data received)",
519
567
  "correct": "You MUST subscribe to each signal you want to receive",
@@ -522,8 +570,7 @@
522
570
  ]
523
571
  },
524
572
  "connection_urls": {
525
- "websocket": "ws://127.0.0.1:8766",
526
- "http": "http://127.0.0.1:8765"
573
+ "websocket": "ws://127.0.0.1:8766"
527
574
  },
528
575
  "architecture_patterns": {
529
576
  "gesture_controlled_ui": {
@@ -601,16 +648,16 @@
601
648
  },
602
649
  "code_examples": {
603
650
  "javascript": {
604
- "description": "Basic WebSocket connection and signal handling",
605
- "code": "const ws = new WebSocket('ws://127.0.0.1:8766');\n\nws.onopen = () => {\n ws.send(JSON.stringify({command: 'subscribe', signal: 'gesture'}));\n ws.send(JSON.stringify({command: 'subscribe', signal: 'pressure'}));\n};\n\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n switch (msg.type) {\n case 'gesture':\n console.log(`Gesture: ${msg.data.type}`);\n break;\n case 'pressure':\n console.log(`Pressure: ${msg.data.normalized * 100}%`);\n break;\n case 'navigation':\n console.log(`Movement: X=${msg.data.delta_x}, Y=${msg.data.delta_y}`);\n break;\n }\n};"
651
+ "description": "Basic WebSocket connection and signal handling (new Dart server protocol)",
652
+ "code": "const ws = new WebSocket('ws://127.0.0.1:8766');\nconst subscriptionRecord = new Set();\nconst backoffSchedule = [1000, 2000, 5000, 10000];\nlet backoffIndex = 0;\nlet suppressReconnect = false;\nlet reconnectTimer = null;\nlet lastBandConnected = false;\n\nfunction subscribe(signal) {\n subscriptionRecord.add(signal);\n ws.send(JSON.stringify({ command: 'subscribe', signal }));\n}\n\nfunction connect() {\n const socket = new WebSocket('ws://127.0.0.1:8766');\n\n socket.onopen = () => {\n backoffIndex = 0;\n socket.send(JSON.stringify({ command: 'get_status' }));\n subscriptionRecord.forEach(signal => socket.send(JSON.stringify({ command: 'subscribe', signal })));\n };\n\n socket.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n if (msg.type === 'status') {\n const bandConnected = Boolean(msg.data.device.firmware && msg.data.device.serial_number);\n const hand = bandConnected ? msg.data.device.hand : 'None';\n updateConnectionLabel(bandConnected, hand);\n if (bandConnected && !lastBandConnected) {\n // Band just reconnected — replay subscriptions (FR-018a)\n subscriptionRecord.forEach(signal => socket.send(JSON.stringify({ command: 'subscribe', signal })));\n }\n lastBandConnected = bandConnected;\n }\n if (msg.type === 'error' && msg.data.error === 'client_already_connected') {\n suppressReconnect = true;\n showAlreadyInUseMessage();\n }\n if (msg.type === 'gesture') handleGesture(msg.data);\n if (msg.type === 'pressure') handlePressure(msg.data);\n if (msg.type === 'navigation') handleNavigation(msg.data);\n };\n\n socket.onclose = () => {\n if (suppressReconnect) return;\n showReconnecting();\n const delay = backoffSchedule[Math.min(backoffIndex, backoffSchedule.length - 1)];\n backoffIndex = Math.min(backoffIndex + 1, backoffSchedule.length - 1);\n reconnectTimer = setTimeout(connect, delay);\n };\n}"
606
653
  },
607
654
  "test_gesture": {
608
655
  "description": "Simulate a gesture for testing without device",
609
656
  "code": "ws.send(JSON.stringify({command: 'trigger_gesture', data: {type: 'tap'}}));"
610
657
  },
611
- "connection_status_check": {
612
- "description": "Handle device disconnection",
613
- "code": "ws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n if (msg.type === 'connection_status' && msg.data.status === 'disconnected') {\n showStatus('Connect your Mudra Band to continue');\n return;\n }\n // ... handle signals\n};"
658
+ "band_connected_check": {
659
+ "description": "Correct way to check if the band (not just the WebSocket) is connected",
660
+ "code": "// After receiving a 'status' frame:\nconst bandConnected = Boolean(msg.data.device.firmware && msg.data.device.serial_number);\nconst hand = bandConnected ? msg.data.device.hand : 'None'; // 'RIGHT', 'LEFT', or 'None'\n// bandConnected = true → show 'Mudra connected' + hand chip\n// bandConnected = false → show 'WebSocket only' in orange (#eab308) + hand 'None'"
614
661
  }
615
662
  },
616
663
  "edge_cases": {
@@ -627,8 +674,16 @@
627
674
  "guidance": "Subscribe to all three (imu_acc + imu_gyro + snc) regardless — they form an inseparable bundle. Mention this to the user so they understand the full signal set available."
628
675
  },
629
676
  "device_not_connected": {
630
- "scenario": "Device is disconnected",
631
- "guidance": "Always include connection status check in the code."
677
+ "scenario": "WebSocket is open but band is not paired (device.firmware and device.serial_number are null)",
678
+ "guidance": "Show 'WebSocket only' label in orange (#eab308). Show 'None' as the hand value. Signals will not arrive until the band is connected. Keep polling get_status every 2s. When band connects, replay subscription record automatically."
679
+ },
680
+ "client_already_connected": {
681
+ "scenario": "Server rejects the connection because another tab already holds the single-client slot",
682
+ "guidance": "Show terminal message: 'Mudra Companion is already in use by another tab — please close it before continuing.' Set suppress-reconnect flag. Do NOT retry."
683
+ },
684
+ "ws_drop_non_conflict": {
685
+ "scenario": "WebSocket closes unexpectedly (Companion closed, OS sleep, etc.)",
686
+ "guidance": "Show 'Reconnecting…' label. Apply backoff [1, 2, 5, 10]s. Retry indefinitely. On reconnect, send get_status and replay subscription record."
632
687
  },
633
688
  "vague_request": {
634
689
  "scenario": "User is vague ('make something cool')",
@@ -643,4 +698,4 @@
643
698
  "guidance": "Adapt the existing code. Don't restart the flow from scratch."
644
699
  }
645
700
  }
646
- }
701
+ }