Repository navigation
Signal reference
A signal is a boolean observation about a repository. Version 0.1 implements 21
of them, and a rule may only reference a signal from this list. validate
rejects a profile that names anything else.
Everything here is implemented in
src/core/project.mjs.
If a signal surprises you, read the pattern. It is deliberately simple enough to
argue with.
Two mechanisms, and nothing else.
Path patterns test the list of relative file paths. Paths are normalized to forward slashes before matching, so the same repository gives the same answer on Linux and on Windows.
Text patterns test the contents of files that were read. Only files with a
known text extension are read: .cs, .csproj, .json, .js, .jsx, .md,
.mjs, .props, .sln, .toml, .ts, .tsx, .xml, .yaml, .yml. Some
patterns are further restricted to .cs files only, which is noted per signal
below.
A third input is derived rather than matched: package dependencies, the
union of dependencies, devDependencies, and peerDependencies across every
package.json in the repository. A package.json that fails to parse is
treated as absent, never as something to execute.
Detection never runs a build, never invokes a package manager, and never imports a project file. See the security model.
| Signal | Type | Detected when |
|---|---|---|
modularDotnet |
composite | A .sln or .csproj exists and domainLayer, applicationLayer, and infrastructureLayer are all true |
domainLayer |
path | Any path contains a Domain/ directory (case-insensitive) |
applicationLayer |
path | Any path contains an Application/ directory (case-insensitive) |
infrastructureLayer |
path | Any path contains an Infrastructure/ directory (case-insensitive) |
endpointLayer |
path | Any path contains a Controllers/ or Endpoints/ directory (case-insensitive) |
modularDotnet is the only composite signal. It exists because "this is a
modular .NET solution" is the claim a rule wants to make, and spreading that
across four rules would let a repository pass three of them while being nothing
of the sort.
| Signal | Type | Detected when |
|---|---|---|
forbiddenNestAbsent |
dependencies |
No dependency name starts with @nestjs/
|
forbiddenNextAbsent |
dependencies + path |
No dependency named next, and no file matching next.config.{js,cjs,mjs,ts,cts,mts}
|
Both are inverted: true means the forbidden thing is absent. That is why the
rules that use them expect true like every other rule, instead of the catalog
needing a mix of expected: true and expected: false that readers get wrong.
An empty repository satisfies both, which is correct. Nothing forbidden is present.
| Signal | Type | Detected when |
|---|---|---|
apiVersioning |
text, .cs only |
A quoted string containing /api/v1 followed by / or the closing quote |
openApi |
text, .cs only |
AddOpenApi, MapOpenApi, AddSwaggerGen, or UseSwagger
|
problemDetails |
text, .cs only |
AddProblemDetails or ProblemDetails
|
apiVersioning requires the quotes. A route built by string concatenation, or
declared through a versioning library that never writes the literal, will not be
detected. Attribute routes such as [Route("api/v1/orders")] also miss, because
the pattern expects a leading slash inside the quotes.
problemDetails matches the bare type name, so any file that mentions the
ProblemDetails type satisfies it, including one that only reads a problem
response. It is a weak signal and it is marked required rather than
blocking for that reason.
| Signal | Type | Detected when |
|---|---|---|
jwtBearer |
text, .cs only |
AddJwtBearer or JwtBearerDefaults
|
authorizationPolicies |
text, .cs only |
AddPolicy or IAuthorizationHandler
|
defaultDeny |
text, .cs only |
FallbackPolicy |
defaultDeny looks for the word, not for its value. A file containing
options.FallbackPolicy = null; satisfies the signal while doing the opposite
of what the rule wants. Per-rule evidence in 0.2 is aimed squarely at this class
of problem. Until then, treat a passing security.default-deny as "somebody
thought about it", not as proof.
| Signal | Type | Detected when |
|---|---|---|
postgresEfCore |
text, any file |
Npgsql.EntityFrameworkCore.PostgreSQL or UseNpgsql
|
migrations |
path | Any path contains a Migrations/ directory |
migrations is the one path pattern that is case-sensitive. A directory
named migrations/ in lowercase will not be detected. That matches the EF Core
default of Migrations/, and it is the single most common false negative people
hit. If your team renamed the directory, this rule will fail on an otherwise
correct repository.
postgresEfCore reads every text file rather than only .cs, so a
PackageReference in a .csproj is enough.
| Signal | Type | Detected when |
|---|---|---|
reactWeb |
dependencies | A dependency named react exists and no dependency named next
|
The next exclusion is why a Next.js application fails both frontend.react
and stack.no-nextjs rather than only the second. That is intentional
redundancy: the profile treats Next.js as a stack decision, not as a detail.
| Signal | Type | Detected when |
|---|---|---|
unitTests |
path | A UnitTest/ or UnitTests/ directory (case-insensitive) |
integrationTests |
path | An IntegrationTest/ or IntegrationTests/ directory (case-insensitive) |
architectureTests |
path | An ArchitectureTest(s)/ or ArchTest(s)/ directory (case-insensitive) |
These are naming-convention signals, and they are the ones most likely to
disagree with a healthy repository. A project with excellent tests in
tests/Orders.Tests/ scores zero on all three. The
profile page documents the expected
layout, and 0.2's exception mechanism exists for teams that will not rename.
| Signal | Type | Detected when |
|---|---|---|
healthChecks |
text, .cs only |
AddHealthChecks or MapHealthChecks
|
structuredLogging |
text, any file |
Serilog or ILogger<
|
structuredLogging is satisfied by ILogger<T> injection, which nearly every
ASP.NET Core application has by default. It detects that logging exists, not
that it is structured or that correlation is propagated. Read it as a floor.
inspect prints every signal with ✓ for found and · for not found:
mestre-yoda inspect --root ~/code/my-apiWhen a signal is unexpectedly false, work through this in order.
Check inventory.files in the JSON output. If it is far below the real file
count, something is being skipped. The skipped directories are .git, .idea,
.next, .vscode, bin, coverage, dist, node_modules, and obj. Note
that bin and obj are skipped for .NET build output, so source that lives
under a directory named bin is invisible.
Check inventory.skippedSymlinks. A symlinked source tree inspects as empty.
Check inventory.inspectedTextFiles against files. A large gap means files
were skipped for size. Any single file above 512 KiB is skipped, and once the
corpus reaches 8 MiB no further files are read.
Then check the pattern for that specific signal above. Most surprises are a naming convention the detector does not know about, and in 0.1 the honest answer is that the detector is narrow by design.
A new signal is a code change, not a catalog edit:
- Implement the detection in
signalMapinsrc/core/project.mjs. - Add its name to
SUPPORTED_SIGNALSinsrc/core/catalog.mjs, keeping the list alphabetical. - Extend the fixture in
tests/evaluator.test.mjsso the signal is exercised both ways. - Only then reference it from a rule, which is a profile version bump.
validate fails if a rule names a signal that is not in SUPPORTED_SIGNALS, so
the wrong order fails fast rather than silently evaluating nothing.
Repository · Releases · Issues · MIT license
Normative contracts live in
docs/. Where
this wiki disagrees with the repository, the repository is correct.
Getting started
Reference
The catalog
Using it
Project