@aryacode/dsh-engineering 0.8.4 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +7 -1
  3. package/cordis.patch.yml +7 -0
  4. package/entries/resources.js +23 -0
  5. package/package.json +6 -3
  6. package/vendor/ftview/lib/edit/tools.d.ts +1 -1
  7. package/vendor/ftview/lib/edit/tools.js +26 -6
  8. package/vendor/ftview/lib/edit.js +2 -1
  9. package/vendor/ftview/lib/session.d.ts +9 -0
  10. package/vendor/ftview/lib/session.js +11 -0
  11. package/vendor/ftview/lib/style.d.ts +60 -0
  12. package/vendor/ftview/lib/style.js +109 -0
  13. package/vendor/l5x/lib/engine/style.d.ts +67 -0
  14. package/vendor/l5x/lib/engine/style.js +119 -0
  15. package/vendor/l5x/lib/session.d.ts +9 -0
  16. package/vendor/l5x/lib/session.js +11 -0
  17. package/vendor/resources/LICENSE-CONTENT.md +17 -0
  18. package/vendor/resources/SKILL.md +27 -0
  19. package/vendor/resources/SOURCE.json +6 -0
  20. package/vendor/resources/corpus/craft/craft-comments-and-intent.md +29 -0
  21. package/vendor/resources/corpus/craft/craft-debounce-and-filtering.md +29 -0
  22. package/vendor/resources/corpus/craft/craft-fault-handling.md +32 -0
  23. package/vendor/resources/corpus/craft/craft-naming.md +30 -0
  24. package/vendor/resources/corpus/craft/craft-review-checklist.md +29 -0
  25. package/vendor/resources/corpus/craft/craft-units-and-ranges.md +32 -0
  26. package/vendor/resources/corpus/iec/iec-ld-fbd-equivalence.md +31 -0
  27. package/vendor/resources/corpus/iec/iec-sequences-and-sfc.md +32 -0
  28. package/vendor/resources/corpus/iec/iec-st-structure.md +33 -0
  29. package/vendor/resources/corpus/iec/iec-state-machines.md +33 -0
  30. package/vendor/resources/corpus/iec/iec-task-model.md +31 -0
  31. package/vendor/resources/corpus/iec/iec-types-and-scaling.md +32 -0
  32. package/vendor/resources/corpus/optix/optix-alarms-and-trends.md +30 -0
  33. package/vendor/resources/corpus/optix/optix-bindings-and-expressions.md +30 -0
  34. package/vendor/resources/corpus/optix/optix-netlogic-usage.md +29 -0
  35. package/vendor/resources/corpus/optix/optix-ui-structure.md +29 -0
  36. package/vendor/resources/corpus/rockwell/rockwell-aoi-and-udt.md +32 -0
  37. package/vendor/resources/corpus/rockwell/rockwell-fault-and-alarm-words.md +31 -0
  38. package/vendor/resources/corpus/rockwell/rockwell-hmi-handshake.md +33 -0
  39. package/vendor/resources/corpus/rockwell/rockwell-program-structure.md +32 -0
  40. package/vendor/resources/corpus/rockwell/rockwell-safety-boundaries.md +31 -0
  41. package/vendor/resources/corpus/rockwell/rockwell-tag-naming.md +33 -0
  42. package/vendor/resources/corpus/view/view-alarm-presentation.md +32 -0
  43. package/vendor/resources/corpus/view/view-colour-and-contrast.md +31 -0
  44. package/vendor/resources/corpus/view/view-faceplates.md +32 -0
  45. package/vendor/resources/corpus/view/view-hpm-01.md +33 -0
  46. package/vendor/resources/corpus/view/view-navigation.md +31 -0
  47. package/vendor/resources/corpus/view/view-popup-patterns.md +31 -0
  48. package/vendor/resources/corpus/view/view-screen-hierarchy.md +31 -0
  49. package/vendor/resources/corpus/yaskawa/yaskawa-cam-and-gear.md +31 -0
  50. package/vendor/resources/corpus/yaskawa/yaskawa-error-handling.md +31 -0
  51. package/vendor/resources/corpus/yaskawa/yaskawa-motion-function-blocks.md +32 -0
  52. package/vendor/resources/corpus/yaskawa/yaskawa-pou-organisation.md +29 -0
  53. package/vendor/resources/corpus/yaskawa/yaskawa-task-rates.md +30 -0
  54. package/vendor/resources/index.json +873 -0
  55. package/vendor/resources/lib/corpus.d.ts +80 -0
  56. package/vendor/resources/lib/corpus.js +126 -0
  57. package/vendor/resources/lib/index.d.ts +55 -0
  58. package/vendor/resources/lib/index.js +88 -0
  59. package/vendor/resources/lib/profile.d.ts +113 -0
  60. package/vendor/resources/lib/profile.js +52 -0
  61. package/vendor/resources/lib/store.d.ts +22 -0
  62. package/vendor/resources/lib/store.js +35 -0
  63. package/vendor/resources/lib/tools.d.ts +138 -0
  64. package/vendor/resources/lib/tools.js +190 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Infer the house style of one L5X project.
3
+ *
4
+ * Every number here is counted from the export, and the shape is deliberately
5
+ * the bundle-wide profile schema (`packages/resources/src/profile.ts`) rather
6
+ * than an L5X-specific one, so the resources row can validate and store it.
7
+ * Nothing is inferred from fewer than a handful of samples, and the confidence
8
+ * field says how much of the project the inference actually saw.
9
+ *
10
+ * @module dsh-engineering-l5x/engine/style
11
+ */
12
+ /** Classify one identifier's case convention. */
13
+ function caseOf(identifier) {
14
+ if (/^[A-Z][A-Za-z0-9]*$/u.test(identifier))
15
+ return 'PascalCase';
16
+ if (/^[a-z][A-Za-z0-9]*$/u.test(identifier))
17
+ return 'camelCase';
18
+ if (/^[A-Za-z0-9]+(?:_[A-Za-z0-9]+)+$/u.test(identifier))
19
+ return 'snake_case';
20
+ return 'other';
21
+ }
22
+ /** The most common value in a counter, with its count. */
23
+ function top(counts) {
24
+ let best;
25
+ for (const [value, count] of counts) {
26
+ if (best === undefined || count > best.count)
27
+ best = { value, count };
28
+ }
29
+ return best;
30
+ }
31
+ /**
32
+ * Read the style of one project.
33
+ *
34
+ * @param project - Snapshot the session opened.
35
+ * @returns The profile.
36
+ */
37
+ export function styleProfileOf(project) {
38
+ const document = project.document;
39
+ const rungs = document.routines
40
+ .filter(routine => routine.body.type === 'RLL')
41
+ .flatMap(routine => (routine.body.type === 'RLL' ? routine.body.rungs.map(rung => ({ routine: routine.name, rung })) : []));
42
+ const identifiers = new Map();
43
+ const prefixes = new Map();
44
+ const cases = new Map();
45
+ let sealIn = 0;
46
+ let latchInstructions = 0;
47
+ let commented = 0;
48
+ const evidence = [];
49
+ const aoiNames = new Set(document.aois.map(aoi => aoi.name.toUpperCase()));
50
+ let firstSealInRung;
51
+ for (const { routine, rung } of rungs) {
52
+ if (rung.comment !== undefined)
53
+ commented += 1;
54
+ const text = rung.text;
55
+ for (const match of text.matchAll(/[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)*/gu)) {
56
+ const whole = match[0];
57
+ if (aoiNames.has(whole.toUpperCase()))
58
+ continue;
59
+ const base = whole.split('.')[0] ?? whole;
60
+ if (base.length < 5)
61
+ continue;
62
+ identifiers.set(base, (identifiers.get(base) ?? 0) + 1);
63
+ const parts = base.split('_');
64
+ if (parts.length > 1) {
65
+ const prefix = parts[0];
66
+ if (/^[A-Z][A-Za-z]{1,6}$/u.test(prefix) && base.length > prefix.length + 3) {
67
+ prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);
68
+ }
69
+ }
70
+ const kind = caseOf(base);
71
+ if (kind !== 'other')
72
+ cases.set(kind, (cases.get(kind) ?? 0) + 1);
73
+ }
74
+ for (const coil of text.matchAll(/OTE\(([^),]+)\)/gu)) {
75
+ const target = (coil[1] ?? '').trim();
76
+ const contact = `XIC(${target})`;
77
+ const coilIndex = coil.index ?? 0;
78
+ const contactIndex = text.indexOf(contact);
79
+ if (contactIndex >= 0 && contactIndex < coilIndex) {
80
+ sealIn += 1;
81
+ if (firstSealInRung === undefined)
82
+ firstSealInRung = rung.number;
83
+ }
84
+ }
85
+ latchInstructions += (text.match(/OT[LU]\(/gu) ?? []).length;
86
+ }
87
+ const total = rungs.length;
88
+ const dominant = top(prefixes);
89
+ const dominantCase = top(cases);
90
+ const prefixShare = dominant === undefined ? 0 : dominant.count / Math.max(1, [...prefixes.values()].reduce((sum, count) => sum + count, 0));
91
+ const routineCases = new Map();
92
+ for (const routine of document.routines) {
93
+ const kind = caseOf(routine.name);
94
+ if (kind !== 'other')
95
+ routineCases.set(kind, (routineCases.get(kind) ?? 0) + 1);
96
+ }
97
+ const routineCase = top(routineCases);
98
+ if (dominant !== undefined)
99
+ evidence.push({ count: dominant.count, note: `tag prefix "${dominant.value}" appears ${dominant.count} time(s)` });
100
+ if (firstSealInRung !== undefined)
101
+ evidence.push({ line: firstSealInRung, count: sealIn, note: `seal-in rungs detected from rung ${firstSealInRung}` });
102
+ evidence.push({ count: total, note: `${total} ladder rung(s) read, ${commented} with a comment` });
103
+ const confidence = total >= 50 && prefixShare >= 0.5 ? 'high' : total >= 10 ? 'medium' : 'low';
104
+ return {
105
+ schema: 1,
106
+ source: { kind: 'l5x', path: project.path, sha256: project.sha256 },
107
+ naming: {
108
+ tags: { prefixes: [...prefixes].sort((left, right) => right[1] - left[1]).slice(0, 6).map(([value, count]) => ({ value, count })), case: dominantCase?.value ?? 'PascalCase' },
109
+ routines: { case: routineCase?.value ?? 'PascalCase', examples: document.routines.slice(0, 5).map(routine => routine.name) },
110
+ },
111
+ idioms: {
112
+ sealIn: { count: sealIn, form: sealIn > 0 ? 'XIC(seal) in parallel with the start contact, in series with the stop contact' : 'not observed' },
113
+ latch: { count: latchInstructions },
114
+ },
115
+ comments: { total, commented, ratio: total === 0 ? 0 : Number((commented / total).toFixed(2)) },
116
+ evidence,
117
+ confidence,
118
+ };
119
+ }
@@ -12,6 +12,7 @@
12
12
  * @module dsh-engineering-l5x/session
13
13
  */
14
14
  import { type L5xDocument } from './model/index.ts';
15
+ import { type L5xStyleProfile } from './engine/style.ts';
15
16
  import type { ExecContext } from './core/tool.ts';
16
17
  import { L5xError } from './core/errors.ts';
17
18
  import type { FsService, FsTarget } from './core/tool.ts';
@@ -93,6 +94,14 @@ export declare class ProjectSession {
93
94
  * @returns The open project.
94
95
  */
95
96
  require(projectId: string | undefined, exec?: ExecContext): Promise<ProjectSnapshot>;
97
+ /**
98
+ * Read the house style of one open project.
99
+ *
100
+ * @param projectId - Explicit handle, or undefined for the chat's only project.
101
+ * @param exec - Calling context.
102
+ * @returns The profile in the shared resources schema.
103
+ */
104
+ styleProfile(projectId: string | undefined, exec?: ExecContext): Promise<L5xStyleProfile>;
96
105
  /**
97
106
  * Resolve one path through the filesystem provider without reading it.
98
107
  *
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { createHash, randomUUID } from 'node:crypto';
15
15
  import { L5xParseError, loadL5x } from "./model/index.js";
16
+ import { styleProfileOf } from "./engine/style.js";
16
17
  import { L5xError } from "./core/errors.js";
17
18
  import { decodeExport } from "./fs.js";
18
19
  /** A failure raised by an analysis engine. */
@@ -130,6 +131,16 @@ export class ProjectSession {
130
131
  }
131
132
  return project;
132
133
  }
134
+ /**
135
+ * Read the house style of one open project.
136
+ *
137
+ * @param projectId - Explicit handle, or undefined for the chat's only project.
138
+ * @param exec - Calling context.
139
+ * @returns The profile in the shared resources schema.
140
+ */
141
+ async styleProfile(projectId, exec) {
142
+ return styleProfileOf(await this.require(projectId, exec));
143
+ }
133
144
  /**
134
145
  * Resolve one path through the filesystem provider without reading it.
135
146
  *
@@ -0,0 +1,17 @@
1
+ # Content licence
2
+
3
+ The pages under `corpus/` are written for this bundle. They summarise engineering practice and
4
+ implementation rules; they do **not** reproduce the text of any standard or vendor manual.
5
+
6
+ Each page names its basis in front matter (`basis:`). Those names are attribution, not endorsement:
7
+
8
+ - **IEC 61131-3** — the programmable-controller languages the pages describe (ST, LD, FBD, SFC).
9
+ - **ISA-101** — human-machine interface design, especially the high-performance HMI model.
10
+ - **ISA-18.2** — alarm management lifecycle and presentation.
11
+ - **Rockwell / Yaskawa / FactoryTalk** — vendor documentation and field practice for those products.
12
+
13
+ Rule IDs (`CRAFT-NAME-01`, `VIEW-HPM-02`, …) identify this bundle's own rules. They are not the
14
+ standards' clause numbers, and a finding that cites one is this bundle's opinion about your code, not a
15
+ certification.
16
+
17
+ Standards documents are copyrighted. If you need the normative text, obtain it from the issuing body.
@@ -0,0 +1,27 @@
1
+ # Engineering standards
2
+
3
+ Use this skill whenever you write, change or review control code or HMI screens — before you author, not
4
+ after.
5
+
6
+ ## Read first
7
+
8
+ - `engineering_reference` with no arguments lists the corpus. Search it for the language and the domain
9
+ you are about to touch (`iec state machine`, `rockwell naming`, `view alarm`, `craft fault`), then read
10
+ the page. Each page carries rule IDs; cite the ID when you report a finding.
11
+ - When a project or export is open, call `engineering_profile` **before** writing. It reports the naming,
12
+ idioms and visual conventions the plant already uses, with evidence and a confidence level. Follow the
13
+ profile where it is confident; where it is low, say so and ask.
14
+
15
+ ## Order of work
16
+
17
+ 1. Read the profile (existing project) or the standard pages (new work).
18
+ 2. Author the change with the project's own names and visual language.
19
+ 3. `engineering_review` on the draft. Fix findings you agree with; explain the ones you do not.
20
+ 4. Then run the product's own validation (`draft_validate`, `ftview_validate`, `mw_compile`).
21
+
22
+ ## Honesty rules
23
+
24
+ - A profile is evidence, not authority: quote the counts and the source path when you rely on it.
25
+ - A rule ID is this bundle's opinion. Never present it as a standards clause or a certification.
26
+ - Do not reproduce standards text; link the page instead.
27
+ - If the corpus or a profile is unavailable, say which one and continue with the product's own checks.
@@ -0,0 +1,6 @@
1
+ {
2
+ "corpus": 1,
3
+ "built": "2026-10-27T16:39:24.478Z",
4
+ "digest": "49ea7864020a72c7affc8f13bbc353a21226e63c1d973111e05898a525dd91de",
5
+ "pages": 34
6
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: craft-comments-and-intent
3
+ title: Comments that explain why
4
+ domain: craft
5
+ level: intro
6
+ basis: [field practice]
7
+ tags: [comments, intent]
8
+ applies_to: [l5x, ftview, mwi, optix]
9
+ rules: [CRAFT-COMMENT-01, CRAFT-COMMENT-02, CRAFT-COMMENT-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A comment that repeats the code is noise; a comment that explains the decision is maintenance.
14
+
15
+ ## Rules
16
+
17
+ - **CRAFT-COMMENT-01** Comment the intent and the constraint (`// 250 ms: debounce for a bouncing
18
+ proximity switch`), not the instruction.
19
+ - **CRAFT-COMMENT-02** Every non-obvious rung gets a rung comment; every routine gets one line of purpose.
20
+ - **CRAFT-COMMENT-03** Record the change reason and date where the project does that, and never leave
21
+ commented-out logic as the only record.
22
+
23
+ ## Apply
24
+
25
+ Write the comment as if the reader has the drawing but not the meeting.
26
+
27
+ ## Anti-patterns
28
+
29
+ `// set bit`; a wall of comments at the top that no one updates; TODOs without an owner.
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: craft-debounce-and-filtering
3
+ title: Debounce, hysteresis and filtering
4
+ domain: craft
5
+ level: practitioner
6
+ basis: [field practice]
7
+ tags: [filter, debounce, hysteresis]
8
+ applies_to: [l5x, mwi]
9
+ rules: [CRAFT-FILTER-01, CRAFT-FILTER-02]
10
+ ---
11
+ ## Principle
12
+
13
+ Real sensors are noisy and real processes overshoot. Filter in the PLC with a stated time, not in the
14
+ operator's head.
15
+
16
+ ## Rules
17
+
18
+ - **CRAFT-FILTER-01** Debounce discrete inputs with a timer sized from the sensor's bounce, and document
19
+ the time next to the timer.
20
+ - **CRAFT-FILTER-02** Use hysteresis for thresholds that would otherwise chatter, and keep the band in one
21
+ place per signal.
22
+
23
+ ## Apply
24
+
25
+ When a value "flickers" on the HMI, find the filter time before adding another one downstream.
26
+
27
+ ## Anti-patterns
28
+
29
+ Filters stacked three deep with no owner; a debounce so long it hides a real trip.
@@ -0,0 +1,32 @@
1
+ ---
2
+ id: craft-fault-handling
3
+ title: Faults: detect, latch, annunciate, reset
4
+ domain: craft
5
+ level: practitioner
6
+ basis: [field practice]
7
+ tags: [faults, reset, diagnostics]
8
+ applies_to: [l5x, mwi, optix]
9
+ rules: [CRAFT-FAULT-01, CRAFT-FAULT-02, CRAFT-FAULT-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A fault is a fact about the machine, not a message. The logic must detect it, remember it, tell someone,
14
+ and define recovery.
15
+
16
+ ## Rules
17
+
18
+ - **CRAFT-FAULT-01** Detect at the source and set one bit for one cause; do not infer a fault from a
19
+ downstream symptom.
20
+ - **CRAFT-FAULT-02** Latch with a reset that only succeeds when the cause is gone; a reset that always
21
+ clears is a way to run a broken machine.
22
+ - **CRAFT-FAULT-03** Record first-out and the time, and surface it where the operator acts.
23
+
24
+ ## Apply
25
+
26
+ For each fault, write the four sentences: what detects it, what it stops, how it is shown, how it is
27
+ cleared.
28
+
29
+ ## Anti-patterns
30
+
31
+ Faults that clear themselves on the next scan; one reset for unrelated faults; a fault with no operator
32
+ message.
@@ -0,0 +1,30 @@
1
+ ---
2
+ id: craft-naming
3
+ title: Naming that survives the project
4
+ domain: craft
5
+ level: intro
6
+ basis: [field practice]
7
+ tags: [naming, readability]
8
+ applies_to: [l5x, ftview, mwi, optix]
9
+ rules: [CRAFT-NAME-01, CRAFT-NAME-02, CRAFT-NAME-03]
10
+ ---
11
+ ## Principle
12
+
13
+ Names are the interface between the code and everyone who will read it after you.
14
+
15
+ ## Rules
16
+
17
+ - **CRAFT-NAME-01** One case convention per project (PascalCase for tags is common; follow the project).
18
+ Consistency matters more than the choice.
19
+ - **CRAFT-NAME-02** Booleans read as assertions (`MotorRunning`, `DoorClosed`), not as questions or
20
+ negatives (`NotStopped`).
21
+ - **CRAFT-NAME-03** Put units and ranges in the description, not hidden in the name (`Temp_C`, `Speed_mps`).
22
+
23
+ ## Apply
24
+
25
+ When a name does not fit the project's scheme, prefer the project's scheme; when adding a new concept, add
26
+ it to the scheme and document the prefix.
27
+
28
+ ## Anti-patterns
29
+
30
+ `tmp`, `data`, `flag`; names that encode the wiring (`DI_3`); renaming a published tag without a migration.
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: craft-review-checklist
3
+ title: Pre-handover review checklist
4
+ domain: craft
5
+ level: practitioner
6
+ basis: [field practice]
7
+ tags: [review, handover, checklist]
8
+ applies_to: [l5x, ftview, mwi, optix]
9
+ rules: [CRAFT-REVIEW-01, CRAFT-REVIEW-02]
10
+ ---
11
+ ## Principle
12
+
13
+ Handover is a review, not a save. Walk the same list every time, and record what was not checked.
14
+
15
+ ## Rules
16
+
17
+ - **CRAFT-REVIEW-01** Before handover: every fault has a message and a reset, every screen has a way back,
18
+ every changed tag is documented, and the backup is taken and named.
19
+ - **CRAFT-REVIEW-02** Distinguish "checked" from "not checked" in the handover note; an unchecked item is
20
+ not a pass.
21
+
22
+ ## Apply
23
+
24
+ Run the product's own checks first, then `engineering_review`, then the human walkthrough with the
25
+ operator.
26
+
27
+ ## Anti-patterns
28
+
29
+ A handover that lists only what changed; signing off a screen nobody navigated.
@@ -0,0 +1,32 @@
1
+ ---
2
+ id: craft-units-and-ranges
3
+ title: Units, ranges and engineering values
4
+ domain: craft
5
+ level: intro
6
+ basis: [field practice]
7
+ tags: [units, range, scaling]
8
+ applies_to: [l5x, mwi, optix]
9
+ rules: [CRAFT-UNIT-01, CRAFT-UNIT-02, CRAFT-UNIT-03]
10
+ ---
11
+ ## Principle
12
+
13
+ Every number the operator sees should have a unit, a range and a resolution. A number without those is a
14
+ guess.
15
+
16
+ ## Rules
17
+
18
+ - **CRAFT-UNIT-01** Carry raw counts and engineering units as separate values with one documented
19
+ conversion.
20
+ - **CRAFT-UNIT-02** State the valid range and what happens outside it (clamp, fault, ignore) at the
21
+ conversion point.
22
+ - **CRAFT-UNIT-03** Display the unit next to the value and keep the decimal places consistent with the
23
+ resolution.
24
+
25
+ ## Apply
26
+
27
+ When a value moves to a new screen, move its unit and range with it.
28
+
29
+ ## Anti-patterns
30
+
31
+ Two conversions for one signal; a percentage displayed as a fraction; more decimal places than the sensor
32
+ has.
@@ -0,0 +1,31 @@
1
+ ---
2
+ id: iec-ld-fbd-equivalence
3
+ title: Ladder, FBD and ST: same logic, different reading
4
+ domain: iec
5
+ level: intro
6
+ basis: [IEC 61131-3]
7
+ tags: [ladder, fbd, st, translation]
8
+ applies_to: [l5x, mwi]
9
+ rules: [IEC-LANG-01, IEC-LANG-02]
10
+ ---
11
+ ## Principle
12
+
13
+ The languages are notations, not designs. Choose the one that makes the intent obvious and keep one
14
+ implementation of each behaviour.
15
+
16
+ ## Rules
17
+
18
+ - **IEC-LANG-01** Ladder for permissives, interlocks and outputs an electrician will trace; ST for
19
+ arithmetic, loops and state machines; FBD for signal flow. Do not translate a whole routine because you
20
+ prefer another language.
21
+ - **IEC-LANG-02** One behaviour, one implementation. A seal-in duplicated in LD and ST will diverge; if a
22
+ block is reused, make it an FB/AOI and call it.
23
+
24
+ ## Apply
25
+
26
+ When reviewing inherited code, read it in its own language before proposing a rewrite. A "modernisation"
27
+ that changes scan-order semantics is a behaviour change.
28
+
29
+ ## Anti-patterns
30
+
31
+ ST one-liners that hide three outputs; ladder with arithmetic chains no one can trace.
@@ -0,0 +1,32 @@
1
+ ---
2
+ id: iec-sequences-and-sfc
3
+ title: Sequences, steps and timeouts
4
+ domain: iec
5
+ level: practitioner
6
+ basis: [IEC 61131-3]
7
+ tags: [sequence, sfc, step]
8
+ applies_to: [mwi, l5x]
9
+ rules: [IEC-SEQ-01, IEC-SEQ-02, IEC-SEQ-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A sequence is a state machine whose states are process steps. The difference is that each step has a
14
+ completion condition and a timeout.
15
+
16
+ ## Rules
17
+
18
+ - **IEC-SEQ-01** Give every step a timeout and a defined action on expiry. A sequence that can wait forever
19
+ is a fault waiting for an operator.
20
+ - **IEC-SEQ-02** Separate the step advance from the step action: advance on `done OR timeout`, act on the
21
+ current step.
22
+ - **IEC-SEQ-03** Make hold, abort and reset explicit transitions to named states, not flags that freeze
23
+ the sequence wherever it was.
24
+
25
+ ## Apply
26
+
27
+ Draw the sequence (states and transitions) before coding it, and keep the drawing with the project. SFC is
28
+ appropriate when the drawing is the specification; a `CASE` machine is simpler when steps share actions.
29
+
30
+ ## Anti-patterns
31
+
32
+ Advancing on a timer alone; a reset that clears outputs without leaving the step.
@@ -0,0 +1,33 @@
1
+ ---
2
+ id: iec-st-structure
3
+ title: Structured Text that reads like a specification
4
+ domain: iec
5
+ level: practitioner
6
+ basis: [IEC 61131-3]
7
+ tags: [st, structure, declarations]
8
+ applies_to: [mwi, optix, l5x]
9
+ rules: [IEC-ST-01, IEC-ST-02, IEC-ST-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A POU should be readable top to bottom without scrolling sideways: declarations grouped, one intent per
14
+ block, no hidden state. The reader is a maintenance technician at 2 a.m., not the author.
15
+
16
+ ## Rules
17
+
18
+ - **IEC-ST-01** Group declarations by role (`VAR_INPUT`, `VAR_OUTPUT`, `VAR`), then by purpose, with a
19
+ comment above each group. Do not interleave.
20
+ - **IEC-ST-02** One POU, one responsibility. A POU that sequences, scales, filters and writes HMI data has
21
+ four reasons to change; split it.
22
+ - **IEC-ST-03** No side effects in conditions. A call inside an `IF` that changes state makes the scan
23
+ order part of the logic; call it explicitly, one statement per line.
24
+
25
+ ## Apply
26
+
27
+ Order the body: input conditioning, permissives, sequence or control, outputs, diagnostics. A reader can
28
+ then stop at the block they need.
29
+
30
+ ## Anti-patterns
31
+
32
+ Deeply nested `IF` (three levels or more) hiding permissives; magic numbers instead of named constants;
33
+ declarations added wherever there was space.
@@ -0,0 +1,33 @@
1
+ ---
2
+ id: iec-state-machines
3
+ title: State machines that fail predictably
4
+ domain: iec
5
+ level: practitioner
6
+ basis: [IEC 61131-3]
7
+ tags: [state-machine, case, sequence]
8
+ applies_to: [mwi, l5x]
9
+ rules: [IEC-SM-01, IEC-SM-02, IEC-SM-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A state machine is a contract: exactly one state is active, every transition has a cause, and an
14
+ unexpected state is handled rather than ignored.
15
+
16
+ ## Rules
17
+
18
+ - **IEC-SM-01** Use one integer or enumeration `step` for the active state and a `CASE` (ST) or
19
+ integer-compare chain (LD) to act on it. Do not scatter `IF step =` tests through the program.
20
+ - **IEC-SM-02** Every `CASE` has an `ELSE` (or a default branch) that forces a known safe state and raises
21
+ a diagnostic. An out-of-range step must never leave outputs energised.
22
+ - **IEC-SM-03** Entry actions run once on the transition; continuous actions live in the state body. Mixing
23
+ them makes a re-entry repeat motion.
24
+
25
+ ## Apply
26
+
27
+ Name states after the machine condition (`Idle`, `Homing`, `Ready`, `Cutting`, `Faulted`), not after
28
+ numbers in the HMI. Keep a parallel `stepTime` for timeouts.
29
+
30
+ ## Anti-patterns
31
+
32
+ Two variables claiming to be the state; timers inside the transition condition; a `GOTO` around the
33
+ `CASE`.
@@ -0,0 +1,31 @@
1
+ ---
2
+ id: iec-task-model
3
+ title: Tasks, scan time and determinism
4
+ domain: iec
5
+ level: specialist
6
+ basis: [IEC 61131-3]
7
+ tags: [task, scan, watchdog]
8
+ applies_to: [mwi, l5x]
9
+ rules: [IEC-TASK-01, IEC-TASK-02, IEC-TASK-03]
10
+ ---
11
+ ## Principle
12
+
13
+ The task schedule is part of the program. A control loop that moved to a slower task is a behaviour
14
+ change even when the code is untouched.
15
+
16
+ ## Rules
17
+
18
+ - **IEC-TASK-01** Put motion and fast interlocks in a fixed-rate task; put HMI handshakes and diagnostics in
19
+ a slower one. Document the rate next to the POU.
20
+ - **IEC-TASK-02** No unbounded work in a cyclic task: no file I/O, no waiting loops, no long string
21
+ handling. Bound every loop.
22
+ - **IEC-TASK-03** Size the watchdog from the measured worst case, not the average. A watchdog that fires
23
+ only under load is doing its job.
24
+
25
+ ## Apply
26
+
27
+ Record for each task: period, priority, watchdog, worst measured scan, and what happens when it overruns.
28
+
29
+ ## Anti-patterns
30
+
31
+ Copying a fast task into a slow one and accepting the jitter; hiding a blocking call behind a one-shot.
@@ -0,0 +1,32 @@
1
+ ---
2
+ id: iec-types-and-scaling
3
+ title: Types, ranges and scaling without surprises
4
+ domain: iec
5
+ level: practitioner
6
+ basis: [IEC 61131-3]
7
+ tags: [types, scaling, real]
8
+ applies_to: [mwi, l5x, optix]
9
+ rules: [IEC-TYPE-01, IEC-TYPE-02, IEC-TYPE-03]
10
+ ---
11
+ ## Principle
12
+
13
+ Most field faults blamed on "the PLC" are a divide by zero, an overflow, or a REAL compared with `=`.
14
+
15
+ ## Rules
16
+
17
+ - **IEC-TYPE-01** Declare the narrowest type that holds the range, and say the range in the comment
18
+ (`INT 0..1000 mm`). Never rely on the default type.
19
+ - **IEC-TYPE-02** Guard every division and scale: check the divisor, clamp the result, and handle the
20
+ out-of-range case explicitly.
21
+ - **IEC-TYPE-03** Compare REALs with a tolerance, never with equality. For counters and positions, use
22
+ integers where the resolution allows.
23
+
24
+ ## Apply
25
+
26
+ Keep one scale/offset pair per signal, in one place. If two blocks scale the same input, they will
27
+ disagree.
28
+
29
+ ## Anti-patterns
30
+
31
+ `INT` for a millisecond timer that can exceed 32 s; scaling in the HMI and again in the PLC; a REAL used
32
+ as a state number.
@@ -0,0 +1,30 @@
1
+ ---
2
+ id: optix-alarms-and-trends
3
+ title: Alarms and trends in Optix
4
+ domain: optix
5
+ level: practitioner
6
+ basis: [FactoryTalk Optix, ISA-18.2]
7
+ tags: [alarms, trends]
8
+ applies_to: [optix]
9
+ rules: [OPTIX-ALM-01, OPTIX-ALM-02]
10
+ ---
11
+ ## Principle
12
+
13
+ Alarms and trends are process evidence. Their configuration is part of the design, not a runtime
14
+ afterthought.
15
+
16
+ ## Rules
17
+
18
+ - **OPTIX-ALM-01** Configure alarm limits and priorities in the model (type or instance), so every
19
+ instance inherits a reviewed definition.
20
+ - **OPTIX-ALM-02** Choose trend sampling from the process dynamics, and keep the same time base across
21
+ pens that will be compared.
22
+
23
+ ## Apply
24
+
25
+ Review the alarm list as a document: condition, limit, priority, message, action. Trends should answer a
26
+ specific question the operator or engineer has.
27
+
28
+ ## Anti-patterns
29
+
30
+ Alarms enabled per instance with different limits; trend pens on unrelated time bases.
@@ -0,0 +1,30 @@
1
+ ---
2
+ id: optix-bindings-and-expressions
3
+ title: Bindings and expressions
4
+ domain: optix
5
+ level: practitioner
6
+ basis: [FactoryTalk Optix]
7
+ tags: [bindings, expressions, converters]
8
+ applies_to: [optix]
9
+ rules: [OPTIX-BIND-01, OPTIX-BIND-02, OPTIX-BIND-03]
10
+ ---
11
+ ## Principle
12
+
13
+ A binding moves a value; an expression computes one. Mixing them hides where a number comes from.
14
+
15
+ ## Rules
16
+
17
+ - **OPTIX-BIND-01** Bind raw values point to point; put formatting and unit conversion in a formatter or a
18
+ converter with a name, not inline in the binding.
19
+ - **OPTIX-BIND-02** Validate expressions with the expression tool before attaching them, and handle the
20
+ null/empty case explicitly.
21
+ - **OPTIX-BIND-03** Keep one source of truth per value: do not bind and also write the same property from
22
+ NetLogic.
23
+
24
+ ## Apply
25
+
26
+ When a value looks wrong, trace binding → converter → source before editing any of them.
27
+
28
+ ## Anti-patterns
29
+
30
+ Long inline expressions in a property; two writers for one property; conversions that silently round.