How Demeanor decides what to protect

Every decision the audit makes — auto-protect this type, ask about that method, flag a code-quality concern — comes from a rule. Rules ship with Demeanor, can be added at the user or project level, and live in your repo as plain JSON.

The short version. The audit runs on its own using Demeanor’s built-in rules. Your project can add its own rules in a .demeanor/ folder that you commit to git. Each rule has a severity that tells Demeanor whether to act silently, mention it for review, or stop and ask. Rules are how the audit’s judgements stay consistent across runs, across developers, and across CI.

Why the rule store exists

The audit’s job is to recognise patterns that interact badly with obfuscation — a type whose name is looked up by a serializer, a method invoked by name from a framework, a property bound to a UI element. The audit recognises those patterns through rules. Without rules the audit would have nothing to say.

Three things follow from that:

  • Demeanor ships with a large built-in set covering the common .NET frameworks and serialization stacks. The set grows over releases; you don’t have to author anything to get value from the audit.
  • Your codebase has patterns Demeanor doesn’t know about — a homegrown plugin contract, an internal RPC layer, a convention your team enforces. You write rules for those once, and the audit applies them on every subsequent run.
  • Some decisions belong to you, not Demeanor. A method might be safely renameable in your project but not in someone else’s. The rule store has a way to record that choice so it sticks.

The four layers

Rules are resolved from four layers, in this order. A rule in a higher layer shadows a same-id rule from a lower layer — the project decides over the user, the user decides over Demeanor’s defaults.

Layer Where it lives Who edits it Tracked in git?
Built-in Inside Demeanor itself Wise Owl Software ships and maintains them No — shipped with the tool
User Your home directory You, across every project on this machine No — personal to you
Project .demeanor/patterns/*.json inside the repo Your team, reviewed in PRs Yes — this is the layer that travels with the code
Session In memory for the current audit run Used for one-off overrides during a conversation No — discarded when the run ends

For most teams, only the project layer matters. The built-in layer covers the common cases without configuration, and the user and session layers exist for edge cases. Project rules are the durable artifact.

The built-in layer — 26 years of refinement

The built-in layer covers ASP.NET Core, EF Core, Blazor, WPF, WinForms, MAUI, COM interop, System.Text.Json, and Newtonsoft.Json out of the box. The catalog has been refined across hundreds of customer codebases over Demeanor’s 26-year history — most of the rules in it exist because a real reflection or serialization pattern broke a real build at some point, and the lesson got encoded. New patterns are added with every release.

You don’t see the built-in rules directly — every decision Demeanor makes during obfuscation is logged in the generated report so you can see the catalog in action, but the catalog definitions themselves are a core proprietary asset. If a built-in rule’s behaviour doesn’t fit your project, the project layer shadows it by id.

Three severities

Every rule carries a severity that tells the audit what to do when it matches.

Auto-protect — act silently

The match is obviously load-bearing. Demeanor keeps the name and reports the count in the “auto-protected” section of the audit. No prompt, no decision required. This is the right severity when the consequence of getting it wrong is the application failing at runtime, and the rule has no plausible exceptions.

Needs decision — stop and ask

The match looks load-bearing but the audit isn’t certain. It lists the finding under “needs your decision” with a recommendation, but does not protect until you say so. This is the right severity when the rule could have legitimate exceptions, or when the right answer depends on something Demeanor can’t see — a deployment topology, a versioning policy, a private API surface.

Advisory — mention it, move on

The obfuscation itself would tolerate the pattern, but it’s worth knowing about — a reflection-path call that won’t survive Native AOT, a public DTO that isn’t registered with the source-generated JSON context, a likely-future-pain shape. No action required to obfuscate; acting on the advisory improves the codebase.

See Exclusions Guide — how the audit classifies findings for how these severities appear in the report. The same taxonomy applies whether the rule came from the built-in layer or your project.

What a project rule looks like

Project rules are JSON files under .demeanor/patterns/ inside your repo. The shape is small enough to read in one glance:

{
  "id": "myteam-plugin-contract",
  "title": "Plugin contracts must keep their public surface",
  "severity": "auto-protect",
  "match": {
    "implementsInterface": "MyCompany.Plugins.IPlugin"
  },
  "rationale": "Plugins are loaded by reflection from third-party DLLs that ship outside this repo. Renaming the interface members breaks downstream integrators."
}

The fields:

  • id — A stable identifier you choose. Use a prefix (your team or module name) so the rule can’t collide with one Demeanor ships. A rule in your project with the same id as one Demeanor ships will shadow it.
  • title — A one-line description that shows up in the audit output and in PR review.
  • severity — One of auto-protect, needs-decision, or advisory.
  • match — What the rule matches on. The supported shapes — interface implementation, attribute presence, base type, IL-flow patterns, and more — are documented in Authoring a rule by hand, which is also the working reference for the full JSON schema, the predicate vocabulary, and composition with all / any / not.
  • rationale — Why this rule exists. This is what your team reads in a year when they’re trying to decide whether to remove it.

A project usually ends up with somewhere between zero and a dozen rules — the things that are specific to your code. The built-in layer covers the rest.

Where they go

your-repo/
  src/
  .demeanor/
    patterns/
      plugin-contract.json
      legacy-rpc.json
      internal-conventions.json

One file per rule, or one file per logical group — whichever your team finds easier to review. The audit picks up everything in .demeanor/patterns/ on every run.

Two ways rules get into your project

1. You write them by hand

Create a JSON file under .demeanor/patterns/, fill in the fields, commit it. The audit picks it up on the next run. This is the right path when you already know the rule you need — a known convention your team enforces, a known fragile interface, a known internal API surface. See Authoring a rule by hand for the full schema, the predicate vocabulary, and worked examples.

2. The conversational workflow proposes them

If you drive the audit through an MCP-capable assistant (Claude Code, Claude Desktop, Cursor, Windsurf, Continue.dev, a VS Code MCP extension), and the assistant encounters a needs-decision finding more than once in a session, it can offer to capture your decision as a project rule. You see the JSON before it’s written, you approve, the file lands in the repo, and the next audit run treats the pattern as resolved — without re-running the conversation.

Both paths produce the same artifact: a JSON file under .demeanor/patterns/ that lives in your repo. The first path is mechanical; the second is collaborative. Neither is required — a project with no rule files still gets the full built-in audit.

Requires your own Claude subscription — not included with Demeanor. Any other MCP-capable assistant drives the same workflow with its own subscription.

Rules vs. [Obfuscation] attributes

Demeanor has had source-level [Obfuscation] attributes since the beginning, and they’re still the right tool for pinning one specific type or member. Rules are the engine-driven layer over them.

[Obfuscation] attribute Project rule
Scope One type or member at a time A pattern — matches every type or member it fits
Lives in The source file, next to the code .demeanor/patterns/ in the repo root
Best for “Keep this type’s names because the caller looks them up” “Keep every type that implements this interface”
Survives refactors Yes — moves with the type Yes — matched by shape, not by name
Visible in PR In the source diff In the .demeanor/patterns/ diff

The two mechanisms compose. Use attributes for pinpoint exclusions next to the code that needs them. Use rules when the same protection should apply to a whole pattern. See the Exclusions Guide for the attribute side and the Decisions & CI page for how rules carry your team’s judgements forward.

What CI sees

The build-time obfuscator reads .demeanor/patterns/ exactly like the interactive audit does. The same rules apply in the same order; the only difference is that CI has no human in the loop to answer needs-decision prompts.

That means two things:

  • Auto-protect and advisory rules behave identically in CI and locally. Auto-protect rules act silently; advisory rules log their finding and the build proceeds.
  • Needs-decision rules need a resolution before they reach CI. Either downgrade the rule to advisory (you’ve decided it’s informational), or split it into two rules — an auto-protect rule for the part you’ve decided to protect, and the needs-decision rule kept locally for the part still under review.

A clean PR ends with no needs-decision rules firing on CI. See Decisions & CI for how the conversational workflow gets you to that state.

Next steps

  • Authoring a rule by hand — the working reference: full JSON schema, the predicate vocabulary, composition with all / any / not, and worked examples
  • Decisions & CI — how the conversational workflow promotes decisions into project rules, and how CI runs against them with no AI in the loop
  • Exclusions Guide[Obfuscation] attributes and CLI exclusions for pinpoint cases
  • Conversational walkthrough — a real audit session, end to end
  • Getting Started — the canonical CLI guide