Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: add winmd-api-search skill for Windows desktop API discovery#860

Open
yeelam-gordon wants to merge 1 commit intogithub:stagedfrom
yeelam-gordon:add-winmd-api-search-skill
Open

feat: add winmd-api-search skill for Windows desktop API discovery#860
yeelam-gordon wants to merge 1 commit intogithub:stagedfrom
yeelam-gordon:add-winmd-api-search-skill

Conversation

@yeelam-gordon
Copy link
Contributor

What this adds

A new skill winmd-api-search that helps GitHub Copilot find and explore Windows desktop APIs (WinRT / WinAppSDK).

What it does

  • Searches a local WinMD metadata cache to discover APIs for platform capabilities (camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc.)
  • Retrieves full type details: methods, properties, events, enumeration values
  • Works out-of-the-box for Windows Platform SDK and WinAppSDK no restore or build needed for baseline coverage
  • Supports additional NuGet packages after \dotnet restore\

Bundled assets

File Purpose
\scripts/Update-WinMdCache.ps1\ Generates the JSON cache from SDK and NuGet packages
\scripts/Invoke-WinMdQuery.ps1\ Searches types, members, enums, and namespaces
\scripts/cache-generator/\ .NET tool that parses WinMD files into JSON

Prerequisites

  • .NET SDK 8.0 or later
  • Windows (reads from local SDK install)

Checklist

  • Skill folder created with
    pm run skill:create\
  • SKILL.md has valid frontmatter (
    ame, \description)

  • pm run skill:validate\ passes

  • pm run build\ ran to update README tables
  • Targets \staged\ branch

Add a skill that helps find and explore Windows desktop APIs (WinRT/WinAppSDK).
It searches a local WinMD metadata cache to discover APIs for platform capabilities
like camera, file access, notifications, UI controls, AI/ML, sensors, and networking.

Includes bundled scripts:
- Update-WinMdCache.ps1: generates the JSON cache from SDK and NuGet packages
- Invoke-WinMdQuery.ps1: searches types, members, enums, and namespaces
- cache-generator: .NET tool that parses WinMD files into JSON

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings March 3, 2026 02:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new winmd-api-search skill to the skills catalog, enabling GitHub Copilot to discover Windows desktop APIs (WinRT / Windows App SDK) by querying a locally generated WinMD-to-JSON cache.

Changes:

  • Introduces a standalone .NET cache generator to parse WinMD files from Windows SDK, NuGet packages, and project outputs into a deduplicated per-package JSON cache.
  • Adds PowerShell scripts to generate the cache and query it (projects/packages/stats/namespaces/types/members/enums/search).
  • Documents the skill usage in SKILL.md and registers it in docs/README.skills.md.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
skills/winmd-api-search/scripts/cache-generator/Program.cs Implements WinMD parsing, package discovery, and per-package+version JSON cache generation.
skills/winmd-api-search/scripts/cache-generator/CacheGenerator.csproj Defines the standalone generator project and its NuGet dependencies.
skills/winmd-api-search/scripts/cache-generator/Directory.Build.props Isolates the generator from repo-level build props.
skills/winmd-api-search/scripts/cache-generator/Directory.Build.targets Isolates the generator from repo-level build targets.
skills/winmd-api-search/scripts/cache-generator/Directory.Packages.props Isolates the generator from repo-level central package management.
skills/winmd-api-search/scripts/Update-WinMdCache.ps1 Builds/runs the generator to produce or refresh the WinMD JSON cache.
skills/winmd-api-search/scripts/Invoke-WinMdQuery.ps1 Provides CLI-style querying over the generated cache (search, members, enums, etc.).
skills/winmd-api-search/SKILL.md Skill documentation and usage guidance for generating and querying the cache.
skills/winmd-api-search/LICENSE.txt Adds license text for the skill’s bundled assets.
docs/README.skills.md Registers the new skill and lists bundled assets.

exit 1
}

$ns = $FullName.Substring(0, $FullName.LastIndexOf('.'))
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as members action: this assumes the enum type name contains '.', and will throw if an unqualified name is provided. Add a guard and emit a friendly error prompting for a fully-qualified type name.

Suggested change
$ns = $FullName.Substring(0, $FullName.LastIndexOf('.'))
$lastDot = $FullName.LastIndexOf('.')
if ($lastDot -lt 0) {
Write-Error "Please provide a fully-qualified type name (e.g. 'Namespace.TypeName') for -TypeName when using the 'enums' action."
exit 1
}
$ns = $FullName.Substring(0, $lastDot)

Copilot uses AI. Check for mistakes.
}

foreach (var lib in libraries.EnumerateObject())
{
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When parsing project.assets.json, the "libraries" section includes non-package entries (e.g., libraries where "type" is "project"). Add a check for the library "type" and only treat entries with type == "package" as NuGet packages to avoid unnecessary disk scans and misleading IDs/versions.

Suggested change
{
{
// Only treat libraries with type == "package" as NuGet packages.
if (!lib.Value.TryGetProperty("type", out var typeProp) ||
!string.Equals(typeProp.GetString(), "package", StringComparison.OrdinalIgnoreCase))
{
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines +897 to +904

try
{
var propSig = prop.DecodeSignature(typeProvider, null);
var propType = propSig.ReturnType;
var accessors = prop.GetAccessors();
var hasGetter = !accessors.Getter.IsNil;
var hasSetter = !accessors.Setter.IsNil;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property parsing emits an entry even if both accessors are non-public. Consider checking the Getter/Setter method attributes and only including the property when at least one accessor method is public (consistent with method filtering).

Suggested change
try
{
var propSig = prop.DecodeSignature(typeProvider, null);
var propType = propSig.ReturnType;
var accessors = prop.GetAccessors();
var hasGetter = !accessors.Getter.IsNil;
var hasSetter = !accessors.Setter.IsNil;
var accessors = prop.GetAccessors();
var hasGetter = !accessors.Getter.IsNil;
var hasSetter = !accessors.Setter.IsNil;
// Align property visibility with method filtering:
// only include properties where at least one accessor is public.
var getterIsPublic = hasGetter &&
(reader.GetMethodDefinition(accessors.Getter).Attributes & MethodAttributes.Public) != 0;
var setterIsPublic = hasSetter &&
(reader.GetMethodDefinition(accessors.Setter).Attributes & MethodAttributes.Public) != 0;
if (!getterIsPublic && !setterIsPublic)
{
continue;
}
try
{
var propSig = prop.DecodeSignature(typeProvider, null);
var propType = propSig.ReturnType;

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +164
var projectsDir = Path.Combine(outputDir, "projects");
Directory.CreateDirectory(projectsDir);
File.WriteAllText(
Path.Combine(projectsDir, $"{projectName}.json"),
JsonSerializer.Serialize(manifest, jsonOptions));
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project manifests are written to "projects/.json" where projectName is just the file name without extension. In --scan mode, different projects with the same name in different directories will overwrite each other’s manifest. Consider including a unique suffix (e.g., relative path hash) in the manifest filename, while keeping ProjectName as a display field inside the JSON.

Copilot uses AI. Check for mistakes.
Comment on lines +935 to +939
var evt = reader.GetEventDefinition(eventHandle);
var evtName = reader.GetString(evt.Name);
var evtType = GetHandleTypeName(reader, evt.Type);

members.Add(new WinMdMemberInfo
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event parsing doesn’t currently validate accessor visibility. Consider checking the add/remove (and optionally raise) methods’ attributes and only including the event when at least one relevant accessor is public.

Copilot uses AI. Check for mistakes.
Comment on lines +327 to +330
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePath = $filePath }
}
$entry = $nsResults[$n]
if ($score -gt $entry.BestScore) { $entry.BestScore = $score }
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search groups results by namespace and stores a single FilePath for that namespace. If the same namespace appears in multiple packages, the stored FilePath may not contain the best-matching type(s) that contributed to the score. Consider tracking results per (package, namespace) or outputting multiple file paths with package id/version.

Suggested change
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePath = $filePath }
}
$entry = $nsResults[$n]
if ($score -gt $entry.BestScore) { $entry.BestScore = $score }
# Initialize with no file path; it will be set when we record the first best score.
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePath = $null }
}
$entry = $nsResults[$n]
if ($score -gt $entry.BestScore) {
# Update both the best score and the file path so FilePath always
# corresponds to the highest-scoring match for this namespace.
$entry.BestScore = $score
$entry.FilePath = $filePath
}

Copilot uses AI. Check for mistakes.
exit 1
}

$ns = $FullName.Substring(0, $FullName.LastIndexOf('.'))
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes the type name contains at least one '.' (namespace separator). If a user passes an unqualified type name, LastIndexOf('.') returns -1 and Substring will throw. Handle the no-namespace case with a clear error message instead of throwing.

Suggested change
$ns = $FullName.Substring(0, $FullName.LastIndexOf('.'))
$nsSeparatorIndex = $FullName.LastIndexOf('.')
if ($nsSeparatorIndex -lt 0) {
Write-Error "Type name must be fully qualified with a namespace (expected at least one '.'): $FullName"
exit 1
}
$ns = $FullName.Substring(0, $nsSeparatorIndex)

Copilot uses AI. Check for mistakes.
.EXAMPLE
.\Update-WinMdCache.ps1
.\Update-WinMdCache.ps1 -ProjectDir BlankWInUI
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example project name appears to have a typo: "BlankWInUI" should likely be "BlankWinUI" (capitalization). This is user-facing documentation in the comment help block.

Suggested change
.\Update-WinMdCache.ps1 -ProjectDir BlankWInUI
.\Update-WinMdCache.ps1 -ProjectDir BlankWinUI

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +21
<!-- System.Reflection.Metadata is inbox in net9.0+, only needed for net8.0 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="System.Reflection.Metadata" Version="9.*" />
</ItemGroup>

<!--
Baseline WinAppSDK packages: downloaded during restore so the cache generator
can always index WinAppSDK APIs, even if the target project hasn't been restored.
ExcludeAssets="all" means they're downloaded but don't affect this tool's build.
-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="*" ExcludeAssets="all" />
</ItemGroup>
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid floating NuGet versions here (System.Reflection.Metadata "9." and Microsoft.WindowsAppSDK "") because it makes restores non-deterministic and can break over time as new versions are released. Pin to specific, known-good versions (and ideally align System.Reflection.Metadata to the chosen target framework) so cache generation is reproducible.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@aaronpowell aaronpowell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small nit on the docs links

Comment on lines +186 to +187
- [Windows Platform SDK API reference](https://learn.microsoft.com/en-us/uwp/api/) — documentation for `Windows.*` namespaces
- [Windows App SDK API reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [Windows Platform SDK API reference](https://learn.microsoft.com/en-us/uwp/api/) — documentation for `Windows.*` namespaces
- [Windows App SDK API reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces
- [Windows Platform SDK API reference](https://learn.microsoft.com/uwp/api/) — documentation for `Windows.*` namespaces
- [Windows App SDK API reference](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces

Minor nit in there to use the non-localised URLs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants