Skip to content

feat: add winapp find-api — local Windows/WinRT API metadata search (port of winmd) - #744

Merged
Nikola Metulev (nmetulev) merged 107 commits into
mainfrom
jay/winmd-port
Sep 11, 2026
Merged

feat: add winapp find-api — local Windows/WinRT API metadata search (port of winmd)#744
Nikola Metulev (nmetulev) merged 107 commits into
mainfrom
jay/winmd-port

Conversation

@Jaylyn-Barbee

@Jaylyn-Barbee Jaylyn Barbee (Jaylyn-Barbee) commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

Adds winapp find-api — a fully local, offline search over the Windows/WinRT API surface a project can actually see, resolved from its own restored NuGet/SDK packages. It answers "does this type exist, what's on it, and did I spell this property right?" from the real metadata instead of from memory.

This is the port of the winmd tool out of microsoft/win-dev-skills (src/tools/winmd-cli/) and into winapp, per #652 — but not a 1:1 copy. The query engine (metadata parsing, XML-doc extraction, scoring, near-miss suggestions) came over largely intact; the surface around it was reworked to fit winapp conventions and to fix real defects found along the way.

Why it matters to users: the answer is grounded in your project's referenced metadata, so it's correct for your package versions rather than for whatever version the model was trained on. It also fails loudly — check-property exits non-zero on a miss, so it can gate a step instead of just printing advice. And it works before a project exists (--project sdk), so it's useful during scaffolding, not only after restore.

Usage Example

# Search the API surface your project can see
winapp find-api "acrylic brush"
winapp find-api NavigationView --max 10

# Inspect a type; filter down long member lists
winapp find-api members Microsoft.UI.Xaml.Controls.TabView
winapp find-api members Microsoft.UI.Xaml.Window --filter Appear

# Validate before you write code — exits non-zero if a property doesn't exist
winapp find-api check-property TextBox Icon

# Batch: many subjects in one call
winapp find-api check-property TabView TabItems SelectedItem IsAddTabButtonVisible
winapp find-api members TabView NavigationView CommandBar
winapp find-api "acrylic" "mica" "backdrop"

# No project? Query the machine-wide Windows SDK scope
winapp find-api "app notification" --project sdk

# Structured output for agents
winapp find-api enums Microsoft.UI.Xaml.Controls.Symbol --json

Related Issue

Closes #652
Closes #774
Closes #796

Type of Change

  • ✨ New feature

Checklist

  • New tests added for new functionality (if applicable)
  • Tested locally on Windows
  • Main README.md updated (if applicable)
  • docs/usage.md updated (if CLI commands changed)
  • Language-specific guides updated (if applicable) — n/a
  • Sample projects updated to reflect changes (if applicable) — n/a
  • Agent skill templates updated — note these now live in plugins/winapp/skills/ (winapp-find-api/SKILL.md) and plugins/winapp/agents/, not docs/fragments/skills/

Additional Notes

What's new versus the winmd tool this ports from. The query core is at parity by design; these are the deltas worth reviewing:

  • Batched lookups. search, members, enums, and check-property each accept several subjects in one call. This is the biggest change in how the command gets used — five property checks go from five calls to one, and the combined output is ~60% smaller than the five separate responses. Two API contracts here are worth explicit scrutiny because they're hard to change later: (1) a single subject returns the original payload shape in both text and --json — the { count, results } envelope only appears for two or more subjects, so existing single-subject callers are unaffected; (2) a batch exits 0 only if every subject resolved and was found. check-property batches N properties against one type (type first) — the dotted Type.Property multi-type form was considered and rejected as too implicit.
  • Machine-wide SDK scope (--project sdk). The original could only answer against an indexed project. Previously, a projectless query would get silently answered from some unrelated indexed project — that's now impossible; you either get the SDK scope or an explicit error.
  • Result attribution. Every scoped result reports which index answered it (Project: <name> (<dir>) in text, scope/projectName/projectDir in --json). Without this you can't tell a wrong-project answer from a right one.
  • Path-keyed project manifests. Two projects with the same name in one solution used to collide in the cache and return each other's results. Fixed.
  • --filter on members/enums. The original had --filter for namespace prefixes only. Guidance is deliberately asymmetric: filter long member lists, dump enums whole — a filtered enum lookup usually costs more than just reading all the values.
  • Parallelized winmd indexing, which the original did not do at all.
  • Three verbs shipped hidden (types, namespaces, projects). Still fully callable, just not surfaced in help or the CLI schema — they overlap with search/packages and were adding noise to the command surface without earning it.
  • Compact check-property batch output — one line per confirmed property, full detail (near-miss suggestions, attached-property forms, other declaring types) only on a miss, which is the case where you actually need it.

Scoped out, tracked separately. Search and ranking quality are unchanged from the ported implementation — this PR does not claim to find APIs better than winmd did, only to make it cheaper to call, honest about which index answered, and usable outside a project. A check-file verb — validate every API reference in a file you just wrote, before building — is filed as #743.

Testing. 319/319 targeted find-api, CLI-schema, and help tests pass. Beyond unit tests, reference resolution and property reporting are verified end to end against real dotnet new/dotnet build fixtures — a multi-project solution (App -> Middle -> Leaf), a project with a System.* PackageReference, and a library with init, set, and get-only properties — by running the built CLI and asserting on its --json output. End-to-end verified against samples/winui-app.

Note for reviewers on diff size. This branch contains the initial port as well as the changes above, so the diff is large. The files that carry the design decisions are Commands/FindApiShared.cs, Commands/FindApiVerbs.cs, and plugins/winapp/skills/winapp-find-api/SKILL.md.

Follow-up for #652. Once this lands, the old winmd references in microsoft/win-dev-skills (src/tools/winmd-cli/ plus the skills/docs pointing at it) should be removed so there's no duplicated implementation across the two repos.

Port the standalone winmd API-metadata search tool into the CLI as a
first-class `winapp find-api` command group (issue #626), mirroring the
find-ui port structurally. The bare form searches; sub-verbs (members,
check-property, types, enums, namespaces, packages, stats, projects,
refresh) drill in. The index is built from a project's restored packages
under the global .winapp cache and auto-refreshes when project.assets.json
changes.

- ApiSearch engine (read side + cache builder) re-namespaced to
  WinApp.Cli.Services.ApiSearch; AOT-safe via System.Reflection.Metadata
  and source-gen JSON.
- ApiMetadataService: cache-dir resolution, lazy auto-indexing with a
  file lock, and --project/--project-dir manifest resolution.
- Command surface + shared emit/render plumbing; bounded, non-PII usage
  telemetry (FindApiUsageEvent).
- DI wiring, JSON output models, root command "API Discovery" category.
- Tests: engine (synthetic cache incl. global-namespace regression),
  service resolution, command routing/exit codes, telemetry.
- Docs: usage.md, README, hand-written skill fragment; regenerated
  cli-schema.json and agent skills.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b442383b-d39a-4a3d-b08e-67ffed6abf50
… packaging)

Security:
- Invoke WinAppSDK runtime detection via absolute Windows PowerShell path
  instead of bare "powershell.exe" (binary-planting guard).
- Add ApiCachePaths with namespace-filename sanitization and a cache-path
  containment check so untrusted namespaces / package Id/Version can't
  traverse outside the cache directory.

Correctness:
- check-property: only a Property counts as "found" (a like-named method or
  event no longer reports a false positive).
- Members/enums: resolve short type names via ResolveType, matching the
  advertised help and check-property behavior.
- ResolveManifest: an explicit --project-dir with no match reports
  "not indexed" instead of silently answering for a lone cached project.
- RunIndexWithLock: narrow the IOException contention catch to lock
  acquisition only so real indexing I/O errors aren't misreported (and don't
  trigger a needless 30s wait).
- refresh now forces a full rebuild (bypasses reused caches); project
  references (version "local") are always re-exported.

CLI UX:
- refresh progress respects --quiet.
- refresh honors --project (refreshes the named project's recorded dir).

Docs / packaging:
- Add find-api to the hand-written agent command reference (+ .claude mirror).
- generate-commands.mjs now emits invokable branch commands, so the bare
  `find-api <query>` wrapper is generated; regenerated winapp-commands.ts
  and docs/npm-usage.md.

Tests: add regressions for the property-kind collision, explicit
--project-dir no-match, short-name members/enums, and refresh force/--project.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b442383b-d39a-4a3d-b08e-67ffed6abf50
…layout

Brings winmd-port up to date with main (18 commits) and migrates the
find-api plugin skill from the removed fragment-based layout to main's
hand-authored per-skill layout:

- Add plugins/winapp/skills/winapp-find-api/SKILL.md (no version frontmatter,
  hand-authored CLI reference + related-skills cross-links)
- Remove old-layout .claude/, .github/plugin/skills/winapp-cli/, and
  docs/fragments/skills/ find-api files (removed on main)
- find-api agent section preserved via main's agent-file rename
- Regenerate cli-schema.json, winapp-commands.ts, npm-usage.md from the
  merged schema (find-api + embed-identity)
- Take main's generate-llm-docs.ps1 (fragment skill generator removed)

Validated: solution builds, 41 find-api tests pass, 230 npm tests pass,
TS lint/format/compile clean, generate-commands/generate-docs --check in sync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a38f2203-35c1-4b34-ae9e-d3a46627705a
…dexing

Resolves the find-api/find-ui merge and consolidates the two discovery
commands where they had drifted apart.

Merge resolutions (kept both features):
- Root command registers find-api and find-ui under one "Discovery" help group.
- WinAppJsonContext registers both find-api and find-ui JSON output models.
- README / agent doc list both under a single "Discovery" heading.
- Regenerated cli-schema.json, npm-usage.md, and winapp-commands.ts.

Parallelize winmd indexing (find-api refresh):
- Resolve every project first, dedupe packages by cache directory, then export
  the distinct packages in parallel, so a package shared by several projects is
  parsed once per run instead of once per project.
- Parallelize .winmd parsing, XML-doc parsing, and per-namespace cache writes
  within a package. Results are reassembled in file order, so output stays
  byte-identical to the sequential build (verified against a full sample index).
- Serialize the progress callback; the console sink behind it is not thread-safe.
- Write package meta.json (the reuse sentinel) and project manifests last, so an
  interrupted run never advertises a cache whose payload is missing.
- ~1.8x faster on a 9-package / 127-winmd WinUI project (4.34s -> 2.46s).

Consolidation between find-api and find-ui:
- Program.cs: the parse-error -> flat {"error":...} JSON bridge covered find-ui
  only, so `find-api --max abc --json` printed human help text. Generalized to
  both discovery commands (IsFlatJsonErrorCommand / EmitFlatJsonError).
- ApiCacheBuilder's private atomic-write helper now delegates to the shared
  PathSafety helper (new sync AtomicWriteAllText next to the async one).
- docs/usage.md: find-api moved next to find-ui, with reciprocal "Related" notes
  so the API-surface vs WinUI-sample vs running-app distinction is explicit.
- docs/telemetry.md documents the find-api usage event alongside find-ui.
- find-ui skill description now disambiguates itself from find-api.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
…eries from an unrelated project

A `find-api` query from a directory with no project could be silently answered
from whichever project happened to be the only one in the global cache, because
`ResolveManifest` had a "lone cached project" fallback and the cache is global
(~/.winapp/cache/find-api/). The answer therefore depended on unrelated global
state: with one project cached the query returned confident results from the
wrong project; with two it errored instead. No output model except `stats`
carried the project name, so `--json` consumers could not detect the swap.

Replace that fallback with an explicit machine-wide SDK scope. The Windows SDK
and Windows App SDK metadata is already project-independent (NuGetResolver's
SDK lookups never took a projectDir), so it can back a real scope of its own.

Resolution precedence is now:
  --project <name> (or `sdk`) -> --project-dir -> project in cwd -> SDK scope

A projectless query never consults the cached project list, so results cannot
depend on unrelated global state. A --project-dir pointing at a real but
unindexed project still errors rather than narrowing to the SDK, since
narrowing would hide that project's NuGet packages and make its types look
nonexistent.

Every scoped payload now carries a `scope` field (`project` or `sdk`), stamped
centrally in `WithManifest`, and text mode prints a note when the SDK scope
answered. The SDK manifest is written to `sdk.json` as a sibling of `projects/`
so it can never collide with a real project manifest or show up in
`find-api projects`.

Also extracts `NuGetResolver.FindSdkPackages` and
`ApiCacheBuilder.ResolvePackageExports` to share logic with the new
`BuildSdkCache`, and adds an `ISdkPackageSource` seam so tests do not depend on
the machine's installed SDK.

Known gap: the SDK index has no staleness check, so a newly installed Windows
SDK needs `find-api refresh --project sdk`. Project scopes self-refresh off
project.assets.json timestamps; there is no cheap equivalent signal for the SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
…ollide

Manifests were written as <projectName>.json with no path component unless
--scan was passed, so two projects with the same name in different directories
overwrote each other in the shared cache. Resolution then compounded it: the
--project-dir branch verified manifest.ProjectDir, but the current-directory
branch, the --project-dir name fallback, and AutoIndexIfStale all matched on
file name alone. A query could therefore be answered from a different project's
index -- wrong package set, presented as authoritative -- and AutoIndexIfStale
would treat the foreign manifest as "found" and skip re-indexing the project
actually being queried.

This is easy to hit in practice: monorepos, and any workflow that scaffolds the
same template repeatedly. It was observed in a benchmark sweep where several
trials built an identically-named app in different directories and one trial's
manifest ended up pointing at another trial's directory.

- ApiCacheBuilder.ManifestName() now always appends a short hash of the
  project's full path, so the cache key is unique per project location. --scan
  no longer changes naming; it only affects which projects are discovered.
- ApiMetadataService resolves manifests by their recorded ProjectDir rather
  than by file name, via a shared FindManifestPathForDir helper used by the
  current-directory branch, the --project-dir branch, and AutoIndexIfStale.
  Matching on ProjectDir also keeps legacy unhashed manifests resolvable.
- The --project-dir name fallback is removed outright: if no manifest claims
  that directory, the project is not indexed, and saying so is correct.
- --project <name> now collapses candidates by ProjectDir and reports an
  ambiguity error listing the directories when several indexed projects share
  a name, instead of silently returning whichever was enumerated first.

Adds four regression tests, including the same-name-different-directory case
for both cwd and --project-dir resolution. Existing tests that gave a manifest
a ProjectDir one level below the current directory were corrected to record the
directory that actually holds the project, which is what indexing writes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
…rs/enums

Three changes driven by an A/B benchmark of find-api against a baseline agent.

Report which index answered. Only `packages` and `stats` carried a project name,
and nothing carried a project directory, so a caller could see `scope: project`
but not *which* project produced the result. Auditing the benchmark run had to
infer this from cache-file timestamps. Every scoped payload now reports
`projectName` and `projectDir`, stamped at the single existing scope-stamp site.
Project names are not unique across directories, so the directory is the only
reliable identity.

Add `--filter` to `members` and `enums`. A benchmark trial ran `enums Symbol`
seven times, grepping the 197-value dump differently each time, because the tool
offered no way to narrow it. `--filter` is a case-insensitive substring match on
the member/value name and reports the unfiltered totals alongside the narrowed
list, so a filtered view is never mistaken for a small API. A filter that matches
nothing still exits 0 -- that is "nothing matched", not "no such type".

Update the skill to use find-api on compile errors. The benchmark showed 13 of 21
type/member errors were fixed by editing blind with no lookup, even though the
tool was available and the errors were exactly claims about the API surface. The
skill now maps CS0246/CS0117/CS1061/CS0104/XAML-unknown-member to the query that
answers each, directs callers to filter rather than dump-and-grep, and states the
case for check-property more firmly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Benchmark iterations 1 and 2 showed find-api's marginal cost is dominated
by the conversation context re-sent on every turn, not by payload size.
The lever that actually works is issuing fewer calls, so this makes the
common verbs batchable and stops optimizing for bytes.

Batching. search, members, enums, and check-property now take multiple
subjects per invocation. One subject returns the exact payload it always
did (text and --json) so nothing existing breaks; two or more return a
{ count, results } envelope, with missingCount added for check-property.
check-property batches properties on one type -- type first, then every
property -- which covers the dominant case unambiguously. A batch exits 0
only when every subject resolved and was found, so batching can never
silently hide a miss. Verifying five properties goes from five turns and
775 chars to one turn and 299.

Attribution in text mode. --json was used once in twelve trials, so the
scope/projectName/projectDir work landed in a payload almost nobody read.
Every scoped verb now prints its source in text mode too.

check-property weight. 21 of 23 checks came back found:true, so the full
near-miss block was paid for on every call and used by almost none. In
batch mode a hit is one line; the full suggestion detail still prints on
a miss, where it is what resolves the question.

--filter guidance. 16 filtered enum calls cost ~3,434 tokens against ~582
for a single unfiltered dump -- 5.9x worse. Guidance now differentiates:
filter large member lists, dump enums whole, and never re-run the same
command with different filter text.

types, namespaces, and projects are hidden (not removed) after zero
invocations across 48 trial-runs. They still work when called explicitly.

Adds 15 tests covering batch fan-out, single-subject back-compat, batch
exit codes, JSON envelope shape, and verb visibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
…ol at a time

Run5 measured 57% of find-api calls as single-subject (median 1), despite
an overall mean of 3.36 subjects per call. The large batches come from
agents front-loading before writing code; the reactive debug loop was
still one lookup per symbol.

Adds an explicit trigger for the second batching moment: read the whole
build error list, collect every uncertain symbol across all of it, and
verify in one call before editing. Fixing errors one at a time costs a
lookup, an edit, and a full rebuild per symbol, and each rebuild tends to
surface the next bad symbol that the same call could have caught.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Brings in `winapp new` (#686), build-tool signature verification, and the
other 15 commits on main since this branch diverged.

Two conflicts:

- Program.cs — main added a parse-error JSON bridge for `winapp new`, while
  this branch had generalized the find-ui bridge's `IsFindUi` predicate into
  `IsFlatJsonErrorCommand` so it also covers find-api. Both are wanted, so
  main's `new` block is kept ahead of the generalized discovery bridge.
- docs/cli-schema.json — generated file. Regenerated from the merged build
  rather than hand-resolved; verified a strict superset of main's schema
  (nothing dropped, 7 find-api entries added).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
A local Debug build self-reports 1.0.0, so regenerating the schema off one
wrote that placeholder into the committed file. validate-llm-docs.ps1
normalizes the fresh schema's version to version.json (0.6.1) before
comparing, so the committed 1.0.0 read as drift and failed validate-docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 44.51 MB 46.13 MB 📈 +1.62 MB (+3.64%)
CLI (x64) 44.47 MB 46.04 MB 📈 +1.56 MB (+3.52%)
MSIX (ARM64) 18.41 MB 19.05 MB 📈 +657.1 KB (+3.49%)
MSIX (x64) 19.51 MB 20.19 MB 📈 +687.7 KB (+3.44%)
NPM Package 38.30 MB 39.63 MB 📈 +1.33 MB (+3.48%)
NuGet Package 38.43 MB 39.76 MB 📈 +1.33 MB (+3.47%)

Test Results

5804 passed, 18 skipped out of 5822 tests in 1025.7s (+396 tests, +262.4s vs. baseline)

Test Coverage

87.3% line coverage, 80.7% branch coverage · ⚠️ -0.9% vs. baseline

CLI Startup Time

63ms median (x64, winapp --version) · ✅ no change vs. baseline

Try This Build

Installs the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing.

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 744
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 744

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-09-11 02:28:34 UTC · commit b293d3a · workflow run

The Release build treats warnings as errors, so the eight CollectionAssert
call sites passing inline \
ew[] { ... }\ literals failed CI's build and
build-and-package jobs. Debug only surfaced them as warnings, which is why
they were missed locally.

Follows the existing convention in the test project (ControlsFetchNoticeTests,
MsBuildPropertyReaderTests, RunCommandTests, and others).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Search printed the absolute on-disk cache path for every namespace hit. It is
a debugging aid rather than an answer, and it was 35-41%% of the response on
queries where it appeared -- cost paid on every call, multiplied by batching.

Default output is now 41-47%% smaller on those queries; --verbose still shows
the paths for diagnosing a stale or unexpected index.

The fake metadata service returned an empty file list, so no existing test
could have caught this; it now returns a path and two tests pin the behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Converts imperative accumulate-into-list loops into equivalent LINQ
projections where the transform is a straightforward filter/map:

- Scoring.allTermsMatch -> terms.All(...)
- NuGetResolver.IsFrameworkPackage -> prefixes.Any(...)
- NuGetResolver compile-entry, dll-path, and xmlDoc collection -> Select/Where/SelectMany
- WinMdParser.ParseEnumValues and GetMethodParameters -> Select/Where
- ApiQueryEngine namespace merge -> HashSet.UnionWith

Behavior is unchanged; short-circuiting is preserved in the All/Any cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81234fca-8857-4447-805a-1971ff162b67
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
@Jaylyn-Barbee

Copy link
Copy Markdown
Contributor Author

Re: the Generic catch clause findings

Reviewed all 10 remaining bot findings individually. Each is an intentional graceful-degradation path, not an oversight, so I'm resolving them rather than narrowing the catches.

The design principle: find-api reads a local, regenerable metadata cache. A corrupt, partially-written, or concurrently-rewritten cache file must degrade the result, never crash the CLI. Narrowing these to IOException/JsonException would convert current graceful degradation into hard failures on the exceptions those sites can genuinely see (UnauthorizedAccessException, NotSupportedException, InvalidOperationException from JsonSerializer, etc.).

Site Behavior on failure
PathSafety.cs:240 Best-effort temp cleanup, then throw; — the original error is preserved and rethrown. Swallowing a cleanup failure here is required to avoid masking the real one.
ApiCacheBuilder.cs:334 Atomic write fails -> falls back to a plain write.
ApiCacheBuilder.cs:438 SDK-discovery subprocess fails -> returns null, caller handles it.
ApiMetadataService.cs:266 Already catch (Exception ex) with LogWarning; auto-indexing is explicitly best-effort.
ApiMetadataService.cs:428 Already catch (Exception ex) with LogWarning, returns ResolvedScope.Failed(...).
ApiMetadataService.cs:446 Corrupt manifest -> null (treated as "no cache", triggers re-index).
ApiQueryEngine.cs:330 Unreadable package meta -> reports that package as meta-unreadable instead of failing the whole listing.
ApiQueryEngine.cs:361 Stats aggregation skips an unreadable package rather than aborting.
ApiQueryEngine.cs:636, 648 Deserialize -> null on a corrupt cache file.

Note that ApiMetadataService.cs:266 and :428 already catch a typed Exception and log it — the rule flags them regardless of the logging.

The LINQ findings from the same review were addressed in 88c025c (8 of 11). Three were declined on merit and called out there: Scoring.IsFuzzySubsequence is a stateful sequential scan with an early return (not a projection), and ApiQueryEngine.cs:245/543 use a side-effecting seen.Add(...) predicate that would depend on mutation during lazy enumeration.

Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiCacheBuilder.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs Fixed
… twins

Search() walked every package cache and scored each type it found, with no
dedupe -- unlike LoadAllTypes(), which every other verb goes through and which
has always kept a `seen` set. Because a type routinely ships in more than one
package in the same graph (Microsoft.UI.Xaml.Controls.Button is in both
WinAppSdkRuntime and Microsoft.WindowsAppSDK.WinUI), the same fully-qualified
name was emitted once per package.

That produced two user-visible defects:

  - Ambiguity candidate lists repeated the same fully-qualified name, so the
    accompanying "use the fully-qualified name to disambiguate" advice could not
    actually disambiguate them. On a WinUI project, 44% of the candidates
    reported for "NavigationView" were exact duplicates.
  - Per-namespace Matches is capped with .Take(5), so duplicates crowded genuine
    results out of the output entirely.

Also excludes CsWinRT's generated ABI.* projection twins from search. They are
marshalling internals nobody writes code against, and since "ABI.Microsoft.UI.
Xaml.Controls" counts as a distinct namespace prefix, they could fabricate a
CS0104 ambiguity warning for a type that really lives in one namespace.

Both filters run before scoring, so they cut work as well as output. Filtering
is scoped to Search() only; LoadAllTypes() is deliberately left unfiltered so
`find-api members ABI.Foo` still resolves for interop debugging.

Measured on samples/winui-app, JSON output size:

  NavigationView   38,322 -> 17,001 chars (-56%), 105 -> 38 candidates
  Button           70,636 -> 42,215 chars (-40%), 168 -> 62 candidates
  AppWindow        12,863 ->  8,792 chars (-32%),  24 ->  8 candidates
  acrylic brush     1,743 ->    964 chars (-45%)
  StorageFile       7,356 ->  6,786 chars  (-8%)

Zero duplicate and zero ABI candidates remain in any of them. Adds 5 regression
tests covering multi-package collapse, the Take(5) crowd-out, ABI exclusion from
search, ABI-only types not raising false ambiguity, and ABI types still being
reachable by exact name.
Deduping inherited members on the substituted signature let one declared
member hide another declared by the same type. A base declaring both
M(T) and M(String), reached as Base<String>, substitutes both to
`void M(String item)`, so the second was dropped and the member vanished
from output with no warning.

Hold a supertype's dedup keys back until the whole type has been walked.
Two members of one declaring type can no longer hide each other, while a
derived type that redeclares an inherited member still hides the base
copy, because its keys are recorded before the base is dequeued.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
A rendered signature spells types and names the same way, so rewriting
every matching identifier renamed anything spelled like a type parameter.
`Base<T>.Add(T T)` reached through `Base<String>` rendered as
`void Add(String String)`, and a property named `T` became `String String
{ get; set; }`. ApiMemberOutput carries no separate parameter list, so the
signature is the whole of what a caller sees — there was no correct field
left to fall back on.

Substitute the head of the signature, which is the only part that carries
types, and rebuild a parameter list from the parameters themselves rather
than parsing it back out of the string. A signature whose declared names
cannot collide keeps the plain rewrite verbatim, so output for real
metadata is unchanged: a sweep of 668 indexed SDK types (15,376 members)
produced byte-identical results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4

@nmetulev Nikola Metulev (nmetulev) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 AI-generated review (winappcli pr-review skill) — verify before acting.

Thanks for the fixes. I’ve narrowed this to the changes that matter most: useful package-based discovery, clear first use, and trustworthy answers. No term aliases, generated tutorials, or universal NuGet catalog are requested.

1. Show usage for bare find-api

winapp find-api currently errors and exits 1. Please show short usage and examples instead; keep errors for genuinely malformed arguments. Entering the command should help someone discover how to use it.

Bare find-api returns an error

2. Search package descriptions and meaningful words

winapp find-api "text generation" finds nothing, although the package describes LanguageModel as providing text generation and embeddings.

Package-description query finds nothing

Meanwhile, llm matches letters inside scroLLMode and returns scrolling APIs:

Current unrelated results

Unrelated scrolling results

Please search the available type/member descriptions and improve identifier-word matching while preserving exact lookup. A package-only prototype improved useful first-three results from 15/28 queries to 24/28, including 5/11 to 9/11 on held-out queries.

There is no requirement to recognize “Phi Silica” or “LLM” when those terms are absent from package information. An honest miss is better than unrelated matches.

3. Keep answers concise without losing important meaning

Show a few strong matches with their purpose, owning package/version, and the matching member signature when relevant. Keep filtered drill-down. Emit coverage warnings once, and do not count a framework plus its runtime-specific restore entry as two frameworks.

These remaining correctness cases should also be addressed:

Current behavior Requested change Why
A metadata file named in the restore inventory disappears; refresh succeeds, then InfoBar is “not found” without explaining the missing file. Mark coverage incomplete and recommend restore. Missing evidence is not evidence that the API is absent.
Getter-only attached Level gets the same green confirmation as writable Count; structured writability is omitted. Label the read-only case and provide writable: false. Finding a property does not make assignment valid.
Rename Before.csproj to After.csproj, restore and refresh; the tool still offers both projects. Ignore or remove the obsolete manifest. A normal rename should not leave a phantom project that blocks queries.
Current outputs for these three cases

Missing metadata is not explained

Read-only status is lost in compact output

A renamed project leaves a phantom choice

4. Preserve the local-read safety boundary

Repository-selected paths still have routes around the checks: an assembly name such as ..\redirect\Poison reaches file enumeration; external lockfile paths bypass reparse checks; and the direct assets path checks its parent rather than the final file.

Please require a literal assembly filename and validate repository-supplied paths, including the final assets file, before reading them. A local metadata query must not follow a repository-selected link to an unintended location or network share.

Source: assembly name · external paths · assets file.

These paths were reconfirmed in current source. Earlier traversal used loopback SMB; no credential capture or exfiltration was performed.

5. V1 proposal: separately cached latest-stable SDK discovery

This is a feature addition, not a defect in local-only lookup. An older project already has LanguageModel but lacks GenerateStructuredJsonResponseAsync; a newer SDK contains it.

Please allow discovery in a cached latest-stable Windows App SDK catalog, clearly separate from project references:

GenerateStructuredJsonResponseAsync
  Project index (AI 1.8.53): no matching member
  SDK catalog (Windows App SDK 2.4.0 / AI 2.4.4): found
The same lookup against older and newer restored projects

Older references lack the member

Newer references include the member

The experiment acquired about 175 MB of selected SDK/API packages and produced a roughly 4.9 MB metadata cache. Make the initial download explicit and reuse the cache; do not modify the project or install a runtime. “Found in version X” must not imply a minimum version, compilation success, or device compatibility.

Images show rendered captures of actual command output from 9fdb3af1, not native terminal-window screenshots. Paths are shortened and excerpts labeled. Fixed generic-substitution, XML-reader, restore-ownership and npm-documentation findings have been removed.

A cloned repository chooses these values, so reading through them without
a check lets a clone steer local metadata reading.

<AssemblyName> became a search pattern, and a search pattern is not
confined to the directory it is rooted at: Directory.GetFiles(bin,
@"..\redirect\Poison.dll") returns that file, and `..\..\*.dll` climbs
further. Require a literal file name, and refuse wildcards for the same
reason the caller takes only the referenced project's own output.

project.assets.json was reached after checking `obj` but not the file
itself, so an ordinary directory holding a linked assets file was read.
Check the final path, which is what CrossesReparsePoint already walks.

A path outside the repository root skipped the reparse check by design,
because a package cache relocated with a junction is a normal setup. That
is kept: only a link resolving off the machine is refused now, which is
the outbound SMB reach the lexical network check cannot see.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
Typing `winapp find-api` is how someone finds out what the command does,
and answering that with a red error and exit 1 turns a discovery moment
into a failure. Show a short usage block with the shapes people reach for
first, and exit 0.

A caller that passed --json is making a programmatic call with a required
argument missing, which is a real error, so that keeps the structured
failure — handing it usage text would only break its parser.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
An attached property with a GetXxx but no SetXxx cannot be assigned, in
markup or in code, yet check-property confirmed it with the same plain
tick a settable one gets and left `writable` out of the JSON entirely. A
caller acts on that and writes an assignment that does not compile.

The detail view did say "(read-only)" in the accessor prose, but led with
the tick a reader skims; the compact view — what a batch check shows —
dropped the fact completely.

Detection already knows whether a setter exists, so carry that out as the
same `writable` answer a plain property gives and let both views use the
marker they already have for it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
--json is what an agent reads, so the structured field is the part of the
read-only fix that has to stay.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Dismissed
Comment thread src/winapp-CLI/WinApp.Cli/Helpers/PathSafety.cs Dismissed
Rename Before.csproj to After.csproj, restore, and refresh: a manifest is
written for After and Before's is left behind, still naming the same
directory. The directory now reads as holding two indexed projects, so
every query there fails with "pick one" — and one of the two choices
cannot be selected, because the project is gone.

Drop a manifest whose project file is no longer on disk. That is enough
for a rename to look like what it is, and it costs nothing when the
project is still there. The stale cache entry is left alone rather than
deleted; it is harmless once nothing offers it.

The test fixtures wrote manifests without ever creating the project file
they named, which is not a state the indexer can produce. They create it
now, so a missing file means what it says.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
…gone

Both restore inventories silently dropped metadata files that were
selected for the build but are no longer on disk: project.assets.json
filtered its chosen compile assets through File.Exists, and the winmds
lockfile skipped entries whose .winmd files were missing. A cleared
NuGet cache or a partial sync therefore produced an index that answered
"no such API" for everything those files defined, with nothing to
attribute the gap to.

Both paths now name the missing files through the existing coverage-
caveat channel, so every later answer is qualified and points at the
restore command that fixes it. The list is capped at five names because
a wholly unrestored project can name hundreds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
Two ways the coverage warnings misrepresented what they described.

A RID-specific restore entry (net8.0-windows10.0.26100.0/win-x64) sits
next to the framework it belongs to in project.assets.json, and both
matched the Windows moniker. An ordinary single-framework project was
therefore told it targets two Windows frameworks and that its answers
may not hold for the other one. Frameworks are now counted by the part
before the RID.

A caveat is usually about the machine or a shared package rather than
one project, so in a solution every project raises the same sentence.
Refresh printed it once per project. It is now printed once per run;
each project still keeps its own copy, so answers stay qualified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
Searching for 'llm' returned scrolling APIs. 'llm' is a substring of
scroLLMode and a subsequence of much of the API surface, so the contains
and fuzzy bands both matched letters that start no word. A confident
unrelated answer is worse than nothing, because the caller has no way to
tell it apart from a real one.

Every band below prefix now requires the match to begin where a word
begins, using PascalCase, digit-run and separator boundaries. 'Mode'
still finds ScrollMode, 'Stream' still finds IOStream and a typo like
'Buton' still finds Button; 'llm' now finds neither.

The bands are also read by check-property, which keeps any suggestion
scoring 40 or more, so the matches those callers depend on are locked
down by tests first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
'text generation' found nothing, although the documentation for
LanguageModel says in as many words that it provides text generation.
Someone searching that way is describing the task, not naming the type,
and the summary is the only place that intent is written down.

A type or member whose name does not match at all is now scored against
its summary, requiring every word of the query to appear so one
incidental word cannot drag a summary in. The band sits below every name
band, including fuzzy, so a named API always outranks a prose match and
this only surfaces when nothing was named.

Descriptions were already merged into the cache during indexing, so no
cache format change and no refresh are needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
A search returned a ranked list of names, so choosing between them meant
opening each one. Two facts already in the index answer that in place:
the documentation summary says what the API is for, and the package id
and version say what the answer is true of - the same type name ships
from more than one package in a normal graph.

Each match now carries both. The summary is cut to its first sentence
and bounded, because a result list is only concise if each line stays a
line. The per-namespace grouping and the member signature on a
member-driven match are unchanged, so the drill-down still works the
same way.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
The solution build enables CA1861, which per-project builds do not, so a
constant array argument added with the stale-manifest test failed the full
build while the targeted test run stayed green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
The usage page and the shipped skill still described search as matching type
and member names only, and showed output that no longer matches what the
command prints. Both now describe word-level name matching, the description
fallback and its limits, the package/summary line on every hit, and the bare
invocation that prints usage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 87a80d05-6e3a-435b-b715-261310ad03a4
@Jaylyn-Barbee

Copy link
Copy Markdown
Contributor Author

2. Search package descriptions and meaningful words

winapp find-api "text generation" finds nothing, although the package describes LanguageModel as providing text generation and embeddings.

Microsoft.WindowsAppSDK.AI ships no XML documentation at all (verified for 1.8.53), so LanguageModel has no summary to search. But we did make some changes to search to improve the experience as called out in the rest of issue 2.

Everything else from that review has been fixed.

@nmetulev

Copy link
Copy Markdown
Member

🤖 AI-generated review clarification (winappcli pr-review skill) — verify before acting.

Jaylyn Barbee (@Jaylyn-Barbee)

You're right about AI 1.8.53: it has no API XML documentation. Our example used
AI 2.4.4, which includes metadata/Microsoft.Windows.AI.Text.xml and this
LanguageModel summary:

Provides text generation and embedding operations backed by a language model.

I should have made that version distinction explicit in the review.

This is also a useful example of why searching a latest-SDK catalog alongside
project references could help. It serves two different cases:

Search What the newer catalog adds What the project check should say
text generation A description that identifies LanguageModel. The type and response-generation methods already exist in AI 1.8.53. Don't recommend an upgrade just because the description came from 2.4.4.
GenerateStructuredJsonResponseAsync A member absent from AI 1.8.53. Found in the newer catalog, not in the current references.

The proposal is to discover with both sources, then use the referenced metadata
for current-project signatures and presence
. Keep the newer description's source
visible rather than treating it as documentation shipped with the older version.
That does not imply behavior is identical across versions.

This remains entirely package-derived: no aliases, invented documentation or
automatic project updates. Thanks for the search and help improvements already
landed; the separately cached latest-SDK lookup remains a feature proposal, not
a claim that the local-only lookup should find text its packages never supplied.

@Jaylyn-Barbee

Jaylyn Barbee (Jaylyn-Barbee) commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

The proposal is to discover with both sources, then use the referenced metadata for current-project signatures and presence. Keep the newer description's source visible rather than treating it as documentation shipped with the older version. That does not imply behavior is identical across versions.

This remains entirely package-derived: no aliases, invented documentation or automatic project updates. Thanks for the search and help improvements already landed; the separately cached latest-SDK lookup remains a feature proposal, not a claim that the local-only lookup should find text its packages never supplied.

So this will be resolved by follow up issue #832, if I am understanding correctly?

Read immediate reparse targets without opening their destinations, validate local chains with cycle and hop bounds, and keep Windows short-name normalization from probing unchecked paths. Validate redirects before resolver containment checks and cover local cache relocations, relative links, hidden redirects, and malformed chains.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3b52aab2-c023-4a3b-9a45-fe8e298e2814
Comment thread src/winapp-CLI/WinApp.Cli.Tests/WinMdParserNestedGenericTests.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli.Tests/ApiMetadataServiceTests.cs Fixed
Recognize spaced identifier words before prefix and namespace matches so LanguageModel stays visible when Windows SDK metadata is indexed first. Preserve literal exact matches, result caps, and alias-free package-based discovery; cover both package orders and helper types crowding the result list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3b52aab2-c023-4a3b-9a45-fe8e298e2814
Wait for first-frame readiness and prove same-owner input completes while recording remains pinned and another owner queues, instead of comparing process startup time with capture duration. Preserve cleanup behavior with scoped disposal and narrow filesystem exception handling.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3b52aab2-c023-4a3b-9a45-fe8e298e2814
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs
@nmetulev
Nikola Metulev (nmetulev) merged commit 4a2776c into main Sep 11, 2026
31 checks passed
@nmetulev
Nikola Metulev (nmetulev) deleted the jay/winmd-port branch September 11, 2026 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

7 participants