schematex 0.8.3 → 0.9.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.
@@ -688,7 +688,7 @@ var EXAMPLES = [
688
688
  "signal-flow"
689
689
  ],
690
690
  "complexity": 2,
691
- "featured": false,
691
+ "featured": true,
692
692
  "dsl": 'blockdiagram "PID control loop"\nC = block("PID C(s)") [role: controller]\nG = block("Plant G(s)") [role: plant]\nerr = sum(+r, -y)\nr = signal("r (setpoint)")\ny = signal("y (output)")\nin -> r\nr -> err\nerr -> C\nC -> G\nG -> y\nG -> err',
693
693
  "notes": '## Scenario\n\nThe standard closed-loop PID block diagram appears in every control systems textbook (Ogata, Franklin, \xC5str\xF6m) and every control system design spec sheet. Schematex renders it from a signal-flow description \u2014 not a generic flowchart \u2014 using proper summing junction symbols and automatic feedback routing.\n\n## Annotation key\n\n- `block("label") [role: ...]` \u2014 transfer function block; `role: controller` and `role: plant` affect visual styling\n- `sum(+r, -y)` \u2014 summing junction: adds the `+r` (reference) signal and subtracts the `-y` (output feedback)\n- `signal("label")` \u2014 named signal node\n- `G -> err` \u2014 the feedback path: plant output `y` routes back to the summing junction\n\n## How to read\n\nThe setpoint `r` enters the summing junction `err`, which subtracts the plant output `y` to compute the error signal. The PID controller `C(s)` processes the error and drives the plant `G(s)`. The plant output `y` is both the system output and the feedback signal. The loop is closed when `G -> err` feeds `y` back to the summing junction.'
694
694
  },
@@ -924,7 +924,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
924
924
  "balancing"
925
925
  ],
926
926
  "complexity": 3,
927
- "featured": false,
927
+ "featured": true,
928
928
  "dsl": 'causalloop "Startup growth engine"\n "Active users" -> "Word of mouth" : +\n "Word of mouth" -> "New signups" : +\n "New signups" -> "Active users" : +\n "Active users" -> "Server load" : +\n "Server load" -> "App performance" : -\n "App performance" -> "Active users" : + delay\n "Active users" -> Revenue : +\n Revenue -> "Infra investment" : +\n "Infra investment" -> "App performance" : + delay\n loop R1 "Viral flywheel"\n loop B1 "Scaling strain"',
929
929
  "notes": '## What this shows\n\nThe two-sided story of every viral product. One reinforcing loop drives growth: more active users produce more word of mouth, which drives signups, which adds users \u2014 an amplifying cycle. A second, balancing loop pushes back: more users raise server load, which degrades app performance, which (after a delay) loses users. The `delay` markers put the system-dynamics hash mark on the slow legs, where the lag between cause and effect is what makes the system overshoot.\n\nThe engine finds and labels the loops for you, which a drawing tool cannot. It enumerates every elementary cycle in the signed graph with Johnson\'s algorithm, then applies Sterman\'s rule \u2014 count the negative links, even means reinforcing (R), odd means balancing (B) \u2014 and numbers them R1, B1\u2026 The growth cycle comes back R (zero negatives), the load cycle B (one negative). The named `loop` declarations attach the human phrasing \u2014 "Viral flywheel", "Scaling strain" \u2014 onto the loops the engine detects.'
930
930
  },
@@ -961,7 +961,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
961
961
  "netlist"
962
962
  ],
963
963
  "complexity": 2,
964
- "featured": false,
964
+ "featured": true,
965
965
  "dsl": 'circuit "Bridge Rectifier Supply" netlist\nV1 ac1 ac2 12Vac\nD1 ac1 vout 1N4007\nD2 ac2 vout 1N4007\nD3 0 ac1 1N4007\nD4 0 ac2 1N4007\nC1 vout 0 470u\nRload vout 0 1k',
966
966
  "notes": "## Scenario\n\nA technician needs a quick schematic for the textbook AC-to-DC rectifier: a transformer secondary, four rectifier diodes, a reservoir capacitor, and a load. The netlist form keeps the topology precise while the renderer chooses the schematic placement.\n\n## Annotation key\n\n- `D1` to `D4` are ordinary diode components inferred from the `D` prefix.\n- `0` is the canonical ground net.\n- `C1` smooths the rectified output across `vout` and ground.\n\n## How to read\n\nDuring each half-cycle, two diodes conduct and steer current into `vout` with the same polarity. The capacitor charges near the peak and supplies the load between peaks, reducing ripple at `Rload`."
967
967
  },
@@ -1251,7 +1251,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1251
1251
  "control-flow"
1252
1252
  ],
1253
1253
  "complexity": 3,
1254
- "featured": false,
1254
+ "featured": true,
1255
1255
  "dsl": 'epc "Purchase requisition"\n layout: tb\n event E1 "Material need identified"\n function F1 "Create requisition"\n function F2 "Determine source of supply"\n xor X1\n event E2 "Stock supplier exists"\n event E3 "New supplier needed"\n function F3 "Issue purchase order"\n function F4 "Run tender process"\n and A1\n event E4 "Order dispatched"\n event E5 "Supplier qualified"\n E1 -> F1 -> F2 -> X1\n X1 -> E2\n X1 -> E3\n E2 -> F3 -> A1\n E3 -> F4 -> A1\n A1 -> E4\n A1 -> E5',
1256
1256
  "notes": '## What this shows\n\nA SAP-style procurement process modelled as an ARIS event-driven process chain (EPC), the notation that strictly alternates passive **events** ("Material need identified") and active **functions** ("Create requisition"). After sourcing, an **XOR** connector splits the flow on a real decision \u2014 an existing stock supplier versus a new supplier needing a tender \u2014 and the two paths re-converge on an **AND** connector that fans back out to the dispatched order and the qualified-supplier outcome.\n\nEPC\'s value here is structural validation, not a computed number. The engine checks the well-formedness rules and flags violations rather than silently drawing a broken model: event/function alternation along every path, single-in/single-out multiplicity carried by the connectors, reachability from start to end, and the **event-cannot-decide signature rule** \u2014 a passive event may not be the source of an XOR/OR split. That\'s why the decision routes *through* function `F2` before reaching the XOR, which is the correct ARIS form.'
1257
1257
  },
@@ -1363,7 +1363,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1363
1363
  "core-damage"
1364
1364
  ],
1365
1365
  "complexity": 3,
1366
- "featured": false,
1366
+ "featured": true,
1367
1367
  "dsl": 'eventtree "Large LOCA"\n initiating LOCA "Large-break LOCA" freq: 1e-4\n function RT "Reactor trips" p: 0.001\n function ECCS "ECCS injection" p: 0.01\n function CHR "Containment heat removal" p: 0.02\n function CI "Containment integrity" p: 0.005\n outcome s s s s -> "OK"\n outcome s s s f -> "Late release"\n outcome s s f * -> "Late release"\n outcome s f * * -> "Early release"\n outcome f * * * -> "Core damage"',
1368
1368
  "notes": '## What this shows\n\nThe textbook nuclear probabilistic-risk-assessment (PRA) tree. One initiating event \u2014 a large pipe break draining the coolant at a frequency of `1e-4` per year \u2014 is asked, left to right, whether each safety function holds: reactor trip, emergency core cooling (ECCS), containment heat removal, then containment integrity. Each `*` prunes a path that has already terminated, so the tree stays compact instead of ballooning to a full 2\u2074 ladder.\n\nThe engine does the arithmetic an event tree exists to give. It multiplies the initiating frequency by the success leg (`1 \u2212 p`) or failure leg (`p`) at each branch to get every sequence frequency, sums the two `"Late release"` leaves into one rolled-up outcome, and paints the largest-frequency path \u2014 the dominant accident sequence \u2014 in red. That computed answer, not the forking picture, is the deliverable a drawing tool can\'t produce.'
1369
1369
  },
@@ -1607,7 +1607,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1607
1607
  "automotive"
1608
1608
  ],
1609
1609
  "complexity": 3,
1610
- "featured": false,
1610
+ "featured": true,
1611
1611
  "dsl": 'fmea "EV battery pack DFMEA"\n type: design\n rank: ap\n flag: ap >= High\n number: DFMEA-2026-014\n item "Cell module" fn "Store and deliver energy"\n mode "Thermal runaway"\n effect "Pack fire / occupant injury" sev: 10\n cause "Internal short from dendrite growth" occ: 3\n controls prevention: "Cell qualification", detection: "In-line CT scan" det: 4\n cause "Overcharge past cutoff" occ: 2\n controls prevention: "BMS voltage clamp", detection: "Redundant voltage sense" det: 3\n mode "Capacity fade"\n effect "Reduced range" sev: 6\n cause "Electrolyte depletion" occ: 5\n controls detection: "Periodic SOH estimate" det: 6\n item "Busbar joint" fn "Conduct current between modules"\n mode "High-resistance connection"\n effect "Local overheating" sev: 8\n cause "Loose torque on weld" occ: 4\n controls detection: "End-of-line resistance test" det: 4',
1612
1612
  "notes": "## What this shows\n\nA design FMEA (DFMEA) on the highest-stakes subsystem in an electric vehicle. The nested chain reads item \u2192 mode \u2192 effect/cause/controls: a cell module that can suffer thermal runaway or capacity fade, and a busbar joint that can go high-resistance. Each effect carries a Severity, each cause an Occurrence, each control the Detection it earns \u2014 the three AIAG-VDA 1\u201310 scales.\n\nThe engine computes the priority rather than just tabling it. It flattens to one worksheet row per (item, mode, cause), multiplies RPN = S \xD7 O \xD7 D, and \u2014 the part that matters \u2014 derives the AIAG-VDA Action Priority, which is severity-primary. The thermal-runaway row sits at S10\xB7O3\xB7D4 with an RPN of 120; a naive RPN sort would rank it below a noisier low-severity defect, but Action Priority keeps every safety failure (S = 9\u201310) at **High** regardless. The `flag: ap >= High` directive highlights exactly those rows so the safety-critical work rises to the top."
1613
1613
  },
@@ -1734,7 +1734,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1734
1734
  "hotfix"
1735
1735
  ],
1736
1736
  "complexity": 3,
1737
- "featured": false,
1737
+ "featured": true,
1738
1738
  "dsl": 'gitGraph\n commit id: "init"\n commit id: "scaffold"\n branch develop\n checkout develop\n commit id: "feature scaffold"\n branch feature/login\n checkout feature/login\n commit id: "login UI"\n commit id: "login API"\n checkout develop\n merge feature/login\n commit id: "polish"\n checkout main\n merge develop tag: "v1.0"\n branch hotfix\n checkout hotfix\n commit id: "patch CVE" type: HIGHLIGHT\n checkout main\n merge hotfix tag: "v1.0.1"\n checkout develop\n merge main',
1739
1739
  "notes": "## What this shows\n\nA full Git Flow release cycle, the branching strategy teams adopt to keep a clean trunk. Work forks off `main` into a long-lived `develop`, then into a short-lived `feature/login` branch; the feature merges back to develop, develop merges to `main` as the tagged `v1.0` release. Then a security `hotfix` branches straight off `main`, lands a HIGHLIGHT commit, ships as `v1.0.1`, and is merged back down into develop so the fix isn't lost \u2014 the discipline that keeps the two lines from diverging.\n\nThe DSL is the Mermaid `gitGraph` dialect verbatim, so existing Mermaid sources port directly. The engine replays the operation list in order to assign each commit to a lane, routes the merge connectors from each branch tip without crossings, renders the patch commit emphasised as a HIGHLIGHT, and draws the version tags as flags \u2014 all from a KB-scale, dependency-free bundle."
1740
1740
  },
@@ -1753,7 +1753,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1753
1753
  "structured-analysis"
1754
1754
  ],
1755
1755
  "complexity": 3,
1756
- "featured": false,
1756
+ "featured": true,
1757
1757
  "dsl": 'idef0 "Fulfil customer order"\nnode A0\nfunction A1 "Validate order"\nfunction A2 "Pick and pack"\nfunction A3 "Ship and invoice"\ninput A1 "Customer order"\ncontrol A1 "Credit policy"\nmechanism A1 "Order management system"\nA1 -> A2 "Validated order"\ninput A2 "Inventory"\ncontrol A2 "Picking rules"\nmechanism A2 "Warehouse staff"\nA2 -> A3 "Packed shipment"\ncontrol A3 "Carrier contract"\nmechanism A3 "Shipping carrier"\noutput A3 "Delivered order"\noutput A3 "Invoice"',
1758
1758
  "notes": '## What this shows\n\nAn IDEF0 A0 diagram \u2014 the top-level functional decomposition of an order-fulfilment system \u2014 showing all four ICOM arrow roles. Reading the box edges by convention: **I**nputs enter on the left (the customer order, inventory), **C**ontrols govern from the top (credit policy, picking rules, carrier contract), **M**echanisms supply resources from the bottom (the order system, warehouse staff, carrier), and **O**utputs leave on the right (delivered order, invoice). The box-to-box flows (`A1 -> A2 "Validated order"`) chain the three activities.\n\nThe differentiator is that the model is correct by construction, not just drawn. Because the arrow keyword *is* the box edge it attaches to, the engine enforces ICOM placement \u2014 you cannot accidentally draw a control as an input. It resolves every box reference, assigns decomposition numbers (A0 \u2192 A1..A3), codes the boundary arrows down each edge (I1/C1/O1/M1\u2026), and applies the FIPS 3-to-6-box guideline. A flow that tried to *enter* a box via its `.output` edge would be rejected, because an output leaves a box \u2014 the standard is enforced, not suggested.'
1759
1759
  },
@@ -1882,7 +1882,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1882
1882
  "steady-state"
1883
1883
  ],
1884
1884
  "complexity": 2,
1885
- "featured": false,
1885
+ "featured": true,
1886
1886
  "dsl": 'markov "Customer lifecycle"\n analysis: classify, stationary\n Trial -> Trial : 0.4\n Trial -> Active : 0.5\n Trial -> Churned : 0.1\n Active -> Active : 0.8\n Active -> Trial : 0.05\n Active -> Churned : 0.15\n Churned -> Churned : 0.7\n Churned -> Trial : 0.3',
1887
1887
  "notes": "## What this shows\n\nA SaaS customer modelled as a memoryless hop between three states \u2014 Trial, Active, Churned \u2014 each month. Every row of probabilities leaving a state sums to 1 (the row-stochastic rule), and because churned customers can re-enter via Trial (`Churned -> Trial : 0.3`), no state is a dead end: the chain is **ergodic**, every state reachable from every other.\n\nThe engine does the linear algebra. It validates the matrix, runs SCC analysis to confirm a single recurrent class with no absorbing sink, then computes the **stationary distribution \u03C0** \u2014 the long-run share of your customer base sitting in each state once the system settles, independent of where it started. That steady-state answer (and the per-state classification) is carried in `data-*`, and it is the number a churn model exists to produce, not the circles-and-arrows."
1888
1888
  },
@@ -2638,7 +2638,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
2638
2638
  "color-coding"
2639
2639
  ],
2640
2640
  "complexity": 3,
2641
- "featured": false,
2641
+ "featured": true,
2642
2642
  "dsl": 'phylo "Bacterial Diversity"\n newick: "((((Ecoli:0.1,Salmonella:0.12):0.05[&&NHX:B=98],Vibrio:0.2):0.08[&&NHX:B=85],((Bacillus:0.15,Staph:0.18):0.06[&&NHX:B=92],Listeria:0.22):0.1):0.15,((Myco_tb:0.3,Myco_leprae:0.28):0.12[&&NHX:B=100],(Strepto:0.25,Lactobacillus:0.2):0.08[&&NHX:B=78]):0.2);"\n clade Gamma = (Ecoli, Salmonella, Vibrio) [color: "#1E88E5", label: "\u03B3-Proteobacteria"]\n clade Firmi = (Bacillus, Staph, Listeria, Strepto, Lactobacillus) [color: "#E53935", label: "Firmicutes"]\n clade Actino = (Myco_tb, Myco_leprae) [color: "#43A047", label: "Actinobacteria"]\n scale "substitutions/site"',
2643
2643
  "notes": '## Scenario\n\nA microbiologist or bioinformatician pastes a Newick tree string exported from RAxML, IQ-TREE, or MEGA and immediately gets a publication-ready SVG with clade highlights and a branch-length scale bar \u2014 no manual layout required.\n\n## Annotation key\n\n- `newick: "..."` \u2014 standard Newick format tree string; branch lengths follow `:` after each taxon name\n- `[&&NHX:B=98]` \u2014 NHX annotation; `B=` is the bootstrap support value (0\u2013100), rendered on internal nodes\n- `clade id = (taxon, ...)` \u2014 defines a named clade by listing its leaf members\n- `[color: "#hex", label: "..."]` \u2014 colors the clade\'s subtree and adds a labeled arc\n- `scale "..."` \u2014 draws a calibrated scale bar with the given unit label\n\n## How to read\n\nThe tree shows three major bacterial clades. Blue (\u03B3-Proteobacteria): *E. coli*, *Salmonella*, and *Vibrio* cluster with 98% bootstrap support. Red (Firmicutes): *Bacillus*, *Staph*, *Listeria*, *Streptococcus*, and *Lactobacillus*. Green (Actinobacteria): the two *Mycobacterium* species form a highly supported clade (bootstrap 100). Branch lengths represent substitutions per site \u2014 longer branches indicate faster evolutionary rates.'
2644
2644
  },
@@ -3243,7 +3243,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
3243
3243
  "data-flow"
3244
3244
  ],
3245
3245
  "complexity": 3,
3246
- "featured": false,
3246
+ "featured": true,
3247
3247
  "dsl": 'threatmodel "E-commerce checkout"\nexternal: Customer\nexternal: Payment Gateway\nprocess 1.0: Web App\nprocess 2.0: Order Service\ndatastore D1: Orders DB\ndatastore D2: Audit Log\nCustomer -> 1.0 : "HTTPS Checkout"\n1.0 -> 2.0 : "Place order"\n2.0 -> D1 : "Write order"\n2.0 -> Payment Gateway : "Charge card"\n2.0 -> D2 : "Order event"\nboundary "Internet" { Customer, Payment_Gateway }\nboundary "DMZ" { 1.0 }\nboundary "Internal" { 2.0, D1, D2 }',
3248
3248
  "notes": "## What this shows\n\nA STRIDE threat model of an e-commerce checkout drawn as a data-flow diagram (DFD): two external entities (the customer and a third-party payment gateway), two processes (the web app in the DMZ, the order service internally), and two data stores (the orders database and an audit log). Three trust boundaries partition the system \u2014 Internet, DMZ, Internal \u2014 and the labelled flows carry the data crossing between them.\n\nThe engine does the STRIDE-per-element analysis, not just the boxes. It applies the canonical mapping \u2014 externals get Spoofing/Repudiation, processes get the full S-T-R-I-D-E, stores get Tampering/Information-disclosure/DoS, and the **audit log additionally gets Repudiation** because it matches the log/journal pattern. Most usefully, it **flags every flow that crosses a trust boundary** \u2014 the customer\u2192web-app HTTPS request (Internet\u2192DMZ), the order-service\u2192payment-gateway charge (Internal\u2192Internet) \u2014 because boundary crossings are where spoofing, tampering, and information disclosure concentrate. Each element and flow carries its applicable STRIDE categories in `data-*`."
3249
3249
  },
@@ -3316,7 +3316,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
3316
3316
  "bus-read"
3317
3317
  ],
3318
3318
  "complexity": 1,
3319
- "featured": false,
3319
+ "featured": true,
3320
3320
  "dsl": 'timing "Synchronous Bus Read"\nCLK: clock 8\nRST: rle 1*2 0*6\nEN: rle 0*2 1*4 0*2\nDATA: zz====zz data: ["D0","D1","D2","D3"]',
3321
3321
  "notes": "## Scenario\n\nA digital designer documents an 8-cycle synchronous read. Rather than typing\n`pppppppp` for the clock and counting `0`/`1` runs by hand for reset and enable \u2014\nthe most common source of misaligned waveforms \u2014 the diagram uses the two\nlength-explicit shorthands.\n\n## Annotation key\n\n- **`clock N`** \u2014 a clock generator with `N` periods. `CLK: clock 8` expands to\n `pppppppp`; add `neg` for a negedge clock. No character-counting.\n- **`rle <state>*<count> \u2026`** \u2014 run-length segments. `RST: rle 1*2 0*6` expands to\n `11000000`; `EN: rle 0*2 1*4 0*2` expands to `00111100`. Every signal's total\n cell count is explicit, so the waves line up.\n- **raw wave string** \u2014 `DATA: zz====zz` keeps per-cell control where it matters;\n `data: [...]` labels the four `=` bus segments.\n\n## How to read\n\nThe clock runs 8 cycles. Reset is asserted for the first 2 cycles, then drops.\nEnable rises for the middle 4 cycles. The data bus is high-impedance until enable,\nthen presents four stable bytes `D0\u2026D3`, returning to high-Z after. Because\n`clock` and `rle` make each signal exactly 8 cells, the edges align without manual\ncounting."
3322
3322
  },
@@ -3544,7 +3544,7 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
3544
3544
  "fabrication"
3545
3545
  ],
3546
3546
  "complexity": 3,
3547
- "featured": false,
3547
+ "featured": true,
3548
3548
  "dsl": 'welding "Bracket assembly"\njoint "gusset to column" {\n arrow: fillet size=8 len=50 pitch=150\n other: fillet size=6\n around\n field\n tail: "GMAW"\n}\njoint "splice plate (butt)" {\n arrow: vgroove angle=60 root=3 throat=12 contour=flush finish=G\n other: backing\n tail: "SMAW; E7018"\n}',
3549
3549
  "notes": "## What this shows\n\nA **welding symbol** is the standard way a drawing tells a fabricator how to weld a joint \u2014 codified by **AWS A2.4** (US) and **ISO 2553** (international). It is a *reference-line skeleton*: a horizontal line, a leader arrow to the joint, and a weld glyph snapped above (other side) or below (arrow side) the line, with dimensions in fixed slots.\n\nThe first joint is an **intermittent double fillet** \u2014 a `size=8` arrow-side fillet welded `50` long on a `150` pitch, a `size=6` fillet on the other side \u2014 carried **all around** the gusset (the open circle at the leader junction) as a **field weld** (the flag), with the **GMAW** process noted in the tail. The second joint is a full-penetration **V-groove butt weld**: a `60\xB0` included angle with a `3 mm` root opening and a `12 mm` effective throat, a **backing** weld on the far side, ground **flush**, welded **SMAW** with **E7018** electrode. Each callout is placed correct-by-construction \u2014 the engine owns the skeleton, you describe the weld."
3550
3550
  }
@@ -3772,280 +3772,696 @@ var PROFILES = {
3772
3772
  genogram: {
3773
3773
  type: "genogram",
3774
3774
  header: 'genogram "Title"',
3775
- mode: "family declarations + indented children",
3775
+ mode: "individual declarations + couple lines + indented children",
3776
+ keywords: 'sex: male female unknown other \xB7 status: deceased stillborn miscarriage abortion \xB7 couple ops: -- (married) ~/~ (cohabiting-ended) ~x~ (divorced) -/- (separated) -o- (engaged) == (consanguineous) ~ (cohabiting) \xB7 child props: adopted foster guardian twin-identical twin-fraternal \xB7 individual attrs: age:N dob:"\u2026" dod:"\u2026" death:YYYY note:"\u2026" birth:out-of-wedlock|adopted|legitimate label:"\u2026" sibling-of:ID index \xB7 conditions: name(fill[,#color]) + \u2026 \xB7 fills: full half-left half-right half-top half-bottom quad-tl quad-tr quad-bl quad-br quarter striped dotted \xB7 emotional ops: -TYPE- or -TYPE-> where TYPE \u2208 harmony close love hostile conflict cutoff fused distant nevermet abuse neglect controlling jealous focused distrust',
3776
3777
  forms: [
3777
- "personId [sex, birthYear, optionalAttrs]",
3778
- 'parentA -- parentB "optional relationship label"',
3779
- "indent children under the couple line"
3778
+ 'genogram "Smith Family"',
3779
+ " john [male, 1975]",
3780
+ " mary [female, 1977]",
3781
+ ' john -- mary "m. 2002"',
3782
+ " alice [female, 2005, index]",
3783
+ " john -conflict- mary"
3784
+ ],
3785
+ prefer: [
3786
+ "Declare every individual (`id [sex, year, attrs]`) before any couple or emotional line that references them.",
3787
+ "Use couple operators `--` (married), `~/~` (cohabiting-ended), `~x~` (divorced), `-/-` (separated), `==` (consanguineous), `~` (cohabiting) on their own line; indent children beneath.",
3788
+ "Annotate conditions as `conditions: name(fill, #color) + name2(fill2)`, e.g. `conditions: diabetes(half-left, #ff9800) + cancer(quad-tr, #9c27b0)`.",
3789
+ "Mark the identified patient with the `index` attribute so the concentric double-border is drawn."
3790
+ ],
3791
+ avoid: [
3792
+ "Don't use pedigree genetic-status tokens (`affected`, `carrier`, `proband`) in a genogram \u2014 use `conditions:` fill patterns instead.",
3793
+ 'Don\'t attach emotional-operator labels with `[label:]`; place the optional quoted label after the right-hand id: `john -conflict- mary "ongoing"`.',
3794
+ "Don't invent fill patterns \u2014 use only `full`, `half-left/right/top/bottom`, `quad-tl/tr/bl/br`, `quarter`, `striped`, `dotted`."
3780
3795
  ],
3781
- prefer: ["Declare people before emotional relationships.", 'Use `[label: "..."]` only where the syntax reference shows a label attribute.'],
3782
- avoid: ["Avoid inline comments and speculative relationship operators."],
3783
- repair: ["Unknown individuals usually need a declaration before the relationship line."]
3796
+ repair: [
3797
+ "'Unknown individual' -> declare `id [sex]` before any couple or emotional line that references that id.",
3798
+ "'Invalid fill pattern' -> use one of: full, half-left, half-right, half-top, half-bottom, quad-tl, quad-tr, quad-bl, quad-br, quarter, striped, dotted.",
3799
+ "'Conflicting sex for' -> an individual was declared with two different sexes \u2014 keep sex in the first declaration only.",
3800
+ "'Invalid birth status' -> `birth:` accepts only `legitimate`, `out-of-wedlock`, or `adopted`."
3801
+ ]
3784
3802
  },
3785
3803
  ecomap: {
3786
3804
  type: "ecomap",
3787
3805
  header: 'ecomap "Title"',
3788
- mode: "center + external systems",
3806
+ mode: "center declaration + external systems + connection operators",
3807
+ keywords: 'center: ID [label:"\u2026"] \xB7 system: ID [label:"\u2026" category:family|friends|work|education|health|mental-health|religion|recreation|legal|community|substance|financial|cultural|technology|pet size:large|medium|small importance:major|moderate|minor] \xB7 connection ops (strength): === (strong) == (moderate) --- (normal) "- -" (weak) ~~~ (stressful) ~x~ (conflictual) -/- (broken) \xB7 directional variants: ===> <=== ==> <== --> <-- <-> <=> \xB7 [label:"\u2026"] on a connection',
3789
3808
  forms: [
3790
- 'center: client [label: "Client"]',
3791
- 'systemId [label: "System", category: family]',
3792
- 'systemId === client [label: "support"]'
3809
+ 'ecomap "Marcus, age 15"',
3810
+ ' center: client [label: "Marcus"]',
3811
+ ' mom [label: "Mother", category: family]',
3812
+ ' school [label: "East High School", category: education]',
3813
+ ' therapist [label: "Ms. Chen", category: mental-health]',
3814
+ ' mom === client [label: "primary caregiver"]',
3815
+ " school === client",
3816
+ ' therapist <-> client [label: "weekly"]'
3817
+ ],
3818
+ prefer: [
3819
+ "Declare exactly one `center: ID [props]` first; this is the central individual or family unit.",
3820
+ 'Declare all external systems (`ID [label: "\u2026", category: \u2026]`) before their connection lines.',
3821
+ "Choose connection strength from `===` (strong), `==` (moderate), `---` (normal), `~~~` (stressful), `-/-` (broken); add a directional arrow variant (`-->`, `<->`, `==>`) when energy-flow direction matters."
3793
3822
  ],
3794
- prefer: ["Declare exactly one center.", "Declare outside systems before their connection lines."],
3795
- avoid: ["Avoid borrowing genogram operators such as `--`."],
3796
- repair: ["A valid render with a missing center is semantically wrong; add `center:` first."]
3823
+ avoid: [
3824
+ "Don't use genogram couple operators (`--`, `~/~`) inside an ecomap \u2014 they are not connection operators here.",
3825
+ "Don't omit the `center:` line; a diagram with no center is structurally invalid even if it renders.",
3826
+ "Don't nest family members under `center:` like a genogram; the ecomap center is a single node (use `sociogram` for network structure)."
3827
+ ],
3828
+ repair: [
3829
+ "'Unexpected:' -> the line is not a center declaration, a system declaration, or a valid connection \u2014 check operator spelling (`~x~` has the letter x).",
3830
+ "'Expected ecomap header' -> the first non-blank line must start with `ecomap`; add or fix the header.",
3831
+ 'A connection to an undeclared id silently creates a stray node \u2014 declare `systemId [label: "\u2026"]` before the connection line.'
3832
+ ]
3797
3833
  },
3798
3834
  pedigree: {
3799
3835
  type: "pedigree",
3800
3836
  header: 'pedigree "Title"',
3801
- mode: "clinical pedigree",
3837
+ mode: "individual declarations + couple lines + indented offspring (NSGC notation)",
3838
+ keywords: 'sex: male female unknown amab afab uaab \xB7 genetic status: affected carrier carrier-x obligate-carrier presymptomatic unaffected \xB7 life status: deceased stillborn pregnancy sab tab ectopic \xB7 markers: proband consultand evaluated \xB7 couple ops: -- (mated) -/- (separated) == (consanguineous) ~ (cohabiting, no offspring) \xB7 multi-trait legend: id = "Label" (fill: quad-tl|quad-tr|quad-bl|quad-br) + affected:trait1+trait2 \xB7 header mode suffix: pedigree:autosomal-dominant|autosomal-recessive|x-linked',
3802
3839
  forms: [
3803
- "personId [sex, generationAttrs]",
3804
- "parentA -- parentB",
3805
- "indent offspring under the couple line"
3840
+ 'pedigree "Hemophilia A"',
3841
+ " I-1 [male, unaffected]",
3842
+ " I-2 [female, carrier-x]",
3843
+ " I-1 -- I-2",
3844
+ " II-1 [male, affected]",
3845
+ " II-2 [female, carrier-x]",
3846
+ " II-3 [male, unaffected]"
3806
3847
  ],
3807
- prefer: ["Use pedigree status/trait attributes from the syntax reference.", "Keep generation structure explicit."],
3808
- avoid: ["Avoid genogram emotional-relationship lines in pedigree output."],
3809
- repair: ["Declare every referenced individual before relationships."]
3848
+ prefer: [
3849
+ "Use Roman-numeral generation labels (`I-1`, `II-3`) for individual ids \u2014 the NSGC clinical convention.",
3850
+ "Declare genetic status on each individual: `affected`, `carrier`, `carrier-x` (X-linked dot), `obligate-carrier`, `presymptomatic`, or `unaffected`.",
3851
+ "Mark the index case with `proband`; mark a family member seeking advice with `consultand`."
3852
+ ],
3853
+ avoid: [
3854
+ "Don't use genogram emotional-relationship operators (`-conflict-`, `-abuse->`) in a pedigree \u2014 only couple and parent-child lines are valid.",
3855
+ "Don't mix pedigree status with genogram `conditions:` fill syntax; pedigree uses `affected`/`carrier` tokens, not `conditions:name(fill)`.",
3856
+ "Don't invent sex tokens; valid values are `male`, `female`, `unknown`, `amab`, `afab`, `uaab`."
3857
+ ],
3858
+ repair: [
3859
+ "'Unknown individual' -> declare `id [sex, geneticStatus]` before any couple line that references that id.",
3860
+ "'Expected pedigree header' -> the first non-blank line must start with `pedigree`; the optional `:mode` suffix and title follow.",
3861
+ "'Conflicting' -> keep one canonical declaration per individual; redeclarations should add only the missing couple reference."
3862
+ ]
3810
3863
  },
3811
3864
  phylo: {
3812
3865
  type: "phylo",
3813
3866
  header: 'phylo "Title"',
3814
- mode: "quoted Newick",
3815
- forms: ['newick: "((A:0.1,B:0.2),C:0.3);"', 'clade Group = (A, B) [label: "Group"]'],
3816
- prefer: ["Use Newick for first-shot generation.", "Quote the Newick string."],
3817
- avoid: ["Avoid indent-tree mode unless the user wants a hand-authored tree."],
3818
- repair: ["A phylo document needs exactly one tree definition: Newick or indent tree."]
3867
+ mode: "Newick string (newick:) or indented-tree DSL, with optional clade highlights",
3868
+ keywords: 'newick: "\u2026" (NHX supported) \xB7 indent tree: root: then indented Name:branchLength \xB7 header opts [layout: rectangular|slanted|circular|unrooted, mode: phylogram|cladogram|chronogram|dendrogram, mrsd:"YYYY", branch-width:N] \xB7 clade ID = (leaf1, leaf2) [color: "#hex", label: "\u2026", highlight: branch|background|both] \xB7 outgroup: ID \xB7 scale "unit label" \xB7 cut N (dendrogram threshold)',
3869
+ forms: [
3870
+ 'phylo "Bacterial Diversity"',
3871
+ ' newick: "((Ecoli:0.1,Salmonella:0.12):0.05,Vibrio:0.2);"',
3872
+ ' clade Gamma = (Ecoli, Salmonella, Vibrio) [color: "#1E88E5", label: "Gammaproteobacteria"]',
3873
+ ' scale "substitutions/site"'
3874
+ ],
3875
+ prefer: [
3876
+ 'Use `newick: "\u2026"` for first-shot generation \u2014 a Newick string with branch lengths (`Name:length`); NHX annotations `[&&NHX:B=98]` carry bootstrap values.',
3877
+ 'Add `clade ID = (leaf1, leaf2, \u2026) [color: "#hex", label: "\u2026"]` after the newick line to highlight named clades.',
3878
+ 'Use `[mode: chronogram, mrsd: "YYYY"]` for time-calibrated trees and `[mode: cladogram]` when branch lengths are absent.'
3879
+ ],
3880
+ avoid: [
3881
+ "Don't use the indent-tree mode (`root:` + indented names) for first-shot generation \u2014 Newick is more compact.",
3882
+ "Don't omit branch lengths in phylogram mode; without them the layout collapses (use `[mode: cladogram]` explicitly instead).",
3883
+ "Don't reference clade member names that don't match the Newick leaf labels exactly \u2014 highlighting silently does nothing on a mismatch."
3884
+ ],
3885
+ repair: [
3886
+ "'No tree definition found (newick: or indent tree)' -> add a `newick: \"\u2026;\"` line or an indent tree starting with `root:` after the header.",
3887
+ "'Empty indent tree definition' -> the `root:` block has no indented content \u2014 add at least one child line.",
3888
+ "If clade colors don't appear, check every member id in `clade X = (\u2026)` exactly matches a leaf label in the Newick string."
3889
+ ]
3819
3890
  },
3820
3891
  sociogram: {
3821
3892
  type: "sociogram",
3822
3893
  header: 'sociogram "Title"',
3823
- mode: "declared nodes + social ties",
3824
- forms: ['nodeId [label: "Person"]', 'nodeA -> nodeB [label: "choice"]', "config: layout = circular"],
3825
- prefer: ["Declare actors first.", "Use one supported layout/config value at a time."],
3826
- avoid: ["Avoid unknown config values; some adapters keep defaults."],
3827
- repair: ["Unknown edge endpoints need matching node declarations."]
3894
+ mode: "declared nodes + edge operators + config",
3895
+ keywords: 'nodeId [label:"\u2026" group:id role:star|isolate|bridge|neglectee|rejected size:small|medium|large] \xB7 group id [label:"\u2026" color:"#hex"] + indented members \xB7 edge ops positive: -> <-> -- ==> <==> === \xB7 negative: -x> <x-> -x- \xB7 neutral: -.> <.-> -.- \xB7 [weight:N label:"\u2026"] on edge \xB7 config: layout = circular|force-directed|concentric \xB7 config: sizing = uniform|in-degree|betweenness \xB7 config: coloring = default|group|role',
3896
+ forms: [
3897
+ 'alice [label: "Alice"] [role: star]',
3898
+ 'bob [label: "Bob"] [group: teamA]',
3899
+ 'group teamA [label: "Team A", color: "#1976D2"]',
3900
+ " alice; bob",
3901
+ 'alice <-> bob [label: "study partners"]',
3902
+ 'carol -x> dave [label: "conflict"]',
3903
+ "config: layout = force-directed"
3904
+ ],
3905
+ prefer: [
3906
+ "Declare nodes explicitly before edge lines when you need `role:`, `group:`, or `size:` \u2014 the parser auto-registers undeclared ids, but only named declarations carry attributes.",
3907
+ "Use `group id [label:\u2026]` + indented members to cluster nodes; coloring follows when `config: coloring = group` is set.",
3908
+ "Pick the edge operator by valence: `->`/`<->`/`--` positive, `-x>`/`-x-` negative (rejection/conflict), `-.>`/`-.-` neutral; heavier `==>`/`===` encode strong-weight ties."
3909
+ ],
3910
+ avoid: [
3911
+ "Don't borrow ecomap operators \u2014 in a sociogram `===` is an undirected strong tie, not an ecomap stress line; every operator encodes direction and valence.",
3912
+ "Don't set `config: layout` to an unsupported value \u2014 only `circular`, `force-directed`, `concentric` are accepted; unknown values are silently ignored.",
3913
+ "Don't omit the spaces around an edge operator: `alice->bob` is not parsed; it must be `alice -> bob`."
3914
+ ],
3915
+ repair: [
3916
+ "'Sociogram must start with' -> the first non-blank line must be `sociogram` (optionally with a quoted title); fix a missing or misspelled header.",
3917
+ 'A node referenced only on edge lines auto-registers with no label \u2014 if the rendered label is the raw id, add an explicit `nodeId [label: "\u2026"]` declaration.',
3918
+ "An unrecognised `config:` value (e.g. `layout = radial`) is silently dropped and defaults to `circular` \u2014 check spelling against the accepted enum."
3919
+ ]
3828
3920
  },
3829
3921
  timing: {
3830
3922
  type: "timing",
3831
3923
  header: 'timing "Title"',
3832
3924
  mode: "WaveDrom signals, with clock/run-length shorthands",
3925
+ keywords: 'timing "title" [hscale: N] \xB7 SIGNAME: wave_spec \xB7 wave chars 0 1 x z = . u d p P n N h H l L 2-9 \xB7 clock N [neg] \xB7 rle <state>*<count> \u2026 \xB7 data: ["a","b"] for = and digit segments \xB7 [GroupName] or group "name" { \u2026 } \xB7 --- spacer \xB7 phase: FLOAT (0.0\u20131.0)',
3833
3926
  forms: [
3834
- "CLK: clock 8 (clock generator, 8 periods \u2014 no char-counting)",
3835
- "RST: rle 1*2 0*6 (run-length: two 1s then six 0s)",
3836
- 'DATA: x====x data: ["A","B"] (raw WaveDrom wave + bus labels)'
3927
+ 'timing "Synchronous Bus Read"',
3928
+ "CLK: clock 8",
3929
+ "RST: rle 1*2 0*6",
3930
+ "EN: rle 0*2 1*4 0*2",
3931
+ 'DATA: zz====zz data: ["D0","D1","D2","D3"]'
3837
3932
  ],
3838
3933
  prefer: [
3839
- "Use `clock N` for clocks instead of counting `p` characters.",
3840
- "Use `rle <state>*<count> ...` for level/data signals instead of counting characters \u2014 it auto-aligns length.",
3841
- "Drop to a raw wave string only for fine control; keep all signals the same total length so they align.",
3842
- "Use `data:` labels for bus segments (`=` or digit states)."
3934
+ "Use `clock N` for clocks instead of counting `p` characters \u2014 `CLK: clock 8` is immune to count errors.",
3935
+ "Use `rle <state>*<count> \u2026` for level/data signals \u2014 `RST: rle 1*2 0*6` makes length explicit and auto-aligns.",
3936
+ 'Drop to a raw wave string only for fine control (e.g. `DATA: zz====zz`); label bus segments (`=` or digit states) with `data: ["A","B",\u2026]`.',
3937
+ "Keep all signals the same total cell count so they align; `clock N` and `rle` make this trivial to verify."
3938
+ ],
3939
+ avoid: [
3940
+ "Avoid hand-counting long runs of identical characters \u2014 mismatched counts are the main source of misaligned waves.",
3941
+ "Don't mix `clock`/`rle` and a raw wave string for the same signal; pick one form per signal.",
3942
+ "Don't use a wave character outside `0 1 x z = . p P n N h H l L u d 2-9`."
3843
3943
  ],
3844
- avoid: ["Avoid hand-counting long runs of identical characters \u2014 that is the main source of misaligned waves."],
3845
3944
  repair: [
3846
- "Wave-state errors name the offending character and list the valid states.",
3847
- "If two signals don't line up, make their total cell counts equal (clock N and rle make this easy)."
3945
+ "'clock needs a positive cycle count' -> provide an integer >= 1 after `clock`, e.g. `CLK: clock 8`.",
3946
+ "'rle segment must be' -> each rle token must match `<char>*<N>` with a valid state char, e.g. `rle 1*3 0*5`.",
3947
+ "'is not a valid state' -> valid states are `0 1 x z = . p P n N h H l L u d 2-9`; replace the offending character, or use `clock`/`rle`."
3848
3948
  ]
3849
3949
  },
3850
3950
  logic: {
3851
3951
  type: "logic",
3852
3952
  header: 'logic "Title"',
3853
- mode: "logic netlist",
3854
- forms: ["INPUT A, B", "G1 = AND(A, B)", "OUTPUT Y = G1"],
3953
+ mode: "logic netlist \u2014 INPUT/OUTPUT declarations + gate assignments; optional module grouping",
3954
+ keywords: 'logic ["Title"] [style: ansi|iec] \xB7 INPUT id, \u2026 \xB7 OUTPUT id, \u2026 \xB7 id = GATE(in1, in2, \u2026) \xB7 active-low prefix ~ on any signal \xB7 module "Label" { \u2026 } \xB7 combinational: AND OR NOT NAND NOR XOR XNOR BUF \xB7 output-buffer: TRISTATE_BUF TRISTATE_INV OPEN_DRAIN SCHMITT \xB7 flip-flops: DFF JKFF SRFF TFF \xB7 latches: LATCH_SR LATCH_D \xB7 complex: MUX DEMUX DECODER ENCODER COUNTER SHIFT_REG',
3955
+ forms: [
3956
+ 'logic "1-bit Full Adder"',
3957
+ "input A, B, Cin",
3958
+ "output Sum, Cout",
3959
+ "s1 = XOR(A, B)",
3960
+ "Sum = XOR(s1, Cin)",
3961
+ "c1 = AND(A, B)",
3962
+ "c2 = AND(s1, Cin)",
3963
+ "Cout = OR(c1, c2)"
3964
+ ],
3855
3965
  prefer: [
3856
- "Gate form is `id = TYPE(in1, in2, \u2026)`; declare `INPUT`/`OUTPUT` ports explicitly. Prefix a signal with `~` for active-low.",
3857
- "Use only these canonical gate TYPEs: combinational AND, OR, NOT, NAND, NOR, XOR, XNOR, BUF; output buffers TRISTATE_BUF, TRISTATE_INV, OPEN_DRAIN, SCHMITT; flip-flops DFF, JKFF, SRFF, TFF; latches LATCH_SR, LATCH_D; complex MUX, DEMUX, DECODER, ENCODER, COUNTER, SHIFT_REG."
3966
+ "Gate form is `id = TYPE(in1, in2, \u2026)`; declare `INPUT`/`OUTPUT` ports explicitly. Prefix any signal with `~` for active-low.",
3967
+ "Use only canonical gate types \u2014 combinational `AND` `OR` `NOT` `NAND` `NOR` `XOR` `XNOR` `BUF`; buffers `TRISTATE_BUF` `TRISTATE_INV` `OPEN_DRAIN` `SCHMITT`; flip-flops `DFF` `JKFF` `SRFF` `TFF`; latches `LATCH_SR` `LATCH_D`; complex `MUX` `DEMUX` `DECODER` `ENCODER` `COUNTER` `SHIFT_REG`.",
3968
+ 'Group related gates with `module "Label" { \u2026 }` to render a named bounding box.'
3969
+ ],
3970
+ avoid: [
3971
+ "Avoid circuit component names (`R`, `C`, transistors) or FBD block names (`TON`, `CTU`) inside logic diagrams \u2014 they are not valid gate types.",
3972
+ "Don't leave a `module { \u2026 }` unclosed \u2014 an unclosed module block throws `Unclosed module`.",
3973
+ "Don't rely on auto-declaration of undeclared signals \u2014 the parser warns and adds them silently; explicit `INPUT` lines are cleaner."
3858
3974
  ],
3859
- avoid: ["Avoid circuit component names (R, C, transistors) inside logic diagrams."],
3860
- repair: ["An unrecognised gate renders as a flagged `?` placeholder; replace it with the closest canonical gate from the list above (e.g. LOAD/REG \u2192 DFF, INV \u2192 NOT)."]
3975
+ repair: [
3976
+ "'has unrecognised type' -> replace with the closest canonical gate (e.g. `LOAD`/`REG` -> `DFF`, `INV` -> `NOT`, `BUFFER` -> `BUF`); the lint hint names the nearest match.",
3977
+ "'Unclosed module' -> add a closing `}` for every `module \"\u2026\" {` block.",
3978
+ "'was not declared; auto-declared as input' -> add an explicit `input X` line to make the port intentional."
3979
+ ]
3861
3980
  },
3862
3981
  circuit: {
3863
3982
  type: "circuit",
3864
3983
  header: 'circuit "Title" netlist',
3865
3984
  mode: "SPICE-style netlist (recommended for generation)",
3985
+ keywords: 'header: circuit "name" netlist \xB7 ID net1 net2 [value] [key=value \u2026] \xB7 prefixes R(resistor) C(capacitor) L(inductor) D(diode) V(voltage_source) I(current_source) Q(BJT) M(MOSFET) J(jfet) S(switch) F(fuse) B(battery) K(relay) U/X(ic) W(wire) T(terminal) \xB7 ground nets 0/gnd/ground/earth/vss/agnd/dgnd \xB7 type= override \xB7 dir=(right|left|up|down) \xB7 pins="\u2026" \xB7 positional mode (no netlist): id: type dir [label= value= at=] \xB7 wire right|left|up|down \xB7 at: id.pin \xB7 net NAME \xB7 ground vcc no_connect',
3866
3986
  forms: [
3867
- "V1 in 0 5V (component-id node-A node-B value)",
3868
- "R1 in out 1k",
3869
- "C1 out 0 100n"
3987
+ 'circuit "Bridge Rectifier Supply" netlist',
3988
+ "V1 ac1 ac2 12Vac",
3989
+ "D1 ac1 vout 1N4007",
3990
+ "D2 ac2 vout 1N4007",
3991
+ "D3 0 ac1 1N4007",
3992
+ "D4 0 ac2 1N4007",
3993
+ "C1 vout 0 470u",
3994
+ "Rload vout 0 1k"
3870
3995
  ],
3871
3996
  prefer: [
3872
- "Always use netlist mode (`... netlist` header). Each line is one component; no spatial state to track.",
3873
- "Two components that share a node name are wired together. `0`, `gnd`, or `GND` is the ground net.",
3874
- "The component-id prefix sets the type (R=resistor, C=capacitor, L=inductor, V=source, D=diode, Q=BJT). Add explicit `type=` only when the prefix is ambiguous.",
3875
- "Optional orientation hint `dir=` (right|left|up|down) nudges a single symbol's facing, e.g. `C1 out 0 100n dir=down` for a shunt cap. Connectivity is unaffected; omit it unless layout readability needs it."
3997
+ 'Always use netlist mode (`circuit "name" netlist`). Each line is one component; no cursor state to track.',
3998
+ "Two components sharing a net name are wired together. Ground is `0`, `GND`, or an alias (`AGND`, `VSS`, `earth`); all normalise to one GND rail.",
3999
+ "The id first letter sets the type (R=resistor, C=capacitor, L=inductor, D=diode, V=voltage_source, Q=BJT, M=MOSFET). Use `type=` only when the prefix is ambiguous.",
4000
+ "Optional `dir=right|left|up|down` nudges a symbol's orientation (e.g. `C1 vout 0 100n dir=down` for a shunt cap); it does not set position."
3876
4001
  ],
3877
4002
  avoid: [
3878
- "Avoid positional cursor routing (`wire`, `at:`) \u2014 that mode is for hand-drawing, not generation.",
3879
- "Do not invent coordinates; the layout engine places components from the net connectivity. `dir=` only rotates a symbol, it does not set position."
4003
+ "Avoid positional cursor mode (`wire`, `at:`) \u2014 it requires tracking mental cursor state and is not recommended for generation.",
4004
+ "Do not invent coordinates; the auto-layout engine places components from net connectivity. `dir=` only rotates a symbol.",
4005
+ "Don't give a multi-terminal part fewer nets than it has pins (a `transformer` needs 4: `T1 p1 p2 s1 s2 type=transformer`)."
3880
4006
  ],
3881
- repair: ["If a component type or pin count is ambiguous, make the symbol type explicit with `type=`."]
4007
+ repair: [
4008
+ "'Cannot infer type from id' -> rename to a SPICE-prefix id (R*, C*, L*, D*, V*, Q*, M*\u2026) or add `type=<name>` (e.g. `N1 in out type=nmos`).",
4009
+ "'must be unique' -> a reference designator is declared more than once; rename the duplicates.",
4010
+ "'no ground reference' -> tie a return node to ground by naming a net `0` or `GND` (e.g. `V1 vin 0 5V`).",
4011
+ "'connects to only one pin' -> wire the net to a second pin, or mark the open pin with a `no_connect` symbol."
4012
+ ]
3882
4013
  },
3883
4014
  blockdiagram: {
3884
4015
  type: "blockdiagram",
3885
4016
  header: 'blockdiagram "Title"',
3886
- mode: "blocks, sums, signals",
3887
- forms: ['ctrl = block("PID") [role: controller]', "err = sum(+r, -y)", "err -> ctrl -> plant"],
3888
- prefer: ["Use named blocks and directed `->` chains."],
3889
- avoid: ["Avoid unlabeled feedback intent; model the summing junction."],
3890
- repair: ["Connections must point at declared blocks, sums, signals, or boundary IDs."]
4017
+ mode: "named block/sum/signal decls + directed -> chains",
4018
+ keywords: 'ID = block("label") [role:\u2026][route:above|below] \xB7 ID = sum(+a, -b) \xB7 ID = signal("label") [discrete] \xB7 connect ids with -> (chainable a -> b -> c) \xB7 in / out boundary ports \xB7 edge label ["text"] | roles: controller plant sensor actuator reference disturbance generic',
4019
+ forms: [
4020
+ 'C = block("PID C(s)") [role: controller]',
4021
+ 'G = block("Plant G(s)") [role: plant]',
4022
+ "err = sum(+r, -y)",
4023
+ "in -> err -> C -> G -> y",
4024
+ "G -> err"
4025
+ ],
4026
+ prefer: [
4027
+ 'Declare blocks `ID = block("label") [role: \u2026]`, junctions `ID = sum(+a, -b)`, and signals `ID = signal("label")`, then wire ids with directed `->` (chainable: `a -> b -> c`).',
4028
+ "Model feedback through an explicit `sum(+ref, -fb)` \u2014 the signs are visual (+ enters, \u2212 subtracts), not computed; send a long return path over the top with `[route: above]` on the sensor block.",
4029
+ 'Roles for styling: controller, plant, sensor, actuator, reference, disturbance, generic. Use the auto-provided `in`/`out` ports for the system boundary, and annotate an edge with `["label"]`.'
4030
+ ],
4031
+ avoid: [
4032
+ "An unknown id on a `->` line does NOT error \u2014 it silently becomes a stray generic `block`, so a typo'd id creates a phantom box. Keep ids consistent.",
4033
+ "Avoid drawing feedback as a bare reverse arrow with no junction; route it into a `sum(...)` so the loop reads correctly.",
4034
+ 'Don\'t quote ids; quotes belong only inside `block("\u2026")` / `signal("\u2026")` labels and edge `["\u2026"]` labels.'
4035
+ ],
4036
+ repair: [
4037
+ "'Invalid connection: <line>' -> a `->` line needs an id on both sides (e.g. `err -> C`); `C ->` or `-> C` alone is rejected.",
4038
+ "Most ids auto-declare, so few lines hard-fail \u2014 if a box is unexpectedly empty or duplicated, look for an id typo (see avoid)."
4039
+ ]
3891
4040
  },
3892
4041
  ladder: {
3893
4042
  type: "ladder",
3894
4043
  header: 'ladder "Title"',
3895
- mode: "rungs + IEC/Rockwell elements",
3896
- forms: ['rung 1 "Run motor":', " XIC(START_PB)", " OTE(MOTOR_RUN)"],
3897
- prefer: ["Use uppercase element names.", "Use `parallel:` + `branch:` only for OR branches."],
3898
- avoid: ["Avoid variable declarations and ST syntax."],
3899
- repair: ["Element typos are repairable from parser suggestions; keep the tag in parentheses."]
4044
+ mode: "rungs with IEC 61131-3 contacts / coils / function blocks; OR branches via parallel:/branch:",
4045
+ keywords: 'rung N ["comment"]: \xB7 contacts XIC XIO ONS OSF \xB7 coils OTE OTL OTU OTN RES \xB7 function blocks TON TOFF TP CTU CTD CTUD ADD SUB MUL DIV MOV EQU NEQ GRT LES GEQ LEQ \xB7 parallel: branch: (OR branch block) \xB7 element syntax ELEM(tag [, address] [, name="\u2026"])',
4046
+ forms: [
4047
+ 'ladder "Motor Start/Stop"',
4048
+ 'rung 1 "Seal-in circuit":',
4049
+ " parallel:",
4050
+ " branch:",
4051
+ ' XIC(START_PB, "IN 1.0", name="Start Button")',
4052
+ " branch:",
4053
+ ' XIC(MOTOR_AUX, "BIT 3.0", name="Aux Contact")',
4054
+ ' XIO(STOP_PB, "IN 1.1", name="Stop Button")',
4055
+ ' OTE(MOTOR_CMD, "OUT 2.0", name="Motor Command")'
4056
+ ],
4057
+ prefer: [
4058
+ "Use uppercase element names: contacts `XIC` `XIO` `ONS` `OSF`; coils `OTE` `OTL` `OTU` `OTN` `RES`; function blocks `TON` `TOFF` `TP` `CTU` `CTD` `CTUD` `ADD` `SUB` `MUL` `DIV` `MOV` `EQU` `NEQ` `GRT` `LES` `GEQ` `LEQ`.",
4059
+ 'Keep the tag as the first argument in parentheses; optional address (second positional) and `name=` follow: `XIC(START_PB, "IN 1.0", name="Start Button")`.',
4060
+ "Model parallel contacts (OR logic) with an indented `parallel:` block containing two or more `branch:` sub-blocks."
4061
+ ],
4062
+ avoid: [
4063
+ "Don't invent element types \u2014 an unrecognised name throws `unknown element type` and halts the parse; use only the canonical list.",
4064
+ "Don't write an empty rung; every rung must contain at least one element or the parser throws `empty rung`.",
4065
+ "Don't use `branch:` outside a `parallel:` block \u2014 it throws `branch: without parallel:`."
4066
+ ],
4067
+ repair: [
4068
+ "'unknown element type' -> replace with the closest canonical name (e.g. `LATCH` -> `OTL`, `UNLATCH` -> `OTU`, `OSR` -> `ONS`); the parser's did-you-mean hint names the nearest match.",
4069
+ "'empty rung' -> add at least one contact or coil element inside the rung.",
4070
+ "'branch: without parallel:' -> wrap the `branch:` block inside a `parallel:` block first.",
4071
+ "'element outside of rung' -> every element line must appear after a `rung N:` header."
4072
+ ]
3900
4073
  },
3901
4074
  sld: {
3902
4075
  type: "sld",
3903
4076
  header: 'sld "Title"',
3904
- mode: "equipment assignments + power-flow edges",
3905
- forms: ['util = utility [label: "Grid"]', 'xfmr = transformer [rating: "500 kVA"]', "util -> xfmr (one edge per line, no chaining)"],
4077
+ mode: "equipment declarations + directed power-flow edges",
4078
+ keywords: 'sld "title" [standard: ansi|iec|abnt|as-nzs] \xB7 ID = nodeType [label:"\u2026" voltage:"\u2026" rating:"\u2026" device:"\u2026"] \xB7 ID -> ID [cable:"\u2026" label:"\u2026"] \xB7 sources: utility generator solar wind ups \xB7 transformers: transformer transformer_dy transformer_yd transformer_yy transformer_dd autotransformer transformer_3winding \xB7 buses: bus bus_tie hub \xB7 switching: breaker breaker_vacuum switch switch_load ground_switch ats recloser sectionalizer fuse fuse_cl \xB7 protection: ct pt relay surge_arrester ground_fault \xB7 loads: motor load capacitor_bank harmonic_filter vfd \xB7 metering: watthour_meter demand_meter \xB7 aliases: mcb/mccb->breaker rcd/rcbo/rccb->ground_fault isolator/disconnector->switch_load panel/consumer_unit/distribution_board->bus',
4079
+ forms: [
4080
+ 'sld "Utility + Generator Backup"',
4081
+ 'UTIL = utility [voltage: "480V", label: "Utility"]',
4082
+ 'GEN = generator [rating: "500 kW", voltage: "480V"]',
4083
+ 'ATS1 = ats [rating: "800A"]',
4084
+ 'BUS1 = bus [voltage: "480V"]',
4085
+ 'CB1 = breaker [rating: "200A"]',
4086
+ 'L1 = load [label: "Critical Load"]',
4087
+ "UTIL -> ATS1",
4088
+ "GEN -> ATS1",
4089
+ "ATS1 -> BUS1",
4090
+ "BUS1 -> CB1",
4091
+ "CB1 -> L1"
4092
+ ],
3906
4093
  prefer: [
3907
- "Declare equipment as `id = nodeType [attrs]`, one `from -> to` connection per line.",
3908
- "Use only these canonical nodeTypes: sources utility, generator, solar, wind, ups; transformers transformer, transformer_dy, transformer_yd, transformer_yy, transformer_dd, autotransformer, transformer_3winding; buses bus, bus_tie, hub; switching breaker, breaker_vacuum, switch, switch_load, ground_switch, ats, recloser, sectionalizer, fuse, fuse_cl; protection ct, pt, relay, surge_arrester, ground_fault; loads motor, load, capacitor_bank, harmonic_filter, vfd; metering watthour_meter, demand_meter.",
3909
- "IEC/REBT residential aliases are also accepted: mcb/mccb\u2192breaker, rcd/rcbo/rccb\u2192ground_fault, isolator/disconnector\u2192switch_load, panel/consumer_unit/distribution_board\u2192bus."
4094
+ "Declare every node as `id = nodeType [attrs]` before any `->` connection references it.",
4095
+ "Use one `from -> to` edge per line \u2014 SLD does not support chained arrows like `a -> b -> c`.",
4096
+ 'Annotate nodes with `[label: "\u2026", rating: "\u2026", voltage: "\u2026"]`; annotate edges with `[cable: "\u2026", label: "\u2026"]`.'
3910
4097
  ],
3911
- avoid: ["Avoid generic flowchart node syntax."],
3912
- repair: ["An unrecognised nodeType renders as a flagged `?` placeholder; pick the closest canonical type or alias from the list above (e.g. meter\u2192watthour_meter, inverter\u2192vfd)."]
4098
+ avoid: [
4099
+ "Avoid nodeTypes not in the catalog \u2014 an unrecognised type renders as a flagged `?` placeholder; pick the closest canonical type or alias (meter->watthour_meter, inverter->vfd, isolator->switch_load).",
4100
+ "Avoid chained arrow syntax (`a -> b -> c`) \u2014 each connection must be its own line.",
4101
+ "Avoid using `[standard: iec]` with ANSI-only devices (`recloser`, `sectionalizer`, `watthour_meter`) \u2014 they have no IEC symbol and render with a warning."
4102
+ ],
4103
+ repair: [
4104
+ "'Connection references unknown node' -> declare the node with `id = nodeType` before the `->` line that names it.",
4105
+ "'Duplicate node id' -> each id must appear on exactly one `id = nodeType` line; rename the second occurrence.",
4106
+ "'Cannot parse line' -> every non-blank line must be the `sld` header, a node declaration (`id = type [attrs]`), or a connection (`id -> id [attrs]`).",
4107
+ "'unrecognised type' -> replace the unknown type with a catalog type or alias (e.g. `inverter` -> `vfd`)."
4108
+ ]
3913
4109
  },
3914
4110
  entity: {
3915
4111
  type: "entity",
3916
4112
  header: 'entity-structure "Title"',
3917
- mode: "legal entities + ownership edges",
4113
+ mode: "legal entity declarations + typed ownership edges",
4114
+ keywords: 'entity ID "Name" TYPE[@JX] [status:new|eliminated|modified tax:ccorp|passthrough|scorp|trust|disregarded|foundation|individual role:"\u2026" note:"\u2026"] \xB7 types: corp llc lp trust individual/person foundation disregarded/branch placeholder/tbf pool \xB7 edges: -> (ownership) ==> (voting-only) -.-> (pool/option) -~-> (license/management) --> (distribution) \xB7 edge attrs [class:"\u2026" label:"\u2026"] \xB7 percentage: "100%" or "V 75% / E 50%" \xB7 jurisdiction CODE "Name" [color:"#hex"] \xB7 cluster "Name" [members: [id1, id2], color:"#hex"]',
3918
4115
  forms: [
3919
- 'entity holdco "HoldCo" corp@US',
3920
- 'entity opco "OpCo" llc@DE',
3921
- "holdco -> opco : 100%",
3922
- 'cluster "Group Name" [members: [holdco, opco]]'
4116
+ 'entity parent "Acme Global, Inc." corp@US [note: "Ultimate Parent"]',
4117
+ 'entity ie-holdco "Acme Ireland Holdings" corp@IE',
4118
+ 'entity ie-ip "Acme IP Ltd" corp@KY',
4119
+ "parent -> ie-holdco : 100%",
4120
+ 'ie-ip -~-> ie-holdco [label: "IP License \xB7 royalty"]',
4121
+ 'jurisdiction US "United States" [color: "#3b82f6"]',
4122
+ 'cluster "Ireland / Cayman IP" [members: [ie-holdco, ie-ip]]'
3923
4123
  ],
3924
4124
  prefer: [
3925
- "Use `entity` declarations before ownership edges.",
3926
- "Keep legal form and jurisdiction explicit when known.",
3927
- 'Group entities with `cluster "Name" [members: [id1, id2]]` \u2014 the members value MUST be a bracketed list.'
4125
+ "Declare all `entity` nodes before any edge lines; the entity type goes after the quoted name (`corp`, `llc`, `lp`, `trust`, `individual`, `foundation`, `disregarded`, `pool`).",
4126
+ "Append `@JX` jurisdiction code directly to the type token (`corp@DE`, `trust@SD`) \u2014 no space; omit only when jurisdiction is unknown.",
4127
+ "Use the right edge operator for semantics: `->` ownership, `==>` voting-only control, `-~->` contractual (license/management), `-->` distribution, `-.->` option pool."
3928
4128
  ],
3929
- avoid: ["Avoid database schema terminology; use `erd` for tables and FKs."],
3930
- repair: ["Unknown ownership endpoints need entity declarations."]
4129
+ avoid: [
4130
+ "Don't use `fund` as an entity type \u2014 only canonical types are accepted; use `lp` for funds/LPs.",
4131
+ 'Don\'t put edge properties outside brackets: `[class: "Series A Pref"]`, not `: Series A Pref class`.',
4132
+ "Don't write `cluster` without a bracketed `[members: [...]]` list \u2014 the members value must be a bracket-enclosed comma list."
4133
+ ],
4134
+ repair: [
4135
+ "'Unknown entity type' -> replace with a canonical type: corp llc lp trust individual foundation disregarded placeholder pool.",
4136
+ "'Edge references unknown entity' -> add the `entity <id> \u2026` declaration before the edge line.",
4137
+ "'has no entities' -> the document needs at least one `entity` line."
4138
+ ]
3931
4139
  },
3932
4140
  fishbone: {
3933
4141
  type: "fishbone",
3934
4142
  header: 'fishbone "Title"',
3935
- mode: "effect + cause categories",
3936
- forms: ['effect "Late Delivery"', 'category process "Process"', 'process: "Handoff delay"'],
3937
- prefer: ["Use one effect and structured categories for generated DSL."],
3938
- avoid: ["Avoid mixing compact alien syntax when a structured category form works."],
3939
- repair: ["If a cause lands nowhere, add or reference its category explicitly."]
4143
+ mode: "effect + structured category ribs + cause lines",
4144
+ keywords: 'effect "\u2026" \xB7 category id "Label" [side:top|bottom order:N color:"#hex"] \xB7 catId : "cause text" \xB7 compact: category Label: cause1; cause2 \xB7 sub-cause: indent >=2 + "- text" \xB7 config direction = left|right \xB7 config sides = both|top|bottom \xB7 config density = compact|normal|spacious',
4145
+ forms: [
4146
+ 'fishbone "Manufacturing defect spike"',
4147
+ 'effect "Solder joint defect > 3%"',
4148
+ 'category man "Man"',
4149
+ 'category machine "Machine"',
4150
+ 'category method "Method"',
4151
+ 'man : "Operator training gaps"',
4152
+ 'machine : "Reflow oven calibration"',
4153
+ ' - "Temperature profile not updated"',
4154
+ 'method : "No incoming inspection"'
4155
+ ],
4156
+ prefer: [
4157
+ 'Declare each `category id "Label"` before referencing `id : "cause"` \u2014 the structured form keeps category ids unambiguous.',
4158
+ "Use `[side: top]` / `[side: bottom]` and `[order: N]` on a category to pin important categories (e.g. the 6M) to a specific rib instead of auto-alternating.",
4159
+ "For second-level sub-causes, indent >= 2 spaces and prefix with `- `; they attach to the last Level-1 cause above them."
4160
+ ],
4161
+ avoid: [
4162
+ "Don't reference a `catId :` cause before declaring that category \u2014 the parser throws `Unknown category`.",
4163
+ 'Don\'t omit `effect` \u2014 without an `effect` line the fish head falls back to the title; an explicit `effect "\u2026"` is clearer.',
4164
+ "Don't mix compact shorthand (`category Label: cause1; cause2`) and structured `catId :` lines for the same category \u2014 the compact id is a slug of the label, so a mismatch silently creates a duplicate."
4165
+ ],
4166
+ repair: [
4167
+ '\'Unknown category\' -> add `category X "Label"` before any `X : "cause"` line.',
4168
+ "'requires at least one' -> the document has no `category` lines; add at least one `category id \"Label\"`.",
4169
+ "'Sub-cause has no preceding Level-1 cause' -> a `- sub text` line appeared before any `catId : \"cause\"` line in that category; add a Level-1 cause above it."
4170
+ ]
3940
4171
  },
3941
4172
  venn: {
3942
4173
  type: "venn",
3943
4174
  header: 'venn "Title"',
3944
- mode: "declared-set region counts",
3945
- forms: ['set A "Group A"', 'set B "Group B"', "A & B : 120", "A only : 80"],
3946
- prefer: ["Use declared sets plus region counts for first-shot Venn output.", "Use Euler relations only when subset/disjoint structure is the request."],
3947
- avoid: ["Avoid mixing enumeration mode with explicit region counts."],
3948
- repair: ["Declare every set before region or Euler relation lines."]
4175
+ mode: "declared sets + region counts (primary); enumeration or Euler relations as alternates",
4176
+ keywords: 'set ID "Label" [color:"#hex"] \xB7 A & B : <integer|percent%|"text"|[list]> \xB7 A only : N \xB7 region A & B : N \xB7 enumeration: ID = { item, item, \u2026 } \xB7 Euler: ID subset|disjoint|overlap ID \xB7 layout venn|euler|auto \xB7 config: proportional=true|false \xB7 config: showCounts=true|false|auto \xB7 config: showPercent=true|false',
4177
+ forms: [
4178
+ 'venn "Customer Segments"',
4179
+ 'set email "Email subscribers" [color: "#1E88E5"]',
4180
+ 'set paid "Paid users" [color: "#E53935"]',
4181
+ 'set mobile "Mobile app users" [color: "#43A047"]',
4182
+ "email & paid : 1840",
4183
+ "email & mobile : 920",
4184
+ "email & paid & mobile : 650",
4185
+ "email only : 12400",
4186
+ "paid only : 3200"
4187
+ ],
4188
+ prefer: [
4189
+ 'Declare every `set ID "Label"` before any region line that references it.',
4190
+ "Use integer counts for research overlap, `%` values for audience overlap, and `[list]` values when each region should show enumerated items.",
4191
+ "Use the Euler relation form (`A subset B`, `A disjoint B`) only when the structural relationship matters more than counts \u2014 it triggers Euler layout mode."
4192
+ ],
4193
+ avoid: [
4194
+ "Don't mix enumeration mode (`ID = { \u2026 }`) with explicit `A & B :` region counts in the same document \u2014 enumeration auto-derives regions, so adding counts duplicates them.",
4195
+ "Don't use a set id in a region line that was never declared with `set ID \u2026` \u2014 the parser throws `unknown set id`.",
4196
+ "Don't add 5+ sets expecting a readable Venn \u2014 it degrades to an UpSet plot automatically; prefer Euler `disjoint`/`subset` when structure is sparse."
4197
+ ],
4198
+ repair: [
4199
+ "'unknown set id' -> declare `set X \"Label\"` before the region line that uses it.",
4200
+ "'duplicate set id' -> each set id must appear in exactly one `set` declaration; rename or remove the second.",
4201
+ "'could not parse line' -> every non-blank, non-header line must match `set`, `region`, `A & B :`, `A only :`, `ID = { }`, a Euler relation, `layout`, or `config:`."
4202
+ ]
3949
4203
  },
3950
4204
  flowchart: {
3951
4205
  type: "flowchart",
3952
- header: 'flowchart TD "Title"',
3953
- mode: "Mermaid-compatible nodes + edges",
3954
- forms: ["start([Start]) --> check{Decision?}", "check -->|Yes| done([Done])"],
3955
- prefer: ["Choose one direction in the header.", "Declare shapes explicitly when shape matters."],
3956
- avoid: ["Avoid subgraph complexity unless grouping is part of the request."],
3957
- repair: ["A bad header direction fails early; use TD, TB, BT, LR, or RL."]
4206
+ header: "flowchart TD",
4207
+ mode: "Mermaid-compatible nodes + edges (Mermaid prior: prefer this syntax)",
4208
+ keywords: 'flowchart TD|TB|BT|LR|RL \xB7 nodes: A[rect] A(round) A([stadium]) A{diamond} A[/parallelogram/] A[[subroutine]] A[(cylinder)] A((circle)) A{{hexagon}} A>asymmetric] \xB7 edges: --> --- -.-> ==> --x --o --|label| -- label --> \xB7 fan-out: A & B --> C & D \xB7 subgraph "Title" \u2026 end \xB7 class A,B name \xB7 classDef name fill:\u2026 \xB7 style A fill:\u2026',
4209
+ forms: [
4210
+ "flowchart LR",
4211
+ " start([New order received])",
4212
+ " start --> validate{Inventory available?}",
4213
+ " validate -->|Yes| reserve[Reserve items]",
4214
+ " validate -->|No| notify[Notify customer]",
4215
+ " reserve --> payment{Payment authorized?}",
4216
+ " payment -->|Yes| ship[Ship order]",
4217
+ " ship --> done([End])",
4218
+ " notify --> done"
4219
+ ],
4220
+ prefer: [
4221
+ "Use Mermaid `flowchart TD` (or `LR`) as the header \u2014 matches the dominant training prior; `graph TD` is also accepted.",
4222
+ "Label decision branches with `-->|Yes|` / `-->|No|` pipe syntax; use `--` inline for longer edge text: `A -- approved --> B`.",
4223
+ "Use `([\u2026])` stadium for terminals, `{\u2026}` diamond for decisions, `[\u2026]` rect for steps, `[(\u2026)]` cylinder for datastores."
4224
+ ],
4225
+ avoid: [
4226
+ "Don't omit the direction token \u2014 a bare `flowchart` defaults to TB; always declare direction explicitly.",
4227
+ "Avoid `subgraph` nesting deeper than one level in generated output; deep nesting is valid but rarely needed.",
4228
+ "Don't use inline `:::className` syntax in generated output \u2014 use separate `class A,B name` or `classDef` statements."
4229
+ ],
4230
+ repair: [
4231
+ "'expected' header -> the first non-comment line must be `flowchart TD` (or another direction); a missing or misspelled header rejects the document.",
4232
+ "'unknown direction' -> direction must be one of TD TB BT LR RL (case-insensitive).",
4233
+ "'expected node identifier' -> every edge must start with a valid id token; a bare `-->` without a left-hand node id is rejected."
4234
+ ]
3958
4235
  },
3959
4236
  mindmap: {
3960
4237
  type: "mindmap",
3961
4238
  header: "mindmap",
3962
- mode: "Markdown headings + bullets",
3963
- forms: ["# Root", "## Branch", "- Child item"],
3964
- prefer: ["Use exactly one `#` root heading.", "Use bullets/headings instead of graph edges."],
3965
- avoid: ["Avoid comments in the body."],
3966
- repair: ["An orphan branch means the root `#` heading is missing."]
4239
+ mode: "Markdown headings + bullets; %% directives for style/theme",
4240
+ keywords: "# root (exactly 1) \xB7 ## H2 branch \xB7 ### deeper headings \xB7 - / * / + bullet (2-space indent = 1 depth level) \xB7 %% style: map|logic-right|futureswheel|driver \xB7 %% theme: default|monochrome|dark \xB7 %% maxLabelWidth: N \xB7 inline: **bold** *italic* `code` [text](url) [x]/[ ] checkbox",
4241
+ forms: [
4242
+ "mindmap",
4243
+ "",
4244
+ "# Product Launch Plan",
4245
+ "",
4246
+ "## Market readiness",
4247
+ "- Competitive analysis",
4248
+ "- Pricing benchmarks",
4249
+ "",
4250
+ "## Engineering",
4251
+ "### Feature freeze",
4252
+ "- Core API complete",
4253
+ "",
4254
+ "## Go-to-market",
4255
+ "- Landing page live"
4256
+ ],
4257
+ prefer: [
4258
+ "Open with exactly one `# Root` heading \u2014 the parser recovers a placeholder when it is missing (with a warning), but an explicit `# Title` is correct.",
4259
+ "Use `%% style: logic-right` for outlines, the default `map` for balanced radial brainstorms, `%% style: futureswheel` for consequence mapping, `%% style: driver` for IHI diagrams.",
4260
+ "Use 2-space indented bullets (` - child`) to add depth under a heading without an extra `###` level \u2014 each 2-space step is one level deeper."
4261
+ ],
4262
+ avoid: [
4263
+ "Don't write more than one `#` root heading \u2014 the parser throws `multiple` center nodes not allowed on the second.",
4264
+ "Don't use graph-edge syntax (`A -> B`) inside a mindmap; the format is heading/bullet hierarchy only.",
4265
+ "Don't place `%%` directives mid-document; they are only processed before the first heading and are otherwise ignored."
4266
+ ],
4267
+ repair: [
4268
+ "'missing central topic' -> add a `# Your Topic` line as the first content line.",
4269
+ "'multiple' center nodes not allowed -> demote all but the first `#` heading to `##` or deeper.",
4270
+ "'no `# Title` heading found' (warning) -> the parser adopted the first line as the topic; add an explicit `# Title` at the top."
4271
+ ]
3967
4272
  },
3968
4273
  matrix: {
3969
4274
  type: "matrix",
3970
4275
  header: 'matrix "Title"',
3971
- mode: "quadrant scatter",
3972
- forms: ["x-axis: Low -> High", "y-axis: Low -> High", '"Item" at (0.25, 0.8)'],
3973
- prefer: ["Use quadrant scatter for first-shot prioritization/portfolio requests.", "Use built-in templates when the framework is named."],
3974
- avoid: ["Avoid heatmap, correlation, and table modes unless the user asks for that form."],
3975
- repair: ["Point coordinates are normalized fractions; keep them in `[0,1]`."]
4276
+ mode: "quadrant scatter (default) | named templates | heatmap | sipoc | qfd | punnett",
4277
+ keywords: 'header variants: matrix "Title" | matrix <template> "Title" | matrix heatmap NxM | matrix correlation | matrix sipoc | matrix qfd | matrix punnett \xB7 templates: eisenhower impact-effort rice bcg ansoff johari 9-box risk-matrix \xB7 quadrant: x-axis: Low -> High \xB7 y-axis: Low -> High \xB7 "Label" at (x,y) [size:N category:C shape:circle|square|triangle|diamond] \xB7 quadrant Q1..Q4 "name" \xB7 Q1:/"Q1: text" cell shortcuts \xB7 style: table \xB7 config: offChartPolicy=clamp-badge|drop bubbleScale=area|radius',
4278
+ forms: [
4279
+ 'matrix eisenhower "This Week"',
4280
+ "style: table",
4281
+ 'Q2: "Ship hotfix"',
4282
+ 'Q1: "Write Q3 OKRs"',
4283
+ 'Q3: "Reorganize Slack channels"',
4284
+ "",
4285
+ 'matrix bcg "Product Portfolio"',
4286
+ '"Platform SDK" at (0.8, 0.8) size: 5 category: star',
4287
+ '"Legacy API" at (0.85, 0.15) size: 4 category: cashcow'
4288
+ ],
4289
+ prefer: [
4290
+ "Use a named template (`eisenhower`, `impact-effort`, `bcg`, `ansoff`, `johari`, `9-box`, `risk-matrix`, `rice`) for first-shot generation \u2014 it pre-fills axes and quadrant labels.",
4291
+ "Add `style: table` with `Q1:`/`Q2:`/`Q3:`/`Q4:` item lines for the four-cell list layout instead of a scatter.",
4292
+ "Quadrant scatter coordinates are normalized `[0,1]` fractions; add `size: N` for a bubble chart and `category:` to drive legend color."
4293
+ ],
4294
+ avoid: [
4295
+ "Don't mix `sipoc:`/`qfd:`/`punnett:` sub-keywords in plain quadrant mode \u2014 they activate only under the matching header mode (`matrix sipoc`).",
4296
+ "Don't set point coordinates outside `[0,1]` expecting the canvas to expand \u2014 they are clamped and badged (use `offChartPolicy: drop` to hide).",
4297
+ "Don't use percentages or raw data values in `at (x, y)` \u2014 coordinates are fractions."
4298
+ ],
4299
+ repair: [
4300
+ "A point that doesn't appear usually has coordinates outside `[0,1]` with `offChartPolicy` defaulting to `clamp-badge` \u2014 check `at (x, y)` are fractions.",
4301
+ 'An empty `qfd`/`sipoc` chart means the header was `matrix "Title"` (quadrant mode) not `matrix qfd "Title"` \u2014 add the mode keyword after `matrix`.',
4302
+ "In `punnett` mode, a missing grid means the `cross:` line was not parsed \u2014 use even-length letter pairs (e.g. `Bb`) separated by `x`."
4303
+ ]
3976
4304
  },
3977
4305
  orgchart: {
3978
4306
  type: "orgchart",
3979
4307
  header: 'orgchart "Title"',
3980
- mode: "indented hierarchy",
3981
- forms: ['ceo: "Name" | CEO', ' cto: "Name" | CTO', ' eng: "Name" | Engineer'],
3982
- prefer: ["Use indentation for the reporting tree.", "Use dotted explicit edges only for matrix reporting."],
3983
- avoid: ["Avoid mixing explicit report edges with indentation unless needed."],
3984
- repair: ["Duplicate IDs and unknown explicit edge endpoints are parse failures."]
4308
+ mode: "indented hierarchy with pipe-separated fields; explicit edges for matrix/dotted lines",
4309
+ keywords: 'id : "Name" | Title | Dept [props] \xB7 node kinds: person role open draft tbh advisor external \xB7 props: role(ceo|cto|cfo|coo|engineer|designer|sales|hr|legal|ops|product|data|advisor|intern|vacant) status(new|leaving|on-leave) assistant-of \xB7 edge ops: -> (report) -.-> (matrix/dotted) === (co-leaders) \xB7 config: direction=TD|LR layout=tree|list',
4310
+ forms: [
4311
+ 'ceo: "Jamie Torres" | CEO [role: ceo]',
4312
+ ' cto: "Raj Patel" | CTO [role: cto]',
4313
+ ' eng1: "Priya Nair" | Eng Lead | Engineering [role: engineer]',
4314
+ ' cfo: "Ellen Wu" | CFO [role: cfo]',
4315
+ 'advisor adv1: "Dr. Alan Ford" | Board Advisor [role: advisor]',
4316
+ "pm_core -.-> lead_core"
4317
+ ],
4318
+ prefer: [
4319
+ 'Use 2-space indentation per level for the reporting tree \u2014 this is the implicit `->` edge. Fields are `id : "Name" | Title | Department`, pipe-separated.',
4320
+ "Use node-kind prefixes `role`/`open` for vacant positions (dashed yellow + HIRING pill), `draft`/`tbh` for planned roles, `advisor`/`external` for contractors.",
4321
+ "Add `[role: ceo]` (cto/cfo/engineer/designer/sales/hr/legal/ops/product/data/advisor/intern/vacant, case-insensitive) for the built-in role icon; draw dotted matrix lines as explicit `a -.-> b` edges."
4322
+ ],
4323
+ avoid: [
4324
+ "Don't mix indentation hierarchy with an explicit `->` edge on the same parent\u2013child pair \u2014 only one is needed; both duplicate the report edge.",
4325
+ 'Don\'t omit the colon in node lines (`id : "Name"`); a line without a colon is silently skipped with a warning.',
4326
+ "Don't reuse an id \u2014 the duplicate is dropped silently, losing its name/title."
4327
+ ],
4328
+ repair: [
4329
+ "'skipped a line that is neither a node nor an edge' -> the line is missing a colon; rewrite as `id : \"Name\" | Title`.",
4330
+ "'kept the first declaration of duplicate node id' -> each person needs a unique id; rename the second declaration.",
4331
+ "'created node(s) from edges that were never declared' -> declare `x : \"Name\" | Title` so the card renders a real label instead of the bare id."
4332
+ ]
3985
4333
  },
3986
4334
  decisiontree: {
3987
4335
  type: "decisiontree",
3988
4336
  header: 'decisiontree "Title"',
3989
- mode: "taxonomy questions",
3990
- forms: ['question "Question?"', ' yes: answer "Outcome"', ' no: answer "Other outcome"'],
3991
- prefer: ["Use taxonomy mode for troubleshooting and triage.", "Select `decisiontree:decision` or `decisiontree:ml` only when expected value or ML is explicit."],
3992
- avoid: ["Avoid mixing mode-specific keywords."],
3993
- repair: ["Indent each child under its parent and use the branch prefix required by the selected mode."]
4337
+ mode: "taxonomy (default) | decision | ml | influence \u2014 select via header suffix",
4338
+ keywords: 'header: decisiontree[:decision|:ml|:influence] "Title" \xB7 config: direction=top-down|left-right \xB7 taxonomy: question/q "\u2026" \xB7 answer/a/leaf "\u2026" \xB7 branch prefixes yes: no: label "X": \xB7 decision mode: decision chance end/outcome \xB7 choice "label" prob N \xB7 payoff=N \xB7 ml mode: split "\u2026" feature=F threshold=V gini/entropy/mse=N value=[N,\u2026] class=C \xB7 influence mode: decision chance value nodes + directed arcs',
4339
+ forms: [
4340
+ 'decisiontree "Customer Support Triage"',
4341
+ "direction: top-down",
4342
+ "",
4343
+ 'question "Is the service completely down?"',
4344
+ ' yes: question "Outage confirmed on status page?"',
4345
+ ' yes: answer "Follow incident protocol \u2014 page on-call"',
4346
+ ' no: answer "Check monitoring \u2014 open severity-1 ticket"',
4347
+ ' no: answer "Escalate to billing team"'
4348
+ ],
4349
+ prefer: [
4350
+ "Default mode is `taxonomy` \u2014 use `question`/`answer` (or `q`/`a`) with `yes:`/`no:` branch prefixes and 2-space indentation.",
4351
+ 'For decision analysis use `decisiontree:decision`; nodes are `decision` (square), `chance` (circle), `end` (triangle); label choices with `choice "label"` and probabilities with `prob N` on chance children; `payoff=N` on `end` nodes.',
4352
+ "For ML visualization use `decisiontree:ml`; split nodes carry `feature=`/`threshold=`/`gini=`; leaves carry `value=[N,\u2026]` plus optional `class=`."
4353
+ ],
4354
+ avoid: [
4355
+ "Don't use `decision`/`chance`/`end` in taxonomy mode \u2014 they throw `Unknown taxonomy node kind`.",
4356
+ "Don't use `question`/`answer` in `:ml` mode \u2014 they throw `Unknown ML node kind`.",
4357
+ "Don't let a chance node's `prob` children sum to anything but 1 \u2014 the parser throws `probabilities do not sum to 1.0`."
4358
+ ],
4359
+ repair: [
4360
+ "'Unknown node kind' -> valid decision-mode kinds are `decision`, `chance`, `end`, `outcome`; switch mode or fix the keyword.",
4361
+ "'Unknown taxonomy node kind' -> valid taxonomy kinds are `question`/`q` and `answer`/`a`/`leaf`.",
4362
+ "'probabilities do not sum to 1.0' -> the `prob N` values on a chance node's children must add to exactly 1.0.",
4363
+ "'Orphan line (bad indent)' -> every non-root node must be indented at least 2 spaces under its parent."
4364
+ ]
3994
4365
  },
3995
4366
  timeline: {
3996
4367
  type: "timeline",
3997
4368
  header: 'timeline "Title"',
3998
- mode: "dated events",
3999
- forms: ['2026-05-22: "Event"', '2026-06-01 - 2026-06-30: "Phase"', '2026-07-01: milestone "Launch"'],
4000
- prefer: ["Use dated events/ranges and `milestone` for important points."],
4001
- avoid: ["Avoid custom row keys when a date is known."],
4002
- repair: ["Quote labels and keep the colon after the date/range."]
4369
+ mode: "dated events on a time axis \u2014 swimlane (default), gantt, or lollipop styles",
4370
+ keywords: 'DATE: "label" \xB7 DATE - DATE: "label" (range) \xB7 DATE: milestone "label" \xB7 era DATE - DATE: "label" \xB7 track "Name": / section Name (lanes) \xB7 config: style=swimlane|gantt|lollipop orientation=horizontal|vertical scale=proportional|equidistant|log \xB7 point props [side:above|below shape:circle|square|diamond|star|flag color:#hex category:\u2026] \xB7 dates: YYYY YYYY-MM YYYY-MM-DD, BC years, ordinal keys (Phase 1, Q1 2024)',
4371
+ forms: [
4372
+ 'timeline "Platform v2 Launch"',
4373
+ "config: style = gantt",
4374
+ "",
4375
+ '2025-07-01 - 2025-08-15: "Engineering build" [category: "engineering"]',
4376
+ '2025-08-20: milestone "Feature freeze" [color: #E53935]',
4377
+ '2025-09-15: milestone "Public launch" [color: #2E7D32]'
4378
+ ],
4379
+ prefer: [
4380
+ "Use ISO dates (`YYYY-MM-DD`/`YYYY-MM`/`YYYY`) for the row key for proportional layout; for non-date groupings (Phase 1, Q1 2024) use an ordinal key placed in declaration order.",
4381
+ "Use `config: style = gantt` for roadmaps (overlapping bars), `lollipop` for milestone stories, default `swimlane` for multi-track streams.",
4382
+ 'Use `milestone` before the quoted label for diamond headline events; group events with `track "Name":` or Mermaid `section Name`; add era bands with `era START - END: "label"`.'
4383
+ ],
4384
+ avoid: [
4385
+ "Don't omit the colon after the date/range \u2014 `Expected ':' after date` is thrown when no colon separator is found.",
4386
+ "Don't use `config: style = X` with a value other than `swimlane`, `gantt`, `lollipop` \u2014 throws `Invalid style`.",
4387
+ "Don't write `era` with a single date instead of a range \u2014 throws `era requires a date range`."
4388
+ ],
4389
+ repair: [
4390
+ "'Unrecognized line' -> an event line must be a date/range, then `:`, then a quoted label (optionally preceded by `milestone`).",
4391
+ "'Invalid style' -> valid `config: style` values are `swimlane`, `gantt`, `lollipop`.",
4392
+ "'era requires a date range' -> write `era 2020 - 2025: \"label\"` (two dates separated by ` - `)."
4393
+ ]
4003
4394
  },
4004
4395
  state: {
4005
4396
  type: "state",
4006
4397
  header: "stateDiagram-v2",
4007
- mode: "Mermaid stateDiagram-v2 (recommended for generation)",
4398
+ mode: "Mermaid stateDiagram-v2 (recommended; native 'state' header also accepted)",
4399
+ keywords: 'stateDiagram-v2 (or state "Title" [direction: LR]) \xB7 [*] --> S / S --> [*] \xB7 S --> T : trigger [guard] / action \xB7 state "Long name" as ID \xB7 state ID <<choice>> | <<fork>> | <<join>> | <<end>> \xB7 composite state ID { \u2026 } with -- for concurrent regions \xB7 note right_of X : text \xB7 direction TB|LR \xB7 native pseudo-states: initial final choice junction fork join history dhistory terminate entry_point exit_point',
4008
4400
  forms: [
4009
- "[*] --> Idle",
4010
- "Idle --> Running : start",
4011
- "Running --> Done : finish",
4012
- "Done --> [*]"
4401
+ "stateDiagram-v2",
4402
+ " [*] --> Idle",
4403
+ " Idle --> Running : start",
4404
+ " Running --> Paused : pause",
4405
+ " Paused --> Running : resume",
4406
+ " Running --> Done : finish",
4407
+ " Done --> [*]"
4013
4408
  ],
4014
4409
  prefer: [
4015
- "Use Mermaid `stateDiagram-v2` syntax: `[*]` for the start/end pseudo-states, `-->` for transitions, `: label` for the event/guard.",
4016
- 'This matches the most common training data, so prefer it over the native `state "Title"` + `initial`/`final` form (which is also accepted).'
4410
+ 'Use Mermaid `stateDiagram-v2` with `[*]` for initial/final pseudo-states and `-->` for transitions \u2014 matches the dominant training prior and is less error-prone than the native `state "Title"` + `initial`/`final` form.',
4411
+ "Write transition labels as `: trigger [guard] / action` \u2014 all three segments optional, but the colon is required if any label follows the arrow.",
4412
+ "For composite states use `state ID { \u2026 }` (Mermaid) or `composite ID { \u2026 }` (native); separate concurrent regions with `--` inside the block."
4017
4413
  ],
4018
4414
  avoid: [
4019
- "Avoid composite/concurrent-state syntax until the request needs it.",
4020
- "Do not mix the two styles in one file (e.g. `[*]` together with `initial X`); pick `[*]`."
4415
+ "Don't mix the two header styles \u2014 `[*]` (Mermaid) and `initial X` (native) are both accepted but must not appear together.",
4416
+ "Don't invent pseudo-state kinds \u2014 valid are `initial`/`final`/`choice`/`junction`/`fork`/`join`/`history`/`dhistory`/`terminate`/`entry_point`/`exit_point` and `<<choice>>`/`<<fork>>`/`<<join>>`/`<<end>>`.",
4417
+ "Don't leave a composite `{` block unclosed \u2014 an unclosed brace is a hard parse error."
4021
4418
  ],
4022
4419
  repair: [
4023
- "Every transition uses `-->`; place at least one named state between a `[*]` start and a `[*]` end.",
4024
- 'If the header is rejected, use exactly `stateDiagram-v2` (or `state "Title"`).'
4420
+ "'Expected' header -> the first non-comment line must be `stateDiagram-v2`, `stateDiagram`, or `state`.",
4421
+ "'Unparseable line' -> check for an unsupported pseudo-state keyword, a missing `-->` arrow, or a line that is neither a transition, declaration, nor directive.",
4422
+ "'Unclosed composite block' -> every `state ID {` or `composite ID {` must be closed with a matching `}`."
4025
4423
  ]
4026
4424
  },
4027
4425
  pid: {
4028
4426
  type: "pid",
4029
4427
  header: 'pid "Title"',
4030
- mode: "equipment + process lines",
4428
+ mode: "equipment + process/signal lines + instrument declarations",
4429
+ keywords: 'pid "title" [direction: LR|TB] \xB7 equip ID : type [tag:"\u2026"] \xB7 line ID from A[.port] to B[.port] [type: process|process_minor|pneumatic|electric|hydraulic|capillary|software|mechanical, size:"\u2026", service:"\u2026"] \xB7 inst TAG : category + indented measures EQUIP_OR_LINE / controls EQUIP \xB7 equipment: tank_atm tank_cone_roof vessel_v vessel_h sphere column_tray column_packed hx_shell_tube hx_air_cooled reboiler condenser pump_centrifugal pump_pd compressor blower reactor_cstr reactor_pfr filter cyclone flare cooling_tower valve_gate valve_ball valve_globe valve_butterfly valve_check valve_control valve_psv \xB7 instrument categories: field_discrete field_shared field_computer field_plc cr_discrete cr_shared cr_computer cr_plc local_discrete local_shared',
4031
4430
  forms: [
4032
- "equip T-101 : tank_atm",
4033
- "equip P-101 : pump_centrifugal",
4034
- "line L1 from T-101.bottom to P-101.in",
4035
- "inst FIC-201 : cr_shared"
4431
+ 'pid "Water Pump Flow Control"',
4432
+ 'equip T-101 : tank_atm [tag: "Feed Tank"]',
4433
+ 'equip P-101 : pump_centrifugal [tag: "Feed Pump"]',
4434
+ "equip V-101 : valve_control",
4435
+ 'line L1 from T-101.bottom to P-101.in [service: "water", type: "process"]',
4436
+ 'line L2 from P-101.out to V-101.in [type: "process"]',
4437
+ "inst FT-101 : field_discrete",
4438
+ " measures L2",
4439
+ "inst FIC-101 : cr_shared",
4440
+ " controls V-101",
4441
+ 'line s1 from FT-101 to FIC-101 [type: "electric"]',
4442
+ 'line s2 from FIC-101 to V-101 [type: "pneumatic"]'
4036
4443
  ],
4037
4444
  prefer: [
4038
- "Declare equipment first (`equip ID : type [attrs]`), then process/signal lines, then instruments (`inst TAG : category`).",
4039
- "Use only these canonical equipment types: tanks/vessels tank_atm, tank_cone_roof, vessel_v, vessel_h, sphere; columns column_tray, column_packed; heat transfer hx_shell_tube, hx_air_cooled, reboiler, condenser; rotating pump_centrifugal, pump_pd, compressor, blower; reactors reactor_cstr, reactor_pfr; misc filter, cyclone, flare, cooling_tower; valves valve_gate, valve_ball, valve_globe, valve_butterfly, valve_check, valve_control, valve_psv.",
4040
- "Instrument categories: field_discrete, field_shared, field_computer, field_plc, cr_discrete, cr_shared, cr_computer, cr_plc, local_discrete, local_shared. Line types (`[type: \u2026]`): process, process_minor, pneumatic, electric, hydraulic, capillary, software, mechanical."
4445
+ "Declare `equip` first, then `line` connections, then `inst` bubbles. Use ISA tag conventions (`FT-101` flow transmitter, `FIC-101` flow indicating controller).",
4446
+ "Use the indented `measures LINE_OR_EQUIP` and `controls EQUIP` continuations under an `inst` to declare loop membership.",
4447
+ "Use canonical `[type: \u2026]` line types: `electric` for transmitter->controller, `pneumatic` for controller->control-valve, `software` for DCS/PLC bus."
4041
4448
  ],
4042
- avoid: ["Avoid SLD electrical nodes (transformer, breaker) in a P&ID."],
4043
- repair: ["An unrecognised equipment type renders as a flagged `?` placeholder; pick the closest canonical type from the list above (e.g. exchanger/heat_exchanger\u2192hx_shell_tube, vessel_horizontal\u2192vessel_h, cstr\u2192reactor_cstr)."]
4449
+ avoid: [
4450
+ "Avoid SLD electrical nodes (`transformer`, `breaker`, `bus`) inside a P&ID \u2014 use `sld` for electrical single-lines.",
4451
+ "Don't invent equipment types; an unrecognised type renders as a flagged placeholder (use `hx_shell_tube` not `heat_exchanger`, `vessel_h` not `vessel_horizontal`, `reactor_cstr` not `cstr`).",
4452
+ "Don't omit `[type: electric]` on a transmitter->controller line or `[type: pneumatic]` on a controller->control-valve line \u2014 both default to `process` pipe with a warning."
4453
+ ],
4454
+ repair: [
4455
+ "'Unparseable line' -> every body line must start with `equip`, `line`, `inst`, `measures`, or `controls`.",
4456
+ "'unrecognised type' -> replace the unknown equipment type with a catalog type (exchanger->hx_shell_tube, vessel_horizontal->vessel_h, cstr->reactor_cstr).",
4457
+ "'no signal path to a controller' -> add a signal line from the transmitter to a controller (C in the tag, e.g. FIC), or add the `controls` continuation under the controller."
4458
+ ]
4044
4459
  },
4045
4460
  erd: {
4046
4461
  type: "erd",
4047
4462
  header: "erDiagram",
4048
4463
  mode: "Mermaid erDiagram (recommended for generation)",
4464
+ keywords: 'erDiagram (Mermaid) \xB7 ENTITY { type name PK|FK|UK } \xB7 A ||--o{ B : label \xB7 glyphs: || one-mandatory \xB7 |o/o| zero-or-one \xB7 }|/|{ one-many \xB7 }o/o{ zero-many \xB7 erd (native) \xB7 table NAME { name type PK|FK|UK|NN } \xB7 ref A <card> -- <card> B : "label" \xB7 named cards: one-mandatory one-optional many-mandatory many-optional \xB7 notation: crowsfoot (v0.1) \xB7 direction: LR|TB',
4049
4465
  forms: [
4050
4466
  "CUSTOMER ||--o{ ORDER : places",
4051
4467
  "ORDER {",
@@ -4054,83 +4470,284 @@ var PROFILES = {
4054
4470
  "}"
4055
4471
  ],
4056
4472
  prefer: [
4057
- "Use Mermaid `erDiagram` syntax: relationships `A <card>--<card> B : label` with crow's-foot glyphs (`||` one, `o{` zero-or-many, `|{` one-or-many, `|o` zero-or-one); entity blocks `NAME { type name KEY }` with attributes **type-first** and KEY \u2208 PK/FK/UK.",
4058
- "Entities are auto-created from relationships; you only need a `{ \u2026 }` block to list attributes.",
4059
- "This matches the dominant training-data prior. The native `erd` header with `table NAME { name type PK }` + `ref \u2026 many-mandatory -- one-mandatory \u2026` is also accepted."
4473
+ "Use the Mermaid `erDiagram` header; entities auto-create from relationship lines and only need a `{ \u2026 }` block to list attributes.",
4474
+ "Attributes are type-first under `erDiagram`: `int id PK`, `varchar email UK` \u2014 never name-first.",
4475
+ "Relationship syntax is `A <left-glyph>--<right-glyph> B : label`; use `||--o{` for one-to-many-optional, `||--|{` for one-to-many-mandatory."
4476
+ ],
4477
+ avoid: [
4478
+ "Do not mix header styles: under `erDiagram` attrs are type-first; under native `erd` they are name-first with `table` blocks and `ref` lines.",
4479
+ "Do not write `notation: chen` or `notation: barker` \u2014 only `crowsfoot` is implemented in v0.1.",
4480
+ "Do not invent glyph pairs; left glyphs are `||` `|o` `}o` `}|`, right glyphs are `||` `o|` `o{` `|{`."
4060
4481
  ],
4061
- avoid: ["Do not mix the two header styles; under `erDiagram`, attributes are type-first (`int id PK`), not name-first."],
4062
4482
  repair: [
4063
- "Crow's-foot glyph pairs must be valid (`||`, `|o`, `}o`, `}|` on the left; `||`, `o|`, `o{`, `|{` on the right).",
4064
- "If the header is rejected, use exactly `erDiagram` (or native `erd`)."
4483
+ "'Invalid Mermaid cardinality glyph' -> the left glyph must be `||`/`|o`/`}o`/`}|` and the right `||`/`o|`/`o{`/`|{`; fix the pair.",
4484
+ "'Unrecognized erDiagram line' -> only relationship lines, entity blocks (`NAME {`), and `}` are valid under `erDiagram`; remove native `table`/`ref` lines.",
4485
+ "'not yet implemented in v0.1; use' -> drop the `notation:` line or set `notation: crowsfoot`."
4065
4486
  ]
4066
4487
  },
4067
4488
  breadboard: {
4068
4489
  type: "breadboard",
4069
4490
  header: "breadboard",
4070
- mode: "parts + wires blocks",
4071
- forms: ['title: "Prototype"', "parts", "wires"],
4072
- prefer: ["Use physical parts and addressable tie points.", "Keep schematic tasks on `circuit` instead."],
4073
- avoid: ["Avoid abstract netlist syntax in breadboard output."],
4074
- repair: ["Missing parts or invalid breadboard connection endpoints need the breadboard syntax reference."]
4491
+ mode: "parts section + wires section; breadboard-native hole/rail coordinates",
4492
+ keywords: 'breadboard \xB7 board: mini|half|full \xB7 title: "\u2026" \xB7 parts section: id: KIND [args] @placement \xB7 wire kinds: resistor led cap-elec cap-ceramic diode button dip header \xB7 mcu uno|nano|esp32|pico \xB7 sensor hcsr04|dht11|dht22 \xB7 display oled-ssd1306|lcd-1602-i2c \xB7 actuator servo-sg90 \xB7 placement: @5e (hole) @5e..9e (span) @+t8 @-t8 @+b14 @-b14 (rails) @beside-left @beside-right @above @below \xB7 wires section: ep --color-- ep [via @coord] \xB7 colors: red black blue yellow orange green white purple brown grey \xB7 endpoints: @coord or partId:pin',
4493
+ forms: [
4494
+ "breadboard",
4495
+ "board: half",
4496
+ 'title: "Blink LED \u2014 Arduino Uno"',
4497
+ "parts",
4498
+ " uno: mcu uno @beside-left",
4499
+ " r1: resistor 220 @5e..9e",
4500
+ " d1: led red @10e..10f",
4501
+ "wires",
4502
+ " uno:5V --red-- @+t1",
4503
+ " uno:GND --black-- @-t1",
4504
+ " uno:D13 --yellow-- @9a"
4505
+ ],
4506
+ prefer: [
4507
+ "Declare every part in the `parts` section before referencing it in `wires`; the part `id` is what you use as `partId:pin` in wire endpoints.",
4508
+ "Use breadboard-native coordinates: `@5e` for hole column 5 row e, `@5e..9e` for a span, `@+t8`/`@-t8` for top positive/negative rail.",
4509
+ "Wire color carries convention \u2014 `red` for +V, `black`/`blue` for GND, signal colors (`yellow`, `orange`, `green`) for data; all wire lines are `ep --color-- ep`."
4510
+ ],
4511
+ avoid: [
4512
+ "Don't use rail coordinates (`@+t8`) on a `board: mini` \u2014 mini boards have no power rails and the parser throws `Mini boards have no power rails`.",
4513
+ "Don't omit the `@` placement from a part \u2014 `missing placement` is thrown when the `@` token is absent.",
4514
+ "Don't reference a part id in wires that was not declared in the parts section \u2014 `Wire references unknown part` fails validation."
4515
+ ],
4516
+ repair: [
4517
+ "'Unknown board' -> set `board:` to `mini`, `half`, or `full`.",
4518
+ "'Unknown part kind' -> use a supported kind (`resistor` `led` `cap-elec` `diode` `button` `dip` `header`) or a compound (`mcu uno`, `sensor hcsr04`, `display oled-ssd1306`).",
4519
+ "'Wire references unknown part' -> add `X: KIND @placement` to the `parts` section before `wires`."
4520
+ ]
4075
4521
  },
4076
4522
  bpmn: {
4077
4523
  type: "bpmn",
4078
4524
  header: "bpmn",
4079
- mode: "pool/lane objects + flows",
4080
- forms: ['pool "Service" {', ' lane "Worker" { A: start "Request" }', "flows", "A --> B"],
4081
- prefer: ["Use pools, lanes, and a `flows` block.", "Use `~~>` only for cross-pool message flow."],
4082
- avoid: ["Avoid generic flowchart syntax for BPMN semantics."],
4083
- repair: ["Sequence flows cannot cross pools; switch to message flow when needed."]
4525
+ mode: "pool/lane declarations + flow objects + flows block",
4526
+ keywords: 'bpmn [direction: LR|TB] [title: "\u2026"] \xB7 pool "Label" { \u2026 } \xB7 pool "Label" blackbox \xB7 lane "Label" { \u2026 } \xB7 id: start|intermediate|end [message|timer] ["label"] \xB7 id: task [user|service|send|receive|manual|script] "label" \xB7 id: subprocess "label" [collapsed] \xB7 id: gateway xor|or|and|event ["label"] \xB7 flows \xB7 A --> B (sequence) \xB7 G --? "cond" --> B (conditional) \xB7 G --* "default" --> B (default) \xB7 "Pool" ~~> id : "label" (message flow)',
4527
+ forms: [
4528
+ "bpmn",
4529
+ "direction: LR",
4530
+ 'title: "Loan Application Approval"',
4531
+ 'pool "Bank" {',
4532
+ ' lane "Clerk" {',
4533
+ ' A: start "Application received"',
4534
+ ' B: task user "Check completeness"',
4535
+ ' G1: gateway xor "Complete?"',
4536
+ " }",
4537
+ ' lane "Underwriter" {',
4538
+ ' C: task service "Risk score"',
4539
+ ' E: end "Approved"',
4540
+ ' F: end "Rejected"',
4541
+ " }",
4542
+ "}",
4543
+ "flows",
4544
+ "A --> B",
4545
+ "B --> G1",
4546
+ 'G1 --? "yes" --> C',
4547
+ 'G1 --* "no" --> F',
4548
+ "C --> E"
4549
+ ],
4550
+ prefer: [
4551
+ 'Declare flow objects inside `lane "Label" { id: kind \u2026 }` nested in `pool "Label" { \u2026 }`. The `id:` prefix before the keyword is required; labels must be quoted.',
4552
+ "Event kinds `start`/`intermediate`/`end` with optional trigger `message`/`timer`; task markers `user`/`service`/`send`/`receive`/`manual`/`script`; gateway kinds `xor`/`or`/`and`/`event`.",
4553
+ "Put all flows in a single `flows` block. Sequence flow `-->` stays inside one pool; use `~~>` only for cross-pool message flow; `--?` is a conditional branch with one `--*` default per gateway."
4554
+ ],
4555
+ avoid: [
4556
+ "Don't use `-->` across pool boundaries \u2014 it is rejected with `crosses pool boundary \u2014 use message flow`.",
4557
+ "Don't add lanes or tasks inside a `blackbox` pool \u2014 rejected with `black-box pool` cannot contain flow objects.",
4558
+ "Don't give a `task` without a quoted label, and don't put more than one `--*` default flow on the same gateway."
4559
+ ],
4560
+ repair: [
4561
+ "'crosses pool boundary \u2014 use message flow' -> replace `-->` with `~~>` and ensure source/target are in different pools.",
4562
+ "'unknown flow-object kind' -> the word after `id:` must be `start`, `end`, `intermediate`, `task`, `subprocess`, or `gateway`.",
4563
+ "'kind must be xor / or / and / event' -> a gateway declaration needs one of those four kinds after the `gateway` keyword."
4564
+ ]
4084
4565
  },
4085
4566
  fbd: {
4086
4567
  type: "fbd",
4087
4568
  header: 'fbd "Title"',
4088
- mode: "networks + blocks",
4089
- forms: ["var Start: bool", 'network "Logic":', "Motor = AND(Start, Safe)"],
4090
- prefer: ["Use network/expression forms from the FBD syntax guide."],
4091
- avoid: ["Avoid ladder rung syntax in FBD output."],
4092
- repair: ["Invalid block calls need a supported block and valid arguments."]
4569
+ mode: "variable declarations + numbered networks of indented assignment statements",
4570
+ keywords: 'var NAME: TYPE [= init] \xB7 scopes: var var_input var_output var_in_out var_global var_external \xB7 types: bool int dint uint real lreal time string byte word dword timer counter \xB7 network [N] ["title"]: \xB7 Inst = BLOCK(args) \xB7 negation: ~varName \xB7 named args: PORT: value \xB7 nested: BLOCK(BLOCK(\u2026)) \xB7 port ref: Inst.PORT \xB7 boolean: AND OR NOT NAND NOR XOR XNOR BUF \xB7 edge: R_TRIG F_TRIG \xB7 bistable: SR RS \xB7 timers: TON TOF TP \xB7 counters: CTU CTD \xB7 math: ADD SUB MUL DIV MOD ABS NEG MOVE \xB7 compare: EQ NE GT GE LT LE \xB7 select: SEL MUX MAX MIN LIMIT',
4571
+ forms: [
4572
+ 'fbd "Motor Control"',
4573
+ "var Start: bool",
4574
+ "var Stop: bool",
4575
+ "var MotorOut: bool",
4576
+ "var Latch: bool",
4577
+ 'network 0 "Start latch":',
4578
+ " Latch = OR(Start, AND(Latch, ~Stop))",
4579
+ 'network 1 "Drive output":',
4580
+ " MotorOut = MOVE(Latch)"
4581
+ ],
4582
+ prefer: [
4583
+ "Declare variables with `var NAME: TYPE` before any `network` line; use `~varName` on an input to place a negation bubble inline instead of a `NOT` block.",
4584
+ 'Each `network [N] ["title"]:` header is followed by indented `Inst = BLOCK(args)` body lines until the next `network`.',
4585
+ "Variadic blocks (`AND`, `OR`, `ADD`, `MUL`, `MAX`, `MIN`) accept any number of positional args and expand `IN1..INn` automatically."
4586
+ ],
4587
+ avoid: [
4588
+ "Don't use ladder element names (`XIC`, `OTE`, ladder-style `TON`) inside FBD networks \u2014 they are not valid FBD blocks.",
4589
+ "Don't reference an instance port (`Inst.PORT`) before that instance is assigned in the same network.",
4590
+ "Don't use ladder comparison aliases \u2014 FBD uses `EQ NE GT GE LT LE`, not `EQU NEQ GRT LES GEQ LEQ`."
4591
+ ],
4592
+ repair: [
4593
+ "'Unknown function block' -> replace with the closest IEC standard block (e.g. `EQUAL` -> `EQ`, `GREATER` -> `GT`, `TIMER_ON` -> `TON`).",
4594
+ "'Duplicate instance name' -> each `Inst =` assignment in a program must use a unique left-hand name.",
4595
+ "'Unrecognized network statement' -> the line must match `Inst = BLOCK(args)` or a bare `BLOCK(args)`; check for missing parentheses.",
4596
+ "'Too many positional arguments' -> drop excess args or switch to named-port form `PORT: value`."
4597
+ ]
4093
4598
  },
4094
4599
  sfc: {
4095
4600
  type: "sfc",
4096
4601
  header: 'sfc "Title"',
4097
- mode: "steps + transitions",
4098
- forms: ["step Idle [initial]", "transition Idle -> Run : Start", "step Run"],
4099
- prefer: ["Use explicit steps and transitions before alternative/simultaneous branches."],
4100
- avoid: ["Avoid UML state syntax in SFC output."],
4101
- repair: ["Every transition step reference must resolve to a declared step."]
4602
+ mode: "steps with indented actions + from:/to: transitions",
4603
+ keywords: 'step ID [initial|final|label:"\u2026"] \xB7 var NAME: TYPE \xB7 transition [id] from: A to: B: COND \xB7 jump from: A to: B \xB7 alt from: STEP: / sim from: STEP: COND with branch [priority:N]: + merge_to: STEP[: COND] \xB7 action line \xABQUAL Name [T#\u2026]\xBB | qualifiers: N S R L D P P0 P1 SD DS SL (L/D/SD/DS/SL need T#\u2026) | var types: bool int real time timer counter',
4604
+ forms: [
4605
+ "step S0 [initial]",
4606
+ " N FillValve_Closed",
4607
+ 'step S1 [label: "Filling"]',
4608
+ " N FillValve_Open",
4609
+ "transition from: S0 to: S1: StartBtn",
4610
+ "transition from: S1 to: S0: TankLevel >= 80.0"
4611
+ ],
4612
+ prefer: [
4613
+ 'Declare each `step ID [initial|final|label: "\u2026"]` first; put actions on indented lines under the step as `<QUAL> ActionName`.',
4614
+ "Wire steps with `transition from: A to: B: CONDITION` \u2014 the condition is free text (e.g. `TankLevel >= 80.0`), stored verbatim, never evaluated.",
4615
+ "Qualifiers are N S R L D P P0 P1 SD DS SL; the timed ones take a literal, e.g. `D Oven_Run T#15m`, `L Cooler_On T#5m`.",
4616
+ "Concurrency: `sim from: STEP: COND` + `branch:` blocks + `merge_to: STEP: COND`. Choice: `alt from: STEP:` + `branch [priority: N]:` (each with `transition: COND`) + `merge_to: STEP`."
4617
+ ],
4618
+ avoid: [
4619
+ "Don't use arrow transitions like `S0 -> S1 : cond` \u2014 SFC only accepts `transition from: S0 to: S1: cond` (a bare arrow line is rejected as 'Unrecognized SFC line').",
4620
+ "Avoid a second `[initial]` step; if none is marked, the first declared step is promoted automatically.",
4621
+ "Don't invent qualifiers \u2014 only N/S/R/L/D/P/P0/P1/SD/DS/SL are valid, and L/D/SD/DS/SL need a `T#\u2026` time literal."
4622
+ ],
4623
+ repair: [
4624
+ "'Transition references unknown step: X' -> declare `step X` (or fix the id) before any transition that names it.",
4625
+ "'Multiple [initial] steps: A and B' -> mark only one step `[initial]`.",
4626
+ "'Unrecognized SFC line: \u2026' -> a transition must be `transition from: A to: B: cond`; an action line must start with a qualifier and sit indented under its step.",
4627
+ "'sim block missing merge_to clause' -> end the branch block with `merge_to: STEP: CONDITION` (alt uses `merge_to: STEP`)."
4628
+ ]
4102
4629
  },
4103
4630
  prisma: {
4104
4631
  type: "prisma",
4105
4632
  header: "prisma",
4106
- mode: "PRISMA 2020 single pipeline",
4107
- forms: ["identification:", " databases:", " n: 1000", "screening:", "included:"],
4108
- prefer: ["Use the required four stages and mandatory counts.", "Use single-pipeline 2020 mode unless other-methods data is present."],
4109
- avoid: ["Avoid generic flowchart boxes for PRISMA requests."],
4110
- repair: ["Missing stage blocks or mandatory `n` fields are intentional parser errors."]
4633
+ mode: "indentation-structured PRISMA 2020 flow diagram (2-space indent)",
4634
+ keywords: "prisma \xB7 mode: 2020-single|2020-dual|2009 \xB7 kind: systematic-review|scoping-review|ipd|nma \xB7 validate-counts: strict|warn|off \xB7 stages: previous-studies \xB7 identification: -> databases: (n: sources: duplicates-removed:) [other: (n:)] \xB7 screening: -> records-screened: excluded: (n: reasons: name=count,\u2026) \xB7 eligibility: -> full-text-assessed: excluded: (n: reasons:) \xB7 included: -> studies: [reports:]",
4635
+ forms: [
4636
+ "prisma",
4637
+ "mode: 2020-single",
4638
+ "title: Exercise for chronic low-back pain \u2014 SR",
4639
+ "",
4640
+ "identification:",
4641
+ " databases:",
4642
+ " n: 1418",
4643
+ " sources: PubMed=600, Embase=450, Cochrane=184, Web of Science=184",
4644
+ " duplicates-removed: 318",
4645
+ "",
4646
+ "screening:",
4647
+ " records-screened: 1100",
4648
+ " excluded:",
4649
+ " n: 870",
4650
+ " reasons: irrelevant title=750, non-English=120",
4651
+ "",
4652
+ "eligibility:",
4653
+ " full-text-assessed: 230",
4654
+ " excluded:",
4655
+ " n: 195",
4656
+ " reasons: wrong population=80, wrong intervention=60, wrong outcome=55",
4657
+ "",
4658
+ "included:",
4659
+ " studies: 35"
4660
+ ],
4661
+ prefer: [
4662
+ "All four stages `identification:`, `screening:`, `eligibility:`, `included:` are required \u2014 the parser throws if any is missing.",
4663
+ "Use 2-space indentation per level: stage keys at indent 0, sub-keys at indent 1, sub-sub-keys at indent 2.",
4664
+ "Put reason breakdowns as `name=count` pairs on one `reasons:` line; for dual-pipeline add an `other:` block under `identification:` (auto-switches to 2020-dual)."
4665
+ ],
4666
+ avoid: [
4667
+ "Don't write free-form stage keys \u2014 only `previous-studies`, `identification`, `screening`, `eligibility`, `included` are valid at the top level.",
4668
+ "Don't omit `n:` inside any stage block \u2014 it is required.",
4669
+ "Don't malform `reasons:` \u2014 pairs must be `name=count`; a missing `=` is an error."
4670
+ ],
4671
+ repair: [
4672
+ "'required stage' is missing -> add the missing stage block (`identification:` needs a nested `databases:` with at least `n:`).",
4673
+ "'is missing required' field -> add the required sub-key (e.g. `n:`, or an `excluded:` block under `screening:`).",
4674
+ "'sources sum to' N but n = M -> make the `sources:` counts add up to the `n:` value, or remove `sources:`.",
4675
+ "'unknown stage' -> valid top-level keys are `previous-studies`, `identification`, `screening`, `eligibility`, `included`."
4676
+ ]
4111
4677
  },
4112
4678
  usecase: {
4113
4679
  type: "usecase",
4114
4680
  header: "usecase",
4115
- mode: "declarative UML use cases",
4116
- forms: ['system: "System"', "actor: Customer", 'usecase: "Checkout" as Checkout', "Customer -- Checkout"],
4117
- prefer: ["Use declarative actor/usecase lines for generated DSL."],
4118
- avoid: ["Avoid PlantUML inline form unless converting PlantUML-like input."],
4119
- repair: ["Include/extend relations must connect use cases, not actors."]
4681
+ mode: "declarative actor/usecase declarations + typed relationship lines",
4682
+ keywords: 'usecase \xB7 title: "\u2026" \xB7 system: "\u2026" \xB7 direction: LR|TB \xB7 actor: Name [as ID] [(external|business|system)] [(left|right)] \xB7 usecase: "Name" [as ID] [\xABstereotype\xBB] [{ extension point: name }] \xB7 relations: -- association \xB7 --> directed \xB7 ..> include \xB7 <.. extend \xB7 --|> generalization \xB7 multiplicity: "1" "0..*" \xB7 note "label" { id, id } \xB7 PlantUML inline: :Name: (Name)',
4683
+ forms: [
4684
+ "usecase",
4685
+ 'title: "ATM"',
4686
+ 'system: "ATM System"',
4687
+ "actor: Customer",
4688
+ "actor: Bank (external)",
4689
+ 'usecase: "Withdraw Cash" as Withdraw',
4690
+ 'usecase: "Check Balance" as Check',
4691
+ "Customer -- Withdraw",
4692
+ "Customer -- Check",
4693
+ "Withdraw -- Bank"
4694
+ ],
4695
+ prefer: [
4696
+ "Declare all `actor:` and `usecase:` lines before relationship lines \u2014 an unknown id on a relation is a hard error.",
4697
+ "Use `actor: Name (external)` for system actors (rectangle), `(business)` for in-org actors, bare for stick figures; add `(left)`/`(right)` to pin placement.",
4698
+ "Use `..>` for \xABinclude\xBB (arrow points to the included use case) and `<..` for \xABextend\xBB (arrow points to the base) \u2014 the directions are opposite and both enforced."
4699
+ ],
4700
+ avoid: [
4701
+ "Don't use `--` between two actors, or `..>`/`<..` involving an actor \u2014 both are hard errors.",
4702
+ "Don't use `--|>` between an actor and a use case \u2014 generalization must be actor\u2194actor or usecase\u2194usecase.",
4703
+ "Don't skip the `as ID` alias when reusing a multi-word name in relationships \u2014 the auto-id replaces spaces with underscores."
4704
+ ],
4705
+ repair: [
4706
+ "'unknown identifier' -> declare `actor: X` or `usecase: \"X\" as X` before the relation line that references it.",
4707
+ "'must connect an actor and a use case' -> use `--|>` for actor generalization, not `--`.",
4708
+ "'endpoints must be use cases' -> both sides of `..>`/`<..` must be use cases, not actors.",
4709
+ "'first non-comment line must start with' -> the document must open with the bare keyword `usecase`."
4710
+ ]
4120
4711
  },
4121
4712
  pert: {
4122
4713
  type: "pert",
4123
4714
  header: "pert",
4124
- mode: "AON tasks + dependencies",
4125
- forms: ['task A "Spec" duration: 3', 'task B "Build" duration: 8 after: A'],
4126
- prefer: ["Use AON network tasks first.", "Use time-scaled/AOA layouts only when requested."],
4127
- avoid: ["Avoid writing computed ES/EF/LS/LF fields yourself."],
4128
- repair: ["Task IDs must exist before they are used in `after:` lists."]
4715
+ mode: "AON tasks with computed schedule (ES/EF/LS/LF/slack/critical path)",
4716
+ keywords: 'pert \xB7 title: "\u2026" \xB7 unit: days|weeks|hours|abstract \xB7 direction: LR|TB \xB7 layout: network|timescaled|aoa \xB7 critical-tolerance: N \xB7 task ID "label" duration: N|O/M/P [after: ref,\u2026] [milestone] [lane: "Name"] \xB7 dependency refs: ID (FS) \xB7 ID FS|SS|FF|SF[+N|-N][d|w|h] \xB7 ID+N (FS lag sugar)',
4717
+ forms: [
4718
+ "pert",
4719
+ 'title: "Q3 Product Launch"',
4720
+ "unit: days",
4721
+ "",
4722
+ 'task A "Market research" duration: 5',
4723
+ 'task B "Design mockups" duration: 8 after: A',
4724
+ 'task C "Backend API" duration: 15 after: A',
4725
+ 'task D "Frontend build" duration: 10 after: B, C',
4726
+ 'task E "QA / testing" duration: 5 after: D',
4727
+ 'task G "Launch event" duration: 2 after: E'
4728
+ ],
4729
+ prefer: [
4730
+ 'Each `task ID "label" duration: N` is one activity; wire dependencies with `after: A, B` (comma-separated, forward references allowed). The engine computes ES/EF/LS/LF and the critical path \u2014 never write those yourself.',
4731
+ "Use three-point estimation `duration: O/M/P` (e.g. `2/3/5`) for uncertainty \u2014 the engine computes `te = (O+4M+P)/6` and variance; add `critical-tolerance: 0.01` when mixing estimate kinds.",
4732
+ 'Declare dependency types when needed: `after: A FS`, `after: A SS+2`, `after: B FF-1` (default FS, zero lag); group activities with `lane: "Team"`.'
4733
+ ],
4734
+ avoid: [
4735
+ "Don't write `ES:`, `EF:`, `LS:`, `LF:`, or `slack:` yourself \u2014 they are computed outputs.",
4736
+ "Don't create a cycle in `after:` references, and don't reference an undeclared predecessor.",
4737
+ "Don't mix lag unit suffixes that don't match `unit:` \u2014 e.g. `FS+2d` with `unit: weeks`."
4738
+ ],
4739
+ repair: [
4740
+ "'is missing' duration -> add `duration: N` or append the bare word `milestone` to the task line.",
4741
+ "'references undeclared predecessor' -> declare `task Y \u2026` anywhere (forward references are allowed).",
4742
+ "'O <= M <= P' -> reorder a three-point estimate so optimistic <= most-likely <= pessimistic.",
4743
+ "'duplicate task id' -> every task needs a unique id; rename the second occurrence."
4744
+ ]
4129
4745
  },
4130
4746
  sequence: {
4131
4747
  type: "sequence",
4132
4748
  header: "sequenceDiagram",
4133
4749
  mode: "Mermaid sequenceDiagram (recommended for generation)",
4750
+ keywords: 'sequenceDiagram (Mermaid) | sequence "Title" (native) \xB7 participant actor boundary control entity database collections queue \xB7 "as" alias \xB7 \xABstereotype\xBB \xB7 -> sync \xB7 ->> async \xB7 --> reply \xB7 -) async \xB7 -x lost \xB7 *Target create \xB7 +/- activation suffix \xB7 activate/deactivate \xB7 note over|left of|right of A[,B]: text \xB7 ref over A,B: text \xB7 == divider == \xB7 autonumber \xB7 fragments: alt else end / opt loop break critical neg ignore consider assert par seq strict and end',
4134
4751
  forms: [
4135
4752
  "participant Alice",
4136
4753
  "participant Bob",
@@ -4139,20 +4756,26 @@ var PROFILES = {
4139
4756
  "Note over Alice,Bob: handshake"
4140
4757
  ],
4141
4758
  prefer: [
4142
- "Use Mermaid `sequenceDiagram` syntax: `->>` is a sync call, `-->>` a reply/return, `-)` async; `participant`/`actor`, `Note over A,B:`, and `loop`/`alt`/`opt`/`par \u2026 end` all work.",
4143
- 'This matches the dominant training-data prior; the native `sequence "Title"` header (where `->>` means async) is also accepted.',
4144
- "Add combined fragments only when control flow matters."
4759
+ "Use the `sequenceDiagram` header (Mermaid mode): `->>` is a sync call and `-->>` is the reply/return \u2014 matches the dominant training prior.",
4760
+ "Declare lifeline kinds with `participant`/`actor`/`boundary`/`control`/`entity`/`database`/`collections`/`queue` before use; undeclared ids auto-create as plain `participant`.",
4761
+ "Use `->+` / `-->-` to open/close activation bars inline; use `alt \u2026 else \u2026 end`, `loop`, `opt`, `par \u2026 and \u2026 end` for fragments."
4762
+ ],
4763
+ avoid: [
4764
+ "Do not mix `sequenceDiagram` and native `sequence` headers \u2014 in Mermaid mode `->>` is sync, in native mode it is async; pick one and keep arrows consistent.",
4765
+ "Do not use `else` outside an `alt`, or `and` outside `par`/`seq`/`strict`.",
4766
+ "Do not leave a combined fragment open \u2014 every `alt`/`opt`/`loop`/`par`/`break`/`critical` must close with `end`."
4145
4767
  ],
4146
- avoid: ["Do not mix the two header styles; pick `sequenceDiagram` and keep Mermaid arrow meanings throughout."],
4147
4768
  repair: [
4148
- "Every fragment (`loop`/`alt`/`opt`/`par`/`break`/`critical`) needs a matching `end`; `else` only inside `alt`.",
4149
- 'If the header is rejected, use exactly `sequenceDiagram` (or `sequence "Title"`).'
4769
+ "'must start with the keyword' -> fix the first line to exactly `sequenceDiagram` or `sequence \"Title\"`.",
4770
+ "'is only valid inside an' -> move the `else` inside an `alt \u2026 end` block (or `and` inside `par`).",
4771
+ "'without a matching combined fragment' -> add the missing `end`, or remove the dangling `else`/`and`."
4150
4772
  ]
4151
4773
  },
4152
4774
  petri: {
4153
4775
  type: "petri",
4154
4776
  header: 'petri "Title"',
4155
4777
  mode: "declared places + transitions + arcs",
4778
+ keywords: 'place ID ["label"] *N|tokens:N [capacity:N] \xB7 transition ID ["label"] [immediate|timed] [rate:N] [priority:N] [guard: expr] \xB7 arc types: -> (standard) | -o (inhibitor, P\u2192T only) | -- (read) | => (reset, P\u2192T only) \xB7 weight: N or *N on arc \xB7 layout: lr|tb \xB7 marking: id=n,\u2026 \xB7 fire: T1, T2, \u2026 (simulate firing)',
4156
4779
  forms: [
4157
4780
  "place P1 *1",
4158
4781
  "transition T1",
@@ -4170,7 +4793,7 @@ var PROFILES = {
4170
4793
  "Avoid `-o`/`=>` arcs from a transition; inhibitor and reset arcs are place\u2192transition only."
4171
4794
  ],
4172
4795
  repair: [
4173
- "An 'unknown node' error means an arc references an undeclared place/transition \u2014 declare it first.",
4796
+ "'arc references unknown node' -> an arc references an undeclared place/transition; declare it first.",
4174
4797
  "Set the initial marking so the transitions you intend to be enabled actually have enough input tokens."
4175
4798
  ]
4176
4799
  },
@@ -4178,27 +4801,28 @@ var PROFILES = {
4178
4801
  type: "network",
4179
4802
  header: 'network "Title"',
4180
4803
  mode: "device declarations + links (annotations are optional)",
4804
+ keywords: "device kinds: router switch l3switch firewall loadbalancer ap wlc gateway modem ids proxy vpngw server serverfarm pc laptop mobile ipphone printer storage camera nvr dvr poeswitch encoder monitor internet wan cloud pstn lan \xB7 aliases: multilayer->l3switch wifi->ap workstation->pc nas/san->storage \xB7 device attrs: tier:edge|core|distribution|access ip: model: count: type:fixed|bullet|dome|ptz|turret at:x,y \xB7 link connectors: -- (undirected) | -> (directed) | == (LAG) \xB7 link annotations: copper|fiber|wireless|serial|poe|vpn|lag trunk|access speed(1G/10G/100M) vlan:N port:near>far \xB7 groups: site rack subnet vlan zone dmz { \u2026 } \xB7 layout: tiered|tree|star|ring|bus|mesh|spine-leaf|manual \xB7 spines: / leaves:",
4181
4805
  forms: [
4182
- 'router r1 "Edge Router" (kind id "label")',
4183
- 'l3switch core1 "Core" tier: core (optional structural hint)',
4806
+ 'router r1 "Edge Router"',
4807
+ 'l3switch core1 "Core" tier: core',
4184
4808
  'switch acc1 "Access" tier: access',
4185
4809
  'pc pc1 "Workstation"',
4186
- "r1 -- core1 (link: id -- id)",
4810
+ "r1 -- core1",
4187
4811
  "core1 -- acc1",
4188
4812
  "acc1 -- pc1"
4189
4813
  ],
4190
4814
  prefer: [
4191
4815
  'Start from the skeleton: `kind id "label"` device lines plus `a -- b` links. That alone renders a complete, valid diagram.',
4192
4816
  "Declare every device before any link references it.",
4193
- "Keep the cheap structural hints `layout:` (tiered/tree/star/ring/bus/mesh/spine-leaf) and `tier:` (edge/core/distribution/access) \u2014 they cost little and drive a readable hierarchy. They are recommended, not noise.",
4817
+ "Keep the cheap structural hints `layout:` (tiered/tree/star/ring/bus/mesh/spine-leaf) and `tier:` (edge/core/distribution/access) \u2014 they drive a readable hierarchy.",
4194
4818
  "Common kinds: router, switch, l3switch, firewall, ap, server, pc, laptop, camera, nvr, poeswitch, internet, cloud."
4195
4819
  ],
4196
4820
  avoid: [
4197
4821
  "Avoid linking to an undeclared device id.",
4198
- 'Add the verbose per-link annotations \u2014 `vlan:`, `port:`, speeds (1G/10G), `trunk`/`access` \u2014 and `subnet "cidr" { ... }` boundaries ONLY when the request explicitly needs them. These don\'t affect the layout and are where generation most often breaks.'
4822
+ 'Add verbose per-link annotations (`vlan:`, `port:`, speeds, `trunk`/`access`) and `subnet "cidr" { ... }` boundaries ONLY when the request needs them \u2014 they don\'t affect layout and are where generation most often breaks.'
4199
4823
  ],
4200
4824
  repair: [
4201
- "An 'undeclared device' error means a link references an id with no `kind id` declaration \u2014 declare it first.",
4825
+ "'undeclared device' -> a link references an id with no `kind id` declaration; declare it first.",
4202
4826
  "If the layout looks flat/messy, add `layout: tiered` + `tier:` on infrastructure; if unsure about per-link annotations, drop them \u2014 the skeleton always renders."
4203
4827
  ]
4204
4828
  },
@@ -4206,40 +4830,42 @@ var PROFILES = {
4206
4830
  type: "umlclass",
4207
4831
  header: "umlclass",
4208
4832
  mode: "classifier declarations + relationship lines (PlantUML-flavoured, Mermaid aliases accepted)",
4833
+ keywords: 'header: umlclass | class-diagram | classDiagram \xB7 classifiers: class | abstract class | interface | enum (or enumeration) | datatype | primitive \xB7 stereotype: \xABword\xBB or <<word>> \xB7 members in { }: visibility + - # ~ \xB7 modifiers {static} {abstract} \xB7 derived /name \xB7 multiplicity [1..*] \xB7 tilde-generics List~T~ -> List<T> \xB7 single-line: ClassName : +member \xB7 connectors: <|-- --|> generalization \xB7 <|.. ..|> realization \xB7 *-- --* composition \xB7 o-- --o aggregation \xB7 --> <-- directed assoc \xB7 ..> <.. dependency \xB7 -- association \xB7 multiplicity "1" "0..*" quoted next to endpoint \xB7 namespace A.B.C { } \xB7 direction: tb|lr',
4209
4834
  forms: [
4210
- "class Order { + id : String + place() : void } (visibility: + - # ~)",
4211
- "\xABinterface\xBB Shape { + area() : double } (stereotype above name)",
4835
+ "class Order { + id : String + place() : void }",
4836
+ "\xABinterface\xBB Shape { + area() : double }",
4212
4837
  "abstract class AbstractShape { + area() : double {abstract} }",
4213
- "\xABenumeration\xBB Suit { HEARTS DIAMONDS CLUBS SPADES } (literals in attr compartment)",
4214
- "Animal <|-- Dog (generalization \u2014 hollow triangle to parent)",
4215
- "Shape <|.. Circle (realization \u2014 dashed + hollow triangle)",
4216
- 'Order *-- "1..*" LineItem : contains (composition \u2014 filled diamond at whole)',
4217
- 'Customer o-- "0..*" Address (aggregation \u2014 hollow diamond at whole)',
4218
- 'A "1" --> "*" B : owns (directed association \u2014 open arrow to target)',
4219
- "X ..> Y (dependency \u2014 dashed + open arrow)"
4838
+ "\xABenumeration\xBB Suit { HEARTS DIAMONDS CLUBS SPADES }",
4839
+ "Animal <|-- Dog",
4840
+ "Shape <|.. Circle",
4841
+ 'Order *-- "1..*" LineItem : contains',
4842
+ 'Customer o-- "0..*" Address',
4843
+ 'A "1" --> "*" B : owns',
4844
+ "X ..> Y"
4220
4845
  ],
4221
4846
  prefer: [
4222
4847
  "Single-word keyword is `umlclass` (also accepts `class-diagram` and Mermaid's `classDiagram`).",
4223
4848
  "Use `class`, `abstract class`, `\xABinterface\xBB`, `\xABenumeration\xBB`, or any custom `\xABstereotype\xBB` above the name.",
4224
4849
  "Members go in `{ \u2026 }`; visibility glyphs are `+ - # ~`; `{static}` underlines, `{abstract}` italicises, `/name` marks a derived attribute.",
4225
- 'Multiplicity is the quoted token next to an endpoint: `"1"`, `"0..*"`, `"1..*"`. The line midpoint label after `:` is the association name.',
4850
+ 'Multiplicity is the quoted token next to an endpoint: `"1"`, `"0..*"`, `"1..*"`. The label after `:` is the association name.',
4226
4851
  "PlantUML connectors are primary; the Mermaid reversed forms `--|>`, `..|>`, `--*`, `--o` are accepted and normalised."
4227
4852
  ],
4228
4853
  avoid: [
4229
- "Don't use bare `class` as the diagram keyword \u2014 that's a reserved programming-language word, and the engine keyword is `umlclass`.",
4230
- "Don't put `-->` for dependency: `-->` is *directed association*; dependency is the dashed `..>` (this is a deliberate deviation from PlantUML, matching Mermaid and the usual UML reading).",
4231
- "Don't declare a classifier with an empty body and `{}` if you want a name-only sketch box \u2014 write `class Foo` on its own line."
4854
+ "Don't use bare `class` as the diagram keyword \u2014 that's a reserved word; the engine keyword is `umlclass`.",
4855
+ "Don't put `-->` for dependency: `-->` is directed association; dependency is the dashed `..>`.",
4856
+ "Don't declare a classifier with an empty `{}` for a name-only box \u2014 write `class Foo` on its own line."
4232
4857
  ],
4233
4858
  repair: [
4234
- "A 'malformed relationship' error means no connector glyph was found between two ids \u2014 check the line uses one of `<|--` `<|..` `*--` `o--` `-->` `..>` `--` `..`.",
4235
- "A 'generalization cycle' error means inheritance edges form a loop \u2014 fix the parent/child direction of one of the edges named in the cycle.",
4236
- "If a class appears as a one-line empty box you didn't expect, the parser auto-created it from an arc reference \u2014 declare it explicitly or fix the typo in the relationship id."
4859
+ "'malformed relationship' -> no connector glyph was found between two ids; use one of `<|--` `<|..` `*--` `o--` `-->` `..>` `--` `..`.",
4860
+ "'generalization cycle' -> inheritance edges form a loop; fix the parent/child direction of one edge in the cycle.",
4861
+ "If a class appears as an unexpected empty box, the parser auto-created it from an arc reference \u2014 declare it explicitly or fix the relationship id typo."
4237
4862
  ]
4238
4863
  },
4239
4864
  faulttree: {
4240
4865
  type: "faulttree",
4241
4866
  header: 'faulttree "Title"',
4242
4867
  mode: "flat event/gate declarations wired by id (top + gates + leaves)",
4868
+ keywords: "header: faulttree | fta \xB7 event kinds: top | gate | basic | undeveloped | house | condition \xB7 gate exprs: AND(a,b,\u2026) | OR(\u2026) | XOR(\u2026) | VOTING(k/n; \u2026) | INHIBIT(x) if cond | PAND(a,b) [order: a,b] \xB7 leaf attr: p: N (or prob:, sci 1e-3) \xB7 house attr: state: 0|1 \xB7 analysis: cutsets | probability | pathsets \xB7 prob: rare|mcub|exact \xB7 layout: tb|bt \xB7 style: ansi|iec",
4243
4869
  forms: [
4244
4870
  "analysis: cutsets, probability",
4245
4871
  'top T "System fails" = OR(G1, G2)',
@@ -4263,9 +4889,9 @@ var PROFILES = {
4263
4889
  "Don't declare more than one `top`, and don't create cycles (a gate may not transitively reference itself)."
4264
4890
  ],
4265
4891
  repair: [
4266
- "'references undefined event' means a gate input id was never declared \u2014 add the `basic`/`gate`/\u2026 line.",
4267
- "'must have exactly one top' \u2014 keep a single `top`; downgrade the others to `gate`.",
4268
- "'VOTING k/n: n must equal the number of inputs' \u2014 make n match the listed inputs.",
4892
+ "'references undefined event' -> a gate input id was never declared; add the `basic`/`gate`/\u2026 line.",
4893
+ "'must have exactly one top' -> keep a single `top`; downgrade the others to `gate`.",
4894
+ "'n must equal the number of inputs' -> in `VOTING(k/n; \u2026)` make n match the listed inputs.",
4269
4895
  "If P(top) shows 'n/a', a basic event in a cut set is missing its `p:`."
4270
4896
  ]
4271
4897
  },
@@ -4273,6 +4899,7 @@ var PROFILES = {
4273
4899
  type: "bowtie",
4274
4900
  header: 'bowtie "Title"',
4275
4901
  mode: "indentation-structured: hazard + topevent, then threat/consequence blocks with indented barrier chains",
4902
+ keywords: 'hazard "\u2026" \xB7 topevent "\u2026" \xB7 threat "\u2026" \xB7 prevent "\u2026" \xB7 consequence "\u2026" \xB7 mitigate "\u2026" \xB7 escalation "\u2026" \xB7 barrier "\u2026" \xB7 layout: symmetric|compact \xB7 legend: on|off|bottom|bottom-right|top',
4276
4903
  forms: [
4277
4904
  'hazard "Working at height"',
4278
4905
  'topevent "Person falls from height"',
@@ -4286,7 +4913,7 @@ var PROFILES = {
4286
4913
  ],
4287
4914
  prefer: [
4288
4915
  "Single-word keyword is `bowtie`. Declare exactly one `topevent` (the knot) and an optional `hazard` header.",
4289
- "Each `threat` needs \u2265 1 indented `prevent` barrier; each `consequence` needs \u2265 1 indented `mitigate` barrier (the engine rejects a bare threat/consequence).",
4916
+ "Each `threat` needs >= 1 indented `prevent` barrier; each `consequence` needs >= 1 indented `mitigate` barrier (the engine rejects a bare threat/consequence).",
4290
4917
  "Barrier order = declaration order (first = outermost, nearest the threat/consequence; last = innermost, nearest the knot).",
4291
4918
  "`escalation` nests (4 spaces) under the `prevent`/`mitigate` barrier it degrades; an escalation-factor `barrier` nests (6 spaces) under the escalation.",
4292
4919
  "Bowtie is qualitative \u2014 no probabilities. For Boolean gates + cut sets behind one threat, use a separate `faulttree`."
@@ -4298,16 +4925,17 @@ var PROFILES = {
4298
4925
  "Don't declare more than one `topevent` or more than one `hazard`."
4299
4926
  ],
4300
4927
  repair: [
4301
- "'has no preventative barrier' / 'no mitigative barrier' \u2014 add a `prevent`/`mitigate` line under the threat/consequence.",
4302
- "'not attached to a barrier' \u2014 move the `escalation` so it follows a `prevent`/`mitigate` line.",
4303
- "'needs at least one threat/consequence' \u2014 a one-wing diagram is a fault tree or event tree; add the missing wing.",
4304
- "'exactly one top event' \u2014 keep a single `topevent` line."
4928
+ "'has no preventative barrier' / 'no mitigative barrier' -> add a `prevent`/`mitigate` line under the threat/consequence.",
4929
+ "'not attached to a barrier' -> move the `escalation` so it follows a `prevent`/`mitigate` line.",
4930
+ "'needs at least one threat/consequence' -> a one-wing diagram is a fault tree or event tree; add the missing wing.",
4931
+ "'exactly one top event' -> keep a single `topevent` line."
4305
4932
  ]
4306
4933
  },
4307
4934
  eventtree: {
4308
4935
  type: "eventtree",
4309
4936
  header: 'eventtree "Title"',
4310
4937
  mode: "initiating event + ordered safety functions, then outcome rows with s/f/* branch patterns",
4938
+ keywords: 'eventtree|eta "Title" \xB7 initiating ID "label" freq: <number|1e-4> \xB7 function ID "label" p: <0..1> \xB7 outcome <s|f|*>\u2026 -> "label" \xB7 layout: lr \xB7 # and // comments',
4311
4939
  forms: [
4312
4940
  'initiating LOCA "Large LOCA" freq: 1e-4',
4313
4941
  'function A "ECCS injects" p: 0.001',
@@ -4318,24 +4946,25 @@ var PROFILES = {
4318
4946
  ],
4319
4947
  prefer: [
4320
4948
  "Keyword `eventtree` (or `eta`). One `initiating` line with a `freq:`; then `function` lines in left-to-right order, each with its failure probability `p:` (success is auto-complemented to 1\u2212p).",
4321
- 'Each `outcome` row gives a `s`/`f`/`*` pattern (one symbol per function) then `-> "label"`. `*` = path already terminated (pruned) \u2014 runs flat to its leaf, no further branching.',
4949
+ 'Each `outcome` row gives a `s`/`f`/`*` pattern (one symbol per function) then `-> "label"`. `*` = path already terminated (pruned).',
4322
4950
  "Let the engine compute path frequencies (f_initiating \xD7 \u03A0 branch-probs) \u2014 don't hand-write them."
4323
4951
  ],
4324
4952
  avoid: [
4325
- "Don't try to make a balanced 2\u207F tree \u2014 prune with `*` where a sequence ends early.",
4953
+ "Don't try to make a balanced 2^n tree \u2014 prune with `*` where a sequence ends early.",
4326
4954
  "Don't put probabilities on outcome rows; `p:` belongs on `function` lines.",
4327
4955
  "Don't reverse the success/failure convention \u2014 success branches up, failure down."
4328
4956
  ],
4329
4957
  repair: [
4330
- "'outcome pattern length' \u2014 give exactly one s/f/* per declared function.",
4331
- "'no initiating event' \u2014 add one `initiating <id> \"\u2026\" freq: \u2026` line.",
4332
- "'once pruned stays pruned' \u2014 after a `*` in a pattern, the rest must also be `*`."
4958
+ "'outcome pattern length' -> give exactly one s/f/* per declared function.",
4959
+ "'no initiating event' -> add one `initiating <id> \"\u2026\" freq: \u2026` line.",
4960
+ "'once pruned stays pruned' -> after a `*` in a pattern, the rest must also be `*`."
4333
4961
  ]
4334
4962
  },
4335
4963
  fmea: {
4336
4964
  type: "fmea",
4337
4965
  header: 'fmea "Title"',
4338
4966
  mode: "indentation-structured worksheet: item \u2192 failure mode \u2192 effect/cause/controls with S/O/D scores",
4967
+ keywords: 'fmea "Title" \xB7 type: design|dfmea|process|pfmea|msr \xB7 rank: ap|rpn \xB7 flag: ap>=High|rpn>100 \xB7 item "\u2026" fn "\u2026" \xB7 mode "\u2026" \xB7 effect "\u2026" sev: 1..10 \xB7 cause "\u2026" occ: 1..10 \xB7 controls prevention: "\u2026" detection: "\u2026" det: 1..10 \xB7 action "mode" do: "\u2026" owner: "\u2026" target: DATE \xB7 revised sev:/occ:/det:',
4339
4968
  forms: [
4340
4969
  'fmea "Brake System DFMEA"',
4341
4970
  ' item "Master cylinder" fn "Generate pressure"',
@@ -4355,40 +4984,47 @@ var PROFILES = {
4355
4984
  "Don't put `occ:` on an effect or `sev:` on a cause \u2014 they bind to the wrong column."
4356
4985
  ],
4357
4986
  repair: [
4358
- "'score out of range' \u2014 keep S/O/D in 1\u201310.",
4359
- "'mode has no effect/cause' \u2014 every failure mode needs at least one effect and one cause."
4987
+ "'score out of range' -> keep S/O/D in 1\u201310.",
4988
+ "'has no effect/cause' -> every failure mode needs at least one effect and one cause."
4360
4989
  ]
4361
4990
  },
4362
4991
  causalloop: {
4363
4992
  type: "causalloop",
4364
4993
  header: 'causalloop "Title"',
4365
4994
  mode: "signed causal links between variables; engine detects R/B loops",
4995
+ keywords: 'causalloop "Title" (alias: cld) \xB7 A -> B : + | - | s | o | same | opposite \xB7 "multi-word var" -> Target : + \xB7 A -> B : + delay \xB7 var "Variable name" \xB7 loop R1 "phrase" | loop B1 "phrase" \xB7 layout: auto | circle',
4366
4996
  forms: [
4367
- 'causalloop "Adoption model"',
4368
- '"Adoption rate" -> Adopters : +',
4369
- 'Adopters -> "Adoption rate" : +',
4370
- '"Adoption rate" -> "Potential adopters" : -',
4371
- 'loop R1 "Word of mouth"',
4372
- 'loop B1 "Market saturation"'
4997
+ 'causalloop "Startup growth engine"',
4998
+ '"Active users" -> "Word of mouth" : +',
4999
+ '"Word of mouth" -> "New signups" : +',
5000
+ '"New signups" -> "Active users" : +',
5001
+ '"Active users" -> "Server load" : +',
5002
+ '"Server load" -> "App performance" : -',
5003
+ '"App performance" -> "Active users" : + delay',
5004
+ 'loop R1 "Viral flywheel"',
5005
+ 'loop B1 "Scaling strain"'
4373
5006
  ],
4374
5007
  prefer: [
4375
- "Keyword `causalloop` (or `cld`). Write `A -> B : +` or `A -> B : -` (you may use `s`/`o` aliases). Multi-word variables go in quotes.",
4376
- 'Let the engine classify loops R (reinforcing) / B (balancing) by counting negative links; optionally name them with `loop R1 "\u2026"`.',
4377
- "Put the `+`/`\u2212` polarity at the end of each link."
5008
+ "Put every polarity after the `->` as `: +` or `: -` \u2014 every causal link must be signed.",
5009
+ 'Wrap multi-word variable names in double quotes: `"Active users"`. Single-token names (e.g. `Revenue`) need no quotes.',
5010
+ 'Let the engine classify loops R/B automatically \u2014 use `loop R1 "phrase"` only to name a loop it already detected.'
4378
5011
  ],
4379
5012
  avoid: [
4380
- "Don't draw boxes around variables \u2014 CLD variables are boxless text.",
4381
- "Don't omit polarity \u2014 every causal link is signed."
5013
+ "Don't omit the polarity separator: `A -> B : +` is the canonical form.",
5014
+ "Don't use `s`/`o`/`same`/`opposite` in generated output \u2014 they normalise to `+`/`-` but reduce readability.",
5015
+ "Don't draw boxes around variables \u2014 CLD variables are boxless text labels; there is no shape syntax."
4382
5016
  ],
4383
5017
  repair: [
4384
- "'link needs a polarity' \u2014 append `: +` or `: -`.",
4385
- "'unknown variable' \u2014 variables are created on first use; check quoting/spelling."
5018
+ "'is missing a polarity' -> append `: +` or `: -` after the target variable name.",
5019
+ "'needs at least one causal link' -> add at least one signed link; a document with only `loop` declarations is rejected.",
5020
+ "'malformed link (expected' -> each link line must contain `->` between the source and target variable names."
4386
5021
  ]
4387
5022
  },
4388
5023
  markov: {
4389
5024
  type: "markov",
4390
5025
  header: 'markov "Title"',
4391
5026
  mode: "probability-labelled state transitions; engine computes stationary distribution + classification",
5027
+ keywords: 'markov | markovchain ["Title"] \xB7 state <id> ["Label"] [absorbing] \xB7 <from> -> <to> : <prob> (colon form) | <from> -> <to> <prob> (space form) \xB7 layout: ring|layered \xB7 normalize: true|false \xB7 analysis: stationary classify absorbing period all',
4392
5028
  forms: [
4393
5029
  'markov "Weather"',
4394
5030
  "Sunny -> Sunny : 0.9",
@@ -4397,23 +5033,26 @@ var PROFILES = {
4397
5033
  "Rainy -> Rainy : 0.5"
4398
5034
  ],
4399
5035
  prefer: [
4400
- "Keyword `markov` (or `markovchain`). Write `S1 -> S2 : 0.3` transitions; each state's outgoing probabilities must sum to 1.",
4401
- "Mark absorbing states by a 1.0 self-loop (or the `absorbing` keyword); the engine computes the stationary distribution and classifies states.",
4402
- "For an absorbing chain add `analysis: classify, absorbing` to get absorption probabilities + expected steps."
5036
+ "Write `S1 -> S2 : 0.3` transitions (colon form is canonical); every state's outgoing probabilities must sum to 1 unless `normalize: true` is set.",
5037
+ "Declare absorbing states with the `absorbing` keyword (`state Broke absorbing`); the engine validates it against the computed structure.",
5038
+ "Use `analysis: classify, stationary` for ergodic chains; add `absorbing` for the fundamental matrix, absorption probabilities, and expected steps."
4403
5039
  ],
4404
5040
  avoid: [
4405
- "Don't let a state's out-probabilities sum to \u2260 1 (the engine errors); use `normalize: true` only if you intend rescaling.",
4406
- "Don't hand-compute the stationary distribution \u2014 it is derived."
5041
+ "Don't let a state's outgoing probabilities sum to anything but 1 \u2014 this hard-errors unless `normalize: true`.",
5042
+ "Don't declare a state `absorbing` unless it has a self-loop of probability 1 and no other out-edges.",
5043
+ "Don't hand-compute the stationary distribution or absorption probabilities \u2014 they are derived."
4407
5044
  ],
4408
5045
  repair: [
4409
- "'row does not sum to 1' \u2014 fix the probabilities or add `normalize: true`.",
4410
- "'probability out of range' \u2014 keep each on (0,1]."
5046
+ "'outgoing probabilities sum to' -> adjust that state's transitions to sum to exactly 1, or add `normalize: true`.",
5047
+ "'is missing a probability' -> add `: <prob>` after the target state id.",
5048
+ "'is declared absorbing but is' -> add `<id> -> <id> : 1` and remove other out-edges, or drop the `absorbing` keyword."
4411
5049
  ]
4412
5050
  },
4413
5051
  gitgraph: {
4414
5052
  type: "gitgraph",
4415
5053
  header: "gitGraph",
4416
5054
  mode: "Mermaid-compatible ordered git operations (commit/branch/checkout/merge)",
5055
+ keywords: 'gitGraph [LR:|TB:|BT:] \xB7 commit [id:"\u2026"] [tag:"\u2026"] [type:NORMAL|REVERSE|HIGHLIGHT] \xB7 branch <name> [order:N] \xB7 checkout <name> | switch <name> \xB7 merge <name> [id:"\u2026"] [tag:"\u2026"] \xB7 cherry-pick id:"\u2026" [parent:"\u2026"] \xB7 %% comment \xB7 config: mainBranchName showBranches showCommitLabel',
4417
5056
  forms: [
4418
5057
  "gitGraph",
4419
5058
  ' commit id: "init"',
@@ -4424,136 +5063,168 @@ var PROFILES = {
4424
5063
  " merge develop"
4425
5064
  ],
4426
5065
  prefer: [
4427
- 'Header `gitGraph` (Mermaid syntax). Operations in order: `commit`, `branch <name>`, `checkout <name>`, `merge <name>`, `cherry-pick id: "\u2026"`.',
4428
- 'Annotate commits with `id: "\u2026"`, `tag: "\u2026"`, `type: HIGHLIGHT|REVERSE|NORMAL`.',
4429
- "`checkout` (or `switch`) before committing onto a branch; `merge <name>` merges that branch into the current one."
5066
+ "Operations must be in execution order: `branch <name>` opens a lane, `checkout <name>` (or `switch`) switches HEAD, `commit` adds a node on the current branch, `merge <name>` merges that branch into the current one.",
5067
+ 'Annotate commits with `id: "\u2026"`, `tag: "\u2026"`, and `type: HIGHLIGHT`/`REVERSE`/`NORMAL`; `cherry-pick id: "\u2026"` copies a commit by id.',
5068
+ "Use `gitGraph LR:` (default) for left-to-right time; add `TB:`/`BT:` inline on the header to change orientation."
4430
5069
  ],
4431
5070
  avoid: [
4432
- "Don't `merge` or `checkout` a branch you never created with `branch`.",
4433
- "Don't merge a branch into itself."
5071
+ "Don't `checkout` or `merge` a branch you have not created with `branch <name>` first.",
5072
+ "Don't `merge` a branch into itself.",
5073
+ "Don't use an option key other than `id`, `tag`, `type`, `order`, `parent`."
4434
5074
  ],
4435
5075
  repair: [
4436
- "'unknown branch' \u2014 add a `branch <name>` before `checkout`/`merge`.",
4437
- "'cannot merge current branch' \u2014 checkout the target lane first."
5076
+ "'checkout of undeclared branch' -> add a `branch <name>` operation before the `checkout`.",
5077
+ "'cannot merge branch' into itself -> `checkout` a different branch before the `merge`.",
5078
+ "'cherry-pick of merge commit' requires parent -> add `parent: \"<id>\"` to the `cherry-pick` line."
4438
5079
  ]
4439
5080
  },
4440
5081
  epc: {
4441
5082
  type: "epc",
4442
5083
  header: 'epc "Title"',
4443
- mode: "alternating events/functions joined by AND/OR/XOR connectors; alternation validated",
5084
+ mode: "alternating events/functions joined by and/or/xor connectors; alternation validated",
5085
+ keywords: 'epc ["title"] [layout: tb|lr] \xB7 event ID ["label"] \xB7 function ID ["label"] (alias: func) \xB7 and ID \xB7 or ID \xB7 xor ID \xB7 A -> B (edge, chainable A -> B -> C) \xB7 A -> B : "label" \xB7 rules: strict event/function alternation; start/end are events; an event may not source an OR/XOR split',
4444
5086
  forms: [
4445
5087
  'epc "Order fulfilment"',
5088
+ " layout: tb",
4446
5089
  ' event E1 "Order received"',
4447
5090
  ' function F1 "Check credit"',
4448
5091
  " xor X1",
4449
5092
  ' event E2 "Credit OK"',
4450
5093
  ' event E3 "Credit rejected"',
5094
+ ' function F2 "Ship goods"',
5095
+ ' function F3 "Notify customer"',
5096
+ ' event E4 "Order shipped"',
5097
+ ' event E5 "Order cancelled"',
4451
5098
  " E1 -> F1 -> X1",
4452
5099
  " X1 -> E2",
4453
- " X1 -> E3"
5100
+ " X1 -> E3",
5101
+ " E2 -> F2 -> E4",
5102
+ " E3 -> F3 -> E5"
4454
5103
  ],
4455
5104
  prefer: [
4456
- "Keyword `epc`. Declare `event`, `function`, and connectors `and`/`or`/`xor` with ids; then the control flow `A -> B -> C`.",
4457
- "Strictly alternate events and functions (a connector may sit between them); start and end with events.",
4458
- "Use a connector node to split/join \u2014 e.g. a `xor` after a function fans out to alternative events."
5105
+ "Declare `event`, `function`, and connector (`and`/`or`/`xor`) nodes with ids first; then wire them with `A -> B` edge lines (chainable: `A -> B -> C`).",
5106
+ "Strictly alternate events and functions: every path follows event \u2192 function \u2192 event \u2192 \u2026 Connectors sit between them but the next typed node must be the opposite kind.",
5107
+ "Use a connector node to split or join; place the connector after a function for any OR/XOR split."
4459
5108
  ],
4460
5109
  avoid: [
4461
- "Don't make an event the source of an OR/XOR split (only functions may decide) \u2014 the engine rejects it.",
4462
- "Don't connect event\u2192event or function\u2192function directly."
5110
+ "Don't make an event the source of an `or`/`xor` split \u2014 only a function may precede an OR/XOR split.",
5111
+ "Don't give an event or function more than one outgoing arc without a connector \u2014 the split belongs on a connector.",
5112
+ "Don't connect two events or two functions in sequence without an intervening node."
4463
5113
  ],
4464
5114
  repair: [
4465
- "'event cannot be an OR/XOR split source' \u2014 put a function (decision) before the connector.",
4466
- "'alternation violated' \u2014 insert the missing event/function between two same-kind nodes."
5115
+ "'is the source of a XOR split' -> insert a `function` between the event and the `xor`/`or` connector.",
5116
+ "'must have a single output' -> add an `and`/`or`/`xor` node and route the multiple arcs from the connector.",
5117
+ "'is used in an edge but never declared' -> add the missing `event`/`function`/connector declaration before the edge."
4467
5118
  ]
4468
5119
  },
4469
5120
  idef0: {
4470
5121
  type: "idef0",
4471
5122
  header: 'idef0 "Title"',
4472
5123
  mode: "function boxes + positional ICOM arrows (input-left, control-top, output-right, mechanism-bottom)",
5124
+ keywords: 'idef0 ["title"] \xB7 node <id> \xB7 purpose "\u2026" \xB7 viewpoint "\u2026" \xB7 function ID "name" [n:N] \xB7 input ID "label" (left) \xB7 control ID "label" (top) \xB7 output ID "label" (right) \xB7 mechanism ID "label" (bottom) \xB7 call ID "label" \xB7 A1 -> A2 "label" (bare target = .input) \xB7 A1 -> A2.input|.control|.mechanism "label" \xB7 (tunnel) flag \xB7 ICOM codes I1/C1/O1/M1 auto-assigned',
4473
5125
  forms: [
4474
- 'idef0 "Manufacture product"',
4475
- ' function A1 "Plan production"',
4476
- ' function A2 "Make parts"',
4477
- ' input A1 "Sales orders"',
4478
- ' control A1 "Production schedule"',
4479
- ' A1 -> A2 "Work plan"',
4480
- ' mechanism A2 "CNC machines"',
4481
- ' output A2 "Product"'
5126
+ 'idef0 "Fulfil customer order"',
5127
+ "node A0",
5128
+ 'function A1 "Validate order"',
5129
+ 'function A2 "Pick and pack"',
5130
+ 'function A3 "Ship and invoice"',
5131
+ 'input A1 "Customer order"',
5132
+ 'control A1 "Credit policy"',
5133
+ 'mechanism A1 "Order management system"',
5134
+ 'A1 -> A2 "Validated order"',
5135
+ 'mechanism A2 "Warehouse staff"',
5136
+ 'A2 -> A3 "Packed shipment"',
5137
+ 'output A3 "Delivered order"'
4482
5138
  ],
4483
5139
  prefer: [
4484
- "Keyword `idef0`. Declare `function` boxes; attach boundary arrows with `input`/`control`/`output`/`mechanism`.",
4485
- 'Box\u2192box flow `A1 -> A2 "label"` lands on A2\'s input by default; use `A1 -> A2.control "\u2026"` to land on another ICOM side.',
4486
- "Remember the ICOM rule: Inputs enter left, Controls enter top, Outputs exit right, Mechanisms enter bottom \u2014 the engine enforces it."
5140
+ 'Declare `function ID "name"` boxes first; attach boundary arrows with `input`/`control`/`output`/`mechanism` + box id + quoted label. Box numbers and ICOM codes are assigned automatically.',
5141
+ 'Box-to-box flow `A1 -> A2 "label"` lands on A2\'s left (`input`) by default; use `A1 -> A2.control "\u2026"` or `A1 -> A2.mechanism "\u2026"` for the top or bottom edge.',
5142
+ "Remember ICOM: Inputs enter left, Controls enter top, Outputs exit right, Mechanisms enter bottom. Use 3\u20136 boxes per diagram (FIPS 183)."
4487
5143
  ],
4488
5144
  avoid: [
4489
- "Don't route an output anywhere but the right edge, or a control anywhere but the top \u2014 the engine rejects it.",
4490
- "Don't hand-number the boxes \u2014 node numbers are assigned."
5145
+ "Don't land a flow on the target's `.output` edge \u2014 outputs exit a box; use `.input`/`.control`/`.mechanism`.",
5146
+ "Don't hand-number boxes with `n:N` unless you number every box \u2014 mixing explicit and auto numbering is an error.",
5147
+ "Don't reference a box id in an arrow before declaring it with `function`."
4491
5148
  ],
4492
5149
  repair: [
4493
- "'output must exit the right edge' \u2014 declare it with `output` / land flows on the correct side.",
4494
- "'unknown box' \u2014 declare the `function` before referencing it."
5150
+ "'arrow references undefined function box' -> add a `function ID \"name\"` line before any arrow that references it.",
5151
+ "'cannot land on the target's .output' -> change `.output` to `.input`, `.control`, or `.mechanism`.",
5152
+ "'must declare at least one' function box -> add at least one `function ID \"name\"` line; a diagram cannot be only arrows."
4495
5153
  ]
4496
5154
  },
4497
5155
  threatmodel: {
4498
5156
  type: "threatmodel",
4499
5157
  header: 'threatmodel "Title"',
4500
- mode: "DFD elements + data flows + trust boundaries; engine maps STRIDE + flags boundary crossings",
5158
+ mode: "DFD elements + data flows + trust boundaries; engine maps STRIDE-per-element + flags boundary crossings",
5159
+ keywords: 'external: Label | external ID: Label \xB7 process ID: Label \xB7 datastore ID: Label [log|audit|journal -> R auto-enabled] \xB7 A -> B : "label" (label required) \xB7 A <-> B : "label" (bidirectional) \xB7 boundary "Name" { id, id } \xB7 title: "\u2026" \xB7 STRIDE-per-element: external S,R \xB7 process all six \xB7 store T,I,D (R conditional) \xB7 flow T,I,D',
4501
5160
  forms: [
4502
- 'threatmodel "Web App \u2014 STRIDE"',
4503
- "external: User",
4504
- "process 1.1: Web Server",
4505
- "datastore D1: User DB",
4506
- "User -> 1.1 : HTTPS Request",
4507
- "1.1 -> D1 : Lookup",
4508
- 'boundary "Internet" { User }',
4509
- 'boundary "DMZ" { 1.1 }'
5161
+ 'threatmodel "E-commerce checkout"',
5162
+ "external: Customer",
5163
+ "external Payment_Gateway: Payment Gateway",
5164
+ "process 1.0: Web App",
5165
+ "process 2.0: Order Service",
5166
+ "datastore D1: Orders DB",
5167
+ "datastore D2: Audit Log",
5168
+ 'Customer -> 1.0 : "HTTPS Checkout"',
5169
+ '1.0 -> 2.0 : "Place order"',
5170
+ '2.0 -> D1 : "Write order"',
5171
+ '2.0 -> Payment_Gateway : "Charge card"',
5172
+ 'boundary "Internet" { Customer, Payment_Gateway }',
5173
+ 'boundary "DMZ" { 1.0 }',
5174
+ 'boundary "Internal" { 2.0, D1, D2 }'
4510
5175
  ],
4511
5176
  prefer: [
4512
- 'Keyword `threatmodel` (or `stride`). Declare `external:`, `process N:`, `datastore D:`; connect with `A -> B : "label"` flows; group ids into `boundary "name" { \u2026 }` trust zones.',
4513
- "Let the engine annotate each element's STRIDE letters and flag flows that cross a trust boundary \u2014 don't list threats by hand.",
4514
- "Name audit/log stores with `log`/`audit`/`journal` so they pick up the conditional Repudiation threat."
5177
+ "Keyword `threatmodel` (alias `stride`). Declare all `external:`, `process ID:`, `datastore ID:` elements first, then flows, then `boundary` groups.",
5178
+ 'Every flow `A -> B : "label"` requires a label \u2014 the colon and label are mandatory; use `<->` for a bidirectional shorthand.',
5179
+ "Name audit/log stores with `log`/`audit`/`journal` to auto-enable the conditional Repudiation threat; let the engine annotate STRIDE and flag boundary-crossing flows."
4515
5180
  ],
4516
5181
  avoid: [
4517
- "Don't connect store\u2192store or external\u2192external (not valid DFD edges).",
4518
- "Don't leave data flows unlabelled."
5182
+ "Don't connect `datastore \u2192 datastore` or `external \u2192 external` directly \u2014 both are hard DFD violations.",
5183
+ "Don't omit the `: label` on a flow; a bare `A -> B` line is rejected.",
5184
+ "Don't put an element id in two different `boundary` blocks \u2014 each element belongs to at most one boundary."
4519
5185
  ],
4520
5186
  repair: [
4521
- "'unknown element' \u2014 declare the external/process/datastore before referencing it.",
4522
- "'flow needs a label' \u2014 add `: \"\u2026\"` to the flow."
5187
+ '\'has no label\' -> add `: "label"` after the target id (e.g. `1.0 -> D1 : "Lookup"`).',
5188
+ "'references unknown element' -> declare the `external:`/`process`/`datastore` before the flow that references it.",
5189
+ "'directly to data store' -> route the flow through a `process` node in between.",
5190
+ "'lists unknown element' -> declare the element before the `boundary { \u2026 }` block that references it."
4523
5191
  ]
4524
5192
  },
4525
5193
  welding: {
4526
5194
  type: "welding",
4527
5195
  header: "welding [standard: aws|iso-a|iso-b]",
4528
5196
  mode: "reference-line callouts; one joint block per joint, weld glyphs above/below the line",
5197
+ keywords: 'joint "label" { arrow: <weldspec> \xB7 other: <weldspec> \xB7 both: <weldspec> \xB7 around \xB7 field \xB7 tail: "\u2026" } \xB7 weldspec = <type> [size=n] [len=n] [pitch=n] [angle=deg] [root=n] [throat=n] [contour=flush|convex|concave] [finish=G|M|C|R|H|U] \xB7 types: fillet square vgroove bevel ugroove jgroove flarev flarebevel plug slot spot seam back backing surfacing edge \xB7 standard: aws (default) iso-a iso-b',
4529
5198
  forms: [
4530
- 'welding "Bracket welds"',
4531
- 'joint "bracket to plate" {',
5199
+ 'welding "Bracket assembly"',
5200
+ 'joint "gusset to column" {',
4532
5201
  " arrow: fillet size=8 len=50 pitch=150",
4533
5202
  " other: fillet size=6",
4534
5203
  " around",
4535
5204
  " field",
4536
- ' tail: "GTAW"',
5205
+ ' tail: "GMAW"',
4537
5206
  "}",
4538
- 'joint "butt weld" {',
4539
- " arrow: vgroove angle=60 root=3 throat=12",
5207
+ 'joint "splice plate (butt)" {',
5208
+ " arrow: vgroove angle=60 root=3 throat=12 contour=flush finish=G",
4540
5209
  " other: backing",
5210
+ ' tail: "SMAW; E7018"',
4541
5211
  "}"
4542
5212
  ],
4543
5213
  prefer: [
4544
- 'Keyword `welding`; one `joint "label" { \u2026 }` block per joint. Put a weld on `arrow:` (arrow side) and/or `other:` (other side); `both:` is shorthand for the same weld on both sides.',
4545
- "A weldspec is `<type> key=value \u2026`: `size=` (left of symbol), `len=`/`pitch=` (length-pitch, right), `angle=`/`root=` (groove only), `throat=`, `contour=flush|convex|concave`, `finish=G|M|C|R|H|U`.",
4546
- 'Flags on their own line: `around` (weld-all-around), `field` (site weld). Process/spec/NDE goes in `tail: "GTAW; WPS-12"`.',
4547
- "Types: fillet \xB7 square \xB7 vgroove \xB7 bevel \xB7 ugroove \xB7 jgroove \xB7 flarev \xB7 flarebevel \xB7 plug \xB7 slot \xB7 spot \xB7 seam \xB7 back \xB7 backing \xB7 surfacing \xB7 edge."
5214
+ 'One `joint "label" { \u2026 }` block per joint. Weld sides are `arrow:` (arrow side), `other:` (other side), or `both:` (same spec both sides).',
5215
+ "A weldspec is `<type> key=value \u2026`: `size=` (leg size / plug diameter), `len=`/`pitch=` (intermittent), `angle=`/`root=` (groove only), `throat=`, `contour=flush|convex|concave`, `finish=G|M|C|R|H|U`.",
5216
+ 'Supplementary symbols on their own line inside the joint: `around` (weld-all-around), `field` (site weld). Process/spec/NDE goes in `tail: "GTAW; WPS-12"`.',
5217
+ "Choose `standard: iso-a` for the dual solid+dashed reference line; default `aws` uses a single line."
4548
5218
  ],
4549
5219
  avoid: [
4550
- "Don't put `angle=` on a fillet/plug/spot \u2014 angle is groove-only (the engine warns).",
4551
- "Don't use `both:` for plug/slot/surfacing \u2014 they are single-side; surfacing is arrow-side only.",
4552
- "Don't give a fillet without `size=`, or a `pitch=` without `len=`."
5220
+ "Don't put `angle=` on a `fillet`, `plug`, `spot`, or `seam` \u2014 angle is groove-only (the engine warns).",
5221
+ "Don't use `both:` for `plug`, `slot`, or `surfacing` \u2014 those are single-side; `surfacing` is arrow-side only.",
5222
+ "Don't give a `fillet` without `size=`, or a `pitch=` without `len=` (an intermittent weld is length-pitch)."
4553
5223
  ],
4554
5224
  repair: [
4555
- "'a fillet weld needs a leg size' \u2014 add `size=\u2026` to the fillet spec.",
4556
- "'angle= only applies to groove welds' \u2014 drop `angle=`, or change the type to a groove."
5225
+ "'a fillet weld needs a leg size' -> add `size=N` to the fillet weldspec (e.g. `arrow: fillet size=8`).",
5226
+ "'angle= only applies to groove welds' -> drop `angle=`, or change the type to a groove such as `vgroove`.",
5227
+ "'pitch= needs a length=' -> add `len=N` alongside `pitch=N` on the same weldspec."
4557
5228
  ]
4558
5229
  }
4559
5230
  };
@@ -4583,6 +5254,7 @@ function buildCanonicalSyntax(profile) {
4583
5254
  `- Canonical type: \`${profile.type}\``,
4584
5255
  `- Canonical header: \`${profile.header}\``,
4585
5256
  `- Preferred mode: ${profile.mode}`,
5257
+ profile.keywords ? `- Keywords: ${profile.keywords}` : "",
4586
5258
  bulletSection("Core forms", profile.forms),
4587
5259
  bulletSection("Prefer", profile.prefer),
4588
5260
  bulletSection("Avoid by default", profile.avoid),
@@ -4695,18 +5367,27 @@ function toValidationError(diagnostic, type) {
4695
5367
  column: diagnostic.column,
4696
5368
  source: diagnostic.source,
4697
5369
  message: diagnostic.message,
4698
- hint: diagnostic.hint ?? repairHint(type)
5370
+ hint: diagnostic.hint ?? repairHint(type, diagnostic.message)
4699
5371
  };
4700
5372
  }
4701
- function repairHint(type) {
5373
+ function repairHint(type, message) {
4702
5374
  const resolved = type ? resolveDiagramType(type) : void 0;
4703
5375
  const profile = resolved ? getGenerationProfile(resolved) : void 0;
4704
- const typeHint = profile?.repair[0];
5376
+ const typeHint = message ? profile ? matchRepair(profile.repair, message) : void 0 : profile?.repair[0];
4705
5377
  return [
4706
5378
  typeHint,
4707
5379
  "Fix the reported DSL error, then call validateDsl again before rendering or returning DSL."
4708
5380
  ].filter(Boolean).join(" ");
4709
5381
  }
5382
+ function matchRepair(repairs, message) {
5383
+ for (const r of repairs) {
5384
+ const quoted = r.match(/^['"]([^'"]+)['"]/);
5385
+ if (!quoted) continue;
5386
+ const fragment = quoted[1].split(":")[0].trim();
5387
+ if (fragment.length >= 6 && message.includes(fragment)) return r;
5388
+ }
5389
+ return void 0;
5390
+ }
4710
5391
 
4711
5392
  exports.DIAGRAM_REGISTRY = DIAGRAM_REGISTRY;
4712
5393
  exports.DIAGRAM_SINCE = DIAGRAM_SINCE;
@@ -4719,5 +5400,5 @@ exports.listDiagrams = listDiagrams;
4719
5400
  exports.renderDsl = renderDsl;
4720
5401
  exports.resolveDiagramType = resolveDiagramType;
4721
5402
  exports.validateDsl = validateDsl;
4722
- //# sourceMappingURL=chunk-FQ4JUNTE.cjs.map
4723
- //# sourceMappingURL=chunk-FQ4JUNTE.cjs.map
5403
+ //# sourceMappingURL=chunk-HKOPXQYU.cjs.map
5404
+ //# sourceMappingURL=chunk-HKOPXQYU.cjs.map