Skip to content

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

Draft
Jaylyn Barbee (Jaylyn-Barbee) wants to merge 16 commits into
mainfrom
jay/winmd-port
Draft

feat: add winapp find-api — local Windows/WinRT API metadata search (port of winmd)#744
Jaylyn Barbee (Jaylyn-Barbee) wants to merge 16 commits into
mainfrom
jay/winmd-port

Conversation

@Jaylyn-Barbee

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

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. 411/411 targeted find-api, CLI-schema, and help tests pass. Full suite: 4463/4469. The 6 failures were confirmed pre-existing by stashing this branch, rebuilding clean, and reproducing them identically — 5 Node/Electron E2E and AnalyzeDumpAsync_ManagedDumpWithDeepStack (the known ARM64-host-analyzing-an-x64-dump environmental failure). 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) 38.62 MB 39.77 MB 📈 +1.15 MB (+2.97%)
CLI (x64) 38.73 MB 39.84 MB 📈 +1.11 MB (+2.87%)
MSIX (ARM64) 16.02 MB 16.45 MB 📈 +443.4 KB (+2.70%)
MSIX (x64) 17.02 MB 17.48 MB 📈 +474.4 KB (+2.72%)
NPM Package 33.42 MB 34.32 MB 📈 +928.0 KB (+2.71%)
NuGet Package 33.46 MB 34.36 MB 📈 +921.2 KB (+2.69%)

Test Results

4641 passed, 5 skipped out of 4646 tests in 630.6s (+79 tests, -59.9s vs. baseline)

Test Coverage

85.4% line coverage, 78.4% branch coverage · ⚠️ -3.7% vs. baseline

CLI Startup Time

59ms median (x64, winapp --version) · ✅ +9ms 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-08-13 21:07:23 UTC · commit e41649e · 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
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiQueryEngine.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/Scoring.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/NuGetResolver.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/WinMdParser.cs
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.

[Feature]: Port winmd (lexical Windows API metadata search) into winapp

1 participant