{"owner":"Devolutions","repo":"UniGetUI","hasSkills":true,"totalSkillsCount":8,"totalTokensCount":13863,"categories":["anthropic-skill","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/translation-source-sync/SKILL.md",".agents/skills/translation-status/SKILL.md",".agents/skills/translation-diff-translate/SKILL.md",".agents/skills/translation-diff-import/SKILL.md",".agents/skills/translation-diff-export/SKILL.md",".agents/skills/translation-review/SKILL.md","AGENTS.md","CLAUDE.md"],"skills":{".agents/skills/translation-source-sync/SKILL.md":"---\nname: translation-source-sync\ndescription: Synchronizes the UniGetUI English language file with source-code usage, identifies missing translation keys, removes unused entries, reports localization drift, and can reorder locale files to match the English key ordering. Use when the user asks to sync language files, missing translations, i18n drift, or align locale file ordering with English.\n---\n\n# translation source sync\n\nUse this skill when UniGetUI source code changed and you need to keep [src/Languages/lang_en.json](src/Languages/lang_en.json) aligned with the strings actually used by the application, or when you need downstream locale files reordered to match the English key ordering.\n\nIt scans supported C#, WinUI XAML, and Avalonia AXAML patterns, finds missing English keys, removes unused entries, reports translation-source warnings that still need manual cleanup, and can align locale file ordering to the English layout.\n\n## Scope\n\n- Extract literal translation keys from supported UniGetUI source patterns.\n- Add new English keys missing from `lang_en.json`.\n- Remove English keys that are no longer used.\n- Reorder non-English locale files to match the English key ordering.\n- Warn about interpolated `CoreTools.Translate($\"...\")` calls that are not synchronized automatically.\n- Leave downstream language propagation to the existing translation diff workflow.\n\n## Supported extraction sources\n\n- `CoreTools.Translate(\"...\")` in C#.\n- `CoreTools.AutoTranslated(\"...\")` in C#.\n- `widgets:TranslatedTextBlock Text=\"...\"` in WinUI XAML.\n- `settings:TranslatedTextBlock Text=\"...\"` in Avalonia AXAML.\n- `{t:Translate Some text}` and `{t:Translate Text='A, B, C'}` in Avalonia AXAML.\n\n## Out of scope for v1\n\n- Interpolated translation calls such as `CoreTools.Translate($\"Running {value}\")`.\n- Non-literal translation inputs passed through variables.\n- CI enforcement.\n\n## Scripts\n\n- `scripts/sync-translation-sources.ps1`: Skill wrapper around the repository sync script.\n- `scripts/set-translation-boundary-order.ps1`: Skill wrapper around the repository locale reordering script.\n- `scripts/test-translation-source-sync.ps1`: Skill wrapper around the repository smoke test.\n- `../../../../scripts/translation/Sync-TranslationSources.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Set-TranslationBoundaryOrder.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Test-TranslationSourceSync.ps1`: Canonical smoke test used by the wrapper.\n\n## Usage\n\nSynchronize the checked-in English language file in place:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1\n```\n\nCheck whether the English file is out of sync without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1 -CheckOnly\n```\n\nReorder locale files to match the English key ordering:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1\n```\n\nPreview whether locale files need reordering without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1 -CheckOnly\n```\n\nRun the smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1\n```\n\n## Recommended workflow\n\n1. Run translation source sync after changing any translatable source string.\n2. If `-CheckOnly` reports drift, run the full sync to rewrite `lang_en.json`.\n3. Review warnings for interpolated translation calls and convert them to stable literals or add the missing English keys manually when needed.\n4. Run `pwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1` to align locale ordering with English.\n5. Run `pwsh ./scripts/translation/Verify-Translations.ps1` to confirm the language files still validate cleanly.\n6. Run `pwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1` if you changed the sync workflow itself.\n7. Export changed work for translators with [translation-diff-export](../translation-diff-export/SKILL.md).\n\n## Notes\n\n- Existing English values are preserved for retained keys; newly added English entries default to `key == value`.\n- The sync script preserves current key order for retained entries and appends newly discovered keys deterministically.\n- The locale reorder script follows English as the canonical order and appends unmapped locale-only keys at the end.\n- The smoke test uses a temporary synthetic repo so it does not mutate the checked-in language files.\n",".agents/skills/translation-status/SKILL.md":"---\nname: translation-status\ndescription: Analyzes checked-in UniGetUI language files to calculate translation percentages, surface untranslated coverage data, and generate localization status reports.\n---\n\n# translation status\n\nUse this skill when the user asks for UniGetUI translation progress, localization or i18n coverage, untranslated-language reports, or a language support status summary.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/get-translation-status.ps1`: Skill wrapper you should invoke from the skill.\n- `../../../../scripts/translation/Get-TranslationStatus.ps1`: Canonical repository implementation delegated to by the wrapper.\n\n## Usage\n\nShow the default table summary:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1\n```\n\nShow only incomplete languages as markdown:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Markdown \\\n  -OnlyIncomplete\n```\n\nWrite JSON output to a file:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Json \\\n  -OutputPath ./generated/translation-status.json\n```\n\n## Output Fields\n\n- `Code`: language code\n- `Language`: display name\n- `Completion`: computed completion percentage using English active keys only\n- `Translated`, `Missing`, `Empty`, `SourceEqual`, `Extra`: per-language entry counts\n- `Stored` and `Delta`: stored percentage metadata and difference from the computed result\n\n## Notes\n\n- The script treats source-equal values as untranslated for non-English languages.\n- Completion never reports `100%` unless every active English key is present and translated.\n- `Extra` counts locale-only keys not present in the English file.\n- Use `-IncludeEnglish` if you want the `en` row included in the report.\n- Use `-OnlyIncomplete` to focus on languages that still need work.\n- For the full parameter surface, inspect `../../../../scripts/translation/Get-TranslationStatus.ps1`.\n",".agents/skills/translation-diff-translate/SKILL.md":"---\nname: translation-diff-translate\ndescription: Translates a sparse UniGetUI JSON language patch, writes completed entries into the working copy, preserves placeholders and terminology, and prepares the patch for merge-back. Use when the user asks to translate or localize i18n strings, localization JSON, or language-file patches.\n---\n\n# translation diff translate\n\nUse this skill after `translation-diff-export` produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to update the sparse translated working copy.\n\nIt translates only the active patch keys, keeps `.translated.json` as the sole writable file, preserves placeholders and formatting, and hands the completed working copy to `translation-diff-import` for merge-back.\n\n## Execution Expectations\n\n1. Edit `.translated.json` directly. Do not create helper scripts, temporary reports, or coverage probes.\n2. Do not install packages or search for external translation services unless the user explicitly asks for automation.\n3. If `.translated.json` is empty, that is expected. Translate from `.source.json` into `.translated.json`.\n4. Use `.reference.json` only as terminology guidance, not as a reason to pause and analyze the rest of the repository.\n5. If the patch is too large to finish reasonably in one pass, report that constraint to the user instead of creating automation on your own.\n\n## Script\n\n- `scripts/write-translation-handoff.ps1`: Writes a small `.prompt.md` file that points an agent at this skill with the concrete patch paths created by the export skill.\n\n## Usage\n\nGenerate a handoff prompt for a translated French working copy:\n\n```powershell\npwsh ./.agents/skills/translation-diff-translate/scripts/write-translation-handoff.ps1 \\\n  -BaseName lang \\\n  -Language fr \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -ReferencePatch ./generated/translation-diff-export/lang.diff.fr.reference.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -MergedTargetJson ./src/Languages/lang_fr.merged.json \\\n  -ValidationScript ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -OutputPrompt ./generated/translation-diff-export/lang.diff.fr.prompt.md\n```\n\n## Translation Rules\n\n1. Do not edit `.source.json`.\n2. Write completed translations only into `.translated.json`.\n3. If a key is not translated yet, omit it from `.translated.json` instead of copying the English source value.\n4. Preserve key names exactly as-is.\n5. Preserve placeholders, named tokens, HTML-like fragments, escape sequences, and line breaks.\n6. Keep translated entries in the same key order as `.source.json` when adding new entries.\n7. Use `.reference.json` to match existing terminology and tone in the destination language.\n8. Do not introduce keys that are not present in the source patch.\n\n## After Translation\n\nMerge the sparse working copy back into the full destination-language file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```",".agents/skills/translation-diff-import/SKILL.md":"---\nname: translation-diff-import\ndescription: Merges translated key-value pairs from a UniGetUI JSON localization patch back into the full language file and validates the merged result. Use when the user asks to apply, merge, or import a translation patch.\n---\n\n# translation diff import\n\nUse this skill after the export skill produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to merge the translated working copy back into the full target-language JSON file.\n\nSee [translation-diff-export](../translation-diff-export/SKILL.md) for the patch-generation step that produces the expected inputs.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `cirup` available on `PATH`.\n\n## Scripts\n\n- `scripts/import-translation-diff.ps1`: Validates and merges a translated patch into the full language file.\n- `scripts/validate-language-file.ps1`: Validates placeholder, token, HTML-fragment, and newline parity for either a full language file or just the active patch keys against `lang_en.json`.\n\n## Usage\n\nImport the translated French working copy from the export skill into the full French JSON file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nValidate the merged output:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-import`)\n- `-AllowUnchangedValues`\n- `-KeepIntermediate`\n\nRecommended input mapping from the export skill:\n\n- `-TranslatedPatch`: `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `-SourcePatch`: `generated/translation-diff-export/lang.diff.fr.source.json`\n\n## Output\n\n- A merged `.json` file that contains both the previously translated entries and the imported patch values.\n- If `-KeepIntermediate` is used, patch snapshots are preserved under `generated/translation-diff-import/tmp/`.\n\n## Edge cases\n\n- If the target JSON file does not exist yet, the script can create an output from the translated patch alone.\n- When `-SourcePatch` is provided, the script validates translated keys, placeholder tokens, HTML-like fragments, newline counts, and likely untranslated values before delegating the merge to `cirup` while allowing missing keys for partial progress.\n- The import skill expects `.translated.json` to remain sparse. Untranslated keys should be omitted instead of copied in English unless you intentionally bypass that check with `-AllowUnchangedValues`.\n",".agents/skills/translation-diff-export/SKILL.md":"---\nname: translation-diff-export\ndescription: Compares UniGetUI JSON locale files against English, identifies untranslated or source-changed keys, and generates patch, reference, and handoff files for a target language. Use when the user asks to export missing translations, untranslated strings, i18n diffs, or locale-file changes.\n---\n\n# translation diff export\n\nUse this skill when you need a small UniGetUI translation patch instead of sending a full language JSON file for review or translation.\n\nIt exports only the active untranslated or source-changed strings, creates the translator handoff files, and prepares the patch set needed by `translation-diff-translate` and `translation-diff-import`.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `git` available on `PATH`.\n- `cirup` available on `PATH`.\n\nWhen source strings changed, run `translation-source-sync` first so `lang_en.json` reflects current source usage before exporting downstream translation patches.\n\n## Scripts\n\n- `scripts/export-translation-diff.ps1`: Exports JSON patch artifacts and generates a translation handoff prompt.\n- `scripts/test-translation-diff.ps1`: Runs a local end-to-end smoke test against the checked-in UniGetUI language files.\n\nUse this skill's wrapper scripts first; the downstream translate and import steps are documented in [translation-diff-translate](../translation-diff-translate/SKILL.md) and [translation-diff-import](../translation-diff-import/SKILL.md).\n\n## Usage\n\nExport untranslated French strings only:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nExport untranslated French strings plus English source values changed since `origin/main`:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr \\\n  -BaseRef origin/main\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-export`)\n- `-KeepIntermediate`\n\nRun the built-in smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/test-translation-diff.ps1\n```\n\n## Output\n\nFor `lang_en.json` and language `fr`, the script generates:\n\n- `generated/translation-diff-export/lang.diff.fr.source.json`\n- `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `generated/translation-diff-export/lang.diff.fr.reference.json`\n- `generated/translation-diff-export/lang.diff.fr.prompt.md`\n\nIf `-KeepIntermediate` is used, git-baseline snapshots are kept under `generated/translation-diff-export/tmp/`.\n\nThe smoke test writes its temporary artifacts under `generated/translation-diff-export-demo/`.\n\n## Validate The Export\n\nAfter export, confirm the patch is usable before handing it off:\n\n1. Check that `.source.json` is not empty unless you expected no work for that language.\n2. Confirm `.translated.json` is sparse and does not contain copied English placeholders for unfinished keys.\n3. If the patch should include recent English changes, rerun with `-BaseRef` and compare the resulting `.source.json`.\n4. Run the smoke test if you changed the export workflow itself.\n\n## Hand Off To Translate\n\nAfter exporting the patch, use the generated `.prompt.md` file with [translation-diff-translate](../translation-diff-translate/SKILL.md) to update the sparse translated working copy.\n\n## Hand Off To Import\n\nAfter translating the patch, merge it back into the full language file with [translation-diff-import](../translation-diff-import/SKILL.md):\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nKeep the `.translated.json` file sparse. If a key is not translated yet, leave it out instead of copying the English source value into the working copy.",".agents/skills/translation-review/SKILL.md":"---\nname: translation-review\ndescription: Reviews UniGetUI .json language files for localization quality, detects parity issues, English-equal entries, wrong-script content, and cross-language outliers, then generates a dataset for LLM-assisted linguistic review. Use when the user asks to review translations, audit localization, inspect i18n or l10n quality, or validate language files.\n---\n\n# translation review\n\nUse this skill when you need a quality review of an existing UniGetUI translation, not just coverage, but correctness, language integrity, and consistency against related languages.\n\nTypical triggers: review translations, check translation quality, mistranslations, wrong language in translation, untranslated strings, localization QA, translation audit, spot-check a language file.\n\n## Scope\n\n- Mechanically detect placeholder, HTML-fragment, and newline mismatches.\n- Flag entries whose translation still equals the English source in a non-English language file.\n- Detect wrong-script entries for non-Latin languages such as `ua`, `zh_CN`, `ar`, `ko`, `ja`, and `he`.\n- Build a cross-language comparison table so the reviewing agent can identify outliers, suspicious wording, and wrong-language content.\n- The agent performs the final linguistic review using the generated report.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/review-translation-json.ps1`: Generate the review dataset for one UniGetUI language JSON file.\n\n## Workflow\n\n1. Generate the review dataset for the target language.\n2. Confirm the output file exists and contains the expected flagged sections or JSON fields.\n3. If the script fails or the output is empty, validate the target JSON with `pwsh ./scripts/translation/Verify-Translations.ps1` and rerun the review.\n4. Read the flagged sections first: parity issues, English-equal entries, and wrong-script entries.\n5. Spot-check the cross-language comparison table, focusing on labels, buttons, settings text, and error messages.\n6. Report findings inline with the English source text, observed problem, and suggested correction.\n\n## Usage\n\nGenerate a Markdown review report for French:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nGenerate a report for Ukrainian and include script-detection checks:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_ua.json \\\n  -Language ua\n```\n\nGenerate a flagged-only JSON report with no cross-language table:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_de.json \\\n  -Language de \\\n  -OutputFormat Json \\\n  -FlaggedOnly\n```\n\nOptional parameters:\n\n- `-NeutralJson` — defaults to `src/Languages/lang_en.json`\n- `-ComparisonLanguages` — languages to include in the comparison table; defaults to other checked-in `lang_*.json` files\n- `-OutputPath` — defaults to `generated/translation-review/review.<lang>.md`\n- `-OutputFormat` — `Markdown` (default) or `Json`\n- `-FlaggedOnly` — skip the full cross-language table and emit only mechanically flagged entries\n\n## Output\n\nThe Markdown report is structured as:\n\n1. Summary\n2. Parity Issues\n3. English-Equal Entries\n4. Wrong Script\n5. Cross-Language Comparison\n\nDefault output path: `generated/translation-review/review.<lang>.md`\n\n## Agent Review Guidelines\n\n### Flagged sections\n\n- Parity issues: confirm whether the placeholder, tag, or newline mismatch is a genuine error or an acceptable localized variant.\n- English-equal entries: decide whether the English value is an acceptable technical or brand string, or whether it should be translated.\n- Wrong script: almost always an error for non-Latin languages. Verify against comparison languages and flag for re-translation when confirmed.\n\n### Cross-language comparison table\n\n- Sample at least 20–30 rows and prefer entries that look like UI labels, buttons, dialogs, settings descriptions, or error messages.\n- Look for wrong language, suspiciously different meaning, missing words, or copied English that should probably be translated.\n- Ignore obvious brand names and technical identifiers.\n\n### Output format for findings\n\nReport each issue in this format:\n\n```text\n**Source**: `Some English source text`\n**Problem**: [describe the issue]\n**Current value**: [the problematic translation]\n**Suggested correction**: [corrected value, if confident]\n**Confidence**: High / Medium / Low\n```\n\nGroup findings by type: parity, English-equal, wrong language, wrong script, or other.\n\n## Integration with Other Skills\n\n- Use [translation-status](../translation-status/SKILL.md) first if you need a coverage snapshot before reviewing quality.\n- Use [translation-diff-export](../translation-diff-export/SKILL.md) to produce a sparse patch for entries that need re-translation after review.\n- Use [translation-diff-import](../translation-diff-import/SKILL.md) to merge corrected entries back into the full language file.","AGENTS.md":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n","CLAUDE.md":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n"},"files":{".agents/skills/translation-source-sync/SKILL.md":"---\nname: translation-source-sync\ndescription: Synchronizes the UniGetUI English language file with source-code usage, identifies missing translation keys, removes unused entries, reports localization drift, and can reorder locale files to match the English key ordering. Use when the user asks to sync language files, missing translations, i18n drift, or align locale file ordering with English.\n---\n\n# translation source sync\n\nUse this skill when UniGetUI source code changed and you need to keep [src/Languages/lang_en.json](src/Languages/lang_en.json) aligned with the strings actually used by the application, or when you need downstream locale files reordered to match the English key ordering.\n\nIt scans supported C#, WinUI XAML, and Avalonia AXAML patterns, finds missing English keys, removes unused entries, reports translation-source warnings that still need manual cleanup, and can align locale file ordering to the English layout.\n\n## Scope\n\n- Extract literal translation keys from supported UniGetUI source patterns.\n- Add new English keys missing from `lang_en.json`.\n- Remove English keys that are no longer used.\n- Reorder non-English locale files to match the English key ordering.\n- Warn about interpolated `CoreTools.Translate($\"...\")` calls that are not synchronized automatically.\n- Leave downstream language propagation to the existing translation diff workflow.\n\n## Supported extraction sources\n\n- `CoreTools.Translate(\"...\")` in C#.\n- `CoreTools.AutoTranslated(\"...\")` in C#.\n- `widgets:TranslatedTextBlock Text=\"...\"` in WinUI XAML.\n- `settings:TranslatedTextBlock Text=\"...\"` in Avalonia AXAML.\n- `{t:Translate Some text}` and `{t:Translate Text='A, B, C'}` in Avalonia AXAML.\n\n## Out of scope for v1\n\n- Interpolated translation calls such as `CoreTools.Translate($\"Running {value}\")`.\n- Non-literal translation inputs passed through variables.\n- CI enforcement.\n\n## Scripts\n\n- `scripts/sync-translation-sources.ps1`: Skill wrapper around the repository sync script.\n- `scripts/set-translation-boundary-order.ps1`: Skill wrapper around the repository locale reordering script.\n- `scripts/test-translation-source-sync.ps1`: Skill wrapper around the repository smoke test.\n- `../../../../scripts/translation/Sync-TranslationSources.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Set-TranslationBoundaryOrder.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Test-TranslationSourceSync.ps1`: Canonical smoke test used by the wrapper.\n\n## Usage\n\nSynchronize the checked-in English language file in place:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1\n```\n\nCheck whether the English file is out of sync without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1 -CheckOnly\n```\n\nReorder locale files to match the English key ordering:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1\n```\n\nPreview whether locale files need reordering without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1 -CheckOnly\n```\n\nRun the smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1\n```\n\n## Recommended workflow\n\n1. Run translation source sync after changing any translatable source string.\n2. If `-CheckOnly` reports drift, run the full sync to rewrite `lang_en.json`.\n3. Review warnings for interpolated translation calls and convert them to stable literals or add the missing English keys manually when needed.\n4. Run `pwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1` to align locale ordering with English.\n5. Run `pwsh ./scripts/translation/Verify-Translations.ps1` to confirm the language files still validate cleanly.\n6. Run `pwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1` if you changed the sync workflow itself.\n7. Export changed work for translators with [translation-diff-export](../translation-diff-export/SKILL.md).\n\n## Notes\n\n- Existing English values are preserved for retained keys; newly added English entries default to `key == value`.\n- The sync script preserves current key order for retained entries and appends newly discovered keys deterministically.\n- The locale reorder script follows English as the canonical order and appends unmapped locale-only keys at the end.\n- The smoke test uses a temporary synthetic repo so it does not mutate the checked-in language files.\n",".agents/skills/translation-status/SKILL.md":"---\nname: translation-status\ndescription: Analyzes checked-in UniGetUI language files to calculate translation percentages, surface untranslated coverage data, and generate localization status reports.\n---\n\n# translation status\n\nUse this skill when the user asks for UniGetUI translation progress, localization or i18n coverage, untranslated-language reports, or a language support status summary.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/get-translation-status.ps1`: Skill wrapper you should invoke from the skill.\n- `../../../../scripts/translation/Get-TranslationStatus.ps1`: Canonical repository implementation delegated to by the wrapper.\n\n## Usage\n\nShow the default table summary:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1\n```\n\nShow only incomplete languages as markdown:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Markdown \\\n  -OnlyIncomplete\n```\n\nWrite JSON output to a file:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Json \\\n  -OutputPath ./generated/translation-status.json\n```\n\n## Output Fields\n\n- `Code`: language code\n- `Language`: display name\n- `Completion`: computed completion percentage using English active keys only\n- `Translated`, `Missing`, `Empty`, `SourceEqual`, `Extra`: per-language entry counts\n- `Stored` and `Delta`: stored percentage metadata and difference from the computed result\n\n## Notes\n\n- The script treats source-equal values as untranslated for non-English languages.\n- Completion never reports `100%` unless every active English key is present and translated.\n- `Extra` counts locale-only keys not present in the English file.\n- Use `-IncludeEnglish` if you want the `en` row included in the report.\n- Use `-OnlyIncomplete` to focus on languages that still need work.\n- For the full parameter surface, inspect `../../../../scripts/translation/Get-TranslationStatus.ps1`.\n",".agents/skills/translation-diff-translate/SKILL.md":"---\nname: translation-diff-translate\ndescription: Translates a sparse UniGetUI JSON language patch, writes completed entries into the working copy, preserves placeholders and terminology, and prepares the patch for merge-back. Use when the user asks to translate or localize i18n strings, localization JSON, or language-file patches.\n---\n\n# translation diff translate\n\nUse this skill after `translation-diff-export` produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to update the sparse translated working copy.\n\nIt translates only the active patch keys, keeps `.translated.json` as the sole writable file, preserves placeholders and formatting, and hands the completed working copy to `translation-diff-import` for merge-back.\n\n## Execution Expectations\n\n1. Edit `.translated.json` directly. Do not create helper scripts, temporary reports, or coverage probes.\n2. Do not install packages or search for external translation services unless the user explicitly asks for automation.\n3. If `.translated.json` is empty, that is expected. Translate from `.source.json` into `.translated.json`.\n4. Use `.reference.json` only as terminology guidance, not as a reason to pause and analyze the rest of the repository.\n5. If the patch is too large to finish reasonably in one pass, report that constraint to the user instead of creating automation on your own.\n\n## Script\n\n- `scripts/write-translation-handoff.ps1`: Writes a small `.prompt.md` file that points an agent at this skill with the concrete patch paths created by the export skill.\n\n## Usage\n\nGenerate a handoff prompt for a translated French working copy:\n\n```powershell\npwsh ./.agents/skills/translation-diff-translate/scripts/write-translation-handoff.ps1 \\\n  -BaseName lang \\\n  -Language fr \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -ReferencePatch ./generated/translation-diff-export/lang.diff.fr.reference.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -MergedTargetJson ./src/Languages/lang_fr.merged.json \\\n  -ValidationScript ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -OutputPrompt ./generated/translation-diff-export/lang.diff.fr.prompt.md\n```\n\n## Translation Rules\n\n1. Do not edit `.source.json`.\n2. Write completed translations only into `.translated.json`.\n3. If a key is not translated yet, omit it from `.translated.json` instead of copying the English source value.\n4. Preserve key names exactly as-is.\n5. Preserve placeholders, named tokens, HTML-like fragments, escape sequences, and line breaks.\n6. Keep translated entries in the same key order as `.source.json` when adding new entries.\n7. Use `.reference.json` to match existing terminology and tone in the destination language.\n8. Do not introduce keys that are not present in the source patch.\n\n## After Translation\n\nMerge the sparse working copy back into the full destination-language file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```",".agents/skills/translation-diff-import/SKILL.md":"---\nname: translation-diff-import\ndescription: Merges translated key-value pairs from a UniGetUI JSON localization patch back into the full language file and validates the merged result. Use when the user asks to apply, merge, or import a translation patch.\n---\n\n# translation diff import\n\nUse this skill after the export skill produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to merge the translated working copy back into the full target-language JSON file.\n\nSee [translation-diff-export](../translation-diff-export/SKILL.md) for the patch-generation step that produces the expected inputs.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `cirup` available on `PATH`.\n\n## Scripts\n\n- `scripts/import-translation-diff.ps1`: Validates and merges a translated patch into the full language file.\n- `scripts/validate-language-file.ps1`: Validates placeholder, token, HTML-fragment, and newline parity for either a full language file or just the active patch keys against `lang_en.json`.\n\n## Usage\n\nImport the translated French working copy from the export skill into the full French JSON file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nValidate the merged output:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-import`)\n- `-AllowUnchangedValues`\n- `-KeepIntermediate`\n\nRecommended input mapping from the export skill:\n\n- `-TranslatedPatch`: `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `-SourcePatch`: `generated/translation-diff-export/lang.diff.fr.source.json`\n\n## Output\n\n- A merged `.json` file that contains both the previously translated entries and the imported patch values.\n- If `-KeepIntermediate` is used, patch snapshots are preserved under `generated/translation-diff-import/tmp/`.\n\n## Edge cases\n\n- If the target JSON file does not exist yet, the script can create an output from the translated patch alone.\n- When `-SourcePatch` is provided, the script validates translated keys, placeholder tokens, HTML-like fragments, newline counts, and likely untranslated values before delegating the merge to `cirup` while allowing missing keys for partial progress.\n- The import skill expects `.translated.json` to remain sparse. Untranslated keys should be omitted instead of copied in English unless you intentionally bypass that check with `-AllowUnchangedValues`.\n",".agents/skills/translation-diff-export/SKILL.md":"---\nname: translation-diff-export\ndescription: Compares UniGetUI JSON locale files against English, identifies untranslated or source-changed keys, and generates patch, reference, and handoff files for a target language. Use when the user asks to export missing translations, untranslated strings, i18n diffs, or locale-file changes.\n---\n\n# translation diff export\n\nUse this skill when you need a small UniGetUI translation patch instead of sending a full language JSON file for review or translation.\n\nIt exports only the active untranslated or source-changed strings, creates the translator handoff files, and prepares the patch set needed by `translation-diff-translate` and `translation-diff-import`.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `git` available on `PATH`.\n- `cirup` available on `PATH`.\n\nWhen source strings changed, run `translation-source-sync` first so `lang_en.json` reflects current source usage before exporting downstream translation patches.\n\n## Scripts\n\n- `scripts/export-translation-diff.ps1`: Exports JSON patch artifacts and generates a translation handoff prompt.\n- `scripts/test-translation-diff.ps1`: Runs a local end-to-end smoke test against the checked-in UniGetUI language files.\n\nUse this skill's wrapper scripts first; the downstream translate and import steps are documented in [translation-diff-translate](../translation-diff-translate/SKILL.md) and [translation-diff-import](../translation-diff-import/SKILL.md).\n\n## Usage\n\nExport untranslated French strings only:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nExport untranslated French strings plus English source values changed since `origin/main`:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr \\\n  -BaseRef origin/main\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-export`)\n- `-KeepIntermediate`\n\nRun the built-in smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/test-translation-diff.ps1\n```\n\n## Output\n\nFor `lang_en.json` and language `fr`, the script generates:\n\n- `generated/translation-diff-export/lang.diff.fr.source.json`\n- `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `generated/translation-diff-export/lang.diff.fr.reference.json`\n- `generated/translation-diff-export/lang.diff.fr.prompt.md`\n\nIf `-KeepIntermediate` is used, git-baseline snapshots are kept under `generated/translation-diff-export/tmp/`.\n\nThe smoke test writes its temporary artifacts under `generated/translation-diff-export-demo/`.\n\n## Validate The Export\n\nAfter export, confirm the patch is usable before handing it off:\n\n1. Check that `.source.json` is not empty unless you expected no work for that language.\n2. Confirm `.translated.json` is sparse and does not contain copied English placeholders for unfinished keys.\n3. If the patch should include recent English changes, rerun with `-BaseRef` and compare the resulting `.source.json`.\n4. Run the smoke test if you changed the export workflow itself.\n\n## Hand Off To Translate\n\nAfter exporting the patch, use the generated `.prompt.md` file with [translation-diff-translate](../translation-diff-translate/SKILL.md) to update the sparse translated working copy.\n\n## Hand Off To Import\n\nAfter translating the patch, merge it back into the full language file with [translation-diff-import](../translation-diff-import/SKILL.md):\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nKeep the `.translated.json` file sparse. If a key is not translated yet, leave it out instead of copying the English source value into the working copy.",".agents/skills/translation-review/SKILL.md":"---\nname: translation-review\ndescription: Reviews UniGetUI .json language files for localization quality, detects parity issues, English-equal entries, wrong-script content, and cross-language outliers, then generates a dataset for LLM-assisted linguistic review. Use when the user asks to review translations, audit localization, inspect i18n or l10n quality, or validate language files.\n---\n\n# translation review\n\nUse this skill when you need a quality review of an existing UniGetUI translation, not just coverage, but correctness, language integrity, and consistency against related languages.\n\nTypical triggers: review translations, check translation quality, mistranslations, wrong language in translation, untranslated strings, localization QA, translation audit, spot-check a language file.\n\n## Scope\n\n- Mechanically detect placeholder, HTML-fragment, and newline mismatches.\n- Flag entries whose translation still equals the English source in a non-English language file.\n- Detect wrong-script entries for non-Latin languages such as `ua`, `zh_CN`, `ar`, `ko`, `ja`, and `he`.\n- Build a cross-language comparison table so the reviewing agent can identify outliers, suspicious wording, and wrong-language content.\n- The agent performs the final linguistic review using the generated report.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/review-translation-json.ps1`: Generate the review dataset for one UniGetUI language JSON file.\n\n## Workflow\n\n1. Generate the review dataset for the target language.\n2. Confirm the output file exists and contains the expected flagged sections or JSON fields.\n3. If the script fails or the output is empty, validate the target JSON with `pwsh ./scripts/translation/Verify-Translations.ps1` and rerun the review.\n4. Read the flagged sections first: parity issues, English-equal entries, and wrong-script entries.\n5. Spot-check the cross-language comparison table, focusing on labels, buttons, settings text, and error messages.\n6. Report findings inline with the English source text, observed problem, and suggested correction.\n\n## Usage\n\nGenerate a Markdown review report for French:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nGenerate a report for Ukrainian and include script-detection checks:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_ua.json \\\n  -Language ua\n```\n\nGenerate a flagged-only JSON report with no cross-language table:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_de.json \\\n  -Language de \\\n  -OutputFormat Json \\\n  -FlaggedOnly\n```\n\nOptional parameters:\n\n- `-NeutralJson` — defaults to `src/Languages/lang_en.json`\n- `-ComparisonLanguages` — languages to include in the comparison table; defaults to other checked-in `lang_*.json` files\n- `-OutputPath` — defaults to `generated/translation-review/review.<lang>.md`\n- `-OutputFormat` — `Markdown` (default) or `Json`\n- `-FlaggedOnly` — skip the full cross-language table and emit only mechanically flagged entries\n\n## Output\n\nThe Markdown report is structured as:\n\n1. Summary\n2. Parity Issues\n3. English-Equal Entries\n4. Wrong Script\n5. Cross-Language Comparison\n\nDefault output path: `generated/translation-review/review.<lang>.md`\n\n## Agent Review Guidelines\n\n### Flagged sections\n\n- Parity issues: confirm whether the placeholder, tag, or newline mismatch is a genuine error or an acceptable localized variant.\n- English-equal entries: decide whether the English value is an acceptable technical or brand string, or whether it should be translated.\n- Wrong script: almost always an error for non-Latin languages. Verify against comparison languages and flag for re-translation when confirmed.\n\n### Cross-language comparison table\n\n- Sample at least 20–30 rows and prefer entries that look like UI labels, buttons, dialogs, settings descriptions, or error messages.\n- Look for wrong language, suspiciously different meaning, missing words, or copied English that should probably be translated.\n- Ignore obvious brand names and technical identifiers.\n\n### Output format for findings\n\nReport each issue in this format:\n\n```text\n**Source**: `Some English source text`\n**Problem**: [describe the issue]\n**Current value**: [the problematic translation]\n**Suggested correction**: [corrected value, if confident]\n**Confidence**: High / Medium / Low\n```\n\nGroup findings by type: parity, English-equal, wrong language, wrong script, or other.\n\n## Integration with Other Skills\n\n- Use [translation-status](../translation-status/SKILL.md) first if you need a coverage snapshot before reviewing quality.\n- Use [translation-diff-export](../translation-diff-export/SKILL.md) to produce a sparse patch for entries that need re-translation after review.\n- Use [translation-diff-import](../translation-diff-import/SKILL.md) to merge corrected entries back into the full language file.","AGENTS.md":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n","CLAUDE.md":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n","isInternal":false,"tokens":4485,"sizeBytes":18316},{"name":"SKILL.md","path":".agents/skills/translation-diff-export/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-diff-export/SKILL.md","title":"translation-diff-export","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-diff-export\ndescription: Compares UniGetUI JSON locale files against English, identifies untranslated or source-changed keys, and generates patch, reference, and handoff files for a target language. Use when the user asks to export missing translations, untranslated strings, i18n diffs, or locale-file changes.\n---\n\n# translation diff export\n\nUse this skill when you need a small UniGetUI translation patch instead of sending a full language JSON file for review or translation.\n\nIt exports only the active untranslated or source-changed strings, creates the translator handoff files, and prepares the patch set needed by `translation-diff-translate` and `translation-diff-import`.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `git` available on `PATH`.\n- `cirup` available on `PATH`.\n\nWhen source strings changed, run `translation-source-sync` first so `lang_en.json` reflects current source usage before exporting downstream translation patches.\n\n## Scripts\n\n- `scripts/export-translation-diff.ps1`: Exports JSON patch artifacts and generates a translation handoff prompt.\n- `scripts/test-translation-diff.ps1`: Runs a local end-to-end smoke test against the checked-in UniGetUI language files.\n\nUse this skill's wrapper scripts first; the downstream translate and import steps are documented in [translation-diff-translate](../translation-diff-translate/SKILL.md) and [translation-diff-import](../translation-diff-import/SKILL.md).\n\n## Usage\n\nExport untranslated French strings only:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nExport untranslated French strings plus English source values changed since `origin/main`:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr \\\n  -BaseRef origin/main\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-export`)\n- `-KeepIntermediate`\n\nRun the built-in smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/test-translation-diff.ps1\n```\n\n## Output\n\nFor `lang_en.json` and language `fr`, the script generates:\n\n- `generated/translation-diff-export/lang.diff.fr.source.json`\n- `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `generated/translation-diff-export/lang.diff.fr.reference.json`\n- `generated/translation-diff-export/lang.diff.fr.prompt.md`\n\nIf `-KeepIntermediate` is used, git-baseline snapshots are kept under `generated/translation-diff-export/tmp/`.\n\nThe smoke test writes its temporary artifacts under `generated/translation-diff-export-demo/`.\n\n## Validate The Export\n\nAfter export, confirm the patch is usable before handing it off:\n\n1. Check that `.source.json` is not empty unless you expected no work for that language.\n2. Confirm `.translated.json` is sparse and does not contain copied English placeholders for unfinished keys.\n3. If the patch should include recent English changes, rerun with `-BaseRef` and compare the resulting `.source.json`.\n4. Run the smoke test if you changed the export workflow itself.\n\n## Hand Off To Translate\n\nAfter exporting the patch, use the generated `.prompt.md` file with [translation-diff-translate](../translation-diff-translate/SKILL.md) to update the sparse translated working copy.\n\n## Hand Off To Import\n\nAfter translating the patch, merge it back into the full language file with [translation-diff-import](../translation-diff-import/SKILL.md):\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nKeep the `.translated.json` file sparse. If a key is not translated yet, leave it out instead of copying the English source value into the working copy.","frontmatter":{"name":"translation-diff-export","description":"Compares UniGetUI JSON locale files against English, identifies untranslated or source-changed keys, and generates patch, reference, and handoff files for a target language. Use when the user asks to export missing translations, untranslated strings, i18n diffs, or locale-file changes."},"isInternal":false,"tokens":933,"sizeBytes":4230},{"name":"SKILL.md","path":".agents/skills/translation-diff-import/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-diff-import/SKILL.md","title":"translation-diff-import","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-diff-import\ndescription: Merges translated key-value pairs from a UniGetUI JSON localization patch back into the full language file and validates the merged result. Use when the user asks to apply, merge, or import a translation patch.\n---\n\n# translation diff import\n\nUse this skill after the export skill produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to merge the translated working copy back into the full target-language JSON file.\n\nSee [translation-diff-export](../translation-diff-export/SKILL.md) for the patch-generation step that produces the expected inputs.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `cirup` available on `PATH`.\n\n## Scripts\n\n- `scripts/import-translation-diff.ps1`: Validates and merges a translated patch into the full language file.\n- `scripts/validate-language-file.ps1`: Validates placeholder, token, HTML-fragment, and newline parity for either a full language file or just the active patch keys against `lang_en.json`.\n\n## Usage\n\nImport the translated French working copy from the export skill into the full French JSON file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nValidate the merged output:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-import`)\n- `-AllowUnchangedValues`\n- `-KeepIntermediate`\n\nRecommended input mapping from the export skill:\n\n- `-TranslatedPatch`: `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `-SourcePatch`: `generated/translation-diff-export/lang.diff.fr.source.json`\n\n## Output\n\n- A merged `.json` file that contains both the previously translated entries and the imported patch values.\n- If `-KeepIntermediate` is used, patch snapshots are preserved under `generated/translation-diff-import/tmp/`.\n\n## Edge cases\n\n- If the target JSON file does not exist yet, the script can create an output from the translated patch alone.\n- When `-SourcePatch` is provided, the script validates translated keys, placeholder tokens, HTML-like fragments, newline counts, and likely untranslated values before delegating the merge to `cirup` while allowing missing keys for partial progress.\n- The import skill expects `.translated.json` to remain sparse. Untranslated keys should be omitted instead of copied in English unless you intentionally bypass that check with `-AllowUnchangedValues`.\n","frontmatter":{"name":"translation-diff-import","description":"Merges translated key-value pairs from a UniGetUI JSON localization patch back into the full language file and validates the merged result. Use when the user asks to apply, merge, or import a translation patch."},"isInternal":false,"tokens":645,"sizeBytes":2997},{"name":"SKILL.md","path":".agents/skills/translation-diff-translate/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-diff-translate/SKILL.md","title":"translation-diff-translate","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-diff-translate\ndescription: Translates a sparse UniGetUI JSON language patch, writes completed entries into the working copy, preserves placeholders and terminology, and prepares the patch for merge-back. Use when the user asks to translate or localize i18n strings, localization JSON, or language-file patches.\n---\n\n# translation diff translate\n\nUse this skill after `translation-diff-export` produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to update the sparse translated working copy.\n\nIt translates only the active patch keys, keeps `.translated.json` as the sole writable file, preserves placeholders and formatting, and hands the completed working copy to `translation-diff-import` for merge-back.\n\n## Execution Expectations\n\n1. Edit `.translated.json` directly. Do not create helper scripts, temporary reports, or coverage probes.\n2. Do not install packages or search for external translation services unless the user explicitly asks for automation.\n3. If `.translated.json` is empty, that is expected. Translate from `.source.json` into `.translated.json`.\n4. Use `.reference.json` only as terminology guidance, not as a reason to pause and analyze the rest of the repository.\n5. If the patch is too large to finish reasonably in one pass, report that constraint to the user instead of creating automation on your own.\n\n## Script\n\n- `scripts/write-translation-handoff.ps1`: Writes a small `.prompt.md` file that points an agent at this skill with the concrete patch paths created by the export skill.\n\n## Usage\n\nGenerate a handoff prompt for a translated French working copy:\n\n```powershell\npwsh ./.agents/skills/translation-diff-translate/scripts/write-translation-handoff.ps1 \\\n  -BaseName lang \\\n  -Language fr \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -ReferencePatch ./generated/translation-diff-export/lang.diff.fr.reference.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -MergedTargetJson ./src/Languages/lang_fr.merged.json \\\n  -ValidationScript ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -OutputPrompt ./generated/translation-diff-export/lang.diff.fr.prompt.md\n```\n\n## Translation Rules\n\n1. Do not edit `.source.json`.\n2. Write completed translations only into `.translated.json`.\n3. If a key is not translated yet, omit it from `.translated.json` instead of copying the English source value.\n4. Preserve key names exactly as-is.\n5. Preserve placeholders, named tokens, HTML-like fragments, escape sequences, and line breaks.\n6. Keep translated entries in the same key order as `.source.json` when adding new entries.\n7. Use `.reference.json` to match existing terminology and tone in the destination language.\n8. Do not introduce keys that are not present in the source patch.\n\n## After Translation\n\nMerge the sparse working copy back into the full destination-language file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```","frontmatter":{"name":"translation-diff-translate","description":"Translates a sparse UniGetUI JSON language patch, writes completed entries into the working copy, preserves placeholders and terminology, and prepares the patch for merge-back. Use when the user asks to translate or localize i18n strings, localization JSON, or language-file patches."},"isInternal":false,"tokens":808,"sizeBytes":3733},{"name":"SKILL.md","path":".agents/skills/translation-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-review/SKILL.md","title":"translation-review","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-review\ndescription: Reviews UniGetUI .json language files for localization quality, detects parity issues, English-equal entries, wrong-script content, and cross-language outliers, then generates a dataset for LLM-assisted linguistic review. Use when the user asks to review translations, audit localization, inspect i18n or l10n quality, or validate language files.\n---\n\n# translation review\n\nUse this skill when you need a quality review of an existing UniGetUI translation, not just coverage, but correctness, language integrity, and consistency against related languages.\n\nTypical triggers: review translations, check translation quality, mistranslations, wrong language in translation, untranslated strings, localization QA, translation audit, spot-check a language file.\n\n## Scope\n\n- Mechanically detect placeholder, HTML-fragment, and newline mismatches.\n- Flag entries whose translation still equals the English source in a non-English language file.\n- Detect wrong-script entries for non-Latin languages such as `ua`, `zh_CN`, `ar`, `ko`, `ja`, and `he`.\n- Build a cross-language comparison table so the reviewing agent can identify outliers, suspicious wording, and wrong-language content.\n- The agent performs the final linguistic review using the generated report.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/review-translation-json.ps1`: Generate the review dataset for one UniGetUI language JSON file.\n\n## Workflow\n\n1. Generate the review dataset for the target language.\n2. Confirm the output file exists and contains the expected flagged sections or JSON fields.\n3. If the script fails or the output is empty, validate the target JSON with `pwsh ./scripts/translation/Verify-Translations.ps1` and rerun the review.\n4. Read the flagged sections first: parity issues, English-equal entries, and wrong-script entries.\n5. Spot-check the cross-language comparison table, focusing on labels, buttons, settings text, and error messages.\n6. Report findings inline with the English source text, observed problem, and suggested correction.\n\n## Usage\n\nGenerate a Markdown review report for French:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nGenerate a report for Ukrainian and include script-detection checks:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_ua.json \\\n  -Language ua\n```\n\nGenerate a flagged-only JSON report with no cross-language table:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_de.json \\\n  -Language de \\\n  -OutputFormat Json \\\n  -FlaggedOnly\n```\n\nOptional parameters:\n\n- `-NeutralJson` — defaults to `src/Languages/lang_en.json`\n- `-ComparisonLanguages` — languages to include in the comparison table; defaults to other checked-in `lang_*.json` files\n- `-OutputPath` — defaults to `generated/translation-review/review.<lang>.md`\n- `-OutputFormat` — `Markdown` (default) or `Json`\n- `-FlaggedOnly` — skip the full cross-language table and emit only mechanically flagged entries\n\n## Output\n\nThe Markdown report is structured as:\n\n1. Summary\n2. Parity Issues\n3. English-Equal Entries\n4. Wrong Script\n5. Cross-Language Comparison\n\nDefault output path: `generated/translation-review/review.<lang>.md`\n\n## Agent Review Guidelines\n\n### Flagged sections\n\n- Parity issues: confirm whether the placeholder, tag, or newline mismatch is a genuine error or an acceptable localized variant.\n- English-equal entries: decide whether the English value is an acceptable technical or brand string, or whether it should be translated.\n- Wrong script: almost always an error for non-Latin languages. Verify against comparison languages and flag for re-translation when confirmed.\n\n### Cross-language comparison table\n\n- Sample at least 20–30 rows and prefer entries that look like UI labels, buttons, dialogs, settings descriptions, or error messages.\n- Look for wrong language, suspiciously different meaning, missing words, or copied English that should probably be translated.\n- Ignore obvious brand names and technical identifiers.\n\n### Output format for findings\n\nReport each issue in this format:\n\n```text\n**Source**: `Some English source text`\n**Problem**: [describe the issue]\n**Current value**: [the problematic translation]\n**Suggested correction**: [corrected value, if confident]\n**Confidence**: High / Medium / Low\n```\n\nGroup findings by type: parity, English-equal, wrong language, wrong script, or other.\n\n## Integration with Other Skills\n\n- Use [translation-status](../translation-status/SKILL.md) first if you need a coverage snapshot before reviewing quality.\n- Use [translation-diff-export](../translation-diff-export/SKILL.md) to produce a sparse patch for entries that need re-translation after review.\n- Use [translation-diff-import](../translation-diff-import/SKILL.md) to merge corrected entries back into the full language file.","frontmatter":{"name":"translation-review","description":"Reviews UniGetUI .json language files for localization quality, detects parity issues, English-equal entries, wrong-script content, and cross-language outliers, then generates a dataset for LLM-assisted linguistic review. Use when the user asks to review translations, audit localization, inspect i18n or l10n quality, or validate language files."},"isInternal":false,"tokens":1089,"sizeBytes":5098},{"name":"SKILL.md","path":".agents/skills/translation-source-sync/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-source-sync/SKILL.md","title":"translation-source-sync","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-source-sync\ndescription: Synchronizes the UniGetUI English language file with source-code usage, identifies missing translation keys, removes unused entries, reports localization drift, and can reorder locale files to match the English key ordering. Use when the user asks to sync language files, missing translations, i18n drift, or align locale file ordering with English.\n---\n\n# translation source sync\n\nUse this skill when UniGetUI source code changed and you need to keep [src/Languages/lang_en.json](src/Languages/lang_en.json) aligned with the strings actually used by the application, or when you need downstream locale files reordered to match the English key ordering.\n\nIt scans supported C#, WinUI XAML, and Avalonia AXAML patterns, finds missing English keys, removes unused entries, reports translation-source warnings that still need manual cleanup, and can align locale file ordering to the English layout.\n\n## Scope\n\n- Extract literal translation keys from supported UniGetUI source patterns.\n- Add new English keys missing from `lang_en.json`.\n- Remove English keys that are no longer used.\n- Reorder non-English locale files to match the English key ordering.\n- Warn about interpolated `CoreTools.Translate($\"...\")` calls that are not synchronized automatically.\n- Leave downstream language propagation to the existing translation diff workflow.\n\n## Supported extraction sources\n\n- `CoreTools.Translate(\"...\")` in C#.\n- `CoreTools.AutoTranslated(\"...\")` in C#.\n- `widgets:TranslatedTextBlock Text=\"...\"` in WinUI XAML.\n- `settings:TranslatedTextBlock Text=\"...\"` in Avalonia AXAML.\n- `{t:Translate Some text}` and `{t:Translate Text='A, B, C'}` in Avalonia AXAML.\n\n## Out of scope for v1\n\n- Interpolated translation calls such as `CoreTools.Translate($\"Running {value}\")`.\n- Non-literal translation inputs passed through variables.\n- CI enforcement.\n\n## Scripts\n\n- `scripts/sync-translation-sources.ps1`: Skill wrapper around the repository sync script.\n- `scripts/set-translation-boundary-order.ps1`: Skill wrapper around the repository locale reordering script.\n- `scripts/test-translation-source-sync.ps1`: Skill wrapper around the repository smoke test.\n- `../../../../scripts/translation/Sync-TranslationSources.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Set-TranslationBoundaryOrder.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Test-TranslationSourceSync.ps1`: Canonical smoke test used by the wrapper.\n\n## Usage\n\nSynchronize the checked-in English language file in place:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1\n```\n\nCheck whether the English file is out of sync without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1 -CheckOnly\n```\n\nReorder locale files to match the English key ordering:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1\n```\n\nPreview whether locale files need reordering without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1 -CheckOnly\n```\n\nRun the smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1\n```\n\n## Recommended workflow\n\n1. Run translation source sync after changing any translatable source string.\n2. If `-CheckOnly` reports drift, run the full sync to rewrite `lang_en.json`.\n3. Review warnings for interpolated translation calls and convert them to stable literals or add the missing English keys manually when needed.\n4. Run `pwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1` to align locale ordering with English.\n5. Run `pwsh ./scripts/translation/Verify-Translations.ps1` to confirm the language files still validate cleanly.\n6. Run `pwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1` if you changed the sync workflow itself.\n7. Export changed work for translators with [translation-diff-export](../translation-diff-export/SKILL.md).\n\n## Notes\n\n- Existing English values are preserved for retained keys; newly added English entries default to `key == value`.\n- The sync script preserves current key order for retained entries and appends newly discovered keys deterministically.\n- The locale reorder script follows English as the canonical order and appends unmapped locale-only keys at the end.\n- The smoke test uses a temporary synthetic repo so it does not mutate the checked-in language files.\n","frontmatter":{"name":"translation-source-sync","description":"Synchronizes the UniGetUI English language file with source-code usage, identifies missing translation keys, removes unused entries, reports localization drift, and can reorder locale files to match the English key ordering. Use when the user asks to sync language files, missing translations, i18n drift, or align locale file ordering with English."},"isInternal":false,"tokens":989,"sizeBytes":4683},{"name":"SKILL.md","path":".agents/skills/translation-status/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/.agents/skills/translation-status/SKILL.md","title":"translation-status","category":"anthropic-skill","format":"markdown","content":"---\nname: translation-status\ndescription: Analyzes checked-in UniGetUI language files to calculate translation percentages, surface untranslated coverage data, and generate localization status reports.\n---\n\n# translation status\n\nUse this skill when the user asks for UniGetUI translation progress, localization or i18n coverage, untranslated-language reports, or a language support status summary.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/get-translation-status.ps1`: Skill wrapper you should invoke from the skill.\n- `../../../../scripts/translation/Get-TranslationStatus.ps1`: Canonical repository implementation delegated to by the wrapper.\n\n## Usage\n\nShow the default table summary:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1\n```\n\nShow only incomplete languages as markdown:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Markdown \\\n  -OnlyIncomplete\n```\n\nWrite JSON output to a file:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Json \\\n  -OutputPath ./generated/translation-status.json\n```\n\n## Output Fields\n\n- `Code`: language code\n- `Language`: display name\n- `Completion`: computed completion percentage using English active keys only\n- `Translated`, `Missing`, `Empty`, `SourceEqual`, `Extra`: per-language entry counts\n- `Stored` and `Delta`: stored percentage metadata and difference from the computed result\n\n## Notes\n\n- The script treats source-equal values as untranslated for non-English languages.\n- Completion never reports `100%` unless every active English key is present and translated.\n- `Extra` counts locale-only keys not present in the English file.\n- Use `-IncludeEnglish` if you want the `en` row included in the report.\n- Use `-OnlyIncomplete` to focus on languages that still need work.\n- For the full parameter surface, inspect `../../../../scripts/translation/Get-TranslationStatus.ps1`.\n","frontmatter":{"name":"translation-status","description":"Analyzes checked-in UniGetUI language files to calculate translation percentages, surface untranslated coverage data, and generate localization status reports."},"isInternal":false,"tokens":429,"sizeBytes":2019},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Devolutions/UniGetUI/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n","isInternal":false,"tokens":4485,"sizeBytes":18316}],"systemPromptSnippet":"<yakaai_skills repo=\"Devolutions/UniGetUI\">\n<!-- File: AGENTS.md (Tokens: ~4485 | Category: root-instruction) -->\n# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n\n<!-- File: .agents/skills/translation-diff-export/SKILL.md (Tokens: ~933 | Category: anthropic-skill) -->\n---\nname: translation-diff-export\ndescription: Compares UniGetUI JSON locale files against English, identifies untranslated or source-changed keys, and generates patch, reference, and handoff files for a target language. Use when the user asks to export missing translations, untranslated strings, i18n diffs, or locale-file changes.\n---\n\n# translation diff export\n\nUse this skill when you need a small UniGetUI translation patch instead of sending a full language JSON file for review or translation.\n\nIt exports only the active untranslated or source-changed strings, creates the translator handoff files, and prepares the patch set needed by `translation-diff-translate` and `translation-diff-import`.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `git` available on `PATH`.\n- `cirup` available on `PATH`.\n\nWhen source strings changed, run `translation-source-sync` first so `lang_en.json` reflects current source usage before exporting downstream translation patches.\n\n## Scripts\n\n- `scripts/export-translation-diff.ps1`: Exports JSON patch artifacts and generates a translation handoff prompt.\n- `scripts/test-translation-diff.ps1`: Runs a local end-to-end smoke test against the checked-in UniGetUI language files.\n\nUse this skill's wrapper scripts first; the downstream translate and import steps are documented in [translation-diff-translate](../translation-diff-translate/SKILL.md) and [translation-diff-import](../translation-diff-import/SKILL.md).\n\n## Usage\n\nExport untranslated French strings only:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nExport untranslated French strings plus English source values changed since `origin/main`:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/export-translation-diff.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr \\\n  -BaseRef origin/main\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-export`)\n- `-KeepIntermediate`\n\nRun the built-in smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-diff-export/scripts/test-translation-diff.ps1\n```\n\n## Output\n\nFor `lang_en.json` and language `fr`, the script generates:\n\n- `generated/translation-diff-export/lang.diff.fr.source.json`\n- `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `generated/translation-diff-export/lang.diff.fr.reference.json`\n- `generated/translation-diff-export/lang.diff.fr.prompt.md`\n\nIf `-KeepIntermediate` is used, git-baseline snapshots are kept under `generated/translation-diff-export/tmp/`.\n\nThe smoke test writes its temporary artifacts under `generated/translation-diff-export-demo/`.\n\n## Validate The Export\n\nAfter export, confirm the patch is usable before handing it off:\n\n1. Check that `.source.json` is not empty unless you expected no work for that language.\n2. Confirm `.translated.json` is sparse and does not contain copied English placeholders for unfinished keys.\n3. If the patch should include recent English changes, rerun with `-BaseRef` and compare the resulting `.source.json`.\n4. Run the smoke test if you changed the export workflow itself.\n\n## Hand Off To Translate\n\nAfter exporting the patch, use the generated `.prompt.md` file with [translation-diff-translate](../translation-diff-translate/SKILL.md) to update the sparse translated working copy.\n\n## Hand Off To Import\n\nAfter translating the patch, merge it back into the full language file with [translation-diff-import](../translation-diff-import/SKILL.md):\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nKeep the `.translated.json` file sparse. If a key is not translated yet, leave it out instead of copying the English source value into the working copy.\n\n<!-- File: .agents/skills/translation-diff-import/SKILL.md (Tokens: ~645 | Category: anthropic-skill) -->\n---\nname: translation-diff-import\ndescription: Merges translated key-value pairs from a UniGetUI JSON localization patch back into the full language file and validates the merged result. Use when the user asks to apply, merge, or import a translation patch.\n---\n\n# translation diff import\n\nUse this skill after the export skill produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to merge the translated working copy back into the full target-language JSON file.\n\nSee [translation-diff-export](../translation-diff-export/SKILL.md) for the patch-generation step that produces the expected inputs.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n- `cirup` available on `PATH`.\n\n## Scripts\n\n- `scripts/import-translation-diff.ps1`: Validates and merges a translated patch into the full language file.\n- `scripts/validate-language-file.ps1`: Validates placeholder, token, HTML-fragment, and newline parity for either a full language file or just the active patch keys against `lang_en.json`.\n\n## Usage\n\nImport the translated French working copy from the export skill into the full French JSON file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n```\n\nValidate the merged output:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```\n\nOptional parameters:\n\n- `-OutputDir` (default: `generated/translation-diff-import`)\n- `-AllowUnchangedValues`\n- `-KeepIntermediate`\n\nRecommended input mapping from the export skill:\n\n- `-TranslatedPatch`: `generated/translation-diff-export/lang.diff.fr.translated.json`\n- `-SourcePatch`: `generated/translation-diff-export/lang.diff.fr.source.json`\n\n## Output\n\n- A merged `.json` file that contains both the previously translated entries and the imported patch values.\n- If `-KeepIntermediate` is used, patch snapshots are preserved under `generated/translation-diff-import/tmp/`.\n\n## Edge cases\n\n- If the target JSON file does not exist yet, the script can create an output from the translated patch alone.\n- When `-SourcePatch` is provided, the script validates translated keys, placeholder tokens, HTML-like fragments, newline counts, and likely untranslated values before delegating the merge to `cirup` while allowing missing keys for partial progress.\n- The import skill expects `.translated.json` to remain sparse. Untranslated keys should be omitted instead of copied in English unless you intentionally bypass that check with `-AllowUnchangedValues`.\n\n<!-- File: .agents/skills/translation-diff-translate/SKILL.md (Tokens: ~808 | Category: anthropic-skill) -->\n---\nname: translation-diff-translate\ndescription: Translates a sparse UniGetUI JSON language patch, writes completed entries into the working copy, preserves placeholders and terminology, and prepares the patch for merge-back. Use when the user asks to translate or localize i18n strings, localization JSON, or language-file patches.\n---\n\n# translation diff translate\n\nUse this skill after `translation-diff-export` produced a `.source.json`, `.translated.json`, and `.reference.json` set and you want to update the sparse translated working copy.\n\nIt translates only the active patch keys, keeps `.translated.json` as the sole writable file, preserves placeholders and formatting, and hands the completed working copy to `translation-diff-import` for merge-back.\n\n## Execution Expectations\n\n1. Edit `.translated.json` directly. Do not create helper scripts, temporary reports, or coverage probes.\n2. Do not install packages or search for external translation services unless the user explicitly asks for automation.\n3. If `.translated.json` is empty, that is expected. Translate from `.source.json` into `.translated.json`.\n4. Use `.reference.json` only as terminology guidance, not as a reason to pause and analyze the rest of the repository.\n5. If the patch is too large to finish reasonably in one pass, report that constraint to the user instead of creating automation on your own.\n\n## Script\n\n- `scripts/write-translation-handoff.ps1`: Writes a small `.prompt.md` file that points an agent at this skill with the concrete patch paths created by the export skill.\n\n## Usage\n\nGenerate a handoff prompt for a translated French working copy:\n\n```powershell\npwsh ./.agents/skills/translation-diff-translate/scripts/write-translation-handoff.ps1 \\\n  -BaseName lang \\\n  -Language fr \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -ReferencePatch ./generated/translation-diff-export/lang.diff.fr.reference.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -MergedTargetJson ./src/Languages/lang_fr.merged.json \\\n  -ValidationScript ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -OutputPrompt ./generated/translation-diff-export/lang.diff.fr.prompt.md\n```\n\n## Translation Rules\n\n1. Do not edit `.source.json`.\n2. Write completed translations only into `.translated.json`.\n3. If a key is not translated yet, omit it from `.translated.json` instead of copying the English source value.\n4. Preserve key names exactly as-is.\n5. Preserve placeholders, named tokens, HTML-like fragments, escape sequences, and line breaks.\n6. Keep translated entries in the same key order as `.source.json` when adding new entries.\n7. Use `.reference.json` to match existing terminology and tone in the destination language.\n8. Do not introduce keys that are not present in the source patch.\n\n## After Translation\n\nMerge the sparse working copy back into the full destination-language file:\n\n```powershell\npwsh ./.agents/skills/translation-diff-import/scripts/import-translation-diff.ps1 \\\n  -TranslatedPatch ./generated/translation-diff-export/lang.diff.fr.translated.json \\\n  -SourcePatch ./generated/translation-diff-export/lang.diff.fr.source.json \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -OutputJson ./src/Languages/lang_fr.merged.json\n\npwsh ./.agents/skills/translation-diff-import/scripts/validate-language-file.ps1 \\\n  -NeutralJson ./src/Languages/lang_en.json \\\n  -TargetJson ./src/Languages/lang_fr.merged.json \\\n  -PatchJson ./generated/translation-diff-export/lang.diff.fr.source.json\n```\n\n<!-- File: .agents/skills/translation-review/SKILL.md (Tokens: ~1089 | Category: anthropic-skill) -->\n---\nname: translation-review\ndescription: Reviews UniGetUI .json language files for localization quality, detects parity issues, English-equal entries, wrong-script content, and cross-language outliers, then generates a dataset for LLM-assisted linguistic review. Use when the user asks to review translations, audit localization, inspect i18n or l10n quality, or validate language files.\n---\n\n# translation review\n\nUse this skill when you need a quality review of an existing UniGetUI translation, not just coverage, but correctness, language integrity, and consistency against related languages.\n\nTypical triggers: review translations, check translation quality, mistranslations, wrong language in translation, untranslated strings, localization QA, translation audit, spot-check a language file.\n\n## Scope\n\n- Mechanically detect placeholder, HTML-fragment, and newline mismatches.\n- Flag entries whose translation still equals the English source in a non-English language file.\n- Detect wrong-script entries for non-Latin languages such as `ua`, `zh_CN`, `ar`, `ko`, `ja`, and `he`.\n- Build a cross-language comparison table so the reviewing agent can identify outliers, suspicious wording, and wrong-language content.\n- The agent performs the final linguistic review using the generated report.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/review-translation-json.ps1`: Generate the review dataset for one UniGetUI language JSON file.\n\n## Workflow\n\n1. Generate the review dataset for the target language.\n2. Confirm the output file exists and contains the expected flagged sections or JSON fields.\n3. If the script fails or the output is empty, validate the target JSON with `pwsh ./scripts/translation/Verify-Translations.ps1` and rerun the review.\n4. Read the flagged sections first: parity issues, English-equal entries, and wrong-script entries.\n5. Spot-check the cross-language comparison table, focusing on labels, buttons, settings text, and error messages.\n6. Report findings inline with the English source text, observed problem, and suggested correction.\n\n## Usage\n\nGenerate a Markdown review report for French:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_fr.json \\\n  -Language fr\n```\n\nGenerate a report for Ukrainian and include script-detection checks:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_ua.json \\\n  -Language ua\n```\n\nGenerate a flagged-only JSON report with no cross-language table:\n\n```powershell\npwsh ./.agents/skills/translation-review/scripts/review-translation-json.ps1 \\\n  -TargetJson ./src/Languages/lang_de.json \\\n  -Language de \\\n  -OutputFormat Json \\\n  -FlaggedOnly\n```\n\nOptional parameters:\n\n- `-NeutralJson` — defaults to `src/Languages/lang_en.json`\n- `-ComparisonLanguages` — languages to include in the comparison table; defaults to other checked-in `lang_*.json` files\n- `-OutputPath` — defaults to `generated/translation-review/review.<lang>.md`\n- `-OutputFormat` — `Markdown` (default) or `Json`\n- `-FlaggedOnly` — skip the full cross-language table and emit only mechanically flagged entries\n\n## Output\n\nThe Markdown report is structured as:\n\n1. Summary\n2. Parity Issues\n3. English-Equal Entries\n4. Wrong Script\n5. Cross-Language Comparison\n\nDefault output path: `generated/translation-review/review.<lang>.md`\n\n## Agent Review Guidelines\n\n### Flagged sections\n\n- Parity issues: confirm whether the placeholder, tag, or newline mismatch is a genuine error or an acceptable localized variant.\n- English-equal entries: decide whether the English value is an acceptable technical or brand string, or whether it should be translated.\n- Wrong script: almost always an error for non-Latin languages. Verify against comparison languages and flag for re-translation when confirmed.\n\n### Cross-language comparison table\n\n- Sample at least 20–30 rows and prefer entries that look like UI labels, buttons, dialogs, settings descriptions, or error messages.\n- Look for wrong language, suspiciously different meaning, missing words, or copied English that should probably be translated.\n- Ignore obvious brand names and technical identifiers.\n\n### Output format for findings\n\nReport each issue in this format:\n\n```text\n**Source**: `Some English source text`\n**Problem**: [describe the issue]\n**Current value**: [the problematic translation]\n**Suggested correction**: [corrected value, if confident]\n**Confidence**: High / Medium / Low\n```\n\nGroup findings by type: parity, English-equal, wrong language, wrong script, or other.\n\n## Integration with Other Skills\n\n- Use [translation-status](../translation-status/SKILL.md) first if you need a coverage snapshot before reviewing quality.\n- Use [translation-diff-export](../translation-diff-export/SKILL.md) to produce a sparse patch for entries that need re-translation after review.\n- Use [translation-diff-import](../translation-diff-import/SKILL.md) to merge corrected entries back into the full language file.\n\n<!-- File: .agents/skills/translation-source-sync/SKILL.md (Tokens: ~989 | Category: anthropic-skill) -->\n---\nname: translation-source-sync\ndescription: Synchronizes the UniGetUI English language file with source-code usage, identifies missing translation keys, removes unused entries, reports localization drift, and can reorder locale files to match the English key ordering. Use when the user asks to sync language files, missing translations, i18n drift, or align locale file ordering with English.\n---\n\n# translation source sync\n\nUse this skill when UniGetUI source code changed and you need to keep [src/Languages/lang_en.json](src/Languages/lang_en.json) aligned with the strings actually used by the application, or when you need downstream locale files reordered to match the English key ordering.\n\nIt scans supported C#, WinUI XAML, and Avalonia AXAML patterns, finds missing English keys, removes unused entries, reports translation-source warnings that still need manual cleanup, and can align locale file ordering to the English layout.\n\n## Scope\n\n- Extract literal translation keys from supported UniGetUI source patterns.\n- Add new English keys missing from `lang_en.json`.\n- Remove English keys that are no longer used.\n- Reorder non-English locale files to match the English key ordering.\n- Warn about interpolated `CoreTools.Translate($\"...\")` calls that are not synchronized automatically.\n- Leave downstream language propagation to the existing translation diff workflow.\n\n## Supported extraction sources\n\n- `CoreTools.Translate(\"...\")` in C#.\n- `CoreTools.AutoTranslated(\"...\")` in C#.\n- `widgets:TranslatedTextBlock Text=\"...\"` in WinUI XAML.\n- `settings:TranslatedTextBlock Text=\"...\"` in Avalonia AXAML.\n- `{t:Translate Some text}` and `{t:Translate Text='A, B, C'}` in Avalonia AXAML.\n\n## Out of scope for v1\n\n- Interpolated translation calls such as `CoreTools.Translate($\"Running {value}\")`.\n- Non-literal translation inputs passed through variables.\n- CI enforcement.\n\n## Scripts\n\n- `scripts/sync-translation-sources.ps1`: Skill wrapper around the repository sync script.\n- `scripts/set-translation-boundary-order.ps1`: Skill wrapper around the repository locale reordering script.\n- `scripts/test-translation-source-sync.ps1`: Skill wrapper around the repository smoke test.\n- `../../../../scripts/translation/Sync-TranslationSources.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Set-TranslationBoundaryOrder.ps1`: Canonical repository implementation used by the wrapper.\n- `../../../../scripts/translation/Test-TranslationSourceSync.ps1`: Canonical smoke test used by the wrapper.\n\n## Usage\n\nSynchronize the checked-in English language file in place:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1\n```\n\nCheck whether the English file is out of sync without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/sync-translation-sources.ps1 -CheckOnly\n```\n\nReorder locale files to match the English key ordering:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1\n```\n\nPreview whether locale files need reordering without writing changes:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1 -CheckOnly\n```\n\nRun the smoke test:\n\n```powershell\npwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1\n```\n\n## Recommended workflow\n\n1. Run translation source sync after changing any translatable source string.\n2. If `-CheckOnly` reports drift, run the full sync to rewrite `lang_en.json`.\n3. Review warnings for interpolated translation calls and convert them to stable literals or add the missing English keys manually when needed.\n4. Run `pwsh ./.agents/skills/translation-source-sync/scripts/set-translation-boundary-order.ps1` to align locale ordering with English.\n5. Run `pwsh ./scripts/translation/Verify-Translations.ps1` to confirm the language files still validate cleanly.\n6. Run `pwsh ./.agents/skills/translation-source-sync/scripts/test-translation-source-sync.ps1` if you changed the sync workflow itself.\n7. Export changed work for translators with [translation-diff-export](../translation-diff-export/SKILL.md).\n\n## Notes\n\n- Existing English values are preserved for retained keys; newly added English entries default to `key == value`.\n- The sync script preserves current key order for retained entries and appends newly discovered keys deterministically.\n- The locale reorder script follows English as the canonical order and appends unmapped locale-only keys at the end.\n- The smoke test uses a temporary synthetic repo so it does not mutate the checked-in language files.\n\n<!-- File: .agents/skills/translation-status/SKILL.md (Tokens: ~429 | Category: anthropic-skill) -->\n---\nname: translation-status\ndescription: Analyzes checked-in UniGetUI language files to calculate translation percentages, surface untranslated coverage data, and generate localization status reports.\n---\n\n# translation status\n\nUse this skill when the user asks for UniGetUI translation progress, localization or i18n coverage, untranslated-language reports, or a language support status summary.\n\n## Prerequisites\n\n- PowerShell 7 (`pwsh`).\n\n## Scripts\n\n- `scripts/get-translation-status.ps1`: Skill wrapper you should invoke from the skill.\n- `../../../../scripts/translation/Get-TranslationStatus.ps1`: Canonical repository implementation delegated to by the wrapper.\n\n## Usage\n\nShow the default table summary:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1\n```\n\nShow only incomplete languages as markdown:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Markdown \\\n  -OnlyIncomplete\n```\n\nWrite JSON output to a file:\n\n```powershell\npwsh ./.agents/skills/translation-status/scripts/get-translation-status.ps1 \\\n  -OutputFormat Json \\\n  -OutputPath ./generated/translation-status.json\n```\n\n## Output Fields\n\n- `Code`: language code\n- `Language`: display name\n- `Completion`: computed completion percentage using English active keys only\n- `Translated`, `Missing`, `Empty`, `SourceEqual`, `Extra`: per-language entry counts\n- `Stored` and `Delta`: stored percentage metadata and difference from the computed result\n\n## Notes\n\n- The script treats source-equal values as untranslated for non-English languages.\n- Completion never reports `100%` unless every active English key is present and translated.\n- `Extra` counts locale-only keys not present in the English file.\n- Use `-IncludeEnglish` if you want the `en` row included in the report.\n- Use `-OnlyIncomplete` to focus on languages that still need work.\n- For the full parameter surface, inspect `../../../../scripts/translation/Get-TranslationStatus.ps1`.\n\n<!-- File: CLAUDE.md (Tokens: ~4485 | Category: claude-rule) -->\n# UniGetUI - Copilot Instructions\n\n## Project Overview\n\nUniGetUI is an Avalonia desktop app (C#/.NET 10) providing a GUI for CLI package managers (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg).\n\nSolution entry points:\n- `src/UniGetUI.Windows.slnx` - official Windows solution; builds the Avalonia app and Windows-specific package-manager integrations\n- `src/UniGetUI.Avalonia.slnx` - cross-platform Avalonia solution\n\n## Architecture\n\nThe codebase follows a **layered, modular structure** with ~40 projects:\n\n- **`UniGetUI.Avalonia/`** - Avalonia entry point, AXAML pages, controls, and app shell (`Program.cs`, `Views/MainWindow.axaml`)\n- **`SharedAssets/`** - shared app icons, symbols, splash images, and utility scripts used by packaging\n- **`UniGetUI.Core.*`** - Shared infrastructure: `Logger`, `Settings`, `Tools` (includes `CoreTools.Translate()`), `IconEngine`, `LanguageEngine`\n- **`UniGetUI.PackageEngine.Interfaces`** - Contracts: `IPackageManager`, `IPackage`, `IManagerSource`, `IPackageDetails`\n- **`UniGetUI.PackageEngine.PackageManagerClasses`** - Base implementations: `PackageManager` (abstract), `Package`, helpers (`BasePkgDetailsHelper`, `BasePkgOperationHelper`, `BaseSourceHelper`)\n- **`UniGetUI.PackageEngine.Managers.*`** - Concrete manager implementations (one project per manager: `WinGet`, `Scoop`, `Chocolatey`, `Pip`, `npm`, etc.)\n- **`UniGetUI.PackageEngine.Operations`** - Install/update/uninstall operation orchestration\n- **`UniGetUI.Interface.*`** - Enums, telemetry, background API\n\n## Adding a New Package Manager\n\nEach manager extends `PackageManager` and must override three abstract methods:\n\n```csharp\nprotected override IReadOnlyList<Package> FindPackages_UnSafe(string query);\nprotected override IReadOnlyList<Package> GetAvailableUpdates_UnSafe();\nprotected override IReadOnlyList<Package> GetInstalledPackages_UnSafe();\n```\n\nEach manager also provides three helper classes (in a `Helpers/` subfolder):\n- `*PkgDetailsHelper` extends `BasePkgDetailsHelper` - overrides `GetDetails_UnSafe`, `GetInstallableVersions_UnSafe`, `GetIcon_UnSafe`, etc.\n- `*PkgOperationHelper` extends `BasePkgOperationHelper` - overrides `_getOperationParameters`, `_getOperationResult`\n- `*SourceHelper` extends `BaseSourceHelper` - overrides `GetSources_UnSafe`, `GetAddSourceParameters`, etc.\n\nThe constructor sets `Capabilities`, `Properties`, and wires the helpers. See `src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs` as a clean reference implementation.\n\n## NuGet-backed managers (.NET Tool, PowerShell, PowerShell 7, Chocolatey)\n\nThese four managers share `BaseNuGet` / `BaseNuGetDetailsHelper`, which talk to a NuGet feed\nover HTTP. Each source picks its protocol independently, in `NuGetV3ServiceIndex.GetServiceIndexUrl`:\n\n- A source URL with the `file` scheme - what `new Uri()` produces for a local folder or a UNC\n  share such as `C:\\packages` or `\\\\server\\share` - is a **local folder feed**, served by\n  `NuGetLocalFeed`. There is no HTTP endpoint to call, so search, details, icons, versions and\n  updates read the `.nupkg` files and their embedded `.nuspec` straight from disk. This check\n  runs before the V3 one, everywhere.\n- A source URL whose path ends in `index.json`, or whose last path segment is `v3`, is a\n  **NuGet V3** feed. Its service index is fetched once per session and cached.\n- Every other source URL is treated as a **V2/OData** feed and keeps the legacy code path.\n\nDetection is purely by URL shape, so it costs no probe request and no V2 feed changes behaviour.\n\nA local folder feed is scanned at most three directories deep, which covers both the flat layout\nand the `<id>/<version>/<id>.<version>.nupkg` layout, and each parsed manifest is cached against\nits file's size and write time. Installers on such a feed are copied from disk instead of being\ndownloaded (`DownloadOperation`), which refuses a destination that is the package file itself.\n\nEvery `.nupkg` in the folder is opened during a search, so its contents are treated as untrusted:\na manifest is rejected above 4 MiB (checked against the declared size *and* while decompressing,\nsince the declared one can lie) and parsed with DTD processing prohibited, and an embedded\n`<icon>` is extracted under the same bounds into the package's icon cache directory, named from\nthe package version, a digest of the archive's path, write time and size, and an allow-listed\nextension - never from the entry path, so a crafted entry name cannot escape that directory, and\ntwo feeds carrying the same id and version keep their own icons. Note that `GetIconLocal` re-reads\na replaced archive whichever way its timestamp moved, but `Package.GetIconUrlIfAny` caches the\nresolved address for the process against the package's versioned hash, so a package replaced in\nplace under the same version keeps showing the icon it had until a restart.\n\n### V3 resources used\n\n| Resource `@type` | Used for |\n|---|---|\n| `SearchQueryService` (3.5.0 → 3.0.0-beta → unversioned) | package search |\n| `PackageBaseAddress/3.0.0` | version enumeration, update detection, `.nupkg` and embedded-icon URLs |\n| `RegistrationsBaseUrl` (3.6.0 → 3.4.0 → 3.0.0-rc → 3.0.0-beta → unversioned) | package metadata, listed/unlisted checks |\n\nA feed needs `PackageBaseAddress` or `RegistrationsBaseUrl` to be usable. If it advertises no\n`SearchQueryService` — or its search returns nothing — search degrades to an exact\npackage-id lookup against the flat container. Flat-container version lists include unlisted\nversions, so an update candidate is confirmed against its registration leaf before being\nsurfaced.\n\n### Fallback behaviour\n\nThere is no V2 fallback for a URL that looks like a V3 service index: such a URL is not a\nvalid V2 root, so a failed or malformed service index is reported as an error rather than\nretried against a fabricated V2 endpoint. V2-only feeds never enter the V3 path at all.\n\n### Which feed is which\n\n- **.NET Tool** — `https://api.nuget.org/v3/index.json`. Safe to repoint because the manager\n  does not support custom sources and never passes the source URL to the `dotnet` CLI. Also\n  filters search on `packageType=DotnetTool`.\n- **PowerShell / PowerShell 7** — sources are enumerated from `Get-PSRepository` (or\n  `Get-PSResourceRepository` on PowerShell 7), so the URL is CLI-owned identity\n  (`PowerShellSourceHelper` compares it literally to choose `Register-PSRepository -Default`)\n  and must not be rewritten. Both helpers ask for an untruncated two-column table and read it by\n  column position (`ManagerTable`), because a repository name or path containing a space breaks\n  whitespace splitting and a long location is otherwise cut by `Format-Table`; a location that\n  is not an http URL - a folder or a UNC share - is kept rather than discarded. The PowerShell\n  Gallery serves no V3 service index, so it stays on V2; a V3-capable custom repository (Azure\n  Artifacts, GitHub Packages, JFrog, MyGet) is picked up automatically.\n- **Chocolatey** — sources come from `choco source list` and `community.chocolatey.org` serves no\n  `index.json`, so it stays on V2. Note that only its updates and version listing are CLI-driven;\n  its search, details and icons run through the shared `BaseNuGet` HTTP path.\n\nV3 has no `IsLatestVersion`, so the client picks \"latest\" itself using the comparator described\nin the next section.\n\n## Version comparison across ecosystems\n\n`CoreTools.VersionStringToStruct` treats `-`, `_`, `/` and `#` as plain numeric separators and\ndrops the letters around them. That is correct for ecosystems where a trailing suffix is a build\nor port revision, and wrong for ecosystems where it is a pre-release - the same string orders\nthe opposite way depending on the feed it came from:\n\n| String | Debian / Scoop / Homebrew / vcpkg | npm / crates.io / NuGet | PyPI |\n|---|---|---|---|\n| `1.0.0-1` | **newer** (revision) | **older** (pre-release) | **newer** (implicit post-release) |\n| `1.0.0rc1` | n/a | n/a | **older** (pre-release) |\n\nSo the comparison is per-manager, not global. `IPackageManager.CompareVersions(a, b)` returns the\nusual negative/zero/positive, or `null` when two versions cannot be meaningfully compared:\n\n- **Default** (`PackageManager.CompareVersions`) - the numeric comparison above. Used by Scoop,\n  WinGet, Apt, Dnf, Pacman, Homebrew, vcpkg, Snap and Flatpak.\n- **`SemanticVersion`** in `UniGetUI.Core.Tools` - SemVer 2.0 with NuGet's fourth numeric segment\n  allowed. Used by `BaseNuGet` (so Chocolatey, .NET Tool, Windows PowerShell and PowerShell 7),\n  npm, Bun and Cargo.\n- **`PythonVersion`** in `UniGetUI.Core.Tools` - PEP 440, matching Python's packaging library.\n  Used by Pip.\n\nEach override falls back to the base implementation when its own parser rejects the input, so an\nunparseable version behaves exactly as it did before.\n\nThree call sites consume it: `Package.NewerVersionIsInstalled()`, which decides whether an update\nis offered at all; `BaseNuGet.KeepUpdatesNewerThanInstalled`; and the\n`UninstallPreviousVersionsOnUpdate` match, which removes superseded copies. Display sorting\ndeliberately does not - those lists mix managers, and a comparison between two packages from\ndifferent ecosystems has no single correct answer.\n\nWhen adding a manager, pick the comparator its registry actually uses. Getting it wrong hides\nreal updates or offers downgrades, and the tests in `PackageTests.cs` assert both directions:\nthat SemVer and PEP 440 managers order pre-releases below their releases, and that revision\necosystems keep reading a trailing suffix as newer.\n\n## Build & Test\n\n```shell\n# Restore & test (from src/)\ndotnet restore UniGetUI.Windows.slnx\ndotnet test UniGetUI.Windows.slnx --verbosity q --nologo /p:Platform=x64\n\n# Publish release build\ndotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj /p:Configuration=Release /p:Platform=x64 -p:RuntimeIdentifier=win-x64\n```\n\n- Target framework: `net10.0-windows10.0.26100.0` (min `10.0.19041`)\n- Build generates secrets via `src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1` and integrity tree via `scripts/generate-integrity-tree.ps1`\n- Self-contained, publish-trimmed (partial), Windows App SDK self-contained\n- Tests use **xUnit** (`[Fact]`, `Assert.*`)\n\n### Formatting (required before every commit)\n\nThe PR check (`.github/workflows/dotnet-test.yml`) fails on any whitespace or code-style diff, even when the build and tests pass. After changing any C# file, apply both formatters from the repo root and commit the result:\n\n```shell\ndotnet format whitespace src --folder\ndotnet format style src/UniGetUI.Windows.slnx --no-restore\n```\n\nTo reproduce the CI gate exactly, add `--verify-no-changes --verbosity minimal` to both commands; they must exit with code 0. Run both — `whitespace` and `style` catch different issues, and passing one does not imply passing the other.\n\n## NativeAOT and Trim Safety\n\nRelease packages are self-contained, fully trimmed NativeAOT binaries on every supported RID. Treat NativeAOT safety as a non-negotiable production requirement: changes must work without runtime-generated code or metadata that the trimmer cannot prove is required.\n\n- Consider every `IL2xxx` trim warning and `IL3xxx` AOT warning a defect. Do not blanket-suppress warnings or use a broad `RequiresUnreferencedCode`, `RequiresDynamicCode`, `DynamicDependency`, `DynamicallyAccessedMembers`, linker descriptor, or root assembly as a workaround. An exception must be narrowly scoped, explain the concrete runtime requirement, and have a publish-path test that proves it is safe.\n- Use `JsonSerializerContext`/`JsonTypeInfo<T>` source-generated metadata for every production JSON serialization or deserialization path, including generic helpers, HTTP payloads, settings, IPC, telemetry, and persisted state. Do not call reflection-based serializer overloads or retain a reflection fallback for unknown application types. Add the closed type to a context and test the persisted or transported path.\n- Do not introduce production reflection-driven activation or discovery (`Assembly.Load`, `Type.GetType`, `Activator`, member lookup/invocation), runtime generic construction, expression compilation, runtime code generation, or dynamically loaded plugins. Prefer explicit registrations, direct constructors, generated metadata, and compiled XAML bindings. If a framework-owned dynamic boundary cannot be removed, isolate it and add published-NativeAOT runtime coverage.\n- New shipping COM interop must use source-generated interfaces and wrappers (`GeneratedComInterface`, `StrategyBasedComWrappers`) or raw ABI calls; do not add `ComImport`, `CoClass`, RCW activation, or legacy marshal-to-object APIs to a NativeAOT path. Define the ABI precisely, balance COM reference ownership, and test both x64 and arm64 where applicable.\n- Prefer `LibraryImport` for new P/Invokes. Existing `DllImport` declarations are acceptable only when their marshalling is supported by NativeAOT; keep platform guards, calling conventions, ownership, callback lifetimes, and x64/arm64 struct layouts explicit and covered.\n- Audit all new or changed startup, XAML/view-loading, dependency-injection, serialization, COM/WinRT, P/Invoke, and package-manager code against the publish output, not only a Debug build. Run the relevant NativeAOT publish profile, for example:\n\n  ```shell\n  dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --configuration Release --runtime win-x64 --self-contained true /p:Platform=x64 /p:PublishProfile=Win-x64-NativeAot\n  ```\n\n- When modifying a shared or portable path, retain the same safety guarantees for all checked-in NativeAOT publish profiles: `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`. Record unsupported runtime-matrix coverage as a gap, never as a pass.\n\n## Avalonia DevTools (Developer-Only)\n\nUse these rules when changing Avalonia diagnostics/devtools behavior:\n\n- Build-time switch is `EnableAvaloniaDiagnostics` in `src/Directory.Build.props`.\n- Default policy: enabled in `Debug`, disabled in `Release`.\n- `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` must condition `AvaloniaUI.DiagnosticsSupport` on `$(EnableAvaloniaDiagnostics)`.\n- Compile-time diagnostics code in `src/UniGetUI.Avalonia/Program.cs` must be gated by `#if AVALONIA_DIAGNOSTICS_ENABLED` (not `#if DEBUG`).\n- Runtime controls are developer-only and intentionally not listed in `docs/CLI.md`.\n- Runtime precedence in `Program.cs`: CLI flags > `UNIGETUI_AVALONIA_DEVTOOLS` environment variable > `Auto` default.\n- Accepted runtime env/CLI values for mode parsing: `auto`, `enabled`, `disabled`, `on`, `off`, `true`, `false`, `1`, `0`.\n- `Auto` mode must remain WSL-safe (DevTools disabled by default on WSL).\n- If diagnostics were excluded at build time, runtime toggle requests should log a no-op warning.\n\n## Key Patterns & Conventions\n\n### Settings\nFile-based settings via `Settings.Get(Settings.K.*)` / `Settings.Set(Settings.K.*, value)` and `Settings.GetValue(Settings.K.*)` / `Settings.SetValue(Settings.K.*, value)`. Setting keys are defined in the `Settings.K` enum in `SettingsEngine_Names.cs`. Boolean settings are stored as file existence; string settings as file content.\n\n### Logging\nUse `Logger.Info()`, `Logger.Warn()`, `Logger.Error()`, `Logger.Debug()`, `Logger.ImportantInfo()` from `UniGetUI.Core.Logging`. Accepts both `string` and `Exception` parameters.\n\n### Localization\nUse `CoreTools.Translate(\"text\")` for all user-facing strings. Parameterized: `CoreTools.Translate(\"{0} packages found\", count)`. In XAML, use the `TranslatedTextBlock` control. Translation assets live under `src/Languages/`; do not assume Tolgee-based automation exists in this repository.\n\n### Naming\n- Types, methods, properties: **PascalCase**\n- Private fields: `__doubleUnderscore` or `_singleUnderscore` prefix\n- Internal unsafe methods: suffix `_UnSafe` (e.g., `FindPackages_UnSafe`)\n- Nullable enabled globally; `LangVersion` is `latest`\n- Code style enforced in build (`EnforceCodeStyleInBuild=true`)\n\n### Manager conventions\n- `FALSE_PACKAGE_NAMES`, `FALSE_PACKAGE_IDS`, `FALSE_PACKAGE_VERSIONS` static arrays filter CLI parsing noise\n- Manager initialization flows through `Initialize()` -> `_loadManagerExecutableFile()` -> `_loadManagerVersion()` -> `_performExtraLoadingSteps()`\n- Operations that may fail return `OperationVeredict` (note: intentional misspelling used throughout codebase)\n\n## Terminal UI\n\n- `src/UniGetUI.Tui` is the Consolonia terminal front-end library, hosted by the main executable's `tui` command (`unigetui tui` or `uniget tui`). Pages must reach the engine through `TuiEngine` (never `PEInterface` directly), so that `--fake-data` can swap in the fake managers from `FakeData/`.\n- Run it with fake data: `dotnet run --project src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj -- tui --fake-data`. Nothing real is installed; all state lives in a sandbox.\n- Dialogs are in-window overlays (`Views/Dialogs/TuiModal`), not Avalonia popups. Popups capture input unreliably in Windows console hosts.\n- Colours come from the active theme: use `TuiPalette` brushes (`Theme/TuiPalette.cs`), never `Color.Parse` in a control, so themes and live theme switching reach it.\n- End-to-end tests in `src/UniGetUI.Tui.Tests/E2E` drive the real UI by keystrokes against the fake data set. Add a test for any new page action.\n\n## Key Files\n\n| Purpose | Path |\n|---|---|\n| Windows solution | `src/UniGetUI.Windows.slnx` |\n| Cross-platform solution | `src/UniGetUI.Avalonia.slnx` |\n| Shared build props | `src/Directory.Build.props` |\n| Version info | `src/SharedAssemblyInfo.cs` |\n| Manager interface | `src/UniGetUI.PackageEngine.Interfaces/IPackageManager.cs` |\n| Base manager class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs` |\n| Package class | `src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs` |\n| Settings engine | `src/UniGetUI.Core.Settings/SettingsEngine.cs` |\n| Setting keys | `src/UniGetUI.Core.Settings/SettingsEngine_Names.cs` |\n| Logger | `src/UniGetUI.Core.Logger/Logger.cs` |\n| CI test workflow | `.github/workflows/dotnet-test.yml` |\n| Terminal UI (Consolonia) | `src/UniGetUI.Tui/` — see `docs/TUI.md` |\n| Terminal UI tests (E2E, fake data) | `src/UniGetUI.Tui.Tests/` |\n</yakaai_skills>"}