0
0
Fork 0

classifier + orchestrator: see untracked files, re-mirror after bump

Two real bugs surfaced from the MCPTest2 v1.1.0.0 -> v1.2.0.0 round.

Fix 1: classifier didn't see untracked files (CLASSIFICATION GAP)
   Adding FB_Position + FB_Random5s via create_pou + mirror_export
   produced new untracked .st files in mcp-mirror/. The classifier
   ran `git diff --name-status` which ONLY reports tracked changes;
   the new files were invisible until git-added. Result: classifier
   counted only 1 'modified' (PLC_PRG, the wiring update) instead
   of 1 modified + 2 added, and resolved 'revision' instead of
   'minor'. Added a `git ls-files --others --exclude-standard --
   mcp-mirror/` pass that pulls untracked files and tags them as
   adds (`added (untracked): <path>`). Now create_pou + release
   correctly classifies as minor.

Fix 2: orchestrator didn't re-mirror after bump (DESYNC)
   Pipeline was: mirror_export -> classify -> bump -> regen md ->
   git commit. mirror_export ran BEFORE the bump, so the captured
   mcp-mirror/_MCP_PROJECT_VERSION.st reflected the pre-bump GVL
   value. Then bump updated the in-memory GVL + saved the .project
   binary. Then commit went out with mcp-mirror at the OLD value
   while the binary already had the NEW value. Two consequences:
     - Next release call sees _MCP_PROJECT_VERSION.st as 'modified'
       vs the just-pushed v* tag (because the next mirror_export
       pulls the post-bump value, which now differs from the
       still-pre-bump mirror in the tag), triggering another bump.
     - The runtime-anchor on disk and the runtime-anchor inside the
       running PLC binary were silently desynced from the docs.
   Surfaced as MCPTest2 v1.1.0.0 (8d79193) shipping with binary
   GVL = 1.0.2.0 while docs said 1.1.0.0; resolved by the v1.2.0.0
   re-sync (1513e9c).
   Added a SECOND mirror_export call right after the bump, before
   regenerating library.md / pou-dump.md / Changelog. Soft-fails
   with WARNING -- the bump itself already succeeded, post-bump
   mirror is the documentation step.

Together these two fixes make release_project_version end-to-end
deterministic: 1 release call -> 1 release commit, no manual finish,
no re-bump on the next call. Verified offline: the path of the new
untracked-detection through ls-files --others, plus the second
mirror_export, give the orchestrator the post-bump state it
previously lacked.
This commit is contained in:
Karstein Phobic Nyvold Kvistad 2026-04-26 02:12:36 +02:00
parent 9854c31b66
commit c50970b15e

View file

@ -288,7 +288,26 @@ function classifyMcpMirrorChanges(projectDir: string): ClassifyResult {
evidence.push(`git diff against ${baseRef} failed -- treating as no-changes`);
return { kind: 'no-changes', evidence };
}
if (!raw.trim()) {
// git diff only reports TRACKED changes. New files that mirror_export just
// wrote are untracked from git's POV until added, and would otherwise be
// invisible to the classifier (they wouldn't trigger a 'minor' bump even
// though they're new public symbols). Pull them in via ls-files --others.
// Surfaced on MCPTest2 today: adding FB_Position + FB_Random5s via
// create_pou caused the classifier to see only 1 modified file (PLC_PRG)
// and classify as 'revision' instead of 'minor'. The added FBs were
// untracked at classify time.
let untracked = '';
try {
untracked = execSync(
`git -C "${projectDir}" ls-files --others --exclude-standard -- mcp-mirror/`,
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
);
} catch {
// ls-files failure shouldn't block classification on tracked diff alone
}
if (!raw.trim() && !untracked.trim()) {
evidence.push('no changes in mcp-mirror/ since baseline');
return { kind: 'no-changes', evidence };
}
@ -315,6 +334,10 @@ function classifyMcpMirrorChanges(projectDir: string): ClassifyResult {
evidence.push(`modified: ${rest}`);
}
}
for (const line of untracked.split('\n').filter((l) => l.trim())) {
hasAdd = true;
evidence.push(`added (untracked): ${line}`);
}
if (hasDelete || hasRename) return { kind: 'bump', level: 'major', evidence };
if (hasAdd) return { kind: 'bump', level: 'minor', evidence };
@ -1836,6 +1859,32 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
}
log.push(`bump: ${from ?? '(none)'} -> ${newVersion}`);
// 3b. Re-run mirror_export AFTER the bump. The pre-bump mirror
// captured the old _MCP_PROJECT_VERSION.sVersion value and (when
// applicable) the old Project Information.Version. After the bump,
// those values changed in CODESYS in-memory and got saved to the
// .project binary. The mirror needs to reflect the post-bump state
// or the next 'release' call will see _MCP_PROJECT_VERSION.st as
// a real diff vs the just-tagged release and bump again. Surfaced
// on MCPTest2 v1.1.0.0 (commit 8d79193): the binary GVL was
// 1.0.2.0 while the docs said 1.1.0.0 because mirror_export
// didn't re-run after the bump.
try {
const mirrorScript2 = scriptManager.prepareScriptWithHelpers(
'mirror_export',
{ PROJECT_FILE_PATH: escaped, MIRROR_ROOT: path.join(projectDir, 'mcp-mirror') },
['ensure_project_open']
);
const mirror2 = await executor.executeScript(mirrorScript2);
if (mirror2.success && mirror2.output.includes('SCRIPT_SUCCESS')) {
log.push('mirror_export (post-bump): OK');
} else {
log.push('mirror_export (post-bump): WARNING -- post-bump mirror may be stale');
}
} catch (e) {
log.push(`mirror_export (post-bump): WARNING -- ${e instanceof Error ? e.message : String(e)}`);
}
// 4. Append Changelog (the manual-bump path doesn't auto-append; do it here)
appendChangelogEntry(projectDir, from, newVersion, levelLabel, classification.evidence);
log.push(`Changelog.md: appended v${newVersion}`);