Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Skills are installed by SHA-256 (a changed one is kept): the same bytes on every checkout, CI included.
skills/** text eol=lf
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
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`.

## 1.4.0

### Added
- 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 the `xpp_*` tools. Based on
[Ponytail](https://github.com/DietrichGebert/ponytail) (MIT), rewritten for X++; the notice is in
`skills/THIRD-PARTY-NOTICES.md`. The source of the skills is now this repository (`skills\`).
- The setup program and `install.ps1` copy them to `%USERPROFILE%\.claude\skills`, in the language of the installation
(English or Polish descriptions and review headings: the wizard's language, or `install.ps1 -SkillsLanguage en|pl`,
by default the Windows language), kept by updates. A check box in the wizard and `install.ps1 -NoSkills` skip them.
- A skill changed locally is never overwritten or removed: `skills-installed.json` in the installation folder keeps
the SHA-256 of what was installed; a changed skill is kept with a warning (the wizard shows it, `install.ps1` prints
it, `xpplens status` lists 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]`.

## 1.3.3

### Added
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ Useful parameters:
.\install.ps1 -StandardModels HugeIsvModel # push a model down to the standard tier
.\install.ps1 -NoStandard # custom models only (fast, small index)
.\install.ps1 -First -NoBuild -NoRegister # non-interactive / CI
.\install.ps1 -NoSkills # without the Claude Code skills
.\install.ps1 -SkillsLanguage pl # skills in Polish (default: the Windows language)
```

Uninstall: `uninstall.ps1` (removes the MCP entries that point to this installation, asks about the index and files).
Uninstall: `uninstall.ps1` (removes the MCP entries that point to this installation and the skills it installed
unless you changed them, asks about the index and files).
New distribution package: `pack.ps1` (add `-FrameworkDependent` if you prefer 5 MB and a .NET 9 requirement).

## Configuration after installation
Expand Down Expand Up @@ -173,6 +176,24 @@ ReferenceGroup controls show `ref=<datasource>.<ReferenceField>`, `replGroup=<Re
and `relPath=<DataRelationPath>`. Menu extension elements show `(under <Parent>)`, `position=<PositionType>`
and `menuitem=<MenuItemName>`.

## Claude Code skills

The package carries two skills that build on the `xpp_*` tools, installed to `%USERPROFILE%\.claude\skills`:

- `/xpp-ponytail` — the smallest complete X++ change: configuration first, then Microsoft's standard, the model's
own code, a narrow extension (delegate, CoC, event handler), and only then new code. Levels `lite`, `full`, `ultra`.
- `/xpp-ponytail-review` — a review of an X++ change: correctness, transactions, security, SQL at real volume,
extensions, tests and code that should not exist, each finding with a concrete case and the smallest fix.

They are installed in the language of the installation (the setup wizard's, or `install.ps1 -SkillsLanguage en|pl`,
by default that of Windows); updates keep it. A skill you change in `.claude\skills` is never overwritten or removed:
the installer keeps it and says so, and `xpplens status` lists it. `xpplens skills` shows the state,
`xpplens skills install --lang pl` switches the language. The setup wizard has a check box for them
(`install.ps1 -NoSkills` skips them).

Based on [Ponytail](https://github.com/DietrichGebert/ponytail) by Dietrich Gebert (MIT license), rewritten for X++;
the notice is in [skills/THIRD-PARTY-NOTICES.md](skills/THIRD-PARTY-NOTICES.md).

## Usage statistics

Every MCP call is appended to `%LOCALAPPDATA%\xpp-lens\usage\usage-YYYYMM.jsonl` (tool, arguments, answer size,
Expand All @@ -194,6 +215,7 @@ Switch off with `xpplens config --usage-log false`.
xpplens find|object|method|callers|callees|refs|ext|scaffold|build-errors|security|join|entity|changed|grep|label …
xpplens build [--full-only] [--std-only] [--compiled-only] [--force]
xpplens status [--counts] [--ascii] | stats | update [--install] | detect | config | register | unregister | mcp | version
xpplens skills [install [--lang en|pl] | remove | status]
```

`xpplens help` lists every option. Environment variables: `XPPLENS_CONFIG` (another configuration),
Expand Down Expand Up @@ -302,6 +324,7 @@ go into a new version.
| `xpplens update`, taking over xpp-graft | `Updater.cs`, `Migration.cs` |
| release automation | `.github\workflows\release.yml`, `release-notes.ps1`, `pack.ps1` |
| CLI commands, configuration, registration in Claude | `Program.cs`, `Config.cs`, `Detect.cs` |
| Claude Code skills (text: `SKILL.md` English, `SKILL.pl.md` Polish — change both) | `skills\<name>\`; installing them: `Skills.cs` |

Work loop:

Expand Down
25 changes: 24 additions & 1 deletion README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ Przydatne parametry:
.\install.ps1 -StandardModels HugeIsvModel # zepchnięcie modelu do poziomu standardowego
.\install.ps1 -NoStandard # tylko modele własne (szybko, mały indeks)
.\install.ps1 -First -NoBuild -NoRegister # tryb nieinteraktywny / CI
.\install.ps1 -NoSkills # bez skilli Claude Code
.\install.ps1 -SkillsLanguage pl # skille po polsku (domyślnie: język Windows)
```

Deinstalacja: `uninstall.ps1` (usuwa wpisy MCP wskazujące na tę instalację, pyta o indeks i pliki).
Deinstalacja: `uninstall.ps1` (usuwa wpisy MCP wskazujące na tę instalację i zainstalowane przez nią skille, o ile ich
nie zmieniłeś, pyta o indeks i pliki).
Nowy pakiet dystrybucyjny: `pack.ps1` (dodaj `-FrameworkDependent`, jeśli wolisz 5 MB i wymóg .NET 9).

## Konfiguracja po instalacji
Expand Down Expand Up @@ -174,6 +177,24 @@ Kontrolki ReferenceGroup pokazują `ref=<datasource>.<ReferenceField>`, `replGro
i `relPath=<DataRelationPath>`. Elementy rozszerzeń menu pokazują `(under <Parent>)`, `position=<PositionType>`
i `menuitem=<MenuItemName>`.

## Skille Claude Code

Paczka zawiera dwa skille oparte na narzędziach `xpp_*`, instalowane do `%USERPROFILE%\.claude\skills`:

- `/xpp-ponytail` — najmniejsza kompletna zmiana w X++: najpierw konfiguracja, potem standard Microsoftu, kod
własnego modelu, wąskie rozszerzenie (delegat, CoC, event handler), a dopiero na końcu nowy kod. Poziomy `lite`,
`full`, `ultra`.
- `/xpp-ponytail-review` — przegląd zmiany w X++: poprawność, transakcje, bezpieczeństwo, SQL przy realnym wolumenie,
rozszerzenia, testy i kod, którego mogłoby nie być; każde znalezisko z konkretnym przypadkiem i najmniejszą poprawką.

Instalują się w języku instalacji (kreatora albo `install.ps1 -SkillsLanguage en|pl`, domyślnie w języku Windows);
aktualizacje go zachowują. Skilla zmienionego w `.claude\skills` instalator nigdy nie nadpisze ani nie usunie: zostawia
go, mówi o tym, a `xpplens status` go wymienia. `xpplens skills` pokazuje stan, `xpplens skills install --lang en`
zmienia język. W kreatorze jest na nie pole wyboru (`install.ps1 -NoSkills` je pomija).

Oparte na [Ponytail](https://github.com/DietrichGebert/ponytail) autorstwa Dietricha Geberta (licencja MIT), przepisane
pod X++; nota licencyjna jest w [skills/THIRD-PARTY-NOTICES.md](skills/THIRD-PARTY-NOTICES.md).

## Statystyki użycia

Każde wywołanie MCP jest dopisywane do `%LOCALAPPDATA%\xpp-lens\usage\usage-RRRRMM.jsonl` (narzędzie, argumenty,
Expand All @@ -195,6 +216,7 @@ Wyłączenie: `xpplens config --usage-log false`.
xpplens find|object|method|callers|callees|refs|ext|scaffold|build-errors|security|join|entity|changed|grep|label …
xpplens build [--full-only] [--std-only] [--compiled-only] [--force]
xpplens status [--counts] [--ascii] | stats | update [--install] | detect | config | register | unregister | mcp | version
xpplens skills [install [--lang en|pl] | remove | status]
```

`xpplens help` wypisuje wszystkie opcje. Zmienne: `XPPLENS_CONFIG` (inna konfiguracja), `XPPLENS_VERBOSE=1`
Expand Down Expand Up @@ -303,6 +325,7 @@ Tagi wydań są chronione: opublikowanej wersji nie da się zmienić — poprawk
| `xpplens update`, przejęcie xpp-graft | `Updater.cs`, `Migration.cs` |
| automatyczne wydania | `.github\workflows\release.yml`, `release-notes.ps1`, `pack.ps1` |
| polecenia CLI, konfiguracja, rejestracja w Claude | `Program.cs`, `Config.cs`, `Detect.cs` |
| skille Claude Code (tekst: `SKILL.md` po angielsku, `SKILL.pl.md` po polsku — zmieniać oba) | `skills\<nazwa>\`; ich instalacja: `Skills.cs` |

Pętla pracy:

Expand Down
30 changes: 27 additions & 3 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

.DESCRIPTION
Copies the files, determines the PackagesLocalDirectory folder (given or detected),
writes the configuration, registers the MCP server in Claude Desktop and Claude Code
and builds the index. The AOS repository is only ever read, never written.
writes the configuration, registers the MCP server in Claude Desktop and Claude Code,
copies the Claude Code skills (xpp-ponytail, xpp-ponytail-review) to ~/.claude/skills
(-NoSkills: not; -SkillsLanguage en|pl, default: the Windows language, then kept for updates) and builds the index. The AOS repository is only ever read, never written.

Run it in a regular (non-elevated) PowerShell: the index is created in the
%LOCALAPPDATA% of the user who runs the installer, and Claude must be able to write to it.
Expand Down Expand Up @@ -41,6 +42,9 @@ param(
[switch] $NoStandard,
[switch] $NoBuild,
[switch] $NoRegister,
[switch] $NoSkills,
[ValidateSet('en', 'pl')]
[string] $SkillsLanguage,
[switch] $DesktopOnly,
[switch] $CodeOnly,
[switch] $RestartClaude
Expand Down Expand Up @@ -117,6 +121,11 @@ else {
foreach ($f in 'install.ps1', 'uninstall.ps1', 'restart-claude.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'LICENSE') {
if (Test-Path (Join-Path $src $f)) { Copy-Item (Join-Path $src $f) $InstallDir -Force }
}
# The package's copy of the skills (installed to ~/.claude/skills in step 4); replaced whole, so a skill
# dropped from the package does not linger here.
$skillsDst = Join-Path $InstallDir 'skills'
if (Test-Path $skillsDst) { Remove-Item $skillsDst -Recurse -Force }
if (Test-Path (Join-Path $src 'skills')) { Copy-Item (Join-Path $src 'skills') $InstallDir -Recurse -Force }
}
$exe = Join-Path $InstallDir 'bin\xpplens.exe'
Say "done: $exe"
Expand Down Expand Up @@ -161,7 +170,7 @@ if ($NoStandard) { $cfgArgs += @('--index-standard', 'false') }
& $exe @cfgArgs
if ($LASTEXITCODE -ne 0) { throw "Configuration failed." }

Head "4/5 Registering in Claude"
Head "4/5 Registering in Claude (MCP server, skills)"
if ($NoRegister) {
Say "skipped (-NoRegister). Manually: `"$exe`" register"
}
Expand All @@ -171,6 +180,21 @@ else {
if ($CodeOnly) { $regArgs += '--code' }
& $exe @regArgs
}
# Claude Code skills (xpp-ponytail): a skill changed locally is kept and reported with a warning.
if ($NoSkills) {
Say "skills skipped (-NoSkills). Manually: `"$exe`" skills install"
}
else {
# Language of the skill texts: -SkillsLanguage, else kept from the last installation, else that of Windows.
$skillArgs = @('skills', 'install')
if ($SkillsLanguage) { $skillArgs += @('--lang', $SkillsLanguage) }
elseif (-not (Test-Path (Join-Path $InstallDir 'skills-installed.json'))) {
$skillArgs += @('--lang', $(if ((Get-UICulture).TwoLetterISOLanguageName -eq 'pl') { 'pl' } else { 'en' }))
}
& $exe @skillArgs | ForEach-Object {
if ($_ -like 'warning:*') { Write-Host " $_" -ForegroundColor Yellow } else { Say $_ }
}
}

Head "5/5 Building the index"
if ($NoBuild) {
Expand Down
2 changes: 2 additions & 0 deletions pack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Remove-Item (Join-Path $stage 'bin\*.pdb') -Force -ErrorAction SilentlyContinue
foreach ($f in 'install.ps1', 'uninstall.ps1', 'restart-claude.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'LICENSE') {
Copy-Item (Join-Path $PSScriptRoot $f) $stage -Force
}
# Claude Code skills (one folder each): install.ps1 and the setup program copy them to ~/.claude/skills.
Copy-Item (Join-Path $PSScriptRoot 'skills') $stage -Recurse -Force

$size = [math]::Round((Get-ChildItem $stage -Recurse -File | Measure-Object Length -Sum).Sum / 1MB, 1)
Write-Host "package: $stage ($size MB)" -ForegroundColor Cyan
Expand Down
63 changes: 62 additions & 1 deletion packaging/inno/xpp-lens.iss
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ VersionInfoVersion={#FileVersion}
Name: "en"; MessagesFile: "compiler:Default.isl"
Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl"

[InstallDelete]
; The package's copy of the skills is replaced whole, so a skill dropped from the package does not linger.
Type: filesandordirs; Name: "{app}\skills"

[Files]
Source: "{#Source}\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion

Expand Down Expand Up @@ -96,6 +100,11 @@ en.StdTitle=Microsoft standard code
en.StdSub=Objects, signatures and calls of the standard models
en.StdText=Recommended: Claude then also finds callers and extensions inside Microsoft code. The first indexing takes 20-90 minutes (in the background), later only what changed.
en.StdOption=Index the Microsoft standard models (recommended)
en.SkillsTitle=Claude Code skills
en.SkillsSub=xpp-ponytail and xpp-ponytail-review
en.SkillsText=Two skills for X++ work in Claude Code: /xpp-ponytail (the smallest complete change: configuration, standard, extension, only then new code) and /xpp-ponytail-review (a review of an X++ change). They go to .claude\skills in your user profile; a skill you change there is never overwritten.
en.SkillsOption=Install the Claude Code skills (xpp-ponytail)
en.SkillsKept=Skills you changed were kept and not updated:%n%n%1
en.ConfigFailed=Saving the settings did not work:%n%n%1
en.Registered=xpp-lens is ready. Open Claude and ask, for example: "find the method SalesTable.validateWrite".
en.BuildNow=Build the index now (a console window shows the progress)
Expand Down Expand Up @@ -123,6 +132,11 @@ pl.StdTitle=Kod standardowy Microsoftu
pl.StdSub=Obiekty, sygnatury i wywołania modeli standardowych
pl.StdText=Zalecane: Claude znajdzie wtedy także wywołania i rozszerzenia w kodzie Microsoftu. Pierwsze indeksowanie trwa 20-90 minut (w tle), później tylko zmiany.
pl.StdOption=Indeksuj modele standardowe Microsoftu (zalecane)
pl.SkillsTitle=Skille Claude Code
pl.SkillsSub=xpp-ponytail i xpp-ponytail-review
pl.SkillsText=Dwa skille do pracy z X++ w Claude Code: /xpp-ponytail (najmniejsza kompletna zmiana: konfiguracja, standard, rozszerzenie, dopiero potem nowy kod) i /xpp-ponytail-review (przegląd zmiany X++). Trafiają do .claude\skills w profilu użytkownika; skilla, którego tam zmienisz, instalator nigdy nie nadpisze.
pl.SkillsOption=Zainstaluj skille Claude Code (xpp-ponytail)
pl.SkillsKept=Zmienione przez ciebie skille zostały zachowane i nie zostały zaktualizowane:%n%n%1
pl.ConfigFailed=Nie udało się zapisać ustawień:%n%n%1
pl.Registered=xpp-lens jest gotowy. Otwórz Claude'a i zapytaj np.: "znajdź metodę SalesTable.validateWrite".
pl.BuildNow=Zbuduj indeks teraz (okno konsoli pokaże postęp)
Expand All @@ -141,6 +155,7 @@ var
PkgInfo: TNewStaticText;
LangPage: TInputQueryWizardPage;
StdPage: TInputOptionWizardPage;
SkillsPage: TInputOptionWizardPage;
Detected: Boolean;
WasUpdate: Boolean;
RemoveUserData: Boolean;
Expand Down Expand Up @@ -259,6 +274,36 @@ begin
StdPage := CreateInputOptionPage(LangPage.ID, CustomMessage('StdTitle'), CustomMessage('StdSub'), CustomMessage('StdText'), False, False);
StdPage.Add(CustomMessage('StdOption'));
StdPage.Values[0] := True;

{ Shown on updates too; a silent update (xpplens update --install) keeps the choice of the last installation. }
SkillsPage := CreateInputOptionPage(StdPage.ID, CustomMessage('SkillsTitle'), CustomMessage('SkillsSub'), CustomMessage('SkillsText'), False, False);
SkillsPage.Add(CustomMessage('SkillsOption'));
SkillsPage.Values[0] := GetPreviousData('Skills', '1') = '1';
end;

procedure RegisterPreviousData(PreviousDataKey: Integer);
begin
if SkillsPage.Values[0] then
SetPreviousData(PreviousDataKey, 'Skills', '1')
else
SetPreviousData(PreviousDataKey, 'Skills', '0');
end;

{ The "warning:" lines of "xpplens skills install / remove" (skills the user changed, kept as they are). }
function SkillWarnings(Output: String): String;
var
Line: String;
P: Integer;
begin
Result := '';
Output := Output + #10;
while Output <> '' do
begin
P := Pos(#10, Output);
Line := Trim(Copy(Output, 1, P - 1));
Delete(Output, 1, P);
if Pos('warning:', Line) = 1 then Result := Result + Trim(Copy(Line, 9, Length(Line))) + #13#10;
end;
end;

{ Fills the folder page from "xpplens detect" (lines: path TAB packages TAB source). }
Expand Down Expand Up @@ -383,6 +428,14 @@ begin
end;
{ Registration in Claude Desktop and Claude Code (an update re-registers the same path). }
RunXpp(Exe, 'register', Output);
{ Claude Code skills; one the user changed is kept, and said so (no message box in a silent update). }
if SkillsPage.Values[0] then
begin
{ In the wizard's language (a silent update runs in the language of the last installation). }
RunXpp(Exe, 'skills install --lang ' + ActiveLanguage, Output);
Output := SkillWarnings(Output);
if (Output <> '') and not WizardSilent then MsgBox(FmtMessage(CustomMessage('SkillsKept'), [Output]), mbInformation, MB_OK);
end;
{ Silent update with /RESTARTCLAUDE=1 (xpplens update --install --restart-claude). }
if ExpandConstant('{param:RESTARTCLAUDE|0}') = '1' then
Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + ExpandConstant('{app}\restart-claude.ps1') + '" -Version {#Version}',
Expand All @@ -392,10 +445,18 @@ end;
{ Before the files go: ask whether to remove the index and settings (default: keep them). After: take the program
folder off the PATH and delete the data when asked to. }
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
Output: String;
begin
if CurUninstallStep = usUninstall then
begin
RemoveUserData := not UninstallSilent and
(MsgBox(CustomMessage('RemoveData'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES)
(MsgBox(CustomMessage('RemoveData'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES);
{ Only the skills xpp-lens installed and nobody changed; a changed one stays where it is. }
RunXpp(ExpandConstant('{app}\bin\xpplens.exe'), 'skills remove', Output);
Output := SkillWarnings(Output);
if (Output <> '') and not UninstallSilent then MsgBox(Output, mbInformation, MB_OK);
end
else if CurUninstallStep = usPostUninstall then
begin
RemoveFromPath(ExpandConstant('{app}\bin'));
Expand Down
3 changes: 2 additions & 1 deletion release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ $archive = [IO.Compression.ZipFile]::OpenRead($zip)
$entries = @($archive.Entries | ForEach-Object { $_.FullName })
$archive.Dispose()
Remove-Item $tmp -Recurse -Force
foreach ($e in 'xpp-lens/install.ps1', 'xpp-lens/bin/xpplens.exe', 'xpp-lens/CHANGELOG.md') {
foreach ($e in 'xpp-lens/install.ps1', 'xpp-lens/bin/xpplens.exe', 'xpp-lens/bin/e_sqlite3.dll', 'xpp-lens/CHANGELOG.md',
'xpp-lens/skills/xpp-ponytail/SKILL.md', 'xpp-lens/skills/xpp-ponytail-review/SKILL.md') {
if ($entries -notcontains $e) { Fail "$asset has no $e (entries: $($entries -join ', '))" }
}
Ok "${asset}: SHA-256 matches, layout ok, $(if ($Pre) { 'test release' } else { 'regular release' })"
Expand Down
Loading
Loading