Agent skill

Sync Implementation Project

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.

MITAuto-check passed

Install Sync Implementation Project

skills CLI
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill sync-implementation-project -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 sync-implementation-project --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
sync-implementation-project
GitHub stars
147
Token cost
~7.4k tokens
SKILL.md length
3,968 words
Files
5 (incl. scripts)
Skills in repo
5
Repo updated
First seen
Licence
MIT

At a glance

Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does.

  • Works in 9 steps: Toolchain and working copy → Describe both projects → Does the installation project still… → …
  • An implementation project (a real building
  • SKILL.md covers Before anything else, The loop, Things that will bite and Worked example: SiteA, first…
  • Runs Python and PowerShell scripts from its folder

What it 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.

When your agent uses it

  • An implementation project (a real building

Example prompts

  • “s CODESYS project up to date with this reference project”
  • “/sync-implementation-project”

Requirements

  • Python 3
  • PowerShell

Workflow steps

9 steps, taken from the step headings in SKILL.md.

  1. Toolchain and working copy
  2. Describe both projects
  3. Does the installation project still build as it is?
  4. Plan
  5. Lift the blocks out of the reference
  6. Verify the sync on a sandbox
  7. Apply to the working copy
  8. Prove it changed only what it was supposed to
  9. Hand it back

What it can do on your machine

Read from SKILL.md and the folder at commit 924c3da. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    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.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~108
When it runs · the whole SKILL.md, loaded when a task matches
~7.4k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from MichielVanwelsenaere/HomeAutomation.CoDeSys3 at commit 924c3da, republished under its MIT licence (© MichielVanwelsenaere). 3,968 words, ~7,383 tokens.

Download SKILL.mdSave it as .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.
name
sync-implementation-project
description
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.

Syncing an installation project to the reference

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 bySynced
FUNCTION_BLOCK, FUNCTION, TYPE, INTERFACE in the project-level POU poolthe referenceyes
PROGRAM (PLC_PRG_MAIN, …) — which blocks exist, on which channelsthe installationnever rewritten; call sites migrated when an interface changed (§6b)
VAR_GLOBAL (MqttVariables, PersistentVars) — the instance declarationsthe installationnever rewritten; appended with shared constants the blocks need (§4)
Task configuration, device tree, I/O modules, I/O mappingthe installationnever
Blocks the installation invented for itselfthe installationnever

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 names on this page are the installation's, not the reference's

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.

Before anything else

An installation project is a building's running control program. Two rules, and neither is negotiable:

  • Never download. codesys.ps1 download refuses a -Project override outright. Do not work around it. This skill changes files on disk and nothing else.
  • Never sync in place. The installation project may not be under source control. Work on a copy in .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.

The loop

Each CODESYS task boots the IDE, so budget 60–120 seconds per step. Everything lands in .ai/, which is gitignored.

1. Toolchain and working copy
powershell
./tools/ai/codesys.ps1 doctor
$impl = 'C:\path\to\SiteA.project'
./.claude/skills/sync-implementation-project/scripts/Working-Copy.ps1 new -Target $impl

The 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.

2. Describe both projects
powershell
$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.json

info 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.

3. Does the installation project still build as it is?
powershell
./tools/ai/codesys.ps1 verify -Baseline -Project $copy

This 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.

4. Plan
powershell
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/edits

The 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:

  • Dependencies are pulled in automatically. If an updated block references a type the installation has never had — 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.
  • Unreferenced new blocks are not. A block the installation never used is dead code there; adding it is a scope decision. --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.

5. Lift the blocks out of the reference
powershell
$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.

6. Verify the sync on a sandbox
powershell
./tools/ai/codesys.ps1 verify -Project $copy -Candidates .ai\sync\candidates `
    -ImportFolders -ImportConflict replace -Edits none

All 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.

6b. Call-site migrations

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:

  • Behaviour-preserving only. Same values, same conditions, different spelling. EMPTY becomes NOT HasMessage(), not "roughly equivalent". A timing moves from a runtime call to an FB_init argument at the same value.
  • Never invent behaviour to satisfy a new parameter. If the new interface needs a value the installation never had, that is a question for the user.
  • Every migration is reported by name and is listed in the check output.

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:

json
{ "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:

python
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) == 1

A 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.

6c. Configuration-style migrations — a green build is not "done"

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:

iecst
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:

powershell
py .claude/skills/sync-implementation-project/scripts/plan_declaration_migration.py `
   --implementation .ai/sync/after.json --out .ai/sync/decl/migrations.json

Run 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:

  • The friendly name comes from the 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.
  • An instance that is never called cyclically is refused. Self-wiring happens on the block's first cyclic call, so moving a never-called instance onto its declaration would silently remove it from Home Assistant, where the init action used to wire it regardless. It is left as an explicit call and reported.

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.

7. Apply to the working copy
powershell
./tools/ai/codesys.ps1 apply -Project $copy -Force -Candidates .ai\sync\candidates `
    -ImportFolders -ImportConflict replace -Edits none

Writes and saves the copy. apply refuses to save if the build fails, and injects no harness — so verify first, always.

Show full SKILL.md (1,651 more words)Show less
8. Prove it changed only what it was supposed to
powershell
./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.json

A 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:

  • an object changed that was not on the plan,
  • an object was removed, added or moved that was not on the plan,
  • an object on the plan did not change — the import silently did nothing,
  • a GVL append that turned out not to be append-only, checked line by line,
  • a listed migration that did not actually change its object,
  • anything else the installation owns differing at all.

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.

9. Hand it back

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:

powershell
./.claude/skills/sync-implementation-project/scripts/Working-Copy.ps1 promote -Target $impl -Force

promote 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.

Things that will bite

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:

powershell
./tools/ai/codesys.ps1 export -Project $copy -Only PLC_PRG_MAIN -Output .ai\sync\decl\PLC_PRG_MAIN.xml

Two things to find, and both were confirmed present for SiteA:

xml
<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:

  • implementation text is never reformatted. If every body is byte-identical to the reference, the code is current whatever the declaration hash says.
  • declarations agree once comments, section order and whitespace are normalised.

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.

Worked example: SiteA, first sync

SiteA is 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:

Compatibilitysame 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 updated24
Types pulled in as dependencies2 (E_MQTT_ENTITY, MQTT_DISCOVERY_FAN)
Already identical27
Global constants appended2 into MqttVariables (+12 lines, append-only verified)
Call sites migrated2 programs, 5 substitutions
Left alone5 site-only blocks under VIRTUAL/, 3 device nodes, the whole task configuration and I/O tree
ResultNEW 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 declaration13 (4 pushbuttons, 2 covers, 6 lights, 1 outlet)
Init calls deleted from MAIN_INIT26
MAIN_INIT size~7000 → 699 → 850 characters after the tidy, one executable statement left
Entity platforms preserved6 × E_MQTT_ENTITY.Light, 1 × Switch
ResultNEW 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

Files

SKILL.md and 4 other files (scripts) in .claude/skills/sync-implementation-project of MichielVanwelsenaere/HomeAutomation.CoDeSys3.

  • SKILL.md
  • scripts/Working-Copy.ps1
  • scripts/check_logic_unchanged.py
  • scripts/plan_declaration_migration.py
  • scripts/plan_sync.py

Open the folder on GitHubat commit 924c3da

Compare with similar skills

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.

Sync Implementation Project compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Sync Implementation Project this skillMichielVanwelsenaere/HomeAutomation.CoDeSys3147—~7.4kAutomated safety check: PassMIT
Generating Python Installeraffaan-m/ECC276k1 repos~6.1kAutomated safety check: PassMIT
React Syncvercel/next.js143k—~486Automated safety check: PassMIT
Claude-Mem Cloud Syncthedotmack/claude-mem99k—~1kAutomated safety check: NotesApache-2.0
Kubeshark Installerkubeshark/kubeshark12k—~3.6kAutomated safety check: NotesApache-2.0
Agent Sync Coordinatorruvnet/ruflo74k2 repos~4kAutomated safety check: PassMIT

Similar skills

  • 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.

    276k GitHub starsUsed in 1 repo~6.1k tokens
    Testing & QAAuto-check passed
  • React Sync

    vercel/next.js

    Official

    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.

    143k GitHub stars~486 tokensUpdated today
    Auto-check passed
  • Claude-Mem Cloud Sync

    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.

    99k GitHub stars~1k tokensUpdated today
    Agent WorkflowsAuto-check: notes
  • Kubeshark Installer

    kubeshark/kubeshark

    Installs and configures Kubeshark on a Kubernetes cluster, choosing between the quick CLI path and a Helm install with custom values.

    12k GitHub stars~3.6k tokensUpdated yesterday
    DevOps & CloudAuto-check: notes
  • Agent skill for sync-coordinator - invoke with $agent-sync-coordinator

    74k GitHub starsUsed in 2 repos~4k tokens
    Auto-check passed
  • Pwa Installability

    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.

    74k GitHub stars~450 tokensUpdated 2 days ago
    Auto-check passed

More from MichielVanwelsenaere/HomeAutomation.CoDeSys3

  • Update Fb Docs

    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.

    147 GitHub stars~1.9k tokensUpdated 24 days ago
    Auto-check passed
  • Troubleshoot Plc Exception

    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…

    147 GitHub stars~3.6k tokensUpdated 24 days ago
    Auto-check passed
  • Codesys Loop

    MichielVanwelsenaere/HomeAutomation.CoDeSys3

    Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine.

    147 GitHub stars~11k tokensUpdated 24 days ago
    Auto-check passed
  • Test Plc Logic

    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…

    147 GitHub stars~8.9k tokensUpdated 24 days ago
    Auto-check passed

Questions about Sync Implementation Project

What does Sync Implementation Project do?

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.

When should I use Sync Implementation Project?

Sync Implementation Project fits situations like: an implementation project (a real building.

How do I install Sync Implementation Project in Claude Code?

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.

How do I install Sync Implementation Project in Codex?

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.

Can I use Sync Implementation Project in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Sync Implementation Project need to run?

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.

Does Sync Implementation Project access the network?

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.

Is Sync Implementation Project safe to install?

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.

What licence does Sync Implementation Project use?

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.

How many tokens does Sync Implementation Project use?

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.

What are the alternatives to Sync Implementation Project?

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.

Who maintains Sync Implementation Project?

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.