Update to Rungstone
Snapshot Nov 3, 2026 · 06:12 UTC · version 0.1.7
Collection source: downloaded plugin package.
Instructions updated for rungstone-plc
Full description — Now says: IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley,...
Observed in instructions or declared skills. Runtime behavior has not been tested.
Up to 10 plugins free · no card required.
All fields and original values.
Full description
Oct 31, 2026 · 01:27 UTC
ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration...
Nov 3, 2026 · 06:12 UTC
IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, Twin...
Skill instructions
Oct 31, 2026 · 01:27 UTC
ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration...
Nov 3, 2026 · 06:12 UTC
IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, Twin...
Supporting files
Oct 31, 2026 · 01:27 UTC
[{"relative_path":"references/output-formats.md","size_in_bytes":6388},{"relative_path":"references/plcopen-xml.md","size_in_bytes":6569},{"relative_path":"references/rockwell-l5x.md","size_in_bytes":12168},{"relative_path":"references/s...
Nov 3, 2026 · 06:12 UTC
[{"relative_path":"references/iec61499.md","size_in_bytes":5723},{"relative_path":"references/openplc.md","size_in_bytes":7029},{"relative_path":"references/output-formats.md","size_in_bytes":8582},{"relative_path":"references/plcopen-xm...
Compare saved observations
Download comparison JSONSKILL.md text changes
− removed · + added
--- before +++ after @@ -1,6 +1,6 @@ --- name: rungstone-plc -description: Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say "translate" and even if they just paste a few rungs. +description: Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, vendor lock-in, portability, moving off CODESYS or TwinCAT, OpenPLC, IEC 61499, 4diac, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say "translate" and even if they just paste a few rungs. license: MIT --- @@ -28,10 +28,17 @@ | Script | Purpose | |---|---| -| `parse_project.py FILES -o parsed.json` | L5X, SimaticML, SCL, ST, PLCopen, TwinCAT, ladder text, I/O CSV → routines, rungs/lines/networks, tags (no data values), comments, I/O, `excluded_safety` | +| `parse_project.py FILES -o parsed.json` | L5X, SimaticML, SCL, ST, PLCopen, TwinCAT, 4diac .fbt, ladder text, I/O CSV → routines, rungs/lines/networks, tags (no data values), comments, I/O, `excluded_safety` | | `estimate.py FILES [--target ...]` | tokens and cost per job, CONFIRM flag for large jobs | | `dependency_map.py --plc FILES [--hmi FILES] [--target T] [--mapping M] --md out.md -o deps.json` | every tag: readers, writers, conflicts, open questions | | `simulate.py run|compare ...` | pure-Python scan-cycle simulator; compares source vs target scan by scan | +| `openplc_check.py FILE.st` | checks an OpenPLC/Beremiz translation against what MatIEC and the OpenPLC runtime accept (structure, syntax, located addresses, Modbus registers) | +| `st_to_plcopen.py FILE.st -o plc.xml` | wraps generated ST as PLCopen XML for OpenPLC Editor / Beremiz | +| `st_to_61499.py FILE.st -o DIR` / `iec61499_check.py FILES.fbt` | IEC 61499 / 4diac: wraps a scan body as a Basic FB plus an E_CYCLE composite, and checks .fbt types (structure, ECC, With sampling, ST) | +| `lint.py FILES --md out.md -o out.json` | static analysis: likely bugs and risky patterns (ST and ladder), each with where, why and a fix | +| `diff_exports.py OLD NEW --md out.md -o out.json` | what changed between two exports, the tags each change writes, and where else those tags are read | +| `make_tests.py --program F --entry FB --scenarios S.json --framework cfunit\|tcunit\|plain` | unit tests for a function block, expected values from the simulator | +| `portability.py FILES [--targets ...] --md out.md -o out.json` | lock-in report for CODESYS, TwinCAT and IEC ST sources: what ties the code to its platform, rated per target with rough hours | | `_shared/state.py` | `.rungstone/` project state: init, review queue (add/approve/edit/reject), export gate, decision log | | `_shared/export.py FILES` | package approved deliverables + MANIFEST; refuses while high-risk items are open | | `_shared/workbench/server.py` / `static.py` | local workbench UI (beta), or one self-contained HTML page when no browser is available | @@ -45,7 +52,7 @@ 3. **Parse:** `parse_project.py <files> -o .rungstone/parsed.json`. Check `warnings` (source-protected routines, unknown formats) and `excluded_safety`. 4. **Safety notice:** if anything was excluded, show the notice block now — before any other output. If a name-based exclusion looks wrong (e.g. "SafetyMat_Count" is an inventory counter), the engineer may release it; see `references/safety-exclusion.md`. 5. **Estimate:** `estimate.py .rungstone/parsed.json --target <t> --tokens-only --json > .rungstone/estimate.json`. If `confirm_before_running` is true (or more than ~20 routines), show the table and ask whether to proceed or narrow scope. Small jobs: one line with the estimate is enough. -6. **Read the right reference** for the source/target platform: `references/rockwell-l5x.md`, `references/siemens-scl.md`, `references/plcopen-xml.md`. For Translate and Verify, also `references/translation-pitfalls.md`. Output layout is in `references/output-formats.md` — follow it exactly. +6. **Read the right reference** for the source/target platform: `references/rockwell-l5x.md`, `references/siemens-scl.md`, `references/plcopen-xml.md`, `references/openplc.md`, `references/iec61499.md`. For Translate and Verify, also `references/translation-pitfalls.md`. Output layout is in `references/output-formats.md` — follow it exactly. ## Workflow 1 — Understand @@ -60,7 +67,7 @@ ## Workflow 2 — Translate -Targets: S7-1500 SCL, Rockwell Logix (L5X partial export), IEC 61131-3 ST. Work **routine by routine against the shared tag map** so names stay consistent across routines and with HMI/SCADA. +Targets: S7-1500 SCL, Rockwell Logix (L5X partial export), IEC 61131-3 ST, OpenPLC Runtime / Beremiz (`openplc`: IEC ST for the MatIEC compiler), IEC 61499 / Eclipse 4diac (`iec61499`: function block types, `.fbt`). Work **routine by routine against the shared tag map** so names stay consistent across routines and with HMI/SCADA. 1. Make sure Understand has been done at least for the routines in scope (signal meanings drive symbol names). If the user only wants code, still build the signal table internally. 2. **Mapping first**: one row per source signal in `.rungstone/mapping.csv` (columns in output-formats.md) — old address/symbol, meaning (basis), new symbol, new address, type old→new. Meaningful names (`Start_PB`, not `I_1_14`); old address in every comment. @@ -70,7 +77,9 @@ `state.py queue add --risk high --location "rung 1" --issue "..." --fix "..."`. Analog scaling is always a high item asking the engineer to confirm module and range. 6. **Cross-check**: run `dependency_map.py` again with `--target <t> --mapping .rungstone/mapping.csv` plus any HMI/SCADA files — anything it reports as `translation_break` (e.g. HMI reads `Timer.ACC`, which has no IEC equivalent; propose an explicit DINT elapsed tag and an HMI rebind) or `broken_link` becomes a review item too. -7. Present the summary in the Translate format: files, mapping, ranked review queue, how to approve. **Nothing is final** until the engineer approves; say so. +7. **OpenPLC target:** run `openplc_check.py out/<name>.st` and fix every error before presenting (a file with errors won't upload). Its warnings (RETAIN, several tasks) become review items. Then `st_to_plcopen.py out/<name>.st -o out/plc.xml` for OpenPLC Editor / Beremiz, and put the `located` list from the check (address → Modbus register) in the answer as the HMI rebind table. Run `dependency_map.py ... --target openplc --mapping .rungstone/mapping.csv` with the HMI files: every HMI tag without a Modbus-visible address is a high item. +8. **IEC 61499 target:** follow `references/iec61499.md`: write the scan body as plain ST with timers/edges/counters as CYCLE arithmetic, then `st_to_61499.py` and `iec61499_check.py` (zero errors, no `[sampling]` warnings). Say that the types haven't been opened in 4diac or run on FORTE here, and that this keeps the scan model as a first step. HMI tags: `dependency_map.py ... --target iec61499`. +9. Present the summary in the Translate format: files, mapping, ranked review queue, how to approve. **Nothing is final** until the engineer approves; say so. ## Workflow 3 — Verify @@ -85,13 +94,45 @@ 2. Present conflicts most severe first with one sentence of consequence each: `label_mismatch` (descriptions disagree or contradict between PLC, I/O map, HMI), `competing_writers` (double coils, PLC vs HMI, same timer twice), `read_never_written` (also an open question), `broken_link` (HMI points at a tag that doesn't exist), `translation_break` (link that breaks after translation). 3. Note the blind spots: indirect addressing, pointers, MSG/comms, and anything in files not supplied. +## Workflow 5 — Lock-in report + +Goal: the engineer (or their manager) sees what ties a CODESYS, TwinCAT or IEC ST project to its platform, what moving it to each target would involve, and a rough effort range, before anyone commits to a migration. + +1. `portability.py <files or .rungstone/parsed.json> --md .rungstone/reports/portability-<source name>.md -o .rungstone/portability.json` (add `--targets openplc,siemens-scl` if the user named targets). If the result says `supported: false`, tell the user what it says (Rockwell and Siemens sources aren't scored yet) and offer Translate instead. +2. Read `references/portability.md` before presenting: what each rating means, the limits, and how to word the effort range. +3. Present it in the Lock-in format from `references/output-formats.md`: the summary table first, then the three to five findings that drive the effort with one sentence each on what moving them involves, then the full findings table and the per-POU view. Keep the report's "rough planning estimate, not a quote" line. +4. Libraries: every namespace the report lists is code that isn't in the export. Say that each needs an equivalent on the target, or a license that allows using it there; don't guess what the library does beyond its name. +5. Don't recommend a platform. The report shows the cost of each; the choice is the engineer's. If they ask which is cheapest, point to the hours and the blockers, and say what the hours leave out. + +## Workflow 6 — Check (static analysis) + +1. `lint.py <files or .rungstone/parsed.json> --md .rungstone/reports/check-<source name>.md -o .rungstone/check.json`. +2. Present in the Check format from `references/output-formats.md`: high findings first, each with the location, one sentence on what goes wrong on the machine, and the fix. Read the code around each high and medium finding before presenting it; drop or downgrade one you can show is fine (say why), and add anything serious you notice that the rules missed, labelled as your reading. +3. Findings are things to check, not proof. Say what static analysis can't see (HMI and comms writes, indirect addressing). + +## Workflow 7 — Diff (what changed between two exports) + +1. `diff_exports.py <old> <new> --md .rungstone/reports/diff-<new name>.md -o .rungstone/diff.json`. Both files must be the same project exported at two times (rename the old one if needed). +2. Start with a plain-English summary of each change: what it does on the machine, the outputs it writes, and what else reads them (the "affects" table). Then the per-routine diffs. +3. Call out anything that changes behaviour in a way the change description may not mention: an output now written from two places, a removed interlock, a timer or task rate change, a type change that can overflow. Run `lint.py` on the new version if the changes are substantial. +4. Safety logic is never compared. If the diff lists safety items, say the safety team must review any change there. + +## Workflow 8 — Unit tests (CfUnit, TcUnit, plain IEC) + +1. Pick a function block (tests call their own instance; a PROGRAM must be moved into an FB first). Write scenarios as in Verify (`scenarios.json`): set only its VAR_INPUTs, observe its VAR_OUTPUTs. +2. `make_tests.py --program <file> --entry <FB> --scenarios <file> --framework cfunit|tcunit|plain -o out/tests`. Expected values are what the code does today, from the simulator: these are characterisation tests that catch any change in behaviour during a refactor or migration. If a scenario's result looks wrong to you, say so: the test would lock in a bug. Quote test and assertion counts from the script's JSON output; don't count them yourself. +3. CfUnit/TcUnit: tell the engineer to add the library, paste or add the generated objects, and run PRG_TEST in a task with the cycle time in the header. Generated code is a draft: it has not been compiled in CODESYS or TwinCAT here. +4. Plain IEC (`--openplc --with-source` for OpenPLC): runs anywhere; pass/fail counters on %MW0-%MW2 (holding registers 1024-1026). +5. Results are "simulated, not hardware-tested" until the engineer runs the tests on their runtime. + For deeper HMI/SCADA work (screens, scripts, alarms, rebinding), the `rungstone-hmi-scada` skill takes over using the same `.rungstone/` state; for "why did it stop" questions with historian data, `rungstone-ops`. ## Results for the workbench The workbench is **beta**: say so when you open it, and treat chat as the reliable path for approvals if it misbehaves. It shows whatever is in `.rungstone/` and refreshes itself, so save every result there as you finish it: -- the full answer you showed, as Markdown: `.rungstone/reports/understand-<source name>.md`, `translate-<source name>.md` or `verify-<source name>.md` (overwrite on a rerun); -- every translated file you wrote: `state.py output out/<file> [...] --workflow translate` (the Translate tab shows the code); +- the full answer you showed, as Markdown: `.rungstone/reports/understand-<source name>.md`, `translate-<source name>.md`, `verify-<source name>.md`, `portability-<source name>.md`, `check-<source name>.md` or `diff-<new name>.md` (overwrite on a rerun); +- every translated file you wrote: `state.py output out/<file> [...] --workflow translate` (the Translate tab shows the code), and generated tests with `--workflow tests` (the Check & tests tab); +- the JSON from Lock-in, Check and Diff at the paths in their workflows (`.rungstone/portability.json`, `check.json`, `diff.json`): the Lock-in, Check & tests and Diff tabs draw their tables from them; - the queue, mapping, verify results and dependency map through the scripts as above. ## Approvals and export
Full technical diff · 3 changed fields
changed /description
"Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs."
"Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, vendor lock-in, portability, moving off CODESYS or TwinCAT, OpenPLC, IEC 61499, 4diac, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs."
changed /included_files
[
{
"relative_path": "references/output-formats.md",
"size_in_bytes": 6388
},
{
"relative_path": "references/plcopen-xml.md",
"size_in_bytes": 6569
},
{
"relative_path": "references/rockwell-l5x.md",
"size_in_bytes": 12168
},
{
"relative_path": "references/safety-exclusion.md",
"size_in_bytes": 5491
},
{
"relative_path": "references/siemens-scl.md",
"size_in_bytes": 11595
},
{
"relative_path": "references/translation-pitfalls.md",
"size_in_bytes": 16284
},
{
"relative_path": "scripts/_shared/__init__.py",
"size_in_bytes": 96
},
{
"relative_path": "scripts/_shared/export.py",
"size_in_bytes": 5545
},
{
"relative_path": "scripts/_shared/safe_xml.py",
"size_in_bytes": 2761
},
{
"relative_path": "scripts/_shared/schemas/dependencies.schema.json",
"size_in_bytes": 2579
},
{
"relative_path": "scripts/_shared/schemas/mapping.schema.json",
"size_in_bytes": 975
},
{
"relative_path": "scripts/_shared/schemas/project.schema.json",
"size_in_bytes": 1092
},
{
"relative_path": "scripts/_shared/schemas/review_queue.schema.json",
"size_in_bytes": 1792
},
{
"relative_path": "scripts/_shared/schemas/scenario.schema.json",
"size_in_bytes": 1954
},
{
"relative_path": "scripts/_shared/schemas/tagmap.schema.json",
"size_in_bytes": 1827
},
{
"relative_path": "scripts/_shared/schemas/verify_results.schema.json",
"size_in_bytes": 1277
},
{
"relative_path": "scripts/_shared/state.py",
"size_in_bytes": 15275
},
{
"relative_path": "scripts/_shared/workbench/app.html",
"size_in_bytes": 47262
},
{
"relative_path": "scripts/_shared/workbench/server.py",
"size_in_bytes": 25038
},
{
"relative_path": "scripts/_shared/workbench/static.py",
"size_in_bytes": 1961
},
{
"relative_path": "scripts/dependency_map.py",
"size_in_bytes": 56325
},
{
"relative_path": "scripts/estimate.py",
"size_in_bytes": 12879
},
{
"relative_path": "scripts/parse_project.py",
"size_in_bytes": 64611
},
{
"relative_path": "scripts/simulate.py",
"size_in_bytes": 74254
}
][
{
"relative_path": "references/iec61499.md",
"size_in_bytes": 5723
},
{
"relative_path": "references/openplc.md",
"size_in_bytes": 7029
},
{
"relative_path": "references/output-formats.md",
"size_in_bytes": 8582
},
{
"relative_path": "references/plcopen-xml.md",
"size_in_bytes": 6569
},
{
"relative_path": "references/portability.md",
"size_in_bytes": 2917
},
{
"relative_path": "references/rockwell-l5x.md",
"size_in_bytes": 12168
},
{
"relative_path": "references/safety-exclusion.md",
"size_in_bytes": 5491
},
{
"relative_path": "references/siemens-scl.md",
"size_in_bytes": 11595
},
{
"relative_path": "references/translation-pitfalls.md",
"size_in_bytes": 16284
},
{
"relative_path": "scripts/_shared/__init__.py",
"size_in_bytes": 96
},
{
"relative_path": "scripts/_shared/export.py",
"size_in_bytes": 5545
},
{
"relative_path": "scripts/_shared/safe_xml.py",
"size_in_bytes": 2761
},
{
"relative_path": "scripts/_shared/schemas/dependencies.schema.json",
"size_in_bytes": 2579
},
{
"relative_path": "scripts/_shared/schemas/mapping.schema.json",
"size_in_bytes": 975
},
{
"relative_path": "scripts/_shared/schemas/project.schema.json",
"size_in_bytes": 1092
},
{
"relative_path": "scripts/_shared/schemas/review_queue.schema.json",
"size_in_bytes": 1792
},
{
"relative_path": "scripts/_shared/schemas/scenario.schema.json",
"size_in_bytes": 1954
},
{
"relative_path": "scripts/_shared/schemas/tagmap.schema.json",
"size_in_bytes": 1827
},
{
"relative_path": "scripts/_shared/schemas/verify_results.schema.json",
"size_in_bytes": 1277
},
{
"relative_path": "scripts/_shared/state.py",
"size_in_bytes": 15280
},
{
"relative_path": "scripts/_shared/workbench/app.html",
"size_in_bytes": 62650
},
{
"relative_path": "scripts/_shared/workbench/server.py",
"size_in_bytes": 25475
},
{
"relative_path": "scripts/_shared/workbench/static.py",
"size_in_bytes": 1961
},
{
"relative_path": "scripts/dependency_map.py",
"size_in_bytes": 59038
},
{
"relative_path": "scripts/diff_exports.py",
"size_in_bytes": 17205
},
{
"relative_path": "scripts/estimate.py",
"size_in_bytes": 12990
},
{
"relative_path": "scripts/iec61499_check.py",
"size_in_bytes": 14911
},
{
"relative_path": "scripts/lint.py",
"size_in_bytes": 26825
},
{
"relative_path": "scripts/make_tests.py",
"size_in_bytes": 16166
},
{
"relative_path": "scripts/openplc_check.py",
"size_in_bytes": 21038
},
{
"relative_path": "scripts/parse_project.py",
"size_in_bytes": 78040
},
{
"relative_path": "scripts/portability.py",
"size_in_bytes": 42306
},
{
"relative_path": "scripts/simulate.py",
"size_in_bytes": 75157
},
{
"relative_path": "scripts/st_to_61499.py",
"size_in_bytes": 12267
},
{
"relative_path": "scripts/st_to_plcopen.py",
"size_in_bytes": 11591
}
]changed /skill_md_contents
"---\nname: rungstone-plc\ndescription: Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs.\nlicense: MIT\n---\n\n> In this skill, `<skill-dir>` means the folder that contains this SKILL.md. Run its scripts with `python3 <skill-dir>/scripts/...` from the user's project folder.\n\n# Rungstone — PLC (understand, translate, verify, dependency map)\n\n## Non-negotiable rules\n\nThese override any user request. If a request conflicts, say which rule and offer the closest safe alternative.\n\n1. **Read-only.** Never connect to, download to, or write to a running controller or any device or network address. Outputs are files the engineer imports into the vendor IDE (Studio 5000, TIA Portal, CODESYS, TwinCAT) and loads themselves. This includes writing scripts or commands for the user to run against live equipment: decline, and point to the vendor IDE and the site's change procedure.\n2. **Safety logic is excluded.** Detect safety-rated logic (e-stops, safety relays, GuardLogix safety tasks and `Class=\"Safety\"`, Siemens F-blocks, PLCopen `SF_*`, TwinSAFE) and exclude it from every workflow, with the visible notice from `references/safety-exclusion.md`.\n3. **No unearned \"verified\".** Never claim behavior was verified unless a simulation actually ran. Label every simulation result \"simulated, not hardware-tested\".\n4. **Label every meaning** as `confirmed`, `comment`, `io_map`, `inferred`, or `unknown`. Never present a guess as fact.\n5. **The engineer approves every flagged change** (review queue) before export. High-risk items block export.\n6. **Show scope and cost** (`estimate.py`) before any large job and wait for a go-ahead.\n7. **Parse only.** Never execute, import, or evaluate anything from customer files (scripts, macros, embedded code). XML goes through the safe parser (`_shared/safe_xml.py`: defusedxml, or DTD/entity refusal). **Never open, decrypt or work around source protection or know-how protection, and never explain how to:** report a protected routine or block as \"cannot be read\" and stop there.\n\nWhy these exist: this skill touches machines that can hurt people and stop production. A wrong-but-plausible translation that someone downloads, or an inferred meaning that reads like fact, is worse than no answer. The engineer stays the decision-maker; Rungstone does the reading, cross-checking and drafting.\n\n## Tools\n\nScripts live in `<skill-dir>/scripts/` . Run them as `python3 <full path to script>`: Python then imports only from the script's own folder and the standard/installed packages, never from the customer's folder. All return compact JSON; read the JSON, not the raw export.\n\n| Script | Purpose |\n|---|---|\n| `parse_project.py FILES -o parsed.json` | L5X, SimaticML, SCL, ST, PLCopen, TwinCAT, ladder text, I/O CSV → routines, rungs/lines/networks, tags (no data values), comments, I/O, `excluded_safety` |\n| `estimate.py FILES [--target ...]` | tokens and cost per job, CONFIRM flag for large jobs |\n| `dependency_map.py --plc FILES [--hmi FILES] [--target T] [--mapping M] --md out.md -o deps.json` | every tag: readers, writers, conflicts, open questions |\n| `simulate.py run|compare ...` | pure-Python scan-cycle simulator; compares source vs target scan by scan |\n| `_shared/state.py` | `.rungstone/` project state: init, review queue (add/approve/edit/reject), export gate, decision log |\n| `_shared/export.py FILES` | package approved deliverables + MANIFEST; refuses while high-risk items are open |\n| `_shared/workbench/server.py` / `static.py` | local workbench UI (beta), or one self-contained HTML page when no browser is available |\n\nLarge exports (tens of MB) are streamed and their tag data dropped while parsing. Never `cat` an L5X into the conversation — tag data is most of the file and carries no logic.\n\n## Start of every job\n\n1. **Collect inputs.** Files the user named; if they named none, the source marked in use in the workbench (`state.py status` → `selected`, set with `state.py select`). If `project.json` has a `target`, or a source has a `platform` set by the engineer in the workbench, use those and don't re-guess. Inputs can be files the user gave (L5X, .xml, .scl, .st, .TcPOU, ladder text, CSV/XLSX I/O lists, HMI exports). If they pasted code, save it to a file (`pasted_rungs.txt`) and parse that. If they have a binary project (.ACD, .RSS, .ap1x), explain the export from the platform reference — don't guess its content.\n2. **State.** `python3 <skill-dir>/scripts/_shared/state.py init --name \"<project>\"` in the user's project folder (creates `.rungstone/`; plain files, fine to commit). Then record every input file with `state.py source FILE [FILE ...] --format <format>` (path and sha256 go into `project.json`, so the export manifest and workbench show what the work was based on), and pass `--target <platform>` to `init` once the target is known. Record what you learn in `.rungstone/tagmap.json` (signal → meaning, basis, used_in, new name) so later jobs reuse it instead of re-reading the project.\n3. **Parse:** `parse_project.py <files> -o .rungstone/parsed.json`. Check `warnings` (source-protected routines, unknown formats) and `excluded_safety`.\n4. **Safety notice:** if anything was excluded, show the notice block now — before any other output. If a name-based exclusion looks wrong (e.g. \"SafetyMat_Count\" is an inventory counter), the engineer may release it; see `references/safety-exclusion.md`.\n5. **Estimate:** `estimate.py .rungstone/parsed.json --target <t> --tokens-only --json > .rungstone/estimate.json`. If `confirm_before_running` is true (or more than ~20 routines), show the table and ask whether to proceed or narrow scope. Small jobs: one line with the estimate is enough.\n6. **Read the right reference** for the source/target platform: `references/rockwell-l5x.md`, `references/siemens-scl.md`, `references/plcopen-xml.md`. For Translate and Verify, also `references/translation-pitfalls.md`. Output layout is in `references/output-formats.md` — follow it exactly.\n\n## Workflow 1 — Understand\n\nGoal: the engineer can explain the machine to someone else after reading your answer, and knows exactly what is still unknown.\n\n1. Run the dependency map on what you have (`dependency_map.py --plc ... --md`) — it gives readers/writers per tag and the read-but-never-written list for open questions.\n2. Walk the logic in execution order (task → program → main routine → JSR calls; OB1 → FB calls; PROGRAM/task order). For each routine, identify its job before its details.\n3. Build the **machine sequence** tied to rung/line numbers: what starts it, each step, what ends or aborts it, interlocks and permissives.\n4. Build the **signal table**: every signal used, its meaning, how you know (basis + quote or reason), where it's used. Normally-open vs normally-closed wiring is a classic trap: an XIC on a stop button means the input is TRUE when *not* pressed — say \"inferred normally closed\" and ask.\n5. **Open questions**: read-but-never-written signals, unknown meanings, analog ranges, ambiguous wiring, setpoints with no writer (probably HMI), anything where a wrong guess changes behavior.\n6. Save confirmed meanings into `tagmap.json` as the engineer answers.\n\n## Workflow 2 — Translate\n\nTargets: S7-1500 SCL, Rockwell Logix (L5X partial export), IEC 61131-3 ST. Work **routine by routine against the shared tag map** so names stay consistent across routines and with HMI/SCADA.\n\n1. Make sure Understand has been done at least for the routines in scope (signal meanings drive symbol names). If the user only wants code, still build the signal table internally.\n2. **Mapping first**: one row per source signal in `.rungstone/mapping.csv` (columns in output-formats.md) — old address/symbol, meaning (basis), new symbol, new address, type old→new. Meaningful names (`Start_PB`, not `I_1_14`); old address in every comment.\n3. **Library and OEM code first.** For every entry under `third_party` in `parsed.json` (add-on instructions or blocks that look like vendor, OEM or library code), add a **high-risk** review item before translating it: \"<name> appears to be vendor/OEM/library code (<evidence>). Confirm its license allows porting it to <target>, or replace it with the target platform's equivalent library block.\" Translate it only after the engineer approves; if they reject, leave a call to the target's equivalent with a TODO.\n4. **Translate** each routine, preserving execution order, rung grouping (`REGION`/comments `was rung N`), timer/counter semantics, and retain behavior. Use the target conventions and templates in the platform reference. Put the DRAFT header on every file. Write files to `out/` (or where the user asks).\n5. **Go through every pitfall** in `references/translation-pitfalls.md` that applies — scan order, immediate I/O, PID, motion, analog scaling, timers (ACC vs ET, time base, reset), one-shots, counters, data types/overflow/rounding, retentive memory, first-scan, fault behavior, protocols, MCR/JMP/indirect addressing. Each instance becomes a review-queue item with a risk level and a concrete proposed fix:\n `state.py queue add --risk high --location \"rung 1\" --issue \"...\" --fix \"...\"`.\n Analog scaling is always a high item asking the engineer to confirm module and range.\n6. **Cross-check**: run `dependency_map.py` again with `--target <t> --mapping .rungstone/mapping.csv` plus any HMI/SCADA files — anything it reports as `translation_break` (e.g. HMI reads `Timer.ACC`, which has no IEC equivalent; propose an explicit DINT elapsed tag and an HMI rebind) or `broken_link` becomes a review item too.\n7. Present the summary in the Translate format: files, mapping, ranked review queue, how to approve. **Nothing is final** until the engineer approves; say so.\n\n## Workflow 3 — Verify\n\n1. Generate the **scenario plan**: normal cycle, each fault/permissive loss, reset, power cycle mid-cycle, overflow/limits, missing or stuck inputs, edge timing. Use source names; write `scenarios.json` (schema in `_shared/schemas/scenario.schema.json`).\n2. Run `simulate.py compare --source <src> --target <tgt> --mapping .rungstone/mapping.csv --scenarios scenarios.json --state-dir .` when both programs are in formats it reads. It reports per scenario `match`, `differs` (first divergence: time, signal, values) or `not run` (unsupported instruction such as PID, motion, MSG — with location). Also \"not run\" today: AOI calls, user FB/FC calls, LAD/FBD network summaries, MCR, JSR with parameters, file/sequencer instructions, runtime faults, and unknown presets (supply them in the scenario's `initial`). The two fixtures in `fixtures/plc/*/sim/` show both outcomes: the two-tank translation differs when the analog raw range isn't converted (`mapping.csv`) and matches when it is (`mapping_scaled.csv`).\n3. If the user has a vendor simulator (PLCSIM Advanced, Logix Emulate/Echo, CODESYS Control Win, TwinCAT UM runtime) connected through a tool you can actually call, you may use it — only against a simulator instance the user explicitly identified as simulated, never a device on a plant network.\n4. Report with the Verify format. Every result carries \"simulated, not hardware-tested\". If nothing ran, the title says \"NOT RUN\" and there are no verdicts. Each `differs` becomes or updates a review-queue item.\n\n## Workflow 4 — Dependency map\n\n1. `dependency_map.py --plc <PLC files or parsed.json> --hmi <HMI/SCADA files> [--target <t>] [--mapping mapping.csv] --md .rungstone/reports/dependencies.md -o .rungstone/dependencies.json`.\n2. Present conflicts most severe first with one sentence of consequence each: `label_mismatch` (descriptions disagree or contradict between PLC, I/O map, HMI), `competing_writers` (double coils, PLC vs HMI, same timer twice), `read_never_written` (also an open question), `broken_link` (HMI points at a tag that doesn't exist), `translation_break` (link that breaks after translation).\n3. Note the blind spots: indirect addressing, pointers, MSG/comms, and anything in files not supplied.\n\nFor deeper HMI/SCADA work (screens, scripts, alarms, rebinding), the `rungstone-hmi-scada` skill takes over using the same `.rungstone/` state; for \"why did it stop\" questions with historian data, `rungstone-ops`.\n\n## Results for the workbench\n\nThe workbench is **beta**: say so when you open it, and treat chat as the reliable path for approvals if it misbehaves. It shows whatever is in `.rungstone/` and refreshes itself, so save every result there as you finish it:\n- the full answer you showed, as Markdown: `.rungstone/reports/understand-<source name>.md`, `translate-<source name>.md` or `verify-<source name>.md` (overwrite on a rerun);\n- every translated file you wrote: `state.py output out/<file> [...] --workflow translate` (the Translate tab shows the code);\n- the queue, mapping, verify results and dependency map through the scripts as above.\n\n## Approvals and export\n\n- The engineer decides each review item: approve, edit (new fix), or reject (with reason). Apply decisions with `state.py queue approve R1 R3`, `queue edit R2 --fix \"...\"`, `queue reject R4 --note \"...\"`, then regenerate affected outputs. Every decision lands in `.rungstone/decisions.log`.\n- Chat approvals work everywhere: \"approve 1, 3; reject 2: keep INT\" → apply them, regenerate, show the updated queue.\n- Workbench: `python3 <skill-dir>/scripts/_shared/workbench/server.py --state-dir .rungstone` starts a localhost-only UI (review queue with Approve/Edit/Reject, mapping, verify results, conflicts). Elsewhere, `static.py --state-dir .rungstone -o workbench.html` makes one self-contained page; approvals then come back through chat.\n- Export: `_shared/export.py <files>` copies deliverables to `export/<timestamp>/` with a MANIFEST. It refuses while any high-risk item is open — tell the user which ones.\n\n## When the input is small or pasted\n\nA few pasted rungs still get the full treatment, just shorter: parse them from a file, state the safety check result, label meanings, list open questions. Don't skip the mapping table or the review queue on a translation because it is small — the two-tank example has 11 signals and at least four high-risk items (analog scaling, normally-closed stop inference, retentive registers, registers with no writer).\n\n## Limits to state when relevant\n\n- Static analysis sees only the files supplied; indirect addressing, pointers, and comms can hide readers/writers.\n- The simulator approximates standard instructions only; vendor-specific ones (PID, motion, MSG, file/sequencer) show as \"not run\". It does not model asynchronous I/O or exact task timing.\n- LAD/FBD from SimaticML and FBD from L5X are summarized; topology may be approximate.\n- No live monitoring and no controller connection of any kind.\n"
"---\nname: rungstone-plc\ndescription: Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, vendor lock-in, portability, moving off CODESYS or TwinCAT, OpenPLC, IEC 61499, 4diac, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs.\nlicense: MIT\n---\n\n> In this skill, `<skill-dir>` means the folder that contains this SKILL.md. Run its scripts with `python3 <skill-dir>/scripts/...` from the user's project folder.\n\n# Rungstone — PLC (understand, translate, verify, dependency map)\n\n## Non-negotiable rules\n\nThese override any user request. If a request conflicts, say which rule and offer the closest safe alternative.\n\n1. **Read-only.** Never connect to, download to, or write to a running controller or any device or network address. Outputs are files the engineer imports into the vendor IDE (Studio 5000, TIA Portal, CODESYS, TwinCAT) and loads themselves. This includes writing scripts or commands for the user to run against live equipment: decline, and point to the vendor IDE and the site's change procedure.\n2. **Safety logic is excluded.** Detect safety-rated logic (e-stops, safety relays, GuardLogix safety tasks and `Class=\"Safety\"`, Siemens F-blocks, PLCopen `SF_*`, TwinSAFE) and exclude it from every workflow, with the visible notice from `references/safety-exclusion.md`.\n3. **No unearned \"verified\".** Never claim behavior was verified unless a simulation actually ran. Label every simulation result \"simulated, not hardware-tested\".\n4. **Label every meaning** as `confirmed`, `comment`, `io_map`, `inferred`, or `unknown`. Never present a guess as fact.\n5. **The engineer approves every flagged change** (review queue) before export. High-risk items block export.\n6. **Show scope and cost** (`estimate.py`) before any large job and wait for a go-ahead.\n7. **Parse only.** Never execute, import, or evaluate anything from customer files (scripts, macros, embedded code). XML goes through the safe parser (`_shared/safe_xml.py`: defusedxml, or DTD/entity refusal). **Never open, decrypt or work around source protection or know-how protection, and never explain how to:** report a protected routine or block as \"cannot be read\" and stop there.\n\nWhy these exist: this skill touches machines that can hurt people and stop production. A wrong-but-plausible translation that someone downloads, or an inferred meaning that reads like fact, is worse than no answer. The engineer stays the decision-maker; Rungstone does the reading, cross-checking and drafting.\n\n## Tools\n\nScripts live in `<skill-dir>/scripts/` . Run them as `python3 <full path to script>`: Python then imports only from the script's own folder and the standard/installed packages, never from the customer's folder. All return compact JSON; read the JSON, not the raw export.\n\n| Script | Purpose |\n|---|---|\n| `parse_project.py FILES -o parsed.json` | L5X, SimaticML, SCL, ST, PLCopen, TwinCAT, 4diac .fbt, ladder text, I/O CSV → routines, rungs/lines/networks, tags (no data values), comments, I/O, `excluded_safety` |\n| `estimate.py FILES [--target ...]` | tokens and cost per job, CONFIRM flag for large jobs |\n| `dependency_map.py --plc FILES [--hmi FILES] [--target T] [--mapping M] --md out.md -o deps.json` | every tag: readers, writers, conflicts, open questions |\n| `simulate.py run|compare ...` | pure-Python scan-cycle simulator; compares source vs target scan by scan |\n| `openplc_check.py FILE.st` | checks an OpenPLC/Beremiz translation against what MatIEC and the OpenPLC runtime accept (structure, syntax, located addresses, Modbus registers) |\n| `st_to_plcopen.py FILE.st -o plc.xml` | wraps generated ST as PLCopen XML for OpenPLC Editor / Beremiz |\n| `st_to_61499.py FILE.st -o DIR` / `iec61499_check.py FILES.fbt` | IEC 61499 / 4diac: wraps a scan body as a Basic FB plus an E_CYCLE composite, and checks .fbt types (structure, ECC, With sampling, ST) |\n| `lint.py FILES --md out.md -o out.json` | static analysis: likely bugs and risky patterns (ST and ladder), each with where, why and a fix |\n| `diff_exports.py OLD NEW --md out.md -o out.json` | what changed between two exports, the tags each change writes, and where else those tags are read |\n| `make_tests.py --program F --entry FB --scenarios S.json --framework cfunit\\|tcunit\\|plain` | unit tests for a function block, expected values from the simulator |\n| `portability.py FILES [--targets ...] --md out.md -o out.json` | lock-in report for CODESYS, TwinCAT and IEC ST sources: what ties the code to its platform, rated per target with rough hours |\n| `_shared/state.py` | `.rungstone/` project state: init, review queue (add/approve/edit/reject), export gate, decision log |\n| `_shared/export.py FILES` | package approved deliverables + MANIFEST; refuses while high-risk items are open |\n| `_shared/workbench/server.py` / `static.py` | local workbench UI (beta), or one self-contained HTML page when no browser is available |\n\nLarge exports (tens of MB) are streamed and their tag data dropped while parsing. Never `cat` an L5X into the conversation — tag data is most of the file and carries no logic.\n\n## Start of every job\n\n1. **Collect inputs.** Files the user named; if they named none, the source marked in use in the workbench (`state.py status` → `selected`, set with `state.py select`). If `project.json` has a `target`, or a source has a `platform` set by the engineer in the workbench, use those and don't re-guess. Inputs can be files the user gave (L5X, .xml, .scl, .st, .TcPOU, ladder text, CSV/XLSX I/O lists, HMI exports). If they pasted code, save it to a file (`pasted_rungs.txt`) and parse that. If they have a binary project (.ACD, .RSS, .ap1x), explain the export from the platform reference — don't guess its content.\n2. **State.** `python3 <skill-dir>/scripts/_shared/state.py init --name \"<project>\"` in the user's project folder (creates `.rungstone/`; plain files, fine to commit). Then record every input file with `state.py source FILE [FILE ...] --format <format>` (path and sha256 go into `project.json`, so the export manifest and workbench show what the work was based on), and pass `--target <platform>` to `init` once the target is known. Record what you learn in `.rungstone/tagmap.json` (signal → meaning, basis, used_in, new name) so later jobs reuse it instead of re-reading the project.\n3. **Parse:** `parse_project.py <files> -o .rungstone/parsed.json`. Check `warnings` (source-protected routines, unknown formats) and `excluded_safety`.\n4. **Safety notice:** if anything was excluded, show the notice block now — before any other output. If a name-based exclusion looks wrong (e.g. \"SafetyMat_Count\" is an inventory counter), the engineer may release it; see `references/safety-exclusion.md`.\n5. **Estimate:** `estimate.py .rungstone/parsed.json --target <t> --tokens-only --json > .rungstone/estimate.json`. If `confirm_before_running` is true (or more than ~20 routines), show the table and ask whether to proceed or narrow scope. Small jobs: one line with the estimate is enough.\n6. **Read the right reference** for the source/target platform: `references/rockwell-l5x.md`, `references/siemens-scl.md`, `references/plcopen-xml.md`, `references/openplc.md`, `references/iec61499.md`. For Translate and Verify, also `references/translation-pitfalls.md`. Output layout is in `references/output-formats.md` — follow it exactly.\n\n## Workflow 1 — Understand\n\nGoal: the engineer can explain the machine to someone else after reading your answer, and knows exactly what is still unknown.\n\n1. Run the dependency map on what you have (`dependency_map.py --plc ... --md`) — it gives readers/writers per tag and the read-but-never-written list for open questions.\n2. Walk the logic in execution order (task → program → main routine → JSR calls; OB1 → FB calls; PROGRAM/task order). For each routine, identify its job before its details.\n3. Build the **machine sequence** tied to rung/line numbers: what starts it, each step, what ends or aborts it, interlocks and permissives.\n4. Build the **signal table**: every signal used, its meaning, how you know (basis + quote or reason), where it's used. Normally-open vs normally-closed wiring is a classic trap: an XIC on a stop button means the input is TRUE when *not* pressed — say \"inferred normally closed\" and ask.\n5. **Open questions**: read-but-never-written signals, unknown meanings, analog ranges, ambiguous wiring, setpoints with no writer (probably HMI), anything where a wrong guess changes behavior.\n6. Save confirmed meanings into `tagmap.json` as the engineer answers.\n\n## Workflow 2 — Translate\n\nTargets: S7-1500 SCL, Rockwell Logix (L5X partial export), IEC 61131-3 ST, OpenPLC Runtime / Beremiz (`openplc`: IEC ST for the MatIEC compiler), IEC 61499 / Eclipse 4diac (`iec61499`: function block types, `.fbt`). Work **routine by routine against the shared tag map** so names stay consistent across routines and with HMI/SCADA.\n\n1. Make sure Understand has been done at least for the routines in scope (signal meanings drive symbol names). If the user only wants code, still build the signal table internally.\n2. **Mapping first**: one row per source signal in `.rungstone/mapping.csv` (columns in output-formats.md) — old address/symbol, meaning (basis), new symbol, new address, type old→new. Meaningful names (`Start_PB`, not `I_1_14`); old address in every comment.\n3. **Library and OEM code first.** For every entry under `third_party` in `parsed.json` (add-on instructions or blocks that look like vendor, OEM or library code), add a **high-risk** review item before translating it: \"<name> appears to be vendor/OEM/library code (<evidence>). Confirm its license allows porting it to <target>, or replace it with the target platform's equivalent library block.\" Translate it only after the engineer approves; if they reject, leave a call to the target's equivalent with a TODO.\n4. **Translate** each routine, preserving execution order, rung grouping (`REGION`/comments `was rung N`), timer/counter semantics, and retain behavior. Use the target conventions and templates in the platform reference. Put the DRAFT header on every file. Write files to `out/` (or where the user asks).\n5. **Go through every pitfall** in `references/translation-pitfalls.md` that applies — scan order, immediate I/O, PID, motion, analog scaling, timers (ACC vs ET, time base, reset), one-shots, counters, data types/overflow/rounding, retentive memory, first-scan, fault behavior, protocols, MCR/JMP/indirect addressing. Each instance becomes a review-queue item with a risk level and a concrete proposed fix:\n `state.py queue add --risk high --location \"rung 1\" --issue \"...\" --fix \"...\"`.\n Analog scaling is always a high item asking the engineer to confirm module and range.\n6. **Cross-check**: run `dependency_map.py` again with `--target <t> --mapping .rungstone/mapping.csv` plus any HMI/SCADA files — anything it reports as `translation_break` (e.g. HMI reads `Timer.ACC`, which has no IEC equivalent; propose an explicit DINT elapsed tag and an HMI rebind) or `broken_link` becomes a review item too.\n7. **OpenPLC target:** run `openplc_check.py out/<name>.st` and fix every error before presenting (a file with errors won't upload). Its warnings (RETAIN, several tasks) become review items. Then `st_to_plcopen.py out/<name>.st -o out/plc.xml` for OpenPLC Editor / Beremiz, and put the `located` list from the check (address → Modbus register) in the answer as the HMI rebind table. Run `dependency_map.py ... --target openplc --mapping .rungstone/mapping.csv` with the HMI files: every HMI tag without a Modbus-visible address is a high item.\n8. **IEC 61499 target:** follow `references/iec61499.md`: write the scan body as plain ST with timers/edges/counters as CYCLE arithmetic, then `st_to_61499.py` and `iec61499_check.py` (zero errors, no `[sampling]` warnings). Say that the types haven't been opened in 4diac or run on FORTE here, and that this keeps the scan model as a first step. HMI tags: `dependency_map.py ... --target iec61499`.\n9. Present the summary in the Translate format: files, mapping, ranked review queue, how to approve. **Nothing is final** until the engineer approves; say so.\n\n## Workflow 3 — Verify\n\n1. Generate the **scenario plan**: normal cycle, each fault/permissive loss, reset, power cycle mid-cycle, overflow/limits, missing or stuck inputs, edge timing. Use source names; write `scenarios.json` (schema in `_shared/schemas/scenario.schema.json`).\n2. Run `simulate.py compare --source <src> --target <tgt> --mapping .rungstone/mapping.csv --scenarios scenarios.json --state-dir .` when both programs are in formats it reads. It reports per scenario `match`, `differs` (first divergence: time, signal, values) or `not run` (unsupported instruction such as PID, motion, MSG — with location). Also \"not run\" today: AOI calls, user FB/FC calls, LAD/FBD network summaries, MCR, JSR with parameters, file/sequencer instructions, runtime faults, and unknown presets (supply them in the scenario's `initial`). The two fixtures in `fixtures/plc/*/sim/` show both outcomes: the two-tank translation differs when the analog raw range isn't converted (`mapping.csv`) and matches when it is (`mapping_scaled.csv`).\n3. If the user has a vendor simulator (PLCSIM Advanced, Logix Emulate/Echo, CODESYS Control Win, TwinCAT UM runtime) connected through a tool you can actually call, you may use it — only against a simulator instance the user explicitly identified as simulated, never a device on a plant network.\n4. Report with the Verify format. Every result carries \"simulated, not hardware-tested\". If nothing ran, the title says \"NOT RUN\" and there are no verdicts. Each `differs` becomes or updates a review-queue item.\n\n## Workflow 4 — Dependency map\n\n1. `dependency_map.py --plc <PLC files or parsed.json> --hmi <HMI/SCADA files> [--target <t>] [--mapping mapping.csv] --md .rungstone/reports/dependencies.md -o .rungstone/dependencies.json`.\n2. Present conflicts most severe first with one sentence of consequence each: `label_mismatch` (descriptions disagree or contradict between PLC, I/O map, HMI), `competing_writers` (double coils, PLC vs HMI, same timer twice), `read_never_written` (also an open question), `broken_link` (HMI points at a tag that doesn't exist), `translation_break` (link that breaks after translation).\n3. Note the blind spots: indirect addressing, pointers, MSG/comms, and anything in files not supplied.\n\n## Workflow 5 — Lock-in report\n\nGoal: the engineer (or their manager) sees what ties a CODESYS, TwinCAT or IEC ST project to its platform, what moving it to each target would involve, and a rough effort range, before anyone commits to a migration.\n\n1. `portability.py <files or .rungstone/parsed.json> --md .rungstone/reports/portability-<source name>.md -o .rungstone/portability.json` (add `--targets openplc,siemens-scl` if the user named targets). If the result says `supported: false`, tell the user what it says (Rockwell and Siemens sources aren't scored yet) and offer Translate instead.\n2. Read `references/portability.md` before presenting: what each rating means, the limits, and how to word the effort range.\n3. Present it in the Lock-in format from `references/output-formats.md`: the summary table first, then the three to five findings that drive the effort with one sentence each on what moving them involves, then the full findings table and the per-POU view. Keep the report's \"rough planning estimate, not a quote\" line.\n4. Libraries: every namespace the report lists is code that isn't in the export. Say that each needs an equivalent on the target, or a license that allows using it there; don't guess what the library does beyond its name.\n5. Don't recommend a platform. The report shows the cost of each; the choice is the engineer's. If they ask which is cheapest, point to the hours and the blockers, and say what the hours leave out.\n\n## Workflow 6 — Check (static analysis)\n\n1. `lint.py <files or .rungstone/parsed.json> --md .rungstone/reports/check-<source name>.md -o .rungstone/check.json`.\n2. Present in the Check format from `references/output-formats.md`: high findings first, each with the location, one sentence on what goes wrong on the machine, and the fix. Read the code around each high and medium finding before presenting it; drop or downgrade one you can show is fine (say why), and add anything serious you notice that the rules missed, labelled as your reading.\n3. Findings are things to check, not proof. Say what static analysis can't see (HMI and comms writes, indirect addressing).\n\n## Workflow 7 — Diff (what changed between two exports)\n\n1. `diff_exports.py <old> <new> --md .rungstone/reports/diff-<new name>.md -o .rungstone/diff.json`. Both files must be the same project exported at two times (rename the old one if needed).\n2. Start with a plain-English summary of each change: what it does on the machine, the outputs it writes, and what else reads them (the \"affects\" table). Then the per-routine diffs.\n3. Call out anything that changes behaviour in a way the change description may not mention: an output now written from two places, a removed interlock, a timer or task rate change, a type change that can overflow. Run `lint.py` on the new version if the changes are substantial.\n4. Safety logic is never compared. If the diff lists safety items, say the safety team must review any change there.\n\n## Workflow 8 — Unit tests (CfUnit, TcUnit, plain IEC)\n\n1. Pick a function block (tests call their own instance; a PROGRAM must be moved into an FB first). Write scenarios as in Verify (`scenarios.json`): set only its VAR_INPUTs, observe its VAR_OUTPUTs.\n2. `make_tests.py --program <file> --entry <FB> --scenarios <file> --framework cfunit|tcunit|plain -o out/tests`. Expected values are what the code does today, from the simulator: these are characterisation tests that catch any change in behaviour during a refactor or migration. If a scenario's result looks wrong to you, say so: the test would lock in a bug. Quote test and assertion counts from the script's JSON output; don't count them yourself.\n3. CfUnit/TcUnit: tell the engineer to add the library, paste or add the generated objects, and run PRG_TEST in a task with the cycle time in the header. Generated code is a draft: it has not been compiled in CODESYS or TwinCAT here.\n4. Plain IEC (`--openplc --with-source` for OpenPLC): runs anywhere; pass/fail counters on %MW0-%MW2 (holding registers 1024-1026).\n5. Results are \"simulated, not hardware-tested\" until the engineer runs the tests on their runtime.\n\nFor deeper HMI/SCADA work (screens, scripts, alarms, rebinding), the `rungstone-hmi-scada` skill takes over using the same `.rungstone/` state; for \"why did it stop\" questions with historian data, `rungstone-ops`.\n\n## Results for the workbench\n\nThe workbench is **beta**: say so when you open it, and treat chat as the reliable path for approvals if it misbehaves. It shows whatever is in `.rungstone/` and refreshes itself, so save every result there as you finish it:\n- the full answer you showed, as Markdown: `.rungstone/reports/understand-<source name>.md`, `translate-<source name>.md`, `verify-<source name>.md`, `portability-<source name>.md`, `check-<source name>.md` or `diff-<new name>.md` (overwrite on a rerun);\n- every translated file you wrote: `state.py output out/<file> [...] --workflow translate` (the Translate tab shows the code), and generated tests with `--workflow tests` (the Check & tests tab);\n- the JSON from Lock-in, Check and Diff at the paths in their workflows (`.rungstone/portability.json`, `check.json`, `diff.json`): the Lock-in, Check & tests and Diff tabs draw their tables from them;\n- the queue, mapping, verify results and dependency map through the scripts as above.\n\n## Approvals and export\n\n- The engineer decides each review item: approve, edit (new fix), or reject (with reason). Apply decisions with `state.py queue approve R1 R3`, `queue edit R2 --fix \"...\"`, `queue reject R4 --note \"...\"`, then regenerate affected outputs. Every decision lands in `.rungstone/decisions.log`.\n- Chat approvals work everywhere: \"approve 1, 3; reject 2: keep INT\" → apply them, regenerate, show the updated queue.\n- Workbench: `python3 <skill-dir>/scripts/_shared/workbench/server.py --state-dir .rungstone` starts a localhost-only UI (review queue with Approve/Edit/Reject, mapping, verify results, conflicts). Elsewhere, `static.py --state-dir .rungstone -o workbench.html` makes one self-contained page; approvals then come back through chat.\n- Export: `_shared/export.py <files>` copies deliverables to `export/<timestamp>/` with a MANIFEST. It refuses while any high-risk item is open — tell the user which ones.\n\n## When the input is small or pasted\n\nA few pasted rungs still get the full treatment, just shorter: parse them from a file, state the safety check result, label meanings, list open questions. Don't skip the mapping table or the review queue on a translation because it is small — the two-tank example has 11 signals and at least four high-risk items (analog scaling, normally-closed stop inference, retentive registers, registers with no writer).\n\n## Limits to state when relevant\n\n- Static analysis sees only the files supplied; indirect addressing, pointers, and comms can hide readers/writers.\n- The simulator approximates standard instructions only; vendor-specific ones (PID, motion, MSG, file/sequencer) show as \"not run\". It does not model asynchronous I/O or exact task timing.\n- LAD/FBD from SimaticML and FBD from L5X are summarized; topology may be approximate.\n- No live monitoring and no controller connection of any kind.\n"
Full snapshot data
{
"description": "Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, vendor lock-in, portability, moving off CODESYS or TwinCAT, OpenPLC, IEC 61499, 4diac, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs.",
"included_files": [
{
"relative_path": "references/iec61499.md",
"size_in_bytes": 5723
},
{
"relative_path": "references/openplc.md",
"size_in_bytes": 7029
},
{
"relative_path": "references/output-formats.md",
"size_in_bytes": 8582
},
{
"relative_path": "references/plcopen-xml.md",
"size_in_bytes": 6569
},
{
"relative_path": "references/portability.md",
"size_in_bytes": 2917
},
{
"relative_path": "references/rockwell-l5x.md",
"size_in_bytes": 12168
},
{
"relative_path": "references/safety-exclusion.md",
"size_in_bytes": 5491
},
{
"relative_path": "references/siemens-scl.md",
"size_in_bytes": 11595
},
{
"relative_path": "references/translation-pitfalls.md",
"size_in_bytes": 16284
},
{
"relative_path": "scripts/_shared/__init__.py",
"size_in_bytes": 96
},
{
"relative_path": "scripts/_shared/export.py",
"size_in_bytes": 5545
},
{
"relative_path": "scripts/_shared/safe_xml.py",
"size_in_bytes": 2761
},
{
"relative_path": "scripts/_shared/schemas/dependencies.schema.json",
"size_in_bytes": 2579
},
{
"relative_path": "scripts/_shared/schemas/mapping.schema.json",
"size_in_bytes": 975
},
{
"relative_path": "scripts/_shared/schemas/project.schema.json",
"size_in_bytes": 1092
},
{
"relative_path": "scripts/_shared/schemas/review_queue.schema.json",
"size_in_bytes": 1792
},
{
"relative_path": "scripts/_shared/schemas/scenario.schema.json",
"size_in_bytes": 1954
},
{
"relative_path": "scripts/_shared/schemas/tagmap.schema.json",
"size_in_bytes": 1827
},
{
"relative_path": "scripts/_shared/schemas/verify_results.schema.json",
"size_in_bytes": 1277
},
{
"relative_path": "scripts/_shared/state.py",
"size_in_bytes": 15280
},
{
"relative_path": "scripts/_shared/workbench/app.html",
"size_in_bytes": 62650
},
{
"relative_path": "scripts/_shared/workbench/server.py",
"size_in_bytes": 25475
},
{
"relative_path": "scripts/_shared/workbench/static.py",
"size_in_bytes": 1961
},
{
"relative_path": "scripts/dependency_map.py",
"size_in_bytes": 59038
},
{
"relative_path": "scripts/diff_exports.py",
"size_in_bytes": 17205
},
{
"relative_path": "scripts/estimate.py",
"size_in_bytes": 12990
},
{
"relative_path": "scripts/iec61499_check.py",
"size_in_bytes": 14911
},
{
"relative_path": "scripts/lint.py",
"size_in_bytes": 26825
},
{
"relative_path": "scripts/make_tests.py",
"size_in_bytes": 16166
},
{
"relative_path": "scripts/openplc_check.py",
"size_in_bytes": 21038
},
{
"relative_path": "scripts/parse_project.py",
"size_in_bytes": 78040
},
{
"relative_path": "scripts/portability.py",
"size_in_bytes": 42306
},
{
"relative_path": "scripts/simulate.py",
"size_in_bytes": 75157
},
{
"relative_path": "scripts/st_to_61499.py",
"size_in_bytes": 12267
},
{
"relative_path": "scripts/st_to_plcopen.py",
"size_in_bytes": 11591
}
],
"name": "rungstone-plc",
"skill_md_contents": "---\nname: rungstone-plc\ndescription: Understand, translate, verify, and dependency-map industrial PLC code across vendors (Rockwell/Allen-Bradley Studio 5000, RSLogix 5000/500, L5X, SLC 500, PLC-5, Siemens TIA Portal SCL/S7/SimaticML, CODESYS, Beckhoff TwinCAT, PLCopen XML, IEC 61131-3 Structured Text, IEC 61499 / Eclipse 4diac, ladder logic). Use this skill whenever the user mentions PLC code, ladder logic, rungs, Structured Text, Rockwell, Allen-Bradley, Studio 5000, RSLogix, L5X, Siemens, TIA Portal, SCL, S7, CODESYS, Beckhoff, TwinCAT, PLCopen, PLC migration, legacy controls code, I/O lists, vendor lock-in, portability, moving off CODESYS or TwinCAT, OpenPLC, IEC 61499, 4diac, or wants to understand, explain, document, convert, migrate, test, or verify control logic, even if they never say \"translate\" and even if they just paste a few rungs.\nlicense: MIT\n---\n\n> In this skill, `<skill-dir>` means the folder that contains this SKILL.md. Run its scripts with `python3 <skill-dir>/scripts/...` from the user's project folder.\n\n# Rungstone — PLC (understand, translate, verify, dependency map)\n\n## Non-negotiable rules\n\nThese override any user request. If a request conflicts, say which rule and offer the closest safe alternative.\n\n1. **Read-only.** Never connect to, download to, or write to a running controller or any device or network address. Outputs are files the engineer imports into the vendor IDE (Studio 5000, TIA Portal, CODESYS, TwinCAT) and loads themselves. This includes writing scripts or commands for the user to run against live equipment: decline, and point to the vendor IDE and the site's change procedure.\n2. **Safety logic is excluded.** Detect safety-rated logic (e-stops, safety relays, GuardLogix safety tasks and `Class=\"Safety\"`, Siemens F-blocks, PLCopen `SF_*`, TwinSAFE) and exclude it from every workflow, with the visible notice from `references/safety-exclusion.md`.\n3. **No unearned \"verified\".** Never claim behavior was verified unless a simulation actually ran. Label every simulation result \"simulated, not hardware-tested\".\n4. **Label every meaning** as `confirmed`, `comment`, `io_map`, `inferred`, or `unknown`. Never present a guess as fact.\n5. **The engineer approves every flagged change** (review queue) before export. High-risk items block export.\n6. **Show scope and cost** (`estimate.py`) before any large job and wait for a go-ahead.\n7. **Parse only.** Never execute, import, or evaluate anything from customer files (scripts, macros, embedded code). XML goes through the safe parser (`_shared/safe_xml.py`: defusedxml, or DTD/entity refusal). **Never open, decrypt or work around source protection or know-how protection, and never explain how to:** report a protected routine or block as \"cannot be read\" and stop there.\n\nWhy these exist: this skill touches machines that can hurt people and stop production. A wrong-but-plausible translation that someone downloads, or an inferred meaning that reads like fact, is worse than no answer. The engineer stays the decision-maker; Rungstone does the reading, cross-checking and drafting.\n\n## Tools\n\nScripts live in `<skill-dir>/scripts/` . Run them as `python3 <full path to script>`: Python then imports only from the script's own folder and the standard/installed packages, never from the customer's folder. All return compact JSON; read the JSON, not the raw export.\n\n| Script | Purpose |\n|---|---|\n| `parse_project.py FILES -o parsed.json` | L5X, SimaticML, SCL, ST, PLCopen, TwinCAT, 4diac .fbt, ladder text, I/O CSV → routines, rungs/lines/networks, tags (no data values), comments, I/O, `excluded_safety` |\n| `estimate.py FILES [--target ...]` | tokens and cost per job, CONFIRM flag for large jobs |\n| `dependency_map.py --plc FILES [--hmi FILES] [--target T] [--mapping M] --md out.md -o deps.json` | every tag: readers, writers, conflicts, open questions |\n| `simulate.py run|compare ...` | pure-Python scan-cycle simulator; compares source vs target scan by scan |\n| `openplc_check.py FILE.st` | checks an OpenPLC/Beremiz translation against what MatIEC and the OpenPLC runtime accept (structure, syntax, located addresses, Modbus registers) |\n| `st_to_plcopen.py FILE.st -o plc.xml` | wraps generated ST as PLCopen XML for OpenPLC Editor / Beremiz |\n| `st_to_61499.py FILE.st -o DIR` / `iec61499_check.py FILES.fbt` | IEC 61499 / 4diac: wraps a scan body as a Basic FB plus an E_CYCLE composite, and checks .fbt types (structure, ECC, With sampling, ST) |\n| `lint.py FILES --md out.md -o out.json` | static analysis: likely bugs and risky patterns (ST and ladder), each with where, why and a fix |\n| `diff_exports.py OLD NEW --md out.md -o out.json` | what changed between two exports, the tags each change writes, and where else those tags are read |\n| `make_tests.py --program F --entry FB --scenarios S.json --framework cfunit\\|tcunit\\|plain` | unit tests for a function block, expected values from the simulator |\n| `portability.py FILES [--targets ...] --md out.md -o out.json` | lock-in report for CODESYS, TwinCAT and IEC ST sources: what ties the code to its platform, rated per target with rough hours |\n| `_shared/state.py` | `.rungstone/` project state: init, review queue (add/approve/edit/reject), export gate, decision log |\n| `_shared/export.py FILES` | package approved deliverables + MANIFEST; refuses while high-risk items are open |\n| `_shared/workbench/server.py` / `static.py` | local workbench UI (beta), or one self-contained HTML page when no browser is available |\n\nLarge exports (tens of MB) are streamed and their tag data dropped while parsing. Never `cat` an L5X into the conversation — tag data is most of the file and carries no logic.\n\n## Start of every job\n\n1. **Collect inputs.** Files the user named; if they named none, the source marked in use in the workbench (`state.py status` → `selected`, set with `state.py select`). If `project.json` has a `target`, or a source has a `platform` set by the engineer in the workbench, use those and don't re-guess. Inputs can be files the user gave (L5X, .xml, .scl, .st, .TcPOU, ladder text, CSV/XLSX I/O lists, HMI exports). If they pasted code, save it to a file (`pasted_rungs.txt`) and parse that. If they have a binary project (.ACD, .RSS, .ap1x), explain the export from the platform reference — don't guess its content.\n2. **State.** `python3 <skill-dir>/scripts/_shared/state.py init --name \"<project>\"` in the user's project folder (creates `.rungstone/`; plain files, fine to commit). Then record every input file with `state.py source FILE [FILE ...] --format <format>` (path and sha256 go into `project.json`, so the export manifest and workbench show what the work was based on), and pass `--target <platform>` to `init` once the target is known. Record what you learn in `.rungstone/tagmap.json` (signal → meaning, basis, used_in, new name) so later jobs reuse it instead of re-reading the project.\n3. **Parse:** `parse_project.py <files> -o .rungstone/parsed.json`. Check `warnings` (source-protected routines, unknown formats) and `excluded_safety`.\n4. **Safety notice:** if anything was excluded, show the notice block now — before any other output. If a name-based exclusion looks wrong (e.g. \"SafetyMat_Count\" is an inventory counter), the engineer may release it; see `references/safety-exclusion.md`.\n5. **Estimate:** `estimate.py .rungstone/parsed.json --target <t> --tokens-only --json > .rungstone/estimate.json`. If `confirm_before_running` is true (or more than ~20 routines), show the table and ask whether to proceed or narrow scope. Small jobs: one line with the estimate is enough.\n6. **Read the right reference** for the source/target platform: `references/rockwell-l5x.md`, `references/siemens-scl.md`, `references/plcopen-xml.md`, `references/openplc.md`, `references/iec61499.md`. For Translate and Verify, also `references/translation-pitfalls.md`. Output layout is in `references/output-formats.md` — follow it exactly.\n\n## Workflow 1 — Understand\n\nGoal: the engineer can explain the machine to someone else after reading your answer, and knows exactly what is still unknown.\n\n1. Run the dependency map on what you have (`dependency_map.py --plc ... --md`) — it gives readers/writers per tag and the read-but-never-written list for open questions.\n2. Walk the logic in execution order (task → program → main routine → JSR calls; OB1 → FB calls; PROGRAM/task order). For each routine, identify its job before its details.\n3. Build the **machine sequence** tied to rung/line numbers: what starts it, each step, what ends or aborts it, interlocks and permissives.\n4. Build the **signal table**: every signal used, its meaning, how you know (basis + quote or reason), where it's used. Normally-open vs normally-closed wiring is a classic trap: an XIC on a stop button means the input is TRUE when *not* pressed — say \"inferred normally closed\" and ask.\n5. **Open questions**: read-but-never-written signals, unknown meanings, analog ranges, ambiguous wiring, setpoints with no writer (probably HMI), anything where a wrong guess changes behavior.\n6. Save confirmed meanings into `tagmap.json` as the engineer answers.\n\n## Workflow 2 — Translate\n\nTargets: S7-1500 SCL, Rockwell Logix (L5X partial export), IEC 61131-3 ST, OpenPLC Runtime / Beremiz (`openplc`: IEC ST for the MatIEC compiler), IEC 61499 / Eclipse 4diac (`iec61499`: function block types, `.fbt`). Work **routine by routine against the shared tag map** so names stay consistent across routines and with HMI/SCADA.\n\n1. Make sure Understand has been done at least for the routines in scope (signal meanings drive symbol names). If the user only wants code, still build the signal table internally.\n2. **Mapping first**: one row per source signal in `.rungstone/mapping.csv` (columns in output-formats.md) — old address/symbol, meaning (basis), new symbol, new address, type old→new. Meaningful names (`Start_PB`, not `I_1_14`); old address in every comment.\n3. **Library and OEM code first.** For every entry under `third_party` in `parsed.json` (add-on instructions or blocks that look like vendor, OEM or library code), add a **high-risk** review item before translating it: \"<name> appears to be vendor/OEM/library code (<evidence>). Confirm its license allows porting it to <target>, or replace it with the target platform's equivalent library block.\" Translate it only after the engineer approves; if they reject, leave a call to the target's equivalent with a TODO.\n4. **Translate** each routine, preserving execution order, rung grouping (`REGION`/comments `was rung N`), timer/counter semantics, and retain behavior. Use the target conventions and templates in the platform reference. Put the DRAFT header on every file. Write files to `out/` (or where the user asks).\n5. **Go through every pitfall** in `references/translation-pitfalls.md` that applies — scan order, immediate I/O, PID, motion, analog scaling, timers (ACC vs ET, time base, reset), one-shots, counters, data types/overflow/rounding, retentive memory, first-scan, fault behavior, protocols, MCR/JMP/indirect addressing. Each instance becomes a review-queue item with a risk level and a concrete proposed fix:\n `state.py queue add --risk high --location \"rung 1\" --issue \"...\" --fix \"...\"`.\n Analog scaling is always a high item asking the engineer to confirm module and range.\n6. **Cross-check**: run `dependency_map.py` again with `--target <t> --mapping .rungstone/mapping.csv` plus any HMI/SCADA files — anything it reports as `translation_break` (e.g. HMI reads `Timer.ACC`, which has no IEC equivalent; propose an explicit DINT elapsed tag and an HMI rebind) or `broken_link` becomes a review item too.\n7. **OpenPLC target:** run `openplc_check.py out/<name>.st` and fix every error before presenting (a file with errors won't upload). Its warnings (RETAIN, several tasks) become review items. Then `st_to_plcopen.py out/<name>.st -o out/plc.xml` for OpenPLC Editor / Beremiz, and put the `located` list from the check (address → Modbus register) in the answer as the HMI rebind table. Run `dependency_map.py ... --target openplc --mapping .rungstone/mapping.csv` with the HMI files: every HMI tag without a Modbus-visible address is a high item.\n8. **IEC 61499 target:** follow `references/iec61499.md`: write the scan body as plain ST with timers/edges/counters as CYCLE arithmetic, then `st_to_61499.py` and `iec61499_check.py` (zero errors, no `[sampling]` warnings). Say that the types haven't been opened in 4diac or run on FORTE here, and that this keeps the scan model as a first step. HMI tags: `dependency_map.py ... --target iec61499`.\n9. Present the summary in the Translate format: files, mapping, ranked review queue, how to approve. **Nothing is final** until the engineer approves; say so.\n\n## Workflow 3 — Verify\n\n1. Generate the **scenario plan**: normal cycle, each fault/permissive loss, reset, power cycle mid-cycle, overflow/limits, missing or stuck inputs, edge timing. Use source names; write `scenarios.json` (schema in `_shared/schemas/scenario.schema.json`).\n2. Run `simulate.py compare --source <src> --target <tgt> --mapping .rungstone/mapping.csv --scenarios scenarios.json --state-dir .` when both programs are in formats it reads. It reports per scenario `match`, `differs` (first divergence: time, signal, values) or `not run` (unsupported instruction such as PID, motion, MSG — with location). Also \"not run\" today: AOI calls, user FB/FC calls, LAD/FBD network summaries, MCR, JSR with parameters, file/sequencer instructions, runtime faults, and unknown presets (supply them in the scenario's `initial`). The two fixtures in `fixtures/plc/*/sim/` show both outcomes: the two-tank translation differs when the analog raw range isn't converted (`mapping.csv`) and matches when it is (`mapping_scaled.csv`).\n3. If the user has a vendor simulator (PLCSIM Advanced, Logix Emulate/Echo, CODESYS Control Win, TwinCAT UM runtime) connected through a tool you can actually call, you may use it — only against a simulator instance the user explicitly identified as simulated, never a device on a plant network.\n4. Report with the Verify format. Every result carries \"simulated, not hardware-tested\". If nothing ran, the title says \"NOT RUN\" and there are no verdicts. Each `differs` becomes or updates a review-queue item.\n\n## Workflow 4 — Dependency map\n\n1. `dependency_map.py --plc <PLC files or parsed.json> --hmi <HMI/SCADA files> [--target <t>] [--mapping mapping.csv] --md .rungstone/reports/dependencies.md -o .rungstone/dependencies.json`.\n2. Present conflicts most severe first with one sentence of consequence each: `label_mismatch` (descriptions disagree or contradict between PLC, I/O map, HMI), `competing_writers` (double coils, PLC vs HMI, same timer twice), `read_never_written` (also an open question), `broken_link` (HMI points at a tag that doesn't exist), `translation_break` (link that breaks after translation).\n3. Note the blind spots: indirect addressing, pointers, MSG/comms, and anything in files not supplied.\n\n## Workflow 5 — Lock-in report\n\nGoal: the engineer (or their manager) sees what ties a CODESYS, TwinCAT or IEC ST project to its platform, what moving it to each target would involve, and a rough effort range, before anyone commits to a migration.\n\n1. `portability.py <files or .rungstone/parsed.json> --md .rungstone/reports/portability-<source name>.md -o .rungstone/portability.json` (add `--targets openplc,siemens-scl` if the user named targets). If the result says `supported: false`, tell the user what it says (Rockwell and Siemens sources aren't scored yet) and offer Translate instead.\n2. Read `references/portability.md` before presenting: what each rating means, the limits, and how to word the effort range.\n3. Present it in the Lock-in format from `references/output-formats.md`: the summary table first, then the three to five findings that drive the effort with one sentence each on what moving them involves, then the full findings table and the per-POU view. Keep the report's \"rough planning estimate, not a quote\" line.\n4. Libraries: every namespace the report lists is code that isn't in the export. Say that each needs an equivalent on the target, or a license that allows using it there; don't guess what the library does beyond its name.\n5. Don't recommend a platform. The report shows the cost of each; the choice is the engineer's. If they ask which is cheapest, point to the hours and the blockers, and say what the hours leave out.\n\n## Workflow 6 — Check (static analysis)\n\n1. `lint.py <files or .rungstone/parsed.json> --md .rungstone/reports/check-<source name>.md -o .rungstone/check.json`.\n2. Present in the Check format from `references/output-formats.md`: high findings first, each with the location, one sentence on what goes wrong on the machine, and the fix. Read the code around each high and medium finding before presenting it; drop or downgrade one you can show is fine (say why), and add anything serious you notice that the rules missed, labelled as your reading.\n3. Findings are things to check, not proof. Say what static analysis can't see (HMI and comms writes, indirect addressing).\n\n## Workflow 7 — Diff (what changed between two exports)\n\n1. `diff_exports.py <old> <new> --md .rungstone/reports/diff-<new name>.md -o .rungstone/diff.json`. Both files must be the same project exported at two times (rename the old one if needed).\n2. Start with a plain-English summary of each change: what it does on the machine, the outputs it writes, and what else reads them (the \"affects\" table). Then the per-routine diffs.\n3. Call out anything that changes behaviour in a way the change description may not mention: an output now written from two places, a removed interlock, a timer or task rate change, a type change that can overflow. Run `lint.py` on the new version if the changes are substantial.\n4. Safety logic is never compared. If the diff lists safety items, say the safety team must review any change there.\n\n## Workflow 8 — Unit tests (CfUnit, TcUnit, plain IEC)\n\n1. Pick a function block (tests call their own instance; a PROGRAM must be moved into an FB first). Write scenarios as in Verify (`scenarios.json`): set only its VAR_INPUTs, observe its VAR_OUTPUTs.\n2. `make_tests.py --program <file> --entry <FB> --scenarios <file> --framework cfunit|tcunit|plain -o out/tests`. Expected values are what the code does today, from the simulator: these are characterisation tests that catch any change in behaviour during a refactor or migration. If a scenario's result looks wrong to you, say so: the test would lock in a bug. Quote test and assertion counts from the script's JSON output; don't count them yourself.\n3. CfUnit/TcUnit: tell the engineer to add the library, paste or add the generated objects, and run PRG_TEST in a task with the cycle time in the header. Generated code is a draft: it has not been compiled in CODESYS or TwinCAT here.\n4. Plain IEC (`--openplc --with-source` for OpenPLC): runs anywhere; pass/fail counters on %MW0-%MW2 (holding registers 1024-1026).\n5. Results are \"simulated, not hardware-tested\" until the engineer runs the tests on their runtime.\n\nFor deeper HMI/SCADA work (screens, scripts, alarms, rebinding), the `rungstone-hmi-scada` skill takes over using the same `.rungstone/` state; for \"why did it stop\" questions with historian data, `rungstone-ops`.\n\n## Results for the workbench\n\nThe workbench is **beta**: say so when you open it, and treat chat as the reliable path for approvals if it misbehaves. It shows whatever is in `.rungstone/` and refreshes itself, so save every result there as you finish it:\n- the full answer you showed, as Markdown: `.rungstone/reports/understand-<source name>.md`, `translate-<source name>.md`, `verify-<source name>.md`, `portability-<source name>.md`, `check-<source name>.md` or `diff-<new name>.md` (overwrite on a rerun);\n- every translated file you wrote: `state.py output out/<file> [...] --workflow translate` (the Translate tab shows the code), and generated tests with `--workflow tests` (the Check & tests tab);\n- the JSON from Lock-in, Check and Diff at the paths in their workflows (`.rungstone/portability.json`, `check.json`, `diff.json`): the Lock-in, Check & tests and Diff tabs draw their tables from them;\n- the queue, mapping, verify results and dependency map through the scripts as above.\n\n## Approvals and export\n\n- The engineer decides each review item: approve, edit (new fix), or reject (with reason). Apply decisions with `state.py queue approve R1 R3`, `queue edit R2 --fix \"...\"`, `queue reject R4 --note \"...\"`, then regenerate affected outputs. Every decision lands in `.rungstone/decisions.log`.\n- Chat approvals work everywhere: \"approve 1, 3; reject 2: keep INT\" → apply them, regenerate, show the updated queue.\n- Workbench: `python3 <skill-dir>/scripts/_shared/workbench/server.py --state-dir .rungstone` starts a localhost-only UI (review queue with Approve/Edit/Reject, mapping, verify results, conflicts). Elsewhere, `static.py --state-dir .rungstone -o workbench.html` makes one self-contained page; approvals then come back through chat.\n- Export: `_shared/export.py <files>` copies deliverables to `export/<timestamp>/` with a MANIFEST. It refuses while any high-risk item is open — tell the user which ones.\n\n## When the input is small or pasted\n\nA few pasted rungs still get the full treatment, just shorter: parse them from a file, state the safety check result, label meanings, list open questions. Don't skip the mapping table or the review queue on a translation because it is small — the two-tank example has 11 signals and at least four high-risk items (analog scaling, normally-closed stop inference, retentive registers, registers with no writer).\n\n## Limits to state when relevant\n\n- Static analysis sees only the files supplied; indirect addressing, pointers, and comms can hide readers/writers.\n- The simulator approximates standard instructions only; vendor-specific ones (PID, motion, MSG, file/sequencer) show as \"not run\". It does not model asynchronous I/O or exact task timing.\n- LAD/FBD from SimaticML and FBD from L5X are summarized; topology may be approximate.\n- No live monitoring and no controller connection of any kind.\n"
}SHA-256 of public snapshot: a6a2dcc4aa523b6e7210dfbebe52a65f8ee3cf070b5d8cbdde6b88f6506d939d