sad-mcp 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sad-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "MCP server for Software Analysis and Design course materials at BGU",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,6 +28,9 @@ The shared files prepended to this prompt define the layout recipes, model shape
28
28
  5. **Every use case must have at least one association line to an actor.**
29
29
  6. **Actors must not overlap** — minimum 200px vertical distance between actor centers.
30
30
  7. **Default: zero «include» and zero «extend».** Most diagrams need none. More than two is almost always wrong. §4 defines a gate every such relationship must pass before you draw it.
31
+ 8. **UC labels describe actions WITH the information system, not physical real-world actions.** See §3 for the full rule and verb list.
32
+ 9. **Every system-time / cron actor association MUST carry a timing label** (e.g., "פעם ביום", "כל 5 דקות"). See §2 and §8 for rendering.
33
+ 10. **No "send-only" use cases.** If a UC's entire behavior is sending a message / notification / alert / email / SMS, it is not a UC — it is a step inside the UC that *decided* to send it. See §3.
31
34
 
32
35
  ---
33
36
 
@@ -56,18 +59,32 @@ The shared files prepended to this prompt define the layout recipes, model shape
56
59
 
57
60
  **System administrator** — include unless explicitly excluded. Responsible for managing the master data of every entity the system references. Each entity gets its own specific UC (not one catch-all).
58
61
 
62
+ **System-time / cron actor (שחקן זמן).** Draw as a system-style actor (amber head, `«system»` label). Every association line from a time actor MUST carry a visible timing label at the midpoint (e.g., `פעם ביום`, `כל 5 דקות`, `בסוף חודש`). Without a timing label the trigger frequency is ambiguous and the diagram is incomplete. Rendering: the label is injected as a `<g class="timing-label">` (white `<rect>` + `<text>`) after `fixLayout` sets the endpoint coordinates — see §8.
63
+
59
64
  ---
60
65
 
61
66
  ## 3. Use Case Rules
62
67
 
63
68
  **Granularity**: one complete user goal per UC, achievable in one session.
64
69
 
70
+ **Labels describe actions WITH the information system, not physical real-world actions.** A use case is the actor's interaction with the IS; the verb must imply data entry, query, or a system-state change.
71
+
72
+ - ✅ Preferred verbs: `רישום`, `הזנה`, `עדכון`, `הצגה`, `הפקה` (of a report), `שליחה` (of a notification), `אישור`, `הקצאה`, `חיפוש`, `סגירה`, `פתיחה` (of a record).
73
+ - ❌ Bare physical verbs that describe things happening outside the system: `הכנת משלוח` (physical preparing), `ספירת מלאי` (physical counting), `מסירת חבילה` (physical delivering), `תשלום במזומן` (physical paying).
74
+ - **Fix rule:** when the requirement uses a physical verb, wrap it with a system verb. `הכנת משלוח` → `רישום הכנת משלוח` or `סימון משלוח כמוכן`. `ביצוע ספירת מלאי` → `הזנת תוצאות ספירת מלאי`. `קבלת משלוח` → `קליטת משלוח נכנס` (data entry on arrival).
75
+
65
76
  **When to combine vs. separate "ניהול X":**
66
77
  - Simple entity (just a form): single "ניהול X" is fine.
67
78
  - Complex entity with sub-items, lifecycle states, or different actors per operation: separate UCs (open / update / cancel / approve).
68
79
 
69
80
  **Implied UCs**: every entity the system references needs at least one UC that manages it.
70
81
 
82
+ **No "send-only" use cases.** A UC must carry non-trivial behavior (decision, data entry, state change, non-trivial computation). If the only behavior is firing a message, it is not a UC — it is a system step inside the UC that *decided* to send.
83
+
84
+ - ❌ Wrong UCs: `שליחת התראה`, `שליחת מייל אישור`, `שליחת SMS`, `הודעה למנהל`, `שליחת דוח`.
85
+ - ✅ Fold into the decider: the alert send-step belongs inside `עדכון רמות מלאי` (which *detected* the low stock); the confirmation email belongs inside `אישור הזמנה` (which *decided* to confirm).
86
+ - This rule applies even when the send is triggered by a condition — conditional sending is part of the conditional UC, not its own UC.
87
+
71
88
  ---
72
89
 
73
90
  ## 4. Include and Extend — Mandatory Justification Gate
@@ -225,17 +242,30 @@ function fixLayout(layout) {
225
242
  sortCol(cols.left, 'left');
226
243
  sortCol(cols.right, 'right');
227
244
 
228
- // Step 3 — Initial Y assignment per column.
229
- function place(ucs, colX) {
245
+ // Step 3 — Adaptive column X positions. Account for actual ellipse widths so
246
+ // adjacent columns never overlap horizontally. Widen the boundary if needed.
247
+ const COL_GAP = 30;
248
+ const maxRx = ucs => (ucs.length ? Math.max(...ucs.map(u => u.rx)) : 0);
249
+ const lRx = maxRx(cols.left), cRx = maxRx(cols.center), rRx = maxRx(cols.right);
250
+ const gaps =
251
+ (cRx ? 2 * COL_GAP : (lRx && rRx ? 2 * COL_GAP : 0));
252
+ const needed = 2 * UC_PAD + 2 * lRx + 2 * cRx + 2 * rRx + gaps;
253
+ if (b.width < needed) b.width = needed;
254
+
255
+ const leftX = b.x + UC_PAD + lRx;
256
+ const rightX = b.x + b.width - UC_PAD - rRx;
257
+ const centerX = (leftX + rightX) / 2;
258
+
259
+ function place(ucs, colX, colName) {
230
260
  ucs.forEach((uc, i) => {
231
261
  uc.cy = b.y + HEADER_H + UC_PAD + uc.ry + i * (uc.ry * 2 + UC_GAP);
232
262
  uc.cx = colX;
233
- uc.col = ucSide[uc.id] || 'center';
263
+ uc.col = colName;
234
264
  });
235
265
  }
236
- place(cols.left, b.x + b.width * 0.28);
237
- place(cols.right, b.x + b.width * 0.72);
238
- place(cols.center, b.x + b.width * 0.50);
266
+ place(cols.left, leftX, 'left');
267
+ place(cols.right, rightX, 'right');
268
+ place(cols.center, centerX, 'center');
239
269
 
240
270
  // Step 4 — Clamp and resolve overlaps. Idempotent fixed-point loop.
241
271
  const minX = b.x + UC_PAD;
@@ -314,15 +344,26 @@ function fixLayout(layout) {
314
344
  line.setAttribute('x2', x2); line.setAttribute('y2', y2);
315
345
  });
316
346
 
317
- // Step 9 — Resize boundary rect and SVG viewBox.
347
+ // Step 9 — Resize boundary rect and SVG viewBox (BOTH width and height).
318
348
  const br = svg.querySelector('.system-boundary, rect[rx="12"]');
319
- if (br) br.setAttribute('height', b.height);
349
+ if (br) { br.setAttribute('width', b.width); br.setAttribute('height', b.height); }
320
350
  const vb = svg.getAttribute('viewBox');
321
351
  if (vb) {
322
352
  const p = vb.split(/\s+/);
353
+ // Height: fit boundary + any bottom actors.
323
354
  p[3] = Math.max(parseFloat(p[3]), b.y + b.height + 120);
355
+ // Width: fit the rightmost actor + label margin. Without this, widening the
356
+ // boundary pushes right-column actors off the visible SVG.
357
+ const ACTOR_LABEL_MARGIN = 120; // covers stick figure width + longest label
358
+ const maxActorX = layout.actors.length
359
+ ? Math.max(...layout.actors.map(a => a.x))
360
+ : b.x + b.width;
361
+ p[2] = Math.max(parseFloat(p[2]), maxActorX + ACTOR_LABEL_MARGIN);
324
362
  svg.setAttribute('viewBox', p.join(' '));
325
363
  }
364
+
365
+ // Step 10 — Decorate timed associations (see §8 renderTimingLabels).
366
+ if (typeof renderTimingLabels === 'function') renderTimingLabels(layout);
326
367
  }
327
368
  ```
328
369
 
@@ -420,7 +461,52 @@ document.addEventListener('click', e => {
420
461
  UC group: `<g id="uc-uc1" data-uc-id="uc1" onclick="showDesc('uc1','uc',event)" style="cursor:pointer">`
421
462
  Actor group: `<g id="actor-student" data-actor-id="student" onclick="showDesc('student','actor',event)" style="cursor:pointer">`
422
463
 
423
- **Association labels (timing/frequency):** when an actor connection has a timing annotation (e.g., "פעם ביום" for a cron actor), add a `<text>` with a white `<rect>` behind it at the midpoint of the line after `fixLayout` has set the endpoints.
464
+ ### Timing labels on cron/time-actor associations (required)
465
+
466
+ When a connection carries a `timing` field (every connection from a system-time actor MUST have one — see §2), inject the label at the line midpoint **after** `fixLayout` has written the endpoints. The helper below is idempotent: it removes any existing `.timing-label` group before re-rendering, so calling it again after another `fixLayout` pass is safe.
467
+
468
+ ```javascript
469
+ function renderTimingLabels(layout) {
470
+ const svg = document.getElementById(layout.svgId);
471
+ if (!svg) return;
472
+ svg.querySelectorAll('.timing-label').forEach(el => el.remove());
473
+ const NS = 'http://www.w3.org/2000/svg';
474
+ (layout.connections || []).forEach(conn => {
475
+ if (!conn.timing) return;
476
+ const line = svg.querySelector(
477
+ `[data-conn-from="${conn.from}"][data-conn-to="${conn.to}"]`);
478
+ if (!line) return;
479
+ const x1 = +line.getAttribute('x1'), y1 = +line.getAttribute('y1');
480
+ const x2 = +line.getAttribute('x2'), y2 = +line.getAttribute('y2');
481
+ const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
482
+ const w = 10 + conn.timing.length * 7, h = 16;
483
+ const g = document.createElementNS(NS, 'g');
484
+ g.setAttribute('class', 'timing-label');
485
+ const rect = document.createElementNS(NS, 'rect');
486
+ rect.setAttribute('x', mx - w / 2); rect.setAttribute('y', my - h / 2);
487
+ rect.setAttribute('width', w); rect.setAttribute('height', h);
488
+ rect.setAttribute('fill', 'white'); rect.setAttribute('stroke', '#334155');
489
+ rect.setAttribute('stroke-width', '0.8'); rect.setAttribute('rx', '3');
490
+ const text = document.createElementNS(NS, 'text');
491
+ text.setAttribute('x', mx); text.setAttribute('y', my + 4);
492
+ text.setAttribute('font-size', '10'); text.setAttribute('text-anchor', 'middle');
493
+ text.setAttribute('fill', '#334155');
494
+ text.setAttribute('font-family', "'Noto Sans Hebrew', sans-serif");
495
+ text.setAttribute('direction', 'rtl');
496
+ text.textContent = conn.timing;
497
+ g.appendChild(rect); g.appendChild(text);
498
+ svg.appendChild(g);
499
+ });
500
+ }
501
+ ```
502
+
503
+ Call `renderTimingLabels(layout)` at the end of every `fixLayout` invocation, **and** on `DOMContentLoaded` (after the initial `fixLayout`). Add it inside `showDiagram(index)` too so the label tracks the active tab.
504
+
505
+ The `connections` entry for a timed link looks like:
506
+
507
+ ```javascript
508
+ { from: 'cron', to: 'uc-daily-reconcile', type: 'association', timing: 'פעם ביום' }
509
+ ```
424
510
 
425
511
  ---
426
512
 
@@ -540,5 +626,9 @@ For a single-tab diagram, drop the tab bar, the split-rationale line, and the se
540
626
  - [ ] `fixLayout` runs on `DOMContentLoaded` and on the Fix Layout button; uses named constants (HEADER_H, UC_PAD, UC_GAP, ACTOR_PAD), not magic numbers.
541
627
  - [ ] UC and actor description popups wired to every `data-uc-id` / `data-actor-id` element.
542
628
  - [ ] System actors drawn as stick figures with `«system»` label (not rectangles).
629
+ - [ ] Every UC label describes an action WITH the information system (data entry, query, state change) — no bare physical verbs (`הכנת X`, `ספירת X`, `מסירת X`, `תשלום במזומן`). Physical verbs are wrapped with a system verb (`רישום`, `הזנה`, `עדכון`, `סימון`, etc.).
630
+ - [ ] Every system-time / cron actor association has a `timing` field (and the rendered diagram shows a visible label at the line midpoint) — no unlabeled cron connections.
631
+ - [ ] No "send-only" UCs (no bare `שליחת X` / `הודעה ל-X`); message-sends are folded into the UC that decided to send.
632
+ - [ ] `fixLayout` uses adaptive column X (widens boundary if needed so left/center/right ellipses never overlap horizontally) AND expands viewBox width so right-side actors are not clipped.
543
633
  - [ ] If split: each tab has a split-rationale line citing the actual trigger that fired (count > 8, or 2+ disjoint actor groups); `showDiagram` and per-tab `fixLayout` wired per §11.
544
634
  - [ ] `diagramModel` defined per `model-shape.md`; VP export button present and wired to `exportXMI()`.