diff --git a/.github/openspec/changes/thunder-plugin-qa/design.md b/.github/openspec/changes/thunder-plugin-qa/design.md index adaca9f2..14056034 100644 --- a/.github/openspec/changes/thunder-plugin-qa/design.md +++ b/.github/openspec/changes/thunder-plugin-qa/design.md @@ -1,4 +1,4 @@ -# Design: Thunder Plugin QA System +# Design: Thunder Plugin QA System ## Architecture Overview @@ -23,7 +23,7 @@ Plugin files in ThunderNanoServices/Dictionary/ Chat report: Issue Summary table, failures only │ ▼ -Reports/plugin/Dictionary_2026-06-05.md ← Markdown report with clickable issue navigation +Reports/plugin/Dictionary_2026-06-05.html <- HTML report with clickable issue navigation and syntax-highlighted code blocks ``` ``` diff --git a/.github/openspec/changes/thunder-plugin-qa/proposal.md b/.github/openspec/changes/thunder-plugin-qa/proposal.md index dc4db196..e1fb3e1c 100644 --- a/.github/openspec/changes/thunder-plugin-qa/proposal.md +++ b/.github/openspec/changes/thunder-plugin-qa/proposal.md @@ -1,4 +1,4 @@ -# Proposal: Thunder PluginQualityAdvisor System +# Proposal: Thunder PluginQualityAdvisor System ## Intent @@ -54,8 +54,8 @@ automatically. **Review reports** (Markdown, generated after each review run) - Single `.md` file per review with Issue Summary table and Detailed Findings -- Plugin report: `Reports/plugin/{PluginName}_{YYYY-MM-DD}.md` -- Interface report: `Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md` +- Plugin report: `Reports/plugin/{PluginName}_{YYYY-MM-DD}.html` +- Interface report: `Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html` - Issue Summary table with clickable `[rule_id - Name](#issue-N)` links navigating to detailed sections - Each finding has: What's wrong (plain English), Code found, Fix - PASS and SKIP rules excluded — only failures shown @@ -102,7 +102,7 @@ ThunderTools/PluginQualityAdvisor/ │ └── thunder-interface-rules.yaml └── Reports/ ├── plugin/ - │ └── {PluginName}_{YYYY-MM-DD}.md + │ └── {PluginName}_{YYYY-MM-DD}.html └── interface/ - └── {InterfaceName}_{YYYY-MM-DD}.md + └── {InterfaceName}_{YYYY-MM-DD}.html ``` \ No newline at end of file diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md index 9aaafdab..f31dcdf3 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -1,4 +1,4 @@ -# Delta for Interface Validation and Plugin Generation +# Delta for Interface Validation and Plugin Generation ## ADDED Requirements @@ -27,7 +27,7 @@ a Thunder COM interface header against 19 rules (16 core + 3 advisory). - THEN it applies all 19 rules loaded from `thunder-interface-rules.yaml` in order: core_1_1 through core_16_1 (16 core), then advisory_m1_1 through advisory_m3_1 (3 advisory) - AND outputs an Issue Summary table with columns: Issue No. | Status | Rule | File | Line | Issue -- AND generates a Markdown report with clickable navigation to Detailed Findings +- AND generates a self-contained HTML report with clickable navigation to Detailed Findings and syntax-highlighted code blocks --- diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md index fc08b5f4..796b1570 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -1,28 +1,27 @@ -# Spec: Thunder PluginQualityAdvisor Report Generation +# Spec: Thunder PluginQualityAdvisor Report Generation ## Purpose -After every `/thunder-plugin-review` or `/thunder-interface-review` run, the system generates a -Markdown report file with an Issue Summary table and Detailed Findings sections with clickable navigation. +After every `/thunder-plugin-review` or `/thunder-interface-review` run, the system generates a self-contained HTML report file with an Issue Summary table and Detailed Findings sections with clickable navigation and syntax-highlighted code blocks. --- ## Requirements -### REQ-R1 — Plugin review Markdown report +### REQ-R1 — Plugin review HTML report **Scenario:** `/thunder-plugin-review` completes all 84 rules -- The system MUST generate a Markdown file at: - `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md` +- The system MUST generate an HTML file at: + `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html` - If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) - The report MUST contain: header with totals, Issue Summary table, and Detailed Findings sections - PASS and SKIP rules are NOT included — only VIOLATION, WARNING, and SUGGESTION -### REQ-R2 — Interface review Markdown report +### REQ-R2 — Interface review HTML report **Scenario:** `/thunder-interface-review` completes all 19 rules -- The system MUST generate a Markdown file at: - `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md` +- The system MUST generate an HTML file at: + `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html` - Same no-overwrite rule applies - Same structure: header, Issue Summary table, Detailed Findings @@ -30,8 +29,8 @@ Markdown report file with an Issue Summary table and Detailed Findings sections The report MUST start with: -```markdown -# Thunder Plugin Review - {PluginName} +```html + **Date:** {YYYY-MM-DD} **Plugin:** {PluginName} @@ -46,7 +45,7 @@ The report MUST contain an Issue Summary table with clickable navigation: |--------|-------------| | Issue No. | Sequential number | | Status | ❌ VIOLATION / ⚠️ WARNING / 💡 SUGGESTION (Unicode emoji, never GitHub shortcodes) | -| Rule | Clickable link: `[rule_id - Name](#issue-N)` navigating to detailed section | +| Rule | Clickable link: `rule_id - Name` navigating to detailed section | | File | Source file name | | Line | Exact line number | | Issue | Short description | @@ -55,12 +54,12 @@ The report MUST contain an Issue Summary table with clickable navigation: Each issue MUST have a detailed section with: -- Heading: `### Issue N` (creates `#issue-n` anchor for navigation) +- Heading: `

Issue N

` (creates `#issue-n` anchor for navigation) - Rule ID and name as bold text: `**rule_XX - Rule Name**` - Status line: `**Status:** ❌ VIOLATION | **File:** filename | **Line:** N` - **What's wrong:** Plain-English explanation a junior developer can understand -- **Code found:** Actual code from the file with file:line comment in a fenced code block -- **Fix:** Corrected code in a fenced code block +- **Code found:** Actual code from the file with file:line comment in a `
` block (highlight.js syntax-highlighted)
+- **Fix:** Corrected code in a `
` block
 - If severity was downgraded: **Note:** paragraph explaining why
 
 Issues MUST be ordered by severity: VIOLATIONS first, then WARNINGS, then SUGGESTIONS.
@@ -74,13 +73,13 @@ Issues MUST be ordered by severity: VIOLATIONS first, then WARNINGS, then SUGGES
 
 - The report MUST be written via terminal (`[System.IO.File]::WriteAllText`) to avoid VS Code editor buffer conflicts
 - After writing, the file size MUST be verified (non-zero)
-- The report MUST be opened in Markdown Preview (`markdown.showPreview`), NOT in the editor
-- The editor MUST NOT open the file directly (triggers notebook mode and creates `codebook-md/` folder)
+- The report MUST be opened in a browser or VS Code Simple Browser (`simpleBrowser.show`), NOT in the editor
+- The editor MUST NOT open the file directly (shows raw HTML)
 
 Post-generation chat message:
 ```
 📄 Full report saved:
-   PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md
+   PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html
    {N} issue(s) - {violations} violations, {warnings} warnings, {suggestions} suggestions
 ```
 
@@ -88,8 +87,8 @@ Post-generation chat message:
 
 If no issues were found, generate:
 
-```markdown
-# Thunder Plugin Review - {PluginName}
+```html
+
 
 **Date:** {YYYY-MM-DD}  
 **Plugin:** {PluginName}  
@@ -106,18 +105,18 @@ If no issues were found, generate:
 ThunderTools/PluginQualityAdvisor/
 └── Reports/
     ├── plugin/
-    │   ├── Dictionary_2026-07-16.md
-    │   └── NetworkControl_2026-07-16.md
+    │   ├── Dictionary_2026-07-16.html
+    │   └── NetworkControl_2026-07-16.html
     └── interface/
-        ├── INetworkControl_2026-07-16.md
-        └── IDictionary_2026-07-16.md
+        ├── INetworkControl_2026-07-16.html
+        └── IDictionary_2026-07-16.html
 ```
 
 ---
 
 ## Out of Scope
 
-- CSV or Excel format (replaced by Markdown reports)
+- CSV or Excel format
 - Report viewer command (Markdown Preview is sufficient)
 - Diff between two reports (future)
 - Automatic email or CI upload (future)
diff --git a/.github/openspec/changes/thunder-plugin-qa/tasks.md b/.github/openspec/changes/thunder-plugin-qa/tasks.md
index c78d5450..8035c8da 100644
--- a/.github/openspec/changes/thunder-plugin-qa/tasks.md
+++ b/.github/openspec/changes/thunder-plugin-qa/tasks.md
@@ -1,4 +1,4 @@
-# Tasks: Thunder Plugin QA System
+# Tasks: Thunder Plugin QA System
 
 ## Phase 1: YAML rule definitions
 
@@ -255,7 +255,7 @@
       - Your Task: 5 steps (identify file → load YAML → validate all 19 rules →
         report with Issue Summary table (❌/⚠️/💡) → provide specific fixes)
       - Step 4 — Validate Findings: eliminate false positives before reporting
-      - Step 6 — Generate Markdown Report: file path format, Issue Summary table with
+      - Step 6 - Generate HTML Report: file path format, Issue Summary table with
         clickable navigation, Detailed Findings sections, write via terminal, open in preview
       - Post-generation action: write via terminal, verify non-empty, open in Markdown Preview
       - Contextual Judgment: JUDGE step table with Status field mapping
@@ -413,9 +413,9 @@
 
 ## Phase 5: Report generation
 
-- [x] 5.1 Add Step 6 (Markdown report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md`
+- [x] 5.1 Add Step 6 (HTML report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md`
       - Appended after Command Examples section
-      - File path: `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md`
+      - File path: `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html`
       - Create folder if absent; never overwrite (append _2, _3 suffix)
       - Report structure: header with totals, Issue Summary table, Detailed Findings sections
       - Issue Summary table: Issue No. | Status | Rule (clickable link) | File | Line | Issue
@@ -425,14 +425,14 @@
       - Verify non-empty after writing; open in Markdown Preview (not editor)
       - Empty report (all pass): header + "✅ All rules passed"
 
-- [x] 5.2 Add Step 6 (Markdown report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md`
+- [x] 5.2 Add Step 6 (HTML report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md`
       - Same format as plugin report
-      - File path: `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md`
+      - File path: `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html`
       - Same no-overwrite rule, same report structure, same post-generation actions
 
 - [x] 5.3 Create spec `ThunderTools/openspec/changes/thunder-plugin-qa/specs/reports/spec.md`
-      - REQ-R1: plugin Markdown report path + no-overwrite rule
-      - REQ-R2: interface Markdown report path + no-overwrite rule
+      - REQ-R1: plugin HTML report path + no-overwrite rule
+      - REQ-R2: interface HTML report path + no-overwrite rule
       - REQ-R3: report header format with totals
       - REQ-R4: Issue Summary table with clickable navigation
       - REQ-R5: Detailed Findings sections format
diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md
index 74455dea..b7814d4c 100644
--- a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md
+++ b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md
@@ -106,7 +106,7 @@ In chat, provide a **concise summary table** of all issues found. Do NOT output
 | 1 | ❌ VIOLATION | core_10_1 - @json Tag | IHdmiCecSink.h | 45 | @json tag missing — ZERO JSON-RPC code generated |
 | 2 | ⚠️ WARNING | core_13_1 - Explicit Integer Widths | IHdmiCecSink.h | 72 | int parameter — use uint32_t |
 
-📄 Full report: `PluginQualityAdvisor/Reports/interface/IHdmiCecSink_2026-07-16.md`
+📄 Full report: `PluginQualityAdvisor/Reports/interface/IHdmiCecSink_2026-07-16.html`
 ```
 
 ### Status Symbols
@@ -118,17 +118,17 @@ In chat, provide a **concise summary table** of all issues found. Do NOT output
 
 End chat output with:
 ```
-📄 Full report saved: PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md
+📄 Full report saved: PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html
    {N} issue(s) - {violations} violations, {warnings} warnings, {suggestions} suggestions
 ```
 
 ---
 
-## Step 6 - Generate Markdown Report
+## Step 6 - Generate HTML Report
 
-After reporting results in chat, generate a Markdown report file with clickable navigation.
+After reporting results in chat, generate an HTML report file with clickable navigation and syntax-highlighted code blocks.
 
-**File path:** `PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md`
+**File path:** `PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html`
 
 - Create `PluginQualityAdvisor/Reports/interface/` if it does not exist
 - Never overwrite an existing file - append `_2`, `_3` etc. if needed
@@ -138,6 +138,38 @@ Run the following command in the interface file's git root to get the repo URL:
 - Repo URL: `git remote get-url origin`
 If git is unavailable, use `unknown`.
 
+**HTML shell (wrap the entire report content in this):**
+
+```html
+
+
+
+
+Thunder Interface Review - {InterfaceName}
+
+
+
+
+
+
+
+
+
+
+```
+
 ### Report Template
 
 ```markdown
@@ -210,10 +242,11 @@ virtual Core::hresult SetVolume(const uint32_t volume) = 0;
 
 ### Report Generation Rules
 
-- Each issue in the summary table links to its detailed section via the Rule column using `[rule_id - Name](#issue-N)` anchors
-- Each detailed section heading uses `### Issue N` (creates the `#issue-n` anchor automatically)
-- Each detailed section must end with a back-link to the summary table: `[\u2B06 Back to Issue Summary](#issue-summary)` — this allows readers to click back to the table after reading a finding
-- The rule ID and name appear as bold text on the first line under the heading
+- Render the report as a complete, self-contained HTML file using the HTML shell above
+- Use `

` for the report title, `

` for section headings, `

` for issue headings +- The issue summary is an HTML `` — each rule name in the Rule column is an `` link +- Each detailed section must end with: `` +- All code blocks use `
` (or `language-cmake` as appropriate) — highlight.js will syntax-highlight them automatically
 - **"What's wrong"** must be a plain-English explanation a junior developer can understand
 - **"Code found"** must show the actual code from the interface with file:line comment
 - **"Fix"** must show the corrected code
@@ -238,7 +271,7 @@ virtual Core::hresult SetVolume(const uint32_t volume) = 0;
 **Post-generation message in chat:**
 ```
 📄 Full report saved:
-   PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.md
+   PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.html
    {N} issue(s) - {violations} violations, {warnings} warnings, {suggestions} suggestions
 ```
 
@@ -249,20 +282,20 @@ virtual Core::hresult SetVolume(const uint32_t volume) = 0;
 1. **Write the file using terminal** — do NOT use create_file or file editing tools for the report. Use:
 
 ```powershell
-   [System.IO.File]::WriteAllText(".md", $content, [System.Text.UTF8Encoding]::new($false))
+   [System.IO.File]::WriteAllText(".html", $content, [System.Text.UTF8Encoding]::new($false))
 ```
 
 2. **Verify the file is not empty** — after writing, check the file size:
 
 ```powershell
-   (Get-Item ".md").Length
+   (Get-Item ".html").Length
 ```
 
 If the size is 0, the write failed — retry once.
 
-3. **Open in Markdown Preview** — run VS Code command `markdown.showPreview` on the generated report file so anchor links work.
+3. **Open in browser** — run VS Code command `simpleBrowser.show` with the file URI, or open the `.html` file in a browser. highlight.js renders syntax highlighting and anchor links work natively.
 
-4. **Do NOT open the report in the editor** — opening `.md` files in the editor triggers notebook mode which creates an unwanted `codebook-md/` folder. Always use preview-only.
+4. **Do NOT open the report in the VS Code editor** — the editor shows raw HTML. Always open in browser or Simple Browser preview.
 
 ---
 
@@ -295,4 +328,4 @@ Severity is **never escalated** above the YAML-defined level.
 - Thunder documentation: https://rdkcentral.github.io/Thunder/
 - Validation priorities: @json tag first → Core::hresult returns → @restrict on vectors → type conventions → binary compatibility → advisory rules
 - Load the YAML before every validation run - rules may have been updated since this prompt was created
-- Interface headers may be anywhere in the workspace — search for the filename if not found in the common `interfaces/` folder
\ No newline at end of file
+- Interface headers may be anywhere in the workspace — search for the filename if not found in the common `interfaces/` folder
diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md
index 3fe21aca..1c1f00c7 100644
--- a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md
+++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md
@@ -232,7 +232,7 @@ GitHub shortcodes (`:x:`, `:warning:`, `:bulb:`) do NOT render in VS Code Markdo
 
 End chat output with:
 ```
-📄 Full report saved: PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md
+📄 Full report saved: PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html
    {N} issue(s) - {violations} violations, {warnings} warnings, {suggestions} suggestions
 ```
 
@@ -265,10 +265,10 @@ End chat output with:
 
 ---
 
-## Step 6 - Generate Markdown Report
+## Step 6 - Generate HTML Report
 
-After reporting all results in chat, generate a Markdown report file with clickable navigation.
-**File path:** `PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md`
+After reporting all results in chat, generate an HTML report file with clickable navigation and syntax-highlighted code blocks.
+**File path:** `PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html`
 
 - Create `PluginQualityAdvisor/Reports/plugin/` if it does not exist
 - Never overwrite an existing file - append `_2`, `_3` etc. if a file with that name already exists
@@ -280,6 +280,41 @@ Run the following commands in the plugin's git root to populate the report heade
 - Commit SHA: `git rev-parse --short HEAD`
 If git is unavailable, use `unknown`.
 
+**HTML shell (wrap the entire report content in this):**
+
+```html
+
+
+
+
+
+Thunder Plugin Review - {PluginName}
+
+
+
+
+
+
+
+
+
+
+```
+
 ### Report Template
 
 ```markdown
@@ -419,7 +454,7 @@ The `MODULE_NAME` macro should use the `Plugin_` prefix convention for consisten
 **Post-generation message in chat:**
 ```
 📄 Full report saved:
-   PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.md
+   PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.html
    {N} issue(s) - {violations} violations, {warnings} warnings, {suggestions} suggestions
 ```
 
@@ -430,7 +465,7 @@ The `MODULE_NAME` macro should use the `Plugin_` prefix convention for consisten
 1. **Write the file using terminal** — do NOT use create_file or file editing tools for the report. Use:
 
 ```powershell
-   [System.IO.File]::WriteAllText(".md", $content, [System.Text.UTF8Encoding]::new($false))
+   [System.IO.File]::WriteAllText(".html", $content, [System.Text.UTF8Encoding]::new($false))
 ```
 
 This bypasses VS Code's editor buffer which can overwrite content with an empty file.
@@ -438,11 +473,11 @@ This bypasses VS Code's editor buffer which can overwrite content with an empty
 2. **Verify the file is not empty** — after writing, check the file size:
 
 ```powershell
-   (Get-Item ".md").Length
+   (Get-Item ".html").Length
 ```
 
 If the size is 0, the write failed — retry once.
 
-3. **Open in Markdown Preview** — run VS Code command `markdown.showPreview` on the generated report file so anchor links work and the user sees a navigable report with clickable issue links.
+3. **Open in browser** — run VS Code command `simpleBrowser.show` with the file URI, or open the `.html` file directly in a browser. highlight.js renders syntax highlighting and anchor links work natively.
 
-4. **Do NOT open the report in the editor** — opening `.md` files in the editor triggers notebook mode which creates an unwanted `codebook-md/` config folder and tries to execute code blocks. Always use preview-only.
\ No newline at end of file
+4. **Do NOT open the report in the VS Code editor** — the editor shows raw HTML. Always open in browser or Simple Browser preview.
\ No newline at end of file
diff --git a/PluginQualityAdvisor/README.md b/PluginQualityAdvisor/README.md
index c11093b6..5f286639 100644
--- a/PluginQualityAdvisor/README.md
+++ b/PluginQualityAdvisor/README.md
@@ -1,4 +1,4 @@
-# ThunderPluginQualityAdvisor
+# ThunderPluginQualityAdvisor
 
 AI-driven validation tools for Thunder plugin and COM interface development, powered by VS Code GitHub Copilot Chat.
 
@@ -91,7 +91,7 @@ Validates a Thunder COM interface header against core and advisory rules defined
 /thunder-interface-review INetworkControl.h
 ```
 
-**Output:** Issue summary table with detailed findings, saved as a Markdown report with clickable navigation.
+**Output:** Issue summary table with detailed findings, saved as an HTML report with clickable navigation and syntax-highlighted code blocks.
 
 ---
 
@@ -177,9 +177,9 @@ ThunderTools/PluginQualityAdvisor/
 |   +-- thunder-interface-rules.yaml
 +-- Reports/
     +-- plugin/
-    |   +-- {PluginName}_{YYYY-MM-DD}.md
+    |   +-- {PluginName}_{YYYY-MM-DD}.html
     +-- interface/
-        +-- {InterfaceName}_{YYYY-MM-DD}.md
+        +-- {InterfaceName}_{YYYY-MM-DD}.html
 ```
 
 ---
@@ -196,4 +196,4 @@ ThunderTools/PluginQualityAdvisor/
 
 ## Reports
 
-After each review, a Markdown report is generated under `Reports/plugin/` or `Reports/interface/`. Reports contain an Issue Summary table with clickable links to Detailed Findings sections, each with a plain-English explanation, code found, and fix.
\ No newline at end of file
+After each review, a self-contained **HTML report** is generated under `Reports/plugin/` or `Reports/interface/`. Reports include an Issue Summary table with clickable anchor links to Detailed Findings sections (plain-English explanation, code found, and fix), with syntax-highlighted code blocks powered by highlight.js. Open the `.html` file in a browser or VS Code Simple Browser.
\ No newline at end of file
diff --git a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml
index 380b604d..98b1adec 100644
--- a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml
+++ b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml
@@ -636,35 +636,40 @@ phase_3_checkpoints:
       code_block: "The Plugin::Metadata static instance declaration and any SERVICE_REGISTRATION calls"
 
     bounded_query:
-      question: "Does the plugin .cpp contain exactly one Plugin::Metadata static registration, does it NOT also contain SERVICE_REGISTRATION in the same file, and do OOP implementation files use SERVICE_REGISTRATION exclusively?"
+      question: "Does the plugin .cpp contain exactly one Plugin::Metadata static registration, and if SERVICE_REGISTRATION also appears in the same file, does it register a DIFFERENT class name (e.g. an OOP implementation class)? SERVICE_REGISTRATION for the SAME class as Plugin::Metadata in the same file is a violation."
       expected_answer: "Yes"
 
     verification_logic:
       - "1. Read the plugin .cpp file (PluginName.cpp)"
       - "2. Check for the presence of exactly ONE static Plugin::Metadata registration"
       - "3. Plugin::Metadata must appear only ONCE for the entire plugin — not in multiple files"
-      - "4. Check whether SERVICE_REGISTRATION also appears in the same plugin .cpp file that contains Plugin::Metadata — this is ALWAYS wrong regardless of OOP mode"
-      - "5. If both Plugin::Metadata and SERVICE_REGISTRATION appear in the same file → VIOLATION (the two mechanisms are mutually exclusive per file)"
-      - "6. All other places (e.g. OOP implementation files) must use SERVICE_REGISTRATION macro instead — not Plugin::Metadata"
-      - "7. For OOP plugins (PLUGIN__MODE = 'Local'): verify at least one SERVICE_REGISTRATION exists in the OOP implementation file"
-      - "8. If Plugin::Metadata is absent → VIOLATION"
-      - "9. If Plugin::Metadata appears more than once across the plugin → VIOLATION"
-      - "10. If OOP part lacks SERVICE_REGISTRATION → VIOLATION"
-      - "11. IMPORTANT — Co-resident plugins in subdirectories: scan ALL subdirectories of the plugin repository (e.g. legacy/, adapters/, compat/) for .cpp files that contain Plugin::Metadata or SERVICE_REGISTRATION for a DIFFERENT plugin class (e.g. Plugin::Network in a legacy/ folder alongside the main plugin). Each such file registers a separate Thunder plugin and must be checked independently: (a) if it contains both Plugin::Metadata AND SERVICE_REGISTRATION for the same plugin class in the same file → VIOLATION; (b) flag the discovery to the user so each co-resident plugin can receive its own full review"
+      - "4. Check whether SERVICE_REGISTRATION also appears in the same file that contains Plugin::Metadata"
+      - "5. If SERVICE_REGISTRATION registers the SAME class as Plugin::Metadata (e.g. Plugin::Metadata + SERVICE_REGISTRATION(AppGateway, ...)) → VIOLATION — this is duplicate registration of the same class via two mechanisms"
+      - "6. If SERVICE_REGISTRATION registers a DIFFERENT class (e.g. Plugin::Metadata + SERVICE_REGISTRATION(AppGatewayImplementation, ...)) → ACCEPTABLE — this is a valid pattern where the main plugin file also registers its OOP implementation service"
+      - "7. All other places (e.g. OOP implementation files) must use SERVICE_REGISTRATION macro instead — not Plugin::Metadata"
+      - "8. For OOP plugins (PLUGIN__MODE = 'Local'): verify at least one SERVICE_REGISTRATION exists in the OOP implementation file"
+      - "9. If Plugin::Metadata is absent → VIOLATION"
+      - "10. If Plugin::Metadata appears more than once across the plugin → VIOLATION"
+      - "11. If OOP part lacks SERVICE_REGISTRATION → VIOLATION"
+      - "12. IMPORTANT — Co-resident plugins in subdirectories: scan ALL subdirectories of the plugin repository (e.g. legacy/, adapters/, compat/) for .cpp files that contain Plugin::Metadata or SERVICE_REGISTRATION for a DIFFERENT plugin class (e.g. Plugin::Network in a legacy/ folder alongside the main plugin). Each such file registers a separate Thunder plugin and must be checked independently: (a) if it contains both Plugin::Metadata AND SERVICE_REGISTRATION for the same plugin class in the same file → VIOLATION; (b) flag the discovery to the user so each co-resident plugin can receive its own full review"
 
     conditional: false
     skip_condition: null
 
-    violation_pattern: "Plugin::Metadata registration missing, duplicated, co-located with SERVICE_REGISTRATION in the same file, or OOP part missing SERVICE_REGISTRATION"
+    violation_pattern: "Plugin::Metadata registration missing, duplicated, co-located with SERVICE_REGISTRATION for the SAME class in the same file, or OOP part missing SERVICE_REGISTRATION"
 
     fix_template: |
-      // WRONG — both in the same PluginName.cpp:
-      static Plugin::Metadata metadata(...);
-      SERVICE_REGISTRATION(Dictionary, 1, 0)  // ← must NOT appear alongside Plugin::Metadata
+      // WRONG — same class registered twice in the same file:
+      static Plugin::Metadata metadata(...);
+      SERVICE_REGISTRATION(AppGateway, 1, 0)  // ← SAME class as Metadata — violation
+
+      // CORRECT — different classes in the same file:
+      static Plugin::Metadata metadata(...);
+      SERVICE_REGISTRATION(AppGatewayImplementation, 1, 0)  // ← DIFFERENT class — valid
 
       // WRONG: (Plugin::Metadata missing entirely)
 
-      // Correct — exactly ONE Plugin::Metadata in PluginName.cpp, no SERVICE_REGISTRATION:
+      // Correct — exactly ONE Plugin::Metadata in PluginName.cpp:
       static Plugin::Metadata metadata(
           Plugin::Information::Versions,
           Plugin::Information::Instances,
@@ -1175,116 +1180,106 @@ phase_4_checkpoints:
     phase: "lifecycle"
 
     extraction:
-      target: "Initialize() failure paths — any call to Deinitialize(), and any member pointer assignments (ptr = nullptr) or framework pointer operations done in the failure branch"
-      method: "Read Initialize() body in full, identify every failure return path, and examine what cleanup operations are performed on each path"
-      code_block: "The full Initialize() body, focusing on cleanup paths after each failure condition"
+      target: "Initialize() body — check for any direct call to Deinitialize() and verify that Deinitialize() properly compensates all resources acquired in Initialize()"
+      method: "Read Initialize() and Deinitialize() in full. Initialize() must NOT call Deinitialize() directly — when Initialize() returns a non-empty error string, the Thunder framework itself calls Deinitialize(). Therefore Initialize() should NOT perform its own cleanup on failure paths; it should simply return the error string and let the framework handle teardown via Deinitialize()."
+      code_block: "The full Initialize() body (checking for Deinitialize() calls) and the full Deinitialize() body (checking for proper compensation)"
 
     bounded_query:
-      question: "On every failure path in Initialize(): (1) Is Deinitialize() never called directly? (2) Are only the resources actually acquired on THAT path cleaned up — never setting framework member pointers (e.g. _service = nullptr) that were assigned BEFORE the failure point? Exception: if AddRef() was called on the pointer in Initialize(), then Release() + nullptr IS correct on the failure path because the AddRef itself must be undone. The concern is about nulling pointers where NO matching AddRef was performed in Initialize() — nulling those breaks Deinitialize()'s invariants."
+      question: "Does Initialize() avoid calling Deinitialize() directly? And does Deinitialize() properly compensate all resources that Initialize() may have partially acquired — i.e., for every AddRef there is a Release, for every Register there is an Unregister, for every Root() result there is a Release, with nullptr guards on each to handle partial initialization gracefully?"
       expected_answer: "Yes"
 
     verification_logic:
-      - "1. Read Initialize() in full and identify all failure return paths (paths that return a non-empty error string)"
-      - "2. Check whether Initialize() calls Deinitialize() directly on any failure path → VIOLATION if so"
-      - "3. For each failure path, identify which member pointers were assigned BEFORE the failure point (e.g. _service = service assigned at the top, before Root() which then fails)"
-      - "4. Check whether those already-assigned member pointers are set to nullptr on the failure path"
-      - "5. Setting a framework pointer (like _service) to nullptr in Initialize()'s failure path is a VIOLATION because: (a) Thunder does not call Deinitialize() after a failed Initialize(), making the nullptr assignment redundant; (b) if Deinitialize() is ever reached, any ASSERT(ptr == expected) or guard based on that pointer will fail unexpectedly"
-      - "6. EXCEPTION: it is correct to Release() and nullptr pointers that were acquired specifically in the failing step (e.g. undo a Root() result or a QueryInterface() result) — only framework pointers assigned BEFORE the failure point must not be nulled"
-      - "7. Also check: is the correct explicit cleanup performed for each resource actually acquired before the failure? (Register → Unregister, AddRef → Release, etc.)"
+      - "1. Read Initialize() in full and check whether it calls Deinitialize() directly on any code path → VIOLATION if so"
+      - "2. Initialize() must NOT perform its own cleanup on failure paths — it should just return the non-empty error string. The Thunder framework will call Deinitialize() automatically when Initialize() returns a non-empty string."
+      - "3. If Initialize() contains explicit Release(), Unregister(), or nullptr assignments on failure paths, this is unnecessary (the framework handles it) but NOT a violation — it is redundant but harmless as long as Deinitialize() also handles it with proper nullptr guards."
+      - "4. Read Deinitialize() in full and verify it gracefully handles partial initialization — every resource cleanup must be guarded with a nullptr check (e.g. if (_service != nullptr) { _service->Release(); _service = nullptr; }) so that Deinitialize() works correctly even when Initialize() only partially completed."
+      - "5. If Deinitialize() does NOT have nullptr guards and would crash or double-release when called after a partial Initialize() → VIOLATION"
 
     conditional: false
     skip_condition: null
 
-    violation_pattern: "Initialize() calls Deinitialize() directly, OR nulls a framework member pointer (_service, _shell, etc.) on a failure path when that pointer was assigned before the failure — breaking Deinitialize()'s invariants"
+    violation_pattern: "Initialize() calls Deinitialize() directly; OR Deinitialize() does not gracefully handle partial initialization (missing nullptr guards on resource cleanup)"
 
     fix_template: |
-      // WRONG — Deinitialize() called directly:
+      // WRONG — Initialize() calls Deinitialize() directly:
       string Initialize(PluginHost::IShell* service) {
           _service = service;
           _service->AddRef();
           if (!Setup()) {
-              Deinitialize(service);  // ← do not call Deinitialize from Initialize
+              Deinitialize(service);  // ← WRONG: do not call Deinitialize from Initialize
               return "Setup failed";
           }
           return string();
       }
 
-      // WRONG — _service nulled in failure path after Root() fails:
+      // Correct — Initialize() just returns the error; framework calls Deinitialize():
       string Initialize(PluginHost::IShell* service) {
           _service = service;
+          _service->AddRef();
           _service->Register(&_notification);
           _impl = service->Root(...);
           if (_impl == nullptr) {
-              _service->Unregister(&_notification);  // ← correct: undo what was registered
-              _service = nullptr;                     // ← WRONG: nulls _service unnecessarily;
-                                                      //   breaks Deinitialize()'s ASSERT(_service == service)
-              return "Root failed";
+              return "Root failed";  // just return error — framework will call Deinitialize()
           }
           return string();
       }
 
-      // Correct:
-      string Initialize(PluginHost::IShell* service) {
-          _service = service;
-          _service->AddRef();
-          _service->Register(&_notification);
-          _impl = service->Root(...);
-          if (_impl == nullptr) {
-              _service->Unregister(&_notification);  // undo registration — correct
-              _service->Release();                   // undo AddRef — correct
-              _service = nullptr;                    // safe here because AddRef was done above
-              return "Root failed";
+      // Deinitialize() must handle partial initialization gracefully:
+      void Deinitialize(PluginHost::IShell* service) {
+          if (_impl != nullptr) {
+              _impl->Release();
+              _impl = nullptr;
           }
-          return string();
+          _service->Unregister(&_notification);
+          _service->Release();
+          _service = nullptr;
       }
 
-      // Note: if _service->AddRef() was NOT called in Initialize(), do NOT Release() or
-      // null _service in the failure path — leave cleanup entirely to Deinitialize().
-
     citation:
-      line_format: "[PluginName.cpp:LINE] Initialize() failure path violates cleanup rules — see rule_27"
+      line_format: "[PluginName.cpp:LINE] Initialize() calls Deinitialize() directly — let the framework handle it"
       rule: "thunder-plugin-rules.yaml / rule_27"
 
   - rule_id: "rule_28"
-    name: "Destructor Must Be Empty"
+    name: "No Plugin Lifecycle Dependencies in Destructor"
     severity: "violation"
     phase: "lifecycle"
 
     extraction:
       target: "Plugin class destructor body"
-      method: "Read the plugin class destructor implementation and reason about whether it performs any non-trivial work"
+      method: "Read the plugin class destructor implementation and reason about whether it performs any work that depends on the plugin lifecycle (Initialize/Deinitialize) or framework state"
       code_block: "The complete destructor body"
 
     bounded_query:
-      question: "Is the plugin destructor body completely empty (no resource cleanup, no logic — all cleanup is in Deinitialize())?"
+      question: "Is the plugin destructor free of any logic that depends on the plugin lifecycle (Initialize/Deinitialize) — no Release() on framework pointers, no Unregister() calls, no service interactions? General C++ cleanup (e.g. deleting owned non-COM objects, closing non-framework resources) is acceptable."
       expected_answer: "Yes"
 
     verification_logic:
       - "1. Read the plugin destructor definition"
-      - "2. The destructor must be empty — all resource cleanup belongs in Deinitialize()"
+      - "2. The destructor must NOT contain any logic that depends on the plugin lifecycle (Initialize/Deinitialize) — no Release() on COM interfaces acquired in Initialize(), no Unregister() calls, no service->* interactions"
       - "3. SYSLOG/TRACE calls for lifecycle tracing are harmless and should be IGNORED — do not flag a destructor that only contains logging"
-      - "4. If the destructor contains any logic or resource release beyond logging → VIOLATION"
+      - "4. General C++ cleanup that does NOT depend on framework or plugin lifecycle state is acceptable (e.g. deleting plain C++ objects, closing file handles not managed by the framework)"
+      - "5. If the destructor contains logic that depends on the plugin lifecycle (framework pointers, COM interfaces, service registrations) → VIOLATION"
 
     conditional: false
     skip_condition: null
 
-    violation_pattern: "Plugin destructor contains cleanup logic — all cleanup must be in Deinitialize()"
+    violation_pattern: "Plugin destructor contains logic that depends on the plugin lifecycle (Initialize/Deinitialize) — such cleanup must be in Deinitialize()"
 
     fix_template: |
       // WRONG:
       Dictionary::~Dictionary() {
           if (_service != nullptr) {
-              _service->Release();   // ← cleanup in destructor is wrong
+              _service->Release();   // ← depends on plugin lifecycle — belongs in Deinitialize()
           }
       }
 
       // Correct:
       Dictionary::~Dictionary() {
-          // empty — cleanup is in Deinitialize()
+          // No plugin lifecycle dependencies here — cleanup is in Deinitialize()
       }
 
     citation:
-      line_format: "[PluginName.cpp:LINE] Destructor contains cleanup logic — move to Deinitialize()"
+      line_format: "[PluginName.cpp:LINE] Destructor contains plugin lifecycle-dependent logic — move to Deinitialize()"
       rule: "thunder-plugin-rules.yaml / rule_28"
 
 # -------------------------------------------------------------------------------------------
@@ -2096,9 +2091,39 @@ general_rules:
     name: "No Heavy Work in Initialize()"
     severity: "violation"
     category: "lifecycle_integrity"
-    review_question: "Using semantic reasoning over Initialize() and Configure() implementations, do they avoid heavy blocking operations such as: synchronous network calls, popen() or shell script execution, long-running filesystem scans, D-Bus/IPC calls, or any operation that could take more than a few milliseconds? Initialize() runs on the Thunder main thread and blocks activation of all other plugins until it returns. Heavy work must be deferred to a WorkerPool job dispatched from Initialize(). CRITICAL for OOP plugins: a synchronous COMRPC call from Initialize() to the OOP implementation (e.g. _impl->Configure(configLine)) blocks the Thunder main thread for the ENTIRE duration of the OOP Configure() execution — if the OOP Configure() calls platform_init(), creates GLib/D-Bus proxies, reads device property files, or starts monitoring loops, ALL of that blocking time is paid on the Thunder main thread. For OOP plugins, read the OOP implementation's Configure() body and platform_init() (or equivalent startup function) and apply the same heavy-work check to them."
+    review_question: |
+      Using semantic reasoning over Initialize() and — for OOP plugins — the OOP implementation's Configure() body and any startup function it calls (e.g. platform_init()), check for the following SPECIFIC blocking operations. These are the only things that count as 'heavy work'. Do NOT apply a time-based heuristic or flag anything vaguely described as 'potentially slow'.
+
+      OPERATIONS THAT COUNT AS HEAVY WORK (flag these):
+        - popen(), system(), fork(), exec() or any child-process spawning
+        - Synchronous network calls: socket connect/send/recv, curl_easy_perform(), HTTP requests
+        - sleep(), usleep(), nanosleep(), std::this_thread::sleep_for() — any explicit delay
+        - D-Bus proxy creation: g_dbus_proxy_new_sync(), g_bus_get_sync(), or any GDBus/GIO blocking call
+        - GLib main loop operations: g_main_loop_run(), g_main_context_iteration() in blocking mode
+        - Platform SDK init calls that internally block: names like platform_init(), hal_init(), rdk_init(), device_init() — ONLY flag if you can trace into the body and confirm one of the above operations occurs inside
+        - Synchronous waiting on a condition variable or semaphore with no timeout
+        - Reading an entire large file or scanning a directory tree with blocking I/O
+
+      OPERATIONS THAT DO NOT COUNT AS HEAVY WORK (do not flag these):
+        - _impl->Configure(configLine) — the COMRPC call itself is not heavy; see OOP rule below
+        - service->Register(_notification) — lightweight framework registration
+        - interface->Register(...) — lightweight
+        - service->SubSystems() — lightweight acquire
+        - Config class parsing (FromString, JSON deserialization of small config payloads)
+        - Memory allocation, string operations, simple arithmetic
+        - A blocking call with a known short fixed timeout (e.g. wait up to 100ms for a flag)
+
+      OOP PLUGIN ANALYSIS RULE (mandatory when plugin is OOP):
+        When Initialize() calls _impl->Configure(configLine) or _impl->Register(...) on the OOP implementation, DO NOT flag the COMRPC call itself. Instead:
+        1. Locate the OOP implementation file (e.g. {PluginName}Implementation.cpp or {PluginName}GnomeProxy.cpp)
+        2. Read the Configure() body (or the Register() implementation) in that file
+        3. Apply the HEAVY WORK list above to what Configure() does directly
+        4. If Configure() calls a startup function (platform_init(), hal_init(), etc.), read that function body too and apply the same list
+        5. Only flag if one of the SPECIFIC operations from the HEAVY WORK list is found inside the OOP implementation
+
+      Initialize() runs on the Thunder main thread and blocks activation of all other plugins until it returns. Any heavy work must be deferred to a WorkerPool job dispatched from Initialize().
     review_method: "Read the full relevant code context (control flow, ownership, lifecycle, and threading where applicable) before deciding PASS/FAIL/SUGGEST. Do not use pattern-only checks."
-    evidence_requirement: "Provide exact [File:line] citation for any failure. For OOP plugins, cite both the blocking COMRPC call in Initialize() (e.g. [NetworkManager.cpp:84]) AND the heavy operation inside the OOP Configure()/platform_init() (e.g. [NetworkManagerGnomeProxy.cpp:315])."
+    evidence_requirement: "Provide exact [File:line] citation for any failure, naming the specific blocking operation found (e.g. 'g_dbus_proxy_new_sync() at [NetworkManagerGnomeProxy.cpp:315]'). For OOP plugins, also cite the COMRPC call in Initialize() that chains into the heavy work (e.g. '[NetworkManager.cpp:84] _impl->Configure() chains to blocking D-Bus proxy creation')."
 
   - rule_id: "rule_84"
     name: "No Override of JSONRPC Dispatch Methods"