skilldrop-cli 1.5.0 → 1.7.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 (133) hide show
  1. package/README.md +4 -1
  2. package/catalogue.json +25 -2
  3. package/model-routing.json +20 -0
  4. package/package.json +1 -1
  5. package/packs/civil-architect/pack.json +60 -0
  6. package/packs/civil-architect/skills/floor-plan-layout/SKILL.md +103 -0
  7. package/packs/civil-architect/skills/floor-plan-layout/evals/eval_queries.json +30 -0
  8. package/packs/civil-architect/skills/floor-plan-layout/evals/evals.json +57 -0
  9. package/packs/civil-architect/skills/floor-plan-layout/evals/files/4/adu-programme.md +12 -0
  10. package/packs/civil-architect/skills/floor-plan-layout/examples/ground-floor.md +65 -0
  11. package/packs/civil-architect/skills/floor-plan-layout/examples/ground-floor.svg +135 -0
  12. package/packs/civil-architect/skills/floor-plan-layout/manifest.json +34 -0
  13. package/packs/civil-architect/skills/floor-plan-layout/scripts/plan_check.py +449 -0
  14. package/packs/civil-architect/skills/floor-plan-layout/templates/plan.json +38 -0
  15. package/packs/event-planner/pack.json +61 -0
  16. package/packs/event-planner/skills/run-of-show/SKILL.md +141 -0
  17. package/packs/event-planner/skills/run-of-show/evals/eval_queries.json +30 -0
  18. package/packs/event-planner/skills/run-of-show/evals/evals.json +103 -0
  19. package/packs/event-planner/skills/run-of-show/evals/files/1/agenda.md +16 -0
  20. package/packs/event-planner/skills/run-of-show/evals/files/2/schedule.csv +8 -0
  21. package/packs/event-planner/skills/run-of-show/evals/files/3/notes.txt +3 -0
  22. package/packs/event-planner/skills/run-of-show/evals/files/4/sessions_export.csv +25 -0
  23. package/packs/event-planner/skills/run-of-show/evals/files/4/venue_and_crew.txt +27 -0
  24. package/packs/event-planner/skills/run-of-show/evals/files/5/runsheet_v4.csv +28 -0
  25. package/packs/event-planner/skills/run-of-show/evals/files/5/venue_and_suppliers.txt +22 -0
  26. package/packs/event-planner/skills/run-of-show/evals/files/6/acts_from_booker.csv +26 -0
  27. package/packs/event-planner/skills/run-of-show/evals/files/6/venue_rooms_and_crew.txt +14 -0
  28. package/packs/event-planner/skills/run-of-show/evals/files/7/runsheet_draft_v4.csv +25 -0
  29. package/packs/event-planner/skills/run-of-show/evals/files/7/venue_and_fixed_times.txt +24 -0
  30. package/packs/event-planner/skills/run-of-show/examples/product-launch.md +77 -0
  31. package/packs/event-planner/skills/run-of-show/examples/product-launch.svg +58 -0
  32. package/packs/event-planner/skills/run-of-show/manifest.json +44 -0
  33. package/packs/event-planner/skills/run-of-show/scripts/show_check.py +295 -0
  34. package/packs/event-planner/skills/run-of-show/templates/show.json +15 -0
  35. package/packs/event-planner/skills/seating-plan/SKILL.md +152 -0
  36. package/packs/event-planner/skills/seating-plan/evals/eval_queries.json +30 -0
  37. package/packs/event-planner/skills/seating-plan/evals/evals.json +134 -0
  38. package/packs/event-planner/skills/seating-plan/evals/files/1/guests.csv +31 -0
  39. package/packs/event-planner/skills/seating-plan/evals/files/2/table-plan.txt +8 -0
  40. package/packs/event-planner/skills/seating-plan/evals/files/3/brief.txt +4 -0
  41. package/packs/event-planner/skills/seating-plan/evals/files/3/guests.csv +49 -0
  42. package/packs/event-planner/skills/seating-plan/evals/files/4/rsvp_export.csv +127 -0
  43. package/packs/event-planner/skills/seating-plan/evals/files/4/seating_notes.txt +21 -0
  44. package/packs/event-planner/skills/seating-plan/evals/files/5/organiser_notes.txt +10 -0
  45. package/packs/event-planner/skills/seating-plan/evals/files/5/registrations.csv +93 -0
  46. package/packs/event-planner/skills/seating-plan/evals/files/5/table_plan_v3.csv +94 -0
  47. package/packs/event-planner/skills/seating-plan/evals/files/6/bridgets_notes.txt +30 -0
  48. package/packs/event-planner/skills/seating-plan/evals/files/6/ticket_export.csv +117 -0
  49. package/packs/event-planner/skills/seating-plan/evals/files/6/venue_floorplan_notes.txt +14 -0
  50. package/packs/event-planner/skills/seating-plan/evals/files/7/keep_apart_CONFIDENTIAL.txt +9 -0
  51. package/packs/event-planner/skills/seating-plan/evals/files/7/prom_seating_v3.csv +101 -0
  52. package/packs/event-planner/skills/seating-plan/evals/files/7/ticket_list.csv +101 -0
  53. package/packs/event-planner/skills/seating-plan/evals/files/7/venue_info.txt +16 -0
  54. package/packs/event-planner/skills/seating-plan/evals/files/8/registrations_export.csv +147 -0
  55. package/packs/event-planner/skills/seating-plan/evals/files/8/room_layout.md +22 -0
  56. package/packs/event-planner/skills/seating-plan/evals/files/8/seating_requests.txt +31 -0
  57. package/packs/event-planner/skills/seating-plan/evals/files/9/guest_list.csv +84 -0
  58. package/packs/event-planner/skills/seating-plan/evals/files/9/notes.txt +16 -0
  59. package/packs/event-planner/skills/seating-plan/evals/files/9/table_plan.csv +85 -0
  60. package/packs/event-planner/skills/seating-plan/examples/wedding-dinner.md +61 -0
  61. package/packs/event-planner/skills/seating-plan/examples/wedding-dinner.svg +39 -0
  62. package/packs/event-planner/skills/seating-plan/manifest.json +44 -0
  63. package/packs/event-planner/skills/seating-plan/scripts/seating_check.py +324 -0
  64. package/packs/event-planner/skills/seating-plan/templates/seating.json +28 -0
  65. package/packs/experience-design/pack.json +5 -0
  66. package/packs/experience-design/skills/locale-check/SKILL.md +139 -0
  67. package/packs/experience-design/skills/locale-check/evals/eval_queries.json +38 -0
  68. package/packs/experience-design/skills/locale-check/evals/evals.json +104 -0
  69. package/packs/experience-design/skills/locale-check/evals/files/1/budget.json +1 -0
  70. package/packs/experience-design/skills/locale-check/evals/files/1/locales/ar.json +16 -0
  71. package/packs/experience-design/skills/locale-check/evals/files/1/locales/de.json +12 -0
  72. package/packs/experience-design/skills/locale-check/evals/files/1/locales/en.json +12 -0
  73. package/packs/experience-design/skills/locale-check/evals/files/1/locales/pl.json +12 -0
  74. package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values/strings.xml +12 -0
  75. package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values-es/strings.xml +11 -0
  76. package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values-fr/strings.xml +12 -0
  77. package/packs/experience-design/skills/locale-check/evals/files/3/po/ja.po +20 -0
  78. package/packs/experience-design/skills/locale-check/evals/files/3/po/messages.pot +23 -0
  79. package/packs/experience-design/skills/locale-check/evals/files/3/po/ru.po +22 -0
  80. package/packs/experience-design/skills/locale-check/evals/files/4/budgets.json +15 -0
  81. package/packs/experience-design/skills/locale-check/evals/files/4/locales/ar.json +265 -0
  82. package/packs/experience-design/skills/locale-check/evals/files/4/locales/de.json +265 -0
  83. package/packs/experience-design/skills/locale-check/evals/files/4/locales/en.json +265 -0
  84. package/packs/experience-design/skills/locale-check/evals/files/4/locales/fr.json +265 -0
  85. package/packs/experience-design/skills/locale-check/evals/files/4/locales/pl.json +265 -0
  86. package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values/strings.xml +77 -0
  87. package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values-es/strings.xml +63 -0
  88. package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values-ja/strings.xml +61 -0
  89. package/packs/experience-design/skills/locale-check/evals/files/6/po/cs.po +605 -0
  90. package/packs/experience-design/skills/locale-check/evals/files/6/po/marlin.pot +602 -0
  91. package/packs/experience-design/skills/locale-check/evals/files/6/po/pt_BR.po +602 -0
  92. package/packs/experience-design/skills/locale-check/evals/files/6/po/ru.po +608 -0
  93. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/en.lproj/Localizable.strings +160 -0
  94. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/en.lproj/Localizable.stringsdict +70 -0
  95. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/he.lproj/Localizable.strings +157 -0
  96. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/he.lproj/Localizable.stringsdict +86 -0
  97. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/it.lproj/Localizable.strings +160 -0
  98. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/it.lproj/Localizable.stringsdict +68 -0
  99. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/ko.lproj/Localizable.strings +160 -0
  100. package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/ko.lproj/Localizable.stringsdict +62 -0
  101. package/packs/experience-design/skills/locale-check/examples/settings-release.md +107 -0
  102. package/packs/experience-design/skills/locale-check/manifest.json +45 -0
  103. package/packs/experience-design/skills/locale-check/scripts/locale_check.py +1105 -0
  104. package/packs/operations-manager/pack.json +60 -0
  105. package/packs/operations-manager/skills/shift-roster/SKILL.md +166 -0
  106. package/packs/operations-manager/skills/shift-roster/evals/eval_queries.json +38 -0
  107. package/packs/operations-manager/skills/shift-roster/evals/evals.json +135 -0
  108. package/packs/operations-manager/skills/shift-roster/evals/files/1/shifts.csv +3 -0
  109. package/packs/operations-manager/skills/shift-roster/evals/files/1/staff.csv +8 -0
  110. package/packs/operations-manager/skills/shift-roster/evals/files/2/rota.csv +22 -0
  111. package/packs/operations-manager/skills/shift-roster/evals/files/2/staff.csv +6 -0
  112. package/packs/operations-manager/skills/shift-roster/evals/files/3/team.md +14 -0
  113. package/packs/operations-manager/skills/shift-roster/evals/files/4/availability_export_wk47.csv +23 -0
  114. package/packs/operations-manager/skills/shift-roster/evals/files/4/rota_notes.txt +20 -0
  115. package/packs/operations-manager/skills/shift-roster/evals/files/5/rota_draft_16-29nov.csv +107 -0
  116. package/packs/operations-manager/skills/shift-roster/evals/files/5/staff_list.csv +17 -0
  117. package/packs/operations-manager/skills/shift-roster/evals/files/6/availability_notes.txt +24 -0
  118. package/packs/operations-manager/skills/shift-roster/evals/files/6/peoplehub_staff_export.csv +14 -0
  119. package/packs/operations-manager/skills/shift-roster/evals/files/6/rota_template.csv +25 -0
  120. package/packs/operations-manager/skills/shift-roster/evals/files/7/leave_and_availability.txt +18 -0
  121. package/packs/operations-manager/skills/shift-roster/evals/files/7/rota_wc_30nov.csv +47 -0
  122. package/packs/operations-manager/skills/shift-roster/evals/files/7/staff_list.csv +15 -0
  123. package/packs/operations-manager/skills/shift-roster/evals/files/8/availability_form_responses.csv +22 -0
  124. package/packs/operations-manager/skills/shift-roster/evals/files/8/cover_requirements.csv +15 -0
  125. package/packs/operations-manager/skills/shift-roster/evals/files/8/staff_export.csv +29 -0
  126. package/packs/operations-manager/skills/shift-roster/evals/files/9/leave_and_restrictions.txt +16 -0
  127. package/packs/operations-manager/skills/shift-roster/evals/files/9/rota_export_07-13Dec.csv +85 -0
  128. package/packs/operations-manager/skills/shift-roster/evals/files/9/staff_list.csv +27 -0
  129. package/packs/operations-manager/skills/shift-roster/examples/cafe-week.md +72 -0
  130. package/packs/operations-manager/skills/shift-roster/examples/cafe-week.svg +123 -0
  131. package/packs/operations-manager/skills/shift-roster/manifest.json +34 -0
  132. package/packs/operations-manager/skills/shift-roster/scripts/roster_check.py +633 -0
  133. package/packs/operations-manager/skills/shift-roster/templates/roster.json +22 -0
@@ -0,0 +1,58 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 316" width="1280" height="316" font-family="Helvetica, Arial, sans-serif">
2
+ <rect width="1280" height="316" fill="#ffffff"/>
3
+ <text x="20" y="28" font-size="16" font-weight="bold">Run of show</text>
4
+ <line x1="150.0" y1="60" x2="150.0" y2="252" stroke="#cfd8dc"/>
5
+ <text x="150.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">08:00</text>
6
+ <line x1="205.0" y1="60" x2="205.0" y2="252" stroke="#eceff1"/>
7
+ <line x1="260.0" y1="60" x2="260.0" y2="252" stroke="#cfd8dc"/>
8
+ <text x="260.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">09:00</text>
9
+ <line x1="315.0" y1="60" x2="315.0" y2="252" stroke="#eceff1"/>
10
+ <line x1="370.0" y1="60" x2="370.0" y2="252" stroke="#cfd8dc"/>
11
+ <text x="370.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">10:00</text>
12
+ <line x1="425.0" y1="60" x2="425.0" y2="252" stroke="#eceff1"/>
13
+ <line x1="480.0" y1="60" x2="480.0" y2="252" stroke="#cfd8dc"/>
14
+ <text x="480.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">11:00</text>
15
+ <line x1="535.0" y1="60" x2="535.0" y2="252" stroke="#eceff1"/>
16
+ <line x1="590.0" y1="60" x2="590.0" y2="252" stroke="#cfd8dc"/>
17
+ <text x="590.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">12:00</text>
18
+ <line x1="645.0" y1="60" x2="645.0" y2="252" stroke="#eceff1"/>
19
+ <line x1="700.0" y1="60" x2="700.0" y2="252" stroke="#cfd8dc"/>
20
+ <text x="700.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">13:00</text>
21
+ <line x1="755.0" y1="60" x2="755.0" y2="252" stroke="#eceff1"/>
22
+ <line x1="810.0" y1="60" x2="810.0" y2="252" stroke="#cfd8dc"/>
23
+ <text x="810.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">14:00</text>
24
+ <line x1="865.0" y1="60" x2="865.0" y2="252" stroke="#eceff1"/>
25
+ <line x1="920.0" y1="60" x2="920.0" y2="252" stroke="#cfd8dc"/>
26
+ <text x="920.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">15:00</text>
27
+ <line x1="975.0" y1="60" x2="975.0" y2="252" stroke="#eceff1"/>
28
+ <line x1="1030.0" y1="60" x2="1030.0" y2="252" stroke="#cfd8dc"/>
29
+ <text x="1030.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">16:00</text>
30
+ <line x1="1085.0" y1="60" x2="1085.0" y2="252" stroke="#eceff1"/>
31
+ <line x1="1140.0" y1="60" x2="1140.0" y2="252" stroke="#cfd8dc"/>
32
+ <text x="1140.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">17:00</text>
33
+ <line x1="1195.0" y1="60" x2="1195.0" y2="252" stroke="#eceff1"/>
34
+ <line x1="1250.0" y1="60" x2="1250.0" y2="252" stroke="#cfd8dc"/>
35
+ <text x="1250.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">18:00</text>
36
+ <text x="20" y="96" font-size="12" fill="#263238">Auditorium</text><line x1="150" y1="124" x2="1250.0" y2="124" stroke="#b0bec5"/>
37
+ <rect x="150.0" y="62" width="1100.0" height="60" fill="#f1f8e9" opacity="0.6"/>
38
+ <rect x="205.0" y="68" width="55.0" height="48" fill="#fff8e1" stroke="#ffb300" stroke-dasharray="3,2"/>
39
+ <rect x="260.0" y="66" width="82.5" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
40
+ <text x="264.0" y="84" font-size="10" fill="#0d47a1">Launch keyno…</text><text x="264.0" y="98" font-size="9" fill="#546e7a">09:00–09:45</text>
41
+ <rect x="379.2" y="66" width="110.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
42
+ <text x="383.2" y="84" font-size="10" fill="#0d47a1">Customer panel</text><text x="383.2" y="98" font-size="9" fill="#546e7a">10:05–11:05</text>
43
+ <rect x="535.0" y="66" width="110.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
44
+ <text x="539.0" y="84" font-size="10" fill="#0d47a1">Lunch</text><text x="539.0" y="98" font-size="9" fill="#546e7a">11:30–12:30</text>
45
+ <rect x="1085.0" y="66" width="55.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
46
+ <text x="1089.0" y="84" font-size="10" fill="#0d47a1">Closing…</text><text x="1089.0" y="98" font-size="9" fill="#546e7a">16:30–17:00</text>
47
+ <text x="20" y="160" font-size="12" fill="#263238">Demo lab</text><line x1="150" y1="188" x2="1250.0" y2="188" stroke="#b0bec5"/>
48
+ <rect x="260.0" y="126" width="880.0" height="60" fill="#f1f8e9" opacity="0.6"/>
49
+ <rect x="351.7" y="130" width="82.5" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
50
+ <text x="355.7" y="148" font-size="10" fill="#0d47a1">Live demo: s…</text><text x="355.7" y="162" font-size="9" fill="#546e7a">09:50–10:35</text>
51
+ <rect x="700.0" y="130" width="275.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
52
+ <text x="704.0" y="148" font-size="10" fill="#0d47a1">Hands-on lab</text><text x="704.0" y="162" font-size="9" fill="#546e7a">13:00–15:30</text>
53
+ <text x="20" y="224" font-size="12" fill="#263238">Foyer</text><line x1="150" y1="252" x2="1250.0" y2="252" stroke="#b0bec5"/>
54
+ <rect x="150.0" y="190" width="1100.0" height="60" fill="#f1f8e9" opacity="0.6"/>
55
+ <rect x="205.0" y="194" width="55.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
56
+ <text x="209.0" y="212" font-size="10" fill="#0d47a1">Doors a…</text><text x="209.0" y="226" font-size="9" fill="#546e7a">08:30–09:00</text>
57
+ <text x="20" y="282" font-size="12" fill="#2e7d32">PASS: no clashes</text><text x="20" y="298" font-size="10" fill="#666">Dashed amber: setup before an item · green band: room open</text>
58
+ </svg>
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "run-of-show",
3
+ "version": "0.1.1",
4
+ "description": "Turn an event's agenda into a timed run of show — every session, speaker, changeover and break with its start, end, room and who's needed — and check it with a script for two things in one room at once, a speaker booked in two places, changeovers shorter than the turnaround and anything outside venue hours, drawn as a timeline per room. Use when an organiser asks for \"a run of show\", \"a running order\", \"the minute-by-minute for the day\", \"check the agenda for clashes\", or has sessions, speakers and rooms for a conference, launch, festival or wedding day.",
5
+ "entrypoint": "SKILL.md",
6
+ "deps": {
7
+ "npm": [],
8
+ "pip": []
9
+ },
10
+ "env": {
11
+ "required": [],
12
+ "optional": []
13
+ },
14
+ "related": [
15
+ "seating-plan"
16
+ ],
17
+ "handoff": [
18
+ {
19
+ "to": "seating-plan",
20
+ "when": "the day includes a seated meal or dinner",
21
+ "purpose": "Seat the dinner guests so every together, apart and access request holds.",
22
+ "fallback": "If seating-plan is absent, list each table with its guests and check by hand that no table is over its count and every keep-apart pair is split."
23
+ }
24
+ ],
25
+ "tags": [
26
+ "events",
27
+ "run-of-show",
28
+ "agenda",
29
+ "schedule",
30
+ "conference",
31
+ "timeline",
32
+ "svg"
33
+ ],
34
+ "permissions": {
35
+ "network": [],
36
+ "commands": [],
37
+ "files": "named-paths",
38
+ "notes": "Reads the show.json it is given and writes the SVG to the path passed with --svg."
39
+ },
40
+ "model": {
41
+ "tier": "standard",
42
+ "rationale": "Ordering a day around rooms and people is bounded judgment; the script decides whether anything clashes."
43
+ }
44
+ }
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env python3
2
+ """Check an event's run of show for clashes and draw it as a timeline. Stdlib only.
3
+
4
+ Reads a show.json: the rooms (opening hours, turnaround between items), the items (title,
5
+ room, start, end or duration, the people needed, setup minutes needed in the room before it
6
+ starts, changeover minutes needed after it), the travel minutes between rooms, and the
7
+ published end of the day. It fails a show where
8
+ - an item ends before it starts
9
+ - two items overlap in one room
10
+ - the gap between two items in a room is shorter than the turnaround, the next item's
11
+ setup or the last item's changeover
12
+ - an item, with its setup, runs outside its room's opening hours
13
+ - a person is needed in two places at once, or has less than the travel time between
14
+ rooms
15
+ and warns where
16
+ - a person works more than --max-hours without a break of --break minutes
17
+ - a room sits empty for more than --max-gap minutes between items
18
+ - the last item ends after the published end
19
+ Then it prints the running order, and with --svg draws one lane per room with each item as
20
+ a block at its times, failures in red.
21
+
22
+ Times are 24-hour "HH:MM". Items on several days carry a "day" (any label, such as
23
+ "2026-06-12" or "Day 2"); each day is checked on its own.
24
+
25
+ Usage:
26
+ python3 show_check.py show.json [--svg timeline.svg] [--json]
27
+ [--max-hours 3] [--break 15] [--max-gap 45]
28
+ Exit codes: 0 no failures, 1 a failure, 2 bad input.
29
+ """
30
+ import argparse
31
+ import json
32
+ import math
33
+ import re
34
+ import sys
35
+
36
+ TIME = re.compile(r"^([01]?\d|2[0-4]):([0-5]\d)$")
37
+
38
+
39
+ class BadInput(ValueError):
40
+ pass
41
+
42
+
43
+ def minutes(v, what):
44
+ """'09:30' -> 570. Raises BadInput."""
45
+ m = TIME.match(str(v).strip()) if v is not None else None
46
+ if not m or (int(m.group(1)) == 24 and m.group(2) != "00"):
47
+ raise BadInput(f"{what} must be a 24-hour time like 09:30, got {v!r}")
48
+ return int(m.group(1)) * 60 + int(m.group(2))
49
+
50
+
51
+ def hhmm(n):
52
+ return f"{int(n) // 60:02d}:{int(n) % 60:02d}"
53
+
54
+
55
+ def _mins(v, what):
56
+ if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) or v < 0:
57
+ raise BadInput(f"{what} must be a number of minutes, got {v!r}")
58
+ return float(v)
59
+
60
+
61
+ def load(data):
62
+ if not isinstance(data, dict):
63
+ raise BadInput("the file must hold a JSON object")
64
+ default_turn = _mins(data.get("turnaround", 0), "turnaround")
65
+ rooms = {}
66
+ for i, r in enumerate(data.get("rooms") or []):
67
+ if not isinstance(r, dict) or not r.get("name"):
68
+ raise BadInput(f"room {i + 1} needs a name")
69
+ n = str(r["name"])
70
+ rooms[n] = {"name": n, "open": minutes(r["open"], f"room {n} open") if r.get("open") else None,
71
+ "close": minutes(r["close"], f"room {n} close") if r.get("close") else None,
72
+ "turnaround": _mins(r.get("turnaround", default_turn), f"room {n} turnaround")}
73
+ items = []
74
+ for i, it in enumerate(data.get("items") or []):
75
+ if not isinstance(it, dict) or not it.get("title"):
76
+ raise BadInput(f"item {i + 1} needs a title")
77
+ iid = str(it.get("id") or f"item {i + 1}")
78
+ room = str(it.get("room") or "")
79
+ if not room:
80
+ raise BadInput(f"{iid} ({it['title']}) needs a room")
81
+ if room not in rooms:
82
+ rooms[room] = {"name": room, "open": None, "close": None, "turnaround": default_turn}
83
+ start = minutes(it.get("start"), f"{iid} start")
84
+ if it.get("end") is not None:
85
+ end = minutes(it["end"], f"{iid} end")
86
+ elif it.get("duration") is not None:
87
+ end = start + _mins(it["duration"], f"{iid} duration")
88
+ else:
89
+ raise BadInput(f"{iid} ({it['title']}) needs an end or a duration")
90
+ people = it.get("people") or []
91
+ if isinstance(people, str):
92
+ people = [people]
93
+ items.append({"id": iid, "title": str(it["title"]), "room": room, "day": str(it.get("day") or ""),
94
+ "start": start, "end": end, "people": [str(p) for p in people],
95
+ "setup": _mins(it.get("setup", 0), f"{iid} setup"),
96
+ "changeover": _mins(it.get("changeover", 0), f"{iid} changeover")})
97
+ if not items:
98
+ raise BadInput("no items")
99
+ travel = {}
100
+ for i, t in enumerate(data.get("travel") or []):
101
+ if not isinstance(t, dict) or not isinstance(t.get("between"), list) or len(t["between"]) != 2:
102
+ raise BadInput(f"travel {i + 1} must be {{\"between\": [room, room], \"minutes\": n}}")
103
+ a, b = (str(x) for x in t["between"])
104
+ travel[frozenset((a, b))] = _mins(t.get("minutes"), f"travel {i + 1} minutes")
105
+ end = minutes(data["published_end"], "published_end") if data.get("published_end") else None
106
+ return {"rooms": rooms, "items": items, "travel": travel, "published_end": end}
107
+
108
+
109
+ def check(show, max_hours=3.0, brk=15.0, max_gap=45.0):
110
+ fails, warns, bad = [], [], set()
111
+ items = show["items"]
112
+ label = lambda it: f"{it['title']} ({hhmm(it['start'])}{', ' + it['day'] if it['day'] else ''})"
113
+ for it in items:
114
+ if it["end"] <= it["start"]:
115
+ fails.append(f"{label(it)} ends at {hhmm(it['end'])}, not after it starts")
116
+ bad.add(it["id"])
117
+ ok = [it for it in items if it["end"] > it["start"]]
118
+ groups = {}
119
+ for it in ok:
120
+ groups.setdefault((it["day"], it["room"]), []).append(it)
121
+ for (day, room), lane in groups.items():
122
+ r = show["rooms"][room]
123
+ lane.sort(key=lambda it: it["start"])
124
+ for it in lane:
125
+ if r["open"] is not None and it["start"] - it["setup"] < r["open"]:
126
+ need = f" with {it['setup']:.0f} min setup" if it["setup"] else ""
127
+ fails.append(f"{label(it)} starts{need} before {room} opens at {hhmm(r['open'])}")
128
+ bad.add(it["id"])
129
+ if r["close"] is not None and it["end"] > r["close"]:
130
+ fails.append(f"{label(it)} ends at {hhmm(it['end'])}, after {room} closes at {hhmm(r['close'])}")
131
+ bad.add(it["id"])
132
+ for a, b in zip(lane, lane[1:]):
133
+ gap = b["start"] - a["end"]
134
+ if gap < 0:
135
+ fails.append(f"{label(a)} and {label(b)} overlap in {room} by {-gap:.0f} min")
136
+ bad.update((a["id"], b["id"]))
137
+ continue
138
+ need = max(r["turnaround"], b["setup"], a["changeover"])
139
+ if gap < need:
140
+ why = ("the room's turnaround" if need == r["turnaround"] else
141
+ f"{b['title']}'s setup" if need == b["setup"] else f"{a['title']}'s changeover")
142
+ fails.append(f"{room}: {gap:.0f} min between {label(a)} and {label(b)}, "
143
+ f"{need:.0f} min needed for {why}")
144
+ bad.update((a["id"], b["id"]))
145
+ elif gap > max_gap:
146
+ warns.append(f"{room} is empty for {gap:.0f} min between {a['title']} and {b['title']}")
147
+ people = {}
148
+ for it in ok:
149
+ for p in it["people"]:
150
+ people.setdefault((it["day"], p), []).append(it)
151
+ for (day, p), mine in people.items():
152
+ mine.sort(key=lambda it: it["start"])
153
+ for a, b in zip(mine, mine[1:]):
154
+ gap = b["start"] - a["end"]
155
+ if gap < 0:
156
+ fails.append(f"{p} is needed at {label(a)} in {a['room']} and {label(b)} in {b['room']} at once")
157
+ bad.update((a["id"], b["id"]))
158
+ elif a["room"] != b["room"]:
159
+ t = show["travel"].get(frozenset((a["room"], b["room"])))
160
+ if t is not None and gap < t:
161
+ fails.append(f"{p} has {gap:.0f} min to get from {a['room']} to {b['room']} "
162
+ f"between {a['title']} and {b['title']}; the walk takes {t:.0f}")
163
+ bad.update((a["id"], b["id"]))
164
+ run_start, last_end = None, None
165
+ for it in mine:
166
+ if last_end is None or it["start"] - last_end >= brk:
167
+ run_start = it["start"]
168
+ last_end = max(last_end or it["end"], it["end"])
169
+ if last_end - run_start > max_hours * 60:
170
+ warns.append(f"{p} works from {hhmm(run_start)} to {hhmm(last_end)}"
171
+ f"{' on ' + day if day else ''} without a {brk:.0f}-minute break")
172
+ run_start = math.inf # one warning per stretch
173
+ # an inf run_start makes the difference negative until the next break resets it
174
+ if show["published_end"] is not None:
175
+ for day in sorted({it["day"] for it in ok}):
176
+ last = max((it for it in ok if it["day"] == day), key=lambda it: it["end"])
177
+ if last["end"] > show["published_end"]:
178
+ warns.append(f"{last['title']} ends at {hhmm(last['end'])}{', ' + day if day else ''}, "
179
+ f"{last['end'] - show['published_end']:.0f} min after the published end "
180
+ f"of {hhmm(show['published_end'])}")
181
+ return {"fails": fails, "warns": warns, "bad": sorted(bad)}
182
+
183
+
184
+ def order(show):
185
+ return sorted(show["items"], key=lambda it: (it["day"], it["start"], it["room"]))
186
+
187
+
188
+ def report(show, r):
189
+ out, day = [], None
190
+ for it in order(show):
191
+ if it["day"] != day and it["day"]:
192
+ out.append(it["day"])
193
+ day = it["day"]
194
+ who = f" — {', '.join(it['people'])}" if it["people"] else ""
195
+ out.append(f" {hhmm(it['start'])}–{hhmm(it['end'])} {it['room']}: {it['title']}{who}")
196
+ out += [f"FAIL {f}" for f in r["fails"]] + [f"WARN {w}" for w in r["warns"]]
197
+ out.append("PASS" if not r["fails"] else f"{len(r['fails'])} failure(s)")
198
+ return "\n".join(out) + "\n"
199
+
200
+
201
+ def esc(t):
202
+ return str(t).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
203
+
204
+
205
+ def svg(show, r, px=3.0, lane=64):
206
+ """One panel per day: a lane per room, a block per item, a tick every 30 minutes."""
207
+ days = sorted({it["day"] for it in show["items"]})
208
+ rooms = list(show["rooms"])
209
+ out_body, y = [], 50
210
+ width = 0
211
+ for day in days:
212
+ its = [it for it in show["items"] if it["day"] == day]
213
+ t0 = min(min(it["start"] for it in its), *(show["rooms"][it["room"]]["open"] or it["start"] for it in its))
214
+ t1 = max(max(it["end"] for it in its), *(show["rooms"][it["room"]]["close"] or it["end"] for it in its))
215
+ t0, t1 = 30 * math.floor(t0 / 30), 30 * math.ceil(t1 / 30)
216
+ scale = min(px, 1100.0 / max(t1 - t0, 30)) # a long day still fits a page
217
+ X = lambda t, t0=t0, scale=scale: 150 + (t - t0) * scale
218
+ width = max(width, X(t1) + 30)
219
+ if day:
220
+ out_body.append(f'<text x="20" y="{y:.0f}" font-size="14" font-weight="bold">{esc(day)}</text>')
221
+ y += 14
222
+ top = y + 10
223
+ for t in range(int(t0), int(t1) + 1, 30):
224
+ major = t % 60 == 0
225
+ out_body.append(f'<line x1="{X(t):.1f}" y1="{top:.0f}" x2="{X(t):.1f}" y2="{top + lane * len(rooms):.0f}" '
226
+ f'stroke="{"#cfd8dc" if major else "#eceff1"}"/>')
227
+ if major:
228
+ out_body.append(f'<text x="{X(t):.1f}" y="{top - 3:.0f}" font-size="10" text-anchor="middle" fill="#607d8b">{hhmm(t)}</text>')
229
+ for k, room in enumerate(rooms):
230
+ ly = top + k * lane
231
+ rm = show["rooms"][room]
232
+ out_body.append(f'<text x="20" y="{ly + lane / 2 + 4:.0f}" font-size="12" fill="#263238">{esc(room)}</text>'
233
+ f'<line x1="150" y1="{ly + lane:.0f}" x2="{X(t1):.1f}" y2="{ly + lane:.0f}" stroke="#b0bec5"/>')
234
+ if rm["open"] is not None and rm["close"] is not None:
235
+ out_body.append(f'<rect x="{X(rm["open"]):.1f}" y="{ly + 2:.0f}" width="{(rm["close"] - rm["open"]) * scale:.1f}" '
236
+ f'height="{lane - 4}" fill="#f1f8e9" opacity="0.6"/>')
237
+ for it in its:
238
+ if it["room"] != room:
239
+ continue
240
+ bad = it["id"] in r["bad"]
241
+ x0, w = X(it["start"]), max((it["end"] - it["start"]) * scale, 2)
242
+ if it["setup"]:
243
+ out_body.append(f'<rect x="{x0 - it["setup"] * scale:.1f}" y="{ly + 8:.0f}" width="{it["setup"] * scale:.1f}" '
244
+ f'height="{lane - 16}" fill="#fff8e1" stroke="#ffb300" stroke-dasharray="3,2"/>')
245
+ out_body.append(f'<rect x="{x0:.1f}" y="{ly + 6:.0f}" width="{w:.1f}" height="{lane - 12}" rx="4" '
246
+ f'fill="{"#ffebee" if bad else "#e3f2fd"}" stroke="{"#c62828" if bad else "#1565c0"}" '
247
+ f'stroke-width="{2.5 if bad else 1}"/>')
248
+ title = it["title"] if len(it["title"]) * 6 < w - 6 else it["title"][: max(int((w - 10) / 6), 1)] + "…"
249
+ out_body.append(f'<text x="{x0 + 4:.1f}" y="{ly + 24:.0f}" font-size="10" fill="#0d47a1">{esc(title)}</text>'
250
+ f'<text x="{x0 + 4:.1f}" y="{ly + 38:.0f}" font-size="9" fill="#546e7a">'
251
+ f'{hhmm(it["start"])}–{hhmm(it["end"])}{" ✗" if bad else ""}</text>')
252
+ y = top + lane * len(rooms) + 30
253
+ status = "PASS: no clashes" if not r["fails"] else f"{len(r['fails'])} clash(es): items marked ✗ in red"
254
+ out_body.append(f'<text x="20" y="{y:.0f}" font-size="12" fill="{"#2e7d32" if not r["fails"] else "#c62828"}">{esc(status)}</text>'
255
+ f'<text x="20" y="{y + 16:.0f}" font-size="10" fill="#666">Dashed amber: setup before an item · '
256
+ f'green band: room open</text>')
257
+ H, W = y + 34, max(width, 520)
258
+ head = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W:.0f} {H:.0f}" width="{W:.0f}" height="{H:.0f}" '
259
+ 'font-family="Helvetica, Arial, sans-serif">', f'<rect width="{W:.0f}" height="{H:.0f}" fill="#ffffff"/>',
260
+ '<text x="20" y="28" font-size="16" font-weight="bold">Run of show</text>']
261
+ return "\n".join(head + out_body + ["</svg>"]) + "\n"
262
+
263
+
264
+ def main(argv=None):
265
+ ap = argparse.ArgumentParser(description="Check a run of show for clashes and draw it as a timeline.")
266
+ ap.add_argument("show")
267
+ ap.add_argument("--svg", help="write the timeline here")
268
+ ap.add_argument("--json", action="store_true", help="print the result as JSON")
269
+ ap.add_argument("--max-hours", type=float, default=3.0, help="hours a person may work without a break (default 3)")
270
+ ap.add_argument("--break", dest="brk", type=float, default=15.0, help="minutes that count as a break (default 15)")
271
+ ap.add_argument("--max-gap", type=float, default=45.0, help="minutes a room may sit empty between items (default 45)")
272
+ args = ap.parse_args(argv)
273
+ try:
274
+ with open(args.show, encoding="utf-8") as fh:
275
+ show = load(json.load(fh))
276
+ except (OSError, ValueError) as e:
277
+ print(f"FAIL cannot read {args.show}: {e}")
278
+ return 2
279
+ r = check(show, args.max_hours, args.brk, args.max_gap)
280
+ if args.json:
281
+ print(json.dumps({"pass": not r["fails"], "fails": r["fails"], "warns": r["warns"],
282
+ "items": [dict(it, start=hhmm(it["start"]), end=hhmm(it["end"])) for it in order(show)]},
283
+ indent=1))
284
+ else:
285
+ print(report(show, r), end="")
286
+ if args.svg:
287
+ with open(args.svg, "w", encoding="utf-8") as fh:
288
+ fh.write(svg(show, r))
289
+ if not args.json:
290
+ print(f"\nWrote {args.svg}")
291
+ return 1 if r["fails"] else 0
292
+
293
+
294
+ if __name__ == "__main__":
295
+ sys.exit(main())
@@ -0,0 +1,15 @@
1
+ {
2
+ "published_end": "17:30",
3
+ "turnaround": 10,
4
+ "rooms": [
5
+ {"name": "Main hall", "open": "08:00", "close": "18:00", "turnaround": 15},
6
+ {"name": "Studio B", "open": "09:00", "close": "17:30"}
7
+ ],
8
+ "travel": [{"between": ["Main hall", "Studio B"], "minutes": 5}],
9
+ "items": [
10
+ {"id": "doors", "title": "Doors and registration", "room": "Main hall", "start": "08:30", "end": "09:00", "people": ["Front of house"]},
11
+ {"id": "key", "title": "Opening keynote", "room": "Main hall", "start": "09:15", "duration": 45, "people": ["Ana Ruiz", "AV crew"], "setup": 15},
12
+ {"id": "ws1", "title": "Workshop: pricing", "room": "Studio B", "start": "10:10", "end": "11:00", "people": ["Ana Ruiz"]},
13
+ {"id": "panel", "title": "Customer panel", "room": "Main hall", "start": "10:15", "end": "11:15", "people": ["Joe Park", "AV crew"], "changeover": 20}
14
+ ]
15
+ }
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: seating-plan
3
+ description: Seat guests at tables for a wedding, gala, conference dinner or awards night, and check the chart with a script — every guest seated once, no table over capacity, people who must sit together together, people who must be kept apart apart, wheelchair users at accessible places — drawn as a room plan with names at each table. Use when an organiser asks to "make the seating plan", "who sits where", "seat these 120 guests", "check my table plan", or has a guest list with groups and keep-apart requests.
4
+ ---
5
+
6
+ # seating-plan
7
+
8
+ A table plan for a seated event, where every rule the organiser gave holds at once: each guest
9
+ has exactly one seat, no table is over its count, households and groups sit together, the
10
+ people who must be kept apart are, and wheelchair users are at tables with step-free access.
11
+ With 120 guests and a dozen requests, a plan that looks right usually breaks one of them. The
12
+ agent decides who sits where and why; a script checks every rule on every table and draws the
13
+ room, so a broken request is found before the organiser prints place cards.
14
+
15
+ ## How to respond
16
+
17
+ **These rules come before the steps and outrank them:**
18
+
19
+ - **Their file comes back in exactly its own shape.** Fixing their plan, return their file
20
+ with the same columns, column order, headers and row order, and only the cells a rule
21
+ forces changed, ready for the venue to print from. Building from a guest list, hand back
22
+ their columns plus only the table (and seat) columns the plan needs. Every change goes in
23
+ the reply, not the file: the guest, what moved and why. Add a change column, or any other
24
+ column, only when they ask for one. Given a pasted list, reply with a table. Write every
25
+ deliverable the request names, such as place-card order or the caterer's list.
26
+ - **Working files stay out of sight.** The checker's input and any intermediate file go in a
27
+ temporary folder outside the working directory, or are deleted before you reply. Draw the
28
+ room only when a drawing is asked for or the venue needs a floor plan, and then once. Never
29
+ leave anything running in the background.
30
+ - **Carry every field through, without reshaping their file.** Every detail the person gave
31
+ per guest (meal choice, dietary need, party, notes) reaches the reply or the deliverable it
32
+ belongs in, such as the caterer's list, and each conflict between them is flagged: a vegan
33
+ guest who chose the beef, a child in a seat marked for a speaker.
34
+ - **Soft notes are rules.** "Keep the grandparents near the door", "not by the speakers":
35
+ build them in, and say which you couldn't meet.
36
+ - **Fixing their plan means minimal changes.** Move only the guests a rule forces, list each
37
+ move with the rule, and keep every other seat as they placed it.
38
+ - **A passing check is the floor, not the reply.** The reply is about their guests, in their
39
+ words: never file formats, tools or paths.
40
+
41
+ 1. **Collect the guest list and the rules.** Take them from the files the organiser names (a
42
+ guest list CSV or spreadsheet export, an RSVP export, a pasted list) or the message:
43
+ - **Guests:** name, household or party, and needs: wheelchair, near an exit, dietary
44
+ (dietary and meal choice are carried through to the deliverable, not seating rules)
45
+ - **Tables:** how many, seats each, round or long, which are step-free, and where they
46
+ stand, if a room layout exists
47
+ - **Requests:** who must sit together, who must be kept apart, fixed seats (the head table,
48
+ a sponsor's table)
49
+
50
+ Ask at most two questions, only about a rule that changes the plan. With no guests, emit
51
+ `BLOCKED: need the guest list`.
52
+
53
+ 2. **Write the plan file** in the shape of [`templates/seating.json`](templates/seating.json):
54
+ `tables` (id, capacity, shape, `at` in metres, `accessible`), `guests` (id, name, party,
55
+ needs, dietary), `together`, `apart`, `fixed`, and `assignments` as table → guest ids.
56
+ Seat in this order, because each step narrows the next:
57
+ 1. fixed seats;
58
+ 2. wheelchair users and anyone who must be near an exit, at the tables that allow it;
59
+ 3. together groups and households, whole;
60
+ 4. the apart pairs, kept on different tables;
61
+ 5. everyone else, filling tables evenly rather than leaving one half empty.
62
+
63
+ 3. **Run the checker and fix every FAIL before showing anything.** Keep `seating.json` in a
64
+ temporary folder; a drawing, when wanted, goes in the working directory as
65
+ `seating-plan.svg`, never inside this folder.
66
+
67
+ ```bash
68
+ # Claude Code
69
+ python3 "${CLAUDE_SKILL_DIR}/scripts/seating_check.py" "$TMPDIR/seating.json"
70
+ # Other IDEs (from the skill folder); add --svg only when a drawing is wanted
71
+ python3 scripts/seating_check.py /tmp/seating/seating.json --svg seating-plan.svg
72
+ ```
73
+
74
+ Exit 0 means every rule holds; 1 means one is broken; 2 means the file is malformed. Move
75
+ guests and rerun until it exits 0. A WARN (a household split because it is bigger than a
76
+ table, a half-empty table, someone who came alone at a table of one family) stands only
77
+ with a sentence saying why.
78
+
79
+ 4. **When the rules can't all hold, say which and stop guessing.** Name the conflict and the
80
+ smallest change that resolves it: ✅ *"The Moss and Park families (14 people) must sit
81
+ together, and Ed and Bo must be kept apart, but Ed is in the Park group and Bo in the Moss:
82
+ with 10-seat tables they can't share one, so one family splits. Splitting the Parks 6 + 4
83
+ keeps every other request."* Never break a request quietly to make the plan pass.
84
+
85
+ 5. **Reply for the organiser, in this order:**
86
+ 1. **The plan:** in their file's format when they gave one, otherwise a table of each
87
+ table and who sits there, step-free tables marked.
88
+ 2. **What couldn't be met,** with the reason and the choice it needs from them.
89
+ 3. **The assumptions,** each tagged `[assumption]`: a seat count or a layout they didn't give.
90
+ 4. **The caterer's list:** meal choice and dietary need per guest and per table, with each
91
+ conflict flagged.
92
+ 5. **The files:** their plan in their format, and the drawing if one was made.
93
+
94
+ Write for someone planning an event: no file formats, no tool or script names, no paths
95
+ inside this folder. Say *"every request is met"*, not what checked it.
96
+
97
+ **Checking a plan the organiser already made:** run the same checker on their plan, report
98
+ each problem with the fix that disturbs the fewest guests, and hand back their file with
99
+ those fixes made and noted. Don't reseat the room unless they ask.
100
+
101
+ **Non-interactive runs** (subagent, CI, headless): with no table size, use 10-seat rounds; with
102
+ no layout, draw tables in a grid; with no accessible tables named, mark the ones nearest an
103
+ exit, each tagged `[assumption]` at the top of the reply. With no guest list, emit
104
+ `BLOCKED: need the guest list`.
105
+
106
+ ## Useful references in this skill
107
+
108
+ - [`templates/seating.json`](templates/seating.json) — the file shape: tables, guests, requests and assignments
109
+ - [`scripts/seating_check.py`](scripts/seating_check.py) — checks every guest seated once, capacity, together, apart, access and fixed seats; draws the room
110
+ - [`examples/wedding-dinner.md`](examples/wedding-dinner.md) — worked example: a 24-guest wedding where the brief itself conflicts, and the reply names it
111
+
112
+ The order-of-the-day timings, the speeches and the changeovers belong to `run-of-show`.
113
+
114
+ ## Quality bar
115
+
116
+ - **Every guest has exactly one seat, and no table is over its count.** The checker exits 0 before the plan is shown.
117
+ - **Every together group and every apart pair holds,** or the reply names the one that can't and why.
118
+ - **Every wheelchair user is at a step-free table,** and every fixed seat is where it was fixed.
119
+ - **Tables fill evenly:** no half-empty table without a reason.
120
+ - **The reply leads with who sits where,** in a table the organiser could print.
121
+ - **Nothing is invented:** no guest, request or table that wasn't given.
122
+ - **Their file comes back in its exact columns and row order,** with every change listed in the reply, not in the file.
123
+ - **Every detail they gave per guest reaches the reply or the deliverable it belongs in,** and each conflict is flagged.
124
+ - **Their notes are honoured,** and their own plan changes only where a rule forces it.
125
+ - **No working file is left in their folder.**
126
+
127
+ ## When to use this skill
128
+
129
+ - ✅ A seating chart for a wedding, gala, awards dinner or conference dinner
130
+ - ✅ Checking an existing table plan before the place cards are printed
131
+ - ✅ Reseating after late RSVPs or a new keep-apart request
132
+ - ✅ A plan for 20 guests or 400
133
+
134
+ ## When NOT to use this skill
135
+
136
+ - ❌ The order of the day, speeches and changeovers: that's `run-of-show`
137
+ - ❌ Theatre or classroom seating with numbered rows and no groups: it's a seat count, not a plan
138
+ - ❌ Furniture placement and clearances in a room: that's a layout drawing
139
+ - ❌ Choosing the venue or the menu
140
+
141
+ ## Anti-patterns to avoid
142
+
143
+ - ❌ **Showing a plan before every rule is checked.** A keep-apart pair at table 7 is the one guests notice.
144
+ - ❌ **Breaking a request quietly to make the numbers fit.** Name the conflict and let the organiser choose.
145
+ - ❌ **Seating in guest-list order.** Fixed seats, access needs and groups go first, or they end up split.
146
+ - ❌ **One half-empty table at the back** while others are full. Spread the space.
147
+ - ❌ **Mentioning files, formats or tools in the reply.** The organiser wants names at tables.
148
+ - ❌ **An extra "change" or "dietary" column in the venue's file.** They print from its exact columns; the changes and the meals go in the reply or the caterer's list.
149
+ - ❌ **Treating dietary needs as a seating rule.** They go to the caterer, per guest and table.
150
+ - ❌ **Dropping a column the person gave.** The meal choices are what the venue needs next.
151
+ - ❌ **A JSON file and a drawing nobody asked for** left beside their guest list.
152
+ - ❌ **Telling them the fixes instead of making them.** Hand back the corrected file.
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "query": "make the seating plan for our wedding, 120 guests, here's the list",
4
+ "should_trigger": true
5
+ },
6
+ {
7
+ "query": "who sits where at the gala? keep the board apart from the press",
8
+ "should_trigger": true
9
+ },
10
+ {
11
+ "query": "check my table plan before I print the place cards",
12
+ "should_trigger": true
13
+ },
14
+ {
15
+ "query": "seat these conference dinner guests by company, tables of 10",
16
+ "should_trigger": true
17
+ },
18
+ {
19
+ "query": "write the minute-by-minute for the wedding day",
20
+ "should_trigger": false
21
+ },
22
+ {
23
+ "query": "draw a floor plan for the venue's new function room, 12 x 18 m",
24
+ "should_trigger": false
25
+ },
26
+ {
27
+ "query": "map the guest's journey from booking a ticket to arriving at the gala",
28
+ "should_trigger": false
29
+ }
30
+ ]