Second RTFM remediation. The helpme-codesys.com Git scripting page
(https://content.helpme-codesys.com/en/CODESYS%20Git/_git_using_scripting.html)
says explicitly:
"Project Save: Call project.save() after operations to persist
changes. Appears after operations like push() and merge() to
persist changes."
The example flows on that page show project.save() interleaved with
init / commit / push / merge. None of the git_* wrappers shipped so
far called save(), which means binding info, configured remotes,
upstream tracking, and post-commit state could fail to persist when
the IDE closes -- silently degrading every flow that spans more than
one CODESYS session.
This commit adds a soft-fail primary_project.save() after every
mutating op:
git_init after git.init(...)
git_commit after git.commit_complete(...)
git_remote_add after git.remote_add(...)
git_branch_set_upstream_to after git.branch_set_upstream_to(...)
git_push after git.push(...)
git_status is unchanged -- it's read-only.
Soft-fail rationale: a save() failure does NOT undo a successful git
op. We log a WARNING and continue, so the visible result still
reflects what actually happened in the repo. Bubbling save errors
would risk telling the user "init failed" when in fact the .git/ is
on disk and the only loss is the in-memory binding. The honest
failure mode is "git op succeeded, persistence may not have."
The 3 git_* tools previously surfaced the raw CODESYS exception
"Permission denied: Rule 'HasGitLicense' failed with state 'False'."
which is opaque to anyone who does not already know that CODESYS Git
scripting is gated behind the Professional Developer Edition
subscription. Verified 2026-04-25 on this workstation that the
plug-in is installed (PlugIns/GitIntegration.plugin.dll v1.7.0.0,
ScriptDriverGit.plugin.dll, full ScriptLib/Stubs/scriptengine/Git*.pyi
set, and the Git menu visible in the IDE) but the runtime rule still
returns False because no PDE subscription is active. Per the CODESYS
Git store page the subscription is the actual gate (Additional
Requirements / Licensing).
Each script now detects 'HasGitLicense' in the exception or traceback
and rewrites SCRIPT_ERROR to a clean, actionable message pointing at
https://store.codesys.com/en/codesys-git.html with a "what to activate"
hint, falling back to the original generic error formatting on any
non-license failure.
git_status: also adds an early license probe via project.git.has_working_tree()
(license-gated per the SP22 stubs) so the rewrite triggers reliably
instead of being swallowed by the per-method probe loop further down.
Tool descriptions in server.ts updated to advertise the PDE
subscription requirement up front, so MCP clients see it without
having to fail first.
Cross-references the official Git scripting docs at
https://content.helpme-codesys.com/en/CODESYS%20Git/_git_using_scripting.html
which exposes a project-bound git API as 'primary_project.git' (when the
CODESYS Git plug-in is loaded). Confirmed earlier today via dir(scriptengine)
that 'git' is one of the top-level scriptengine modules on this install.
Three tools in this commit, ordered shallow-to-deep:
git_status Read-only. Returns current branch via
project.git.branch_show_current() AND a defensive probe
of any status/changes/diff/changed_files methods on
project.git (the docs page lists the call patterns by
example but does not enumerate the full API surface, so
the probe + diagnostic dump is how we'll discover the
rest in the next iteration).
git_init Wraps project.git.init(local_repo_path). Defaults the
repo path to the project file's parent directory so a
plain 'init this project's folder' call needs no extra
args. One-shot setup; pair with git_status afterwards.
git_commit Wraps project.git.commit_complete(message, user, mail).
Required: message + authorName + authorEmail (latter
validated as email by zod). Stages all working-tree
changes and commits in one shot per the docs. Multi-line
messages handled via triple-quoted Python injection with
standard backslash + triple-quote escaping.
Defensive checks across all three:
- hasattr(script_engine, 'git') so we can distinguish "Git plug-in not
installed" from "project not in a repo".
- project.git is None handled with a clear 'run git_init first' message.
- On missing methods the script dumps sorted(dir(project.git)) so the
next debug session sees the exact surface.
Out of scope for this commit (deliberate -- one feature per commit per
the project rule, more git ops can land separately):
- branch ops (branch_copy, checkout)
- remote ops (remote_add, push, pull, fetch, branch_set_upstream_to)
- merge
- clone (script_engine.git.clone -- top-level, not project-bound)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>