fix: UltiEssentials follow-up (#66, #67, #68, #69) - #70
Merged
Merged
Conversation
…rk (#68) UltiTools-API 6.3.0 removes the configuration accessor RemovedConfigKeys read, so on the merged framework this module does not compile (and a jar built against 6.2.x is refused at load with NoSuchMethodError). New cases pin what the migration must keep: a removed key holding an explicit null is reported, an unparseable or unreadable file reports no removed key, and a removed key put back between two reloads is reported on the second and not once removed again. The existing start-up, reload and clean-file controls stay. RED: main does not compile against the merged framework (RemovedConfigKeys.java:83, getConfig()). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rver thread From UltiTools-API 6.3.0 ConfigManager refuses to register, load or reload a configuration off the server thread (ConfigManager#permitsConfigThread, UltiKits/UltiTools-Reborn#538). The shared mock server answered isPrimaryThread() with Mockito's default false, so on the merged framework every config load in 70-odd tests was refused and they failed for a reason no real server has. WildCooldownBindingTest already stubbed it locally; the stub now lives in the shared helper. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he module loads on 6.3.0 (#68) UltiTools-API 6.3.0 removes the configuration entity's parsed-file accessor that RemovedConfigKeys#warningsFor read (RemovedConfigKeys.java:83), so the module did not compile on the merged framework and a jar built against 6.2.x is refused at load with NoSuchMethodError. The warning now asks AbstractConfigEntity#isPresentInFile for each removed key: the same ordered, localized lines at start-up and on every /ul reload for a file holding a removed key (an explicit null included), none for a clean file. A file the framework could not read or parse reports no key (the framework logs its own SEVERE line); a missing configuration bean still yields the "not checked" line. Javadoc no longer describes the removed accessor. CHANGELOG, FEATURES row ultiessentials.lifecycle.removed-key-warning and new checklist row ultiessentials.lifecycle.removed-key-warning.reload-restored in the same commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… on 6.3.0 (#69) On the merged framework a JSON read is a detached copy and insert caches a copy with putIfAbsent (UltiKits/UltiTools-Reborn#522), so the repair's in-place key write no longer reaches the store and 11 existing EntityIdBackfillServiceTest cases go red (repaired 0). The cache-backed case is restated: the store is asked to delete the legacy entry by its identity (one cache lookup) and never by condition (the full-cache pass the repair avoids for speed); SilentlyFailingStore counts condition deletes separately. A new case pins that a legacy entry stored under a name other than its identity is not counted repaired while an un-keyed copy remains. RED: 12 failures in EntityIdBackfillServiceTest on the merged framework. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lete destroys nothing (#69) The repair now has to remove the legacy entry before inserting the keyed record on a JSON store, so an insert that silently reaches nothing would lose the record. The detached-read fixture (which modelled the open question of UltiKits/UltiTools-Reborn#522 before 6.3.0, now the real JSON operator's behaviour) is restated for that hazard and delegates its transaction to the real operator, whose rollback restores the entries: four legacy records stay, unkeyed, reported as untouched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…again (#69) UltiTools-API 6.3.0 makes JSON reads detached copies and makes insert cache a copy with putIfAbsent (UltiKits/UltiTools-Reborn#522). The repair skipped the delete on a cache-backed store and relied on insert writing the key onto the cached legacy record in place, so on the merged framework it repaired nothing on JSON and logged its partial-write error at every start. On a cache-backed store the legacy entry is now removed by its identity (delById, one cache lookup, not the full-cache condition delete the repair avoids for speed) and the keyed record inserted, inside the same single transaction; each key is confirmed there, and a key that did not reach the store fails the transaction, whose rollback restores every entry. A record is counted repaired only when no unkeyed copy of its identity remains. The relational path is unchanged. CHANGELOG, FEATURES row ultiessentials.storedkey.repair and new checklist row ultiessentials.storedkey.repair.json-store in the same commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mework's CJK gate Guard 2 gains one assertion per range the framework's check-cjk-scope.sh detects beyond U+4E00 to U+9FFF (Han Extension A, a supplementary-plane ideograph, a compatibility ideograph, CJK Symbols and Punctuation, Halfwidth and Fullwidth Forms), a kana and range-edge control that must stay undetected, and a literal holding only a widened-range character. RED on the old scanner (6 failures). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ct (UltiRemoteBag#44) I18nSourceScanner#containsCjk iterates code points and detects the Han script (extensions, supplementary planes, compatibility ideographs, radicals), U+3000 to U+303F and U+FF00 to U+FFEF; kana stays out. Character for character the contract of the framework's check-cjk-scope.sh. Nothing in this module's sources or catalogues is newly caught (both guards green, 88 tests). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… key sites (#67) A comment written as one {key} token resolves through the module catalogue, so guard 1 must treat it like an i18n call: the key has to exist in every language and every catalogue key has to be reachable. A literal comment, a token with more text, two tokens and an empty token are not sites. RED on the old scanner, which has no such site (3 failures). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…key sites (#67) I18nSourceScanner adds a CONFIG_COMMENT key site for a comment that is one string literal holding a single trimmed {key} token, read exactly as the framework reads it, and marks that literal as a key. Both language guards now check such a key in every catalogue and keep its catalogue entries reachable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…guard 2 no longer skips them (#67) Guard 2's skip for the comment element of @ConfigEntry ends with the adoption of translatable config comments, so a new Chinese-only comment fails the build; the guard file's header names this one deliberate difference from the modules that keep the skip. EssentialsConfigCommentsTest runs the framework's real load of all five files (essentials, lobby, motd, spawn, tabbar): every one of the 78 comments is one {key} token, the zh catalogue holds the text master 0875d16 wrote verbatim and the en catalogue its translation (fixture src/test/resources/config/config-comments.tsv), a fresh install under en writes English comments and under zh the Chinese ones, and an upgrade from a file with the Chinese comments switches only the comment lines, keeps an edited value, and is byte identical on the second start. RED: guard 2 reports the 78 Chinese comments; four of the five new cases fail (the zh fresh install already holds, as it must). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… files (#67) The 78 Chinese @ConfigEntry comments in EssentialsConfig (59), SpawnConfig (8), LobbyConfig (6), MotdConfig (3) and TabBarConfig (2) become {essentials.config.<file stem>.<key path>} tokens; lang/zh.json carries the Chinese text master wrote verbatim and lang/en.json its English translation. The framework (UltiTools-Reborn#542) writes them in the server's language on a fresh install and rewrites existing token comments on every write of the file; values are untouched. 0 Chinese comment attributes remain (78 of 78 converted; control: git grep counts 78 comment attributes). CHANGELOG, FEATURES row ultiessentials.config.comments and checklist rows ultiessentials.config.comments-fresh and ultiessentials.config.comments-upgrade in the same commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…al, naming it (#66) The reload tests now call the hook the framework calls, onReload(ReloadReport), with a fresh report (UltiEssentialsServiceReloadTest, UltiEssentialsTest, UltiEssentialsRemovedConfigKeyTest, EssentialsConfigTextTest); on master the framework's default forwards it to onReload(), so every existing case still holds there. New assertions: a service whose reload throws, and one the container cannot resolve, each reach the report as one partial reason naming the service; a clean reload reports nothing partial; through the framework's own reloadWithReport() a failing service makes the report partial and the framework logs its partial line, never the plain "reloaded." line; UltiEssentials declares onReload(ReloadReport). RED: 4 failures in UltiEssentialsServiceReloadTest (the report stays empty) and UltiEssentialsLifecycleTest#declaresTheReportingReloadHook (no such method). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…eloadReport) (#66) WildCooldownBindingTest verified onReload() directly, which compiled only while UltiEssentials declared that overload in its own package. The framework calls onReload(ReloadReport); the verification now reaches that protected hook reflectively, so it holds before and after the module moves to it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ad (#66) UltiEssentials now overrides onReload(ReloadReport). reloadService still restarts each of ScheduledCommandService, ScoreboardService and NamePrefixService on its own and logs a failure as before, and also records it in the framework's report naming the service: "<Service> could not be reached and was not restarted; ..." or "<Service> did not restart: <cause>" (new catalogue keys essentials.reload.partial_unreachable / partial_failed in en and zh). /ul reload UltiEssentials then replies that the reload was partial instead of an unconditional success, and the framework logs its partial line (UltiKits/UltiTools-Reborn#529). The start-up restart in registerSelf passes no report and is unchanged. Javadoc that named onReload() or said the reply is unconditional is updated. CHANGELOG, FEATURES Lifecycle prose and row ultiessentials.lifecycle.reload-partial, and checklist row ultiessentials.lifecycle.reload-partial in the same commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
Author
|
Local Codex review, run 1 of at most 2 (gate 2).
This repository is not onboarded to Codacy; gate 2 for module repositories is the Codex review only. |
Member
Author
|
Top-up review P3 / out-of-threat-model findings are collected in the follow-up issue: #71 |
…de and reload tests Owned by the 17-70 cross-module sweep (17-FU2-MODULE-SWEEP.md, UltiEssentials): - class B (EssentialsConfigCommentsTest:194): since the maintainer decision of 2026-10-04 only framework-written comments are rewritten, and the framework recognises its own comment by comparing it with the module's shipped catalogues (UltiToolsPlugin#shippedCatalogueTexts). The test's mocked module answered that seam with an empty list, so the old Chinese comment counted as the operator's. The mock is now the module's own class and that one method runs for real, reading this module's catalogues as a real server does. - class D (UltiEssentialsServiceReloadTest, 17 tests; UltiEssentialsRemoved- ConfigKeyTest, 2 tests): a module save that leaves a change unwritten now logs a WARNING through UltiTools#getLogger(), which the test's mocked UltiTools answered with null. The mock now returns a real logger, as the running plugin always has. No production code changes; 1337 tests, 0 failures against the merged framework. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…on settings (#72) Maintainer decision 2026-10-04 (what code may write, by file type): an operator's command writes exactly the item it names - /setspawn only the location. Maintainer decision 2026-10-05: a write the gate refuses is answered 'not saved' and why, and the in-memory change is rolled back. New real-file tests through the framework's write gate (RED by behaviour on this head, whose whole-entity save() leaves a hand-edited location setting unwritten and answers a refused write with 'Spawn point set'): - a location setting edited by hand since the load is replaced too, and only the six location lines change (spawn and lobby); - an anchored file is not written, the reply says not saved and why, and the running location is the one the file holds (spawn and lobby); - guard: a hand edit of spawn.teleport-on-respawn stays. The mock-based command tests now expect saveOperatorChange with the six paths instead of save(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tings, and roll back a refused write (#72) Both commands now call saveOperatorChange with their six location paths (SpawnConfig/LobbyConfig#locationPaths) instead of the whole-entity save(): the operator's command is consent for those six settings, so a location setting edited by hand is replaced, and the framework's write gate keeps every other line byte for byte (maintainer decision 2026-10-04). A ConfigWriteRefusedException is answered with the new 'Spawn point not saved: <reason>. ...' / 'Lobby not saved: ...' line, and any IOException puts the six settings in memory back to what the file holds (maintainer decision 2026-10-05); other IOExceptions keep the existing save-failed line. CHANGELOG, FEATURES (two persistence rows; spawn.set and lobby.set point at them) and UAT rows ultiessentials.spawn.setspawn-writes-location-only (+ a refused-write negative row) and ultiessentials.spawn.setlobby-writes- location-only in the same commit; the two existing rows' stale line citations now name the method. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…editing an official language file Maintainer decision 2026-10-04 (official language files are framework-owned): UltiTools-API 6.3.0 restores an edited official language file at every start. The changelog no longer tells operators to re-apply edits to lang/en.json or lang/zh.json, the README names the copy-rename-select customisation, and the checklist's note that an upgrade never refreshes an extracted language file (convention and ultiessentials.i18n.language) is replaced by what the framework now does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ngs of essentials.yml (write-gate sweep S5) Maintainer decision 2026-10-04 (what code may write, by file type): code may re-render a value that still equals shipped text after a language switch and nothing else. Real-file guard over the module's materializer save: after an en -> zh switch and reload, a typo value (default-max-homes: 3O) and an operator's comment written above a framework comment stay byte for byte, and every value line outside the four shipped-text settings is unchanged. Green on the merged framework; a mutation control shows the instrument sees an overwrite. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…st.java Gate-1 top-up IN-03 of plan 17-72: the file was CRLF at dd29137 and 9c596cf rewrote it with LF. Whitespace only: `git diff -w --ignore-cr-at-eol` against the previous commit is empty; `file` reports CRLF line terminators again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ues held before the command and logs why (#72 review) Gate-1 top-up IN-01/IN-02 of plan 17-72: the rollback after a write failure that is not a refusal had no test; it now asserts the six setters are called with the values held before the command. Both failure branches must log one WARNING with the reason, so the reply's "fix the file the server log names" holds on every path (RED on this head: nothing is logged). CRLF kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e rollback restores the values held before the command (#72) Gate-1 top-up IN-01/IN-02 of plan 17-72: - both catch blocks (refusal and any other write failure) log one WARNING with the write's own message - file and reason, never a value - through the new essentials.log.location_not_saved, so the reply's "fix the file the server log names" holds on every path; - the javadoc, the code comment, CHANGELOG (EN/ZH), FEATURES and the refused- write UAT row now say the rollback restores the values held before the command, not "what the file holds" (the two differ after a hand edit). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… release v1.0.0 follows the language (#73) Gate-1 top-up F-02 (found by the UltiRemoteBag review, routed to plan 17-72): release v1.0.0 wrote 随机传送冷却时间(秒) above features.wild.cooldown; it equals no shipped catalogue text and is not registered, so under en it stays Chinese. The upgrade test (en and zh) is RED on that text; a one-character variant is the operator's and must stay (control). The language-guard exemption for the literal the fix registers is added here (stale, and so RED, until the fix lands). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t as one the module shipped (#73) @ConfigEntry(previousComments) on features.wild.cooldown holds the text release v1.0.0 wrote there, so the framework counts an upgraded file's copy as its own and rewrites it in the server's language; any other text above the key stays the operator's (maintainer decision 2026-10-04, register old shipped comment texts as framework-owned). The #67 entry in CHANGELOG (EN/ZH) and the ultiessentials.config.comments FEATURES row no longer say a hand-written comment is replaced: under the 2026-10-04 decision it stays byte for byte, and only the module's own comment (catalogue text, or a registered earlier text) is switched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ck in full (class-L sweep) Gate-1 top-up class-L sweep of plan 17-72: the copy-rename-select advice in the changelog and the README now carries the framework's whole rule (alpha de8d9c0c, Localized#officialLanguageOf and isSafeLanguageCode): the new name starts with the language code and a hyphen and holds only ASCII letters, digits, `_` and `-`; one `language` setting serves the framework and every module; messages the copy lacks come from the official file its name starts with, and a module without that file uses its official one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
Author
|
Local Codex review, run 2 of 2 (plan 17-72 write-gate follow-up)
|
This was referenced Oct 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue closure
Closes #66
Closes #67
Closes #68
Closes #69
Closes #72
Closes #73
/ul reload UltiEssentialsreports a partial reload naming the service that did not restart, instead of an unconditional success (adopts onReload() cannot report a partial reload, so /ul reload replies success when part of a module did not reload UltiTools-Reborn#529). 部分重载时告知执行者哪个服务未重启。language: enwrites English comments (adopts Config entry comments cannot be translated: @ConfigEntry(comment) is written verbatim with no language-catalogue path UltiTools-Reborn#542). 配置注释改为取自语言文件。isPresentInFileinstead of reading the configuration accessor UltiTools-API 6.3.0 removes, so the module loads on 6.3.0. 改用isPresentInFile,模块可在 6.3.0 上加载。/setspawnand/setlobbywrite exactly their six location settings, and a refused write says so and is rolled back./setspawn、/setlobby只写六个坐标项;写入被拒绝时告知未保存并撤回。features.wild.cooldownis registered as one the module shipped, so an upgraded server's copy follows the language. v1.0.0 写在features.wild.cooldown上方的注释已登记为模块自带,升级后随语言切换。The language-guard widening is tracked by UltiKits/UltiRemoteBag#44 (decision recorded there); this pull request applies it here and closes nothing for it.
Decisions applied
Checklist rows amended
Write-gate follow-up (plan 17-72, gate-1 top-up):
FEATURES.mdultiessentials.config.comments(amended: a hand-written comment stays; #73),ultiessentials.spawn.setspawn-writes-location-onlyandultiessentials.spawn.setlobby-writes-location-only(rollback wording);UAT-CHECKLIST.mdultiessentials.spawn.setspawn-writes-location-only.neg-refused-write(rollback wording).Write-gate follow-up (plan 17-72):
FEATURES.mdultiessentials.spawn.set,ultiessentials.lobby.set(amended),ultiessentials.spawn.setspawn-writes-location-only,ultiessentials.spawn.setlobby-writes-location-only(new);UAT-CHECKLIST.mdultiessentials.spawn.setspawn-writes-location-only,ultiessentials.spawn.setspawn-writes-location-only.neg-refused-write,ultiessentials.spawn.setlobby-writes-location-only(new),ultiessentials.spawn.set,ultiessentials.lobby.set(stale line citations),ultiessentials.i18n.languageand the language convention (precondition note corrected).FEATURES.md:ultiessentials.config.comments(new),ultiessentials.lifecycle.reload-partial(new),ultiessentials.lifecycle.removed-key-warning(amended),ultiessentials.storedkey.repair(amended).UAT-CHECKLIST.md(all new, run by the real-server session):ultiessentials.config.comments-fresh,ultiessentials.config.comments-upgrade,ultiessentials.lifecycle.reload-partial,ultiessentials.lifecycle.removed-key-warning.reload-restored,ultiessentials.storedkey.repair.json-store.Red-when-reverted evidence
UltiEssentials-guard-RED.log/UltiEssentials-guard-GREEN.logUltiEssentials-66-RED.log/UltiEssentials-66-GREEN.logUltiEssentials-67-RED.log/UltiEssentials-67-GREEN.log; guard 1 key sitesUltiEssentials-67-guard1-RED.log/UltiEssentials-67-guard1-GREEN.logUltiEssentials-68-RED.log(compile error — the reverted code calls the accessor 6.3.0 removed; behaviour held byUltiEssentialsRemovedConfigKeyTest) /UltiEssentials-68-GREEN.logUltiEssentials-69-RED.log/UltiEssentials-69-GREEN.log, plus mutation REDsUltiEssentials-69-MUTATION-no-delete-by-id-RED.log,UltiEssentials-69-MUTATION-no-in-transaction-check-RED.log,UltiEssentials-69-MUTATION-count-unkeyed-copy-RED.logBehaviour changes
Threat model
This pull request defends against: server crashes and restarts (the #69 repair runs in one transaction and rolls back whole; a key that did not reach the store fails the transaction), the operator's normal commands (
/ul reload UltiEssentials, editing the five config files, switchinglanguage:), and an upgrade from the currentmasterbuild with existing config and JSON data. It does not defend against hand-edited framework-internal files, a Java security manager, or a third party changing the data folder while the server runs.Known limitation
This module's
plugin.ymlapi-versionand any README compatibility line still advertise the pre-6.3.0 API level, and the framework's 6.3.0 lifecycle hooks (onReload(ReloadReport),onUnregister()) run only under a 6.3.0 framework. Theapi-version: 630pin lands together with the UltiTools 6.3.0 release package, by an earlier maintainer decision, before any module release. No module version is bumped and nothing is released from this pull request. Tracking instance: UltiKits/UltiRecipe#19.Gates
This repository is not onboarded to Codacy — gate 2 for module repositories is the Codex review only, per the phase's carried-forward decision.
gpt-6.1-sol, efforthigh) on headdd29137: 0 findings — "未发现本次变更引入的可确认缺陷。" (no confirmable defect introduced by this change). No fix commit followed, so no confirmation run. Summary comment posted on this pull request.maven-cisuccessondd29137— https://github.com/UltiKits/UltiEssentials/actions/runs/37179872226 (pull_request) and https://github.com/UltiKits/UltiEssentials/actions/runs/37152783021 (push)🤖 Generated with Claude Code
Write-gate follow-up (plan 17-72)
Built against the merged UltiTools-API
6.3.0-SNAPSHOT(frameworkalphade8d9c0c, the write gate of UltiTools-Reborn#611).0713318). The 17-70 cross-module sweep found 20 module tests failing against the merged framework, no production defect: the comment-upgrade test's mocked module could not read its own shipped catalogues (the framework now replaces only comments it identifies as its own), and the reload tests' mockedUltiToolshad no logger for the framework's not-written warning. Both mocks now behave like the running server; 1337 tests, 0 failures.saveOperatorChangewith their six location paths instead of the whole-entitysave(), which on the merged framework left a hand-edited location setting unwritten while chat said the location was set, and answered a refused write as a success. A refused write (ConfigWriteRefusedException) is answeredSpawn point not saved: <reason>. ...and any failed write puts the six settings in memory back to what the file holds (maintainer decision of 2026-10-05). Commits9c596cf(test, red by behaviour),fd76c3a(fix).46b111badds a real-file guard that a language switch rewrites only the shipped-text settings ofessentials.yml(a typo value and a hand-written comment stay byte for byte; a mutation control goes red);13e1929replaces the advice to edit official language files with copy-rename-select.UltiEssentials-72-RED.log/UltiEssentials-72-GREEN.log;UltiEssentials-S5-CONTROL-overwrite-RED.log.Gate-1 top-up follow-ups (plan 17-72)
218f217(test) /8153747(fix) — a write failure that is not a refusal is rolled back too (now tested), both failure branches log one WARNING with the write's own message, and every text says the rollback restores the values held before the command. ProofUltiEssentials-72-review-RED.log/-GREEN.log.197ed3frestores the CRLF line endings ofSpawnLobbyCommandsTest.java(whitespace only).a68be33(test + language-guard exemption) /f891663(fix:previousComments; the Config comments are Chinese-only under language: en; adopt translatable comments (UltiTools-Reborn#542) #67 changelog entry and the comments FEATURES row no longer say a hand-written comment is replaced). ProofUltiEssentials-73-RED.log/-GREEN.log.c0088f8gives the custom language file's naming rule and fallback in full.tabbar.yml/motd.yml), Single-write in-place fallback failure can leave the new text on disk while the IOException contract says the file kept its bytes UltiTools-Reborn#622 (IN-05, framework).c0088f8: no finding.