Generating Python Installer
affaan-m/ECC
Commercial-grade Python installer expert for Windows: Nuitka extreme compilation, dist slimming, DLL footprint analysis, and Inno Setup packaging to ship the smallest, fastest installers.
Agent skill
by MichielVanwelsenaere in MichielVanwelsenaere/HomeAutomation.CoDeSys3
Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/sync-implementation-project .claude/skills/sync-implementation-project && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .claude/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-projectType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/sync-implementation-project .agents/skills/sync-implementation-project && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .agents/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/sync-implementation-project .cursor/skills/sync-implementation-project && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .cursor/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git --path .claude/skills/sync-implementation-project--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/sync-implementation-project .gemini/skills/sync-implementation-project && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .gemini/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-projectInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/sync-implementation-project .github/skills/sync-implementation-project && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .github/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/sync-implementation-project .opencode/skills/sync-implementation-project && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "sync-implementation-project" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/sync-implementation-project into .opencode/skills/sync-implementation-project/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sync-implementation-project", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
sync-implementation-projectBring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does.
Sync Implementation Project is an agent skill from MichielVanwelsenaere/HomeAutomation.CoDeSys3. Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does. Use when an implementation project (a real building, e.g. SiteA.project) has fallen behind the reference, when checking whether such a project is still CODESYS-version-compatible with the reference, or when comparing an installation project against this one.
Its SKILL.md is about 7.4k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including scripts (for example `scripts/check_logic_unchanged.py`, `scripts/plan_declaration_migration.py` and `scripts/plan_sync.py`).
The repository describes itself as: Home Automation system build in CoDeSys 3 with MQTT communication to any third party Home Automation software. The licence is MIT.
9 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 924c3da. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 4 files in scripts/ (Python and PowerShell), which the agent can run.
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Sync Implementation Project loads about 7.4k tokens when it runs. Until then it costs about 108 tokens; SKILL.md has 3,968 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from MichielVanwelsenaere/HomeAutomation.CoDeSys3 at commit 924c3da, republished under its MIT licence (© MichielVanwelsenaere). 3,968 words, ~7,383 tokens.
.claude/skills/sync-implementation-project/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.This repository is the reference project. Real buildings run installation
projects — separate .project files, in other repositories, each wired to its
own I/O, its own rooms, its own equipment. They share the function blocks and
data types; they do not share logic.
A sync moves the shared half and leaves the other half exactly as it was:
| Owned by | Synced | |
|---|---|---|
FUNCTION_BLOCK, FUNCTION, TYPE, INTERFACE in the project-level POU pool | the reference | yes |
PROGRAM (PLC_PRG_MAIN, …) — which blocks exist, on which channels | the installation | never rewritten; call sites migrated when an interface changed (§6b) |
VAR_GLOBAL (MqttVariables, PersistentVars) — the instance declarations | the installation | never rewritten; appended with shared constants the blocks need (§4) |
| Task configuration, device tree, I/O modules, I/O mapping | the installation | never |
| Blocks the installation invented for itself | the installation | never |
The two qualified rows are where the real work is. Both are append-or-migrate only, both are checked afterwards, and neither ever changes what the building does.
The dividing line is structural, not a naming convention: anything filed under
a device node belongs to the installation; anything at project level is shared
library code. The planner applies that rule plus the declared kind, so a
site's own FB_VIRTUAL_BOOL_MQTT under VIRTUAL/ is left alone and a site's
PLC_PRG_MAIN is never touched.
The reference project renamed its objects to the convention in
docs/CodingStyle.md: MqttVariables is
GVL_MQTT there, PersistentVars is GVL_PERSISTENT, PLC_PRG_MAIN is
PRG_MAIN. An installation keeps its own spelling until it is migrated, which is
exactly why the names above are still the old ones — they name objects this skill
promises never to rewrite.
Consequence for a sync across that boundary: a synced block now refers to
GVL_MQTT, so the installation's GVL has to be renamed in the same window or
every synced block fails with Identifier 'GVL_MQTT' not defined. Use the
rename task for it (see the codesys-loop skill), against the installation
project, before syncing the blocks. That is a breaking, one-off migration and it
belongs to the major release, not to a routine sync.
An installation project is a building's running control program. Two rules, and neither is negotiable:
codesys.ps1 download refuses a -Project override
outright. Do not work around it. This skill changes files on disk and nothing
else..ai/sync/, and only replace the original once the
copy has built cleanly, passed the logic-unchanged check, and the user has
said in so many words to replace it.Read codesys-loop first if you have not — the tasks below are its tasks, with
-Project pointed elsewhere. Its warnings apply here too, especially: a clean
build does not mean checked, and an edit can report success without taking
effect.
A sync is two passes, not one. Pass one brings the blocks across (§1–§7). Pass two brings the installation onto the way the reference now configures those blocks (§6c) — a refactor the compiler cannot ask for, because the old style still works. Skipping pass two produces a project that builds clean, passes every check, and has not caught up. Plan for both from the start.
Each CODESYS task boots the IDE, so budget 60–120 seconds per step. Everything
lands in .ai/, which is gitignored.
./tools/ai/codesys.ps1 doctor
$impl = 'C:\path\to\SiteA.project'
./.claude/skills/sync-implementation-project/scripts/Working-Copy.ps1 new -Target $implThe copy lands at .ai/sync/<stem>/<stem>.project. Everything from here on
targets the copy, except the info in step 2, which reads the original
read-only to describe what is actually deployed.
$copy = '.ai\sync\SiteA\SiteA.project'
./tools/ai/codesys.ps1 info -Full -Output .ai\sync\reference.json
./tools/ai/codesys.ps1 info -Full -Project $copy -Output .ai\sync\before.jsoninfo opens a project read-only with VersionUpdateFlags.NoUpdates, so
looking at an older installation project never converts it as a side effect.
-Full includes source text, which the planner needs to work out dependencies.
The report carries: the IDE version that read it, every library reference, every device with its id and version, and every code-bearing object with a hash of its declaration, implementation and members. That hash is what "this block differs from the reference" means here — not a text diff, not the XML.
./tools/ai/codesys.ps1 verify -Baseline -Project $copyThis is the version-compatibility gate, and it is the one that matters. The compiler version a project stores is not reachable from the scripting API — a reflection sweep over the application object finds nothing, so do not go looking again. Building it with the installed IDE answers the question directly: if the project needs a compiler version, device description or library this install does not have, the build says so by name.
It also records the baseline, so the post-sync build reports only what the sync
caused. Reports for a foreign project are stem-qualified
(baseline.SiteA.json), so this cannot overwrite the reference
project's own baseline.
If this build is not clean, stop. A sync onto a project that was already broken cannot be assessed, because every later message is ambiguous.
py .claude/skills/sync-implementation-project/scripts/plan_sync.py `
--reference .ai/sync/reference.json `
--implementation .ai/sync/before.json `
--out .ai/sync/plan.json --emit-edits .ai/sync/editsThe plan prints a compatibility verdict and five lists: what to update, what was
pulled in as a dependency, what is already identical, what belongs to the
installation, and what needs a human. Exit codes: 0 clean, 2 incompatible,
3 something needs a decision.
Read it before running anything else, and show the user the update list and the left-alone list. This is the point where a misclassification is cheap to catch and expensive to miss.
Two behaviours worth knowing:
E_MQTT_ENTITY is the real example —
the type is added too, transitively. Without that the sync produces
Identifier 'E_MQTT_ENTITY' not defined and nothing in the plan explains why.--add-missing includes them
if the user asks for that.--emit-edits additionally writes .ai/sync/edits/ with the global-variable
members the synced blocks need and the installation lacks. The shared/logic
split is not clean at the GVL boundary: MqttVariables holds a building's
instance declarations and shared infrastructure the blocks read, such as the
MQTT_TOPIC_LEN / MQTT_SUFFIX_LEN string-length constants. Without them every
synced block fails with Identifier 'MqttVariables.MQTT_TOPIC_LEN' not defined
and the GVL is the last place anyone looks, because the GVL is on the
do-not-touch list.
The generated edit is an append with an idempotence sentinel — nothing
existing is rewritten, so no instance declaration can be lost, and re-running
does not declare anything twice. check_logic_unchanged.py re-verifies that
append-only property line by line afterwards. Anything beyond an append is a
human's call.
$plan = Get-Content .ai/sync/plan.json -Raw | ConvertFrom-Json
Remove-Item .ai\sync\candidates -Recurse -Force -ErrorAction SilentlyContinue
./tools/ai/codesys.ps1 export -Only $plan.export -Output .ai\sync\candidates\shared.xml-Only exports exactly those objects and errors out by name if one is not
found, rather than quietly exporting less than asked. The export carries folder
structure, which the import needs.
./tools/ai/codesys.ps1 verify -Project $copy -Candidates .ai\sync\candidates `
-ImportFolders -ImportConflict replace -Edits noneAll four flags are load-bearing:
-ImportConflict replace — without a conflict policy the importer has no
say in what happens when the object already exists, and what it does is add a
second one. The installation keeps compiling its old block and the sync
achieves nothing.-ImportFolders — a candidate exported from another project carries
folders; without the flag the block lands at the project root beside the real
one, is never compiled, and the build stays green while the sync has done
nothing. The two flags fail the same way and are needed together.-Candidates keeps the sync's set separate from .ai/candidates, so a
half-finished block edit cannot ride along into a building's project.-Edits none because this repo's edit spec targets the reference project.verify sandboxes into .ai/work — the working copy is not written yet.
Read NEW vs baseline, not just the result line. Expect breakage here — see the
next section, which is where most of the work in a real sync actually is.
A block whose interface changed leaves the installation's programs calling
something that no longer exists. On the first real sync of SiteA that
was four errors in two programs: FB_OUTPUT_COVER_MQTT had replaced
ConfigureFunctionBlock with FB_init parameters, and FB_MqttPublishQueue
had replaced an EMPTY flag with HasMessage().
These have to be fixed for "all blocks updated" to mean anything — an un-buildable project is not a delivered sync. But they edit the installation's own code, so:
EMPTY becomes NOT HasMessage(), not "roughly equivalent".
A timing moves from a runtime call to an FB_init argument at the same value.Author them in .ai/sync/edits/migrations.json, which plan_sync.py folds into
the generated edits.json — its own file, so re-running the planner cannot wipe
it:
{ "edits": [
{ "pou": "PLC_PRG_MAIN",
"replace_in_decl": [
{ "find": "FB_DO_COVER_001\t\t\t\t:FB_OUTPUT_COVER_MQTT;",
"with": "FB_DO_COVER_001\t\t\t\t:FB_OUTPUT_COVER_MQTT(T#1S, T#20S);",
"count": 1 } ] },
{ "pou": "PLC_PRG_MQTT", "member": "MQTT_PUBLISH",
"replace_in_body": [
{ "find": "NOT(MqttVariables.fbMqttPublishQueue.EMPTY)",
"with": "MqttVariables.fbMqttPublishQueue.HasMessage()", "count": 1 } ] }
] }count is worth setting every time: it turns "I expected one call site and there
were three" from a silent over-edit into a failure.
Never hand-type a find string. ST here is tab-aligned, and the tabs do not
survive being read off a screen — the first attempt at the cover migration used
seven tabs where the source had eleven, and replace_in_body refused it. Extract
the exact substring from the -Full info report instead:
impl = <the member's "impl" text from before.json>
i = impl.find("FB_DO_COVER_001.ConfigureFunctionBlock")
find = impl[i:impl.find(");", i) + len(");\n\n")]
assert impl.count(find) == 1A miss is an error, not a no-op, which is the one thing that makes this safe: a half-applied migration cannot pass as a success.
FB_init arguments: adding is safe, changing is not. Giving an instance that
had no arguments a FB_init list works — CODESYS parses it from the
declaration text, and the cover migration above is verified to land. Changing
an argument an instance already has does not reach the compiler at all; the
value lives in InputAssignments metadata that no script can touch. apply and
verify print an ADVISORY when an edit changes an existing argument list. If
you see one, that edit did nothing — do it in the IDE. CLAUDE.md has the full
account.
This is the step most likely to be skipped, and skipping it is invisible.
The reference does not only change what is inside a block. It changes how a
block is configured, and those refactors are deliberately backwards
compatible — the old way keeps working. So the project compiles, NEW vs baseline reads 0, every check passes, and the installation is still written in
the previous generation's style. Nothing fails. It just never catches up.
The case in this project is self-wiring. A block that carries the
self-wiring prologue in its body and inherits FriendlyName from
FB_MQTT_BASE configures itself from its declaration:
FB_DO_LIGHT_001 :FB_OUTPUT_BINARY_MQTT := (FriendlyName := 'Landing spots',
EntityType := E_MQTT_ENTITY.Light);…which replaces roughly thirty lines of InitMqtt / InitMqttDiscoveryAsLight
in MAIN_INIT per instance. Detect and generate it:
py .claude/skills/sync-implementation-project/scripts/plan_declaration_migration.py `
--implementation .ai/sync/after.json --out .ai/sync/decl/migrations.jsonRun it after the block sync, against the post-sync inventory — the self-wiring prologue only exists in the project once the blocks have landed. Then verify and apply it as a second pass, exactly like §6b but with no candidates.
Three things it gets right that a careless hand edit does not:
Name := argument, never from the
trailing comment. In SiteA, FB_DO_LIGHT_005 is commented
// Hall and announces to Home Assistant as 'Landing'. Believe the code.
Copy the name verbatim, including what looks like a typo — that site's
pushbutton 002 announces as 'Kithcen' where the comment says Kitchen, and
"fixing" it renames a live Home Assistant entity. Mention it; do not correct
it.InitMqttDiscoveryAsLight becomes EntityType := E_MQTT_ENTITY.Light.
A binary output's default platform is Switch. Drop the EntityType on a
block that was announcing as a Light and you orphan its retained
light/.../config topic and create a second, switch entity in Home
Assistant. No compiler and no build catches that; only an MQTT snapshot diff
does.The generated edits are ordinary replace_in_decl / replace_in_body rules —
read them before applying, and note that the script appends to an existing
migrations.json rather than overwriting your hand-written ones.
Then tidy the init action by hand. Deleting thirty statements leaves the
section comments that headed them ((* Light 001 *), (* INIT COVERS STUFF *))
pointing at nothing. The script collapses the blank runs, because that is
mechanical; it cannot tell which comments are now lies. Replace what is left
with a short header saying where the wiring went, modelled on the reference's own
MAIN_INIT — otherwise the next person reads an action full of headings for
code that is not there and goes looking for it. A comments-only body_replace
carrying the remaining executable statements across verbatim cannot change
behaviour, and the before/after check still proves only that program moved.
Check the result over MQTT. This is the one migration where a clean build
proves the least: it changes which discovery topics get published and under
which platform. Snapshot the broker before and after with
tools/ai/Mqtt-Snapshot.ps1 and read the GONE section — a discovery config
that was retained before and is absent after is an entity Home Assistant still
shows and nothing publishes to any more. See codesys-loop.
Generalising. The detector encodes one specific pattern because that is the
one this reference uses. When the reference introduces the next configuration
refactor, the sync will again compile clean and again be incomplete. The
question to ask at the end of every sync is not "does it build" but "is the
installation now written the way the reference is written?" — compare the
reference's own PLC_PRG_MAIN declaration and init action against the
installation's and see whether they are the same shape.
./tools/ai/codesys.ps1 apply -Project $copy -Force -Candidates .ai\sync\candidates `
-ImportFolders -ImportConflict replace -Edits noneWrites and saves the copy. apply refuses to save if the build fails, and
injects no harness — so verify first, always.
./tools/ai/codesys.ps1 info -Full -Project $copy -Output .ai\sync\after.json
py .claude/skills/sync-implementation-project/scripts/check_logic_unchanged.py `
--before .ai/sync/before.json --after .ai/sync/after.json `
--plan .ai/sync/plan.json --migrations .ai/sync/edits/migrations.jsonA clean build says the project compiles. It does not say the sync left the programs, the GVLs and the I/O alone. Only this does. It fails on four things, each of which has actually happened somewhere in this toolchain:
It also prints compile coverage, which is how to read verify's
NOT COMPILE-CHECKED list. On a sync that warning is usually a false alarm: the
harness cannot auto-declare a block whose FB_init takes parameters, but the
installation's own programs and GVLs declare those blocks for real and those
objects are compiled. The coverage check works out which planned blocks the
installation's own compiled code actually names, instead of assuming. A block in
the uncovered list is genuinely unverified — nothing instantiates it, so nothing
compiled it — and must be reported as such.
Run codesys.ps1 compare -Project $copy -Against src\HomeAutomation.project if
you want CODESYS's own object-level diff as a second opinion; it is slower and
coarser, but it is independent of the hashing above.
Report to the user: the compatibility verdict, the list of blocks updated, any compiler messages new versus baseline, and the check result. Then ask whether to replace the original. Only after they say yes:
./.claude/skills/sync-implementation-project/scripts/Working-Copy.ps1 promote -Target $impl -Forcepromote takes a timestamped backup of the original into
.ai/sync/<stem>/backups/ first. It replaces a file and nothing more — the PLC
keeps running whatever was last downloaded to it until someone opens the project
in the IDE and downloads it deliberately.
Programs appear twice. A program lives under PRG's/ and is called from a
node under Task Configuration/<task>/, and both objects carry the same name.
The call node holds no code. Indexing an inventory by name without filtering it
hands you the empty one for every program in the project, and they all read as
"no declaration, no kind" and get misclassified. The reports carry
is_task_call for exactly this; the planner filters on it and reports any name
that is still ambiguous rather than picking one.
FB_init arguments are not in the declaration text. The reference project's
CLAUDE.md documents this at length and it applies double here: an instance in
the installation's GVL carries its FB_init arguments in InputAssignments
metadata that no script can reach. A synced block whose FB_init signature
changed will therefore compile against stale argument values on the installation
side. If a plan touches a block with an FB_init, say so explicitly — that one
needs the IDE.
The installation may extend a reference block. FB_VIRTUAL_* in
SiteA sit in the site's own folder but derive from FB_MQTT_BASE. A
change to the base surfaces as an error in a site-only block, which looks like
the sync broke something it never touched. It did not — the base changed, and
the site's derived block has to catch up. That is a report-to-the-user outcome.
Placeholders make the library check meaningful, not vacuous. Both projects
reference libraries almost entirely as placeholders (#MQTT, #OSCAT_BASIC),
which resolve through the installed library repository. Two projects opened by
the same IDE resolve them identically, so an identical reference set really is a
guarantee. A fixed reference (NETWORK, 1.3.5.3 (OSCAT)) pins a version and
must match exactly; the planner treats a missing one as blocking.
Prove an initialiser landed — do not assume it. A declaration edit that the
compiler ignores produces a clean build, a correct-looking export and a PLC
running the old value; that is the FB_init trap, and a structured initialiser
is close enough to it to be worth checking rather than trusting. Export the
program and look at how CODESYS stored it:
./tools/ai/codesys.ps1 export -Project $copy -Only PLC_PRG_MAIN -Output .ai\sync\decl\PLC_PRG_MAIN.xmlTwo things to find, and both were confirmed present for SiteA:
<variable name="FB_DO_LIGHT_001">
<type><derived name="FB_OUTPUT_BINARY_MQTT" /></type>
<initialValue><structValue>
<value member="FriendlyName"><simpleValue value="'Landing spots'" /></value>
<value member="EntityType"><simpleValue value="E_MQTT_ENTITY.Light" /></value>
</structValue></initialValue>A := (...) initialiser is stored as <initialValue><structValue>, which is
what the compiler reads — so unlike an FB_init argument, this migration is
safe to make from a script. And on the covers, InputAssignments reads
TIME#1s0ms / TIME#20s0ms, which proves the §6b FB_init arguments reached
the compiler too, not merely the text. That is the check that distinguishes
"added arguments to an instance that had none" (works) from "changed an existing
argument" (silently does not).
A block's inputs are declared on its base, not on itself. Anything that asks
"does this block have FriendlyName?" has to walk EXTENDS — the input is
declared once on FB_MQTT_BASE and inherited by fifteen blocks. Looking at a
derived block's own declaration finds nothing, and a detector written that way
reports cheerfully that there is nothing to migrate. That is exactly how the
first version of plan_declaration_migration.py found zero instances in a
project with thirteen.
A second controller means a second everything. A building with two PLCs has
two GVL_MQTT, two PRG_MAIN and two of every other application-level object,
each real and each carrying that controller's own topic prefixes. The planner
emits one path-qualified append per controller for exactly this reason; an
edit addressed by bare name would be refused as ambiguous, and a planner that
indexes by name can only see one of the two. If you hand-author an edit against
such a project, qualify it with "path" or it will not run.
decl_append already present means the constant was NOT added. Every GVL
append is guarded by skip_if_contains, and that guard is the name of the member
being declared - never a provenance comment, which is already in the GVL of any
project synced once before and would skip every later append in silence. The
build stays green either way, because the block that reads the constant is
usually not instantiated yet. Read that line in the apply output; it is the only
place the skip is reported.
A "PLANNED but unchanged" finding is usually CODESYS reformatting, not a failed
import. check_logic_unchanged.py compares text hashes, and CODESYS rewrites a
declaration as it imports it: (* ... *) and trailing // ... become /// doc
comments, name : TYPE loses its spaces, VAR sections get reordered. A block
brought across by an earlier sync therefore hashes differently from the reference
for ever, is planned again on the next sync, and reports as "the import did not
take" when re-importing it changes nothing.
Do not wave that away, and do not weaken the check - it is the one thing that catches an import that really did nothing. Tell the two apart by comparing the working copy against the reference:
A genuinely failed import shows up as a body that differs, or as a member the reference has and the copy does not.
import shared.xml: +1 replaced 1 skipped 1 is three nulls. The ScriptEngine
returns no per-object lists for an import, and @($null).Count is 1 in
PowerShell. The line looks like a result and carries none; the object counts come
from check_logic_unchanged.py.
Do not run this against src/HomeAutomation.project. It is the source, not a
target. compare refuses when both sides are the same file; nothing else checks,
so pass -Project deliberately.
SiteAis a placeholder, and so is every room and device name in this file. An installation project is named after the building it runs, and a building is somebody's address — as are the names on its lights and pushbuttons. This repository is public. Keep real ones out of it: out of the skills, out of commit messages, out of PR descriptions. Nothing here needs a real name to make sense, and the examples are written so that placeholders lose nothing.
The numbers a real run produced, as a sanity check that yours is in the right shape:
| Compatibility | same IDE 3.5.21.30, identical 36-library reference set, same PFC200 0013 / 4.19.0.0. Untouched project built clean with 5 pre-existing warnings. |
| Shared blocks updated | 24 |
| Types pulled in as dependencies | 2 (E_MQTT_ENTITY, MQTT_DISCOVERY_FAN) |
| Already identical | 27 |
| Global constants appended | 2 into MqttVariables (+12 lines, append-only verified) |
| Call sites migrated | 2 programs, 5 substitutions |
| Left alone | 5 site-only blocks under VIRTUAL/, 3 device nodes, the whole task configuration and I/O tree |
| Result | NEW vs baseline: 0, logic-unchanged check passed |
Read the coverage line. In that run only 15 of the 26 synced objects are reachable from SiteA's own code — it has binary outputs, covers and RS485, and no dimmers, no HVAC and no binary sensors. The other 11 were updated correctly but nothing in that project compiles them, so its clean build says nothing about them. They are compile-checked in the reference project, which does instantiate them; say that rather than implying the sync verified them.
The first attempt at the whole thing produced 40 errors, which is what a sync looks like before the GVL constants and the call-site migrations are in place. That is the normal path, not a sign something went wrong.
And it was still not finished. Everything above passed — clean build, zero
new messages, logic-unchanged check green — and MAIN_INIT was still ~180 lines
of InitMqtt / InitMqttDiscovery* calls written in the previous generation's
style, because the reference keeps that style working. The second pass (§6c):
| Instances moved onto their declaration | 13 (4 pushbuttons, 2 covers, 6 lights, 1 outlet) |
Init calls deleted from MAIN_INIT | 26 |
MAIN_INIT size | ~7000 → 699 → 850 characters after the tidy, one executable statement left |
| Entity platforms preserved | 6 × E_MQTT_ENTITY.Light, 1 × Switch |
| Result | NEW vs baseline: 0, only PLC_PRG_MAIN changed |
A sync takes two passes: the blocks, then the way the blocks are configured. Budget for both. The second one is the one nothing will remind you about.
© MichielVanwelsenaere, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 4 other files (scripts) in .claude/skills/sync-implementation-project of MichielVanwelsenaere/HomeAutomation.CoDeSys3.
Open the folder on GitHubat commit 924c3da
Sync Implementation Project next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Sync Implementation Project this skillMichielVanwelsenaere/HomeAutomation.CoDeSys3 | 147 | — | ~7.4k | Automated safety check: Pass | MIT | |
| Generating Python Installeraffaan-m/ECC | 276k | 1 repos | ~6.1k | Automated safety check: Pass | MIT | |
| React Syncvercel/next.js | 143k | — | ~486 | Automated safety check: Pass | MIT | |
| Claude-Mem Cloud Syncthedotmack/claude-mem | 99k | — | ~1k | Automated safety check: Notes | Apache-2.0 | |
| Kubeshark Installerkubeshark/kubeshark | 12k | — | ~3.6k | Automated safety check: Notes | Apache-2.0 | |
| Agent Sync Coordinatorruvnet/ruflo | 74k | 2 repos | ~4k | Automated safety check: Pass | MIT |
affaan-m/ECC
Commercial-grade Python installer expert for Windows: Nuitka extreme compilation, dist slimming, DLL footprint analysis, and Inno Setup packaging to ship the smallest, fastest installers.
vercel/next.js
Build local React changes in the bundle variants consumed by Next.js, sync them into a local Next.js checkout, and test the resulting integration.
thedotmack/claude-mem
Checks claude-mem cloud sync status and guides you through connecting a cmem.ai Pro account without the sync token ever passing through the chat.
kubeshark/kubeshark
Installs and configures Kubeshark on a Kubernetes cluster, choosing between the quick CLI path and a Helm install with custom values.
ruvnet/ruflo
Agent skill for sync-coordinator - invoke with $agent-sync-coordinator
thedaviddias/Front-End-Checklist
A skill your agent uses when auditing PWA readiness, adding an install prompt, or preparing a web app for submission to Microsoft Store or Google Play via PWA Builder.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Regenerate the machine-owned parts of the function block docs from the PLCopen export, scaffold a page for a new function block, and check whether the docs still match the code.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Find out why a PFC200 stopped - an application that died, a building that went dead, an exception, a crash, or a restart nobody explained - by reading the runtime's own log and core dump on the…
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Exercise the PLC's actual behaviour on real hardware - lights, pushbuttons, covers, dimmers and the HVAC chain - by commanding it over MQTT and asserting the result on the broker and in the running…
Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does. CoDeSys3. Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does.
Sync Implementation Project fits situations like: an implementation project (a real building.
Run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a claude-code`. Or copy the skill folder (.claude/skills/sync-implementation-project in MichielVanwelsenaere/HomeAutomation.CoDeSys3) into .claude/skills/sync-implementation-project in your project. Claude Code loads it when a task matches its description.
Run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a codex`. Or copy the skill folder (.claude/skills/sync-implementation-project in MichielVanwelsenaere/HomeAutomation.CoDeSys3) into .agents/skills/sync-implementation-project in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/sync-implementation-project, .gemini/skills/sync-implementation-project, .github/skills/sync-implementation-project and .opencode/skills/sync-implementation-project in your project.
Going by SKILL.md and its folder, Sync Implementation Project needs Python and PowerShell for the scripts in its folder. Our summary lists: Python 3; PowerShell.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Sync Implementation Project is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.4k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Sync Implementation Project: Generating Python Installer (affaan-m/ECC, 276k stars), React Sync (vercel/next.js, 143k stars), Claude-Mem Cloud Sync (thedotmack/claude-mem, 99k stars) and Kubeshark Installer (kubeshark/kubeshark, 12k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
MichielVanwelsenaere (a GitHub user) maintains it in MichielVanwelsenaere/HomeAutomation.CoDeSys3, which has 147 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on September 14, 2026.
Source: MichielVanwelsenaere/HomeAutomation.CoDeSys3 on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.