Repository navigation
v1.0.1: real-world fixes (Windows doctor, init TOML, Unity entry points, --budget) - #22
Merged
Merged
Conversation
find_program joined the bare catalog name (cargo, tsc, ...) and checked is_file, so on Windows cargo.exe / tsc.cmd were never found and plumb doctor reported every provider as missing. Bare names are now tried with the executable extensions from PATHEXT (Windows only; kept to .com/.exe/.bat/.cmd, which std::process::Command can start), directory order first, then extension order. Unix keeps the exact-name + execute-bit lookup. detect_with_exts injects the list so the Windows rules are tested on Linux; one cfg(windows) test covers PATHEXT.
The snippet interpolated root.display() into a TOML basic string. On Windows canonicalize() returns \\?\C:\..., and \\, \U, \o etc. are escape sequences in basic strings, so the printed block did not parse. The verbatim prefix is now stripped (\\?\C:\ -> C:\, \\?\UNC\ -> \\) and the path is encoded by toml_string: a literal string when possible, otherwise a basic string with quotes, backslashes and control characters escaped. Paths in the init report use the same plain form. Tests parse the snippet with the toml crate.
MonoBehaviour messages (Awake, Start, Update, OnTriggerEnter2D, ...) are called by the engine by name, so every one was reported as dead code (0.71 medium) and so was the component class. - non-static message methods of classes deriving from MonoBehaviour, ScriptableObject, NetworkBehaviour, StateMachineBehaviour, UIBehaviour (Editor/EditorWindow in Unity files), or deriving from anything under Assets/, are entry points - [RuntimeInitializeOnLoadMethod], [InitializeOnLoadMethod], [InitializeOnLoad], [MenuItem], [ContextMenu], ... are entry points in Unity files (Assets/, Packages/, or a UnityEngine/UnityEditor using or qualified name in code; comments and strings do not count) - public methods of those classes keep a finding with a 0.25 penalty and an fp_risk about UnityEvent bindings in scenes/prefabs (new SymbolFact.framework_risk, stored in a new column; schema 3, extractor 8) Golden fixture tests/fixtures/unity; docs/PACKS.md documents the rules.
plumb map --budget 1500 failed with 'unexpected argument'; --tokens was the only spelling although the help, output and docs all call it a budget. --budget is now a visible clap alias (shown in --help), passing both is a usage error (exit 2), and the README quickstart shows the flag.
- --dry-run now says which lines it would add, not just 'would write .gitignore' - writes '.plumbgraph/*' + '!.plumbgraph/allow.toml' instead of '.plumbgraph/': excluding the directory made the documented, committed allow-list impossible to commit - recognises existing spellings (/.plumbgraph, **/.plumbgraph/*, BOM) instead of appending a duplicate - works for a project in a subdirectory of a repo (and worktrees, where .git is a file) - never rewrites a .gitignore it cannot read as UTF-8: it was read as empty and overwritten, losing e.g. a UTF-16 file from PowerShell 5 - keeps CRLF line endings - new --no-gitignore option
Documents cargo install --path crates/cli --locked on Windows, adding %USERPROFILE%\.cargo\bin to the user PATH (rustup does it; Chocolatey's Rust does not), and the one machine/toolchain this was tried on (Windows 11, Rust 1.91.1 x86_64-pc-windows-gnu, no MSVC, no 1.85, no CI), including the 10 tests that fail there on main as well.
Found while checking the change on a real Unity project (Unity's open-project-1) and re-reading the diff: - Assets/ indirect rule: a class whose first base is an interface (IDisposable, IComparable<T>) cannot be a MonoBehaviour; its Update is no longer treated as an engine message - AddItemsToMenu (IHasCustomMenu, called by the editor on EditorWindows) added to the message list - UnityEvent fp_risk now also names Animation Events, which is what many still-reported public handlers on the real project are - init: any existing .gitignore rule mentioning .plumbgraph (including a negation such as !.plumbgraph/) means the user manages it; the appended lines would otherwise win as the last match
ISerializationCallbackReceiver (OnBeforeSerialize/OnAfterDeserialize), IHasCustomMenu (AddItemsToMenu) and the UI EventSystem handler interfaces (IPointerClickHandler.OnPointerClick, IDragHandler.OnDrag, ...) are called by Unity on any implementing type, not only on components. Found on open-project-1: SceneSelector.Storage, a plain [Serializable] class, reported its serialization callbacks as dead.
Raises the test-integrity baseline by the 35 tests added on this branch (no test removed or weakened). README: the Windows section now says which binary ran which command.
Merged
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.
Fixes for problems found while using v1.0.0 for real on Windows 11 and on a Unity-style C# codebase. One commit per fix plus one review-follow-up commit for Unity/
.gitignoreand one for Unity callback interfaces, then CHANGELOG/baseline. Nothing is merged, tagged, released or published; the crate version stays 1.0.0.Per bug
1. Windows:
plumb doctorreports every provider as missingcrates/core/tests/providers_exe_ext.rs:windows_extensions_find_exe_and_cmd_programs,windows_extension_order_follows_the_list(Linux, injected extension list) andreal_windows_detection_finds_cargo_exe(cfg(windows), realPATHEXT).Before the fix, on Linux and natively on Windows:
Detected { id: "cargo-check", ..., available: false, path: None, ... }.find_programjoined the bare catalog name (cargo,tsc) and checkedis_file(). On Windows the files arecargo.exe/tsc.cmd, so nothing was ever found.executable_extensions()readsPATHEXTon Windows (and returns nothing elsewhere). Only.com/.exe/.bat/.cmdare kept, because those are whatstd::process::Commandcan start;.js/.vbsneed a host. Lookup order is directory first, then extension, as in cmd.exe. A bare extensionless file is not accepted when extensions apply (npm puts an sh shim next totsc.cmd). Unix keeps the exact-name + execute-bit lookup (no_extension_list_keeps_exact_name_lookup,unix_default_detection_is_exact_name_only,platform_extension_list_matches_the_platform).detect_with_extsis the test seam. The same lookup is used byenrichand by rule-engine detection.cargo test -p plumbgraph-core --test providers_exe_extpasses 7/7 on Linux and natively on Windows. A real Windows run of the built binary,plumb doctor C:\...\Wraith, now showscargo-check installed C:\ProgramData\chocolatey\bin\cargo.exeandruff installed ...\Scripts\ruff.exe; v1.0.0 showed both asmissing.2.
plumb initprints an invalid TOML Codex snippet on Windowscrates/core/tests/init_snippet.rs:codex_snippet_is_valid_toml_for_a_windows_verbatim_path,codex_snippet_maps_verbatim_unc_to_a_plain_unc_path,codex_snippet_round_trips_awkward_paths. They parse the snippet with thetomlcrate.Before:
TOML parse error at line 4, column 31 ... missing escaped value, expected b, e, f, n, r, \, ", x, u, Uon"\\?\C:\Users\...".root.display()was interpolated into a TOML basic string. On Windowscanonicalize()returns\\?\C:\..., and\U,\o, ... are escape sequences there.strip_verbatim(\\?\C:\→C:\,\\?\UNC\srv\share→\\srv\share;\\?\Volume{..}and device paths are left alone) plustoml_string: a literal string'C:\...'when possible, otherwise a basic string with",\and control characters escaped (round trip tested with',", tab, newline, DEL, BEL, non-ASCII). Paths in the init report use the plain form too.cargo test -p plumbgraph-core --test init_snippetpasses 6/6 on Linux and on Windows. On Windowsinit_prints_a_parseable_codex_snippet_without_a_verbatim_prefixgoes through a realcanonicalize(). I also piped the snippet printed by the real Windows binary through Python'stomllib: the branch printsparsed OK, args = ['mcp', '--root', 'C:\\Users\\ofekv\\Desktop\\repos\\Wraith'], while v1.0.0 givesTOML ERROR: Unescaped '\' in a string (at line 3, column 32).3. Dead-code false positives for Unity C#
Failing tests:
crates/core/tests/unity_entry_points.rswith the golden fixturecrates/core/tests/fixtures/unity(Assets/,Server/). Before the fix it failed withfalse positive Player.Awake,false positive BuildTools.Bake,false positive GameSettings.OnEnable, andPlayer.Jumpwas notlow. Pre-fix default output: 47 findings, every lifecycle method atmedium 0.71; after: 13 on the same fixture before the review follow-ups added more fixture files.Honest note: my first version of these tests used
Game.Player.Updatequalified names. C# qnames do not include the namespace, so three negative tests passed vacuously. I caught this in the first run.symbols()now asserts a positive control (Player.NeverCalledis reported), so an absence check cannot pass on a naming mismatch.Root cause: Unity calls these methods by name, so the tier-0 graph has no caller, and only
override/Main/constructors were C# entry points.Fix:
UNITY_MESSAGES:Awake,Start,Update,LateUpdate,FixedUpdate,OnEnable/Disable/Destroy,OnCollision*/OnTrigger*incl.2D,OnGUI,OnValidate,Reset, rendering/mouse/animation/editor callbacks) are entry points for non-static methods of classes that:MonoBehaviour,ScriptableObject,NetworkBehaviour,StateMachineBehaviourorUIBehaviour; orEditor/EditorWindowin a Unity file; orAssets/with a first base type that is a class, not anI-prefixed interface (indirectEnemy : Character : MonoBehaviour).[RuntimeInitializeOnLoadMethod],[InitializeOnLoadMethod],[InitializeOnLoad],[MenuItem],[ContextMenu],[DidReloadScripts],[PostProcessBuild],[PostProcessScene],[OnOpenAsset], with or without theAttributesuffix.ISerializationCallbackReceiver,IHasCustomMenu, UI EventSystem handlers. These apply to any implementing type.Assets/orPackages/, or code (an AST check; comments and strings don't count) with aUnityEngine/UnityEditorusingor qualified name.SymbolFact.framework_risk, stored in a new column; schema 3 / extractor 8, so old indexes rebuild once (existing mechanism).[SerializeField]fields: C# fields are not extracted, so they are never reported. There is a test for it, so it will be noticed if field extraction is added.docs/PACKS.md.Proof:
cargo test -p plumbgraph-core --test unity_entry_pointspasses 9/9 (Linux and Windows). Goldensunity_default.txt/unity_low.txtwere blessed and reviewed by hand at each step; later steps changed only line numbers or added lines from new fixture files. Real code, v1.0.0 vs this branch,dead-code --include-low --max-findings 0, tier-0, no SCIP:52fa3ae(non-Unity C#, control)608eac9,UOP1_Project/Assets/ScriptsonlyOn open-project-1, 203 symbols are no longer reported at any level. The top names are
Start×23,OnTriggerEnter×7,OnGUI×6,OnDestroy×5 andOnValidate×4; the rest became reachable through those. The 2 lost HIGH findings were checked by hand:LocalizeComponent_TMProFont(a[MenuItem]method) andReplaceContextMenu([InitializeOnLoadMethod]+[MenuItem]). For the remaining mediums I grepped 9 names: 7 (e.g.MenuController.OpenMenu/UnpauseMenu, private) occur only at their definition in any.csfile. These look like real leads. Index time on Newtonsoft (3 runs each): 1.13-1.21 s before, 1.14-1.28 s after, so no measurable change.4.
plumb map --budgetfails while--tokensworkscrates/cli/tests/cli.rs::map_budget_is_an_alias_of_tokens. Before:error: unexpected argument '--budget' found.--tokens, although the help text, the output (budget 1500) and the docs all call it a budget.visible_alias = "budget". Help shows[alias: --budget]. Both flags at once is a usage error (exit 2), and the output is byte-identical with either flag. The README quickstart shows--tokens 1500and mentions the alias. The MCPrepo_mapparameter staystokens(unchanged).cargo test -p plumbgraph-cli --test cli map_. On real Windows,plumb map C:\...\Wraith --budget 200gives# plumb map: 5 files, 18 of 264 symbols (~191 tokens, budget 200).5. Windows install docs
### Windowssection:cargo install --path crates/cli --locked, and the PATH setup for%USERPROFILE%\.cargo\bin(rustup adds it; Chocolatey's Rust does not, which is exactly what happened here). It also states exactly what was tested. "No Windows testing" became "one machine, manual runs, no CI".cargo install --path crates/cli --locked --root <tmp>built and installedplumb.exe/plumbgraph.exefor this branch, and v1.0.0 was installed the same way into~/.cargo/bin. The PowerShell PATH snippet in the README was run as written; it is idempotent, and the user PATH still has exactly one.cargo\binentry.6.
plumb initand.gitignoreinitalready appended.plumbgraph/, so the work here went into what was wrong with it.crates/core/tests/init_gitignore.rs. Before:index_is_ignored_but_the_allow_list_stays_committable:allow.toml is documented as committed: .plumbgraph/, checked with realgit check-ignore.dry_run_reports_the_gitignore_lines_and_writes_nothing.equivalent_existing_patterns_are_respected: a duplicate was appended after/.plumbgraph.a_project_in_a_subdirectory_of_a_repo_gets_its_own_entry.crlf_gitignore_keeps_its_line_endings.outside_git_nothing_is_written_and_the_report_says_why.a_gitignore_that_is_not_utf8_is_never_overwritten:user content must survive.read_to_string(..).unwrap_or_default()read a UTF-16.gitignore(what PowerShell 5echo > .gitignorewrites) as empty and overwrote it.!.plumbgraph/and.plumbgraph*must be respected. Before, the appended.plumbgraph/*won as the last match and silently undid the user's negation.would write .gitignore; it wrote the directory form.plumbgraph/, which git cannot re-include files from; it used an exact-line equality check; it required<root>/.git; and it read the file with a lossy fallback..plumbgraph/*+!.plumbgraph/allow.toml, with a comment line..plumbgraphmeans the user manages it, so the file is left alone..gitin ancestors (as a dir, or as a file for worktrees and submodules).--dry-runprintsnote: would add to <path>/.gitignore: .plumbgraph/* and !.plumbgraph/allow.toml.--no-gitignoreoption (init_with).init_writes_instructions_and_mcp_config_without_clobberingis unchanged and passes.cargo test -p plumbgraph-core --test init_gitignorepasses 10/10 (Linux and Windows), andcargo test -p plumbgraph-cli --test cli initpasses. Note: the CLI testinit_dry_run_shows_the_gitignore_lineswas written after the core fix, so it was not shown failing; the same behaviour was shown failing at core level.Self-critique: what review found and fixed before opening this
source.contains("UnityEditor"). A comment in my own fixture triggered it and turned[MenuItem]in a non-Unity file into an entry point. It is now an AST check overusingdirectives and qualified names; comments and strings are skipped. Covered byoutside_assets_unity_context_comes_from_code_not_comments_or_stringsand theServer/Worker.csfixture.Assets/indirect rule accepted interface-only classes (Pool : IDisposable); now excluded (Pooled.Updateis still reported).AddItemsToMenuwas missing.ISerializationCallbackReceivercallbacks on a plain[Serializable]class (found on open-project-1,SceneSelector.Storage) are now covered by the callback-interface map. Each of these was a failing test first..gitignorenegations (above); a UTF-8 BOM.deadcode.rsmodule doc pointed at a non-existentdocs/DEAD_CODE.mdand omitted the existing subclass penalty; both fixed.scripts/verify.sh(full, not--quick)Run on a clean Linux checkout of this branch (WSL2 Ubuntu, toolchain 1.85.0 from
rust-toolchain.toml,CI=true, cargo-deny and cargo-audit installed). Exit 0.target/debug/plumb weakening --base origin/main --fail-on high:0 finding(s) in 30 changed file(s) vs origin/main (98f17c191747), exit 0.Test-count baseline raised 159 → 194 for the 35 added tests. No test was removed, skipped or loosened. The only edits to pre-existing test files are additions (
cli.rs).CI
All checks green on head
ac8bf5a(run 38039417801, CodeQL run 38039417811):scripts/verify.shwith cargo-deny + cargo-audit, Rust 1.85.0test-integrity: OK (194 tests, baseline 194), dogfoodverify: PASS (0 new ...),verify: OK0 finding(s) in 30 changed file(s) vs origin/main (98f17c191747)No workflow files were changed; the existing actions are already pinned to SHAs.
Not tested
No Unity install. No Unity editor or player was run. The rules come from Unity's scripting reference, and were checked on source only: the fixture, plus the scripts folder of Unity's open-project-1. Scenes, prefabs, animation clips and
.assetfiles were not checked out or parsed.Windows: real, but one machine: Windows 11 Home 64-bit (build 26200) with Rust 1.91.1
x86_64-pc-windows-gnufrom Chocolatey. No rustup, so 1.85 was not used on Windows; the MSVC target was not tried; there is no Windows CI.On that machine
cargo test --workspace --no-fail-fastpasses every new test (the new test files were re-run natively on the final HEADac8bf5a: providers_exe_ext 7/7, init_snippet 6/6, init_gitignore 10/10, unity_entry_points 9/9, deadcode_golden 7/7, climap_/init2+2). 10 tests fail there, and the same 10 fail on unmodifiedmain(checked in a separate worktree):cli(closed_stdout_pipe_does_not_panic: Windows os error 232 instead of EPIPE; two LSP tests);diagnosticsand 1 inmcp(lsp_through_mcp_when_allowed). The LSP fake server needs a realpython3; on that machinepython3is the Microsoft Store stub.These are not addressed here. The Windows PATHEXT lookup on Linux CI is exercised through the injected extension list, plus one
cfg(windows)test that only runs on Windows.verify --runon Windows: the diagnostics runner (builtin_tools) still starts tools by bare name.cargo/ruffwork, becauseCommandappends.exe, but npm.cmdshims such astscwould not. Not changed here; it needs--allow-exec/--runtesting on Windows.MSVC Windows, macOS, Unity packages under
Packages/, theEditor/EditorWindowrules on a real editor-tools repo.Risks
Assets/indirect rule treats any class whose first base is a class as a possible component, soUpdate/Start/... on such a class are entry points even if it is not a MonoBehaviour. A base class named like an interface (IKController) would be misclassified the other way.Packages/in a non-Unity .NET tree would turn on the attribute rules and theEditor/EditorWindowbases for C# files there..plumbgraph/index.dbis rebuilt once on first use (existing mechanism,store::tests::version_mismatch_rebuilds).plumb initnow writes.gitignorein a subdirectory of a repository (previously only at a repo root), and writes.plumbgraph/*+!.plumbgraph/allow.tomlinstead of.plumbgraph/. Repositories that already ran v1.0.0initkeep their.plumbgraph/line; it is respected, not rewritten.PATHEXTentries other than.com/.exe/.bat/.cmdare ignored on purpose.