dotnet-assembly-query-unity
2.0.0
dotnet tool install --global dotnet-assembly-query-unity --version 2.0.0
dotnet new tool-manifest
dotnet tool install --local dotnet-assembly-query-unity --version 2.0.0
#tool dotnet:?package=dotnet-assembly-query-unity&version=2.0.0
nuke :add-package dotnet-assembly-query-unity --version 2.0.0
Tesearis.DotNetAssemblyQuery.Unity
Unity conventions for daq:
finds your repo's Library/ScriptAssemblies so agents/editors can query it with zero config.
A thin wrapper around daq that adds the one thing Unity projects need: finding the Unity
project root (the folder containing Library/ScriptAssemblies) and pointing daq at it.
Everything else (the actual query commands, and why reading compiled assemblies works better
than a Roslyn workspace or decompiler for Unity) is daq's job; see its README.
Usage
Run from anywhere inside a Unity project (that has been built at least once, so
Library/ScriptAssemblies exists):
daq-unity find-symbol <name> \
[--path <path>]... [--kind type|method|field|property] [--namespace <ns>] \
[--assembly-name <name>] [--contains] [--assets-only] [--project <path>] [--no-plugin-scan] \
[--no-daemon] [--daemon-idle-timeout <seconds>] [--json] [--verbose]
daq-unity <hover|go-to-definition|find-references|list-members> <name> \
[--path <path>]... [--kind type|method|field|property] [--namespace <ns>] \
[--assembly-name <name>] [--assets-only] [--project <path>] [--no-plugin-scan] \
[--no-daemon] [--daemon-idle-timeout <seconds>] [--json] [--verbose]
daq-unity implementations <interface-or-base-type> \
[--path <path>]... [--namespace <ns>] [--assembly-name <name>] [--assets-only] \
[--auto-framework] [--include-framework-results] [--project <path>] [--no-plugin-scan] \
[--no-daemon] [--daemon-idle-timeout <seconds>] [--json] [--verbose]
daq-unity list-assemblies \
[--path <path>]... [--project <path>] [--no-plugin-scan] \
[--no-daemon] [--daemon-idle-timeout <seconds>] [--json] [--verbose]
daq-unity daemon status [--json]
daq-unity daemon stop [--project <path>] [--no-plugin-scan] [--json] [--verbose]
daq-unity daemon start [--foreground] [--project <path>] [--no-plugin-scan] \
[--daemon-idle-timeout <seconds>] [--json] [--verbose]
daq-unity resolves the project root, then queries Library/ScriptAssemblies by calling
the Tesearis.DotNetAssemblyQuery API directly (in-process, not by shelling out to
daq's CLI) with the right --source-root value filled in for you (and Library/ScriptAssemblies
folded into the --path set alongside anything you pass yourself). --dir was removed (mirroring
daq's own --assembly/--dir merge into --path); --source-root is resolved automatically
and rejected if passed manually. If Library/ScriptAssemblies doesn't exist yet, it errors and
suggests building the project first.
Match filters: --kind type|method|field|property restricts matches to that member kind (on
list-members, it restricts which kind of member is listed instead); --namespace <ns> and
--assembly-name <name> restrict matches to a containing namespace/assembly (exact match). All
three are optional and combine with each other and the name match. implementations takes
--namespace/--assembly-name but not --kind; list-assemblies takes neither. find-symbol
also takes --contains for a case-insensitive substring match instead of an exact name match
(there's no separate search command). Pass --json to any command for machine-readable output
instead of text. See daq's own README for the exact shape, since daq-unity's JSON matches it
field-for-field - except list-assemblies, whose origin field is a deliberate daq-unity
addition (see below), and implementations, which is a single object
(targetIndexed/hint/implementations) rather than a bare array, mirroring daq's own
single-object exception for the same command.
implementations only searches types physically declared in an indexed assembly, including the
interface/base type itself: looking up a framework/BCL type like IDisposable requires indexing
its defining assembly alongside your project's own, or daq-unity reports it as not found -
unless --auto-framework is passed, in which case a not-found target triggers a one-time
discovery and load of the exact reference assemblies Unity's own build system last used to
compile this project, before retrying. A target that is indexed but genuinely has no
implementers is reported separately from one that isn't indexed at all. Once --auto-framework
has resolved the target, types found only via that discovery are used to resolve it but omitted
from the reported matches by default (to avoid flooding output with e.g. every BCL implementer of
IDisposable) - pass --include-framework-results to report them too.
--auto-framework discovery
Unity ships its own bundled reference assemblies inside the Unity Editor installation, which is
not the same runtime as any locally-installed dotnet SDK - so --auto-framework can't just
point at a local Microsoft.NETCore.App shared framework the way daq's own CLI does. Instead it
reads back the exact reference-assembly paths Unity's own Bee-based incremental compiler (Unity
2020.2+) already resolved the last time it compiled this project, from the Roslyn response file
(.rsp) it writes under Library/Bee/artifacts/*.dag/<AssemblyName>.rsp - no version-matching or
install-path guessing needed, since Unity has already done that resolution itself on this exact
machine. Two more guess-based approaches were considered and rejected: scanning well-known Unity
Hub install locations (Unity Hub's own install registry can be stale/empty even when editors are
installed) and parsing IDE-generated .csproj <Reference>/<HintPath> entries (a third-party
IDE-integration dependency, and gitignored by default).
This requires the project to have been compiled by the Unity Editor at least once (so a
.rsp exists); otherwise --auto-framework reports that plainly instead of silently doing
nothing, and that one call runs in-process (forcing --no-daemon behavior for it) since the
diagnostic has nowhere to travel back over the daemon wire. A successful discovery, on the other
hand, runs through a warm daemon like any other query: the discovered reference set is a list of
exact file paths spanning multiple directories (the API-level facade folder and its nested
Facades/ subfolder, etc.), which DaemonRequest.FrameworkPaths carries verbatim.
Project discovery
By default, daq-unity finds the Unity project root by walking up from the current directory
until it finds one containing Library/ScriptAssemblies. Pass --project <path> to start that
walk somewhere else instead of the current directory - useful when your cwd is a monorepo root
(or a repo with more than one Unity project) rather than inside the project itself. It's still a
starting point for the same upward walk, so it doesn't need to be the project root exactly - any
directory inside it works.
Assets vs. Packages
Library/ScriptAssemblies mixes the developer's own scripts with every Unity/third-party package
assembly. daq-unity classifies each assembly by where its PDB says its code came from:
Assets (the project's own code), Packages (a local, embedded, or registry-resolved package -
including code compiled from Library/PackageCache), or Unknown (no PDB or no usable debug
info at all - typically a precompiled plugin DLL, see below). When a package's PDB source path
doesn't resolve under the project root at all (e.g. Unity's shared/global package cache, or a
package referenced via an external file: path), daq-unity falls back to matching the
assembly's name against every .asmdef-declared name and precompiled .dll found under
Packages/ and Library/PackageCache/, so it still classifies as Packages instead of
Unknown.
list-assembliesprints this as a trailing[Origin](or anoriginJSON field) so you can see which is which without guessing from the name.- Pass
--assets-onlyto any other query command to restrict matches toAssets-classified assemblies only, filtering out package/plugin noise. Limitation:--assets-onlyalways runs in-process - it can't be forwarded to a background daemon (the shared daemon protocol's request shape has no field for it), so a daemon isn't tried for that one call, though it may still be spawned afterward for later, unscoped calls.
Precompiled Assets/ plugins
Unity never recompiles a precompiled managed plugin DLL (e.g. Assets/Plugins/Foo.dll) into
Library/ScriptAssemblies (it just references it as-is). daq-unity auto-discovers every *.dll
under Assets/ (recursively) and includes them alongside Library/ScriptAssemblies by default,
so these are queryable with zero config too. Pass --no-plugin-scan to skip this.
Native (non-.NET) DLLs commonly live alongside managed plugins under Assets/ (often duplicated
per platform slice), so daq-unity checks each discovered file's PE header up front and silently
excludes native ones from auto-discovery instead of one failure warning per file. This (and a
couple of other expected, non-actionable conditions) is hidden by default; pass --verbose to see
the collapsed Note: skipped N native (non-.NET) DLL(s) under Assets/ line and similar diagnostics
- see Verbose diagnostics below. A native DLL passed explicitly via
--pathis unaffected by this filtering and still gets today's per-file failure warning, since that's a direct request about a specific file.--no-plugin-scanmust be passed consistently betweendaemon startand later query calls - see below.
Verbose diagnostics
By default daq-unity only prints warnings to stderr for things worth acting on: an unreadable or
unmatched --path, or a DLL that failed to load. Expected, non-actionable noise - most notably the
native (non-.NET) DLLs routinely skipped under Assets/ (see above) - is hidden. Pass --verbose
to any query or daemon start/daemon stop command to see it too, e.g. when debugging why a
particular DLL isn't showing up in results.
Limitations
- Stale/uncompiled sources: not-yet-compiled scripts (new, edited, or failing to build) won't show up until Unity recompiles the project.
- Managed libraries outside
Assets/, for now: not auto-discovered (e.g. packages underPackages/) - pass them explicitly with--path <path>.
Background daemon
Like daq itself, daq-unity keeps this project's assemblies warm in a background daemon
between invocations, so repeated queries skip re-loading Library/ScriptAssemblies from scratch.
The daemon (named-pipe transport, process lifecycle, staleness detection) is daq's own daemon
library, shared rather than duplicated. See dotnet-assembly-query's README for how it behaves.
daq-unity daemon status [--json] # list running daemons (machine-wide)
daq-unity daemon stop [--project <path>] [--no-plugin-scan] [--json] [--verbose] # stop this project's daemon
daq-unity daemon start [--foreground] [--project <path>] [--no-plugin-scan] \
[--daemon-idle-timeout <seconds>] [--json] [--verbose]
Pass --no-daemon to any query command to run in-process only (never try or spawn a daemon), or
--daemon-idle-timeout <seconds> to control how long an auto-spawned daemon stays warm before
exiting on its own (default 1800s, or DAQ_DAEMON_IDLE_TIMEOUT_SECONDS). --daemon-idle-timeout
is intentionally hidden from --help output but works like any other flag. Unlike daq's own
daemon stop/daemon start, daq-unity's take no --path filter; they always target
this Unity project's own Library/ScriptAssemblies, resolved the same way a query would (via
--project, same as a query command).
A warm daemon is matched to a call by its exact resolved DLL set, so --no-plugin-scan must be
passed the same way on every call against a given daemon - toggling it between calls (including
between daemon start and the query calls that follow) spawns a second, separate daemon instead
of reusing the first, same as passing a different --path set would.
Exit codes: 0 on success (including a query that found nothing - that's a valid result,
not an error), 2 on a command-line usage error (bad/unknown flag, missing value, unknown
command), 1 on any other runtime failure (e.g. project root or Library/ScriptAssemblies
not found) - matching daq's own convention.
Agent integration
Add an allowlist entry so an agent (e.g. Claude Code) can call this without a permission
prompt each time, in .claude/settings.json:
{
"permissions": {
"allow": ["Bash(daq-unity:*)"]
}
}
For Claude Code specifically, this repo ships a daq-unity skill as an installable plugin.
From your Unity project, run:
/plugin marketplace add marek-jelo-jelinek/dotnet-assembly-query-unity
/plugin install daq-unity@marek-jelo-jelinek
It documents preflight, the full command/flag cheatsheet, and the allowlist snippet above, so
the agent picks up daq-unity correctly without re-deriving usage from this README each time.
Building & testing
dotnet build Tesearis.DotNetAssemblyQuery.Unity.slnx
dotnet test Tesearis.DotNetAssemblyQuery.Unity.slnx
License
MIT. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.