Skip to content

Commit 21b1c29

Browse files
ANcpLuaclaude
andcommitted
docs: improve GenAI model documentation and fix workflow
- Add publishing method reference to GenAICommand - Add seealso cross-references between GenAICommand and GenAIEvent - Update GenAIEvent summary to reference GenAICommand (matching OcrEvent pattern) - Fix NuGet workflow: remove broken login step (Trusted Publishing handles auth) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent a64c588 commit 21b1c29

File tree

4 files changed

+355
-8
lines changed

4 files changed

+355
-8
lines changed

.github/workflows/nuget-publish.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ jobs:
4242
- name: Pack NuGet package
4343
run: dotnet pack SWEN3.Paperless.RabbitMq/SWEN3.Paperless.RabbitMq.csproj --configuration Release --no-build --output ./nupkg -p:GeneratePackageOnBuild=false
4444

45-
- name: Authenticate with NuGet
46-
uses: NuGet/login@v1
47-
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
48-
4945
- name: Publish packages to NuGet.org
5046
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
5147
run: |

ANALYZERS.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
# Analyzer Catalog - SWEN3.Paperless.RabbitMq
2+
3+
**Philosophy:** "Zero Tolerance for Real Bugs, Zero Care About Style"
4+
**Strategy:** CA + VSTHRD + MA + RCS (bugs only) - Modern .NET 10/C# 14 analyzer stack
5+
**Last Updated:** November 20, 2025
6+
7+
## Core Principle
8+
9+
We care about:
10+
- Production deadlocks (VSTHRD002, MA0042)
11+
- Crashes (VSTHRD100, CA2000)
12+
- Security holes (CA2100, CA3001, CA5350)
13+
- Resource leaks (CA2000, CA1816)
14+
- Dead code (CA1852)
15+
16+
We don't care about:
17+
- Style opinions (CA1711 disabled; MA0053 suggestion in main, none in tests; MA0048 warning in main, none in tests)
18+
- Formatting (RCS0001-RCS0063 - disabled entirely)
19+
- Naming conventions (CA1715, CA1716)
20+
- Whether you use `var` or explicit types (IDE0008)
21+
- Brace styles, expression bodies, XML docs everywhere
22+
23+
## Selected Analyzers
24+
25+
| Package | Version | Purpose | Justification |
26+
|---------|---------|---------|---------------|
27+
| **Built-in .NET Analyzers** | 10.0.x (SDK) | Performance, reliability, security (CA rules) | Maintained by Microsoft, .NET 10 GA quality |
28+
| **Meziantou.Analyzer** | 2.0.254+ | Modern C# 14, perf, security, culture-sensitive APIs | Best-in-class for modern C#, actively maintained |
29+
| **Roslynator.Analyzers** | 4.14.1+ | Code simplification, readability hints | Complements Meziantou (refactoring suggestions) |
30+
| **MS.VisualStudio.Threading.Analyzers** | 17.14.15+ | Async/await correctness, ConfigureAwait, deadlock prevention | Critical for SSE + RabbitMQ async patterns |
31+
32+
## Disabled Style Analyzers (Not Real Bugs)
33+
34+
| Rule ID | Description | Reason |
35+
|---------|-------------|--------|
36+
| **CA1711** | Identifiers should not have incorrect suffix | Disabled - `ISseStream` is not `System.IO.Stream`, suffix is intentional |
37+
| **MA0048** | File name must match type name | Disabled - Style opinion, not a bug |
38+
| **MA0053** | Make class sealed | Disabled - Premature optimization, style opinion |
39+
| **RCS0001-RCS0063** | All Roslynator formatting rules | Disabled entirely - Use built-in formatter, avoid IDE lag |
40+
| **IDE0008** | Use explicit type vs var | Disabled - Style preference, not a bug |
41+
| **CA1715** | Identifiers should have correct prefix | Disabled - Naming opinion, not a bug |
42+
| **CA1716** | Identifiers should not match keywords | Disabled - Too noisy, context makes it clear |
43+
44+
## Excluded Analyzers
45+
46+
| Package | Reason for Exclusion |
47+
|---------|----------------------|
48+
| **ErrorProne.NET.CoreAnalyzers** | Beta quality (0.8.1-beta.1), overlaps with MA/VSTHRD on async/nullability, adds noise without value |
49+
| **SonarLint (IDE)** | Legacy rules, conflicts with modern C# 14 syntax (records, file-scoped namespaces), rely on MSBuild analyzers instead |
50+
| **StyleCop** | Style-focused, conflicts with modern C# idioms, not worth the noise |
51+
52+
---
53+
54+
## Overlap Resolution
55+
56+
### ConfigureAwait Enforcement
57+
58+
**Decision:** Keep **VSTHRD111 as sole owner**
59+
**Disabled:** CA2007, MA0004, RCS1090
60+
61+
| Rule ID | Library | Description | Status | Reason |
62+
|---------|---------|-------------|--------|--------|
63+
| **VSTHRD111** | VS.Threading | Use .ConfigureAwait(bool) |**KEEP** | Sole owner (detects Task.Delay, WhenAll, most comprehensive) |
64+
| CA2007 | NetAnalyzers | Do not directly await a Task | ❌ DISABLE | Redundant (VSTHRD111 covers it) |
65+
| MA0004 | Meziantou | Use ConfigureAwait(false) | ❌ DISABLE | Redundant (would double-fire with VSTHRD111) |
66+
| RCS1090 | Roslynator | Add/remove ConfigureAwait(false) | ❌ DISABLE | Redundant (VSTHRD111 covers it) |
67+
68+
---
69+
70+
### CancellationToken Propagation
71+
72+
**Decision:** Keep **MA0032/MA0040/MA0079/MA0080 as sole owners**
73+
**Disabled:** CA2016, RCS1229
74+
75+
| Rule ID | Library | Description | Status | Reason |
76+
|---------|---------|-------------|--------|--------|
77+
| **MA0032** | Meziantou | Use an overload with a CancellationToken |**KEEP** | Detects missing CT overloads |
78+
| **MA0040** | Meziantou | Forward the CancellationToken to methods |**KEEP** | Ensures CT propagation in call chains |
79+
| **MA0079** | Meziantou | Forward CT using .WithCancellation() |**KEEP** | IAsyncEnumerable-specific |
80+
| **MA0080** | Meziantou | Use CT using .WithCancellation() |**KEEP** | IAsyncEnumerable-specific |
81+
| CA2016 | NetAnalyzers | Forward the CancellationToken | ❌ DISABLE | Redundant (MA rules more comprehensive) |
82+
| RCS1229 | Roslynator | Use async/await when necessary | ❌ DISABLE | Redundant (MA rules cover CT usage) |
83+
84+
---
85+
86+
### Async Void / Naming
87+
88+
**Decision:** Keep **VSTHRD100/VSTHRD101/VSTHRD200**
89+
**Disabled:** RCS1046/RCS1047 (Roslynator async naming)
90+
91+
| Rule ID | Library | Description | Status | Reason |
92+
|---------|---------|-------------|--------|--------|
93+
| **VSTHRD100** | VS.Threading | Avoid async void methods |**KEEP** | Catches async void outside event handlers |
94+
| **VSTHRD101** | VS.Threading | Avoid unsupported async delegates |**KEEP** | Prevents async void in LINQ/Task.Run |
95+
| **VSTHRD200** | VS.Threading | Use Async naming convention |**KEEP** | Enforces XxxAsync suffix |
96+
| RCS1046 | Roslynator | Method name should end with 'Async' | ❌ DISABLE | Redundant (VSTHRD200 covers it) |
97+
| RCS1047 | Roslynator | Non-async method should not end with 'Async' | ❌ DISABLE | Redundant (VSTHRD200 covers it) |
98+
99+
---
100+
101+
### Await Before Dispose
102+
103+
**Decision:** Keep **MA0100/MA0129**
104+
**Disabled:** VSTHRD114
105+
106+
| Rule ID | Library | Description | Status | Reason |
107+
|---------|---------|-------------|--------|--------|
108+
| **MA0100** | Meziantou | Await task before disposing of resources |**KEEP** | Catches `using (var x = Task.Run(...))` |
109+
| **MA0129** | Meziantou | Await task in using statement |**KEEP** | Detects incomplete awaits in using |
110+
| VSTHRD114 | VS.Threading | Avoid returning null from Task method | ⚠️ KEEP | Different scenario (null Task returns) |
111+
| EPC31 | ErrorProne | Do not return null for Task-like types | ❌ N/A | Not installed (ErrorProne excluded) |
112+
113+
---
114+
115+
### String Comparison
116+
117+
**Decision:** Keep **MA0001 as sole owner**
118+
**Disabled:** CA1307, CA1309, CA1310, RCS1155
119+
120+
| Rule ID | Library | Description | Status | Reason |
121+
|---------|---------|-------------|--------|--------|
122+
| **MA0001** | Meziantou | StringComparison is missing |**KEEP** | Sole owner (most comprehensive, detects more patterns) |
123+
| CA1307 | NetAnalyzers | Specify StringComparison for clarity | ❌ DISABLE | Redundant (MA0001 covers it) |
124+
| CA1309 | NetAnalyzers | Use ordinal StringComparison | ❌ DISABLE | Redundant (MA0001 covers it) |
125+
| CA1310 | NetAnalyzers | Specify StringComparison for correctness | ❌ DISABLE | Redundant (MA0001 covers it) |
126+
| RCS1155 | Roslynator | Use StringComparison when comparing strings | ❌ DISABLE | Redundant (MA0001 covers it) |
127+
128+
---
129+
130+
### LINQ Optimization
131+
132+
**Decision:** Keep **MA0020/MA0031 as sole owners**
133+
**Disabled:** CA1826, CA1827, RCS1077, RCS1080
134+
135+
| Rule ID | Library | Description | Status | Reason |
136+
|---------|---------|-------------|--------|--------|
137+
| **MA0020** | Meziantou | Use direct methods instead of LINQ |**KEEP** | Sole owner (List<T>.Find vs FirstOrDefault, etc.) |
138+
| **MA0031** | Meziantou | Optimize Enumerable.Count() usage |**KEEP** | Sole owner (.Count property vs .Count() method) |
139+
| CA1826 | NetAnalyzers | Do not use Enumerable on indexable collections | ❌ DISABLE | Redundant (MA0020 covers it) |
140+
| CA1827 | NetAnalyzers | Do not use Count() when Any() can be used | ❌ DISABLE | Redundant (MA0031 covers it) |
141+
| RCS1077 | Roslynator | Optimize LINQ method call | ❌ DISABLE | Redundant (MA0020/MA0031 cover it) |
142+
| RCS1080 | Roslynator | Use Count/Length instead of Any | ❌ DISABLE | Redundant (MA0031 covers it) |
143+
144+
---
145+
146+
### StringBuilder Optimization
147+
148+
**Decision:** Keep **MA0028 as sole owner**
149+
**Disabled:** CA1830, CA1834, RCS1197
150+
151+
| Rule ID | Library | Description | Status | Reason |
152+
|---------|---------|-------------|--------|--------|
153+
| **MA0028** | Meziantou | Optimize StringBuilder usage |**KEEP** | Sole owner (Append(char) vs Append(string), more patterns) |
154+
| CA1830 | NetAnalyzers | Prefer StringBuilder.Append(char) | ❌ DISABLE | Redundant (MA0028 covers it) |
155+
| CA1834 | NetAnalyzers | Use StringBuilder.Append(char) | ❌ DISABLE | Redundant (MA0028 covers it) |
156+
| RCS1197 | Roslynator | Optimize StringBuilder.Append/AppendLine | ❌ DISABLE | Redundant (MA0028 covers it) |
157+
158+
---
159+
160+
### Array Allocations
161+
162+
**Decision:** Keep **MA0005**
163+
**Disabled:** CA1825
164+
165+
| Rule ID | Library | Description | Status | Reason |
166+
|---------|---------|-------------|--------|--------|
167+
| **MA0005** | Meziantou | Use Array.Empty<T>() |**KEEP** | Modern C# 12+ collection expressions support |
168+
| CA1825 | NetAnalyzers | Avoid zero-length array allocations | ❌ DISABLE | Redundant (MA0005 covers it) |
169+
170+
---
171+
172+
### Async Call in Sync Context
173+
174+
**Decision:** Keep **MA0042**
175+
**Disabled:** CA1849
176+
177+
| Rule ID | Library | Description | Status | Reason |
178+
|---------|---------|-------------|--------|--------|
179+
| **MA0042** | Meziantou | Do not use blocking calls in async method |**KEEP** | Fewer false positives, supports CreateAsyncScope |
180+
| CA1849 | NetAnalyzers | Call async methods when in async method | ❌ DISABLE | Redundant (MA0042 covers it) |
181+
182+
---
183+
184+
### Observe Async Results
185+
186+
**Decision:** Keep **VSTHRD110** + **MA0134**
187+
**Disabled:** None
188+
189+
| Rule ID | Library | Description | Status | Reason |
190+
|---------|---------|-------------|--------|--------|
191+
| **VSTHRD110** | VS.Threading | Observe result of async calls |**KEEP** | Catches fire-and-forget Task |
192+
| **MA0134** | Meziantou | Observe result of async calls |**KEEP** | Complements VSTHRD110 (different patterns) |
193+
| EPC13 | ErrorProne | Suspiciously unobserved result | ❌ N/A | Not installed (ErrorProne excluded) |
194+
195+
---
196+
197+
## Roslynator Formatting Rules (All Disabled)
198+
199+
**Decision:** Disable **ALL** Roslynator formatting rules (RCS0001-RCS0063)
200+
**Reason:** Use built-in .NET formatter only (avoid IDE lag, conflicts with .editorconfig)
201+
202+
| Rule ID Pattern | Count | Status | Reason |
203+
|----------------|-------|--------|--------|
204+
| RCS0001-RCS0063 | ~60 rules | ❌ DISABLED | Use .editorconfig + built-in formatter instead |
205+
206+
Examples:
207+
- RCS0001: Add blank line after embedded statement → **DISABLED**
208+
- RCS0027: Place new line after/before binary operator → **DISABLED**
209+
- RCS0046: Use spaces instead of tab → **DISABLED**
210+
211+
---
212+
213+
## Critical Rules to Keep (High Signal)
214+
215+
### Performance (CA18xx)
216+
217+
| Rule ID | Description | Severity |
218+
|---------|-------------|----------|
219+
| CA1848 | Use LoggerMessage delegates | ⚠️ Warning |
220+
| CA1851 | Possible multiple enumerations | ⚠️ Warning |
221+
| CA1852 | Seal internal types | ⚠️ Warning |
222+
| CA1853 | Unnecessary Dictionary.ContainsKey | ⚠️ Warning |
223+
| CA1854 | Prefer TryGetValue | ⚠️ Warning |
224+
| CA1860 | Avoid Enumerable.Any() | ⚠️ Warning |
225+
| CA1861 | Avoid constant arrays as arguments | ⚠️ Warning |
226+
| CA1869 | Cache JsonSerializerOptions | ⚠️ Warning |
227+
228+
### Reliability (CA20xx)
229+
230+
| Rule ID | Description | Severity |
231+
|---------|-------------|----------|
232+
| CA2000 | Dispose objects before losing scope | ⚠️ Warning |
233+
| CA2008 | Do not create tasks without TaskScheduler | ⚠️ Warning |
234+
| CA2012 | Use ValueTasks correctly | ⚠️ Warning |
235+
| CA2013 | Do not use ReferenceEquals with value types | ⚠️ Warning |
236+
| CA2014 | Do not use stackalloc in loops | ⚠️ Warning |
237+
238+
### Security (CA3xxx)
239+
240+
| Rule ID | Description | Severity |
241+
|---------|-------------|----------|
242+
| CA3001 | SQL injection vulnerabilities | ⚠️ Warning |
243+
| CA3002 | XSS vulnerabilities | ⚠️ Warning |
244+
| CA3006 | Process command injection | ⚠️ Warning |
245+
| CA3012 | Regex injection vulnerabilities | ⚠️ Warning |
246+
247+
---
248+
249+
## Test-Specific Overrides
250+
251+
**File Pattern:** `SWEN3.Paperless.RabbitMq.Tests/**/*.cs` (no leading `**/` in .editorconfig)
252+
253+
| Rule ID | Reason for Disable |
254+
|---------|-------------------|
255+
| VSTHRD111 | No SynchronizationContext in xUnit/NUnit |
256+
| VSTHRD002 | ERROR in prod (sync-over-async = deadlock); disabled in tests |
257+
| VSTHRD003 | ERROR in prod (awaiting foreign tasks); disabled in tests |
258+
| MA0004 | ConfigureAwait unnecessary in tests |
259+
| MA0032 | CT forwarding not needed in tests |
260+
| MA0040 | CT propagation not critical in tests |
261+
| MA0134 | Allow fire-and-forget in test setup |
262+
| MA0002 | String comparers not needed in tests |
263+
| MA0074 | Avoid `this.` prefix unnecessary in tests |
264+
| MA0076 | Enum.GetValues<T>() unnecessary in tests |
265+
| MA0006 | Use String.Equals unnecessary in tests |
266+
| MA0079 | IAsyncEnumerable CT not needed in tests |
267+
| IDE0058 | Allow expression-only statements |
268+
| MA0048 | Allow flexible test file naming |
269+
| CA1852 | Tests don't need sealing |
270+
| CA1816 | Tests don't need GC.SuppressFinalize |
271+
| MA0053 | Tests don't need sealing |
272+
| CA1707 | Allow underscores in test names |
273+
274+
---
275+
276+
## Migration Notes
277+
278+
### From No Analyzers → Clean Trinity
279+
280+
1.**Added:** Directory.Build.props with 3 analyzer packages
281+
2.**Added:** .editorconfig with overlap suppressions
282+
3.**Disabled:** SonarLint "Sonar way" profile in Rider
283+
4.**Excluded:** ErrorProne.NET (beta, overlaps, noise)
284+
285+
### Why No ErrorProne.NET?
286+
287+
| Overlap | ErrorProne Rule | Replaced By |
288+
|---------|----------------|-------------|
289+
| ConfigureAwait | EPC14, EPC15 | VSTHRD111 + MA0004 |
290+
| Async void | EPC27 | VSTHRD100 |
291+
| Null Task returns | EPC31 | VSTHRD114 |
292+
| Observe results | EPC13 | VSTHRD110 + MA0134 |
293+
| Quality | Beta (0.8.1-beta.1) | GA-quality alternatives |
294+
295+
---
296+
297+
## Maintenance
298+
299+
### Checking for Updates
300+
301+
```bash
302+
# Check analyzer versions
303+
dotnet list package --outdated
304+
305+
# Update to latest
306+
dotnet add package Meziantou.Analyzer
307+
dotnet add package Roslynator.Analyzers
308+
dotnet add package Microsoft.VisualStudio.Threading.Analyzers
309+
```
310+
311+
### Performance Profiling
312+
313+
```bash
314+
# Enable analyzer performance reporting
315+
dotnet build /bl /p:ReportAnalyzer=true
316+
```
317+
318+
### Adding New Rules
319+
320+
1. Search for the rule ID in this catalog
321+
2. If duplicate exists, disable the weaker one
322+
3. Update .editorconfig with severity
323+
4. Document the decision in this file
324+
325+
---
326+
327+
## FAQ
328+
329+
**Q: Why disable MA0004 but keep VSTHRD111?**
330+
A: VSTHRD111 is the sole owner for ConfigureAwait enforcement. Keeping both would cause double-firing. VSTHRD111 is more comprehensive (catches Task.Delay, WhenAll, etc.).
331+
332+
**Q: Why keep both VSTHRD110 and MA0134?**
333+
A: They catch different patterns. VSTHRD110 focuses on JoinableTaskFactory, MA0134 catches general fire-and-forget.
334+
335+
**Q: Why disable all Roslynator formatting rules?**
336+
A: Prevents IDE lag and conflicts. Use .editorconfig + built-in .NET formatter instead.
337+
338+
**Q: When should I add ErrorProne.NET?**
339+
A: Only if you need stricter nullability/concurrency checks and can tolerate beta-quality diagnostics. Not recommended for this project.
340+
341+
---
342+
343+
## References
344+
345+
- [Meziantou.Analyzer Rules](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/)
346+
- [Roslynator Rules](https://github.com/dotnet/roslynator/blob/main/docs/analyzers/README.md)
347+
- [VS.Threading Rules](https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md)
348+
- [.NET Code Analysis (CA rules)](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
using SWEN3.Paperless.RabbitMq.Publishing;
2+
13
namespace SWEN3.Paperless.RabbitMq.Models;
24

35
/// <summary>
46
/// Command to initiate GenAI summarization after successful OCR.
57
/// Published by REST service after persisting OCR content.
8+
/// Use <see cref="GenAIPublishingExtensions.PublishGenAICommandAsync{T}" /> to publish this command.
69
/// </summary>
710
/// <param name="DocumentId">The document ID to summarize.</param>
811
/// <param name="Text">The OCR-extracted text to summarize.</param>
12+
/// <seealso cref="GenAIEvent" />
913
public record GenAICommand(Guid DocumentId, string Text);

SWEN3.Paperless.RabbitMq/Models/GenAIEvent.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ namespace SWEN3.Paperless.RabbitMq.Models;
44

55
/// <summary>
66
/// Represents a GenAI processing result event.
7-
/// This event is published after AI-based text summarization.
8-
/// Success is indicated by a non-null Summary; failure by a non-null ErrorMessage.
7+
/// This event is published after processing a <see cref="GenAICommand" />.
98
/// </summary>
10-
/// <param name="DocumentId">Unique identifier for the document being summarized.</param>
9+
/// <param name="DocumentId">Unique identifier for the document being summarized, matching the <see cref="GenAICommand.DocumentId" />.</param>
1110
/// <param name="Summary">The generated summary text (non-null on success).</param>
1211
/// <param name="GeneratedAt">Timestamp when the summary was generated.</param>
1312
/// <param name="ErrorMessage">Optional error message if processing failed.</param>
14-
/// <seealso cref="OcrEvent" />
13+
/// <seealso cref="GenAICommand" />
1514
/// <seealso cref="GenAIPublishingExtensions.PublishGenAIEventAsync{T}" />
1615
public record GenAIEvent(Guid DocumentId, string? Summary, DateTimeOffset GeneratedAt, string? ErrorMessage = null);

0 commit comments

Comments
 (0)