All notable changes to xpp-lens (called xpp-graft up to 1.2.0). Versions follow <Version> in
src/XppLens/XppLens.csproj; release tags are vX.Y.Z.
- An installation by
install.ps1(the ZIP package, and every update from a version before 1.3.0) leftxpplensoff the PATH ("The term 'xpplens' is not recognized"), with no Start menu entries and no entry in Windows' installed apps.install.ps1now does what the setup program does: the program folder on the user's PATH (kept asREG_EXPAND_SZ, so%VAR%entries keep working; new terminals see it at once), the same Start menu entries with the icon, and an installed-apps entry that runsuninstall.ps1. When the setup program installed xpp-lens for the user, its entries stay; the setup program replaces those ofinstall.ps1when it takes a folder over.uninstall.ps1removes the PATH entry, and the Start menu and installed-apps entriesinstall.ps1made.
xpplens <command> --helpstopped with "unknown option '--help'"; it now shows the help of that command (also-handxpplens help <command>).xpplens helpdid not listconfig --standard-models,config --remove-standard-publisherandregister/unregister --exe. A test now checks that every command and option the program accepts is in the help.
- Claude Code skills in the package:
/xpp-ponytail(the smallest complete X++ change: configuration, standard, the model's own code, a narrow extension, only then new code) and/xpp-ponytail-review(a review of an X++ change with a concrete case and the smallest fix per finding), built on thexpp_*tools. Based on Ponytail (MIT), rewritten for X++; the notice is inskills/THIRD-PARTY-NOTICES.md. The source of the skills is now this repository (skills\). - The setup program and
install.ps1copy them to%USERPROFILE%\.claude\skills, in the language of the installation (English or Polish descriptions and review headings: the wizard's language, orinstall.ps1 -SkillsLanguage en|pl, by default the Windows language), kept by updates. A check box in the wizard andinstall.ps1 -NoSkillsskip them. - A skill changed locally is never overwritten or removed:
skills-installed.jsonin the installation folder keeps the SHA-256 of what was installed; a changed skill is kept with a warning (the wizard shows it,install.ps1prints it,xpplens statuslists it after a silent update). The uninstallers remove only unchanged skills they installed. A folder of that name not installed by xpp-lens is left alone unless it equals the package's copy. xpplens skills [install [--lang en|pl] | remove | status].
- An icon (a lens over X++ on a violet tile) for
xpplens.exe, the setup program, the Start menu and Windows' installed apps; drawn bypackaging/icon/make-icon.ps1. - Start menu entry
xpp-lens: a console that shows the command list and takes anyxpplenscommand, also right after installing, before Explorer picks up the new PATH.
xpplensfailed to start with "Failed to determine default extraction location" when TEMP pointed to a missing folder (a remote desktop session'sTemp\<n>after a cleanup), e.g. from the Start menu entries. The SQLite library now ships next toxpplens.exe(bin\e_sqlite3.dll) instead of being extracted to TEMP at every start; the setup program renames it like the running EXE when an open Claude session holds it.
- The
modelfilter ofxpp_find,xpp_refs,xpp_changedandproperty=listings took one name only: a list such asmodel="Contoso;Fabrikam*"matched nothing and the answer was empty. All of them now take names separated by;with*and?, likexpp_grepalready did. xpp_find "Table.Extension.Field"split the name at the first dot and found nothing; a field added in a named extension (CustTable.ContosoExtension.MyField) is now found.xpp_methodwith onlylines(no method) returned nothing; it now shows those lines of the object's file and the methods they belong to — the way to read a spot named by a build error or a grep hit.
xpp_extensionsis several times faster for objects with many extension classes (measured warm:SalesTable9.2 s → 0.5 s,VendTable5.1 s → 1.8 s): the Chain of Command details of each extension class were looked up by a column without an index, which read the whole reference table once per extension class.xpp_joinreads the table relations from a small index of its own instead of the whole member table (the first call of a server and every CLI call: 1.3 s → 0.65 s); the index is built once, in a few seconds.
xpp_refs name="Table.Field"answered "no references": the whole text was taken as an object name, whilexpp_findtakes that form.xpp_refsnow readsTable.Field/Class::methodas the object and its member (unless an object has that whole name, as extensions namedBase.Modeldo) and says how it read the name, so the answer equalsname=Table member=Field: code reads (also afterTable::find().Field) and form controls.xpp_objectdid not show whether a table is per company: the XML leaves outSaveDataPerCompanyandTableTypewhen they have their default values. Tables now always list them, marked(default)when not in the file, andxpp_find property=SaveDataPerCompanyreportsYesfor those tables instead of-.
- Setup program
xpp-lens-setup-X.Y.Z.exe(Inno Setup): a wizard in Polish or English for the current user, no administrator rights. It finds PackagesLocalDirectory (xpplens detect, or choose the folder), asks for the label languages and whether to index the Microsoft standard, registers xpp-lens in Claude Desktop and Claude Code and offers to build the index. It putsxpplenson the user's PATH, adds Start menu entries (status, update the index, check for updates, uninstall) and an entry in Windows' installed apps; uninstalling asks whether to remove the index. An installation made byinstall.ps1(C:\Tools\xpp-lens) is taken over in place with its settings and index. xpplens update --installruns the setup program of the release silently into the current installation (SHA-256 checked,--restart-claudepassed on); releases without one still go through the ZIP andinstall.ps1.restart-claude.ps1: the Claude Desktop restart of the installers, shared byinstall.ps1and the setup program.- Test releases: a version can first be published as
X.Y.Z-beta.N, marked as a pre-release on GitHub.xpplens updatenever offers it;xpplens update --install --preinstalls the newest release including test releases, so a new version can be tried on one machine before everyone gets it. The regularX.Y.Zlater replaces a test release through the usualxpplens update --install. xpplens version,statusand the MCP server report the full version of a test release (1.3.0-beta.1); versions compare with test releases before their final version (1.3.0-beta.2<1.3.0).
- Work happens on the branch
dev;mainalways holds the last regular release.release.ps1 -Version X.Y.Z -Prepublishes a test release fromdev;release.ps1 -Version X.Y.Zopens the pull requestdev→main, waits for its CI, merges it (merge commit), tags the merge and publishes the release, then movesdevtomain. CI also runs for pushes todev. The release notes of a test release come from the section of its final version.
- The file properties of
xpplens.exeshowed a garbled title and copyright (xpp-lens — …,Copyright ©): the project file held those characters double-encoded.
release.ps1 -Version X.Y.Zreleases a version end to end and stops at the first failure: CHANGELOG section and free tag,<Version>, a scan for private names (customers, partners, people) from a local list that is never part of the repository, Release build and tests, commit and push, CI, tag, Release workflow, and a check of the published package (SHA-256 digest, layout).-DryRunruns the checks only. JSON from the GitHub CLI is filtered in PowerShell: Windows PowerShell 5.1 drops the inner quotes of a--jqfilter passed to a native program.
- Restarting Claude Desktop at the end of an update could close Claude and fail to start it again ("Cannot process argument transformation on parameter 'Package'"): after Claude had updated itself, the running process was in an older version folder than the installed package, so the package was not found. The application is now found by its package family (from the Start menu, then the package list) and the way to start it is worked out before Claude is stopped — if it cannot be, Claude is left running with a note to restart it yourself. Per-user installations start through their launcher, which always runs the current version.
- The restart question did not show the new version (
$version?is a different variable in PowerShell). - Release packages since 1.2.5 nested their files as
xpp-lens/xpp-lens/dist/xpp-lens/…because the release workflow passes a relative output folder;pack.ps1now works with the full path. Updating was not affected (the updater findsinstall.ps1at any depth), unpacking by hand was.
- X++ kernel types are indexed as the compiled model
Kernel:QueryBuildDataSource,QueryBuildRange,FormDataObject,xFormRun,xRecord,Mapand the other system classes with complete method signatures (parameter types, defaults, static / final), base classes, system enums, EDTs and tables — about 3 400 objects read from the resources ofbin\Microsoft.Dynamics.AX.Metadata.dll(the metadata the X++ compiler uses), without loading the assembly. Indexed in the background with the standard tier; re-read when the assembly changes. xpp_objectdata sources show their behaviour properties (InsertIfEmpty,InsertAtEnd,AllowCreate/AllowEdit/AllowDelete,LinkType,OnlyFetchActive,DelayActive,Index,CounterField…) and the number of fields;parent=<data source>shows one data source with all its fields.
xpp_find "Table.member"did not find fields and methods added in extensions of the table (found only with*.memberor the extension name). The object part now also matches extensions that name the object as their target; the base object's own members come first.
- Faster answers, mostly after a pause, when the OS has dropped the index from its file cache and the disk is
slow. Measured on a real installation before / after: a substring search (
xpp_find *Text*) right after the server starts 41 s → 1 s, later 1.3 s → 0.2 s; earlier usage logs showed single calls of 100–180 s.- Queries run in parallel, each on its own connection: one slow query no longer holds up the others (they used to finish together, all waiting for the first one).
- The periodic rescan of custom model files runs on a timer in the MCP server instead of inside a query; files changed on disk (seen by the watchers) are still indexed before the next query.
- Wildcard and substring name searches (
xpp_find *Invoice*, "Similar" suggestions) are matched against the distinct names of objects, methods and members kept in memory (about 730 000 names, read in under a second at start and after index changes); the index then answers only exact names instead of scanning whole tables. - Results of
xpp_findare ordered completely (ties broken by name), so the same question gives the same answer.
xpp_find property=…lists root properties of every object matching the name, type and model filters, from the index:query="*" type=entity model=Contoso property="PublicEntityName;PublicCollectionName";property="PublicEntityName=*Order*"filters by value (xpplens find "*" --type entity --property …).- The usage log records, per call, the time spent waiting for the index refresh and the query time;
xpplens statsshows them (p95 wait,p95 query, and per slowest call).statusshows the names held in memory.
- Updating no longer needs Claude to be closed. The installer does not stop running
xpplens.exeprocesses any more: a file in use is renamed to*.old, so open sessions keep working on the old version and new sessions start the new one; the next start of xpplens deletes the leftovers. At the end the installer offers to restart Claude Desktop;xpplens update --install --restart-claude(install.ps1 -RestartClaude) does it without asking. Claude Code processes in terminals are never stopped. xpp_grepaccepts several names separated by;inobjectNameandmodel, like the other tools. The answer for standard code without a scope, or with too large a scope, points toxpp_refs/xpp_callersfor names.- The release package stores its paths with
/, as the ZIP format requires. - GitHub Actions:
actions/checkoutv7 andactions/setup-dotnetv6 (Node 24).
- Command line options:
-installwas silently ignored (only--installwas known), soxpplens update -installonly checked for the update. Options now work with one or two dashes and as--name=value, may come before the arguments, and an unknown option or a missing value stops with an error that lists the options of the command.
xpp_build_errorsreads the result of a Visual Studio project build (BuildProjectResult.xml) next to that of the model build. The header shows both build times and the time of the model DLL. Diagnostics of the newer build come first and replace those of the model build for the same objects.xpp_build_errorslists the objects changed after the newest build, which are not compiled yet. After a project build it also lists the objects changed between the model build and the project build, which are compiled only if they are in the project. This answers "did my build take everything?" in one call.xpp_changed since=build(xpplens changed --since build): per package, the objects changed after its last build.
xpplens status/xpp_statusstart with the xpp-lens version (and an available update) and show the index tiers and counts as tables. The console gets box-drawing frames; output redirected to a file or a pipe, or--ascii, gets+-|frames;xpp_statusreturns Markdown tables. Numbers are grouped by thousands; long model lists come last.
- After a project build,
xpp_build_errorsshowed the older model build as the latest result. Objects changed after the build were marked only when they had a diagnostic; with a clean build they were not mentioned at all.
xpp_build_errorspointed at wrong lines: the compiler numbers lines through the object's whole code (the declaration without its closing brace, then every method in file order), not from the start of the method. Lines are now translated that way and accepted only when they fall into the method the compiler named. Metadata diagnostics (form designs, controls, form extensions) show the object's file and the element instead of a raw path. Methods of form data sources and controls show where the method starts; their exact line is not mapped yet.
xpplens updatechecks GitHub for a newer release;xpplens update --installdownloads it, verifies its SHA-256 digest and runs its installer, keeping settings and index.statusmentions an available update found by the last check. This is the only network access of xpp-lens and happens only when the command is run.- Automated releases: pushing a tag
vX.Y.Zruns the tests, checks the tag against the project version, builds the package and publishes the release with its notes taken from this file (release-notes.ps1). - Continuous integration: GitHub Actions builds the solution and runs the tests on Windows for every push and pull request.
SECURITY.mdand issue forms for bug reports and feature requests, which ask not to share customer code or data.
xpplens status/xpp_statusno longer count all references and labels (tens of seconds on a cold disk);--counts/counts=trueshows them. A standard tier that is listed but not indexed is explained, with the command that indexes it.xpplens build --std-onlyswitchesindexStandardon when an installation was made without the standard tier.- Running
install.ps1over an existing installation keeps its label languages andPackagesLocalDirectoryunless-Languages/-PackagesDirare given.
- Taking over an xpp-graft installation could leave the index behind and build a new one from scratch. The index
files are now moved one by one by
xpplens migrateand checked; when they cannot be moved (in use, or the new folder already holds an index) the new configuration keeps using the old index where it is.
- Renamed from xpp-graft to xpp-lens, to avoid confusion with the unrelated
Graft project. Program
xpplens.exe, MCP serverxpp-lens, configurationxpplens.json, default foldersC:\Tools\xpp-lensand%LOCALAPPDATA%\xpp-lens, environment variablesXPPLENS_CONFIG/XPPLENS_VERBOSE/XPPLENS_TIMING/XPPLENS_USAGE. The MCP tool names (xpp_find, …) are unchanged. install.ps1takes over an existing xpp-graft installation: settings (incl. label languages), the index and the usage log move over without a rebuild, and the old MCP entries are removed. The old folder is left for you to delete.- Release packages are named after the version (
xpp-lens-1.2.1.zip).
- Usage statistics: every MCP call is logged locally (
%LOCALAPPDATA%\xpp-graft\usage);xppgraft statsshows tokens, time and empty answers per tool, the largest and slowest calls and the latest empty answers.xppgraft config --usage-log falseswitches it off. - Several lookups in one call:
xpp_findandxpp_objecttake names separated by;;xpp_methodtakesmethod="a;b"orobjectName="Obj.method;Obj2::method". - Method fragments:
xpp_methodwithmatch(regex) and/orlinesreturns only those lines with context, numbered with file lines; the signature and variable declarations are always included, skipped parts are marked. Long methods returned in full carry a hint about it. - Callers inside Microsoft code: the standard tier now stores call references (calls,
new, intrinsics) —xpp_callerslists them after custom code;standard=falseleaves them out. About +300 MB of index;xppgraft config --standard-code falsekeeps the old dictionary-only standard tier. - Chained calls:
Table::find(x).method(),a.b().c(),new T().m()andvar x = A::b()are typed from method return types — inxpp_callers,xpp_refs(withmember) andxpp_callees. - New tools:
xpp_scaffold— CoC wrapper (exact signature, default values removed, warnings for private / final / non-wrappable methods), table / form / data source / control / field event handlers with parameters learned from existing handlers, delegate subscribers, pre/post handlers; class names follow the pattern found in your models.xpp_build_errors— errors and warnings fromBuildModelResult.xmlmapped to XML file lines, flags files changed after the build.xpp_security— menu item / form → privileges (granted access) → duties → roles, and privilege / duty / role views.xpp_join— shortest relation path between two tables as a ready X++ join.xpp_entity— data entity overview (public names, staging, data source tree with joins, field mapping, keys) or the entities that use a table.xpp_changed— objects changed on disk since a time, per model.
- Matching CLI commands:
scaffold,build-errors,security,join,entity,changed,stats;method --match/--lines/--context,callers --standard false. - xUnit test project (
tests/XppGraft.Tests) with neutral sample AOT XML; runs in Visual Studio Test Explorer or withdotnet test. - Visual Studio launch profiles for the new commands.
- Analyzer upgrades re-parse custom models once at start-up and re-index the standard tier once in the background, package by package (an interrupted run resumes) — no manual rebuild after updating.
- Parser: data entity keys, query / entity data source joins (
on Field=DataSource.Field) and data source tree, privilege entry point grants (grant=Read,Update,…). - Tool descriptions no longer contain installation-specific examples.
- The release package also ships
CHANGELOG.mdandLICENSE;build.ps1 -Testruns the tests before building. - The WAL file is trimmed after checkpoints (
journal_size_limit), so a large rebuild does not leave it at peak size.
First published release, under the MIT License.
- Three index tiers: custom models (full code references), Microsoft standard (dictionary), compiled-only packages
(from
.xref,bin\*.mdand resource DLLs);xpp_statuslists packages on disk that are not indexed. - Tools:
xpp_find,xpp_object,xpp_method,xpp_callers,xpp_callees,xpp_refs,xpp_extensions,xpp_grep,xpp_label,xpp_status. - Control and menu trees with
parent/depth/filter, full control paths, ReferenceField / ReplacementFieldGroup, menu extension parent and position. - Literal underscore in name searches; dotted extension names (e.g.
*Staging.Contoso) found as objects. - Index follows the files on disk (watcher + periodic scan); read-only index detection with live parsing.
- Portable installer / uninstaller, configurable languages, models and
PackagesLocalDirectory(given or detected).