From 5940efeb518422744b2f59502581366ab8e3cc7f Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Mon, 15 Jun 2026 15:53:20 +0530 Subject: [PATCH 01/15] Added OpenSpec folder for the Rules Review --- openspec/changes/thunder-plugin-qa/design.md | 279 +++ .../changes/thunder-plugin-qa/proposal.md | 105 ++ .../interface/Thunder-interface-rules.md | 758 ++++++++ .../thunder-plugin-qa/specs/interface/spec.md | 262 +++ .../specs/plugin/Thunder-plugin-rules.md | 1556 +++++++++++++++++ .../thunder-plugin-qa/specs/plugin/spec.md | 611 +++++++ .../thunder-plugin-qa/specs/reports/spec.md | 134 ++ openspec/changes/thunder-plugin-qa/tasks.md | 455 +++++ openspec/config.yaml | 20 + 9 files changed, 4180 insertions(+) create mode 100644 openspec/changes/thunder-plugin-qa/design.md create mode 100644 openspec/changes/thunder-plugin-qa/proposal.md create mode 100644 openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md create mode 100644 openspec/changes/thunder-plugin-qa/specs/interface/spec.md create mode 100644 openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md create mode 100644 openspec/changes/thunder-plugin-qa/specs/plugin/spec.md create mode 100644 openspec/changes/thunder-plugin-qa/specs/reports/spec.md create mode 100644 openspec/changes/thunder-plugin-qa/tasks.md create mode 100644 openspec/config.yaml diff --git a/openspec/changes/thunder-plugin-qa/design.md b/openspec/changes/thunder-plugin-qa/design.md new file mode 100644 index 00000000..b7570801 --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/design.md @@ -0,0 +1,279 @@ +# Design: Thunder Plugin QA System + +## Architecture Overview + +Three VS Code Copilot Chat slash commands, each backed by a `.prompt.md` file. +Rules are stored in separate YAML files and loaded at runtime — prompts contain +behaviour logic, YAML files contain the rule data. This separation means rules +can be updated without touching prompt logic. + +``` +User types /thunder-plugin-review Dictionary + │ + ▼ +thunder-plugin-review.prompt.md + │ + ▼ +rules/thunder-plugin-rules.yaml ← 79 unified rules (rule_01 to rule_79) + │ + ▼ +Plugin files in ThunderNanoServices/Dictionary/ + │ + ▼ +Chat report: FAIL citations only, PASS/SKIP as counts + │ + ▼ +Reports/plugin/Dictionary_2026-06-05.csv ← one row per issue, Excel-compatible +``` + +``` +User types /thunder-plugin-rule-manager + │ + ▼ +thunder-plugin-rule-manager.prompt.md ← behaviour: questionnaire + file sync logic + │ collects via vscode_askQuestions + ▼ +User answers (or pastes filled template) + │ + ▼ +Atomically updates 4 files: + thunder-plugin-rules.yaml + thunder-plugin-review.prompt.md + README.md + specs/plugin/spec.md +``` + +## Delivery Mechanism + +**File type:** VS Code Copilot Chat prompt files (`.prompt.md`) +**Registration:** `chat.promptFilesLocations` key in VS Code `settings.json` + set to `ThunderTools/PluginQA/Prompts: true` +**Invocation:** User types `/thunder-plugin-review`, `/thunder-interface-review`, + `/thunder-generate-plugin`, `/thunder-plugin-rule-manager`, or + `/thunder-interface-rule-manager` in Copilot Chat + +**Prompt file frontmatter (required by VS Code):** + +```markdown +--- +title: Thunder Plugin Rule Review +description: Semantic code review for Thunder plugins — understand first, then check +--- +``` + +## Unified Review Methodology (ALL 79 rules) + +Every rule — whether it targets a specific block (rule_01–39) or a broader concern +(rule_40–79) — uses the same "understand first, then check" approach: + +1. **UNDERSTAND** — Read ALL plugin source files first. Build a complete mental model + of the plugin's architecture: lifecycle flow, threading model, ownership patterns, + data flow, and how Initialize/Deinitialize relate. +2. **FOCUS** — For the specific rule, examine the relevant code block WITH full context + already understood. Never examine a block in isolation. +3. **REASON** — Ask the rule's question. If the specific block looks wrong in isolation + but is correct given the full plugin architecture, it's not a real violation. +4. **JUDGE** — A rule FAILs only when the code is genuinely unsafe or incorrect in the + context of the whole plugin. If the developer's approach is valid in context → + downgrade severity and explain in the `reasoning` field. +5. **CITE** — exact [File:line] for any genuine failure +6. **FIX** — show corrected code block, not the whole file + +This means: no block-wise pattern matching, no checking one line without understanding +its surrounding lifecycle. The reviewer must reason like a senior developer who has +read the entire plugin before commenting on any single line. + +## Contextual Judgment + +The JUDGE step implements severity downgrade logic: + +| Scenario | Effective status | +|---|---| +| Rule satisfied → PASS | `PASS` | +| Rule violated, no mitigating context → FAIL | `VIOLATION` (or `WARNING`/`SUGGESTION` per YAML) | +| Rule technically violated but developer's approach is valid in context | downgrade one level; add `reasoning` | +| Rule violated with residual risk but approach is reasonable | downgrade to `WARNING`; add `reasoning` | + +The `reasoning` field in the output block is **required** when severity is downgraded; +it is omitted when no downgrade occurs. Severity is never escalated above the level +defined in the YAML source. + +## YAML Rule Structures + +The YAML file contains two sections that produce identical report output: + +**Phase checkpoints** (39 rules, under `phase_X_checkpoints` keys): +- Fields: `rule_id`, `name`, `severity`, `phase`, `extraction`, `bounded_query`, `verification_logic`, `conditional`, `skip_condition`, `citation`, `fix_template` + +**Holistic Rules (8 sub-phases)** (40 rules, under `general_rules` key): +- Fields: `rule_id`, `name`, `severity`, `category`, `review_question`, `review_method`, `evidence_requirement` + +Both types produce the same YAML output block in the report. The structural +difference is an internal implementation detail — users see one unified list +of findings grouped by file. + +## YAML Interface Rule Structure + +Each rule in `thunder-interface-rules.yaml`: + +```yaml +- rule_id: "rule_17" + name: "IShell AddRef in Initialize" + severity: "violation" # violation | warning | suggestion + phase: "lifecycle" + + extraction: + target: "IShell* member assignment and AddRef() call in Initialize()" + method: "Read Initialize() body looking for _service (or similar IShell* member) assignment" + code_block: "Lines in Initialize() that assign the service pointer" + + bounded_query: + question: "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. Check if the class has an IShell* member variable (e.g. _service, _shell)" + - "2. If no IShell* member → SKIP this checkpoint" + - "3. If IShell* member exists: find the assignment in Initialize() (e.g. _service = service;)" + - "4. Check that AddRef() is called on it immediately after: _service->AddRef();" + - "5. If AddRef() is missing → VIOLATION" + + conditional: true # true = SKIP if prerequisite not found + skip_condition: "Class has no stored IShell* member variable" + + citation: + line_format: "[PluginName.cpp:LINE] IShell* stored but AddRef() not called after assignment" + rule: "thunder-plugin-rules.yaml / rule_17" + + fix_template: | + const string Initialize(PluginHost::IShell* service) { + ASSERT(service != nullptr); + _service = service; + _service->AddRef(); // ADD THIS — must AddRef when storing the pointer + // ... + } +``` + +## YAML Interface Rule Structure + +Each rule in `thunder-interface-rules.yaml`: + +```yaml +- id: "core_12_1" + name: "@json Tag (CRITICAL)" + severity: "violation" + description: | + Without the @json tag, ZERO JSON-RPC code is generated for the interface. + This is the most common critical omission. The tag must appear immediately + above the struct declaration as: // @json 1.0.0 + extraction_logic: | + 1. Search for '// @json' or '/* @json' comment above the interface struct declaration + 2. Check the line immediately preceding the 'struct EXTERNAL I...' line + verification_logic: | + 1. Tag must appear as: // @json 1.0.0 + 2. Must be immediately above the struct declaration (no blank lines between) + 3. If the interface should generate JSON-RPC code and the tag is missing → VIOLATION + 4. If the interface is intentionally not JSON-RPC (pure COM only) → acceptable to omit + violation_pattern: "No @json comment found above interface struct — no RPC code will be generated" + fix_template: | + // @json 1.0.0 ← ADD THIS + struct EXTERNAL IMyInterface : virtual public Core::IUnknown { + enum { ID = RPC::ID_MY_INTERFACE }; + // ... + }; + citation: | + ThunderInterfaces/interfaces/IDictionary.h:LINE — @json 1.0.0 above struct +``` + +NOTE: No `category` field in interface rules — it was removed in v3.2.2 as unused by validation logic. + +## Report Design Decisions + +**Why show failures only?** +Developers fixing bugs need to see what's broken, not a list of 15 passing +checks before finding the 2 that fail. Phase summaries provide the pass count +for completeness without clutter. + +**Why YAML-style per-checkpoint output?** +The structured format (rule_id, question, answer, extracted_code, citation, +fix) makes it easy to scan. Each violation is self-contained: what was checked, +what was found, exactly where, and exactly how to fix it. + +**Citation format:** `[{ActualPluginName}.cpp:{LineNumber}]` +Always uses the real plugin name from the user's command. Never uses placeholder +text like `{PluginName}` in output. + +**Severity indicators:** +- ❌ = Violation (must fix — causes bugs, crashes, or codegen failures) +- ⚠️ = Warning (should fix — best practice violation) +- 💡 = Suggestion (optional — style/consistency) +- ✅ = Phase passed (summary line only, never individual checkpoint) + +## File Scoping Rules (Critical for Correctness) + +**Phase 1 (Module Structure):** +- rule_01 applies ONLY to `Module.h` — checks MODULE_NAME starts with Plugin_ prefix +- rule_02 applies ONLY to `Module.cpp` — checks MODULE_NAME_DECLARATION(BUILD_REFERENCE) +- rule_03 applies ONLY to `Module.h` — checks for `#pragma once` instead of legacy `#ifndef` guard + +**Phase 3 (Class Registration):** +- rule_14 applies ONLY to the main Plugin class (implements PluginHost::IPlugin) +- Internal helper classes (Notification, Sink, Config, etc.) are excluded + +**Conditional checkpoints:** +Checkpoints 4.2, 4.3, 4.5, 4.6, 4.7, 4.8, 4.10, 5.2, 5.4, 5.9, 5C/9.1, 5C/9.2, Phase 6 are conditional. +If the prerequisite is not found (e.g. no stored IShell pointer for 4.2), the +checkpoint SKIPS — it does not fail. + +## Plugin Generator Design + +**Why `vscode_askQuestions` not chat?** +Parameters collected via VS Code's native dialog appear as a structured form, +not a back-and-forth conversation. This is faster and avoids parsing chat text. + +**Why interactive PSG mode (not --config)?** +PSG's interactive mode is stable. The --config path has known bugs. Running PSG +interactively and feeding answers programmatically is more reliable. + +**Include path fix:** +PSG has a known bug where generated .h files have incorrect include paths. +After PSG completes, the generator automatically fixes these — no other +modifications are made to generated files. + +**Answer mapping:** +- "Yes"/"No" from VS Code dialog → "y"/"n" for PSG prompts +- Empty OutputDirectory → default to ThunderNanoServices +- Multiple interface paths → feed one per PSG prompt, then Enter on empty line +- "Does your plugin rely on Thunder subsystems?" → "y" if ANY of Preconditions, + Terminations, or Controls is non-empty; "n" otherwise + +## Setup Script Design + +All three scripts (`.ps1`, `.sh`, `.py`) do the same thing: + +1. Detect VS Code settings.json location (platform-specific paths, also checks VS Code Insiders) +2. Create a timestamped backup of existing settings.json +3. Parse the JSON safely (handle missing file, handle existing `chat.promptFilesLocations`) +4. Merge the new entry: `"ThunderTools/PluginQA/Prompts": true` +5. Write back to settings.json +6. Print success message and next steps (Ctrl+Shift+P → Reload Window) + +**Idempotent:** Running the script multiple times does not duplicate entries. +**Non-destructive:** All other existing settings are preserved. + +## YAML File Versioning + +- `thunder-plugin-rules.yaml`: version 3.0.0 + - 39 checkpoints, organisation: Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, + Phase5C:2, Phase6:3, Phase7:1, Phase8:1 + - New checkpoints added over v1.0.0: rule_08 (nullptr after Release), rule_09–10 (COM ownership + no-throw), rule_16 + (INTERFACE_MAP + JSONRPC), rule_21/4_6/4_7/4_9/4_10/4_11/4_12 (full lifecycle + correctness), rule_31 (Unavailable in SinkType), + rule_34 (connectionId guard), + rule_36/6_3 (JSON::Container + no magic numbers) + +- `thunder-interface-rules.yaml`: version 3.2.2 + - 15 core rules + 4 advisory = 19 total + - Core::hresult MANDATORY for Thunder 5.0+ @json interfaces + - advisory_m5_1 explicitly excludes std::vector (core_17_1 covers that) + - No `category` field — removed in v3.2.2 \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/proposal.md b/openspec/changes/thunder-plugin-qa/proposal.md new file mode 100644 index 00000000..7dabc41e --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/proposal.md @@ -0,0 +1,105 @@ +# Proposal: Thunder PluginQA System + +## Intent + +Thunder plugin development lacks automated validation. Developers ship plugins +with common, repeatable bugs — missing ASSERT in Initialize(), wrong NULL vs +nullptr, hardcoded paths, missing observer cleanup, wrong CMake ordering — that +reviewers catch manually in code review. This costs review time and introduces +production defects. + +We need a structured, AI-driven validation layer that runs inside VS Code via +GitHub Copilot Chat, checking plugins against proven Thunder best practices +automatically. + +## Scope + +**Rule managers** (`/thunder-plugin-rule-manager`, `/thunder-interface-rule-manager`) +- Guided questionnaire to add, update, or remove rules via `vscode_askQuestions` +- Document template fast path: user can paste a filled `.md` template instead of answering all questions +- Automatic classification: determines whether a new rule belongs as an automated checkpoint vs manual review rule (plugin manager), or core vs advisory (interface manager) +- UPDATE flow: displays the current rule annotated with field numbers, asks only which fields to change +- Keeps all affected files in sync atomically: YAML + prompt + README + spec + +**Plugin checkpoint validation (`/thunder-plugin-review`)** +- 79 unified rules numbered sequentially (rule_01 to rule_79) +- Each rule: read code semantically -> decide pass/fail -> cite exact line on failure +- Phases: Module Structure, Code Style, Class Registration, Lifecycle, Implementation, + COM Interface Rules, Out-of-Process, Configuration, CMake, General +- Single unified report - no separate sections +- Report shows ONLY failures - PASS/SKIP appear as counts in summary table only + +**COM interface validator (`/thunder-interface-review`)** +- 19 rules: 15 core (VIOLATION level) + 4 advisory +- Validates Thunder COM interfaces in ThunderInterfaces/interfaces/ +- Covers: file structure, @json tag, @restrict on vectors, return types, ID registration, + nested event interfaces, binary compatibility, no std::map, explicit integer widths + +**Plugin skeleton generator (`/thunder-generate-plugin`)** +- Interactive: collects parameters via VS Code `vscode_askQuestions` (NOT chat) +- Runs ThunderTools PluginSkeletonGenerator.py in interactive mode +- Auto-fixes include paths in generated .h files (known PSG bug workaround) + +**Setup script** (Python cross-platform) +- `setup-prompts.py` registers prompt files with VS Code via `chat.promptFilesLocations` in settings.json +- Works on Windows, macOS, and Linux (stdlib only, no dependencies) +- Safe: creates timestamped backup, preserves existing settings, idempotent + +**YAML rule definitions** (loaded by prompts at runtime, not embedded) +- `thunder-plugin-rules.yaml` — 79 unified rules (v3.3.0) +- `thunder-interface-rules.yaml` — 19 interface rules (v3.2.2) + +**Review reports** (CSV, generated after each review run) +- Single CSV file per review, Excel-compatible, UTF-8, CRLF +- Plugin report: `Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` — one row per issue +- Interface report: `Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` — one row per violated rule +- Columns: No, Plugin/Interface, Date, Phase/Category, Checkpoint/Rule ID, Rule Name, Status, Severity, File, Line, Citation, Issue Description, Fix Summary, Reasoning +- PASS and SKIP rows excluded — only failures logged +- Post-generation: chat message with count summary + `Start-Process` command to open in Excel + +## Out of Scope + +- CI/CD pipeline integration (future) +- Non-Copilot AI tools (future) +- Auto-fix application (validation only, fixes are shown but not applied) + +## Approach + +Use VS Code `.prompt.md` files as slash commands. Each prompt loads its YAML +rule definitions at runtime so rules can be updated without touching prompt +logic. Plugin validation uses semantic code review: read code as a human +developer and reason about meaning. All 79 rules produce the same unified +output format — no distinction between "automated" and "manual" in the report. + +All checkpoint verification uses semantic reasoning — the validator reads code +as a human developer and reasons about meaning. No regex or text search is used +as the primary detection method. + +Severity output reflects contextual judgment: if a developer's approach +technically violates a rule but is valid and reasonable in context, the +effective severity is downgraded (violation→suggestion or violation→warning) +and a `reasoning` field explains the rule, the developer's approach, and why +it is acceptable. Severity is never escalated above what the YAML defines. + +## Delivery Structure + +``` +ThunderTools/PluginQA/ +├── README.md +├── setup-prompts.py (cross-platform, Python 3) +├── Prompts/ +│ ├── thunder-plugin-review.prompt.md +│ ├── thunder-interface-review.prompt.md +│ ├── thunder-generate-plugin.prompt.md +│ ├── thunder-plugin-rule-manager.prompt.md +│ └── thunder-interface-rule-manager.prompt.md +├── rules/ +│ ├── thunder-plugin-rules.yaml +│ └── thunder-interface-rules.yaml +└── Reports/ + ├── plugin/ + │ └── {PluginName}_{YYYY-MM-DD}.csv + └── interface/ + └── {InterfaceName}_{YYYY-MM-DD}.csv +``` + └── interface/ \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md new file mode 100644 index 00000000..51616b1f --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -0,0 +1,758 @@ +# Thunder Interface Validation Rules + +**Version:** 3.2.2 +**Status:** For Review + +## Description + +Rules for validating Thunder COM interface headers in `ThunderInterfaces/interfaces/`. +Every rule uses semantic reasoning — read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. + +## Changelog + +| Version | Changes | +|---------|---------| +| v3.2.2 (current) | Removed category field from all rules (unused by validation logic); Rule names standardised to Title Case throughout; advisory_m5_1 clarified: explicitly does NOT apply to std::vector (covered by core_17_1) | +| v3.2.1 | Added core_17_1: @restrict mandatory with std::vector parameters; advisory_m5_1 scoped to non-vector parameters only | +| v3.2.0 | Added core_16_1: explicit integer widths (uint32_t not int); Added advisory_m5_1: @restrict for non-vector constrained parameters | +| v3.1.0 | Added core_15_1: no std::map in interfaces; Strengthened core_5_1: Core::hresult mandatory for @json interfaces in Thunder 5.0+ | +| v3.0.2 | Initial public release with 13 core rules and 3 advisory rules | + +--- + +## Core Rules (17) — Severity: Violation + +### core_1_1 — File and Namespace Structure + +**Severity:** violation + +**Description:** +Thunder interface headers must follow the standard file and namespace structure: +- File must be in `ThunderInterfaces/interfaces/` (or `qa_interfaces/` for QA) +- Interface must be declared inside the `WPEFramework::Exchange` namespace +- File name must match the interface name: `IFoo.h` for `struct EXTERNAL IFoo` +- No implementation code in interface headers — pure declarations only + +**Extraction Logic:** +1. Read the full interface header file +2. Identify the namespace block(s) and their names +3. Note the file name and the primary interface struct name +4. Check for any non-declaration code (function implementations, static variables, etc.) + +**Verification Logic:** +1. Verify the file is inside the `WPEFramework::Exchange` namespace +2. Verify the primary interface struct name matches the file name (e.g. `IDictionary` in `IDictionary.h`) +3. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations +4. If any of these conditions fail → VIOLATION + +**Violation Pattern:** Interface not in WPEFramework::Exchange namespace, file name mismatch, or implementation code present + +**Fix Example:** +```cpp +// WRONG: +namespace WPEFramework { + // Missing Exchange namespace + struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; +} + +// Correct: +namespace WPEFramework { + namespace Exchange { + struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; + } +} +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Exchange namespace and file naming + +--- + +### core_2_1 — Interface Declaration Shape + +**Severity:** violation + +**Description:** +Thunder COM interfaces must follow the correct declaration shape: +- Must be a struct (not class) with the `EXTERNAL` macro +- Must inherit virtually from `Core::IUnknown` (or from another Thunder interface) +- Must declare a nested enum with the ID value (`RPC::ID_*`) +- All methods must be pure virtual + +**Extraction Logic:** +1. Read the interface struct declaration +2. Examine the struct keyword, EXTERNAL macro, and inheritance list +3. Check for the nested `enum { ID = RPC::ID_* }` +4. Examine all method declarations for pure virtual (`= 0`) + +**Verification Logic:** +1. Verify the declaration uses `struct EXTERNAL IName` +2. Verify inheritance is `virtual public Core::IUnknown` or a Thunder interface +3. Verify a nested enum contains `ID = RPC::ID_*` +4. Verify all methods are pure virtual (`= 0`) +5. If any fails → VIOLATION + +**Violation Pattern:** Interface missing EXTERNAL macro, wrong inheritance, missing ID enum, or non-pure-virtual methods + +**Fix Example:** +```cpp +// WRONG: +class IDictionary : public Core::IUnknown { + enum { ID = 0x100 }; // ← raw value, not RPC::ID_* + virtual void Get(const string& key) { } // ← not pure virtual + +// Correct: +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY }; + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +}; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — struct EXTERNAL shape + +--- + +### core_3_1 — Interface ID Registration + +**Severity:** violation + +**Description:** +Every Thunder COM interface must have a unique numeric ID registered in `RPC::IDs` (in `ThunderInterfaces/interfaces/ids.h` or equivalent). The ID enum value in the struct must reference the registered `RPC::ID_*` constant. Sub-interfaces (`INotification`, `ICallback` nested in a parent) must also have their own unique IDs. + +**Extraction Logic:** +1. Read the interface struct declaration and note its ID value +2. Check whether the ID value uses an `RPC::ID_*` constant +3. Check `ids.h` (or the ID registration file) for the corresponding entry + +**Verification Logic:** +1. Verify the struct `enum { ID = RPC::ID_* }` uses a named constant, not a raw number +2. Verify the ID is registered in ThunderInterfaces `ids.h` (or equivalent) +3. Verify the ID is unique — no other interface uses the same value +4. Nested `INotification` and `ICallback` interfaces must also have their own IDs +5. If any condition fails → VIOLATION + +**Violation Pattern:** Interface ID missing from IDs registration, uses raw number, or is not unique + +**Fix Example:** +```cpp +// WRONG: +enum { ID = 0x100 }; // ← raw number, not registered + +// Correct: +enum { ID = RPC::ID_DICTIONARY }; +// AND in ids.h: +// ID_DICTIONARY = RPC_ID_OFFSET + N, +``` + +**Citation:** `ThunderInterfaces/interfaces/ids.h` — ID_DICTIONARY registration + +--- + +### core_4_1 — Pure Virtual Methods Only + +**Severity:** violation + +**Description:** +Thunder COM interface methods must be pure virtual (`= 0`). No default implementations, no inline code, no static methods, no non-virtual methods. The interface is a pure abstract contract — all implementation is in the implementing class, not in the interface. + +**Extraction Logic:** +1. Read all method declarations in the interface struct +2. Check each method for the `= 0` specifier +3. Look for any inline code, default implementations, or static methods + +**Verification Logic:** +1. Every method in the interface must end with `= 0` +2. No method may have a body (even `{}`) +3. No static methods allowed in interfaces +4. No non-virtual methods (constructors, operators excepted) +5. If any violation → VIOLATION + +**Violation Pattern:** Non-pure-virtual method, inline implementation, or static method in COM interface + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult Get(const string& key, string& value) { + value = "default"; // ← inline implementation + return Core::ERROR_NONE; +} + +// Correct: +virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — pure virtual methods + +--- + +### core_5_1 — Return Type Conventions + +**Severity:** violation + +**Description:** +In Thunder 5.0+, all COM interface methods annotated with `@json` (i.e. methods that generate JSON-RPC code) MUST return `Core::hresult`. Void return types and other return types are not allowed for JSON-RPC-generating methods. Methods in pure COM interfaces (no `@json`) should also use `Core::hresult` for error reporting where applicable. + +**Extraction Logic:** +1. Read all interface method declarations +2. Note which methods are under a `@json`-tagged interface or have `@json` annotations +3. Check the return type of each method + +**Verification Logic:** +1. For interfaces tagged with `@json` (or in Thunder 5.0+ contexts), all methods must return `Core::hresult` +2. Void return types are not allowed for JSON-RPC methods +3. Custom return types (not `Core::hresult`) for non-status purposes are not allowed — use `@out` parameters instead +4. If any JSON-RPC method lacks `Core::hresult` return type → VIOLATION + +**Violation Pattern:** Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+ + +**Fix Example:** +```cpp +// WRONG: +virtual void SetVolume(const uint8_t volume) = 0; +virtual string GetStatus() = 0; + +// Correct: +virtual Core::hresult SetVolume(const uint8_t volume) = 0; +virtual Core::hresult GetStatus(string& status /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IAudioOutput.h` — Core::hresult return types + +--- + +### core_6_1 — Const Correctness + +**Severity:** violation + +**Description:** +Interface methods must use const correctly: +- Input-only parameters should be const (`const string& key`) +- Output parameters should be non-const references (`string& value`) +- Methods that do not modify the object should be const (though pure virtual const is rare) +- `@out` parameters must be non-const references to allow the implementation to write + +**Extraction Logic:** +1. Read all method parameter declarations +2. Examine const qualifiers on each parameter +3. Identify parameters marked `@out` and verify they are non-const references +4. Identify input parameters and verify they are const where appropriate + +**Verification Logic:** +1. `@out` parameters must be non-const references (`string& value`, not `const string& value`) +2. Input parameters that are passed by value or const ref are correct +3. Non-const reference parameters without `@out` indicate potential API design issues +4. If `@out` parameters are const (preventing write) → VIOLATION + +**Violation Pattern:** @out parameter declared const preventing the implementation from writing the output value + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult Get(const string& key, const string& value /* @out */) = 0; +// ^^^^^ ← const prevents writing + +// Correct: +virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — const correctness on @out params + +--- + +### core_9_1 — Thunder Type Conventions + +**Severity:** violation + +**Description:** +Thunder interfaces must use Thunder type aliases, not `std::` types directly: +- `string` (not `std::string`) for text +- `Core::hresult` (not `HRESULT` or `int`) for error returns +- `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` (not `int`, `long`, `short`) +- `bool` (acceptable) but not `BOOL` + +Using `std::string` in interfaces breaks cross-ABI compatibility. + +**Extraction Logic:** +1. Read all method parameter types and return types in the interface +2. Identify any `std::` type usage (`std::string`, `std::vector` without @restrict review) +3. Check for non-Thunder integer types (`int`, `long`, `short`, `BOOL`, `HRESULT`) + +**Verification Logic:** +1. `std::string` in interface parameters → VIOLATION (use `string`) +2. `HRESULT` or raw `int` for error codes → VIOLATION (use `Core::hresult`) +3. Non-width-specific integer types (`int`, `long`) for interface params → check core_16_1 +4. `BOOL` → VIOLATION (use `bool`) +5. If `std::string` found → VIOLATION + +**Violation Pattern:** std::string used in interface — must use Thunder string type alias + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult Get(const std::string& key, std::string& value) = 0; + +// Correct: +virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Thunder string type alias + +--- + +### core_10_1 — Register/Unregister Patterns + +**Severity:** violation + +**Description:** +Notification interfaces follow two patterns: +- **INotification (1:many observer):** `Register(INotification*)` and `Unregister(INotification*)` for multiple simultaneous subscribers +- **ICallback (1:1 callback):** `Callback(ICallback*)` sets a single callback (`nullptr` to clear) + +Register/Unregister must take a pointer to the notification interface. ICallback::Callback() replaces the previous callback, so no Unregister needed. + +**Extraction Logic:** +1. Read the interface for Register/Unregister/Callback method declarations +2. Identify whether it follows the INotification (1:many) or ICallback (1:1) pattern +3. Check the method signatures + +**Verification Logic:** +1. INotification pattern: must have both `Register(INotification*)` and `Unregister(INotification*)` +2. ICallback pattern: must have `Callback(ICallback*)` accepting nullptr to clear +3. Register without matching Unregister → VIOLATION +4. If notification registration pattern is non-standard → VIOLATION + +**Violation Pattern:** Register(INotification*) present but Unregister(INotification*) missing, or non-standard notification pattern + +**Fix Example:** +```cpp +// WRONG: (Unregister missing) +virtual Core::hresult Register(INotification* notification) = 0; +// Missing: Unregister + +// Correct — INotification (1:many): +virtual Core::hresult Register(INotification* notification) = 0; +virtual Core::hresult Unregister(const INotification* notification) = 0; + +// Correct — ICallback (1:1, nullptr clears): +virtual Core::hresult Callback(ICallback* callback) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Register/Unregister pattern + +--- + +### core_11_1 — Nested Event Interfaces + +**Severity:** violation + +**Description:** +Event/notification interfaces nested inside a parent COM interface must: +- Have the `@event` tag (`// @event`) above the struct declaration +- Use `EXTERNAL` in the struct declaration +- Have their own unique ID in the RPC ID list +- Inherit from `Core::IUnknown` (not from the parent interface) + +Missing `@event` prevents the code generator from emitting event dispatch code. + +**Extraction Logic:** +1. Read the interface for any nested struct declarations +2. For each nested struct that represents a notification/event (INotification, ICallback, etc.) +3. Check for the `@event` comment tag above the declaration +4. Check for `EXTERNAL` in the declaration +5. Check for a nested `enum { ID = RPC::ID_* }` + +**Verification Logic:** +1. Every nested event/notification interface must have `// @event` immediately above the struct +2. Must use `EXTERNAL` in the declaration +3. Must have its own ID in the RPC ID list +4. Must inherit from `Core::IUnknown` +5. If any condition fails → VIOLATION + +**Violation Pattern:** @event tag missing on nested notification interface, or missing EXTERNAL/ID + +**Fix Example:** +```cpp +// WRONG: +struct INotification : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY_NOTIFICATION }; // ← @event tag missing + +// Correct: +// @event +struct EXTERNAL INotification : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY_NOTIFICATION }; + virtual void ValueChanged(const string& key, const string& value) = 0; +}; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — @event tag on INotification + +--- + +### core_12_1 — @json Tag (CRITICAL) + +**Severity:** violation + +**Description:** +Without the `@json` tag, ZERO JSON-RPC code is generated for the interface. This is the most common critical omission. The tag must appear immediately above the struct declaration as: `// @json 1.0.0`. No blank lines are allowed between the `@json` tag and the struct declaration. + +If an interface intentionally does not need JSON-RPC (pure COM only), the absence of `@json` is acceptable — but this must be an intentional design choice. + +**Extraction Logic:** +1. Read the lines immediately above the `struct EXTERNAL I...` declaration +2. Look for a `// @json` comment with a version number +3. Check there are no blank lines between the tag and the struct declaration + +**Verification Logic:** +1. If the interface is intended to generate JSON-RPC code: verify `// @json N.N.N` appears immediately above the struct declaration +2. No blank lines between the `@json` tag and the struct line +3. If the interface is intentionally JSON-RPC-free (pure COM) → acceptable, note as design choice +4. If `@json` is missing from an interface that should have it → VIOLATION + +**Violation Pattern:** @json tag missing above interface struct declaration — no JSON-RPC code will be generated + +**Fix Example:** +```cpp +// WRONG: +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + // ← @json tag missing — no RPC code will be generated + +// Correct: +// @json 1.0.0 +struct EXTERNAL IDictionary : virtual public Core::IUnknown { +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h:LINE` — @json 1.0.0 above struct + +--- + +### core_13_1 — Binary Compatibility + +**Severity:** violation + +**Description:** +Thunder COM interfaces are binary interfaces — once released, they cannot be changed in backward-incompatible ways without breaking all compiled clients: +- Methods must not be reordered (vtable order is fixed) +- Methods must not be removed (creates vtable holes) +- Method signatures must not change (parameter types/count/order) +- New methods must be added at the END of the interface + +Binary-incompatible changes require creating a new interface version (`IFoo2`). + +**Extraction Logic:** +1. Read the interface method declarations in order +2. If comparing against a previous version: note any additions, removals, or reorderings +3. For new interfaces: verify method ordering follows a logical grouping with extensibility in mind + +**Verification Logic:** +1. When reviewing against a baseline: check for removed methods, reordered methods, or changed signatures +2. New methods in a released interface must be at the end +3. If a released interface has been structurally modified → VIOLATION +4. For new (unreleased) interfaces: recommend logical method ordering for future extensibility + +**Violation Pattern:** Released interface has methods removed, reordered, or signatures changed — breaks binary compatibility + +**Fix Example:** +```cpp +// WRONG: (removed a method or changed signature in a released interface) +// v1: virtual Core::hresult Get(const string& key, string& value) = 0; +// v2: virtual Core::hresult Get(const string& key, string& value, bool caseSensitive) = 0; +// ^^^^^^^^^^^^^^^^^ ← changed signature + +// Correct: create a new interface version +struct EXTERNAL IDictionary2 : virtual public IDictionary { + enum { ID = RPC::ID_DICTIONARY2 }; + virtual Core::hresult GetCaseSensitive(const string& key, const bool caseSensitive, string& value /* @out */) = 0; +}; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — binary compatibility + +--- + +### core_14_1 — No AddRef/Release Redeclaration + +**Severity:** violation + +**Description:** +`AddRef()` and `Release()` are inherited from `Core::IUnknown` and must NOT be redeclared in interface structs. Redeclaring them creates a separate vtable entry, breaking COM reference counting and causing crashes. + +**Extraction Logic:** +1. Read all method declarations in the interface struct (including nested structs) +2. Look for any `AddRef()` or `Release()` declarations + +**Verification Logic:** +1. The interface must not declare `AddRef()` or `Release()` +2. These methods are inherited from `Core::IUnknown` via the virtual inheritance chain +3. If `AddRef()` or `Release()` appears as an interface method → VIOLATION + +**Violation Pattern:** AddRef() or Release() redeclared in interface — must be inherited from Core::IUnknown only + +**Fix Example:** +```cpp +// WRONG: +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE + virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual Core::hresult Get(const string& key, string& value) = 0; +}; + +// Correct: +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY }; + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +}; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — AddRef/Release inherited from Core::IUnknown + +--- + +### core_15_1 — No std::map in Interfaces + +**Severity:** violation + +**Description:** +`std::map` (and other `std::` associative containers) must not appear as interface method parameters or return types. `std::map` is not serialisable across process boundaries via Thunder's RPC mechanism and causes code generation failures. Use structured types, repeated calls, or JSON containers instead. + +**Extraction Logic:** +1. Read all method parameter types in the interface +2. Look for `std::map`, `std::unordered_map`, `std::multimap`, or similar associative containers + +**Verification Logic:** +1. Any `std::map` or similar associative container in method parameters → VIOLATION +2. Consider whether a `Core::JSON::Container` or repeated method calls can replace it +3. If `std::map` found in interface → VIOLATION + +**Violation Pattern:** std::map used in interface parameter — not serialisable across process boundaries + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult GetAll(std::map& values /* @out */) = 0; + +// Correct: use repeated queries or a structured type +virtual Core::hresult GetKeys(RPC::IStringIterator*& keys /* @out */) = 0; +virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — no std::map in interfaces + +--- + +### core_16_1 — Explicit Integer Widths + +**Severity:** violation + +**Description:** +Interface method parameters and return values must use explicit-width integer types (`uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `int32_t`, etc.) rather than platform-dependent types (`int`, `long`, `short`, `size_t`, `unsigned int`). Implicit-width types change size between platforms and break binary compatibility. + +**Extraction Logic:** +1. Read all method parameter types and return types +2. Identify any integer parameters using platform-dependent types + +**Verification Logic:** +1. `int`, `long`, `short`, `unsigned int`, `unsigned long`, `size_t` in interface parameters → VIOLATION +2. `char` is acceptable for character data; `bool` is acceptable +3. `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `int32_t` etc. are correct +4. If platform-dependent integer type found → VIOLATION + +**Violation Pattern:** Platform-dependent integer type (int, long, short) used in interface — must use explicit-width types (uint32_t, etc.) + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult SetTimeout(const int timeout) = 0; +virtual Core::hresult GetSize(unsigned int& size /* @out */) = 0; + +// Correct: +virtual Core::hresult SetTimeout(const uint32_t timeout) = 0; +virtual Core::hresult GetSize(uint32_t& size /* @out */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IVolume.h` — explicit integer widths + +--- + +### core_17_1 — @restrict Mandatory with std::vector + +**Severity:** violation + +**Description:** +Every interface method parameter of type `std::vector` (or `Core::JSON::ArrayType` equivalent) MUST be annotated with `/* @restrict:N */` where N is the maximum allowed element count. Without `@restrict`, the code generator cannot produce safe bounds-checking code and may generate unbounded deserialization that is exploitable. + +> **Note:** This rule specifically applies to `std::vector`. Non-vector parameters with optional constraints are covered by advisory_m5_1. + +**Extraction Logic:** +1. Read all method parameter declarations +2. Identify any parameters of type `std::vector` +3. Check for the `@restrict` comment annotation on each vector parameter + +**Verification Logic:** +1. Every `std::vector` parameter must have `/* @restrict:N */` annotation +2. N must be a positive integer representing the maximum element count +3. Missing `@restrict` on `std::vector` → VIOLATION +4. `@restrict` on non-vector types is advisory (see advisory_m5_1) + +**Violation Pattern:** std::vector parameter missing @restrict annotation — required for safe bounds checking + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult GetItems(std::vector& items /* @out */) = 0; + +// Correct: +virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IPackager.h` — @restrict on std::vector + +--- + +## Advisory Rules (4) + +### advisory_m1_1 — Single Responsibility Principle + +**Severity:** violation + +**Description:** +Each COM interface should have a single, clearly defined responsibility. An interface that mixes unrelated concerns (e.g. audio control + network management) is harder to implement, test, and maintain. If an interface is doing too many unrelated things, it should be split into multiple focused interfaces. + +**Extraction Logic:** +1. Read the full interface and identify all the method groups +2. Reason about whether all methods serve a single coherent purpose +3. Look for method groups that could logically belong to separate interfaces + +**Verification Logic:** +1. Reason about the interface's overall purpose from its name and method set +2. If methods clearly belong to two or more distinct responsibilities → VIOLATION +3. Minor convenience methods on an otherwise focused interface are acceptable +4. Apply judgment: is the mixing of concerns gratuitous or is there a clear design reason? + +**Violation Pattern:** Interface mixes multiple unrelated responsibilities — consider splitting into focused interfaces + +**Fix Example:** +```cpp +// WRONG: IDictionaryAndNetwork mixes dictionary and network concerns +struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value) = 0; + virtual Core::hresult GetNetworkStatus(string& status) = 0; // ← unrelated +}; + +// Correct: split into IDictionary and INetwork +``` + +**Citation:** `ThunderInterfaces/interfaces/IHdmiCecSink.h` — single responsibility + +--- + +### advisory_m2_1 — Enum Underlying Types + +**Severity:** warning + +**Description:** +Enums used in interface parameters or return types should use explicit underlying types (`: uint8_t`, `: uint32_t`) for ABI stability. + +**Exception:** the anonymous ID enum inside the interface struct (`enum { ID = RPC::ID_* }`) must NOT have an explicit underlying type — this is by Thunder convention. Only named enums that are used as parameter types need explicit underlying types. + +**Extraction Logic:** +1. Read all enum declarations in the interface header +2. Identify named enums used as method parameter types +3. Identify the anonymous ID enum `{ ID = RPC::ID_* }` +4. Check for explicit underlying types on named enums + +**Verification Logic:** +1. Anonymous ID enum (`enum { ID = RPC::ID_* }`) must NOT have explicit type — skip it +2. Named enums used as parameter types should have explicit underlying types +3. If a named enum used as a parameter lacks an explicit underlying type → WARNING +4. Apply judgment: if the enum range clearly fits a known type, it is advisable + +**Violation Pattern:** Named enum used in interface parameter lacks explicit underlying type — consider adding : uint8_t or : uint32_t + +**Fix Example:** +```cpp +// WRONG: (named enum without explicit type) +enum State { IDLE, ACTIVE, ERROR }; +virtual Core::hresult SetState(const State state) = 0; + +// Correct: +enum State : uint8_t { IDLE = 0, ACTIVE = 1, ERROR = 2 }; +virtual Core::hresult SetState(const State state) = 0; + +// The ID enum stays anonymous (by convention): +enum { ID = RPC::ID_MY_INTERFACE }; +``` + +**Citation:** `ThunderInterfaces/interfaces/IAVInput.h` — explicit enum underlying type + +--- + +### advisory_m3_1 — No Exceptions + +**Severity:** violation + +**Description:** +Thunder COM interfaces and their implementations must not use C++ exceptions. Exceptions cannot cross COM/RPC process boundaries safely. All error conditions must be reported via `Core::hresult` return values. Exception specifications (`throw(...)`, `noexcept`) are irrelevant — the real issue is that `throw` statements must not appear in COM implementation code. + +**Extraction Logic:** +1. Read all interface method signatures and any associated implementation hints +2. Check for exception specifications or throw annotations +3. If implementation files are accessible, check for throw statements + +**Verification Logic:** +1. No exception specifications that imply throws (`throw(...)`) on interface methods +2. `noexcept` specifications are acceptable (they prevent exceptions from propagating) +3. In implementation code: no throw statements in COM method implementations +4. If throw appears in COM code → VIOLATION + +**Violation Pattern:** Exception specification or throw statement in COM interface or implementation — use Core::hresult for error reporting + +**Fix Example:** +```cpp +// WRONG: +virtual Core::hresult Get(const string& key, string& value) throw(std::exception) = 0; + +// Correct: +virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +// Implementation: return Core::ERROR_NOT_FOUND; instead of throwing +``` + +**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — no exceptions in COM interfaces + +--- + +### advisory_m5_1 — @restrict for Non-vector Params + +**Severity:** warning + +**Description:** +Parameters with natural upper bounds (strings with max length, integers with max value, buffers with max size) may benefit from `@restrict` annotations to communicate constraints to callers and enable bounds checking in generated code. + +> **NOTE:** This rule applies to non-`std::vector` parameters only. `std::vector` parameters are covered by core_17_1 where `@restrict` is MANDATORY. This rule is advisory for non-vector types where bounds are meaningful. + +**Extraction Logic:** +1. Read all method parameter declarations +2. Skip `std::vector` parameters (those are covered by core_17_1) +3. For remaining parameters, reason about whether they have natural upper bounds +4. Check for `@restrict` annotations on constrained parameters + +**Verification Logic:** +1. Do NOT apply this rule to `std::vector` parameters — core_17_1 covers those +2. For string parameters used as identifiers, keys, or names: consider whether a max length makes sense +3. For integer count/size parameters: consider whether a max value is meaningful +4. If a non-vector parameter clearly has a natural bound but lacks `@restrict` → WARNING (advisory) +5. Apply judgment: not every parameter needs `@restrict` — only those with meaningful constraints + +**Violation Pattern:** Non-vector parameter with natural upper bound lacks @restrict annotation (advisory) + +**Fix Example:** +```cpp +// Without restriction (acceptable for many cases): +virtual Core::hresult SetName(const string& name) = 0; + +// With @restrict for bounded parameters: +virtual Core::hresult SetName(const string& name /* @restrict:64 */) = 0; + +// Note: for std::vector, @restrict is MANDATORY (see core_17_1): +virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; +``` + +**Citation:** `ThunderInterfaces/interfaces/IVolumeControl.h` — @restrict on bounded params diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md new file mode 100644 index 00000000..218e58e3 --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -0,0 +1,262 @@ +# Delta for Interface Validation and Plugin Generation + +## ADDED Requirements + +--- + +### Requirement: COM interface validator command +The system MUST provide a `/thunder-interface` slash command that validates +a Thunder COM interface header against 19 rules (15 core + 4 advisory). + +#### Scenario: Interface with critical violations +- GIVEN a Thunder interface file with a missing `@json` tag +- WHEN `/thunder-interface` runs +- THEN it reports under `🔴 Violations (Must Fix)`: + `[IMyInterface.h:LINE] Missing @json tag — ZERO RPC code will be generated` +- AND reports under `✅ Validated` all passing rules + +#### Scenario: Interface with vector without @restrict +- GIVEN an interface method with a `std::vector` parameter lacking `@restrict` +- WHEN validation runs checkpoint core_17_1 +- THEN it reports VIOLATION: `std::vector without @restrict (MANDATORY)` +- AND shows the fix: add `/* @restrict:... */` annotation + +#### Scenario: All 19 rules applied in order +- GIVEN any Thunder interface file +- WHEN the validator runs +- THEN it applies all 19 rules loaded from `thunder-interface-rules.yaml` in order: + core_1_1 through core_17_1 (15 core), then advisory_m1_1 through advisory_m5_1 (4 advisory) +- AND groups output into: 🔴 Violations, 🟡 Warnings, 🟢 Suggestions, ✅ Validated, Compatibility Notes + +--- + +### Requirement: 19 interface rules stored in YAML (v3.2.2) +The YAML file MUST contain all 19 rules, each with: +id, name, severity, description, extraction_logic, +verification_logic, violation_pattern, fix_template, and real code examples. + +All rule verification MUST use semantic reasoning — the validator MUST read the +interface header in full and reason about the code as a human reviewer would. +Regex or text search MUST NOT be used as the primary detection mechanism. +Each rule's `extraction_logic` and `verification_logic` fields MUST describe +reading and understanding the code, not searching for patterns. + +The validator MUST apply contextual judgment (JUDGE step): if a developer's approach +technically violates a rule but is valid and reasonable in their specific context, +the severity MUST be downgraded (violation→warning or violation→suggestion) and a +`Reasoning` field MUST be populated explaining the rule, the developer's approach, +and why it is acceptable. Severity is NEVER escalated above the YAML-defined level. + +The `Status` field in output MUST reflect the **effective** severity after contextual +judgment: `violation`→`VIOLATION`, `warning`→`WARNING`, `suggestion`→`SUGGESTION`. +After a downgrade the downgraded level is used. Emoji prefix must match: 🔴 VIOLATION, +🟡 WARNING, 🟢 SUGGESTION. + +#### Scenario: Core rules covered +- 15 core rules at violation level: file/namespace structure, interface declaration shape, + interface ID registration (nested INotification + ICallback), pure virtual methods, + return type conventions (Core::hresult mandatory for @json interfaces in Thunder 5.0+), + const correctness, Thunder type conventions (string not std::string), + Register/Unregister patterns, nested event interfaces (@event tag required), + @json tag (CRITICAL — without it zero RPC code is generated), binary compatibility, + no AddRef/Release redeclaration, no std::map, explicit integer widths (uint32_t not int), + @restrict mandatory with all std::vector parameters + +#### Scenario: Advisory rules covered +- advisory_m1_1: Single responsibility principle (violation) +- advisory_m2_1: Enum underlying types — exclude anonymous ID enum (warning) +- advisory_m3_1: No C++ exceptions (violation) +- advisory_m5_1: @restrict for non-vector parameter constraints — explicitly excludes + std::vector (that is covered by core_17_1) (warning) + +--- + +### Requirement: Plugin skeleton generator command +The system MUST provide a `/thunder-generate-plugin` slash command that +collects parameters via VS Code `vscode_askQuestions` and runs +PluginSkeletonGenerator.py in interactive mode. + +#### Scenario: Simple in-process plugin +- GIVEN the user invokes `/thunder-generate-plugin` +- WHEN the generator collects: PluginName=HelloWorld, OutOfProcess=No, + CustomConfig=No, all others empty +- THEN it changes to the output directory (default: ThunderNanoServices) +- AND runs `python ThunderTools/PluginSkeletonGenerator/PluginSkeletonGenerator.py` + interactively, feeding user answers to PSG's prompts +- AND auto-fixes include paths in the generated .h file (PSG bug workaround) +- AND reports the generated files: CMakeLists.txt, Module.h, Module.cpp, + HelloWorld.h, HelloWorld.cpp + +#### Scenario: Out-of-process plugin with interface +- GIVEN PluginName=MediaPlayer, OutOfProcess=Yes, CustomConfig=Yes, + InterfacePaths=/path/to/IMediaPlayer.h, Preconditions=PLATFORM,NETWORK +- WHEN the generator runs +- THEN PSG is fed "y" for OOP, "y" for config, the interface path, subsystem answers +- AND generates OOP plugin files including MediaPlayerImplementation.h/cpp +- AND generated .conf.in includes preconditions + +#### Scenario: vscode_askQuestions dialog +- GIVEN the user invokes `/thunder-generate-plugin` +- WHEN the prompt starts +- THEN it MUST call `vscode_askQuestions` with questions for: + PluginName, OutputDirectory, OutOfProcess (Yes/No dropdown), CustomConfig (Yes/No dropdown), + InterfacePaths, Preconditions, Terminations, Controls +- AND MUST NOT ask these questions in chat messages + +--- + +### Requirement: Setup scripts register prompts with VS Code +Three setup scripts MUST modify VS Code settings.json to add +`chat.promptFilesLocations` pointing to `ThunderTools/PluginQA/Prompts`. + +#### Scenario: Windows PowerShell setup +- GIVEN a Windows machine with VS Code +- WHEN `.\setup-prompts.ps1` is run from `ThunderTools/PluginQA/` +- THEN it auto-detects the VS Code settings.json location +- AND creates a backup of existing settings +- AND safely merges `chat.promptFilesLocations` into the JSON +- AND prints next steps (reload VS Code window) + +#### Scenario: Linux/Mac bash setup +- GIVEN a Linux or Mac machine +- WHEN `./setup-prompts.sh` is run +- THEN the same behaviour as the PowerShell script, adapted for bash +- AND handles both VS Code and VS Code Insiders + +#### Scenario: Python cross-platform setup +- GIVEN any OS with Python 3 +- WHEN `python setup-prompts.py` is run +- THEN it performs the same safe settings merge +- AND works identically on Windows, Mac, and Linux + +#### Scenario: Script is safe to run multiple times +- GIVEN the script has already been run once +- WHEN it is run again +- THEN it does not duplicate the settings entry +- AND the backup is not overwritten + +--- + +### Requirement: README documents all three commands +The README.md MUST document: quick start (setup scripts, reload VS Code, use the commands), +directory structure, all rules for each of the three commands, +the understand-first review methodology with example, setup script details, FAQ, and example output +for each command (interface validation output, plugin checkpoint output). + +--- + +### Requirement: Rules can be updated without touching prompt files +Because rules are loaded at runtime from YAML, any rule change — adding, modifying, +removing, or bumping severity — requires only editing the relevant YAML file. +No prompt file (`*.prompt.md`) needs to be changed. + +#### Scenario: Updating an existing interface rule +- GIVEN a developer needs to strengthen `core_5_1` (return type convention) +- WHEN they open `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` +- THEN they edit the relevant fields directly in the rule entry: + +```yaml + - id: core_5_1 + name: Return type conventions (method types) + severity: violation # change severity here + description: | # update description here + ... + verification_logic: | # update logic here + ... + fix_template: | # update fix here + ... +``` + +- AND bump the file-level `version:` field (e.g. `3.2.1` → `3.2.2`) +- AND add a CHANGELOG entry in the `description:` block at the top of the file +- AND save — the next time `/thunder-interface` runs it picks up the change automatically + +#### Scenario: Adding a new interface rule +- GIVEN a developer needs to add a new rule (e.g. `core_18_1`) +- WHEN they open `thunder-interface-rules.yaml` +- THEN they append a new entry to the `core_rules` list: + +```yaml + - id: core_18_1 + name: "" + severity: violation # or: warning / suggestion + description: | + + extraction_logic: | + 1. + verification_logic: | + 1. + 2. + violation_pattern: "" + fix_template: | + // WRONG: + + + // Correct: + + citation: | + ThunderInterfaces/interfaces/SourceFile.h — real example +``` + +- AND bump `version:` and add a CHANGELOG entry +- NOTE: No `category` field — it was removed in v3.2.2 +- AND also add a row for `core_18_1` to the Quick Reference table inside + `thunder-interface-review.prompt.md` (the only prompt edit required when adding a rule) + +#### Scenario: Adding a new rule +- GIVEN a developer needs to add a new rule (e.g. `rule_80`) +- WHEN they open `thunder-plugin-rules.yaml` +- THEN they append a new entry to the appropriate phase block: + +```yaml + - Rule_ID: "rule_13" + name: "Short Name in Title Case" + severity: "violation" # or: warning / suggestion + phase: "code_style" + conditional: false # set true if check can be skipped + skip_condition: null # or description of skip condition + + extraction: + target: "" + method: "" + code_block: "" + + bounded_query: + question: "?" + expected_answer: "Yes" + + verification_logic: + - "1. " + - "2. " + - "3. If condition → VIOLATION" + + violation_pattern: "" + + fix_template: | + // WRONG: + + + // Correct: + + + citation: + line_format: "[PluginName.cpp:LINE] " + rule: "thunder-plugin-rules.yaml / rule_80" +``` + +- AND increment `total_checkpoints` in the `metadata` block +- AND update the `organization:` string to reflect the new phase count +- AND bump `version:` (e.g. `3.0.0` → `3.1.0`) + +#### Scenario: Changing a rule severity +- GIVEN a rule currently at `severity: warning` that should become `severity: violation` +- WHEN the developer updates the YAML field and saves +- THEN the next prompt run reports that finding under `🔴 Violations` instead of `🟡 Warnings` +- AND no prompt file change is needed + +#### Scenario: Removing a rule +- GIVEN a rule that is no longer applicable (e.g. a Thunder API was deprecated) +- WHEN the developer deletes the rule entry from the YAML and saves +- THEN the next prompt run no longer checks that rule +- AND `total_checkpoints` (for checkpoint YAML) MUST be decremented accordingly +- AND a CHANGELOG entry MUST document the removal and the reason \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md b/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md new file mode 100644 index 00000000..12f60eaf --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md @@ -0,0 +1,1556 @@ +# Thunder Plugin Rules — v3.3.0 + +> **Purpose:** This document is the authoritative checklist for every Thunder plugin PR review. +> Every rule listed here must be verified before a PR is approved. +> Violations and warnings must be addressed; suggestions are encouraged but not blocking. + +--- + +## How to Use This Document + +1. **Read the full plugin first** — all source files, headers, CMakeLists, and config. + Build a mental model of the plugin's architecture, lifecycle, threading model, and ownership before checking any individual rule. +2. **Check each rule** using the full context, not in isolation. +3. **Cite violations** with exact file and line number (e.g. `[Dictionary.cpp:108]`). +4. **Show the fix** — the corrected code block only, not the whole file. +5. **Never escalate severity** beyond what is defined per rule. + +### Severity Levels + +| Level | Meaning | +|---|---| +| `violation` | **Blocking.** PR cannot be merged until fixed. | +| `warning` | **Should fix.** Strong recommendation; reviewer discretion for blocking. | +| `suggestion` | **Nice to have.** Non-blocking improvement. | + +--- + +## Rule Index + +| # | Rule Name | Severity | Phase | +|---|---|---|---| +| [rule_01](#rule_01) | MODULE_NAME Plugin_ Prefix | suggestion | Module Structure | +| [rule_02](#rule_02) | MODULE_NAME_DECLARATION | violation | Module Structure | +| [rule_03](#rule_03) | Module.h Uses #pragma once | warning | Module Structure | +| [rule_04](#rule_04) | VARIABLE_IS_NOT_USED Accuracy | violation | Code Style | +| [rule_05](#rule_05) | Error Code Preservation | violation | Code Style | +| [rule_06](#rule_06) | NULL vs nullptr | warning | Code Style | +| [rule_07](#rule_07) | No delete on COM Interface Pointers | violation | Code Style | +| [rule_08](#rule_08) | nullptr After Release | violation | Code Style | +| [rule_09](#rule_09) | No QueryInterfaceByCallsign as Member | violation | Code Style | +| [rule_10](#rule_10) | No Smart Pointers on COM Objects | violation | Code Style | +| [rule_11](#rule_11) | No SmartLinkType for COMRPC Plugins | violation | Code Style | +| [rule_12](#rule_12) | No delete on Plugin Object | violation | Code Style | +| [rule_13](#rule_13) | No throw Keyword in Plugin Code | violation | Code Style | +| [rule_14](#rule_14) | Special Members Deleted (Main Class) | warning | Class Registration | +| [rule_15](#rule_15) | Plugin Metadata Registration | violation | Class Registration | +| [rule_16](#rule_16) | JSONRPC Inheritance When Used | violation | Class Registration | +| [rule_17](#rule_17) | IShell AddRef in Initialize | violation | Lifecycle | +| [rule_18](#rule_18) | IShell Release in Deinitialize | violation | Lifecycle | +| [rule_19](#rule_19) | Information() Method | violation | Lifecycle | +| [rule_20](#rule_20) | Root\() Null Check | violation | Lifecycle | +| [rule_21](#rule_21) | Root\() Release in Deinitialize | violation | Lifecycle | +| [rule_22](#rule_22) | Observer Cleanup in Deinitialize | violation | Lifecycle | +| [rule_23](#rule_23) | SubSystems() Release in Deinitialize | violation | Lifecycle | +| [rule_24](#rule_24) | Constructor Must Be Empty | violation | Lifecycle | +| [rule_25](#rule_25) | service->Register/Unregister Pairing | violation | Lifecycle | +| [rule_26](#rule_26) | Initialize Returns Error String on Failure | violation | Lifecycle | +| [rule_27](#rule_27) | No Manual Deinitialize() in Initialize | violation | Lifecycle | +| [rule_28](#rule_28) | Destructor Must Be Empty | violation | Lifecycle | +| [rule_29](#rule_29) | JSON-RPC Register/Unregister Pairing | violation | Implementation | +| [rule_30](#rule_30) | SinkType Pattern for Subscribers | violation | Implementation | +| [rule_31](#rule_31) | Unavailable() in SinkType Classes | violation | Implementation | +| [rule_32](#rule_32) | No Hardcoded Paths | violation | Implementation | +| [rule_33](#rule_33) | OOP Connection Termination in Deinitialize | violation | Out-of-Process | +| [rule_34](#rule_34) | connectionId Checked in IRemoteConnection Callbacks | violation | Out-of-Process | +| [rule_35](#rule_35) | Startmode Declaration | violation | Configuration | +| [rule_36](#rule_36) | Config Core::JSON::Container | violation | Configuration | +| [rule_37](#rule_37) | No Hardcoded Numeric Tuning Parameters | violation | Configuration | +| [rule_38](#rule_38) | CXX_STANDARD Uses Thunder Variable | violation | CMake | +| [rule_39](#rule_39) | COM Methods Return Core::hresult | violation | COM Interface | +| [rule_40](#rule_40) | #pragma once (all headers) | suggestion | Conventions | +| [rule_41](#rule_41) | Apache 2.0 Copyright Header | suggestion | Conventions | +| [rule_42](#rule_42) | STL Types | warning | Code Quality | +| [rule_43](#rule_43) | ASSERT vs Error Handling | warning | Code Quality | +| [rule_44](#rule_44) | OOP Registration Order | violation | Lifecycle Integrity | +| [rule_45](#rule_45) | Complete State Reset in Deinitialize | violation | Lifecycle Integrity | +| [rule_46](#rule_46) | Reverse-Order Cleanup | suggestion | Lifecycle Integrity | +| [rule_47](#rule_47) | Observer Locking | violation | Concurrency | +| [rule_48](#rule_48) | AddRef/Release Balance | violation | COM Safety | +| [rule_49](#rule_49) | CMake NAMESPACE Variable | suggestion | Conventions | +| [rule_50](#rule_50) | Handlers Must Not Block | violation | Concurrency | +| [rule_51](#rule_51) | No Activate/Deactivate from Handlers | violation | Concurrency | +| [rule_52](#rule_52) | Shared State Protected by CriticalSection | violation | Concurrency | +| [rule_53](#rule_53) | No Lock Held During Framework Callbacks | violation | Concurrency | +| [rule_54](#rule_54) | Worker Jobs Check Deinitialize Guard | violation | Concurrency | +| [rule_55](#rule_55) | File Descriptors / Sockets Wrapped in RAII | violation | Resource Management | +| [rule_56](#rule_56) | No Unbounded Memory Growth | violation | Resource Management | +| [rule_57](#rule_57) | Config Errors Return Non-Empty from Initialize | violation | Lifecycle Integrity | +| [rule_58](#rule_58) | interface->Register/Unregister Pairing | violation | JSON-RPC Compliance | +| [rule_59](#rule_59) | Handler Registration Order | violation | JSON-RPC Compliance | +| [rule_60](#rule_60) | Use Core::ERROR_* for Handler Failure Codes | violation | JSON-RPC Compliance | +| [rule_61](#rule_61) | Input Validation in JSON-RPC Handlers | violation | JSON-RPC Compliance | +| [rule_62](#rule_62) | Event Constants and Typed JSON Payloads | warning | JSON-RPC Compliance | +| [rule_63](#rule_63) | COM Reference Counting Correctness | violation | COM Safety | +| [rule_64](#rule_64) | No Hard Inter-Plugin Dependencies | warning | Inter-Plugin Design | +| [rule_65](#rule_65) | JSON-RPC Handlers Are Re-entrant Safe | violation | Concurrency | +| [rule_66](#rule_66) | IPlugin::INotification Callbacks Must Not Block | violation | Concurrency | +| [rule_67](#rule_67) | Lock Scope Minimized | violation | Concurrency | +| [rule_68](#rule_68) | Plugin Threads Joined in Deinitialize | violation | Concurrency | +| [rule_69](#rule_69) | Memory and Allocation Safety | warning | Resource Management | +| [rule_70](#rule_70) | Framework Pointers Not Accessed After Deinitialize | violation | Lifecycle Integrity | +| [rule_71](#rule_71) | hresult Return Values Checked | violation | COM Safety | +| [rule_72](#rule_72) | ASSERT Only for Programmer Invariants | warning | Code Quality | +| [rule_73](#rule_73) | Security: Logging, Shell, Path, and Error Exposure | violation | Code Quality | +| [rule_74](#rule_74) | JSON-RPC Input Validation for Bounds and Types | violation | JSON-RPC Compliance | +| [rule_75](#rule_75) | Config Completeness and Resource Cleanup | warning | Code Quality | +| [rule_76](#rule_76) | OOP Error Propagation and Method Naming | warning | Inter-Plugin Design | +| [rule_77](#rule_77) | Observer Classes Private and Nested | suggestion | Conventions | +| [rule_78](#rule_78) | No Deprecated JSON-RPC APIs | violation | JSON-RPC Compliance | +| [rule_79](#rule_79) | All Acquired Pointers Cleared After Deinitialize | violation | Lifecycle Integrity | + +--- + +## Phase 1 — Module Structure + +--- + +### rule_01 + +**MODULE_NAME Plugin_ Prefix** | `suggestion` + +**What to check:** The `#define MODULE_NAME` value in `Module.h` must start with the `Plugin_` prefix. + +**Where to look:** `Module.h` — the `#define MODULE_NAME ...` line. + +**Violation pattern:** `MODULE_NAME` value does not start with `Plugin_`. + +```cpp +// WRONG: +#define MODULE_NAME Dictionary + +// Correct: +#define MODULE_NAME Plugin_Dictionary +``` + +**Citation format:** `[Module.h:LINE] MODULE_NAME value does not use Plugin_ prefix` + +--- + +### rule_02 + +**MODULE_NAME_DECLARATION** | `violation` + +**What to check:** `Module.cpp` must contain `MODULE_NAME_DECLARATION(BUILD_REFERENCE)`. + +**Where to look:** `Module.cpp` — look for the macro invocation. Argument must be `BUILD_REFERENCE`, not empty or a hardcoded string. + +**Violation pattern:** `MODULE_NAME_DECLARATION(BUILD_REFERENCE)` absent from `Module.cpp`. + +```cpp +// WRONG: (macro missing) + +// Correct: +MODULE_NAME_DECLARATION(BUILD_REFERENCE) +``` + +**Citation format:** `[Module.cpp:LINE] MODULE_NAME_DECLARATION(BUILD_REFERENCE) not found` + +--- + +### rule_03 + +**Module.h Uses #pragma once** | `warning` + +**What to check:** `Module.h` must use `#pragma once` — not a legacy `#ifndef`/`#define`/`#endif` guard. + +**Where to look:** First 10 lines of `Module.h`. + +**Violation pattern:** Legacy `#ifndef` guard used instead of `#pragma once`. + +```cpp +// WRONG: +#ifndef __MODULE_H +#define __MODULE_H +// ... +#endif + +// Correct: +#pragma once +// ... +``` + +**Citation format:** `[Module.h:LINE] Legacy #ifndef guard — use #pragma once` + +--- + +## Phase 2 — Code Style + +--- + +### rule_04 + +**VARIABLE_IS_NOT_USED Accuracy** | `violation` + +**What to check:** Every parameter annotated with `VARIABLE_IS_NOT_USED` must genuinely be unused in the function body. Read the complete function body — not just the signature. + +**Where to look:** All functions using `VARIABLE_IS_NOT_USED` in any form (inline annotation in signature or macro call in body). + +**Violation pattern:** `VARIABLE_IS_NOT_USED` applied to a parameter that is actually referenced in the function body. + +```cpp +// WRONG: +void Callback(VARIABLE_IS_NOT_USED const string& name, const uint32_t value) { + ProcessValue(name, value); // 'name' IS used +} + +// Correct: +void Callback(const string& name, const uint32_t value) { + ProcessValue(name, value); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] VARIABLE_IS_NOT_USED on parameter that is actually used` + +--- + +### rule_05 + +**Error Code Preservation** | `violation` + +**What to check:** An error code variable that is conditionally set to a failure value must never be unconditionally overwritten with `ERROR_NONE` / `SUCCESS` afterward. + +**Where to look:** All function bodies that contain error code variables (`result`, `errorCode`, `ret`, etc.). + +**Violation pattern:** Conditional error assignment followed by an unconditional `ERROR_NONE` assignment. + +```cpp +// WRONG: +uint32_t result = Core::ERROR_NONE; +if (condition) { + result = Core::ERROR_GENERAL; +} +result = Core::ERROR_NONE; // overwrites the conditional failure + +// Correct: +uint32_t result = Core::ERROR_NONE; +if (condition) { + result = Core::ERROR_GENERAL; +} +return result; +``` + +**Citation format:** `[PluginName.cpp:LINE] Error code unconditionally overwritten after conditional failure assignment` + +--- + +### rule_06 + +**NULL vs nullptr** | `warning` + +**What to check:** `nullptr` must be used exclusively as the null pointer literal. `NULL` must not appear as a pointer value in code (assignments, comparisons, function arguments). Exclude `NULL` inside string literals and comments. + +**Where to look:** All function bodies and variable declarations. + +**Violation pattern:** `NULL` used as a null pointer value in code. + +```cpp +// WRONG: +IPlugin* plugin = NULL; +if (service == NULL) { ... } + +// Correct: +IPlugin* plugin = nullptr; +if (service == nullptr) { ... } +``` + +**Citation format:** `[PluginName.cpp:LINE] NULL used as null pointer — use nullptr` + +--- + +### rule_07 + +**No delete on COM Interface Pointers** | `violation` + +**What to check:** `delete` or `delete[]` must never be used on a COM interface pointer (`I*` types). COM interfaces must use `Release()` for cleanup. + +**Where to look:** All `delete` expressions in function bodies. Reason about the type of the deleted pointer from its declaration, name (`I` prefix convention), and usage context. + +**Violation pattern:** `delete` used on a COM interface pointer. + +```cpp +// WRONG: +delete _service; // COM interfaces must NOT be deleted + +// Correct: +_service->Release(); +_service = nullptr; +``` + +**Citation format:** `[PluginName.cpp:LINE] delete used on COM interface pointer — use Release()` + +--- + +### rule_08 + +**nullptr After Release** | `violation` + +**What to check:** Every `->Release()` call on a member variable must be immediately followed by assigning `nullptr` to that member. + +**Where to look:** All `->Release()` calls on member variables (prefixed with `_` or `this->` per Thunder convention). + +**Violation pattern:** Member pointer not set to `nullptr` immediately after `->Release()`. + +```cpp +// WRONG: +_service->Release(); +// other code, no nullptr assignment + +// Correct: +_service->Release(); +_service = nullptr; +``` + +**Citation format:** `[PluginName.cpp:LINE] _service->Release() not followed by _service = nullptr` + +--- + +### rule_09 + +**No QueryInterfaceByCallsign as Member** | `violation` | *Conditional: skip if no `QueryInterfaceByCallsign()` calls exist* + +**What to check:** The result of `QueryInterfaceByCallsign()` must be used transiently — acquired, used, and released within the same scope. It must never be stored as a class member variable. + +**Where to look:** All `QueryInterfaceByCallsign()` call sites and the storage of their return values. + +**Violation pattern:** `QueryInterfaceByCallsign()` result stored as a member variable. + +```cpp +// WRONG: +_remotePlugin = _service->QueryInterfaceByCallsign("RemotePlugin"); +// _remotePlugin stored as member, not released until Deinitialize + +// Correct: +IPlugin* plugin = _service->QueryInterfaceByCallsign("RemotePlugin"); +if (plugin != nullptr) { + plugin->SomeOperation(); + plugin->Release(); + plugin = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] QueryInterfaceByCallsign() result stored as member variable` + +--- + +### rule_10 + +**No Smart Pointers on COM Objects** | `violation` + +**What to check:** COM interface pointers (`I*` types) must never be wrapped in `shared_ptr` or `unique_ptr`. COM interfaces manage their own lifetime via `AddRef`/`Release` — smart pointers cause double-delete. + +**Where to look:** All class member declarations and local variable declarations. + +**Violation pattern:** COM interface pointer wrapped in a smart pointer. + +```cpp +// WRONG: +std::shared_ptr _plugin; +std::unique_ptr _dict; + +// Correct: +IPlugin* _plugin = nullptr; +IDictionary* _dict = nullptr; +// Release manually in Deinitialize() +``` + +**Citation format:** `[PluginName.h:LINE] COM interface wrapped in smart pointer` + +--- + +### rule_11 + +**No SmartLinkType for COMRPC Plugins** | `violation` + +**What to check:** `SmartLinkType` is deprecated. COMRPC plugins must use direct interface pointers, not `SmartLinkType`. + +**Where to look:** All class declarations, member variable definitions, and `typedef`/`using` statements. + +**Violation pattern:** `SmartLinkType` found anywhere in the plugin. + +```cpp +// WRONG: +SmartLinkType> _link; + +// Correct: +IPlugin* _plugin = nullptr; +// Acquire via QueryInterface, release in Deinitialize +``` + +**Citation format:** `[PluginName.h:LINE] SmartLinkType used — deprecated, use direct interface pointer` + +--- + +### rule_12 + +**No delete on Plugin Object** | `violation` + +**What to check:** `delete this` must never appear in plugin code. The Thunder framework owns the plugin object's lifetime. + +**Where to look:** All function bodies in the plugin. + +**Violation pattern:** `delete this` used in plugin code. + +```cpp +// WRONG: +void SomeMethod() { + if (done) { + delete this; // Thunder framework owns this object + } +} + +// Correct: signal to the framework instead +void SomeMethod() { + if (done) { + _service->Submit(PluginHost::IShell::DEACTIVATED, this); + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] delete this used — plugin lifetime is framework-managed` + +--- + +### rule_13 + +**No throw Keyword in Plugin Code** | `violation` + +**What to check:** The `throw` keyword must not appear in any executable plugin code. Exceptions cannot cross COM boundaries. Exclude `throw` inside string literals and comments. + +**Where to look:** All function bodies, read as a human reviewer. + +**Violation pattern:** `throw` used in executable plugin code. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + if (!Init()) throw std::runtime_error("Init failed"); + return {}; +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + if (!Init()) return "Initialization failed"; + return {}; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] throw keyword used — use return error string instead` + +--- + +## Phase 3 — Class Registration + +--- + +### rule_14 + +**Special Members Deleted (Main Class)** | `warning` + +**What to check:** The main plugin class (the one implementing `PluginHost::IPlugin`) must explicitly delete all 4 special members: copy constructor, copy assignment, move constructor, move assignment. Internal helper classes (`Notification`, `Sink`, `Config`, `JobWorker`, etc.) are explicitly excluded from this rule. + +**Where to look:** The main plugin class declaration in the plugin header file. + +**Violation pattern:** One or more of the 4 special members are not explicitly deleted. + +```cpp +// WRONG: (one or more missing) +class Dictionary : public PluginHost::IPlugin { +public: + Dictionary() = default; + Dictionary(const Dictionary&) = delete; + // Missing: move ctor, copy assign, move assign +}; + +// Correct: +class Dictionary : public PluginHost::IPlugin { +public: + Dictionary() = default; + Dictionary(const Dictionary&) = delete; + Dictionary& operator=(const Dictionary&) = delete; + Dictionary(Dictionary&&) = delete; + Dictionary& operator=(Dictionary&&) = delete; +}; +``` + +**Citation format:** `[PluginName.h:LINE] Not all 4 special members deleted in main plugin class` + +--- + +### rule_15 + +**Plugin Metadata Registration** | `violation` + +**What to check:** The plugin `.cpp` file must contain a `static Plugin::Metadata` registration. This registers the plugin with the Thunder framework. + +**Where to look:** `PluginName.cpp`. + +**Violation pattern:** `Plugin::Metadata` registration absent. + +```cpp +// Correct — add to PluginName.cpp: +static Plugin::Metadata metadata( + Plugin::Information::Versions, + Plugin::Information::Instances, + Plugin::Information::Prefix, + Plugin::Information::Interfaces, + Plugin::Information::Configuration, + Plugin::Information::Extends +); +``` + +**Citation format:** `[PluginName.cpp:LINE] Plugin::Metadata registration absent` + +--- + +### rule_16 + +**JSONRPC Inheritance When Used** | `violation` | *Conditional: skip if plugin registers no JSON-RPC handlers* + +**What to check:** If the plugin registers JSON-RPC handlers (`Register()` calls in `Initialize()`), the plugin class must inherit `PluginHost::JSONRPC`. + +**Where to look:** `Initialize()` for `Register()` calls; the class declaration for JSONRPC inheritance. + +**Violation pattern:** `Register()` calls present in `Initialize()` but class does not inherit `PluginHost::JSONRPC`. + +```cpp +// WRONG: +class Dictionary : public PluginHost::IPlugin { + // Missing PluginHost::JSONRPC inheritance +}; + +// Correct: +class Dictionary : public PluginHost::IPlugin, + public PluginHost::JSONRPC { +}; +``` + +**Citation format:** `[PluginName.h:LINE] JSON-RPC used but JSONRPC inheritance missing` + +--- + +## Phase 4 — Lifecycle + +--- + +### rule_17 + +**IShell AddRef in Initialize** | `violation` | *Conditional: skip if class has no stored `IShell*` member* + +**What to check:** If the plugin stores the `IShell*` pointer as a member, `AddRef()` must be called on it immediately after assignment in `Initialize()`. + +**Where to look:** Assignment of the `IShell*` member in `Initialize()`. + +**Violation pattern:** `IShell*` stored as member but `AddRef()` not called after assignment. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + _service = service; // AddRef() missing +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + ASSERT(service != nullptr); + _service = service; + _service->AddRef(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] IShell* stored but AddRef() not called after assignment` + +--- + +### rule_18 + +**IShell Release in Deinitialize** | `violation` | *Conditional: skip if class has no stored `IShell*` member* + +**What to check:** `Deinitialize()` must call `Release()` on the stored `IShell*` and immediately set it to `nullptr`. + +**Where to look:** `Deinitialize()` body. + +**Violation pattern:** `IShell*` member not released or not nulled in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(PluginHost::IShell* service) { + // _service->Release() or _service = nullptr missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + _service->Release(); + _service = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] IShell* not released or not nulled in Deinitialize()` + +--- + +### rule_19 + +**Information() Method** | `violation` + +**What to check:** The plugin must implement `string Information() const`. This method is part of `PluginHost::IPlugin` and is mandatory. + +**Where to look:** Plugin `.cpp` and `.h` files. + +**Violation pattern:** `string Information() const` not implemented. + +```cpp +// Correct: +string PluginName::Information() const { + return string(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Information() const method not implemented` + +--- + +### rule_20 + +**Root\() Null Check** | `violation` | *Conditional: skip if `Initialize()` does not call `Root()`* + +**What to check:** The return value of `service->Root()` must be checked for `nullptr` before use. + +**Where to look:** `Root()` call sites in `Initialize()`. + +**Violation pattern:** `Root()` return value used without `nullptr` check. + +```cpp +// WRONG: +_implementation = service->Root(); +_implementation->DoSomething(); // may crash if Root returns nullptr + +// Correct: +_implementation = service->Root(); +if (_implementation != nullptr) { + _implementation->DoSomething(); +} else { + return "Failed to acquire implementation"; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Root() return value not checked for nullptr` + +--- + +### rule_21 + +**Root\() Release in Deinitialize** | `violation` | *Conditional: skip if plugin does not call `Root()`* + +**What to check:** The pointer acquired via `Root()` must be released and set to `nullptr` in `Deinitialize()`. + +**Where to look:** `Deinitialize()` body — find the member that stores the `Root()` result. + +**Violation pattern:** `Root()` pointer not released or not nulled in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(PluginHost::IShell* service) { + // _implementation->Release() missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + if (_implementation != nullptr) { + _implementation->Release(); + _implementation = nullptr; + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Root() pointer not released in Deinitialize()` + +--- + +### rule_22 + +**Observer Cleanup in Deinitialize** | `violation` | *Conditional: skip if plugin registers no observers* + +**What to check:** Every observer or notification registered in `Initialize()` must be unregistered in `Deinitialize()`. Every `Register`/`Subscribe` call must have a matching `Unregister`/`Unsubscribe`. + +**Where to look:** `Initialize()` for all `Register`/`Subscribe` calls; `Deinitialize()` for matching cleanup. + +**Violation pattern:** Observer registered in `Initialize()` but not unregistered in `Deinitialize()`. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + service->Register(_notification); +} +void Deinitialize(PluginHost::IShell* service) { + // service->Unregister(_notification) missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Observer registered in Initialize() but not cleaned up in Deinitialize()` + +--- + +### rule_23 + +**SubSystems() Release in Deinitialize** | `violation` | *Conditional: skip if plugin does not use `SubSystems()`* + +**What to check:** If `service->SubSystems()` is acquired in `Initialize()`, it must be released in `Deinitialize()`. + +**Where to look:** `Initialize()` for `SubSystems()` acquisition; `Deinitialize()` for the matching `Release()`. + +**Violation pattern:** `SubSystems()` acquired in `Initialize()` but not released in `Deinitialize()`. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + _subSystems = service->SubSystems(); +} +void Deinitialize(PluginHost::IShell* service) { + // _subSystems->Release() missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + _subSystems->Release(); + _subSystems = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] SubSystems() not released in Deinitialize()` + +--- + +### rule_24 + +**Constructor Must Be Empty** | `violation` + +**What to check:** The plugin class constructor body must be empty — no initialization logic, no resource acquisition, no system calls. All initialization belongs in `Initialize()`. Member initializer lists with null/default values are acceptable. + +**Where to look:** Plugin class constructor definition. + +**Violation pattern:** Constructor body contains non-trivial initialization logic. + +```cpp +// WRONG: +Dictionary::Dictionary() { + _config.FromString("..."); + _service = GetService(); +} + +// Correct: +Dictionary::Dictionary() + : _service(nullptr) + , _config() +{ + // empty +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Constructor contains initialization logic` + +--- + +### rule_25 + +**service->Register/Unregister Pairing** | `violation` | *Conditional: skip if plugin does not call `service->Register()`* + +**What to check:** Every `service->Register()` call in `Initialize()` must be matched by a `service->Unregister()` call in `Deinitialize()`. + +**Where to look:** `Initialize()` for `service->Register()` calls; `Deinitialize()` for matching `service->Unregister()` calls. + +**Violation pattern:** `service->Register()` in `Initialize()` not matched by `service->Unregister()` in `Deinitialize()`. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + service->Register(_notification); +} +void Deinitialize(PluginHost::IShell* service) { + // service->Unregister(_notification) missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] service->Register() without matching Unregister() in Deinitialize()` + +--- + +### rule_26 + +**Initialize Returns Error String on Failure** | `violation` + +**What to check:** `Initialize()` must return a non-empty error string on every failure path. Returning an empty string on failure prevents Thunder from knowing initialization failed. + +**Where to look:** All return statements in `Initialize()`. + +**Violation pattern:** `Initialize()` returns empty string on a failure condition. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + if (service == nullptr) { + return string(); // empty string does not signal failure + } + return string(); +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + if (service == nullptr) { + return "Service pointer is null"; + } + return string(); // success +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Initialize() returns empty string on failure condition` + +--- + +### rule_27 + +**No Manual Deinitialize() in Initialize** | `violation` + +**What to check:** `Initialize()` must never call `Deinitialize()` directly. Failure cleanup must be done explicitly with targeted resource release. + +**Where to look:** The full `Initialize()` body. + +**Violation pattern:** `Initialize()` calls `Deinitialize()` directly. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + _service = service; + _service->AddRef(); + if (!Setup()) { + Deinitialize(service); // do not call Deinitialize from Initialize + return "Setup failed"; + } + return string(); +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + _service = service; + _service->AddRef(); + if (!Setup()) { + _service->Release(); + _service = nullptr; + return "Setup failed"; + } + return string(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Initialize() calls Deinitialize() — handle failures explicitly` + +--- + +### rule_28 + +**Destructor Must Be Empty** | `violation` + +**What to check:** The plugin destructor body must be completely empty. All resource cleanup belongs in `Deinitialize()`. + +**Where to look:** Plugin class destructor definition. + +**Violation pattern:** Destructor contains cleanup logic. + +```cpp +// WRONG: +Dictionary::~Dictionary() { + if (_service != nullptr) { + _service->Release(); + } +} + +// Correct: +Dictionary::~Dictionary() { + // empty +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Destructor contains cleanup logic — move to Deinitialize()` + +--- + +## Phase 5 — Implementation + +--- + +### rule_29 + +**JSON-RPC Register/Unregister Pairing** | `violation` | *Conditional: skip if plugin registers no JSON-RPC handlers* + +**What to check:** Every JSON-RPC handler registered via `JSONRPC::Register()` in `Initialize()` must be unregistered via `JSONRPC::Unregister()` in `Deinitialize()`. Missing unregistration causes stale handler references after plugin deactivation. + +**Where to look:** `Initialize()` for `Register()` calls; `Deinitialize()` for matching `Unregister()` calls. + +**Violation pattern:** JSON-RPC handler registered but not unregistered. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + Register(_T("method"), &PluginName::Method, this); +} +void Deinitialize(PluginHost::IShell* service) { + // Unregister(_T("method")) missing +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + Unregister(_T("method")); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] JSON-RPC handler registered but not unregistered` + +--- + +### rule_30 + +**SinkType Pattern for Subscribers** | `violation` | *Conditional: skip if plugin has no notification subscriptions* + +**What to check:** Notification subscriber classes must follow the SinkType pattern — properly inheriting from the notification interface and implementing all required callbacks. + +**Where to look:** All notification handler class declarations. + +**Violation pattern:** Notification subscriber class does not properly implement the SinkType pattern. + +```cpp +// WRONG: +class MyNotification { // not inheriting IPlugin::INotification + +// Correct: +class Notification : public PluginHost::IPlugin::INotification { +public: + BEGIN_INTERFACE_MAP(Notification) + INTERFACE_ENTRY(PluginHost::IPlugin::INotification) + END_INTERFACE_MAP + void Activated(const string& callsign, PluginHost::IShell* service) override; + void Deactivated(const string& callsign, PluginHost::IShell* service) override; + void Unavailable(const string& callsign, PluginHost::IShell* service) override; +}; +``` + +**Citation format:** `[PluginName.h:LINE] Notification subscriber does not follow SinkType pattern` + +--- + +### rule_31 + +**Unavailable() in SinkType Classes** | `violation` | *Conditional: skip if plugin has no SinkType classes* + +**What to check:** Every class implementing `IPlugin::INotification` must also implement `Unavailable()`. `IPlugin::INotification` has 3 pure virtuals: `Activated`, `Deactivated`, and `Unavailable`. + +**Where to look:** All classes inheriting `IPlugin::INotification`. + +**Violation pattern:** SinkType class missing `Unavailable()` implementation. + +```cpp +// WRONG: +class Notification : public PluginHost::IPlugin::INotification { + void Activated(const string& callsign, PluginHost::IShell* service) override { ... } + void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } + // Unavailable() missing +}; + +// Correct: +class Notification : public PluginHost::IPlugin::INotification { + void Activated(const string& callsign, PluginHost::IShell* service) override { ... } + void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } + void Unavailable(const string& callsign, PluginHost::IShell* service) override { ... } +}; +``` + +**Citation format:** `[PluginName.h:LINE] SinkType class missing Unavailable() implementation` + +--- + +### rule_32 + +**No Hardcoded Paths** | `violation` + +**What to check:** No hardcoded filesystem paths in plugin code. Paths must come from configuration or be constructed from Thunder's data path API. + +**Where to look:** All function bodies and member initializers — look for string literals that resemble filesystem paths (`/usr/`, `/etc/`, `/tmp/`, `C:\`, etc.). + +**Violation pattern:** Hardcoded filesystem path found in plugin code. + +```cpp +// WRONG: +string configFile = "/etc/myapp/config.json"; +string dataDir = "/var/lib/thunder/data/"; + +// Correct: +string configFile = service->DataPath() + "config.json"; +// Or: _config.DataPath.Value() +``` + +**Citation format:** `[PluginName.cpp:LINE] Hardcoded filesystem path` + +--- + +## Phase 5C — Out-of-Process (OOP) + +--- + +### rule_33 + +**OOP Connection Termination in Deinitialize** | `violation` | *Conditional: skip if plugin is not out-of-process* + +**What to check:** `Deinitialize()` must properly terminate the OOP connection — release the remote implementation interface and terminate/release the connection channel. + +**Where to look:** `Deinitialize()` body in OOP plugins using `IRemoteConnection`. + +**Violation pattern:** OOP connection not properly terminated in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(PluginHost::IShell* service) { + // OOP connection not cleaned up +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + if (_implementation != nullptr) { + _implementation->Release(); + _implementation = nullptr; + } + if (_connection != nullptr) { + _connection->Terminate(); + _connection->Release(); + _connection = nullptr; + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] OOP connection not terminated in Deinitialize()` + +--- + +### rule_34 + +**connectionId Checked in IRemoteConnection Callbacks** | `violation` | *Conditional: skip if plugin does not implement `IRemoteConnection::INotification`* + +**What to check:** In every `IRemoteConnection` callback (`Activated`, `Deactivated`, `Terminated`), the `connectionId` parameter must be checked against the plugin's stored connection ID before taking any action. + +**Where to look:** All `IRemoteConnection::INotification` callback implementations. + +**Violation pattern:** `IRemoteConnection` callback does not check `connectionId` before acting. + +```cpp +// WRONG: +void Terminated(const uint32_t id) override { + // acts without checking if id matches + _parent.ConnectionTerminated(); +} + +// Correct: +void Terminated(const uint32_t id) override { + if (id == _parent._connectionId) { + _parent.ConnectionTerminated(); + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] connectionId not checked in IRemoteConnection callback` + +--- + +## Phase 6 — Configuration + +--- + +### rule_35 + +**Startmode Declaration** | `violation` | *Conditional: skip if no `.conf.in` file exists* + +**What to check:** The `.conf.in` file must declare an explicit `startmode`. + +**Where to look:** `PluginName.conf.in`. + +**Violation pattern:** `startmode` field absent from `.conf.in`. + +```ini +# Correct — add to PluginName.conf.in: +startmode = Activated +``` + +**Citation format:** `[PluginName.conf.in:LINE] startmode field missing` + +--- + +### rule_36 + +**Config Core::JSON::Container** | `violation` | *Conditional: skip if plugin has no configuration class* + +**What to check:** The plugin configuration class must inherit `Core::JSON::Container`. Config classes that don't inherit `Core::JSON::Container` won't parse configuration correctly. + +**Where to look:** The `Config` class declaration. + +**Violation pattern:** `Config` class does not inherit `Core::JSON::Container`. + +```cpp +// WRONG: +class Config { +public: + string Root; +}; + +// Correct: +class Config : public Core::JSON::Container { +public: + Config() : Core::JSON::Container() { + Add(_T("root"), &Root); + } + Core::JSON::String Root; +}; +``` + +**Citation format:** `[PluginName.h:LINE] Config class missing Core::JSON::Container inheritance` + +--- + +### rule_37 + +**No Hardcoded Numeric Tuning Parameters** | `violation` + +**What to check:** Numeric tuning parameters (timeouts, buffer sizes, retry counts, thresholds) must come from the `Config` class, not be hardcoded inline. Structural constants (`0`, `1`, `-1`, array indices) are acceptable inline. + +**Where to look:** All function bodies and class member initializations. + +**Violation pattern:** Hardcoded numeric tuning parameter. + +```cpp +// WRONG: +Core::Thread::Run(5000); // hardcoded 5-second timeout +if (retries > 3) { ... } // hardcoded retry count + +// Correct: +Core::Thread::Run(_config.Timeout.Value()); +if (retries > _config.MaxRetries.Value()) { ... } +``` + +**Citation format:** `[PluginName.cpp:LINE] Hardcoded numeric tuning parameter — move to Config` + +--- + +## Phase 7 — CMake + +--- + +### rule_38 + +**CXX_STANDARD Uses Thunder Variable** | `violation` | *Conditional: skip if `CMakeLists.txt` does not set `CXX_STANDARD`* + +**What to check:** If `CXX_STANDARD` is set in `CMakeLists.txt`, it must use `${CXX_STD}` (the Thunder build system variable) rather than a hardcoded value. + +**Where to look:** `CMakeLists.txt`. + +**Violation pattern:** `CXX_STANDARD` set to a hardcoded value. + +```cmake +# WRONG: +set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD 14) + +# Correct: +set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD ${CXX_STD}) +``` + +**Citation format:** `[CMakeLists.txt:LINE] CXX_STANDARD hardcoded — use ${CXX_STD}` + +--- + +## Phase 8 — COM Interface + +--- + +### rule_39 + +**COM Methods Return Core::hresult** | `violation` + +**What to check:** All COM interface methods declared in the plugin must return `Core::hresult`. Void return types and other return types are not allowed in COM interfaces (Thunder 5.0+). + +**Where to look:** All interface method declarations in plugin header files — focus on pure virtual methods in COM interfaces. + +**Violation pattern:** COM interface method does not return `Core::hresult`. + +```cpp +// WRONG: +virtual void GetValue(const string& key, string& value) = 0; +virtual bool SetValue(const string& key, const string& value) = 0; + +// Correct: +virtual Core::hresult GetValue(const string& key, string& value /* @out */) = 0; +virtual Core::hresult SetValue(const string& key, const string& value) = 0; +``` + +**Citation format:** `[PluginName.h:LINE] COM interface method does not return Core::hresult` + +--- + +## Holistic Rules + +The following rules require reading the full plugin context before evaluating. They span multiple files and patterns. + +--- + +### rule_40 + +**#pragma once** | `suggestion` | Category: Conventions + +Every header file in the plugin must use `#pragma once` as its include guard. + +--- + +### rule_41 + +**Apache 2.0 Copyright Header** | `suggestion` | Category: Conventions + +Every source file must contain a proper Apache 2.0 copyright header at the top. + +--- + +### rule_42 + +**STL Types** | `warning` | Category: Code Quality + +Thunder type aliases (`string`, `vector`) must be used instead of their `std::` equivalents (`std::string`, `std::vector`) wherever Thunder aliases are available. Check function signatures, class members, and return types. + +--- + +### rule_43 + +**ASSERT vs Error Handling** | `warning` | Category: Code Quality + +`ASSERT` must be used only for programmer invariants — conditions that must always be true in correct code. `ASSERT` must never be used for runtime error handling (user input errors, network failures, missing configuration). + +--- + +### rule_44 + +**OOP Registration Order** | `violation` | Category: Lifecycle Integrity + +In out-of-process plugins, `service->Register()` and `service->Unregister()` must be called in the correct order relative to acquiring and releasing the remote implementation. + +--- + +### rule_45 + +**Complete State Reset in Deinitialize** | `violation` | Category: Lifecycle Integrity + +Every member variable set during `Initialize()` or during operation must be reset to its default/null state in `Deinitialize()`. A subsequent `Initialize()` call must start from a completely clean state. + +--- + +### rule_46 + +**Reverse-Order Cleanup** | `suggestion` | Category: Lifecycle Integrity + +`Deinitialize()` should release resources in the reverse order of how they were acquired in `Initialize()`. + +--- + +### rule_47 + +**Observer Locking** | `violation` | Category: Concurrency + +Observer lists must be protected by appropriate locking when iterated and when observers are added or removed, to prevent data races. + +--- + +### rule_48 + +**AddRef/Release Balance** | `violation` | Category: COM Safety + +Every `AddRef()` call on a COM interface must be balanced by exactly one `Release()` call across all code paths, including error paths. No leaks, no double-releases. + +--- + +### rule_49 + +**CMake NAMESPACE Variable** | `suggestion` | Category: Conventions + +`CMakeLists.txt` must use the `${NAMESPACE}` variable for target naming and installation paths per Thunder conventions. + +--- + +### rule_50 + +**Handlers Must Not Block** | `violation` | Category: Concurrency + +No notification handler (`IPlugin::INotification` callbacks, `IRemoteConnection::INotification` callbacks) may perform blocking operations (network calls, file I/O, sleep, synchronous waiting) on the framework's callback thread. + +--- + +### rule_51 + +**No Activate/Deactivate from Handlers** | `violation` | Category: Concurrency + +No notification callback may call `service->Activate()` or `service->Deactivate()` directly — doing so causes deadlock on the framework thread. + +--- + +### rule_52 + +**Shared State Protected by CriticalSection** | `violation` | Category: Concurrency + +Every member variable accessed from both the main thread and notification callbacks must be protected by a `Core::CriticalSection` lock. + +--- + +### rule_53 + +**No Lock Held During Framework Callbacks** | `violation` | Category: Concurrency + +All locks must be released before calling back into the Thunder framework (e.g. before `service->Submit()`, `Activate()`, or notification dispatch) to prevent deadlock. + +--- + +### rule_54 + +**Worker Jobs Check Deinitialize Guard** | `violation` | Category: Concurrency + +All worker job (`IJob`, `WorkerPool`) `Dispatch()` methods must check a deinitialization guard flag at the start to avoid using stale pointers after `Deinitialize()` has run. + +--- + +### rule_55 + +**File Descriptors / Sockets Wrapped in RAII** | `violation` | Category: Resource Management + +All file descriptors, sockets, and OS handles must be wrapped in RAII types or explicitly closed in `Deinitialize()`. They must never be leaked. + +--- + +### rule_56 + +**No Unbounded Memory Growth** | `violation` | Category: Resource Management + +All data structures (`map`, `vector`, `list`, `queue`) populated at runtime must have a bounded size or eviction policy to prevent unbounded memory growth during sustained plugin operation. + +--- + +### rule_57 + +**Config Errors Return Non-Empty from Initialize** | `violation` | Category: Lifecycle Integrity + +All configuration errors (missing required fields, invalid values, out-of-range values) must cause `Initialize()` to return a non-empty error string. + +--- + +### rule_58 + +**interface->Register/Unregister Pairing** | `violation` | Category: JSON-RPC Compliance + +Every `interface->Register()` call (registering a notification/callback on a non-service interface) must be matched by a corresponding `interface->Unregister()` in `Deinitialize()`. + +--- + +### rule_59 + +**Handler Registration Order in Initialize/Deinitialize** | `violation` | Category: JSON-RPC Compliance + +Notification handlers must be registered **after** the interfaces they observe are fully acquired and set up, and unregistered **before** those interfaces are released — to avoid callbacks on partially-initialized state. + +--- + +### rule_60 + +**Use Core::ERROR_\* for Handler Failure Codes** | `violation` | Category: JSON-RPC Compliance + +All failures in JSON-RPC handlers and COM interface method implementations must be reported using `Core::ERROR_*` constants — never raw numeric literals or Windows error codes. + +--- + +### rule_61 + +**Input Validation in JSON-RPC Handlers** | `violation` | Category: JSON-RPC Compliance + +Every JSON-RPC handler must validate its input parameters (null pointer checks, string length limits, enum range checks) before using them. + +--- + +### rule_62 + +**Event Constants and Typed JSON Payloads** | `warning` | Category: JSON-RPC Compliance + +Event names must be defined as named constants rather than inline string literals. Event payloads must be typed JSON objects rather than untyped free-form strings. + +--- + +### rule_63 + +**COM Reference Counting Correctness** | `violation` | Category: COM Safety + +COM reference counting must be correctly maintained across all code paths where COM interfaces are passed, returned, or stored — `AddRef` when storing, `Release` when done, no double-release or leak on any path including error paths. + +--- + +### rule_64 + +**No Hard Inter-Plugin Dependencies** | `warning` | Category: Inter-Plugin Design + +The plugin must use the observer pattern and dynamic interface acquisition (`QueryInterfaceByCallsign`, `IPlugin::INotification`) for inter-plugin communication — no hard compile-time or startup-time dependencies on specific other plugins being present. + +--- + +### rule_65 + +**JSON-RPC Handlers Are Re-entrant Safe** | `violation` | Category: Concurrency + +All JSON-RPC handlers must be safe for concurrent invocation — either accessing only local state or properly locking shared state. + +--- + +### rule_66 + +**IPlugin::INotification Callbacks Must Not Block** | `violation` | Category: Concurrency + +No `IPlugin::INotification` callback (`Activated`, `Deactivated`, `Unavailable`) may block the calling thread — no synchronous waits, no contended mutex acquisitions, no I/O. + +--- + +### rule_67 + +**Lock Scope Minimized** | `violation` | Category: Concurrency + +All critical sections must be held for the minimum time necessary — covering only the actual shared state access. Blocking operations, callbacks, and I/O must never occur while holding a lock. + +--- + +### rule_68 + +**Plugin Threads Joined in Deinitialize** | `violation` | Category: Concurrency + +All threads started by the plugin must be explicitly stopped and joined (or detached with a lifetime guarantee) in `Deinitialize()` before the function returns. + +--- + +### rule_69 + +**Memory and Allocation Safety** | `warning` | Category: Resource Management + +There must be no memory leaks (every `new` must have a corresponding `delete` on all code paths), no use-after-free, and no buffer overflows. + +--- + +### rule_70 + +**Framework Pointers Not Accessed After Deinitialize** | `violation` | Category: Lifecycle Integrity + +No worker thread, background job, or async callback may access framework pointers (`IShell*`, service pointers, notification interfaces) after `Deinitialize()` has completed and nulled those pointers. + +--- + +### rule_71 + +**hresult Return Values Checked** | `violation` | Category: COM Safety + +The return value of every `Core::hresult`-returning COM interface method call must be checked and errors must be handled appropriately — never silently discarded. + +--- + +### rule_72 + +**ASSERT Only for Programmer Invariants** | `warning` | Category: Code Quality + +`ASSERT` must only be used for conditions that represent programmer errors — invariants that should never be false in correct code. It must never be used for conditions that can legitimately occur at runtime. + +--- + +### rule_73 + +**Security: Logging, Shell, Path, and Error Exposure** | `violation` | Category: Code Quality + +The plugin must avoid: logging sensitive data (passwords, tokens, PII); executing shell commands with unsanitized input; exposing internal error details through JSON-RPC responses; and path traversal via external inputs. + +--- + +### rule_74 + +**JSON-RPC Input Validation for Bounds and Types** | `violation` | Category: JSON-RPC Compliance + +Every JSON-RPC handler must validate numeric inputs for range bounds and type correctness before use in operations that could overflow, underflow, or cause undefined behavior. + +--- + +### rule_75 + +**Config Completeness and Resource Cleanup** | `warning` | Category: Code Quality + +All configuration fields must be registered with `Add()` in the `Config` constructor. All resources acquired based on configuration values must be properly released in `Deinitialize()`. + +--- + +### rule_76 + +**OOP Error Propagation and Method Naming** | `warning` | Category: Inter-Plugin Design + +Out-of-process method implementations must properly propagate errors from the remote process back to the plugin host. Method names must follow Thunder naming conventions — no abbreviations, clear verb+noun form. + +--- + +### rule_77 + +**Observer Classes Private and Nested** | `suggestion` | Category: Conventions + +Observer/notification classes should be declared as private nested classes within the plugin class — not as separate public classes in the header — to keep the plugin's internal observer implementation encapsulated. + +--- + +### rule_78 + +**No Deprecated JSON-RPC APIs** | `violation` | Category: JSON-RPC Compliance + +No deprecated JSON-RPC APIs must be used — no legacy `Register` overloads replaced by typed versions, no deprecated event dispatch patterns. + +--- + +### rule_79 + +**All Acquired Pointers Cleared After Deinitialize** | `violation` | Category: Lifecycle Integrity + +Every pointer member set to a non-null value during the plugin's lifetime must be explicitly set to `nullptr` in `Deinitialize()` (in addition to being released), such that a double-`Deinitialize()` would not cause a double-release. + +--- + +## Summary by Phase + +| Phase | Rules | Violations | Warnings | Suggestions | +|---|---|---|---|---| +| Module Structure | rule_01 – rule_03 | 1 | 1 | 1 | +| Code Style | rule_04 – rule_13 | 9 | 1 | 0 | +| Class Registration | rule_14 – rule_16 | 2 | 1 | 0 | +| Lifecycle | rule_17 – rule_28 | 12 | 0 | 0 | +| Implementation | rule_29 – rule_32 | 4 | 0 | 0 | +| Out-of-Process | rule_33 – rule_34 | 2 | 0 | 0 | +| Configuration | rule_35 – rule_37 | 3 | 0 | 0 | +| CMake | rule_38 | 1 | 0 | 0 | +| COM Interface | rule_39 | 1 | 0 | 0 | +| Holistic (rule_40 – rule_79) | 40 | 29 | 7 | 4 | +| **Total** | **79** | **64** | **10** | **5** | + +--- + +*Document generated from `thunder-plugin-rules.yaml` v3.3.0. For questions or rule updates, raise a PR against the spec folder.* diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md new file mode 100644 index 00000000..9ec824d1 --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -0,0 +1,611 @@ +# Delta for Plugin Validation + +## ADDED Requirements + +--- + +### Requirement: PluginQA folder created inside ThunderTools +A `PluginQA/` directory MUST be created inside the existing `ThunderTools/` folder, +containing all prompts, rules, setup script, and documentation for the QA system. + +#### Scenario: Directory structure created +- GIVEN the ThunderTools repository +- WHEN the PluginQA system is delivered +- THEN the following structure MUST exist inside `ThunderTools/`: + +``` +ThunderTools/PluginQA/ +├── README.md +├── PLUGIN_GENERATOR_GUIDE.md +├── setup-prompts.py (cross-platform Python 3) +├── Prompts/ +│ ├── thunder-plugin-review.prompt.md +│ ├── thunder-interface-review.prompt.md +│ └── thunder-generate-plugin.prompt.md +└── rules/ + ├── thunder-plugin-rules.yaml + └── thunder-interface-rules.yaml +``` + +#### Scenario: Rules files are the source of truth +- GIVEN the two YAML files under `ThunderTools/PluginQA/rules/` +- WHEN any prompt runs +- THEN it MUST load rules at runtime from those YAML files +- AND rules MUST NOT be embedded directly inside the prompt files +- AND updating a YAML file updates validation behaviour without touching prompt logic + +#### Scenario: Prompt files are registered with VS Code +- GIVEN `ThunderTools/PluginQA/Prompts/` containing the three `.prompt.md` files +- WHEN a setup script (`setup-prompts.ps1` / `.sh` / `.py`) is run +- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQA/Prompts` +- WHEN `setup-prompts.py` is run +- AND the three slash commands (`/thunder-plugin-review`, `/thunder-interface-review`, + `/thunder-generate-plugin`) become available in VS Code Copilot Chat +- AND the script is safe to run multiple times (idempotent, creates backup of settings) + +--- + +### Requirement: Setup scripts modify VS Code settings.json to register prompt location +Three setup scripts MUST modify the user-level VS Code `settings.json` to add +### Requirement: Setup script modifies VS Code settings.json to register prompt location +The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add +#### Scenario: Resulting settings.json structure +- GIVEN VS Code `settings.json` before the script runs (may be empty `{}` or have existing entries) +- WHEN `setup-prompts.py` completes successfully +- THEN `settings.json` MUST contain the following merged entry: + +```json +{ + "chat.promptFilesLocations": { + "ThunderTools/PluginQA/Prompts": true + } +} +``` + +- AND any pre-existing keys in `settings.json` MUST be preserved unchanged + +#### Scenario: Settings.json location per platform +- GIVEN the platform the script is running on +- THEN the script MUST locate `settings.json` at: + - **Windows**: `%APPDATA%\Code\User\settings.json` + (also check `%APPDATA%\Code - Insiders\User\settings.json`) + - **macOS**: `~/Library/Application Support/Code/User/settings.json` + (also check `~/Library/Application Support/Code - Insiders/User/settings.json`) + - **Linux**: `~/.config/Code/User/settings.json` + (also check `~/.config/Code - Insiders/User/settings.json`) +- AND if the file does not exist, the script MUST create it with `{}` as the starting content + +#### Scenario: Script creates a timestamped backup before modifying +- GIVEN an existing `settings.json` +- WHEN the setup script runs +- THEN it MUST copy `settings.json` to `settings.json.backup.{timestamp}` before any write +- AND the backup MUST be in the same directory as `settings.json` +- AND if the backup already exists from a previous run, it MUST NOT be overwritten + +#### Scenario: Script is idempotent +- GIVEN `chat.promptFilesLocations` already contains `"ThunderTools/PluginQA/Prompts": true` +- WHEN the setup script is run again +- THEN it MUST NOT add a duplicate entry +- AND it MUST print a message indicating the entry is already present + +#### Scenario: Script prints next steps after success +- GIVEN the script completes without error +- THEN it MUST print: + 1. Confirmation that `settings.json` was updated + 2. The path to the backup file + 3. Instructions to reload the VS Code window: + `Press Ctrl+Shift+P → "Developer: Reload Window"` (or equivalent for platform) + 4. The three slash commands now available: `/thunder-plugin-review`, + `/thunder-interface-review`, `/thunder-generate-plugin` + +--- + +### Requirement: thunder-plugin-rules.yaml (v3.3.0) created under PluginQA/rules/ +The file `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` MUST exist +with version `3.2.0` and contain all 79 rules numbered sequentially (rule_01 to rule_79). + +#### Scenario: Metadata block +- GIVEN the YAML file +- THEN it MUST contain a `metadata` block with: + `version: "3.0.0"`, `total_rules: 79`, `total_general_rules: 40`, + `approach: "semantic code review — understand whole plugin first, then check specifics"`, + and a `validation_approach` block listing the 5-step workflow + (understand whole plugin → focus on specific concern → reason in context → cite if genuinely wrong → fix) + +#### Scenario: All 39 phase checkpoints present with required fields +- GIVEN each phase checkpoint entry in the YAML (under phase sections) +- THEN it MUST contain: `rule_id`, `name` (Title Case), `severity`, `phase`, + `extraction` block (target / method / code_block), + `bounded_query` as an object with `question` and `expected_answer` fields (NOT a plain string), + `verification_logic` as a YAML list of numbered step strings (NOT a multiline block scalar) + that describe SEMANTIC reasoning ("read the code and understand..."), NEVER regex/pattern, + `violation_pattern` (single-line string), `fix_template`, and + `citation` object with `line_format` and `rule` fields +- AND conditional rules MUST include a `conditional: true` flag and + a `skip_condition` describing when to skip + +#### Scenario: All 40 Holistic Rules (8 sub-phases) present with required fields +- GIVEN each General rule entry in the YAML (under `general_rules` section) +- THEN it MUST contain: `rule_id` (e.g. "rule_40"), `name` (Title Case), + `severity`, `category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security)`, + `review_question` (semantic question about what to verify), + `review_method` (must state "Read the full relevant code context... + Do not use pattern-only checks."), + `evidence_requirement` (must state "Provide exact [File:line] citation") +- AND Holistic Rules (8 sub-phases) MUST NOT have `extraction`, `bounded_query`, or `verification_logic` fields + +#### Scenario: Report output is unified — no "automated" vs "manual" split +- GIVEN any review run completing all 79 rules +- THEN the report MUST present ONE unified list of findings grouped by file +- AND there MUST NOT be separate "Part 1" / "Part 2" or "Automated" / "Manual" sections +- AND the summary table MUST include all 79 rules in a single table with rows for each + phase plus "Holistic Rules (8 sub-phases)" and a "Total (79 rules)" footer row + +#### Scenario: Phase breakdown matches spec +- GIVEN the 39 checkpoints distributed across phases +- THEN the counts MUST be: + Phase 1 (module_structure): 3 — `rule_01`, `rule_02`, `rule_03` + Phase 2 (code_style): 10 — `rule_04`, `rule_05`, `rule_06`, + `rule_07`, + `rule_08`, `rule_09`, `rule_10`, `rule_11`, `rule_12`, + `rule_13` + Phase 3 (class_registration): 3 — `rule_14`, + `rule_15`, `rule_16` + Phase 4 (lifecycle): 12 — `rule_17`, `rule_18`, `rule_19`, + `rule_20`, `rule_21`, `rule_22`, `rule_23`, + `rule_24`, `rule_25`, `rule_26`, `rule_27`, + `rule_28` + Phase 5 (implementation): 4 — `rule_29`, `rule_30`, + `rule_31`, `rule_32` + Phase 5C (oop): 2 — `rule_33`, `rule_34` + Phase 6 (configuration): 3 — `rule_35`, `rule_36`, `rule_37` + Phase 7 (cmake): 1 — `rule_38` + Phase 8 (interfaces): 1 — `rule_39` + +--- + +### Requirement: thunder-interface-rules.yaml (v3.2.2) created under PluginQA/rules/ +The file `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` MUST exist +with version `3.2.2` and contain all 19 interface rule definitions (15 core + 4 advisory). + +#### Scenario: File header present +- GIVEN the YAML file +- THEN it MUST contain a YAML front-matter header (not a metadata block) with: + `version: 3.2.2`, `title`, `description` including the full CHANGELOG + covering v3.2.2, v3.2.1, v3.2.0, v3.1.0, and v3.0.2 + +#### Scenario: All 15 core rules present with required fields +- GIVEN the `core_rules` list +- THEN it MUST contain exactly these IDs in order: + `core_1_1`, `core_2_1`, `core_3_1`, `core_4_1`, `core_5_1`, `core_6_1`, + `core_9_1`, `core_10_1`, `core_11_1`, `core_12_1`, `core_13_1`, `core_14_1`, + `core_15_1`, `core_16_1`, `core_17_1` +- AND each rule MUST contain: `id`, `name`, `severity: violation`, + `description`, `extraction_logic`, `verification_logic`, `violation_pattern`, + `fix_template`, `citation` (with real Thunder interface file references) + +#### Scenario: All 4 advisory rules present with required fields +- GIVEN the `advisory_rules` list +- THEN it MUST contain exactly these IDs: + `advisory_m1_1` (SRP — violation), `advisory_m2_1` (enum underlying types — warning), + `advisory_m3_1` (no exceptions — violation), + `advisory_m5_1` (@restrict for non-vector params — warning, + explicitly stating it does NOT apply to `std::vector`) +- AND each advisory rule MUST contain the same fields as core rules + +--- + + +### Requirement: Plugin validation command with unified output +The system MUST provide a `/thunder-plugin-review ` slash command +in VS Code Copilot Chat that validates a Thunder plugin against all 79 rules +using semantic code review, producing a single unified report. + +#### Scenario: Plugin found and reviewed +- GIVEN the user types `/thunder-plugin-review Dictionary` +- WHEN the prompt runs +- THEN it locates `ThunderNanoServices/Dictionary/` automatically +- AND identifies Dictionary.h, Dictionary.cpp, Module.h, Module.cpp, CMakeLists.txt, + Dictionary.conf.in, and any OOP implementation files +- AND executes all 79 rules in order (phase checkpoints first, then General) +- AND outputs a single unified report showing ONLY failures with exact line citations + +#### Scenario: No plugin name provided +- GIVEN the user types `/thunder-plugin-review` with no argument +- WHEN the prompt runs +- THEN it asks "Which plugin would you like to review?" +- AND proceeds normally once the user supplies a name + +#### Scenario: Plugin not in ThunderNanoServices +- GIVEN the user types `/thunder-plugin-review MyPlugin` +- AND `ThunderNanoServices/MyPlugin/` does not exist +- WHEN the prompt runs +- THEN it searches the workspace for a folder named `MyPlugin` +- AND if still not found, asks the user for the location + +--- + +### Requirement: Report shows failures only, phase summaries for passes +The report MUST show violations, warnings, and suggestions in full detail. +PASS and SKIP checkpoints MUST appear only as counts in phase summary lines. + +#### Scenario: Phase with all passes +- GIVEN Phase 1 (Module Structure) has 3 passing checkpoints +- WHEN the report is generated +- THEN Phase 1 shows: `Status: 3 PASS, 0 FAIL, 0 SKIP ✅ (No issues)` +- AND no individual checkpoint details are shown for Phase 1 + +#### Scenario: Phase with failures +- GIVEN Phase 4 (Lifecycle) has checkpoint 4.1 failing +- WHEN the report is generated +- THEN the phase summary shows the PASS/FAIL/SKIP counts +- AND the failing checkpoint shows: citation with exact file and line number, + extracted code block with line numbers, fix template + +--- + +### Requirement: Output status field reflects effective severity after contextual judgment +The `status` field in every output YAML block MUST reflect the **effective** severity +after contextual judgment — not always the raw YAML severity. + +#### Scenario: Status field maps from YAML severity by default +- GIVEN a checkpoint with `severity: violation` fires and the developer's approach is genuinely wrong +- THEN the output block MUST show `status: VIOLATION` +- GIVEN a checkpoint with `severity: warning` fires +- THEN the output block MUST show `status: WARNING` (or lower if context permits) +- GIVEN a checkpoint with `severity: suggestion` fires +- THEN the output block MUST show `status: SUGGESTION` + +#### Scenario: Severity downgraded when developer's approach is valid in context +- GIVEN a checkpoint with `severity: violation` fires +- AND the validator reasons that the developer's actual approach is valid, safe, and achieves + the same safety goal as the rule intends +- THEN the output block MUST show `status: SUGGESTION` (downgraded from VIOLATION) +- AND the output block MUST include a `reasoning` field that: + 1. States why the rule normally exists + 2. Describes what the developer did instead + 3. Explains why their approach is valid in this context + 4. Notes any residual risk, if any +- GIVEN the approach is acceptable but has a residual risk +- THEN the output block MUST show `status: WARNING` (downgraded from VIOLATION) + +#### Scenario: Severity MUST NOT be escalated +- GIVEN a checkpoint with `severity: warning` +- THEN the output MUST NOT show `status: VIOLATION` +- GIVEN a checkpoint with `severity: suggestion` +- THEN the output MUST NOT show `status: WARNING` or `status: VIOLATION` + +#### Scenario: Section heading emoji matches effective severity +- GIVEN a failing checkpoint of any severity +- THEN the heading for that issue block MUST use the corresponding emoji: + `❌` for VIOLATION, `⚠️` for WARNING, `💡` for SUGGESTION + +--- + +### Requirement: 79 unified rules organised across phase groups and General concerns +The prompt MUST load rule definitions from +`ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` at runtime. +Each rule definition includes sufficient information for semantic validation. +All 79 rules produce the same output format. + +Phase breakdown: Phase 1: 3, Phase 2: 10, Phase 3: 3, Phase 4: 12, Phase 5: 4, +Phase 5C: 2, Phase 6: 3, Phase 7: 1, Phase 8: 1 = 39 phase rules + 40 holistic rules = 79 total. + +--- + +### Requirement: All checkpoint verification MUST use semantic reasoning — no regex or text search +Every checkpoint verification step MUST be performed by reading the relevant source +code in full as a human code reviewer would and reasoning about its meaning — +NOT by running regular expressions or keyword searches against raw text. + +#### Scenario: Correct semantic verification approach +- GIVEN any checkpoint that needs to detect a pattern in plugin source code +- WHEN the validator processes that checkpoint +- THEN it MUST read the complete relevant function bodies, class declarations, or file contents +- AND reason about the semantic meaning of the code (types, lifetimes, control flow, ownership) +- AND apply domain knowledge about Thunder/COM conventions to determine compliance +- AND MUST NOT use regex patterns, text searches, or string matching as the primary detection method + +#### Scenario: New checkpoints added in future +- GIVEN a new checkpoint is being authored (added to the YAML or prompt) +- THEN the `extraction.method` field in the YAML MUST describe reading code as a human developer + (e.g. "Read the full function body as a human developer — understand the context") +- AND the `verification_logic` steps MUST describe semantic reasoning steps + (e.g. "Reason about whether the pointer is a COM interface type from its name and usage context") +- AND ANY use of "search for", "find pattern", or "match regex" language in a checkpoint's + `extraction.method` or `verification_logic` is a spec violation and MUST be corrected to + semantic reasoning language + +#### Scenario: Verifier encounters an ambiguous case +- GIVEN a code pattern that is not immediately clear from a surface read +- WHEN the verifier applies semantic reasoning +- THEN it MUST consider: the variable's declared type, the class hierarchy, the call context, + and the surrounding logic — not just the presence or absence of a keyword +- AND apply reasonable judgment: if the semantic intent is clear and compliant, report PASS; + if the semantic intent is clearly non-compliant, report VIOLATION/WARNING with a citation + +--- + +#### Scenario: Phase 1 - Module Structure (3 checkpoints) +- GIVEN Module.cpp of a Thunder plugin +- WHEN checkpoint rule_01 runs (suggestion) +- THEN it checks MODULE_NAME in Module.h starts with `Plugin_` prefix +- WHEN checkpoint rule_02 runs (violation) +- THEN it checks Module.cpp contains `MODULE_NAME_DECLARATION(BUILD_REFERENCE)` +- WHEN checkpoint rule_03 runs (warning) +- THEN it checks Module.h uses `#pragma once` instead of a legacy `#ifndef/#define/#endif` guard + +#### Scenario: Phase 2 - Code Style (10 checkpoints) +- GIVEN plugin .cpp and .h files +- WHEN checkpoint rule_04 runs (violation) +- THEN it flags any parameter annotated `VARIABLE_IS_NOT_USED` that is actually used in the function body; + the validator MUST read each function in its entirety (signature + body) and reason semantically + about whether the parameter is used — NOT by regex or text search, but by understanding the code + as a human reviewer would; covers all annotation forms (inline in signature, macro call in body, etc.) +- WHEN checkpoint rule_05 runs (violation) +- THEN it flags any function that conditionally sets an error code but then + unconditionally overwrites it with ERROR_NONE/SUCCESS +- WHEN checkpoint rule_06 runs (warning) +- THEN it flags any use of `NULL` as a null pointer literal (must be `nullptr`); + the validator MUST read each function body and variable declaration as a human reviewer, + reasoning about the semantic meaning of each null expression — NOT by regex search; + NULL inside string literals and comments MUST be excluded by context understanding +- WHEN checkpoint rule_07 runs (violation) +- THEN it flags any `delete` or `delete[]` used on a COM interface pointer type; + the validator MUST read each function body and reason about the type of the deleted pointer + from context (declarations, naming, class hierarchy) — NOT by pattern matching +- WHEN checkpoint rule_08 runs (violation) +- THEN it flags every `->Release()` on a member variable not immediately followed by `= nullptr`; + the validator MUST read each function in full and reason about what follows each Release call, + and whether the pointer is a member variable with lifetime beyond the current scope +- WHEN checkpoint rule_09 runs (violation, conditional) +- THEN it flags any `QueryInterfaceByCallsign()` result stored as a member variable; + the validator MUST read function bodies and class declarations and reason about the result's + lifetime — transient local use (acquired, used, released in same scope) is acceptable +- SKIP if no `QueryInterfaceByCallsign` calls found +- WHEN checkpoint rule_10 runs (violation) +- THEN it flags any COM interface pointer (`I*` type) wrapped in `shared_ptr` or `unique_ptr`; + the validator MUST read class member declarations and reason about types from context +- WHEN checkpoint rule_11 runs (violation) +- THEN it flags any use of `SmartLinkType`; the validator MUST read class declarations + and reason about member types — use a direct COMRPC interface instead +- WHEN checkpoint rule_12 runs (violation) +- THEN it flags any `delete this` in plugin code; the validator MUST read function bodies + and reason about ownership — plugin lifetime is owned by the framework +- WHEN checkpoint rule_13 runs (violation) +- THEN it flags any `throw` statement in executable plugin code; the validator MUST read + function bodies as a human reviewer and reason about error-handling patterns — + exclude `throw` inside string literals and comments by context understanding + +#### Scenario: Phase 3 - Class Registration (3 checkpoints) +- GIVEN the main plugin class (the one implementing PluginHost::IPlugin) +- WHEN checkpoint rule_14 runs (warning) +- THEN it checks all 4 special members are deleted in the MAIN class only + (copy ctor, copy assign, move ctor, move assign) — internal helper classes excluded +- WHEN checkpoint rule_15 runs (violation) +- THEN it checks `Plugin::Metadata` exists in main Plugin.cpp + AND `SERVICE_REGISTRATION(...)` in ImplementationClass.cpp if OOP +- WHEN checkpoint rule_16 runs (violation, conditional) +- IF the plugin registers JSON-RPC handlers (`Register(` calls in Initialize()) +- THEN it checks the class inherits `PluginHost::JSONRPC` +- SKIP if no JSON-RPC handler registrations found + +#### Scenario: Phase 4 - Lifecycle (12 checkpoints, some conditional) +- GIVEN Initialize() and Deinitialize() method bodies +- WHEN checkpoint rule_17 runs (violation, conditional) +- IF `IShell* _service` member exists: checks AddRef() called after assignment in Initialize +- IF no stored IShell pointer: SKIP +- WHEN checkpoint rule_18 runs (violation, conditional) +- IF `IShell* _service` stored: checks `_service->Release()` and `_service = nullptr` + in Deinitialize +- WHEN checkpoint rule_19 runs (violation) +- THEN it checks the plugin `.cpp` implements `string Information() const` +- WHEN checkpoint rule_20 runs (violation, conditional) +- IF `service->Root()` called in Initialize: checks return value is tested for nullptr +- SKIP if no Root() calls +- WHEN checkpoint rule_21 runs (violation, conditional) +- IF `service->Root()` result stored as member: checks it is Released and set to nullptr in Deinitialize +- SKIP if no Root() calls +- WHEN checkpoint rule_22 runs (violation, conditional) +- IF observer container (`_observers`, `_sinks`) exists: checks Release loop + `.clear()` in Deinitialize +- IF no container: SKIP +- WHEN checkpoint rule_23 runs (violation, conditional) +- IF `service->SubSystems()` stored as member: checks it is Released in Deinitialize +- SKIP if SubSystems() not stored as member +- WHEN checkpoint rule_24 runs (violation) +- THEN it checks the plugin constructor body is empty (no logic, no calls) — + all initialisation must be in `Initialize()` +- WHEN checkpoint rule_25 runs (violation, conditional) +- IF `service->Register(observer)` called in Initialize: checks matching `service->Unregister(observer)` in Deinitialize +- SKIP if no service->Register() calls +- WHEN checkpoint rule_26 runs (violation) +- THEN it checks every failure return in Initialize() returns a non-empty diagnostic string + (the success return MUST be `return string();` or `return EMPTY_STRING;`) +- WHEN checkpoint rule_27 runs (violation) +- THEN it checks Initialize() does NOT manually call `Deinitialize()` on failure paths — + Thunder calls Deinitialize() automatically when Initialize() returns a non-empty string +- WHEN checkpoint rule_28 runs (violation) +- THEN it checks the main plugin class destructor body is completely empty + (only `ASSERT` invariant checks are acceptable — no Release calls or cleanup logic) +#### Scenario: Phase 5 - Implementation (4 checkpoints, mostly conditional) +- GIVEN plugin implementation files +- WHEN checkpoint rule_29 runs (violation, conditional) +- IF `Exchange::J*::Register` in Initialize: checks matching `::Unregister` in Deinitialize +- WHEN checkpoint rule_30 runs (violation, conditional) +- SKIP if the plugin is a **publisher** — i.e. it only exposes `Register(INotification* sink)` / + `Unregister()` methods and stores incoming external `INotification*` pointers in an ObserverMap. + That pattern means callers register with the plugin, NOT that the plugin subscribes to anything. +- ONLY CHECK if the plugin is a **subscriber** — i.e. it defines its OWN inner class + (e.g. `class NetworkNotification : public Exchange::INetwork::INotification`) to listen + to an external service. That inner class member MUST be `Core::SinkType`, + NOT a raw pointer (`ClassName* _member`) +- WHEN checkpoint rule_31 runs (violation, conditional) +- IF any `Core::SinkType<>` subscriber class found: checks each class implements `void Unavailable()`; + the validator MUST read the full class declaration and reason about whether the method exists — + NOT by searching for the string "Unavailable" +- SKIP if no Core::SinkType<> subscriber classes in plugin +- WHEN checkpoint rule_32 runs (violation) +- THEN it checks for hardcoded absolute paths; the validator MUST read function bodies and + reason about how paths are constructed from context — NOT by searching for path substrings +#### Scenario: Phase 8 - COM Interface Rules (1 checkpoint) +- GIVEN interface struct definitions in the plugin +- WHEN checkpoint rule_39 runs (violation) +- THEN it checks that action/non-getter virtual methods on COM interfaces return `Core::hresult` +- NOTE: The "no delete on COM interface pointers" rule is enforced in Phase 2 + as checkpoint `rule_07` (applies to all .cpp files, not only interface structs) + +#### Scenario: Phase 5C - Out-of-Process (2 checkpoints, conditional) +- GIVEN Deinitialize() and IRemoteConnection::INotification callbacks of an OOP plugin +- IF plugin is in-process: SKIP the entire phase +- WHEN checkpoint rule_33 runs +- THEN it checks `service->RemoteConnection()` and `connection->Terminate()` and + `connection->Release()` all present in Deinitialize() +- WHEN checkpoint rule_34 runs (violation, conditional) +- THEN it checks that the very first action in every `Activated()` and `Deactivated()` callback is + a guard checking `connection->Id() == _connectionId` +- SKIP if plugin has no IRemoteConnection::INotification implementation + +#### Scenario: Phase 6 - Configuration (3 checkpoints, conditional) +- GIVEN the plugin's .conf.in file and Initialize() body +- IF no .conf.in file exists: SKIP rule_35 +- WHEN checkpoint rule_35 runs (suggestion) +- THEN it checks the .conf.in file contains a `startmode =` declaration; + the validator MUST read the file in full and reason about the startup configuration — + note that `autostart` is not the same as `startmode` +- WHEN checkpoint rule_36 runs (violation, conditional) +- IF `service->ConfigLine()` is called in Initialize(): checks that a typed + `Config : public Core::JSON::Container` struct exists and its `FromString()` is called +- SKIP if plugin does not call `service->ConfigLine()` +- WHEN checkpoint rule_37 runs (violation) +- THEN it checks for hardcoded numeric tuning parameters (timeouts, retry counts, buffer sizes) + as inline literals; the validator MUST read Initialize() in full and reason about the semantic + meaning of each numeric value in context — NOT by searching for number patterns; + apply judgment: protocol constants (e.g. port 80) are acceptable, deployment tuning values are not + +#### Scenario: Phase 7 - CMake (1 checkpoint) +- GIVEN CMakeLists.txt +- WHEN checkpoint rule_38 runs +- THEN it checks `CXX_STANDARD` is set to `${CXX_STD}` (Thunder variable), not a literal like `11` or `14`; + the validator MUST read CMakeLists.txt in full and reason about each target's property settings + +--- + +### Requirement: 40 Holistic Rules (8 sub-phases) loaded from YAML and reported in unified output +After the 39 phase checkpoints, the validator MUST also run the 40 Holistic Rules (8 sub-phases) +loaded from the `general_rules` section of `thunder-plugin-rules.yaml`. +All rules produce the same output format — there is no separate section for these rules. + +#### Scenario: Holistic Rules (8 sub-phases) integrated into unified output +- GIVEN the phase checkpoint evaluation is complete +- WHEN the validator runs the 40 Holistic Rules (8 sub-phases) +- THEN any failures appear in the same file-grouped findings list as phase checkpoint failures +- AND the summary table includes a "Holistic Rules (8 sub-phases)" row with PASS/FAIL/SKIP counts +- AND there is NO separate "Part 2" or "Manual Review" heading in the output + `rule_id`, `rule` (name), `status` (PASS/FAIL/SUGGEST), `severity`, + `extracted_code` (with [File:line] prefix where applicable), `violation_line`, + `citation`, `fix`, `reasoning` +- AND PASS rules are NOT listed individually — they appear only as counts in the summary table +- Holistic Rules (8 sub-phases) cover (Rules 1–12 original, Rules 13–40 new): + 1. `#pragma once` in every .h file (suggestion) + 2. Apache 2.0 copyright headers in all source files (suggestion) + 3. No STL types where Thunder equivalents exist (warning) + 4. ASSERT used correctly — on prerequisites, not return values (warning) + 5. OOP registration order correct (violation, conditional) + 6. Complete state reset in Deinitialize (violation) + 7. Reverse-order cleanup in Deinitialize (warning) + 8. Observer AddRef before Register, Release after Unregister (violation) + 9. AddRef/Release balance across plugin lifetime (violation) + 10. Config struct used for all configuration (warning) + 11. CMake NAMESPACE variable set to WPEFramework (violation) + 12. write_config() called LAST in CMakeLists.txt (suggestion) + 13. Handlers must not block — dispatch to WorkerPool (violation) + 14. No Activate/Deactivate calls from notification handlers (violation) + 15. Shared member state protected by Core::CriticalSection (violation) + 16. No framework callbacks (Notify, Submit, Register) called while holding _adminLock (violation) + 17. WorkerPool jobs guard against post-Deinitialize execution (violation) + 18. File descriptors and sockets wrapped in RAII (violation) + 19. No unbounded memory growth in event-fed containers (violation) + 20. Config validation failures return non-empty string from Initialize (violation) + 21. Notify() only called after Initialize() has completed (violation) + 22. interface->Register paired with interface->Unregister in Deinitialize (violation) + 23. Handler registration order in Initialize/Deinitialize: acquire first, unregister before release (violation) + 24. Use Core::ERROR_* named constants for failure codes, not raw integers (violation) + 25. Input validation before use in JSON-RPC handlers (violation) + 26. Event name strings as named constants; JSON payloads use typed JsonData::* classes (warning) + 27. COM reference counting correctness: one AddRef per pointer, one Release per lifetime end (violation) + 28. No hard inter-plugin dependencies; use PluginSmartInterfaceType for held cross-plugin pointers (warning) + 29. JSON-RPC handlers are re-entrant safe: shared state under lock (violation) + 30. IPlugin::INotification callbacks must not block: delegate to WorkerPool (violation) + 31. Lock scope minimized: no I/O or long operations while lock is held (violation) + 32. Plugin threads joined/stopped in Deinitialize before releasing interfaces (violation) + 33. Memory and allocation safety: no large stack buffers, no VLAs, no deep recursion, RAII preferred (warning) + 34. Framework pointers (IShell*, acquired interfaces) not accessed from background threads after Deinitialize (violation) + 35. Core::hresult return values from interface calls checked, not silently ignored (violation) + 36. ASSERT used only for programmer invariants, not runtime conditions (warning) + 37. Security: no sensitive data in logs, no shell commands from external input, paths sanitized (violation) + 38. JSON-RPC input validated for type, bounds, enum range, and string length (violation) + 39. Config completeness: defaults for optional fields, typed members, Get() results released, config read-only after Initialize (warning) + 40. OOP error propagation: hresult from interface calls propagated; method names match API contract (warning) + 41. Observer/notification inner classes declared as private nested members (suggestion) + 42. No deprecated JSON-RPC APIs: no IDispatcher, no Announce-style events (violation) + 43. All acquired interface pointers set to nullptr on all Deinitialize() exit paths (violation) + +--- + +### Requirement: Violation output format grouped by file with exact line numbers +Every failing checkpoint MUST be output as a YAML block grouped under the source file +it belongs to. The report structure is: + +``` +### {FileName} — N issue(s) + +```yaml +rule_id: +status: FAIL +severity: violation|warning|suggestion +question: "..." +answer: "..." +extracted_code: | + // [FileName:line-line] + +violation_line: +citation: "[FileName:line] " +fix: | + +reasoning: "..." +` ` ` +``` + +#### Scenario: File-wise grouping +- GIVEN a plugin review that finds issues in Dictionary.cpp, Dictionary.h, and CMakeLists.txt +- WHEN the report is generated +- THEN failures appear under three separate file headers in document order: + `### Dictionary.cpp — 8 issue(s)`, `### Dictionary.h — 1 issue(s)`, `### CMakeLists.txt — 1 issue(s)` +- AND each YAML block includes a `severity` field (violation / warning / suggestion) +- AND files with no failures are NOT listed + +#### Scenario: Lifecycle violation output +- GIVEN checkpoint 4.1 (Initialize ASSERT) fails +- WHEN the report is generated +- THEN it appears under `### {PluginName}.cpp — N issue(s)` +- AND the citation reads `[Dictionary.cpp:115] Initialize missing ASSERT(service != nullptr)` + (using the actual plugin name, not a placeholder) +- AND extracted_code shows the Initialize method body with `// [Dictionary.cpp:113-117]` prefix + +--- + +### Requirement: Checkpoint summary as table and Next Steps as numbered list +The report MUST include a `### Checkpoint Summary` table after all file sections. + +#### Scenario: Summary table format +- GIVEN a completed review +- THEN the summary MUST be a markdown table: + + | Phase | PASS | FAIL | SKIP | + |-------|------|------|------| + | Phase 1: Module Structure | 3 | 0 | 0 | + | ... | ... | ... | ... | + | **Total** | **N** | **N** | **N** | + +- AND a `### Next Steps` numbered list follows the table +- AND each item references the exact `[File:line]` location and checkpoint ID \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md new file mode 100644 index 00000000..9aa22b0d --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -0,0 +1,134 @@ +# Spec: Thunder PluginQA Report Generation + +## Purpose + +After every `/thunder-plugin-review` or `/thunder-interface-review` run, the system generates a +single CSV report file. The CSV is Excel-compatible and contains one row per issue found, +with all fields needed to understand, prioritise, and track fixes. + +--- + +## Requirements + +### REQ-R1 — Plugin review CSV + +**Scenario:** `/thunder-plugin-review` completes all 39 checkpoints +- The system MUST generate a CSV file at: + `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` +- If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) +- The CSV MUST contain one header row followed by one data row per FAIL or WARNING or SUGGESTION +- PASS and SKIP rows are NOT included in the CSV (they appear only in the phase summary in chat) + +### REQ-R2 — Interface review CSV + +**Scenario:** `/thunder-interface` completes all 19 rules +- The system MUST generate a CSV file at: + `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` +- Same no-overwrite rule applies +- One row per violated rule only + +### REQ-R3 — Plugin CSV columns (exact, in order) + +``` +No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +``` + +| Column | Description | Example | +|---|---|---| +| No | Row number starting at 1 | `1` | +| Plugin | Plugin name from command argument | `HdmiCecSink` | +| Date | ISO date of review | `2026-06-05` | +| Phase | Full phase label | `Phase 2 — Code Style` | +| Rule_ID | Sequential rule ID | `rule_09` | +| Rule_Name | Rule name | `NULL vs nullptr` | +| Status | Effective status after JUDGE step | `VIOLATION` / `WARNING` / `SUGGESTION` | +| Severity | YAML severity level | `violation` / `warning` / `suggestion` | +| File | Source file where issue was found | `HdmiCecSinkImplementation.cpp` | +| Line | Exact line number | `128` | +| Citation | Short citation string | `[HdmiCecSinkImplementation.cpp:128]` | +| Issue_Description | What was found | `NULL used instead of nullptr` | +| Fix_Summary | One-line fix description | `Replace NULL with nullptr` | +| Reasoning | Populated only when severity was downgraded; empty otherwise | `` | + +### REQ-R4 — Interface CSV columns (exact, in order) + +``` +No,Interface,Date,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +``` + +| Column | Description | Example | +|---|---|---| +| No | Row number starting at 1 | `1` | +| Interface | Interface filename without extension | `IHdmiCecSink` | +| Date | ISO date of review | `2026-06-05` | +| Rule_ID | YAML rule id | `core_12_1` | +| Rule_Name | Rule name | `@json tag (CRITICAL)` | +| Status | `VIOLATION` / `WARNING` / `SUGGESTION` | `VIOLATION` | +| Severity | YAML severity | `violation` | +| File | Interface file | `IHdmiCecSink.h` | +| Line | Exact line number | `45` | +| Citation | Short citation string | `[IHdmiCecSink.h:45]` | +| Issue_Description | What was found | `@json tag missing above struct declaration` | +| Fix_Summary | One-line fix | `Add // @json 1.0.0 above struct EXTERNAL` | +| Reasoning | Populated only on severity downgrade; empty otherwise | `` | + +### REQ-R5 — CSV formatting rules + +- Comma-separated, UTF-8, no BOM +- Fields containing commas MUST be wrapped in double quotes: `"Phase 2 — Code Style"` +- Fields containing double quotes MUST escape them as `""`: `"Use ""nullptr"" not NULL"` +- No trailing commas +- Line ending: CRLF (Windows Excel compatible) +- Empty fields: leave blank (two consecutive commas: `,,`) + +### REQ-R6 — Folder creation + +- `ThunderTools/PluginQA/Reports/plugin/` and `ThunderTools/PluginQA/Reports/interface/` + MUST be created if they do not exist before writing the file + +### REQ-R7 — Post-generation message + +After saving the CSV, the prompt MUST display in chat: + +``` +📊 Report saved: + ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv + {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions + +To open in Excel (Windows): + Start-Process "ThunderTools\PluginQA\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" +``` + +### REQ-R8 — Empty report (all PASS) + +If no issues were found, still generate the CSV with the header row only and add a comment row: + +```csv +No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +,,,,,,,,,,,,All checkpoints passed — no issues found, +``` + +--- + +## Folder structure + +``` +ThunderTools/PluginQA/ +└── Reports/ + ├── plugin/ + │ ├── HdmiCecSink_2026-06-05.csv + │ ├── Dictionary_2026-06-05.csv + │ └── NetworkControl_2026-06-05.csv + └── interface/ + ├── IHdmiCecSink_2026-06-05.csv + └── IDictionary_2026-06-05.csv +``` + +--- + +## Out of Scope + +- `.xlsx` format (CSV opens natively in Excel — no library dependency needed) +- Report viewer command (VS Code file explorer is sufficient) +- Diff between two reports (Excel handles this) +- Automatic email or CI upload (future) \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/tasks.md b/openspec/changes/thunder-plugin-qa/tasks.md new file mode 100644 index 00000000..98ab8e08 --- /dev/null +++ b/openspec/changes/thunder-plugin-qa/tasks.md @@ -0,0 +1,455 @@ +# Tasks: Thunder Plugin QA System + +## Phase 1: YAML rule definitions + +- [x] 1.1 Create `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` (v3.3.0) + - metadata block: version, description, approach, + total_rules: 79, total_general_rules: 40, + organization: "Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1" + - validation_approach block: principles list + 5-step workflow (including + Step 3b JUDGE: contextual judgment — if developer's approach technically + violates a rule but is valid and reasonable in context, downgrade severity + (violation→suggestion or →warning) and populate reasoning field) + - CRITICAL: ALL rules (both automated and manual) use SEMANTIC code review. + Never pattern-match. Always read the full relevant code context (control flow, + ownership, lifecycle, threading) before deciding PASS/FAIL. + + YAML STRUCTURE — PHASE RULES (39 rules, rule_01 to rule_39): + - rule_id, name (Title Case), severity, phase + - extraction: { target, method, code_block } + - bounded_query: { question: "...", expected_answer: "Yes" } ← MUST be object, NOT plain string + - verification_logic: YAML list of strings (- "1. ...", - "2. ..."), NOT multiline block + MUST describe semantic reasoning ("read the code and understand..."), NEVER regex/pattern + - violation_pattern: single-line string + - fix_template: multiline block showing WRONG + Correct pattern + - conditional: true/false + - skip_condition: string or null + - citation: { line_format: "[PluginName.cpp:LINE] ...", rule: "thunder-plugin-rules.yaml / {id}" } + + YAML STRUCTURE — MANUAL RULES (semantic review, 40 rules): + - rule_id (e.g. "rule_40"), name (Title Case), severity, category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security) + - review_question: semantic question describing what to verify + - 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." + NOTE: Manual rules require holistic code understanding — they cannot be reduced + to a single block check. They require understanding the full plugin architecture — concurrency, + security, and General concerns that span multiple code paths. + + - phase_1_checkpoints (3): + rule_01 (suggestion): "MODULE_NAME Plugin_ Prefix" — checks #define MODULE_NAME in Module.h starts with Plugin_ + rule_02 (violation): "MODULE_NAME_DECLARATION" — MODULE_NAME_DECLARATION(BUILD_REFERENCE) present in Module.cpp + rule_03 (warning): "Module.h Uses #pragma once" — not legacy #ifndef guard + - phase_2_checkpoints (10): + rule_04 (violation): "VARIABLE_IS_NOT_USED Accuracy" — wraps only genuinely unused params + rule_05 (violation): "Error Code Preservation" — conditional error not unconditionally overwritten + rule_06 (warning): "NULL vs nullptr" — nullptr used not NULL + rule_07 (violation): "No delete on COM Interface Pointers" — use Release() + rule_08 (violation): "nullptr After Release" — nullptr assigned immediately after Release() + rule_09 (violation conditional): "No QueryInterfaceByCallsign as Member" — transient use only + rule_10 (violation): "No Smart Pointers on COM Objects" — no shared_ptr/unique_ptr on COM + rule_11 (violation): "No SmartLinkType for COMRPC Plugins" — deprecated mechanism + rule_12 (violation): "No delete on Plugin Object" — no delete this + rule_13 (violation): "No throw Keyword in Plugin Code" — no exceptions across COM + - phase_3_checkpoints (3): + rule_14 (warning): "Special Members Deleted (Main Class)" — all 4 deleted, main class only + rule_15 (violation): "Plugin Metadata Registration" — Plugin::Metadata in .cpp + rule_16 (violation conditional): "JSONRPC Inheritance When Used" — when JSON-RPC handlers registered + - phase_4_checkpoints (12): + rule_17 (violation conditional): "IShell AddRef in Initialize" + rule_18 (violation conditional): "IShell Release in Deinitialize" + rule_19 (violation): "Information() Method" — string Information() const implemented + rule_20 (violation conditional): "Root() Null Check" + rule_21 (violation conditional): "Root() Release in Deinitialize" + rule_22 (violation conditional): "Observer Cleanup in Deinitialize" + rule_23 (violation conditional): "SubSystems() Release in Deinitialize" + rule_24 (violation): "Constructor Must Be Empty" + rule_25 (violation conditional): "service->Register/Unregister Pairing" + rule_26 (violation): "Initialize Returns Error String on Failure" + rule_27 (violation): "No Manual Deinitialize() in Initialize" + rule_28 (violation): "Destructor Must Be Empty" + - phase_5_checkpoints (4): + rule_29 (violation conditional): "JSON-RPC Register/Unregister Pairing" + rule_30 (violation conditional): "SinkType Pattern for Subscribers" + rule_31 (violation conditional): "Unavailable() in SinkType Classes" + rule_32 (violation): "No Hardcoded Paths" + - phase_5C_checkpoints (2): + rule_33 (violation conditional): OOP connection termination in Deinitialize + rule_34 (violation conditional): connectionId checked first in IRemoteConnection callbacks + - phase_6_checkpoints (3): + rule_35 (violation conditional): startmode declaration + rule_36 (violation conditional): Config Core::JSON::Container + rule_37 (violation): no hardcoded numeric tuning params + - phase_7_checkpoints (1): rule_38 (violation conditional): CXX_STANDARD uses ${CXX_STD} + - phase_8_checkpoints (1): rule_39 (violation): "COM Methods Return Core::hresult" + + Holistic Rules (8 sub-phases) (40, all in general_rules section): + rule_40 (suggestion): "#pragma once" + rule_41 (suggestion): "Apache 2.0 Copyright Header" + rule_42 (warning): "STL Types" + rule_43 (warning): "ASSERT vs Error Handling" + rule_44 (violation): "OOP Registration Order" + rule_45 (violation): "Complete State Reset in Deinitialize" + rule_46 (suggestion): "Reverse-Order Cleanup" + rule_47 (violation): "Observer Locking" + rule_48 (violation): "AddRef/Release Balance" + rule_49 (suggestion): "CMake NAMESPACE Variable" + rule_50 (violation): "Handlers Must Not Block" + rule_51 (violation): "No Activate/Deactivate from Handlers" + rule_52 (violation): "Shared State Protected by CriticalSection" + rule_53 (violation): "No Lock Held During Framework Callbacks" + rule_54 (violation): "Worker Jobs Check Deinitialize Guard" + rule_55 (violation): "File Descriptors / Sockets Wrapped in RAII" + rule_56 (violation): "No Unbounded Memory Growth" + rule_57 (violation): "Config Errors Return Non-Empty from Initialize" + rule_58 (violation): "interface->Register/Unregister Pairing" + rule_59 (violation): "Handler Registration Order in Initialize/Deinitialize" + rule_60 (violation): "Use Core::ERROR_* for Handler Failure Codes" + rule_61 (violation): "Input Validation in JSON-RPC Handlers" + rule_62 (warning): "Event Constants and Typed JSON Payloads" + rule_63 (violation): "COM Reference Counting Correctness" + rule_64 (warning): "No Hard Inter-Plugin Dependencies" + rule_65 (violation): "JSON-RPC Handlers Are Re-entrant Safe" + rule_66 (violation): "IPlugin::INotification Callbacks Must Not Block" + rule_67 (violation): "Lock Scope Minimized" + rule_68 (violation): "Plugin Threads Joined in Deinitialize" + rule_69 (warning): "Memory and Allocation Safety" + rule_70 (violation): "Framework Pointers Not Accessed After Deinitialize" + rule_71 (violation): "hresult Return Values Checked" + rule_72 (warning): "ASSERT Only for Programmer Invariants" + rule_73 (violation): "Security: Logging, Shell, Path, and Error Exposure" + rule_74 (violation): "JSON-RPC Input Validation for Bounds and Types" + rule_75 (warning): "Config Completeness and Resource Cleanup" + rule_76 (warning): "OOP Error Propagation and Method Naming" + rule_77 (suggestion): "Observer Classes Private and Nested" + rule_78 (violation): "No Deprecated JSON-RPC APIs" + rule_79 (violation): "All Acquired Pointers Cleared After Deinitialize" + +- [x] 1.2 Create `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` (v3.2.2) + - File header: version (unquoted number: 3.2.2, NOT quoted string "3.2.2"), title, description + with full CHANGELOG using format: `v3.2.2 (current):` (NOT dates like `v3.2.2 (2026-06-08):`) + - Section headers use `# ===...===` style (NOT `# ───...───`) + - Rule names use Title Case (e.g. "File and Namespace Structure" not "File and namespace structure") + - core_rules list (15 rules, all severity: violation): + core_1_1 ("File and Namespace Structure"), core_2_1 ("Interface Declaration Shape"), + core_3_1 ("Interface ID Registration"), core_4_1 ("Pure Virtual Methods Only"), + core_5_1 ("Return Type Conventions" — Core::hresult mandatory for @json in Thunder 5.0+), + core_6_1 ("Const Correctness"), core_9_1 ("Thunder Type Conventions" — string not std::string), + core_10_1 ("Register/Unregister Patterns" — INotification 1:many + ICallback 1:1), + core_11_1 ("Nested Event Interfaces" — @event tag, EXTERNAL, ID required), + core_12_1 ("@json Tag (CRITICAL)"), core_13_1 ("Binary Compatibility"), + core_14_1 ("No AddRef/Release Redeclaration"), core_15_1 ("No std::map in Interfaces"), + core_16_1 ("Explicit Integer Widths"), core_17_1 ("@restrict Mandatory with std::vector") + - advisory_rules list (4 rules): + advisory_m1_1 ("Single Responsibility Principle" — violation), + advisory_m2_1 ("Enum Underlying Types" — warning, exclude anonymous ID enum), + advisory_m3_1 ("No Exceptions" — violation), + advisory_m5_1 ("@restrict for Non-vector Params" — warning, + explicitly states: does NOT apply to std::vector, that is covered by core_17_1) + - Each rule structure: id, name, severity, description (multiline), extraction_logic (numbered steps), + verification_logic (numbered steps), violation_pattern (single-line string NOT bullet list), + fix_template (shows // WRONG: + // Correct: pattern), citation (reference to real Thunder file) + - NO category field (removed in v3.2.2) + +## Phase 2: Prompt files + +- [x] 2.1 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` + Frontmatter: title: "Thunder Plugin Rule Review", description (mention 79 unified rules, semantic review) + Sections (in order): + - Core Principle: semantic code review for all 79 rules. NEVER pattern-match. + ❌ open-ended vs ✅ bounded examples. All rules produce the same output format. + - Report Output Philosophy: CRITICAL note — only report issues; PASS/SKIP as summary counts only; + line numbers always required; always use ACTUAL plugin name in citations, NEVER {PluginName} + - Usage: command syntax supporting TWO modes: + Mode 1 (full plugin): `/thunder-plugin-review ` + — reviews ALL files in the plugin folder against all applicable rules + Examples: `/thunder-plugin-review Dictionary`, `/thunder-plugin-review HdmiCecSink` + Mode 2 (single file): `/thunder-plugin-review ` + — reviews ONLY the specified file against rules applicable to that file + — skips phases/rules that target other files + Examples: `/thunder-plugin-review Dictionary Dictionary.cpp`, + `/thunder-plugin-review HdmiCecSink Module.h` + File scoping logic for Mode 2: + - If file is Module.h → run Phase 1 rules (rule_01, rule_03) + Holistic Rules (8 sub-phases) relevant to headers + - If file is Module.cpp → run Phase 1 rules (rule_02) + Holistic Rules (8 sub-phases) for .cpp + - If file is {PluginName}.h → run Phase 2, 3, 4 (class declaration), Holistic Rules (8 sub-phases) for headers + - If file is {PluginName}.cpp → run Phase 2, 3, 4, 5 (implementation), Holistic Rules (8 sub-phases) for .cpp + - If file is CMakeLists.txt → run Phase 7 only + CMake Holistic Rules (8 sub-phases) + - If file is {PluginName}.conf.in → run Phase 6 only + - If file is {PluginName}Implementation.h/cpp → run Phase 5C (OOP) + applicable Holistic Rules (8 sub-phases) + - For any file: run applicable Holistic Rules (8 sub-phases) that match the file type + 5-step workflow (accept name + optional file → locate folder → identify target files + → run applicable rules only → report) + - Methodology: Step 1 (load YAML from ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml — + contains all 79 rules in phase_X_checkpoints and general_rules sections), + Step 2 (identify plugin files — primary: ThunderNanoServices/{PluginName}/, fallback: workspace search, + last resort: ask user; files table: Module.cpp, Module.h, {PluginName}.h, {PluginName}.cpp, + CMakeLists.txt, {PluginName}.conf.in (optional), {PluginName}Implementation.h/cpp (optional)), + Step 3 (execute all rules: read → reason → cite → fix) + - CRITICAL FILE SCOPING RULES: Phase 1 applies ONLY to Module.h/Module.cpp; + Class Registration (Phase 3) applies ONLY to main plugin class — internal helpers excluded + - Contextual Judgment section: severity downgrade table with concrete example showing + reasoning field (required on downgrade, omitted otherwise), no escalation rule + - All rules defined inline in the prompt for reference + (exact severity, extraction target, question, verification logic, + citation format, conditional skip rules). + Phase counts: + Phase 1 Module Structure (3): + rule_01 (suggestion): "MODULE_NAME Plugin_ Prefix" + rule_02 (violation): "MODULE_NAME_DECLARATION" + rule_03 (warning): "Module.h Uses #pragma once" + Phase 2 Code Style (10): + rule_04 (violation): "VARIABLE_IS_NOT_USED Accuracy" + rule_05 (violation): "Error Code Preservation" + rule_06 (warning): "NULL vs nullptr" + rule_07 (violation): "No delete on COM Interface Pointers" + rule_08 (violation): "nullptr After Release" + rule_09 (violation conditional): "No QueryInterfaceByCallsign as Member" + rule_10 (violation): "No Smart Pointers on COM Objects" + rule_11 (violation): "No SmartLinkType for COMRPC Plugins" + rule_12 (violation): "No delete on Plugin Object" + rule_13 (violation): "No throw Keyword in Plugin Code" + Phase 3 Class Registration (3): + rule_14 (warning): "Special Members Deleted (Main Class)" + rule_15 (violation): "Plugin Metadata Registration" + rule_16 (violation conditional): "JSONRPC Inheritance When Used" + Phase 4 Lifecycle (12): + rule_17 (violation conditional): "IShell AddRef in Initialize" + rule_18 (violation conditional): "IShell Release in Deinitialize" + rule_19 (violation): "Information() Method" + rule_20 (violation conditional): "Root() Null Check" + rule_21 (violation conditional): "Root() Release in Deinitialize" + rule_22 (violation conditional): "Observer Cleanup in Deinitialize" + rule_23 (violation conditional): "SubSystems() Release in Deinitialize" + rule_24 (violation): "Constructor Must Be Empty" + rule_25 (violation conditional): "service->Register/Unregister Pairing" + rule_26 (violation): "Initialize Returns Error String on Failure" + rule_27 (violation): "No Manual Deinitialize() in Initialize" + rule_28 (violation): "Destructor Must Be Empty" + Phase 5 Implementation (4): + rule_29 (violation conditional): "JSON-RPC Register/Unregister Pairing" + rule_30 (violation conditional): "SinkType Pattern for Subscribers" + rule_31 (violation conditional): "Unavailable() in SinkType Classes" + rule_32 (violation): "No Hardcoded Paths" + Phase 5C Out-of-Process (2 conditional): + rule_33: "OOP Connection Termination in Deinitialize" + rule_34: "connectionId Checked in IRemoteConnection Callbacks" + Phase 6 Configuration (3): + rule_35 (violation conditional): startmode declaration + rule_36 (violation conditional): Config Core::JSON::Container + rule_37 (violation): no hardcoded numeric tuning params + Phase 7 CMake (1): + rule_38 (violation conditional): "CXX_STANDARD Uses Thunder Variable" + Phase 8 COM Interface Rules (1): + rule_39 (violation): "COM Methods Return Core::hresult" + - Output Format: UNIFIED FILE-WISE grouping — all issues from all 79 rules grouped by + source file with a header "### {FileName} — N issue(s)" for each file that has failures; + within each file group, each failing rule is a YAML block with fields: + rule_id, status (FAIL/PASS/SKIP), severity (violation/warning/suggestion), + question, answer, extracted_code with [File:line] prefix, violation_line, + citation using ACTUAL plugin filename, fix, reasoning. + NO separate "Part 1" / "Part 2" sections — all findings in one list. + - Summary Format: Single unified Summary TABLE with columns Phase | PASS | FAIL | SKIP + (one row per phase + one row for "Holistic Rules (8 sub-phases)" + a Total row showing all 79), + followed by a numbered Next Steps list referencing [File:line] for each action item + - Key Advantages section, Important Notes section + - Command Examples at end + +- [x] 2.2 Create `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` + Frontmatter: title: "Thunder Interface Validator", description (mention 19 rules, 15 core + 4 advisory) + Sections (in order): + - Context: Thunder uses COM-style interfaces, rules in thunder-interface-rules.yaml (v3.2.2) + - Quick Reference table: all 19 rules (15 core + 4 advisory) as a markdown table + with ID, Rule name (Title Case matching YAML), Key Point + - CRITICAL note: load ThunderTools/PluginQA/rules/thunder-interface-rules.yaml for full + rule definitions, extraction logic, verification logic, and fix templates before validating + - Your Task: 5 steps (identify file → load YAML → validate all 19 rules → + report with 🔴/🟡/🟢/✅/Compatibility Notes sections → provide specific fixes) + - Step 6 — Generate CSV Report: file path format, CSV columns table, formatting rules, + post-generation message with file count summary + Start-Process command + - Example Output structure showing grouped sections + - Contextual Judgment: JUDGE step table with Status field mapping + - Common Critical Issues: 5 bullet points (missing @json, vector without @restrict, + std::map, missing nested IDs, ambiguous int types) + - Important Notes: Thunder docs link, validation priorities numbered list + (@json first through type conventions last) + +- [x] 2.4 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-rule-manager.prompt.md` + Frontmatter: title: "Thunder Plugin Rule Manager", + description: "Add, update, or remove plugin rules (automated or manual) via guided questionnaire — keeps YAML, prompt, README, and spec in sync" + Sections (in order): + - Purpose: files kept in sync (YAML + prompt + README + spec) + - Step 0: document template fast path — user pastes filled .md template with sections: + "What This Rule Checks", "Extraction Target", "Yes/No Question", "Verification Steps", + "Violation Pattern", "Fix", "Conditional"; also runs Manual vs Automated Classification + to verify Type is correct + - Step 1: collect action via vscode_askQuestions using TEXTUAL descriptions (not JSON blocks): + Question 1 (header: "action"): "What do you want to do?" with options Add/Update/Remove + Question 2 (header: "rule_kind"): "Is this an automated rule or a manual rule?" with + options: "Automated rule (bounded yes/no query, one code block)" | + "Manual rule (semantic review, requires holistic code understanding)" + message explaining the difference: + - Automated: can be reduced to ONE yes/no question on ONE specific code block + - Manual: requires architectural judgment, threading analysis, security reasoning, + or reading multiple code paths that cannot be bounded to a single question + Question 3 (header: "phase"): (only if automated) "Which phase does this rule belong to?" + with options for all 9 phase groups + - Step 2 branching: + - REMOVE: ask for rule_id (rule_id or manual_XX) → Step 4 + - UPDATE: (2a) ask for rule_id; (2b) display current rule annotated with numbered + field labels; (2c) multi-select which fields to change; (2d) ask only new values + - ADD (phase rule): ask all structured fields (extraction, question, verification + logic, fix, conditional) with detailed messages including ✅/❌ guidance + - ADD (manual): ask only: name, severity, review_question, review_method, + evidence_requirement — simpler flow since holistic rules don't need extraction/bounded_query + - Step 3 (ADD/UPDATE): apply to YAML (phase checkpoints go under phase_N section, + Holistic Rules (8 sub-phases) go under general_rules section) + prompt + README + spec; + increment/decrement counts in metadata + - Step 4 (REMOVE): remove from YAML + prompt + README + spec; decrement counts + - Step 5: confirmation report listing all files updated with new counts + - Phase vs General Classification table at end: 5 phase checkpoint criteria vs 6 + General criteria; ambiguous cases prompt user before proceeding + +- [x] 2.5 Create `ThunderTools/PluginQA/Prompts/thunder-interface-rule-manager.prompt.md` + Frontmatter: title: "Thunder Interface Rule Manager", + description: "Add, update, or remove COM interface validation rules via guided questionnaire — keeps YAML, prompt, and spec in sync" + Sections (in order): + - Purpose: files kept in sync (YAML + prompt + spec) + - Step 0: document template fast path — user pastes filled .md template with sections: + "Description", "How to Find It (Extraction Logic)", "How to Verify It", + "Violation Pattern", "Fix", "Example"; also runs Core vs Advisory Classification + - Step 1: collect action via vscode_askQuestions using TEXTUAL descriptions (not JSON): + Question 1 (header: "action"): "What do you want to do?" with options Add/Update/Remove + Question 2 (header: "rule_list"): "Is this a core rule or an advisory rule?" with + detailed message explaining difference between core (codegen/ABI/crash) and advisory + (best practice/style) + - Step 2 branching: + - REMOVE: ask for rule_id only → Step 4 + - UPDATE: (2a) ask for rule_id; (2b) display current rule annotated with numbered + field labels [1]–[9]; (2c) multi-select which fields to change; (2d) ask only new + values for selected fields + - ADD: ask all fields (id, name, severity, description, extraction_logic, + verification_logic, violation_pattern, fix_template, example) with detailed messages + - Step 3 (ADD/UPDATE): update YAML (bump patch version + CHANGELOG entry), update + Quick Reference table row + rule detail block in prompt, update spec.md requirement line; + update rule count comments + - Step 4 (REMOVE): remove from all 3 files; bump version; add CHANGELOG removal entry + - Step 5: confirmation report listing all 3 files updated with new version and counts + - Core vs Advisory Classification table at end: core = codegen/ABI/crash failures; + advisory = best practice; wrong list auto-corrected with explanation + +- [x] 2.3 Create `ThunderTools/PluginQA/Prompts/thunder-generate-plugin.prompt.md` + Frontmatter: title: "Thunder Plugin Generator", + description: "Interactive Thunder plugin skeleton generator using PluginSkeletonGenerator.py" + Sections: + - Overview: what the generator does; CRITICAL RULES (use vscode_askQuestions, NOT chat; + only run PSG; auto-fix include paths; do not modify anything else; NO --config flag) + - Required Inputs from User: table with columns Parameter | Format | Options | Default + (all 8 parameters: PluginName, OutputDirectory, OutOfProcess, CustomConfig, + InterfacePaths, Preconditions, Terminations, Controls) + - Step 1: Collect Parameters via vscode_askQuestions — JSON block with SIMPLER question text + (e.g. "What is the name of your plugin? (PascalCase, e.g. HelloWorld)" not elaborate + markdown formatting in the question text); options for Yes/No with descriptions; + message fields for additional context + - Answer processing rules: Yes/No → y/n, empty OutputDirectory → ThunderNanoServices, + subsystems conditional (if any non-empty → PSG subsystems answer is y) + - Workflow — Step 2 (locate PSG), Step 3 (run PSG interactively; feed answers to each + PSG prompt in exact order; handle subsystems conditional) + - Post-generation: auto-fix include paths bug workaround; list generated files + - Error handling for PSG not found or generation failure + +## Phase 3: Setup script + +- [x] 3.1 Create `ThunderTools/PluginQA/setup-prompts.py` (cross-platform Python 3) + - No external dependencies (stdlib only: json, os, sys, shutil, platform, datetime) + - Detect platform (Windows/Mac/Linux) and find settings.json accordingly + - Also detect VS Code Insiders variant + - Backup: copy settings.json to settings.json.backup.{timestamp} + - Parse JSON (handle missing or empty file: default to {}) + - Merge: add "ThunderTools/PluginQA/Prompts": true under chat.promptFilesLocations + - Write back with indent=4 + - Idempotent: skip if key already present + - Print clear next steps + NOTE: Only a single Python script is provided. No .ps1 or .sh variants. + +## Phase 4: Documentation + +- [x] 4.1 Create `ThunderTools/PluginQA/README.md` + Sections (in order, matching final PluginQA README exactly): + - Title: Thunder PluginQA + - Overview: automated validation tools, bullet list: Thunder COM Interfaces (19 rules), + Thunder Plugins (39 checkpoints, 8 phases) + - Quick Start: Setup (3 platform options with code blocks), Reload VS Code (2 steps + note), + Use the Prompts (/thunder-interface-review, /thunder-plugin-review, /thunder-generate-plugin) + each with description + - Directory Structure: tree diagram of ThunderTools/PluginQA/ + - Interface Validation section: intro, Core Rules table (15 rows: ID, Rule, Critical Issues), + Advisory Rules list (4 bullets), link to YAML + - Plugin Validation section: intro, Validation Phases table by phase name with checkpoint count, + all 8 phases listed with their checkpoints as sub-bullets + - Plugin Generation section: How It Works (4 steps), Parameters table (8 rows), + Example Usage (simple + with interface), Generated Files tree + - Next Steps After Generation (5 numbered steps) + - Integration with Validation (1 code block) + - Methodology section: Understand-First methodology explanation, example block + - Setup Script Details: what settings.json entry looks like, What the scripts do (5 bullets), + Manual Setup fallback instructions + - FAQ: 5 Q&A pairs + - Validation Examples: Interface Validation Output code block, Plugin Checkpoint Output code block + - Version History: v3.2.2 (current), v3.2.1, v3.3.0, v3.0.0 + - Contributing: 5-step process + - Support: 3 bullet points including Thunder docs URL + - Footer: Made with ⚡ for Thunder developers + +- [x] 4.2 Create `ThunderTools/PluginQA/PLUGIN_GENERATOR_GUIDE.md` + Sections: + - Title, Overview paragraph + - Quick Start: 5 numbered steps + - Input Parameters: Required (3 params) and Optional (5 params) with descriptions + - Example Scenarios: 3 scenarios (simple hello world, with config, OOP with interface) + each showing exact inputs and what gets generated + - Generated Files: tree for each scenario type + - Troubleshooting: common issues (PSG not found, include path errors) + - Integration with validation commands + +- [x] 4.3 Create `ThunderTools/PluginQA/Prompts/README-interface-rules-manager.md` + - Documentation for the interface rules manager (optional supplemental prompt) + - Covers: how to add new rules to thunder-interface-rules.yaml, + rule structure reference, testing new rules against example interfaces + +## Phase 5: Report generation + +- [x] 5.1 Add Step 6 (CSV report) to `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` + - Appended after Command Examples section + - File path: `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` + - Create folder if absent; never overwrite (append _2, _3 suffix) + - Columns (14, exact order): No, Plugin, Date, Phase, rule_id, Rule_Name, Status, + Severity, File, Line, Citation, Issue_Description, Fix_Summary, Reasoning + - One row per FAIL/WARNING/SUGGESTION only — PASS and SKIP excluded + - Status = effective status after JUDGE step (VIOLATION/WARNING/SUGGESTION) + - Reasoning column populated only when JUDGE downgraded severity; empty otherwise + - Formatting: UTF-8, CRLF, fields with commas/dashes quoted, embedded quotes escaped as "" + - Empty report (all pass): header row + one comment row + - Post-generation chat message: file path, issue counts, Start-Process command for Excel + +- [x] 5.2 Add Step 6 (CSV report) to `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` + - Appended after Important Notes section + - File path: `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` + - Same no-overwrite rule, same formatting rules + - Columns (13, exact order): No, Interface, Date, Rule_ID, Rule_Name, Status, + Severity, File, Line, Citation, Issue_Description, Fix_Summary, Reasoning + - One row per violated rule only + - Same post-generation chat message format + +- [x] 5.3 Create spec `ThunderTools/openspec/changes/thunder-plugin-qa/specs/reports/spec.md` + - REQ-R1: plugin CSV path + no-overwrite rule + - REQ-R2: interface CSV path + no-overwrite rule + - REQ-R3: plugin CSV columns table (14 columns with descriptions + examples) + - REQ-R4: interface CSV columns table (13 columns — Category removed) + - REQ-R5: CSV formatting rules (UTF-8, CRLF, quoting, escaping, empty fields) + - REQ-R6: folder creation requirement + - REQ-R7: post-generation chat message format + - REQ-R8: empty report format (all pass case) + - Folder structure diagram + - Out of scope: .xlsx, report viewer, diff, CI upload \ No newline at end of file diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 00000000..392946c6 --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,20 @@ +schema: spec-driven + +# Project context (optional) +# This is shown to AI when creating artifacts. +# Add your tech stack, conventions, style guides, domain knowledge, etc. +# Example: +# context: | +# Tech stack: TypeScript, React, Node.js +# We use conventional commits +# Domain: e-commerce platform + +# Per-artifact rules (optional) +# Add custom rules for specific artifacts. +# Example: +# rules: +# proposal: +# - Keep proposals under 500 words +# - Always include a "Non-goals" section +# tasks: +# - Break tasks into chunks of max 2 hours From fe2814220a216a40e65408a72cdcc37ec79580b4 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Mon, 15 Jun 2026 16:33:12 +0530 Subject: [PATCH 02/15] Updated the Plugin and Interface Rules --- .../interface/Thunder-interface-rules.md | 31 +- .../specs/plugin/Thunder-plugin-rules.md | 918 ++++++++++++++++-- 2 files changed, 881 insertions(+), 68 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 51616b1f..6debea54 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -8,16 +8,6 @@ Rules for validating Thunder COM interface headers in `ThunderInterfaces/interfaces/`. Every rule uses semantic reasoning — read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. -## Changelog - -| Version | Changes | -|---------|---------| -| v3.2.2 (current) | Removed category field from all rules (unused by validation logic); Rule names standardised to Title Case throughout; advisory_m5_1 clarified: explicitly does NOT apply to std::vector (covered by core_17_1) | -| v3.2.1 | Added core_17_1: @restrict mandatory with std::vector parameters; advisory_m5_1 scoped to non-vector parameters only | -| v3.2.0 | Added core_16_1: explicit integer widths (uint32_t not int); Added advisory_m5_1: @restrict for non-vector constrained parameters | -| v3.1.0 | Added core_15_1: no std::map in interfaces; Strengthened core_5_1: Core::hresult mandatory for @json interfaces in Thunder 5.0+ | -| v3.0.2 | Initial public release with 13 core rules and 3 advisory rules | - --- ## Core Rules (17) — Severity: Violation @@ -48,6 +38,7 @@ Thunder interface headers must follow the standard file and namespace structure: **Violation Pattern:** Interface not in WPEFramework::Exchange namespace, file name mismatch, or implementation code present **Fix Example:** + ```cpp // WRONG: namespace WPEFramework { @@ -94,6 +85,7 @@ Thunder COM interfaces must follow the correct declaration shape: **Violation Pattern:** Interface missing EXTERNAL macro, wrong inheritance, missing ID enum, or non-pure-virtual methods **Fix Example:** + ```cpp // WRONG: class IDictionary : public Core::IUnknown { @@ -133,6 +125,7 @@ Every Thunder COM interface must have a unique numeric ID registered in `RPC::ID **Violation Pattern:** Interface ID missing from IDs registration, uses raw number, or is not unique **Fix Example:** + ```cpp // WRONG: enum { ID = 0x100 }; // ← raw number, not registered @@ -169,6 +162,7 @@ Thunder COM interface methods must be pure virtual (`= 0`). No default implement **Violation Pattern:** Non-pure-virtual method, inline implementation, or static method in COM interface **Fix Example:** + ```cpp // WRONG: virtual Core::hresult Get(const string& key, string& value) { @@ -205,6 +199,7 @@ In Thunder 5.0+, all COM interface methods annotated with `@json` (i.e. methods **Violation Pattern:** Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+ **Fix Example:** + ```cpp // WRONG: virtual void SetVolume(const uint8_t volume) = 0; @@ -245,6 +240,7 @@ Interface methods must use const correctly: **Violation Pattern:** @out parameter declared const preventing the implementation from writing the output value **Fix Example:** + ```cpp // WRONG: virtual Core::hresult Get(const string& key, const string& value /* @out */) = 0; @@ -286,6 +282,7 @@ Using `std::string` in interfaces breaks cross-ABI compatibility. **Violation Pattern:** std::string used in interface — must use Thunder string type alias **Fix Example:** + ```cpp // WRONG: virtual Core::hresult Get(const std::string& key, std::string& value) = 0; @@ -323,6 +320,7 @@ Register/Unregister must take a pointer to the notification interface. ICallback **Violation Pattern:** Register(INotification*) present but Unregister(INotification*) missing, or non-standard notification pattern **Fix Example:** + ```cpp // WRONG: (Unregister missing) virtual Core::hresult Register(INotification* notification) = 0; @@ -370,6 +368,7 @@ Missing `@event` prevents the code generator from emitting event dispatch code. **Violation Pattern:** @event tag missing on nested notification interface, or missing EXTERNAL/ID **Fix Example:** + ```cpp // WRONG: struct INotification : virtual public Core::IUnknown { @@ -410,6 +409,7 @@ If an interface intentionally does not need JSON-RPC (pure COM only), the absenc **Violation Pattern:** @json tag missing above interface struct declaration — no JSON-RPC code will be generated **Fix Example:** + ```cpp // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { @@ -451,6 +451,7 @@ Binary-incompatible changes require creating a new interface version (`IFoo2`). **Violation Pattern:** Released interface has methods removed, reordered, or signatures changed — breaks binary compatibility **Fix Example:** + ```cpp // WRONG: (removed a method or changed signature in a released interface) // v1: virtual Core::hresult Get(const string& key, string& value) = 0; @@ -487,6 +488,7 @@ struct EXTERNAL IDictionary2 : virtual public IDictionary { **Violation Pattern:** AddRef() or Release() redeclared in interface — must be inherited from Core::IUnknown only **Fix Example:** + ```cpp // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { @@ -525,6 +527,7 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { **Violation Pattern:** std::map used in interface parameter — not serialisable across process boundaries **Fix Example:** + ```cpp // WRONG: virtual Core::hresult GetAll(std::map& values /* @out */) = 0; @@ -558,6 +561,7 @@ Interface method parameters and return values must use explicit-width integer ty **Violation Pattern:** Platform-dependent integer type (int, long, short) used in interface — must use explicit-width types (uint32_t, etc.) **Fix Example:** + ```cpp // WRONG: virtual Core::hresult SetTimeout(const int timeout) = 0; @@ -595,6 +599,7 @@ Every interface method parameter of type `std::vector` (or `Core::JSON::ArrayTyp **Violation Pattern:** std::vector parameter missing @restrict annotation — required for safe bounds checking **Fix Example:** + ```cpp // WRONG: virtual Core::hresult GetItems(std::vector& items /* @out */) = 0; @@ -630,6 +635,7 @@ Each COM interface should have a single, clearly defined responsibility. An inte **Violation Pattern:** Interface mixes multiple unrelated responsibilities — consider splitting into focused interfaces **Fix Example:** + ```cpp // WRONG: IDictionaryAndNetwork mixes dictionary and network concerns struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { @@ -668,6 +674,7 @@ Enums used in interface parameters or return types should use explicit underlyin **Violation Pattern:** Named enum used in interface parameter lacks explicit underlying type — consider adding : uint8_t or : uint32_t **Fix Example:** + ```cpp // WRONG: (named enum without explicit type) enum State { IDLE, ACTIVE, ERROR }; @@ -706,6 +713,7 @@ Thunder COM interfaces and their implementations must not use C++ exceptions. Ex **Violation Pattern:** Exception specification or throw statement in COM interface or implementation — use Core::hresult for error reporting **Fix Example:** + ```cpp // WRONG: virtual Core::hresult Get(const string& key, string& value) throw(std::exception) = 0; @@ -744,6 +752,7 @@ Parameters with natural upper bounds (strings with max length, integers with max **Violation Pattern:** Non-vector parameter with natural upper bound lacks @restrict annotation (advisory) **Fix Example:** + ```cpp // Without restriction (acceptable for many cases): virtual Core::hresult SetName(const string& name) = 0; @@ -755,4 +764,4 @@ virtual Core::hresult SetName(const string& name /* @restrict:64 */) = 0; virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IVolumeControl.h` — @restrict on bounded params +**Citation:** `ThunderInterfaces/interfaces/IVolumeControl.h` — @restrict on bounded params \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md b/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md index 12f60eaf..bc8ecd6f 100644 --- a/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md @@ -1,20 +1,5 @@ # Thunder Plugin Rules — v3.3.0 -> **Purpose:** This document is the authoritative checklist for every Thunder plugin PR review. -> Every rule listed here must be verified before a PR is approved. -> Violations and warnings must be addressed; suggestions are encouraged but not blocking. - ---- - -## How to Use This Document - -1. **Read the full plugin first** — all source files, headers, CMakeLists, and config. - Build a mental model of the plugin's architecture, lifecycle, threading model, and ownership before checking any individual rule. -2. **Check each rule** using the full context, not in isolation. -3. **Cite violations** with exact file and line number (e.g. `[Dictionary.cpp:108]`). -4. **Show the fix** — the corrected code block only, not the whole file. -5. **Never escalate severity** beyond what is defined per rule. - ### Severity Levels | Level | Meaning | @@ -1213,13 +1198,33 @@ virtual Core::hresult SetValue(const string& key, const string& value) = 0; The following rules require reading the full plugin context before evaluating. They span multiple files and patterns. +> **YAML source:** `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` → `general_rules` section + --- ### rule_40 **#pragma once** | `suggestion` | Category: Conventions -Every header file in the plugin must use `#pragma once` as its include guard. +**What to check:** Every header file (`.h`) in the plugin uses `#pragma once` as its include guard — not legacy `#ifndef`/`#define`/`#endif`. + +**Where to look:** All `.h` files in the plugin folder. + +**Violation pattern:** A header file uses legacy `#ifndef` include guard instead of `#pragma once`. + +```cpp +// WRONG: +#ifndef DICTIONARY_H +#define DICTIONARY_H +// ... +#endif + +// Correct: +#pragma once +// ... +``` + +**Citation format:** `[FileName.h:1] Missing #pragma once — uses legacy include guard` --- @@ -1227,7 +1232,26 @@ Every header file in the plugin must use `#pragma once` as its include guard. **Apache 2.0 Copyright Header** | `suggestion` | Category: Conventions -Every source file must contain a proper Apache 2.0 copyright header at the top. +**What to check:** Every source file (`.cpp`, `.h`) starts with the standard Apache 2.0 copyright header block. + +**Where to look:** First 20 lines of every source file. + +**Violation pattern:** Source file is missing the Apache 2.0 copyright header. + +```cpp +// Correct — top of every file: +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * ... + */ +``` + +**Citation format:** `[FileName.cpp:1] Missing Apache 2.0 copyright header` --- @@ -1235,7 +1259,23 @@ Every source file must contain a proper Apache 2.0 copyright header at the top. **STL Types** | `warning` | Category: Code Quality -Thunder type aliases (`string`, `vector`) must be used instead of their `std::` equivalents (`std::string`, `std::vector`) wherever Thunder aliases are available. Check function signatures, class members, and return types. +**What to check:** Thunder type aliases (`string`, `vector`) are used instead of their `std::` equivalents (`std::string`, `std::vector`) wherever Thunder aliases are available. + +**Where to look:** Function signatures, class member declarations, return types, and local variables. + +**Violation pattern:** `std::string`, `std::vector`, or other STL types used where Thunder aliases exist. + +```cpp +// WRONG: +std::string GetName() const; +std::vector _items; + +// Correct: +string GetName() const; +std::vector _items; // Thunder aliases 'string' but not all containers +``` + +**Citation format:** `[PluginName.h:LINE] std::string used — use Thunder alias 'string'` --- @@ -1243,7 +1283,32 @@ Thunder type aliases (`string`, `vector`) must be used instead of their `std::` **ASSERT vs Error Handling** | `warning` | Category: Code Quality -`ASSERT` must be used only for programmer invariants — conditions that must always be true in correct code. `ASSERT` must never be used for runtime error handling (user input errors, network failures, missing configuration). +**What to check:** `ASSERT` is used only for programmer invariants (conditions that must always be true in correct code). It must never be used for runtime error handling — things that can legitimately fail (user input, network, configuration). + +**Where to look:** All `ASSERT(...)` calls in the plugin. + +**Violation pattern:** `ASSERT` used on a condition that can fail at runtime. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + _impl = service->Root(); + ASSERT(_impl != nullptr); // Root() CAN return nullptr at runtime + _impl->DoWork(); +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + _impl = service->Root(); + if (_impl == nullptr) { + return "Failed to acquire implementation"; + } + ASSERT(_service != nullptr); // OK — _service was set above and must be valid here + _impl->DoWork(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] ASSERT used on runtime condition — use proper error handling` --- @@ -1251,7 +1316,34 @@ Thunder type aliases (`string`, `vector`) must be used instead of their `std::` **OOP Registration Order** | `violation` | Category: Lifecycle Integrity -In out-of-process plugins, `service->Register()` and `service->Unregister()` must be called in the correct order relative to acquiring and releasing the remote implementation. +**What to check:** In out-of-process plugins, `service->Register()` must be called **after** the remote implementation is acquired, and `service->Unregister()` must be called **before** the remote implementation is released. + +**Where to look:** `Initialize()` and `Deinitialize()` in OOP plugins. + +**Violation pattern:** `service->Register()` called before `Root()`, or `service->Unregister()` called after implementation release. + +```cpp +// WRONG: +string Initialize(PluginHost::IShell* service) { + service->Register(_notification); // too early + _impl = service->Root(); +} + +// Correct: +string Initialize(PluginHost::IShell* service) { + _impl = service->Root(); + if (_impl != nullptr) { + service->Register(_notification); // after acquisition + } +} +void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); // before release + _impl->Release(); + _impl = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Registration order incorrect relative to implementation acquisition` --- @@ -1259,7 +1351,30 @@ In out-of-process plugins, `service->Register()` and `service->Unregister()` mus **Complete State Reset in Deinitialize** | `violation` | Category: Lifecycle Integrity -Every member variable set during `Initialize()` or during operation must be reset to its default/null state in `Deinitialize()`. A subsequent `Initialize()` call must start from a completely clean state. +**What to check:** Every member variable set during `Initialize()` or during operation must be reset to its default/null state in `Deinitialize()`. A subsequent `Initialize()` call must start from a completely clean state. + +**Where to look:** Compare class member declarations with `Deinitialize()` body — every non-trivial member must be reset. + +**Violation pattern:** Member variable set during plugin operation but not reset in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(PluginHost::IShell* service) { + _service->Release(); + _service = nullptr; + // _connectionId not reset, _isActive not reset +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + _service->Release(); + _service = nullptr; + _connectionId = 0; + _isActive = false; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Member '_connectionId' not reset in Deinitialize()` --- @@ -1267,7 +1382,31 @@ Every member variable set during `Initialize()` or during operation must be rese **Reverse-Order Cleanup** | `suggestion` | Category: Lifecycle Integrity -`Deinitialize()` should release resources in the reverse order of how they were acquired in `Initialize()`. +**What to check:** `Deinitialize()` should release resources in the reverse order of how they were acquired in `Initialize()`. This prevents use-after-free when later resources depend on earlier ones. + +**Where to look:** Compare acquisition order in `Initialize()` with release order in `Deinitialize()`. + +**Violation pattern:** Resources released in the same order as acquired (not reversed). + +```cpp +// Initialize acquires: _service → _impl → _notification +// Deinitialize should release: _notification → _impl → _service + +// WRONG: +void Deinitialize(PluginHost::IShell* service) { + _service->Release(); _service = nullptr; // first acquired, first released + _impl->Release(); _impl = nullptr; +} + +// Correct: +void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); // last acquired, first released + _impl->Release(); _impl = nullptr; + _service->Release(); _service = nullptr; // first acquired, last released +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Cleanup order does not reverse acquisition order` --- @@ -1275,7 +1414,32 @@ Every member variable set during `Initialize()` or during operation must be rese **Observer Locking** | `violation` | Category: Concurrency -Observer lists must be protected by appropriate locking when iterated and when observers are added or removed, to prevent data races. +**What to check:** Observer/notification lists must be protected by a lock (`Core::CriticalSection`) when iterated and when observers are added or removed — to prevent data races from concurrent callback dispatch vs. registration changes. + +**Where to look:** Observer container access (iteration in notification dispatch, `push_back`/`erase` in Register/Unregister). + +**Violation pattern:** Observer list accessed without lock held. + +```cpp +// WRONG: +void NotifyObservers() { + for (auto& obs : _observers) { // no lock — race with Register/Unregister + obs->StateChanged(); + } +} + +// Correct: +void NotifyObservers() { + _adminLock.Lock(); + auto copy = _observers; // copy under lock + _adminLock.Unlock(); + for (auto& obs : copy) { + obs->StateChanged(); + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Observer list iterated without lock` --- @@ -1283,7 +1447,33 @@ Observer lists must be protected by appropriate locking when iterated and when o **AddRef/Release Balance** | `violation` | Category: COM Safety -Every `AddRef()` call on a COM interface must be balanced by exactly one `Release()` call across all code paths, including error paths. No leaks, no double-releases. +**What to check:** Every `AddRef()` call on a COM interface is balanced by exactly one `Release()` call across all code paths — including error/early-return paths. No leaks, no double-releases. + +**Where to look:** All COM interface pointer acquisitions and their corresponding release paths. + +**Violation pattern:** `AddRef()` without matching `Release()` on an error path, or `Release()` called twice. + +```cpp +// WRONG — leak on error path: +_impl = service->Root(); +if (_impl != nullptr) { + if (!Setup()) { + return "Setup failed"; // _impl leaked — never released + } +} + +// Correct: +_impl = service->Root(); +if (_impl != nullptr) { + if (!Setup()) { + _impl->Release(); + _impl = nullptr; + return "Setup failed"; + } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] COM interface leaked on error path — missing Release()` --- @@ -1291,7 +1481,21 @@ Every `AddRef()` call on a COM interface must be balanced by exactly one `Releas **CMake NAMESPACE Variable** | `suggestion` | Category: Conventions -`CMakeLists.txt` must use the `${NAMESPACE}` variable for target naming and installation paths per Thunder conventions. +**What to check:** `CMakeLists.txt` uses `${NAMESPACE}` for target naming and installation paths as per Thunder build conventions. + +**Where to look:** `CMakeLists.txt` — target names, install paths, and project declarations. + +**Violation pattern:** Hardcoded namespace string used instead of `${NAMESPACE}`. + +```cmake +# WRONG: +install(TARGETS ${MODULE_NAME} DESTINATION lib/wpeframework/plugins) + +# Correct: +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${NAMESPACE}/plugins) +``` + +**Citation format:** `[CMakeLists.txt:LINE] Hardcoded namespace — use ${NAMESPACE}` --- @@ -1299,7 +1503,26 @@ Every `AddRef()` call on a COM interface must be balanced by exactly one `Releas **Handlers Must Not Block** | `violation` | Category: Concurrency -No notification handler (`IPlugin::INotification` callbacks, `IRemoteConnection::INotification` callbacks) may perform blocking operations (network calls, file I/O, sleep, synchronous waiting) on the framework's callback thread. +**What to check:** No notification handler (`IPlugin::INotification`, `IRemoteConnection::INotification` callbacks) performs blocking operations — no network calls, file I/O, sleep, or synchronous waiting on the framework's callback thread. + +**Where to look:** All `Activated()`, `Deactivated()`, `Unavailable()`, and `Terminated()` callback bodies. + +**Violation pattern:** Blocking call inside a notification handler. + +```cpp +// WRONG: +void Activated(const string& callsign, PluginHost::IShell* shell) override { + std::this_thread::sleep_for(std::chrono::seconds(5)); // blocks framework thread + auto data = HttpGet("http://..."); // blocks on network +} + +// Correct: +void Activated(const string& callsign, PluginHost::IShell* shell) override { + _parent.ScheduleWork(callsign); // dispatch to WorkerPool +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Blocking operation in notification callback` --- @@ -1307,7 +1530,26 @@ No notification handler (`IPlugin::INotification` callbacks, `IRemoteConnection: **No Activate/Deactivate from Handlers** | `violation` | Category: Concurrency -No notification callback may call `service->Activate()` or `service->Deactivate()` directly — doing so causes deadlock on the framework thread. +**What to check:** No notification callback directly calls `service->Activate()` or `service->Deactivate()` — this causes deadlock because those methods acquire the same framework lock that dispatches callbacks. + +**Where to look:** All notification callback bodies. + +**Violation pattern:** `Activate()` or `Deactivate()` called from inside a notification handler. + +```cpp +// WRONG: +void Deactivated(const string& callsign, PluginHost::IShell* shell) override { + _service->Activate(PluginHost::IShell::REQUESTED); // DEADLOCK +} + +// Correct: +void Deactivated(const string& callsign, PluginHost::IShell* shell) override { + // Schedule activation on WorkerPool instead + Core::IWorkerPool::Instance().Submit(Core::ProxyType(*this)); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Activate/Deactivate called from notification handler — causes deadlock` --- @@ -1315,7 +1557,32 @@ No notification callback may call `service->Activate()` or `service->Deactivate( **Shared State Protected by CriticalSection** | `violation` | Category: Concurrency -Every member variable accessed from both the main thread and notification callbacks must be protected by a `Core::CriticalSection` lock. +**What to check:** Every member variable accessed from both the main thread (Initialize/Deinitialize, JSON-RPC handlers) and notification callbacks must be protected by a `Core::CriticalSection` lock. + +**Where to look:** Member variables modified in handlers AND accessed in other methods without locking. + +**Violation pattern:** Shared member accessed from multiple threads without lock. + +```cpp +// WRONG: +void Activated(...) override { _isReady = true; } // callback thread +uint32_t GetStatus(...) { return _isReady ? 1 : 0; } // JSON-RPC thread — no lock + +// Correct: +void Activated(...) override { + _adminLock.Lock(); + _isReady = true; + _adminLock.Unlock(); +} +uint32_t GetStatus(...) { + _adminLock.Lock(); + bool ready = _isReady; + _adminLock.Unlock(); + return ready ? 1 : 0; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Shared member '_isReady' accessed without lock` --- @@ -1323,7 +1590,27 @@ Every member variable accessed from both the main thread and notification callba **No Lock Held During Framework Callbacks** | `violation` | Category: Concurrency -All locks must be released before calling back into the Thunder framework (e.g. before `service->Submit()`, `Activate()`, or notification dispatch) to prevent deadlock. +**What to check:** All locks (`_adminLock`) must be released before calling back into the Thunder framework — before `service->Submit()`, `Notify()`, `Activate()`, or dispatching notifications. Holding a lock while calling framework APIs causes deadlock. + +**Where to look:** Code paths that hold `_adminLock` and then call framework methods. + +**Violation pattern:** Lock held during a framework callback invocation. + +```cpp +// WRONG: +_adminLock.Lock(); +_state = newState; +_service->Submit(PluginHost::IShell::ACTIVATED, this); // framework call while locked +_adminLock.Unlock(); + +// Correct: +_adminLock.Lock(); +_state = newState; +_adminLock.Unlock(); +_service->Submit(PluginHost::IShell::ACTIVATED, this); // lock released first +``` + +**Citation format:** `[PluginName.cpp:LINE] Framework callback called while holding _adminLock` --- @@ -1331,7 +1618,32 @@ All locks must be released before calling back into the Thunder framework (e.g. **Worker Jobs Check Deinitialize Guard** | `violation` | Category: Concurrency -All worker job (`IJob`, `WorkerPool`) `Dispatch()` methods must check a deinitialization guard flag at the start to avoid using stale pointers after `Deinitialize()` has run. +**What to check:** All worker job `Dispatch()` methods must check a deinitialization guard flag (e.g. `_deinitialized`) at the start to avoid using stale/null framework pointers after `Deinitialize()` has run. + +**Where to look:** All `IJob::Dispatch()` or WorkerPool job implementations. + +**Violation pattern:** Job dispatches without checking whether plugin has been deinitialized. + +```cpp +// WRONG: +void Dispatch() override { + _parent._service->DoSomething(); // _service may be nullptr after Deinitialize +} + +// Correct: +void Dispatch() override { + _parent._adminLock.Lock(); + if (_parent._service == nullptr) { + _parent._adminLock.Unlock(); + return; // plugin already deinitialized + } + auto* svc = _parent._service; + _parent._adminLock.Unlock(); + svc->DoSomething(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Worker job does not check deinitialize guard before using framework pointers` --- @@ -1339,7 +1651,26 @@ All worker job (`IJob`, `WorkerPool`) `Dispatch()` methods must check a deinitia **File Descriptors / Sockets Wrapped in RAII** | `violation` | Category: Resource Management -All file descriptors, sockets, and OS handles must be wrapped in RAII types or explicitly closed in `Deinitialize()`. They must never be leaked. +**What to check:** All file descriptors, sockets, and OS handles are wrapped in RAII types or explicitly closed in `Deinitialize()`. They must never be leaked on any code path. + +**Where to look:** All `open()`, `socket()`, `fopen()` calls and their corresponding close paths. + +**Violation pattern:** File descriptor or socket opened but not closed on all paths (including error paths). + +```cpp +// WRONG: +int fd = open("/dev/input0", O_RDONLY); +if (error) return "Failed"; // fd leaked + +// Correct: +Core::DataElementFile _file; // RAII — auto-closes on destruction +// Or explicit: +void Deinitialize(...) { + if (_fd >= 0) { close(_fd); _fd = -1; } +} +``` + +**Citation format:** `[PluginName.cpp:LINE] File descriptor not closed on error path` --- @@ -1347,7 +1678,28 @@ All file descriptors, sockets, and OS handles must be wrapped in RAII types or e **No Unbounded Memory Growth** | `violation` | Category: Resource Management -All data structures (`map`, `vector`, `list`, `queue`) populated at runtime must have a bounded size or eviction policy to prevent unbounded memory growth during sustained plugin operation. +**What to check:** All data structures (`map`, `vector`, `list`, `queue`) populated at runtime have a bounded size or eviction policy. Without bounds, long-running plugins exhaust memory. + +**Where to look:** All `push_back`, `insert`, `emplace` calls on member containers — check for corresponding eviction, size cap, or periodic cleanup. + +**Violation pattern:** Runtime-populated container with no size limit or eviction. + +```cpp +// WRONG: +void OnEvent(const Event& e) { + _eventLog.push_back(e); // grows forever +} + +// Correct: +void OnEvent(const Event& e) { + if (_eventLog.size() >= MAX_EVENTS) { + _eventLog.erase(_eventLog.begin()); + } + _eventLog.push_back(e); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Container '_eventLog' grows unbounded — add size limit` --- @@ -1355,7 +1707,30 @@ All data structures (`map`, `vector`, `list`, `queue`) populated at runtime must **Config Errors Return Non-Empty from Initialize** | `violation` | Category: Lifecycle Integrity -All configuration errors (missing required fields, invalid values, out-of-range values) must cause `Initialize()` to return a non-empty error string. +**What to check:** All configuration errors (missing required fields, invalid values, out-of-range values) must cause `Initialize()` to return a non-empty error string. Silently accepting bad config leads to undefined runtime behavior. + +**Where to look:** Configuration parsing in `Initialize()` — all branches after `FromString()` or `Config` field access. + +**Violation pattern:** Configuration error detected but `Initialize()` still returns empty string (success). + +```cpp +// WRONG: +_config.FromString(service->ConfigLine()); +if (_config.Port.Value() == 0) { + SYSLOG(Logging::Error, "Invalid port"); + // falls through to return string() — Thunder thinks init succeeded +} +return string(); + +// Correct: +_config.FromString(service->ConfigLine()); +if (_config.Port.Value() == 0) { + return "Configuration error: port must be non-zero"; +} +return string(); +``` + +**Citation format:** `[PluginName.cpp:LINE] Config error not reported — Initialize() returns empty string` --- @@ -1363,15 +1738,58 @@ All configuration errors (missing required fields, invalid values, out-of-range **interface->Register/Unregister Pairing** | `violation` | Category: JSON-RPC Compliance -Every `interface->Register()` call (registering a notification/callback on a non-service interface) must be matched by a corresponding `interface->Unregister()` in `Deinitialize()`. +**What to check:** Every `interface->Register()` call (registering a notification/callback on a non-service COM interface) must be matched by `interface->Unregister()` in `Deinitialize()`. + +**Where to look:** `Initialize()` for `_impl->Register(...)` calls; `Deinitialize()` for matching `_impl->Unregister(...)`. + +**Violation pattern:** Interface notification registered but not unregistered in `Deinitialize()`. + +```cpp +// WRONG: +string Initialize(...) { + _impl->Register(_sink); +} +void Deinitialize(...) { + _impl->Release(); _impl = nullptr; + // _impl->Unregister(_sink) missing — dangling callback +} + +// Correct: +void Deinitialize(...) { + _impl->Unregister(_sink); // unregister BEFORE release + _impl->Release(); _impl = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] interface->Register() without matching Unregister() in Deinitialize()` --- ### rule_59 -**Handler Registration Order in Initialize/Deinitialize** | `violation` | Category: JSON-RPC Compliance +**Handler Registration Order** | `violation` | Category: JSON-RPC Compliance + +**What to check:** Notification handlers must be registered **after** the interfaces they observe are fully acquired, and unregistered **before** those interfaces are released. Registering before acquisition or unregistering after release causes callbacks on partially-initialized state. + +**Where to look:** Ordering of `Register()`/`Unregister()` calls relative to interface acquisition/release in `Initialize()`/`Deinitialize()`. + +**Violation pattern:** Handler registered before interface is ready, or unregistered after interface is released. + +```cpp +// WRONG: +void Deinitialize(...) { + _impl->Release(); _impl = nullptr; // release first + service->Unregister(_notification); // unregister after — callback may fire on null _impl +} + +// Correct: +void Deinitialize(...) { + service->Unregister(_notification); // unregister first + _impl->Release(); _impl = nullptr; // then release +} +``` -Notification handlers must be registered **after** the interfaces they observe are fully acquired and set up, and unregistered **before** those interfaces are released — to avoid callbacks on partially-initialized state. +**Citation format:** `[PluginName.cpp:LINE] Handler unregistered after interface release — wrong order` --- @@ -1379,7 +1797,27 @@ Notification handlers must be registered **after** the interfaces they observe a **Use Core::ERROR_\* for Handler Failure Codes** | `violation` | Category: JSON-RPC Compliance -All failures in JSON-RPC handlers and COM interface method implementations must be reported using `Core::ERROR_*` constants — never raw numeric literals or Windows error codes. +**What to check:** All failure returns in JSON-RPC handlers and COM interface method implementations must use `Core::ERROR_*` named constants — not raw numeric literals. + +**Where to look:** Return statements in handler methods. + +**Violation pattern:** Raw integer returned instead of `Core::ERROR_*` constant. + +```cpp +// WRONG: +uint32_t SetValue(...) { + if (!valid) return 1; // raw integer + if (!found) return 0x80004005; // Windows HRESULT +} + +// Correct: +uint32_t SetValue(...) { + if (!valid) return Core::ERROR_GENERAL; + if (!found) return Core::ERROR_UNKNOWN_KEY; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Raw integer error code — use Core::ERROR_* constant` --- @@ -1387,7 +1825,30 @@ All failures in JSON-RPC handlers and COM interface method implementations must **Input Validation in JSON-RPC Handlers** | `violation` | Category: JSON-RPC Compliance -Every JSON-RPC handler must validate its input parameters (null pointer checks, string length limits, enum range checks) before using them. +**What to check:** Every JSON-RPC handler validates its input parameters (null checks, string length limits, enum range checks) before using them. Unvalidated external input leads to crashes or undefined behavior. + +**Where to look:** All JSON-RPC handler method bodies — the first thing after parameter extraction. + +**Violation pattern:** Input parameter used without validation. + +```cpp +// WRONG: +uint32_t SetName(const string& name) { + _name = name; // no length check — could be 10MB string + return Core::ERROR_NONE; +} + +// Correct: +uint32_t SetName(const string& name) { + if (name.empty() || name.length() > 256) { + return Core::ERROR_BAD_REQUEST; + } + _name = name; + return Core::ERROR_NONE; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] JSON-RPC input not validated before use` --- @@ -1395,7 +1856,24 @@ Every JSON-RPC handler must validate its input parameters (null pointer checks, **Event Constants and Typed JSON Payloads** | `warning` | Category: JSON-RPC Compliance -Event names must be defined as named constants rather than inline string literals. Event payloads must be typed JSON objects rather than untyped free-form strings. +**What to check:** Event names are defined as named constants (not inline string literals). Event payloads use typed `JsonData::*` classes, not untyped free-form strings. + +**Where to look:** All `Notify()` or event dispatch calls. + +**Violation pattern:** Inline string literal for event name, or untyped payload. + +```cpp +// WRONG: +Notify("stateChanged", "{\"state\":\"active\"}"); // inline string, untyped + +// Correct: +static constexpr auto EVT_STATE_CHANGED = _T("stateChanged"); +JsonData::StateInfo info; +info.State = "active"; +Notify(EVT_STATE_CHANGED, info); +``` + +**Citation format:** `[PluginName.cpp:LINE] Event name is inline string — define as named constant` --- @@ -1403,7 +1881,29 @@ Event names must be defined as named constants rather than inline string literal **COM Reference Counting Correctness** | `violation` | Category: COM Safety -COM reference counting must be correctly maintained across all code paths where COM interfaces are passed, returned, or stored — `AddRef` when storing, `Release` when done, no double-release or leak on any path including error paths. +**What to check:** COM reference counting is correctly maintained across all code paths — `AddRef` when storing a new copy, `Release` when done, no double-release or leak on any path including error paths. + +**Where to look:** All COM interface pointer assignments, returns, and storage patterns. + +**Violation pattern:** Interface pointer stored without `AddRef`, or released twice, or not released on error path. + +```cpp +// WRONG — double release: +_impl->Release(); +// ... later in same function ... +_impl->Release(); // double release — undefined behavior + +// WRONG — missing AddRef when copying: +IPlugin* copy = _impl; // no AddRef — two owners, one ref count + +// Correct: +IPlugin* copy = _impl; +copy->AddRef(); // now balanced +// ... use copy ... +copy->Release(); +``` + +**Citation format:** `[PluginName.cpp:LINE] COM reference counting incorrect — double release / missing AddRef` --- @@ -1411,7 +1911,26 @@ COM reference counting must be correctly maintained across all code paths where **No Hard Inter-Plugin Dependencies** | `warning` | Category: Inter-Plugin Design -The plugin must use the observer pattern and dynamic interface acquisition (`QueryInterfaceByCallsign`, `IPlugin::INotification`) for inter-plugin communication — no hard compile-time or startup-time dependencies on specific other plugins being present. +**What to check:** The plugin uses the observer pattern and dynamic interface acquisition (`QueryInterfaceByCallsign`, `IPlugin::INotification`) for inter-plugin communication — no hard compile-time or startup-time dependencies. + +**Where to look:** `#include` directives, `Initialize()` for hardcoded callsign requirements. + +**Violation pattern:** Plugin fails to start if another specific plugin is not present. + +```cpp +// WRONG: +#include "../OtherPlugin/OtherPlugin.h" // compile-time dependency +auto* other = static_cast(GetPlugin("OtherPlugin")); + +// Correct: +auto* other = _service->QueryInterfaceByCallsign("OtherPlugin"); +if (other != nullptr) { + other->DoWork(); + other->Release(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Hard dependency on another plugin — use dynamic acquisition` --- @@ -1419,7 +1938,28 @@ The plugin must use the observer pattern and dynamic interface acquisition (`Que **JSON-RPC Handlers Are Re-entrant Safe** | `violation` | Category: Concurrency -All JSON-RPC handlers must be safe for concurrent invocation — either accessing only local state or properly locking shared state. +**What to check:** All JSON-RPC handlers are safe for concurrent invocation — they either access only local state or properly lock shared state before access. + +**Where to look:** All JSON-RPC handler bodies that access class member variables. + +**Violation pattern:** Handler reads/writes shared state without lock. + +```cpp +// WRONG: +uint32_t GetCount(...) { + return _count; // no lock — may race with notification thread modifying _count +} + +// Correct: +uint32_t GetCount(...) { + _adminLock.Lock(); + uint32_t count = _count; + _adminLock.Unlock(); + return count; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] JSON-RPC handler accesses shared state without lock` --- @@ -1427,7 +1967,28 @@ All JSON-RPC handlers must be safe for concurrent invocation — either accessin **IPlugin::INotification Callbacks Must Not Block** | `violation` | Category: Concurrency -No `IPlugin::INotification` callback (`Activated`, `Deactivated`, `Unavailable`) may block the calling thread — no synchronous waits, no contended mutex acquisitions, no I/O. +**What to check:** No `IPlugin::INotification` callback (`Activated`, `Deactivated`, `Unavailable`) blocks the calling thread — no synchronous waits, no contended mutex acquisitions, no I/O operations. + +**Where to look:** All `IPlugin::INotification` callback implementations. + +**Violation pattern:** Blocking operation inside a plugin notification callback. + +```cpp +// WRONG: +void Deactivated(const string& callsign, PluginHost::IShell*) override { + _adminLock.Lock(); // may block if held by long operation + WriteLogToFile(callsign); // I/O blocks + _adminLock.Unlock(); +} + +// Correct: +void Deactivated(const string& callsign, PluginHost::IShell*) override { + // schedule work, don't block + _parent.ScheduleCleanup(callsign); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] INotification callback performs blocking operation` --- @@ -1435,7 +1996,27 @@ No `IPlugin::INotification` callback (`Activated`, `Deactivated`, `Unavailable`) **Lock Scope Minimized** | `violation` | Category: Concurrency -All critical sections must be held for the minimum time necessary — covering only the actual shared state access. Blocking operations, callbacks, and I/O must never occur while holding a lock. +**What to check:** Critical sections are held for the minimum time necessary — only the actual shared state access. No I/O, no blocking operations, no framework callbacks while holding a lock. + +**Where to look:** All `_adminLock.Lock()` ... `_adminLock.Unlock()` spans. + +**Violation pattern:** Lock held during I/O, sleep, or framework call. + +```cpp +// WRONG: +_adminLock.Lock(); +_data = FetchFromNetwork(); // blocks while locked +ProcessData(_data); +_adminLock.Unlock(); + +// Correct: +auto data = FetchFromNetwork(); // do I/O outside lock +_adminLock.Lock(); +_data = data; // only protect the assignment +_adminLock.Unlock(); +``` + +**Citation format:** `[PluginName.cpp:LINE] Lock held during blocking operation — minimize lock scope` --- @@ -1443,7 +2024,28 @@ All critical sections must be held for the minimum time necessary — covering o **Plugin Threads Joined in Deinitialize** | `violation` | Category: Concurrency -All threads started by the plugin must be explicitly stopped and joined (or detached with a lifetime guarantee) in `Deinitialize()` before the function returns. +**What to check:** All threads started by the plugin are explicitly stopped and joined in `Deinitialize()` before it returns. Threads left running after `Deinitialize()` access null pointers. + +**Where to look:** Thread creation (in `Initialize()` or during operation) and `Deinitialize()` for join/stop. + +**Violation pattern:** Thread started but not joined/stopped in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(...) { + _service->Release(); _service = nullptr; + // _workerThread still running — will access _service +} + +// Correct: +void Deinitialize(...) { + _workerThread.Stop(); // signal stop + _workerThread.Wait(); // join — wait for exit + _service->Release(); _service = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Thread not joined in Deinitialize() — may access stale pointers` --- @@ -1451,7 +2053,25 @@ All threads started by the plugin must be explicitly stopped and joined (or deta **Memory and Allocation Safety** | `warning` | Category: Resource Management -There must be no memory leaks (every `new` must have a corresponding `delete` on all code paths), no use-after-free, and no buffer overflows. +**What to check:** No memory leaks (every `new` has a corresponding `delete` on all paths), no use-after-free, no buffer overflows, no large stack allocations or VLAs. + +**Where to look:** All `new`/`delete` pairs, raw buffer operations, and stack-allocated arrays. + +**Violation pattern:** `new` without matching `delete` on error path, or access to freed memory. + +```cpp +// WRONG: +char* buffer = new char[size]; +if (error) return Core::ERROR_GENERAL; // buffer leaked +delete[] buffer; + +// Correct: +std::unique_ptr buffer(new char[size]); +if (error) return Core::ERROR_GENERAL; // auto-freed +// Or use stack for small, known-size buffers +``` + +**Citation format:** `[PluginName.cpp:LINE] Memory leak on error path — 'buffer' not freed` --- @@ -1459,7 +2079,31 @@ There must be no memory leaks (every `new` must have a corresponding `delete` on **Framework Pointers Not Accessed After Deinitialize** | `violation` | Category: Lifecycle Integrity -No worker thread, background job, or async callback may access framework pointers (`IShell*`, service pointers, notification interfaces) after `Deinitialize()` has completed and nulled those pointers. +**What to check:** No worker thread, background job, or async callback can access framework pointers (`IShell*`, service pointers, notification interfaces) after `Deinitialize()` has completed and nulled them. + +**Where to look:** Worker jobs, timers, and async callbacks that use `_service` or other framework pointers. + +**Violation pattern:** Background task accesses `_service` without checking if it's been nulled. + +```cpp +// WRONG: +void TimerExpired() { + _service->Submit(...); // _service may be nullptr after Deinitialize +} + +// Correct: +void TimerExpired() { + _adminLock.Lock(); + if (_service == nullptr) { _adminLock.Unlock(); return; } + PluginHost::IShell* svc = _service; + svc->AddRef(); + _adminLock.Unlock(); + svc->Submit(...); + svc->Release(); +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Framework pointer accessed without null check after potential Deinitialize()` --- @@ -1467,7 +2111,25 @@ No worker thread, background job, or async callback may access framework pointer **hresult Return Values Checked** | `violation` | Category: COM Safety -The return value of every `Core::hresult`-returning COM interface method call must be checked and errors must be handled appropriately — never silently discarded. +**What to check:** The return value of every `Core::hresult`-returning COM interface method call is checked and errors handled — never silently discarded. + +**Where to look:** All calls to COM interface methods that return `Core::hresult`. + +**Violation pattern:** `hresult` return value ignored. + +```cpp +// WRONG: +_impl->SetConfiguration(config); // return value ignored + +// Correct: +Core::hresult result = _impl->SetConfiguration(config); +if (result != Core::ERROR_NONE) { + SYSLOG(Logging::Error, "SetConfiguration failed: %d", result); + return result; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] hresult return value from COM call ignored` --- @@ -1475,7 +2137,15 @@ The return value of every `Core::hresult`-returning COM interface method call mu **ASSERT Only for Programmer Invariants** | `warning` | Category: Code Quality -`ASSERT` must only be used for conditions that represent programmer errors — invariants that should never be false in correct code. It must never be used for conditions that can legitimately occur at runtime. +**What to check:** `ASSERT` is used only for conditions that represent programmer errors (invariants that should never be false in correct code). Never for runtime conditions that can legitimately fail. + +**Where to look:** All `ASSERT(...)` calls — evaluate whether the condition can ever be false at runtime. + +**Violation pattern:** `ASSERT` on a condition that can fail at runtime (network result, user input, config value). + +*(Same principle as rule_43 — this rule targets holistic patterns across the full plugin.)* + +**Citation format:** `[PluginName.cpp:LINE] ASSERT on runtime condition` --- @@ -1483,7 +2153,23 @@ The return value of every `Core::hresult`-returning COM interface method call mu **Security: Logging, Shell, Path, and Error Exposure** | `violation` | Category: Code Quality -The plugin must avoid: logging sensitive data (passwords, tokens, PII); executing shell commands with unsanitized input; exposing internal error details through JSON-RPC responses; and path traversal via external inputs. +**What to check:** The plugin avoids: (1) logging sensitive data (passwords, tokens, PII), (2) executing shell commands with unsanitized external input, (3) exposing internal error details through JSON-RPC responses, (4) path traversal via external inputs. + +**Where to look:** All `SYSLOG`/`TRACE` calls, any `system()`/`popen()` calls, JSON-RPC error responses, and path construction from external input. + +**Violation pattern:** Sensitive data logged, or shell command built from unvalidated input. + +```cpp +// WRONG: +SYSLOG(Logging::Info, "Auth token: %s", token.c_str()); // leaks secret +system(("rm " + userInput).c_str()); // command injection + +// Correct: +SYSLOG(Logging::Info, "Auth token received (length=%d)", token.length()); +// Never use system() with external input +``` + +**Citation format:** `[PluginName.cpp:LINE] Sensitive data logged / unsanitized shell command` --- @@ -1491,7 +2177,27 @@ The plugin must avoid: logging sensitive data (passwords, tokens, PII); executin **JSON-RPC Input Validation for Bounds and Types** | `violation` | Category: JSON-RPC Compliance -Every JSON-RPC handler must validate numeric inputs for range bounds and type correctness before use in operations that could overflow, underflow, or cause undefined behavior. +**What to check:** Every JSON-RPC handler validates numeric inputs for range bounds and type correctness before use in operations that could overflow, underflow, or cause undefined behavior. + +**Where to look:** All JSON-RPC handlers that accept numeric parameters. + +**Violation pattern:** Numeric input used in arithmetic or array indexing without bounds check. + +```cpp +// WRONG: +uint32_t SetVolume(const uint8_t volume) { + _device->SetVol(volume); // no check — what if volume > 100? +} + +// Correct: +uint32_t SetVolume(const uint8_t volume) { + if (volume > 100) return Core::ERROR_BAD_REQUEST; + _device->SetVol(volume); + return Core::ERROR_NONE; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Numeric input not bounds-checked before use` --- @@ -1499,7 +2205,31 @@ Every JSON-RPC handler must validate numeric inputs for range bounds and type co **Config Completeness and Resource Cleanup** | `warning` | Category: Code Quality -All configuration fields must be registered with `Add()` in the `Config` constructor. All resources acquired based on configuration values must be properly released in `Deinitialize()`. +**What to check:** All configuration fields are registered with `Add()` in the `Config` constructor. All resources acquired based on configuration values are properly released in `Deinitialize()`. + +**Where to look:** `Config` class constructor (for `Add()` completeness) and `Deinitialize()` (for config-based resource cleanup). + +**Violation pattern:** Config field used but not registered with `Add()`, or config-acquired resource not released. + +```cpp +// WRONG: +class Config : public Core::JSON::Container { + Config() : Core::JSON::Container() { + Add(_T("port"), &Port); + // Timeout field used in code but not Add()'d — won't parse + } + Core::JSON::DecUInt16 Port; + Core::JSON::DecUInt32 Timeout; // missing Add() +}; + +// Correct: +Config() : Core::JSON::Container() { + Add(_T("port"), &Port); + Add(_T("timeout"), &Timeout); +} +``` + +**Citation format:** `[PluginName.h:LINE] Config field 'Timeout' not registered with Add()` --- @@ -1507,7 +2237,26 @@ All configuration fields must be registered with `Add()` in the `Config` constru **OOP Error Propagation and Method Naming** | `warning` | Category: Inter-Plugin Design -Out-of-process method implementations must properly propagate errors from the remote process back to the plugin host. Method names must follow Thunder naming conventions — no abbreviations, clear verb+noun form. +**What to check:** Out-of-process method implementations properly propagate errors from the remote process back to the plugin host. Method names follow Thunder conventions — no abbreviations, clear verb+noun form. + +**Where to look:** OOP implementation wrapper methods and their return value handling. + +**Violation pattern:** Remote error swallowed (returns `ERROR_NONE` even when remote call failed), or method named with abbreviations. + +```cpp +// WRONG: +Core::hresult GetVol(uint8_t& vol) { // abbreviated name + _impl->GetVolume(vol); // ignores return value + return Core::ERROR_NONE; +} + +// Correct: +Core::hresult GetVolume(uint8_t& volume) { + return _impl->GetVolume(volume); // propagates error +} +``` + +**Citation format:** `[PluginName.cpp:LINE] OOP error not propagated / method name uses abbreviation` --- @@ -1515,7 +2264,25 @@ Out-of-process method implementations must properly propagate errors from the re **Observer Classes Private and Nested** | `suggestion` | Category: Conventions -Observer/notification classes should be declared as private nested classes within the plugin class — not as separate public classes in the header — to keep the plugin's internal observer implementation encapsulated. +**What to check:** Observer/notification inner classes are declared as `private` nested classes within the plugin class — not exposed as separate public classes in the header. + +**Where to look:** Plugin header file — notification class declarations. + +**Violation pattern:** Notification class declared outside the plugin class or as public. + +```cpp +// WRONG: +class DictionaryNotification : public PluginHost::IPlugin::INotification { ... }; +class Dictionary : public PluginHost::IPlugin { ... }; + +// Correct: +class Dictionary : public PluginHost::IPlugin { +private: + class Notification : public PluginHost::IPlugin::INotification { ... }; +}; +``` + +**Citation format:** `[PluginName.h:LINE] Observer class not nested as private inside plugin class` --- @@ -1523,7 +2290,21 @@ Observer/notification classes should be declared as private nested classes withi **No Deprecated JSON-RPC APIs** | `violation` | Category: JSON-RPC Compliance -No deprecated JSON-RPC APIs must be used — no legacy `Register` overloads replaced by typed versions, no deprecated event dispatch patterns. +**What to check:** No deprecated JSON-RPC APIs are used — no legacy `Register` overloads replaced by typed versions, no `IDispatcher`, no `Announce`-style event patterns. + +**Where to look:** All JSON-RPC registration and event dispatch code. + +**Violation pattern:** Use of deprecated API (e.g. untyped `Register`, legacy event dispatch). + +```cpp +// WRONG: +Register("method", [this](const string& params) -> string { ... }); // legacy untyped + +// Correct: +Register(_T("method"), &Plugin::Method, this); +``` + +**Citation format:** `[PluginName.cpp:LINE] Deprecated JSON-RPC API used` --- @@ -1531,7 +2312,30 @@ No deprecated JSON-RPC APIs must be used — no legacy `Register` overloads repl **All Acquired Pointers Cleared After Deinitialize** | `violation` | Category: Lifecycle Integrity -Every pointer member set to a non-null value during the plugin's lifetime must be explicitly set to `nullptr` in `Deinitialize()` (in addition to being released), such that a double-`Deinitialize()` would not cause a double-release. +**What to check:** Every pointer member set to a non-null value during the plugin's lifetime is explicitly set to `nullptr` in `Deinitialize()` (in addition to being released). This ensures a double-`Deinitialize()` call cannot cause a double-release. + +**Where to look:** All pointer members vs. `Deinitialize()` body. + +**Violation pattern:** Pointer member released but not nulled, or not touched at all in `Deinitialize()`. + +```cpp +// WRONG: +void Deinitialize(...) { + _impl->Release(); // released but not nulled + _service->Release(); + _service = nullptr; + // _connection not touched at all +} + +// Correct: +void Deinitialize(...) { + _impl->Release(); _impl = nullptr; + _service->Release(); _service = nullptr; + _connection = nullptr; +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Pointer member not set to nullptr after Release()` --- @@ -1553,4 +2357,4 @@ Every pointer member set to a non-null value during the plugin's lifetime must b --- -*Document generated from `thunder-plugin-rules.yaml` v3.3.0. For questions or rule updates, raise a PR against the spec folder.* +*Document generated from `thunder-plugin-rules.yaml` v3.3.0. For questions or rule updates, raise a PR against the spec folder.* \ No newline at end of file From aabcf1fd38e5fdf69b7b40b0d34479a1835edd87 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Mon, 15 Jun 2026 16:35:43 +0530 Subject: [PATCH 03/15] Updated the Plugin and Interface Rules header --- .../specs/interface/Thunder-interface-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 6debea54..4f24e73e 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -1,6 +1,6 @@ # Thunder Interface Validation Rules -**Version:** 3.2.2 + **Status:** For Review ## Description From 29d93effac102e9b8d0574e215c3886a6ec26341 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Mon, 15 Jun 2026 19:03:02 +0530 Subject: [PATCH 04/15] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- openspec/changes/thunder-plugin-qa/design.md | 4 +-- .../changes/thunder-plugin-qa/proposal.md | 3 +-- .../thunder-plugin-qa/specs/interface/spec.md | 25 ++++--------------- .../thunder-plugin-qa/specs/reports/spec.md | 6 ++--- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/design.md b/openspec/changes/thunder-plugin-qa/design.md index b7570801..1337ee6e 100644 --- a/openspec/changes/thunder-plugin-qa/design.md +++ b/openspec/changes/thunder-plugin-qa/design.md @@ -249,7 +249,7 @@ modifications are made to generated files. ## Setup Script Design -All three scripts (`.ps1`, `.sh`, `.py`) do the same thing: +The `setup-prompts.py` script does the following: 1. Detect VS Code settings.json location (platform-specific paths, also checks VS Code Insiders) 2. Create a timestamped backup of existing settings.json @@ -263,7 +263,7 @@ All three scripts (`.ps1`, `.sh`, `.py`) do the same thing: ## YAML File Versioning -- `thunder-plugin-rules.yaml`: version 3.0.0 +- `thunder-plugin-rules.yaml`: version 3.3.0 - 39 checkpoints, organisation: Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1 - New checkpoints added over v1.0.0: rule_08 (nullptr after Release), rule_09–10 (COM ownership + no-throw), rule_16 diff --git a/openspec/changes/thunder-plugin-qa/proposal.md b/openspec/changes/thunder-plugin-qa/proposal.md index 7dabc41e..214f728c 100644 --- a/openspec/changes/thunder-plugin-qa/proposal.md +++ b/openspec/changes/thunder-plugin-qa/proposal.md @@ -101,5 +101,4 @@ ThunderTools/PluginQA/ │ └── {PluginName}_{YYYY-MM-DD}.csv └── interface/ └── {InterfaceName}_{YYYY-MM-DD}.csv -``` - └── interface/ \ No newline at end of file +``` \ No newline at end of file diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md index 218e58e3..98ec5231 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -5,12 +5,12 @@ --- ### Requirement: COM interface validator command -The system MUST provide a `/thunder-interface` slash command that validates +The system MUST provide a `/thunder-interface-review` slash command that validates a Thunder COM interface header against 19 rules (15 core + 4 advisory). #### Scenario: Interface with critical violations - GIVEN a Thunder interface file with a missing `@json` tag -- WHEN `/thunder-interface` runs +- WHEN `/thunder-interface-review` runs - THEN it reports under `🔴 Violations (Must Fix)`: `[IMyInterface.h:LINE] Missing @json tag — ZERO RPC code will be generated` - AND reports under `✅ Validated` all passing rules @@ -105,30 +105,15 @@ PluginSkeletonGenerator.py in interactive mode. --- -### Requirement: Setup scripts register prompts with VS Code -Three setup scripts MUST modify VS Code settings.json to add +### Requirement: Setup script registers prompts with VS Code +The `setup-prompts.py` script MUST modify VS Code settings.json to add `chat.promptFilesLocations` pointing to `ThunderTools/PluginQA/Prompts`. -#### Scenario: Windows PowerShell setup -- GIVEN a Windows machine with VS Code -- WHEN `.\setup-prompts.ps1` is run from `ThunderTools/PluginQA/` -- THEN it auto-detects the VS Code settings.json location -- AND creates a backup of existing settings -- AND safely merges `chat.promptFilesLocations` into the JSON -- AND prints next steps (reload VS Code window) - -#### Scenario: Linux/Mac bash setup -- GIVEN a Linux or Mac machine -- WHEN `./setup-prompts.sh` is run -- THEN the same behaviour as the PowerShell script, adapted for bash -- AND handles both VS Code and VS Code Insiders - #### Scenario: Python cross-platform setup - GIVEN any OS with Python 3 - WHEN `python setup-prompts.py` is run - THEN it performs the same safe settings merge - AND works identically on Windows, Mac, and Linux - #### Scenario: Script is safe to run multiple times - GIVEN the script has already been run once - WHEN it is run again @@ -169,7 +154,7 @@ No prompt file (`*.prompt.md`) needs to be changed. - AND bump the file-level `version:` field (e.g. `3.2.1` → `3.2.2`) - AND add a CHANGELOG entry in the `description:` block at the top of the file -- AND save — the next time `/thunder-interface` runs it picks up the change automatically +- AND save — the next time `/thunder-interface-review` runs it picks up the change automatically #### Scenario: Adding a new interface rule - GIVEN a developer needs to add a new rule (e.g. `core_18_1`) diff --git a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md index 9aa22b0d..2cd9260e 100644 --- a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -12,7 +12,7 @@ with all fields needed to understand, prioritise, and track fixes. ### REQ-R1 — Plugin review CSV -**Scenario:** `/thunder-plugin-review` completes all 39 checkpoints +**Scenario:** `/thunder-plugin-review` completes all 79 rules - The system MUST generate a CSV file at: `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) @@ -21,7 +21,7 @@ with all fields needed to understand, prioritise, and track fixes. ### REQ-R2 — Interface review CSV -**Scenario:** `/thunder-interface` completes all 19 rules +**Scenario:** `/thunder-interface-review` completes all 19 rules - The system MUST generate a CSV file at: `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` - Same no-overwrite rule applies @@ -105,7 +105,7 @@ If no issues were found, still generate the CSV with the header row only and add ```csv No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning -,,,,,,,,,,,,All checkpoints passed — no issues found, +,,,,,,,,,,,,,"All checkpoints passed — no issues found" ``` --- From 8f2f2e9a5392512eb148872e4c1514da66ed6681 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Mon, 15 Jun 2026 19:46:53 +0530 Subject: [PATCH 05/15] Apply suggestions from code review - 2 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../specs/interface/Thunder-interface-rules.md | 2 +- openspec/changes/thunder-plugin-qa/specs/plugin/spec.md | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 4f24e73e..04cefb96 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -10,7 +10,7 @@ Every rule uses semantic reasoning — read the interface header in full and rea --- -## Core Rules (17) — Severity: Violation +## Core Rules (15) — Severity: Violation ### core_1_1 — File and Namespace Structure diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index 9ec824d1..d42575cd 100644 --- a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -36,17 +36,14 @@ ThunderTools/PluginQA/ #### Scenario: Prompt files are registered with VS Code - GIVEN `ThunderTools/PluginQA/Prompts/` containing the three `.prompt.md` files -- WHEN a setup script (`setup-prompts.ps1` / `.sh` / `.py`) is run -- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQA/Prompts` - WHEN `setup-prompts.py` is run +- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQA/Prompts` - AND the three slash commands (`/thunder-plugin-review`, `/thunder-interface-review`, `/thunder-generate-plugin`) become available in VS Code Copilot Chat - AND the script is safe to run multiple times (idempotent, creates backup of settings) --- -### Requirement: Setup scripts modify VS Code settings.json to register prompt location -Three setup scripts MUST modify the user-level VS Code `settings.json` to add ### Requirement: Setup script modifies VS Code settings.json to register prompt location The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add #### Scenario: Resulting settings.json structure @@ -102,12 +99,12 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` ### Requirement: thunder-plugin-rules.yaml (v3.3.0) created under PluginQA/rules/ The file `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` MUST exist -with version `3.2.0` and contain all 79 rules numbered sequentially (rule_01 to rule_79). +with version `3.3.0` and contain all 79 rules numbered sequentially (rule_01 to rule_79). #### Scenario: Metadata block - GIVEN the YAML file - THEN it MUST contain a `metadata` block with: - `version: "3.0.0"`, `total_rules: 79`, `total_general_rules: 40`, + `version: "3.3.0"`, `total_rules: 79`, `total_general_rules: 40`, `approach: "semantic code review — understand whole plugin first, then check specifics"`, and a `validation_approach` block listing the 5-step workflow (understand whole plugin → focus on specific concern → reason in context → cite if genuinely wrong → fix) From a3fd6ee884089bbbefb4a44da83b8395ed960f6b Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 16 Jun 2026 09:41:53 +0530 Subject: [PATCH 06/15] Apply suggestions from code review - 3 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- openspec/changes/thunder-plugin-qa/design.md | 8 ++++---- .../changes/thunder-plugin-qa/specs/interface/spec.md | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/design.md b/openspec/changes/thunder-plugin-qa/design.md index 1337ee6e..1cbd2129 100644 --- a/openspec/changes/thunder-plugin-qa/design.md +++ b/openspec/changes/thunder-plugin-qa/design.md @@ -112,9 +112,9 @@ Both types produce the same YAML output block in the report. The structural difference is an internal implementation detail — users see one unified list of findings grouped by file. -## YAML Interface Rule Structure +## YAML Plugin Rule Structure -Each rule in `thunder-interface-rules.yaml`: +Each rule in `thunder-plugin-rules.yaml`: ```yaml - rule_id: "rule_17" @@ -167,8 +167,8 @@ Each rule in `thunder-interface-rules.yaml`: This is the most common critical omission. The tag must appear immediately above the struct declaration as: // @json 1.0.0 extraction_logic: | - 1. Search for '// @json' or '/* @json' comment above the interface struct declaration - 2. Check the line immediately preceding the 'struct EXTERNAL I...' line + 1. Read the comment lines immediately above the interface struct declaration and determine whether an @json tag is present + 2. Verify the @json tag is directly adjacent to the 'struct EXTERNAL I...' line (no blank lines) verification_logic: | 1. Tag must appear as: // @json 1.0.0 2. Must be immediately above the struct declaration (no blank lines between) diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md index 98ec5231..419e0a2c 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -131,9 +131,8 @@ for each command (interface validation output, plugin checkpoint output). --- ### Requirement: Rules can be updated without touching prompt files -Because rules are loaded at runtime from YAML, any rule change — adding, modifying, -removing, or bumping severity — requires only editing the relevant YAML file. -No prompt file (`*.prompt.md`) needs to be changed. +Because rules are loaded at runtime from YAML, updating existing rule definitions (rewording, logic, or severity) requires only editing the relevant YAML file. +Adding/removing rules may require updating prompt documentation (e.g., the Quick Reference table). #### Scenario: Updating an existing interface rule - GIVEN a developer needs to strengthen `core_5_1` (return type convention) @@ -194,7 +193,7 @@ No prompt file (`*.prompt.md`) needs to be changed. - THEN they append a new entry to the appropriate phase block: ```yaml - - Rule_ID: "rule_13" + - rule_id: "rule_80" name: "Short Name in Title Case" severity: "violation" # or: warning / suggestion phase: "code_style" From 62e95409c0b1fe3038b70f5119344804da4016e0 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 16 Jun 2026 00:35:19 -0400 Subject: [PATCH 07/15] Apply suggestions from code review - 4 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../thunder-plugin-qa/specs/plugin/spec.md | 19 ++++++++----------- openspec/changes/thunder-plugin-qa/tasks.md | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index d42575cd..34a775d1 100644 --- a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -45,7 +45,8 @@ ThunderTools/PluginQA/ --- ### Requirement: Setup script modifies VS Code settings.json to register prompt location -The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add +The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add `"ThunderTools/PluginQA/Prompts": true` under `chat.promptFilesLocations`. + #### Scenario: Resulting settings.json structure - GIVEN VS Code `settings.json` before the script runs (may be empty `{}` or have existing entries) - WHEN `setup-prompts.py` completes successfully @@ -462,11 +463,7 @@ NOT by running regular expressions or keyword searches against raw text. a guard checking `connection->Id() == _connectionId` - SKIP if plugin has no IRemoteConnection::INotification implementation -#### Scenario: Phase 6 - Configuration (3 checkpoints, conditional) -- GIVEN the plugin's .conf.in file and Initialize() body -- IF no .conf.in file exists: SKIP rule_35 -- WHEN checkpoint rule_35 runs (suggestion) -- THEN it checks the .conf.in file contains a `startmode =` declaration; +- WHEN checkpoint rule_35 runs (violation, conditional) the validator MUST read the file in full and reason about the startup configuration — note that `autostart` is not the same as `startmode` - WHEN checkpoint rule_36 runs (violation, conditional) @@ -553,12 +550,12 @@ All rules produce the same output format — there is no separate section for th Every failing checkpoint MUST be output as a YAML block grouped under the source file it belongs to. The report structure is: -``` +~~~~text ### {FileName} — N issue(s) -```yaml +~~~yaml rule_id: -status: FAIL +status: severity: violation|warning|suggestion question: "..." answer: "..." @@ -570,8 +567,8 @@ citation: "[FileName:line] " fix: | reasoning: "..." -` ` ` -``` +~~~ +~~~~ #### Scenario: File-wise grouping - GIVEN a plugin review that finds issues in Dictionary.cpp, Dictionary.h, and CMakeLists.txt diff --git a/openspec/changes/thunder-plugin-qa/tasks.md b/openspec/changes/thunder-plugin-qa/tasks.md index 98ab8e08..d71fcc12 100644 --- a/openspec/changes/thunder-plugin-qa/tasks.md +++ b/openspec/changes/thunder-plugin-qa/tasks.md @@ -275,7 +275,7 @@ - Important Notes: Thunder docs link, validation priorities numbered list (@json first through type conventions last) -- [x] 2.4 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-rule-manager.prompt.md` +- [x] 2.3 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-rule-manager.prompt.md` Frontmatter: title: "Thunder Plugin Rule Manager", description: "Add, update, or remove plugin rules (automated or manual) via guided questionnaire — keeps YAML, prompt, README, and spec in sync" Sections (in order): @@ -311,7 +311,7 @@ - Phase vs General Classification table at end: 5 phase checkpoint criteria vs 6 General criteria; ambiguous cases prompt user before proceeding -- [x] 2.5 Create `ThunderTools/PluginQA/Prompts/thunder-interface-rule-manager.prompt.md` +- [x] 2.4 Create `ThunderTools/PluginQA/Prompts/thunder-interface-rule-manager.prompt.md` Frontmatter: title: "Thunder Interface Rule Manager", description: "Add, update, or remove COM interface validation rules via guided questionnaire — keeps YAML, prompt, and spec in sync" Sections (in order): @@ -339,7 +339,7 @@ - Core vs Advisory Classification table at end: core = codegen/ABI/crash failures; advisory = best practice; wrong list auto-corrected with explanation -- [x] 2.3 Create `ThunderTools/PluginQA/Prompts/thunder-generate-plugin.prompt.md` +- [x] 2.5 Create `ThunderTools/PluginQA/Prompts/thunder-generate-plugin.prompt.md` Frontmatter: title: "Thunder Plugin Generator", description: "Interactive Thunder plugin skeleton generator using PluginSkeletonGenerator.py" Sections: From 5e8e5053c4332a16d3cbb60006db9c61dea7d23b Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 16 Jun 2026 10:14:57 +0530 Subject: [PATCH 08/15] Apply suggestions from code review - 5 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../specs/interface/Thunder-interface-rules.md | 2 +- openspec/changes/thunder-plugin-qa/tasks.md | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 04cefb96..2b483ecc 100644 --- a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -328,7 +328,7 @@ virtual Core::hresult Register(INotification* notification) = 0; // Correct — INotification (1:many): virtual Core::hresult Register(INotification* notification) = 0; -virtual Core::hresult Unregister(const INotification* notification) = 0; +virtual Core::hresult Unregister(INotification* notification) = 0; // Correct — ICallback (1:1, nullptr clears): virtual Core::hresult Callback(ICallback* callback) = 0; diff --git a/openspec/changes/thunder-plugin-qa/tasks.md b/openspec/changes/thunder-plugin-qa/tasks.md index d71fcc12..5495e1f7 100644 --- a/openspec/changes/thunder-plugin-qa/tasks.md +++ b/openspec/changes/thunder-plugin-qa/tasks.md @@ -191,9 +191,8 @@ Class Registration (Phase 3) applies ONLY to main plugin class — internal helpers excluded - Contextual Judgment section: severity downgrade table with concrete example showing reasoning field (required on downgrade, omitted otherwise), no escalation rule - - All rules defined inline in the prompt for reference - (exact severity, extraction target, question, verification logic, - citation format, conditional skip rules). + - A shortened inline quick-reference list of all 79 rules is included in the prompt (rule_id + severity + high-level target only). + Full rule definitions (extraction, bounded_query, verification_logic, fix_template) are loaded from the YAML files at runtime (source of truth). Phase counts: Phase 1 Module Structure (3): rule_01 (suggestion): "MODULE_NAME Plugin_ Prefix" From 45a169466524bda8a3e28e7013528af0bb7d0854 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 16 Jun 2026 10:44:26 +0530 Subject: [PATCH 09/15] Apply suggestions from code review - 6 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- openspec/changes/thunder-plugin-qa/design.md | 10 ++++------ .../changes/thunder-plugin-qa/specs/plugin/spec.md | 2 +- .../changes/thunder-plugin-qa/specs/reports/spec.md | 2 +- openspec/changes/thunder-plugin-qa/tasks.md | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/openspec/changes/thunder-plugin-qa/design.md b/openspec/changes/thunder-plugin-qa/design.md index 1cbd2129..e7545717 100644 --- a/openspec/changes/thunder-plugin-qa/design.md +++ b/openspec/changes/thunder-plugin-qa/design.md @@ -221,9 +221,8 @@ text like `{PluginName}` in output. - Internal helper classes (Notification, Sink, Config, etc.) are excluded **Conditional checkpoints:** -Checkpoints 4.2, 4.3, 4.5, 4.6, 4.7, 4.8, 4.10, 5.2, 5.4, 5.9, 5C/9.1, 5C/9.2, Phase 6 are conditional. -If the prerequisite is not found (e.g. no stored IShell pointer for 4.2), the -checkpoint SKIPS — it does not fail. +Checkpoints rule_09, rule_16, rule_17, rule_18, rule_20, rule_21, rule_22, rule_23, rule_25, rule_29, rule_30, rule_31, rule_33, rule_34, rule_35, rule_36, and rule_38 are conditional. +If the prerequisite is not found (e.g. no stored IShell pointer for rule_17), the checkpoint SKIPS — it does not fail. ## Plugin Generator Design @@ -267,10 +266,9 @@ The `setup-prompts.py` script does the following: - 39 checkpoints, organisation: Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1 - New checkpoints added over v1.0.0: rule_08 (nullptr after Release), rule_09–10 (COM ownership + no-throw), rule_16 - (INTERFACE_MAP + JSONRPC), rule_21/4_6/4_7/4_9/4_10/4_11/4_12 (full lifecycle - correctness), rule_31 (Unavailable in SinkType), + (INTERFACE_MAP + JSONRPC), rule_20–rule_23 (full lifecycle correctness), rule_31 (Unavailable in SinkType), rule_34 (connectionId guard), - rule_36/6_3 (JSON::Container + no magic numbers) + rule_36 (JSON::Container configuration), rule_37 (no hardcoded numeric tuning parameters) - `thunder-interface-rules.yaml`: version 3.2.2 - 15 core rules + 4 advisory = 19 total diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index 34a775d1..8e355f14 100644 --- a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -499,7 +499,7 @@ All rules produce the same output format — there is no separate section for th `extracted_code` (with [File:line] prefix where applicable), `violation_line`, `citation`, `fix`, `reasoning` - AND PASS rules are NOT listed individually — they appear only as counts in the summary table -- Holistic Rules (8 sub-phases) cover (Rules 1–12 original, Rules 13–40 new): +- Holistic Rules (rule_40–rule_79) cover: 1. `#pragma once` in every .h file (suggestion) 2. Apache 2.0 copyright headers in all source files (suggestion) 3. No STL types where Thunder equivalents exist (warning) diff --git a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md index 2cd9260e..5b9f3370 100644 --- a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md +++ b/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -16,7 +16,7 @@ with all fields needed to understand, prioritise, and track fixes. - The system MUST generate a CSV file at: `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) -- The CSV MUST contain one header row followed by one data row per FAIL or WARNING or SUGGESTION +- The CSV MUST contain one header row followed by one data row per VIOLATION or WARNING or SUGGESTION - PASS and SKIP rows are NOT included in the CSV (they appear only in the phase summary in chat) ### REQ-R2 — Interface review CSV diff --git a/openspec/changes/thunder-plugin-qa/tasks.md b/openspec/changes/thunder-plugin-qa/tasks.md index 5495e1f7..e73f31b3 100644 --- a/openspec/changes/thunder-plugin-qa/tasks.md +++ b/openspec/changes/thunder-plugin-qa/tasks.md @@ -423,12 +423,12 @@ - Appended after Command Examples section - File path: `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - Create folder if absent; never overwrite (append _2, _3 suffix) - - Columns (14, exact order): No, Plugin, Date, Phase, rule_id, Rule_Name, Status, + - Columns (14, exact order): No, Plugin, Date, Phase, Rule_ID, Rule_Name, Status, Severity, File, Line, Citation, Issue_Description, Fix_Summary, Reasoning - - One row per FAIL/WARNING/SUGGESTION only — PASS and SKIP excluded + - One row per VIOLATION/WARNING/SUGGESTION only — PASS and SKIP excluded - Status = effective status after JUDGE step (VIOLATION/WARNING/SUGGESTION) - Reasoning column populated only when JUDGE downgraded severity; empty otherwise - - Formatting: UTF-8, CRLF, fields with commas/dashes quoted, embedded quotes escaped as "" + - Formatting: UTF-8, CRLF, fields with commas or double quotes quoted, embedded quotes escaped as "" - Empty report (all pass): header row + one comment row - Post-generation chat message: file path, issue counts, Start-Process command for Excel From e4b3f3e4ea9085438c00928cd68500f3e98c2ade Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 30 Jun 2026 17:22:22 +0530 Subject: [PATCH 10/15] Added the PluginQA Tools folder and moved openspec to .github --- .../changes/thunder-plugin-qa/design.md | 0 .../changes/thunder-plugin-qa/proposal.md | 0 .../interface/Thunder-interface-rules.md | 0 .../thunder-plugin-qa/specs/interface/spec.md | 0 .../specs/plugin/Thunder-plugin-rules.md | 0 .../thunder-plugin-qa/specs/plugin/spec.md | 0 .../thunder-plugin-qa/specs/reports/spec.md | 0 .../changes/thunder-plugin-qa/tasks.md | 0 {openspec => .github/openspec}/config.yaml | 0 .../Prompts/thunder-generate-plugin.prompt.md | 153 ++ .../thunder-interface-review.prompt.md | 184 ++ .../thunder-interface-rule-manager.prompt.md | 206 ++ .../Prompts/thunder-plugin-review.prompt.md | 534 +++++ .../thunder-plugin-rule-manager.prompt.md | 275 +++ PluginQualityAdvisor/README.md | 171 ++ .../interface-rule-template-guide.md | 364 +++ .../plugin-rule-template-guide.md | 383 +++ .../rules/thunder-interface-rules.yaml | 616 +++++ .../rules/thunder-plugin-rules.yaml | 2059 +++++++++++++++++ PluginQualityAdvisor/setup-prompts.py | 143 ++ 20 files changed, 5088 insertions(+) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/design.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/proposal.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/specs/interface/spec.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/specs/plugin/spec.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/specs/reports/spec.md (100%) rename {openspec => .github/openspec}/changes/thunder-plugin-qa/tasks.md (100%) rename {openspec => .github/openspec}/config.yaml (100%) create mode 100644 PluginQualityAdvisor/Prompts/thunder-generate-plugin.prompt.md create mode 100644 PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md create mode 100644 PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md create mode 100644 PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md create mode 100644 PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md create mode 100644 PluginQualityAdvisor/README.md create mode 100644 PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md create mode 100644 PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md create mode 100644 PluginQualityAdvisor/rules/thunder-interface-rules.yaml create mode 100644 PluginQualityAdvisor/rules/thunder-plugin-rules.yaml create mode 100644 PluginQualityAdvisor/setup-prompts.py diff --git a/openspec/changes/thunder-plugin-qa/design.md b/.github/openspec/changes/thunder-plugin-qa/design.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/design.md rename to .github/openspec/changes/thunder-plugin-qa/design.md diff --git a/openspec/changes/thunder-plugin-qa/proposal.md b/.github/openspec/changes/thunder-plugin-qa/proposal.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/proposal.md rename to .github/openspec/changes/thunder-plugin-qa/proposal.md diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md rename to .github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md diff --git a/openspec/changes/thunder-plugin-qa/specs/interface/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/specs/interface/spec.md rename to .github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md rename to .github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md diff --git a/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/specs/plugin/spec.md rename to .github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md diff --git a/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/specs/reports/spec.md rename to .github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md diff --git a/openspec/changes/thunder-plugin-qa/tasks.md b/.github/openspec/changes/thunder-plugin-qa/tasks.md similarity index 100% rename from openspec/changes/thunder-plugin-qa/tasks.md rename to .github/openspec/changes/thunder-plugin-qa/tasks.md diff --git a/openspec/config.yaml b/.github/openspec/config.yaml similarity index 100% rename from openspec/config.yaml rename to .github/openspec/config.yaml diff --git a/PluginQualityAdvisor/Prompts/thunder-generate-plugin.prompt.md b/PluginQualityAdvisor/Prompts/thunder-generate-plugin.prompt.md new file mode 100644 index 00000000..6c52cb1f --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-generate-plugin.prompt.md @@ -0,0 +1,153 @@ +--- +title: "Thunder Plugin Generator" +description: "Interactive Thunder plugin skeleton generator using PluginSkeletonGenerator.py — collects parameters via VS Code dialogs and runs PSG in interactive mode" +--- + +## Overview + +This prompt generates a Thunder plugin skeleton by: +1. Collecting plugin parameters via `vscode_askQuestions` (NOT chat messages) +2. Locating `PluginSkeletonGenerator.py` (PSG) in the workspace +3. Running PSG interactively and feeding answers to each prompt in order +4. Auto-fixing include path bugs in generated header files (PSG workaround) + +### CRITICAL RULES + +- **Always use `vscode_askQuestions`** to collect parameters — never ask in chat +- **Only run PSG** — do not create, modify, or delete plugin files manually +- **Auto-fix include paths** in generated .h files after PSG completes (known PSG bug) +- **Do not modify anything else** in generated files +- **NO `--config` flag** — PSG's interactive mode is more reliable than the config path + +--- + +## Required Inputs from User + +| Parameter | Format | Options | Default | +|-----------|--------|---------|---------| +| PluginName | PascalCase string | e.g. HelloWorld, NetworkControl | (required) | +| OutputDirectory | Path string | Absolute or relative | ThunderNanoServices | +| OutOfProcess | Boolean | Yes / No | No | +| CustomConfig | Boolean | Yes / No | No | +| InterfacePaths | Semicolon-separated paths | One or more .h files | (empty) | +| Preconditions | Comma-separated strings | e.g. PLATFORM,NETWORK | (empty) | +| Terminations | Comma-separated strings | e.g. PLATFORM | (empty) | +| Controls | Comma-separated strings | e.g. SUBSYSTEM | (empty) | + +--- + +## Step 1 — Collect Parameters via vscode_askQuestions + +Collect all parameters in a single `vscode_askQuestions` call: + +```json +[ + { + "header": "PluginName", + "question": "What is the name of your plugin? (PascalCase, e.g. HelloWorld)", + "message": "Use PascalCase. This becomes the class name, file names, and CMake target." + }, + { + "header": "OutputDirectory", + "question": "Where should the plugin be generated?", + "message": "Leave blank for the default: ThunderNanoServices. Or enter an absolute path." + }, + { + "header": "OutOfProcess", + "question": "Should the plugin run out-of-process (OOP)?", + "options": [ + { "label": "No", "description": "In-process plugin (default)", "recommended": true }, + { "label": "Yes", "description": "Out-of-process plugin — generates Implementation files" } + ] + }, + { + "header": "CustomConfig", + "question": "Does the plugin have custom configuration?", + "options": [ + { "label": "No", "description": "No custom config class", "recommended": true }, + { "label": "Yes", "description": "Generates a Config class inheriting Core::JSON::Container" } + ] + }, + { + "header": "InterfacePaths", + "question": "Interface header files to include? (semicolon-separated paths, or leave blank)", + "message": "Example: ThunderInterfaces/interfaces/IDictionary.h;ThunderInterfaces/interfaces/INetworkControl.h" + }, + { + "header": "Preconditions", + "question": "Subsystem preconditions? (comma-separated, or leave blank)", + "message": "Example: PLATFORM,NETWORK — these are Thunder subsystems that must be active before this plugin starts" + }, + { + "header": "Terminations", + "question": "Subsystem terminations? (comma-separated, or leave blank)", + "message": "Example: PLATFORM — subsystems to signal when this plugin terminates" + }, + { + "header": "Controls", + "question": "Subsystem controls? (comma-separated, or leave blank)", + "message": "Example: SUBSYSTEM — subsystems this plugin controls" + } +] +``` + +### Answer processing rules: + +- `Yes` / `No` dropdown answers → convert to `y` / `n` for PSG prompts +- Empty `OutputDirectory` → use `ThunderNanoServices` as the output directory +- Subsystems conditional: if ANY of Preconditions, Terminations, or Controls is non-empty → answer PSG's "Does your plugin rely on Thunder subsystems?" with `y`; otherwise `n` +- Multiple interface paths (semicolon-separated) → feed one per PSG prompt, then `Enter` on empty line + +--- + +## Step 2 — Locate PSG + +Search for `PluginSkeletonGenerator.py` in the workspace: +- Primary: `ThunderTools/PluginSkeletonGenerator/PluginSkeletonGenerator.py` +- Fallback: search workspace for `PluginSkeletonGenerator.py` +- If not found: report the error and ask the user for the path + +--- + +## Step 3 — Run PSG Interactively + +Change to the output directory, then run: +``` +python +``` + +Feed answers to PSG's prompts **in exact order**: + +| PSG Prompt | Answer | +|-----------|--------| +| Plugin name | PluginName from Step 1 | +| Out of process? | y or n (from OutOfProcess) | +| Custom config? | y or n (from CustomConfig) | +| Interface paths (one per prompt, blank line to finish) | Each InterfacePaths entry, then Enter on empty | +| Does your plugin rely on Thunder subsystems? | y if any subsystem fields non-empty, n otherwise | +| Preconditions (if subsystems = y) | Preconditions value | +| Terminations (if subsystems = y) | Terminations value | +| Controls (if subsystems = y) | Controls value | + +After PSG completes, report the generated files. + +--- + +## Post-Generation — Auto-Fix Include Paths + +PSG has a known bug where generated `.h` files have incorrect include paths. After PSG completes: + +1. Read each generated `.h` file +2. Fix include paths that reference non-existent relative locations +3. Correct to proper Thunder include paths (e.g. `#include ` → `#include "Module.h"` where appropriate) +4. **Make no other changes** to any generated file + +List all fixed include paths in the output. + +--- + +## Error Handling + +- **PSG not found:** "PluginSkeletonGenerator.py not found. Please provide the path." +- **Generation failure:** Report the PSG error output and stop — do not attempt manual file creation +- **Invalid PluginName:** If PluginName is not PascalCase or contains invalid characters, warn the user and ask for a corrected name before running PSG diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md new file mode 100644 index 00000000..5a30aae3 --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md @@ -0,0 +1,184 @@ +## Context + +Thunder uses COM-style interfaces defined in `ThunderInterfaces/interfaces/`. These interfaces are binary contracts used across process boundaries. All validation uses semantic reasoning — the validator reads the interface header in full as a human reviewer would, never using regex or text search as the primary detection method. + +Rules are loaded at runtime from: `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` + +--- + +## Quick Reference — All 19 Rules + +| ID | Rule | Key Point | +|----|------|-----------| +| core_1_1 | File and Namespace Structure | Must be in WPEFramework::Exchange namespace; file name matches interface name | +| core_2_1 | Interface Declaration Shape | struct EXTERNAL, virtual Core::IUnknown, ID enum, all pure virtual | +| core_3_1 | Interface ID Registration | ID must be RPC::ID_* constant registered in ids.h | +| core_4_1 | Pure Virtual Methods Only | All methods = 0, no inline code, no static methods | +| core_5_1 | Return Type Conventions | Core::hresult mandatory for @json interfaces in Thunder 5.0+ | +| core_6_1 | Const Correctness | @out parameters must be non-const references | +| core_9_1 | Thunder Type Conventions | string not std::string; explicit integer widths | +| core_10_1 | Register/Unregister Patterns | INotification: Register+Unregister; ICallback: Callback(nullptr clears) | +| core_11_1 | Nested Event Interfaces | @event tag required; EXTERNAL; own ID; inherits Core::IUnknown | +| core_12_1 | @json Tag (CRITICAL) | Without @json, ZERO RPC code generated | +| core_13_1 | Binary Compatibility | No method removal, reordering, or signature changes in released interfaces | +| core_14_1 | No AddRef/Release Redeclaration | Inherited from Core::IUnknown — never redeclare | +| core_15_1 | No std::map in Interfaces | Not serialisable across process boundaries | +| core_16_1 | Explicit Integer Widths | uint32_t not int; no platform-dependent types | +| core_17_1 | @restrict Mandatory with std::vector | Every std::vector parameter must have @restrict:N | +| advisory_m1_1 | Single Responsibility Principle | One coherent purpose per interface | +| advisory_m2_1 | Enum Underlying Types | Named enums used as params need explicit type; anonymous ID enum excluded | +| advisory_m3_1 | No Exceptions | No throw; use Core::hresult for errors | +| advisory_m5_1 | @restrict for Non-vector Params | Advisory for non-vector params with natural bounds (not std::vector — covered by core_17_1) | + +--- + +> **CRITICAL:** Load `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` before validating. The YAML contains the full rule definitions, extraction logic, verification logic, and fix templates. This quick reference is a navigation aid only. + +--- + +## Your Task + +1. **Identify** the interface file to validate (from user's command or ask if not provided) +2. **Load** `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` for full rule definitions +3. **Validate** the interface against all 19 rules in order (core_1_1 → core_17_1, then advisory_m1_1 → advisory_m5_1) +4. **Report** with grouped sections using the output format below +5. **Provide** specific fix examples using the `fix_template` from the YAML + +For each rule, apply contextual judgment (JUDGE step): if the developer's approach technically violates a rule but is valid and reasonable in their specific context, downgrade the severity and populate the `Reasoning` field. + +--- + +## Step 6 — Generate CSV Report + +After reporting results in chat, generate a CSV file: + +**File path:** `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` + +- Create `ThunderTools/PluginQA/Reports/interface/` if it does not exist +- Never overwrite an existing file — append `_2`, `_3` etc. if needed + +**CSV columns (exact order):** + +| Column | Description | Example | +|--------|-------------|---------| +| No | Row number starting at 1 | `1` | +| Interface | Interface filename without extension | `IHdmiCecSink` | +| Date | ISO date of review | `2026-06-05` | +| Rule_ID | YAML rule id | `core_12_1` | +| Rule_Name | Rule name | `@json Tag (CRITICAL)` | +| Status | Effective status after JUDGE step | `VIOLATION` | +| Severity | YAML severity | `violation` | +| File | Interface file | `IHdmiCecSink.h` | +| Line | Exact line number | `45` | +| Citation | Short citation string | `[IHdmiCecSink.h:45]` | +| Issue_Description | What was found | `@json tag missing above struct declaration` | +| Fix_Summary | One-line fix | `Add // @json 1.0.0 above struct EXTERNAL` | +| Reasoning | Populated only on severity downgrade; empty otherwise | `` | + +**Formatting rules:** +- UTF-8, no BOM, CRLF line endings +- Fields containing commas must be wrapped in double quotes +- Embedded double quotes escaped as `""` +- Empty fields: leave blank (two consecutive commas) +- One row per violated rule only — PASS rows excluded + +**If no issues found:** Generate header row + comment row: + +```csv +No,Interface,Date,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +,,,,,,,,,,All rules passed — no issues found,, +``` + +**Post-generation message:** +``` +📊 Report saved: + ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv + {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions + +To open in Excel (Windows): + Start-Process "ThunderTools\PluginQA\Reports\interface\{InterfaceName}_{YYYY-MM-DD}.csv" +``` + +--- + +## Example Output Structure + +``` +## Thunder Interface Validation — IHdmiCecSink + +### 🔴 Violations (Must Fix) + +**core_12_1 — @json Tag (CRITICAL)** +Status: VIOLATION +Citation: [IHdmiCecSink.h:45] @json tag missing above struct declaration +Issue: Without @json 1.0.0, ZERO JSON-RPC code will be generated for this interface. +Fix: + // @json 1.0.0 ← ADD THIS LINE + struct EXTERNAL IHdmiCecSink : virtual public Core::IUnknown { + +--- + +### 🟡 Warnings (Should Fix) + +**core_16_1 — Explicit Integer Widths** +Status: WARNING +Citation: [IHdmiCecSink.h:72] int parameter — use uint32_t +Issue: int type is platform-dependent and may differ in size across architectures. +Fix: Replace `const int volume` with `const uint32_t volume` + +--- + +### 🟢 Suggestions (Optional) + +(none) + +--- + +### ✅ Validated (N rules passed) + +core_1_1, core_2_1, core_3_1, core_4_1, core_5_1, core_6_1, core_9_1, +core_10_1, core_11_1, core_13_1, core_14_1, core_15_1, core_17_1, +advisory_m1_1, advisory_m2_1, advisory_m3_1, advisory_m5_1 + +--- + +### Compatibility Notes + +(Binary compatibility assessment — note if interface appears to be newly created vs released) +``` + +--- + +## Contextual Judgment (JUDGE step) + +| Scenario | Status field | Reasoning field | +|----------|-------------|-----------------| +| Rule satisfied | `PASS` — include in ✅ section | Omit | +| Rule violated, no mitigation | `VIOLATION` / `WARNING` / `SUGGESTION` | Omit | +| Rule technically violated but developer's approach is valid | Downgrade one level + `Status` field shows downgraded level | **Required** | + +The `Status` field reflects the **effective** severity after JUDGE: +- `violation` → `VIOLATION` (🔴), downgrade to `WARNING` (🟡) or `SUGGESTION` (🟢) +- `warning` → `WARNING` (🟡), downgrade to `SUGGESTION` (🟢) +- `suggestion` → `SUGGESTION` (🟢) + +Severity is **never escalated** above the YAML-defined level. + +--- + +## Common Critical Issues + +- **Missing @json tag** — the #1 cause of "why is there no JSON-RPC for my interface?" — results in zero generated code +- **std::vector without @restrict** — required; missing @restrict produces unsafe unbounded deserialization +- **std::map in interface** — not serialisable across process boundaries; use iterators instead +- **Missing nested IDs** — INotification/ICallback without their own RPC::ID_* values +- **Ambiguous integer types** — `int` and `long` change size on different platforms; always use uint32_t etc. + +--- + +## Important Notes + +- 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 are in `ThunderInterfaces/interfaces/` — search there first \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md new file mode 100644 index 00000000..ce16662c --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md @@ -0,0 +1,206 @@ +--- +title: "Thunder Interface Rule Manager" +description: "Add, update, or remove COM interface validation rules via guided questionnaire — keeps YAML, prompt, and spec in sync" +--- + +## Purpose + +This prompt manages rules in `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: + +1. `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` — rule data +2. `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` — Quick Reference table + rule detail blocks +3. `ThunderTools/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` — spec requirements + +--- + +## Step 0 — Document Template Fast Path + +If the user pastes a filled template with the following sections, skip the questionnaire and proceed directly to Step 3/4: + +``` +## Description + + +## How to Find It (Extraction Logic) +1. +2. + +## How to Verify It +1. +2. + +## Violation Pattern + + +## Fix +// WRONG: + + +// Correct: + + +## Example + +``` + +After receiving a template, also run **Core vs Advisory Classification** to verify the rule list placement is correct. + +--- + +## Step 1 — Collect Action + +Ask the user the following questions using `vscode_askQuestions`: + +**Question 1** (header: `action`): +What do you want to do? +- Add a new rule +- Update an existing rule +- Remove a rule + +**Question 2** (header: `rule_list`): +Is this a core rule or an advisory rule? + +Options: +- Core rule (codegen failure, ABI breakage, or crash risk) +- Advisory rule (best practice or style) + +Message: | + **Core rules** are violations that cause codegen failures, ABI breakage, runtime crashes, + or incorrect RPC behaviour. All 15 current core rules are severity: violation. + Examples: missing @json tag, wrong return type, missing @restrict on std::vector. + + **Advisory rules** are best-practice or style guidance that improves interface quality + but does not cause immediate failures. + Examples: single responsibility, explicit enum types. + +--- + +## Step 2 — Branch by Action + +### Branch: REMOVE + +Ask for the rule ID only (e.g. `core_1_1` or `advisory_m2_1`) then go to **Step 4**. + +### Branch: UPDATE + +**(2a)** Ask for the rule ID. + +**(2b)** Read the current rule from `thunder-interface-rules.yaml` and display it annotated with numbered field labels: + +``` +[1] id: +[2] name: +[3] severity: +[4] description: +[5] extraction_logic: +[6] verification_logic: +[7] violation_pattern: +[8] fix_template: +[9] citation: +``` + +Note: There is **no `category` field**. + +**(2c)** Ask the user (via `vscode_askQuestions`, multi-select): Which fields do you want to change? (select from [1]–[9]) + +**(2d)** For each selected field number, ask only the new value. + +Then go to **Step 3**. + +### Branch: ADD + +Ask all fields via `vscode_askQuestions`: + +- **id**: What is the rule ID? (e.g. `core_18_1` or `advisory_m6_1`) + Message: Core rules use `core_N_1` numbering. Advisory rules use `advisory_mN_1`. + +- **name**: What is the rule name? (Title Case, e.g. "No Inline Default Values") + +- **severity**: What is the severity? + Options: violation | warning | suggestion + +- **description**: Describe what the rule checks and why it matters. (multiline) + +- **extraction_logic**: How should the validator find the relevant code? (numbered steps) + Message: Describe reading and understanding the code — not searching for patterns. + Example: "1. Read the full interface declaration\n2. Identify all method parameter types" + +- **verification_logic**: What are the verification steps? (numbered steps) + Message: Each step must describe semantic reasoning — "Reason about X", never "search for Y". + +- **violation_pattern**: Single-line description of the wrong pattern. + +- **fix_template**: Show WRONG pattern and Correct pattern. + +- **citation**: A reference to a real Thunder interface file that illustrates the rule. + +--- + +## Step 3 — Apply Changes (ADD / UPDATE) + +### Update `thunder-interface-rules.yaml`: + +1. Add/update the rule in the `core_rules` or `advisory_rules` list as appropriate +2. **Do NOT add a `category` field** +5. Update rule count comments if present + +### Update `thunder-interface-review.prompt.md`: + +1. Add/update the row in the **Quick Reference table** with: `| id | Name | Key Point |` +2. The Quick Reference table row is the only required prompt edit for most rule changes + +### Update `specs/interface/spec.md`: + +1. Add/update the corresponding requirement line for the new/changed rule + +--- + +## Step 4 — Remove Rule + +### From `thunder-interface-rules.yaml`: +- Remove the entire rule block from the `core_rules` or `advisory_rules` list + + +### From `thunder-interface-review.prompt.md`: +- Remove the row from the Quick Reference table for the removed rule + +### From `specs/interface/spec.md`: +- Remove the corresponding requirement line + +--- + +## Step 5 — Confirmation Report + +After completing all changes, display: + +``` +## Interface Rule Manager — Changes Applied + +**Action:** [Add/Update/Remove] +**Rule:** [id] — [name] +**List:** [Core/Advisory] + +### Files Updated +| File | Change | +|------|--------| +| thunder-interface-rules.yaml | [Added/Updated/Removed] rule | +| thunder-interface-review.prompt.md | Quick Reference table [updated/row added/row removed] | +| specs/interface/spec.md | Requirement [added/updated/removed] | +``` + +--- + +## Core vs Advisory Classification + +| Criterion | Core ✅ | Advisory ✅ | +|-----------|---------|------------| +| Causes codegen failure (missing @json, wrong return type) | ✅ | ❌ | +| Causes ABI breakage (vtable incompatibility, type size change) | ✅ | ❌ | +| Causes runtime crash or RPC protocol error | ✅ | ❌ | +| Prevents cross-process serialisation | ✅ | ❌ | +| Best practice / style improvement | ❌ | ✅ | +| Improves maintainability or SRP | ❌ | ✅ | + +**Wrong-list correction:** If a submitted rule clearly belongs in the other list (e.g. a crash-causing rule submitted as advisory), move it to the correct list and explain the reclassification to the user before applying changes. + + diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md new file mode 100644 index 00000000..ceaf80bd --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md @@ -0,0 +1,534 @@ +## Core Principle + +This prompt performs **semantic code review** — reading plugin source code as a human developer and reasoning about its meaning. + +**Phase checkpoint rules (39):** Each uses a bounded yes/no query on a specific code block. + +❌ Open-ended (never do this): "Check this file for issues" +✅ Bounded (always this): "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" + +**Holistic rules (40):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. + +--- + +## Report Output Philosophy + +> **CRITICAL:** Only report issues. PASS and SKIP appear as **summary counts only** in phase tables — never as individual checkpoint details. +> Every citation must use the **ACTUAL plugin name** from the user's command — NEVER use `{PluginName}` as a placeholder in output. +> **Every reported issue requires a `[File:line]` citation** — no exceptions. + +--- + +## Usage + +**Mode 1 — Full plugin review:** +``` +/thunder-plugin-review +``` +Reviews ALL files in the plugin folder against all applicable rules. + +Examples: +- `/thunder-plugin-review Dictionary` +- `/thunder-plugin-review HdmiCecSink` + +**Mode 2 — Single file review:** +``` +/thunder-plugin-review +``` +Reviews ONLY the specified file against rules applicable to that file type. + +Examples: +- `/thunder-plugin-review Dictionary Dictionary.cpp` +- `/thunder-plugin-review HdmiCecSink Module.h` + +**File scoping for Mode 2:** +- `Module.h` → Phase 1 (rule_01, rule_03) + holistic rules relevant to headers +- `Module.cpp` → Phase 1 (rule_02) + holistic rules relevant to .cpp +- `{PluginName}.h` → Phase 2, 3, 4 (class declaration rules) + holistic rules for headers +- `{PluginName}.cpp` → Phase 2, 3, 4, 5 (implementation rules) + holistic rules for .cpp +- `CMakeLists.txt` → Phase 7 only + CMake holistic rules +- `{PluginName}.conf.in` → Phase 6 only +- `{PluginName}Implementation.h/cpp` → Phase 5C (only if CMakeLists.txt has PLUGIN__MODE = "Local") + applicable holistic rules +- Any file → applicable holistic rules matching the file type + +**5-step workflow:** +1. Accept plugin name (+ optional file argument) +2. Locate the plugin folder +3. Identify target files to review +4. Run applicable rules only (scoped by file if Mode 2) +5. Report failures with exact citations + +--- + +## Methodology + +### Step 1 — Load Rules + +Load `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml`. This file contains all 79 rules: +- `phase_1_checkpoints` through `phase_8_checkpoints` — 39 rules with bounded queries +- `general_rules` — 40 holistic rules across 8 sub-phases (rule_40 to rule_79) + +All rules produce the same output format. There is no distinction between "phase checkpoint" and "holistic" in the report. + +### Step 2 — Identify Plugin Files + +Primary search: `ThunderNanoServices/{PluginName}/` +Fallback: Search workspace for a folder named `{PluginName}` +Last resort: Ask user for location + +| File | Phase relevance | +|------|----------------| +| `Module.cpp` | Phase 1 (rule_02) | +| `Module.h` | Phase 1 (rule_01, rule_03) | +| `{PluginName}.h` | Phase 2, 3, 4 (class declarations) | +| `{PluginName}.cpp` | Phase 2, 3, 4, 5 (implementation) | +| `CMakeLists.txt` | Phase 7 | +| `{PluginName}.conf.in` | Phase 6 (optional) | +| `{PluginName}Implementation.h` | Phase 5C (only if PLUGIN__MODE = "Local" in CMakeLists.txt) | +| `{PluginName}Implementation.cpp` | Phase 5C (only if PLUGIN__MODE = "Local" in CMakeLists.txt) | + +### Step 3 — Execute Rules (CRITICAL: Understand First, Then Check) + +**Review philosophy for ALL 79 rules:** + +1. **UNDERSTAND FIRST** — Read ALL plugin source files. Build a complete mental model of the plugin's architecture: its lifecycle flow, threading model, ownership patterns, data flow, and how Initialize/Deinitialize relate to each other. Do this ONCE before checking any rule. +2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. +3. **REASON** — Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full plugin context make this approach correct and safe?" If yes → downgrade severity. +4. **CITE** — If genuinely wrong (not just technically different), cite exact `[ActualPluginName.cpp:LINE]` +5. **FIX** — Show corrected code using `fix_template` + +**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole plugin — not because a single block viewed in isolation doesn't match a pattern. + +--- + +## CRITICAL FILE SCOPING RULES + +- **Phase 1** (Module Structure) applies ONLY to `Module.h` and `Module.cpp` — never to the main plugin class files +- **Phase 3** (Class Registration) applies ONLY to the **main plugin class** (the one implementing `PluginHost::IPlugin`) — internal helper classes (Notification, Sink, Config, etc.) are explicitly excluded + +--- + +## Contextual Judgment (JUDGE step) + +After applying verification logic, if the answer is "No" (rule technically violated), reason whether the developer's actual approach is **valid and reasonable in context**: + +| Scenario | Effective status | `reasoning` field | +|----------|-----------------|-------------------| +| Rule satisfied | `PASS` | Omit | +| Rule violated, no mitigation | `VIOLATION` / `WARNING` / `SUGGESTION` | Omit | +| Rule violated but developer's approach is valid in context | Downgrade one level | **Required** — explain rule, developer's approach, why it is acceptable | +| Violated with residual risk but approach is reasonable | `WARNING` | **Required** | + +The `reasoning` field is **required** when severity is downgraded; it is **omitted** when no downgrade occurs. +Severity is **never escalated** above the YAML-defined level. + +**Example (severity downgraded):** + +```yaml +rule_id: rule_17 +status: SUGGESTION # downgraded from VIOLATION +severity: violation +question: "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" +answer: "No" +extracted_code: | + [Dictionary.cpp:45] _service = service; + [Dictionary.cpp:46] // AddRef not called here — service pointer is used locally only +violation_line: "[Dictionary.cpp:45]" +citation: "[Dictionary.cpp:45] IShell* assigned without AddRef()" +fix: "_service->AddRef(); // add immediately after _service = service;" +reasoning: "The rule exists to prevent dangling pointers when the IShell* is stored across calls. In this plugin, _service is used only within Initialize() and does not persist — it is not a true member field storing the pointer for later use. The developer's approach is valid in this context with no residual lifetime risk." +``` + +--- + +## Rules Reference (79 rules, sequential) + +### Phase 1 — Module Structure (rule_01 to rule_03) + +**rule_01** (suggestion) — `MODULE_NAME Plugin_ Prefix` +- Target: `Module.h` only +- Query: Does MODULE_NAME value start with `Plugin_` prefix? + +**rule_02** (violation) — `MODULE_NAME_DECLARATION` +- Target: `Module.cpp` only +- Query: Does Module.cpp contain `MODULE_NAME_DECLARATION(BUILD_REFERENCE)`? + +**rule_03** (warning) — `Module.h Uses #pragma once` +- Target: `Module.h` only +- Query: Does Module.h use `#pragma once` instead of a legacy `#ifndef` guard? + +--- + +### Phase 2 — Code Style (rule_04 to rule_13) + +**rule_04** (violation) — `VARIABLE_IS_NOT_USED Accuracy` +- Query: Are all VARIABLE_IS_NOT_USED-annotated parameters genuinely unused? + +**rule_05** (violation) — `Error Code Preservation` +- Query: Is the error code preserved on all paths — never unconditionally overwritten? + +**rule_06** (warning) — `NULL vs nullptr` +- Query: Is nullptr used exclusively — no NULL as a pointer value? + +**rule_07** (violation) — `No delete on COM Interface Pointers` +- Query: Are there zero uses of delete on COM interface pointer types? + +**rule_08** (violation) — `nullptr After Release` +- Query: Is nullptr assigned immediately after every ->Release() on a member? + +**rule_09** (violation, conditional) — `No QueryInterfaceByCallsign as Member` +- SKIP if no QueryInterfaceByCallsign() calls +- Query: Is the result always used transiently (never stored as member)? + +**rule_10** (violation) — `No Smart Pointers on COM Objects` +- Query: Are there zero COM interface pointers wrapped in shared_ptr or unique_ptr? + +**rule_11** (violation) — `No SmartLinkType for COMRPC Plugins` +- Query: Is SmartLinkType absent from the plugin? + +**rule_12** (violation) — `No delete on Plugin Object` +- Query: Is delete this absent from all plugin code? + +**rule_13** (violation) — `No throw Keyword in Plugin Code` +- Query: Is throw absent from all executable plugin code? + +--- + +### Phase 3 — Class Registration (rule_14 to rule_16) + +**rule_14** (warning) — `Special Members Deleted (Main Class)` +- MAIN class only — helper classes excluded +- Query: Are all 4 special members deleted in the main plugin class? + +**rule_15** (violation) — `Plugin Metadata Registration` +- Query: Does the plugin .cpp contain Plugin::Metadata registration? + +**rule_16** (violation, conditional) — `JSONRPC Inheritance When Used` +- SKIP if no JSON-RPC Register() calls +- Query: Does the class inherit PluginHost::JSONRPC when JSON-RPC is used? + +--- + +### Phase 4 — Lifecycle (rule_17 to rule_28) + +**rule_17** (violation, conditional) — `IShell AddRef in Initialize` +- SKIP if no stored IShell* member +- Query: Is AddRef() called immediately after assignment? + +**rule_18** (violation, conditional) — `IShell Release in Deinitialize` +- SKIP if no stored IShell* member +- Query: Is Release() + nullptr called in Deinitialize()? + +**rule_19** (violation) — `Information() Method` +- Query: Does the plugin implement string Information() const? + +**rule_20** (violation, conditional) — `Root() Null Check` +- SKIP if no Root() calls +- Query: Is the return value checked for nullptr before use? + +**rule_21** (violation, conditional) — `Root() Release in Deinitialize` +- SKIP if no Root() stored +- Query: Is the pointer Released and nulled in Deinitialize()? + +**rule_22** (violation, conditional) — `Observer Cleanup in Deinitialize` +- SKIP if no observer registration +- Query: Is every registered observer unregistered in Deinitialize()? + +**rule_23** (violation, conditional) — `SubSystems() Release in Deinitialize` +- SKIP if no SubSystems() call +- Query: Is SubSystems() released in Deinitialize()? + +**rule_24** (violation) — `Constructor Must Be Empty` +- Query: Is the constructor body empty (no logic)? + +**rule_25** (violation, conditional) — `service->Register/Unregister Pairing` +- SKIP if no service->Register() calls +- Query: Is every Register() matched by Unregister() in Deinitialize()? + +**rule_26** (violation) — `Initialize Returns Error String on Failure` +- Query: Does Initialize() return non-empty error string on every failure path? + +**rule_27** (violation) — `No Manual Deinitialize() in Initialize` +- Query: Is Deinitialize() never called from within Initialize()? + +**rule_28** (violation) — `Destructor Must Be Empty` +- Query: Is the destructor body completely empty? + +--- + +### Phase 5 — Implementation (rule_29 to rule_32) + +**rule_29** (violation, conditional) — `JSON-RPC Register/Unregister Pairing` +- SKIP if no JSON-RPC handlers +- Query: Is every handler registered in Initialize() unregistered in Deinitialize()? + +**rule_30** (violation, conditional) — `SinkType Pattern for Subscribers` +- SKIP if no subscriber classes +- Query: Do notification subscribers follow the SinkType pattern? + +**rule_31** (violation, conditional) — `Unavailable() in SinkType Classes` +- SKIP if no SinkType classes +- Query: Does every SinkType class implement Unavailable()? + +**rule_32** (violation) — `No Hardcoded Paths` +- Query: Are there zero hardcoded filesystem paths? + +--- + +### Phase 5C — Out-of-Process (rule_33 to rule_34) + +**rule_33** (violation, conditional) — `OOP Connection Termination in Deinitialize` +- SKIP if CMakeLists.txt does NOT have PLUGIN__MODE set to "Local" +- Query: Does Deinitialize() terminate the OOP connection? + +**rule_34** (violation, conditional) — `connectionId Checked in IRemoteConnection Callbacks` +- SKIP if no IRemoteConnection::INotification implementation +- Query: Is connectionId checked before acting in every callback? + +--- + +### Phase 6 — Configuration (rule_35 to rule_37) + +**rule_35** (violation, conditional) — `Startmode Declaration` +- SKIP if no .conf.in file +- Query: Does .conf.in declare an explicit startmode? + +**rule_36** (violation, conditional) — `Config Core::JSON::Container` +- SKIP if no Config class +- Query: Does Config inherit Core::JSON::Container? + +**rule_37** (violation) — `No Hardcoded Numeric Tuning Parameters` +- Query: Are there zero hardcoded tuning parameters (timeouts, sizes, counts)? + +--- + +### Phase 7 — CMake (rule_38) + +**rule_38** (violation, conditional) — `CXX_STANDARD Uses Thunder Variable` +- SKIP if CMakeLists.txt does not set CXX_STANDARD +- Query: Does CXX_STANDARD use `${CXX_STD}`? + +--- + +### Phase 8 — COM Interface (rule_39) + +**rule_39** (violation) — `COM Methods Return Core::hresult` +- Query: Do all COM interface methods return Core::hresult? + +--- + +### Holistic Rules (rule_40 to rule_79) — 8 sub-phases + +Rules 40–79 require reading broader code context across multiple methods/files. +Full definitions in `thunder-plugin-rules.yaml`. + +#### Conventions & Encapsulation (4 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_40 | #pragma once in all headers | suggestion | +| rule_41 | Apache 2.0 Copyright Header | suggestion | +| rule_49 | CMake NAMESPACE Variable | suggestion | +| rule_77 | Observer Classes Private and Nested | suggestion | + +#### Lifecycle & State Integrity (6 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_44 | OOP Registration Order | violation | +| rule_45 | Complete State Reset in Deinitialize | violation | +| rule_46 | Reverse-Order Cleanup | suggestion | +| rule_57 | Config Errors Return Non-Empty from Initialize | violation | +| rule_70 | Framework Pointers Not Accessed After Deinitialize | violation | +| rule_79 | All Acquired Pointers Cleared After Deinitialize | violation | + +#### Concurrency & Threading (10 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_47 | Observer Locking | violation | +| rule_50 | Handlers Must Not Block | violation | +| rule_51 | No Activate/Deactivate from Handlers | violation | +| rule_52 | Shared State Protected by CriticalSection | violation | +| rule_53 | No Lock Held During Framework Callbacks | violation | +| rule_54 | Worker Jobs Check Deinitialize Guard | violation | +| rule_65 | JSON-RPC Handlers Are Re-entrant Safe | violation | +| rule_66 | IPlugin::INotification Callbacks Must Not Block | violation | +| rule_67 | Lock Scope Minimized | violation | +| rule_68 | Plugin Threads Joined in Deinitialize | violation | + +#### COM Reference & Memory Safety (3 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_48 | AddRef/Release Balance | violation | +| rule_63 | COM Reference Counting Correctness | violation | +| rule_71 | hresult Return Values Checked | violation | + +#### Resource Management (3 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_55 | File Descriptors / Sockets Wrapped in RAII | violation | +| rule_56 | No Unbounded Memory Growth | violation | +| rule_69 | Memory and Allocation Safety | warning | + +#### JSON-RPC Compliance (7 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_58 | interface->Register/Unregister Pairing | violation | +| rule_59 | Handler Registration Order | violation | +| rule_60 | Use Core::ERROR_* for Handler Failure Codes | violation | +| rule_61 | Input Validation in JSON-RPC Handlers | violation | +| rule_62 | Event Constants and Typed JSON Payloads | warning | +| rule_74 | JSON-RPC Input Validation for Bounds and Types | violation | +| rule_78 | No Deprecated JSON-RPC APIs | violation | + +#### Inter-Plugin & OOP Design (2 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_64 | No Hard Inter-Plugin Dependencies | warning | +| rule_76 | OOP Error Propagation and Method Naming | warning | + +#### Code Quality & Security (5 rules) +| ID | Name | Severity | +|----|------|----------| +| rule_42 | STL Types | warning | +| rule_43 | ASSERT vs Error Handling | warning | +| rule_72 | ASSERT Only for Programmer Invariants | warning | +| rule_73 | Security: Logging, Shell, Path, and Error Exposure | violation | +| rule_75 | Config Completeness and Resource Cleanup | warning | + +--- + +## Output Format + +### Issue Report — Grouped by File + +Group all issues (from any rule) by source file. For files with failures: + +``` +### {ActualFileName} — N issue(s) +``` + +For each failing rule (same format for all 79 rules): + +```yaml +rule_id: rule_06 +status: WARNING +severity: warning +question: "Is nullptr used exclusively — no NULL as a pointer value in code?" +answer: "No" +extracted_code: | + [Dictionary.cpp:108] IPlugin* plugin = NULL; +violation_line: "[Dictionary.cpp:108]" +citation: "[Dictionary.cpp:108] NULL used as null pointer — use nullptr" +fix: "IPlugin* plugin = nullptr;" +reasoning: # omit if no severity downgrade +``` + +Severity heading prefixes: +- ❌ = VIOLATION +- ⚠️ = WARNING +- 💡 = SUGGESTION + +### Summary Table (single unified table for all 79 rules) + +| Phase | PASS | FAIL | SKIP | +|-------|------|------|------| +| Phase 1 — Module Structure | N | N | N | +| Phase 2 — Code Style | N | N | N | +| Phase 3 — Class Registration | N | N | N | +| Phase 4 — Lifecycle | N | N | N | +| Phase 5 — Implementation | N | N | N | +| Phase 5C — Out-of-Process | N | N | N | +| Phase 6 — Configuration | N | N | N | +| Phase 7 — CMake | N | N | N | +| Phase 8 — COM Interfaces | N | N | N | +| Holistic Rules (8 sub-phases) | N | N | N | +| **Total (79 rules)** | **N** | **N** | **N** | + +Followed by a numbered **Next Steps** list citing `[File:line]` for each action item. + +--- + +## Key Advantages + +- **Reproducible:** Same bounded questions → same answers on the same code +- **Fast:** One code block per checkpoint — no whole-file analysis per rule +- **Actionable:** Each failure has an exact line, an explanation, and a fix +- **Contextual:** Severity downgrade logic handles valid alternative approaches + +## Important Notes + +1. Load `thunder-plugin-rules.yaml` at the start of every review run — rules may have been updated +2. Never embed rule data in this prompt — always load from YAML at runtime +3. If a plugin is not found in `ThunderNanoServices/`, search workspace before asking +4. Total: 79 rules (rule_01 to rule_79), all sequential, all producing unified output +5. Rule IDs in reports use the format `rule_XX` — no phase prefixes + +## Command Examples + +``` +/thunder-plugin-review Dictionary +/thunder-plugin-review NetworkControl +/thunder-plugin-review HdmiCecSink +/thunder-plugin-review Dictionary Dictionary.cpp +/thunder-plugin-review NetworkControl Module.h +``` + +--- + +## Step 6 — Generate CSV Report + +After reporting all results in chat, generate a CSV file for tracking and Excel analysis. + +**File path:** `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` + +- Create `ThunderTools/PluginQA/Reports/plugin/` if it does not exist +- Never overwrite an existing file — append `_2`, `_3` etc. if a file with that name already exists + +**CSV columns (exact order, 14 columns):** + +| Column | Description | Example | +|--------|-------------|---------| +| No | Row number starting at 1 | `1` | +| Plugin | Plugin name from command argument | `HdmiCecSink` | +| Date | ISO date of review | `2026-06-05` | +| Phase | Full phase label | `Phase 2 — Code Style` | +| Rule_ID | Sequential rule ID | `rule_06` | +| Rule_Name | Checkpoint name from YAML | `NULL vs nullptr` | +| Status | Effective status after JUDGE step | `VIOLATION` / `WARNING` / `SUGGESTION` | +| Severity | YAML severity level | `violation` / `warning` / `suggestion` | +| File | Source file where issue was found | `HdmiCecSink.cpp` | +| Line | Exact line number | `128` | +| Citation | Short citation string | `[HdmiCecSink.cpp:128]` | +| Issue_Description | What was found | `NULL used instead of nullptr` | +| Fix_Summary | One-line fix description | `Replace NULL with nullptr` | +| Reasoning | Populated only when severity was downgraded by JUDGE; empty otherwise | `` | + +**Header row:** +``` +No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +``` + +**Rules:** +- One row per FAIL / WARNING / SUGGESTION only — PASS and SKIP rows are excluded +- `Status` reflects the **effective** severity after the JUDGE step +- `Reasoning` column is populated only when severity was downgraded; empty otherwise +- UTF-8, no BOM, CRLF line endings +- Fields containing commas must be wrapped in double quotes: `"Phase 2 — Code Style"` +- Embedded double quotes escaped as `""`: `"Use ""nullptr"" not NULL"` +- Empty fields: leave blank (two consecutive commas: `,,`) + +**If no issues found** (all checkpoints pass), still generate the CSV with header + one comment row: + +```csv +No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning +,,,,,,,,,,,,All checkpoints passed — no issues found, +``` + +**Post-generation message:** +``` +📊 Report saved: + ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv + {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions + +To open in Excel (Windows): + Start-Process "ThunderTools\PluginQA\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" +``` \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md new file mode 100644 index 00000000..dc94e901 --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md @@ -0,0 +1,275 @@ +--- +title: "Thunder Plugin Rule Manager" +description: "Add, update, or remove plugin rules (phase checkpoint or holistic) via guided questionnaire — keeps YAML, prompt, README, and spec in sync" +--- + +## Purpose + +This prompt manages rules in `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: + +1. `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` — rule data +2. `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` — checkpoint descriptions +3. `ThunderTools/PluginQA/README.md` — documentation +4. `ThunderTools/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` — spec requirements + +--- + +## Step 0 — Document Template Fast Path + +If the user pastes a filled template with the following sections, skip the questionnaire and go directly to Step 3/4: + +``` +## What This Rule Checks + + +## Extraction Target + + +## Yes/No Question + + +## Verification Steps +1. +2. + +## Violation Pattern + + +## Fix + + +## Conditional +Yes/No — and if Yes, what is the skip condition? +``` + +After receiving a template, also run **Phase Checkpoint vs Holistic Classification** to verify the `Type` field is correct before proceeding. + +--- + +## Step 1 — Collect Action + +Ask the user the following questions using `vscode_askQuestions`: + +**Question 1** (header: `action`): +What do you want to do? +- Add a new rule +- Update an existing rule +- Remove a rule + +**Question 2** (header: `rule_kind`): +Is this a phase checkpoint rule or a holistic rule? + +Options: +- Phase checkpoint rule (bounded yes/no query, one code block) +- Holistic rule (semantic review, requires full-plugin reasoning) + +Message: | + **Phase checkpoint rule:** Can be reduced to ONE yes/no question on ONE specific code block. + Example: "Is AddRef() called immediately after assigning the IShell* pointer?" + + **Holistic rule:** Requires architectural judgment, threading analysis, security reasoning, + or reading multiple code paths that cannot be bounded to a single yes/no question. + Example: "Are all notification callbacks safe from deadlock across the full plugin lifecycle?" + +**Question 3** (header: `phase`) — ask only if rule_kind = Phase checkpoint: +Which phase does this rule belong to? + +Options: +- Phase 1 — Module Structure (module_1_*) +- Phase 2 — Code Style (style_2_*) +- Phase 3 — Class Registration (registration_3_*) +- Phase 4 — Lifecycle (lifecycle_4_*) +- Phase 5 — Implementation (implementation_5_*) +- Phase 5C — Out-of-Process (oop_9_*) +- Phase 6 — Configuration (config_6_*) +- Phase 7 — CMake (cmake_7_*) +- Phase 8 — COM Interface Rules (com_8_*) + +--- + +## Step 2 — Branch by Action + +### Branch: REMOVE + +Ask for the rule ID (rule_id for phase checkpoint, rule_XX) then go to **Step 4**. + +### Branch: UPDATE + +**(2a)** Ask for the rule ID. + +**(2b)** Read the current rule from `thunder-plugin-rules.yaml` and display it annotated with numbered field labels: + +``` +[1] rule_id / rule_id: +[2] name: +[3] severity: +[4] phase: +[5] extraction.target: (phase checkpoint only) +[6] extraction.method: (phase checkpoint only) +[7] bounded_query.question: (phase checkpoint only) +[8] verification_logic: (phase checkpoint only) +[9] violation_pattern: +[10] fix_template: +[11] conditional: (phase checkpoint only) +[12] skip_condition: (phase checkpoint only) +[13] citation.line_format: (phase checkpoint only) +[14] review_question: (holistic only) +[15] review_method: (holistic only) +[16] evidence_requirement: (holistic only) +``` + +**(2c)** Ask the user (via `vscode_askQuestions`, multi-select): Which fields do you want to change? (list field numbers) + +**(2d)** For each selected field, ask for the new value only. + +Then go to **Step 3**. + +### Branch: ADD (phase checkpoint) + +Ask the following via `vscode_askQuestions`: + +- **rule_id**: What is the checkpoint ID? (e.g. `rule_041`) + Message: Use the phase prefix + sequential number: `module_1_*`, `style_2_*`, `lifecycle_4_*`, etc. + +- **name**: What is the rule name? (Title Case, e.g. "No Raw Pointers After Move") + +- **severity**: What is the severity? + Options: violation | warning | suggestion + +- **extraction_target**: What specific code block should be extracted? + Message: Describe the EXACT code location (e.g. "All ->Release() calls on member variables in Deinitialize()") + +- **extraction_method**: How should the code be read? + Message: ✅ "Read the full function body as a human developer..." + ❌ "Search for the pattern X..." (do not use search/regex language) + +- **bounded_question**: What is the yes/no question? + Message: ✅ "Is nullptr assigned immediately after every ->Release() call on a member?" + ❌ "Does the code have nullptr after Release?" (too vague) + +- **verification_steps**: What are the verification steps? (numbered list) + Message: Each step must describe semantic reasoning — "Read X and reason about Y". Never "search for" or "match pattern". + +- **violation_pattern**: Single-line description of what's wrong. + +- **fix_template**: Show WRONG pattern and Correct pattern. + +- **conditional**: Is this rule conditional (SKIP if prerequisite not found)? Yes/No + If Yes: What is the skip condition? + +### Branch: ADD (holistic) + +Ask only: + +- **rule_id**: What is the rule ID? (e.g. `rule_80`) +- **name**: What is the rule name? (Title Case) +- **severity**: Options: violation | warning | suggestion +- **review_question**: What semantic question describes what to verify? + Message: Frame as a full-context semantic question, e.g. "Using semantic reasoning over the full lifecycle, is every..." +- **review_method**: (pre-filled) "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**: (pre-filled) "Provide exact [File:line] citation for any failure." + +--- + +## Step 3 — Apply Changes (ADD / UPDATE) + +### Update `thunder-plugin-rules.yaml`: + +**For phase checkpoint rules:** +- Add/update the rule in the correct `phase_N_checkpoints` section +- Rule must use object form for `bounded_query`: `{ question: "...", expected_answer: "Yes" }` +- `verification_logic` must be a YAML list of numbered step strings (`- "1. ..."`) +- Increment `total_rules` in the `metadata` block + +**For general rules:** +- Add/update the rule in the `general_rules` section +- Include: rule_id, name, severity, `category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security)`, review_question, review_method, evidence_requirement +- **Do NOT add** extraction, bounded_query, or verification_logic fields to general rules +- Increment `total_general_rules` in the `metadata` block + +### Update `thunder-plugin-review.prompt.md`: + +- Add/update the checkpoint description in the appropriate Phase section +- Include: rule_id, severity indicator, name, SKIP condition (if conditional), query text, citation format +- Update the phase count total if a new rule was added + +### Update `README.md`: + +- Add/update the rule entry under the appropriate phase in the Plugin Validation section +- Update checkpoint counts in the Validation Phases table + +### Update `specs/plugin/spec.md`: + +- Add/update the corresponding scenario in the spec under the relevant phase + +--- + +## Step 4 — Remove Rule + +### From `thunder-plugin-rules.yaml`: +- Remove the entire rule block from the appropriate section +- Decrement `total_rules` or `total_general_rules` in metadata + +### From `thunder-plugin-review.prompt.md`: +- Remove the checkpoint description from the appropriate Phase section +- Update the phase count total + +### From `README.md`: +- Remove the rule entry from the Plugin Validation section +- Update checkpoint counts + +### From `specs/plugin/spec.md`: +- Remove the corresponding scenario + +--- + +## Step 5 — Confirmation Report + +After completing all changes, display: + +``` +## Rule Manager — Changes Applied + +**Action:** [Add/Update/Remove] +**Rule:** [rule_id or rule_id] — [name] +**Type:** [Phase checkpoint/Holistic] + +### Files Updated +| File | Change | +|------|--------| +| thunder-plugin-rules.yaml | [Added/Updated/Removed] rule; total phase checkpoints: N, holistic: N | +| thunder-plugin-review.prompt.md | [Added/Updated/Removed] Phase N checkpoint description | +| README.md | [Added/Updated/Removed] rule entry; Phase N total: N checkpoints | +| specs/plugin/spec.md | [Added/Updated/Removed] scenario | +``` + +--- + +## Phase Checkpoint vs Holistic Classification + +| Criterion | Phase checkpoint ✅ | Holistic ✅ | +|-----------|-------------|----------| +| Can reduce to ONE yes/no question | ✅ | ❌ | +| Targets ONE specific code block | ✅ | ❌ | +| Result is verifiable/reproducible across runs | ✅ | Requires judgment | +| Requires understanding multiple code paths | ❌ | ✅ | +| Requires architectural/threading/security reasoning | ❌ | ✅ | +| Would need a bounded query covering entire codebase | ❌ | ✅ | + +**Phase checkpoint criteria (all 5 must be YES):** +1. Can express as a single yes/no bounded query? +2. Targets a single specific code block? +3. Verification logic is a numbered list of semantic reasoning steps? +4. Does NOT require broad architectural concern analysis? +5. Does NOT require multi-path reasoning? + +**Holistic criteria (any of these → holistic):** +1. Requires holistic code understanding beyond one function? +2. Involves threading, concurrency, or re-entrancy analysis? +3. Involves security reasoning or OWASP-type checks? +4. Requires understanding of architectural patterns (observer, lifecycle symmetry)? +5. Requires reading multiple code paths to decide? +6. Cannot be reduced to a single bounded yes/no query? + +**Ambiguous cases:** If the classification is not clear, ask the user before proceeding. Display the classification criteria above and explain which criteria pull each way. + diff --git a/PluginQualityAdvisor/README.md b/PluginQualityAdvisor/README.md new file mode 100644 index 00000000..224dc8af --- /dev/null +++ b/PluginQualityAdvisor/README.md @@ -0,0 +1,171 @@ +# ThunderPluginQualityAdvisor + +AI-driven validation tools for Thunder plugin and COM interface development, powered by VS Code GitHub Copilot Chat. + +--- + +## Prerequisites + +1. **VS Code** with GitHub Copilot Chat extension installed +2. **ThunderNanoServices** and **ThunderInterfaces** repositories in your workspace (or specify the path when prompted) +3. Register the prompt file location in VS Code: + + Open your VS Code `settings.json` (`Ctrl+Shift+P` -> `Preferences: Open User Settings (JSON)`) and add: + +```json + { + "chat.promptFilesLocations": { + "ThunderTools/PluginQA/Prompts": true + } + } +``` + +The path should point to the `Prompts` folder inside PluginQA. Use the path relative to your workspace root, or an absolute path if ThunderTools is outside the workspace. + +4. **Reload VS Code** — press `Ctrl+Shift+P` -> `Developer: Reload Window` + +After reload, the `/thunder-*` slash commands appear in Copilot Chat. + +--- + +## Commands + +| Command | What it does | +|---------|--------------| +| `/thunder-plugin-review` | Validate a Thunder plugin against all rules | +| `/thunder-interface-review` | Validate a Thunder COM interface header | +| `/thunder-generate-plugin` | Generate a new Thunder plugin skeleton | +| `/thunder-plugin-rule-manager` | Add, update, or remove plugin rules | +| `/thunder-interface-rule-manager` | Add, update, or remove interface rules | + +--- + +## `/thunder-plugin-review` + +Validates a Thunder plugin against all rules defined in `thunder-plugin-rules.yaml` using semantic code review. + +**Usage:** +``` +/thunder-plugin-review +/thunder-plugin-review +``` + +**Examples:** +``` +/thunder-plugin-review Dictionary +/thunder-plugin-review Dictionary Dictionary.cpp +``` + +- **Full plugin mode** — reviews all files in the plugin folder +- **Single file mode** — reviews only the specified file against applicable rules + +**Output:** Failures grouped by file with exact `[File:line]` citations, followed by a summary table and next steps. + +--- + +## `/thunder-interface-review` + +Validates a Thunder COM interface header against core and advisory rules defined in `thunder-interface-rules.yaml`. + +**Usage:** +``` +/thunder-interface-review +``` + +**Examples:** +``` +/thunder-interface-review IDictionary.h +/thunder-interface-review INetworkControl.h +``` + +**Output:** Findings grouped into Violations, Warnings, Suggestions, Validated, and Compatibility Notes. + +--- + +## `/thunder-generate-plugin` + +Generates a new Thunder plugin skeleton interactively using PluginSkeletonGenerator. + +**Usage:** +``` +/thunder-generate-plugin +``` + +Collects parameters via VS Code dropdowns (plugin name, in-process/out-of-process, JSON-RPC support, etc.) and generates the plugin files. + +--- + +## `/thunder-plugin-rule-manager` + +Add, update, or remove plugin validation rules. + +**Usage:** +``` +/thunder-plugin-rule-manager +``` + +**Two ways to provide input:** +- **Interactive** — answers questions via VS Code dropdowns +- **Document template** — paste a filled template from `plugin-rule-template-guide.md` + +Updates `thunder-plugin-rules.yaml` and `thunder-plugin-review.prompt.md` atomically. + +--- + +## `/thunder-interface-rule-manager` + +Add, update, or remove interface validation rules. + +**Usage:** +``` +/thunder-interface-rule-manager +``` + +**Two ways to provide input:** +- **Interactive** — answers questions via VS Code dropdowns +- **Document template** — paste a filled template from `interface-rule-template-guide.md` + +Updates `thunder-interface-rules.yaml` and `thunder-interface-review.prompt.md` atomically. + +--- + +## Project Structure + +``` +ThunderTools/PluginQA/ ++-- README.md ++-- setup-prompts.py ++-- plugin-rule-template-guide.md ++-- interface-rule-template-guide.md ++-- wiki.md ++-- Prompts/ +| +-- thunder-plugin-review.prompt.md +| +-- thunder-interface-review.prompt.md +| +-- thunder-generate-plugin.prompt.md +| +-- thunder-plugin-rule-manager.prompt.md +| +-- thunder-interface-rule-manager.prompt.md ++-- rules/ +| +-- thunder-plugin-rules.yaml +| +-- thunder-interface-rules.yaml ++-- Reports/ + +-- plugin/ + | +-- {PluginName}_{YYYY-MM-DD}.csv + +-- interface/ + +-- {InterfaceName}_{YYYY-MM-DD}.csv +``` + +--- + +## Severity Levels + +| Level | Meaning | +|---|---| +| `violation` | Blocking | +| `warning` | Should fix | +| `suggestion` | Optional | + +--- + +## Reports + +After each review, a CSV report is generated under `Reports/plugin/` or `Reports/interface/`. Reports contain one row per finding with exact file, line, citation, description, fix summary, and reasoning (if severity was downgraded). \ No newline at end of file diff --git a/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md new file mode 100644 index 00000000..088091b7 --- /dev/null +++ b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md @@ -0,0 +1,364 @@ +# Interface Rule Manager — Rule Template Guide + +Use this guide to fill in a rule template and pass it to `/thunder-interface-rule-manager`. +A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, +validates it, and updates all three files in one shot: + +- `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` +- `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` +- `ThunderTools/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` + +--- + +## Is my rule Core or Advisory? + +| Question | Core Rule | Advisory Rule | +|---|---|---| +| Does the violation break code generation (e.g. no JSON-RPC output)? | Yes | No | +| Does it cause ABI/binary compatibility issues? | Yes | No | +| Does it cause crashes or undefined behaviour at runtime? | Yes | No | +| Is it a structural requirement for Thunder COM interfaces? | Yes | No | +| Is it a design best practice that improves quality but does not break anything? | No | Yes | +| Is it a style/convention preference? | No | Yes | + +**Core rules** enforce strict COM interface correctness — structural requirements that MUST be satisfied for the interface to function correctly with Thunder's code generators and runtime. + +**Advisory rules** are design-quality checks that address broader best practices (single responsibility, no exceptions, enum types, @restrict usage) — violations here indicate design concerns rather than broken code generation. + +If the manager determines your rule is in the wrong list, it will auto-correct with an explanation. + +--- + +## How to use this template + +1. Copy the blank template below into a new `.md` file +2. Set `Action` to `Add`, `Update`, or `Remove` +3. Fill in the fields that apply to your action (see field reference below) +4. Attach the file — or paste its contents — when you open `/thunder-interface-rule-manager` +5. The manager confirms what it parsed before making any changes + +--- + +## Blank template + +```markdown +## Interface Rule + +Action: Add + +Rule_ID: +Rule_List: core +Name: +Severity: violation + +### Description + + +### How to Find It (Extraction Logic) + +1. +2. + +### How to Verify It (Verification Logic) + +1. +2. +3. + +### Violation Pattern + + +### Fix +// WRONG: + +// Correct: + +### Example Citation + +ThunderInterfaces/interfaces/IExample.h — description +``` + +--- + +## Field Reference + +### `Action` *(required — must be the first field)* + +| Value | When to use | +|---|---| +| `Add` | Creating a new rule that does not yet exist | +| `Update` | Changing one or more fields on an existing rule | +| `Remove` | Deleting a rule entirely | + +--- + +### `Rule_ID` *(required for Update/Remove; optional for Add)* + +Format: `core_X_1` for core rules or `advisory_mX_1` for advisory rules. + +Current core rules: `core_1_1` through `core_17_1` (15 rules) +Current advisory rules: `advisory_m1_1`, `advisory_m2_1`, `advisory_m3_1`, `advisory_m5_1` (4 rules) + +- **Add** — leave blank to auto-assign the next available ID, or specify your own +- **Update/Remove** — mandatory; this is how the manager finds the rule + +--- + +### `Rule_List` *(required for Add)* + +| Value | When to use | +|---|---| +| `core` | Rule enforces codegen/ABI/crash-level correctness | +| `advisory` | Rule enforces design best practices | + +--- + +### `Name` *(required for Add; optional for Update)* + +Short descriptive title in Title Case. 2-5 words. + +**Good:** `@json Tag (CRITICAL)`, `Binary Compatibility`, `No std::map in Interfaces` +**Avoid:** `Check Interface`, `Tag Rule` + +--- + +### `Severity` *(required for Add; optional for Update)* + +| Value | Meaning | +|---|---| +| `violation` | Must fix — breaks code generation, ABI, or causes crashes | +| `warning` | Should fix — best practice with real risk | +| `suggestion` | Optional — style or convention preference | + +Note: All 15 core rules currently use `violation`. Advisory rules use mixed severities. + +--- + +### `### Description` *(required for Add; optional for Update)* + +Explain what the rule checks and why. Include the impact of violations. +Write 2-5 sentences as if explaining to a developer unfamiliar with Thunder interfaces. + +--- + +### `### How to Find It (Extraction Logic)` *(required for Add; optional for Update)* + +Numbered steps describing how to locate the relevant code in the interface header. +Must describe reading and understanding the code, NOT searching for text patterns. + +**Good:** +``` +1. Read the comment lines immediately above the interface struct declaration +2. Verify the @json tag is directly adjacent to the struct declaration (no blank lines between) +``` + +**Avoid:** +``` +1. Search for "// @json" in the file +``` + +--- + +### `### How to Verify It (Verification Logic)` *(required for Add; optional for Update)* + +Numbered steps describing what constitutes a pass vs fail. +Must use semantic reasoning — understand the code, don't pattern match. + +--- + +### `### Violation Pattern` *(required for Add; optional for Update)* + +Single-line string describing the wrong pattern. Shown in the violation output. + +**Good:** `No @json comment found above interface struct — no RPC code will be generated` +**Avoid:** Multi-line descriptions or bullet lists + +--- + +### `### Fix` *(required for Add; optional for Update)* + +Show corrected code using `// WRONG:` and `// Correct:` markers. + +--- + +### `### Example Citation` *(required for Add; optional for Update)* + +Reference to a real Thunder interface file showing where this rule applies. +Format: `ThunderInterfaces/interfaces/IFileName.h — description` + +--- + +## What to fill per action + +| Field | Add | Update | Remove | +|---|---|---|---| +| `Action` | Required | Required | Required | +| `Rule_ID` | Optional | Required | Required | +| `Rule_List` | Required | Only if moving | — | +| `Name` | Required | Only if changing | — | +| `Severity` | Required | Only if changing | — | +| Content sections | All | Only changed ones | — | + +**Update rule:** fill only the fields you want to change — leave everything else blank. + +--- + +## Examples + +### Add — Core rule + +```markdown +## Interface Rule + +Action: Add + +Rule_ID: +Rule_List: core +Name: No Raw Pointer Returns +Severity: violation + +### Description +Interface methods must not return raw pointers. Raw pointer returns create ownership +ambiguity — callers don't know whether to call Release() on the result. All pointer +returns must use the COM pattern: pass an output pointer parameter and return Core::hresult. + +### How to Find It (Extraction Logic) +1. Read all virtual method declarations in the interface struct +2. For each method, examine the return type + +### How to Verify It (Verification Logic) +1. For each virtual method, check if the return type is a raw pointer (T*) +2. Exclude Core::hresult and void returns — those are correct +3. Exclude string_view and similar value types — those are not ownership transfers +4. If any method returns a raw pointer type -> VIOLATION +5. Otherwise -> PASS + +### Violation Pattern +Interface method returns raw pointer — ownership ambiguity + +### Fix +// WRONG: +virtual IConnection* GetConnection(const uint32_t id) = 0; + +// Correct: +virtual Core::hresult GetConnection(const uint32_t id, IConnection*& connection /* @out */) = 0; + +### Example Citation +ThunderInterfaces/interfaces/INetworkControl.h — method return types +``` + +--- + +### Add — Advisory rule + +```markdown +## Interface Rule + +Action: Add + +Rule_ID: +Rule_List: advisory +Name: Method Count Limit +Severity: warning + +### Description +Interfaces with more than 12 methods may violate the single responsibility principle. +Consider splitting into focused sub-interfaces. Large interfaces are harder to implement, +test, and version safely. + +### How to Find It (Extraction Logic) +1. Read the interface struct declaration +2. Count all pure virtual method declarations + +### How to Verify It (Verification Logic) +1. Count the number of pure virtual methods in the interface +2. If count exceeds 12 -> WARNING (suggest splitting) +3. If count is 12 or fewer -> PASS + +### Violation Pattern +Interface has more than 12 methods — consider splitting + +### Fix +// WRONG: +struct EXTERNAL IMediaPlayer : virtual public Core::IUnknown { + // 15+ methods in one interface +}; + +// Correct: +struct EXTERNAL IMediaPlayer : virtual public Core::IUnknown { + // Core playback methods only (8 methods) +}; +struct EXTERNAL IMediaPlayerMetadata : virtual public Core::IUnknown { + // Metadata methods split out (7 methods) +}; + +### Example Citation +ThunderInterfaces/interfaces/IMediaPlayer.h — interface method count +``` + +--- + +### Update — change severity and verification logic + +```markdown +## Interface Rule + +Action: Update + +Rule_ID: core_5_1 + +Severity: violation + +### How to Verify It (Verification Logic) +1. For @json-tagged interfaces in Thunder 5.0+, ALL action methods must return Core::hresult +2. Getter methods (property getters) may return the value type directly +3. If any action method returns void or another type -> VIOLATION +``` + +--- + +### Remove + +```markdown +## Interface Rule + +Action: Remove + +Rule_ID: advisory_m2_1 +``` + +The manager confirms the rule name before deleting. + +--- + +## Common mistakes + +| Mistake | Problem | Fix | +|---|---|---| +| Extraction logic uses "search for" language | Must reason semantically | Write "read and examine" not "search for" | +| Violation pattern is multi-line | Must be single-line string | Condense to one sentence | +| Missing Example Citation | Manager cannot validate against real code | Always reference a real Thunder interface file | +| Core rule that is actually best-practice | Manager will flag misclassification | Let manager auto-correct to advisory | +| Advisory rule that breaks codegen | Manager will flag misclassification | Let manager auto-correct to core | +| `Rule_ID` format wrong | Must be `core_X_1` or `advisory_mX_1` | Check existing IDs in YAML | + +--- + +## Quick reference + +``` +## Interface Rule + +Action: Add | Update | Remove +Rule_ID: e.g. core_18_1 or advisory_m6_1 +Rule_List: core | advisory +Name: Title Case, 2-5 words +Severity: violation | warning | suggestion + +### Description — the WHY (required for Add) +### How to Find It — extraction steps (required for Add) +### How to Verify It — verification steps (required for Add) +### Violation Pattern — single-line summary (required for Add) +### Fix — WRONG / Correct code (required for Add) +### Example Citation — real Thunder interface reference (required for Add) +``` diff --git a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md new file mode 100644 index 00000000..65c177a1 --- /dev/null +++ b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md @@ -0,0 +1,383 @@ +# Plugin Rule Manager — Rule Template Guide + +Use this guide to fill in a rule template and pass it to `/thunder-plugin-rule-manager`. +A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, +validates it, and updates all four files in one shot: + +- `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` +- `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` +- `ThunderTools/PluginQA/README.md` +- `ThunderTools/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` + +--- + +## Which template should I use? + +Use this table to pick the right template. Both follow the same semantic-review methodology. + +| Question | Template A | Template B | +|---|---|---| +| Does the rule focus on a specific function or declaration? | Yes | No | +| Does the rule check behavior across multiple parts of the plugin? | No | Yes | +| Does it involve thread safety, resource cleanup flow, or security? | No | Yes | + +**Mostly Template A answers -> use Template A (Phase Checkpoint).** +**Any Template B answer -> use Template B (Holistic Rule).** + +If your rule could go either way, the manager will offer you both options and ask you to confirm. + +--- + +## How to use this template + +1. Copy the appropriate blank template below into a new `.md` file +2. Set `Action` to `Add`, `Update`, or `Remove` +3. Fill in the fields that apply to your action (see field reference below) +4. Attach the file — or paste its contents — when you open `/thunder-plugin-rule-manager` +5. The manager confirms what it parsed before making any changes + +--- + +## Template A — Phase Checkpoint Rule + +Phase checkpoint rules are organized under phase sections (rule_01 to rule_39). + +### Blank template + +```markdown +## Plugin Rule — Phase Checkpoint + +Action: Add + +Rule_ID: +Phase: Phase 4 — Lifecycle +Name: +Severity: violation + +### What This Rule Checks + + +### Extraction Target + + +### Yes/No Question + + +### Verification Steps +1. +2. +3. + +### Violation Pattern + + +### Fix +// WRONG: + +// CORRECT: + +### Conditional +No +Skip when: +``` + +--- + +## Template B — Holistic Rule + +Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_40 to rule_79). + +### Blank template + +```markdown +## Plugin Rule — Holistic + +Action: Add + +Rule_ID: +Category: concurrency +Name: +Severity: violation + +### What This Rule Checks + + +### Review Question + + +### Review Method + + +### Evidence Requirement + +``` + +--- + +## Field Reference + +### `Action` *(required — must be the first field)* + +| Value | When to use | +|---|---| +| `Add` | Creating a new rule that does not yet exist | +| `Update` | Changing one or more fields on an existing rule | +| `Remove` | Deleting a rule entirely | + +--- + +### `Rule_ID` *(required for Update/Remove; optional for Add)* + +Sequential identifier in the format `rule_XX` (e.g. `rule_17`, `rule_45`). + +- **Add** — leave blank to auto-assign the next available number, or specify your own +- **Update/Remove** — mandatory; this is how the manager finds the rule + +Current rule ranges: +- rule_01 to rule_39: Phase checkpoints +- rule_40 to rule_79: Holistic rules + +--- + +### `Phase` *(required for Add phase checkpoint; not used for holistic rules)* + +Pick exactly one: + +``` +Phase 1 — Module Structure (currently: rule_01 to rule_03) +Phase 2 — Code Style (currently: rule_04 to rule_13) +Phase 3 — Class Registration (currently: rule_14 to rule_16) +Phase 4 — Lifecycle (currently: rule_17 to rule_28) +Phase 5 — Implementation (currently: rule_29 to rule_32) +Phase 5C — Out-of-Process (currently: rule_33 to rule_34) +Phase 6 — Configuration (currently: rule_35 to rule_37) +Phase 7 — CMake (currently: rule_38) +Phase 8 — COM Interface Rules (currently: rule_39) +``` + +--- + +### `Category` *(required for Add holistic rule; not used for phase checkpoints)* + +| Category | What it covers | +|---|---| +| `conventions` | Code style, naming, headers, pragmas | +| `lifecycle_integrity` | State management, cleanup, Deinitialize correctness | +| `concurrency` | Thread safety, locking, re-entrancy | +| `com_safety` | COM reference counting, AddRef/Release balance | +| `resource_management` | RAII, memory, file descriptors, bounded containers | +| `jsonrpc_compliance` | JSON-RPC handler correctness, input validation | +| `inter_plugin_design` | Plugin dependencies, registration order | +| `code_quality_security` | Security, ASSERT usage, error propagation | + +--- + +### `Name` *(required for Add; optional for Update)* + +Short descriptive title in Title Case. 2-5 words. + +**Good:** `Observer Cleanup In Deinitialize`, `Handlers Must Not Block` +**Avoid:** `Check Initialize`, `COM Rule` + +--- + +### `Severity` *(required for Add; optional for Update)* + +| Value | Meaning | Report symbol | +|---|---|---| +| `violation` | Must fix — causes bugs, crashes, or codegen failures | VIOLATION | +| `warning` | Should fix — best practice with real risk | WARNING | +| `suggestion` | Optional — style or consistency improvement | SUGGESTION | + +--- + +### Phase Checkpoint-specific fields + +- **Extraction Target** — exact function/section/lines to read (e.g. "Initialize() method body") +- **Yes/No Question** — single binary question; Yes = PASS, No = VIOLATION +- **Verification Steps** — 3-8 numbered steps using semantic reasoning (never regex) +- **Violation Pattern** — one-line citation summary, under 10 words +- **Fix** — `// WRONG:` and `// CORRECT:` code snippets +- **Conditional** — `Yes` or `No`; if Yes, include `Skip when:` condition + +### Holistic Rule-specific fields + +- **Review Question** — semantic question spanning multiple code paths +- **Review Method** — must describe reading full code context, not pattern matching +- **Evidence Requirement** — must require exact `[File:line]` citation + +--- + +## What to fill per action + +| Field | Add (Phase) | Add (Holistic) | Update | Remove | +|---|---|---|---|---| +| `Action` | Required | Required | Required | Required | +| `Rule_ID` | Optional | Optional | Required | Required | +| `Phase` | Required | — | Only if changing | — | +| `Category` | — | Required | Only if changing | — | +| `Name` | Required | Required | Only if changing | — | +| `Severity` | Required | Required | Only if changing | — | +| Content sections | All | All | Only changed ones | — | + +**Update rule:** fill only the fields you want to change — leave everything else blank. + +--- + +## Examples + +### Add — Phase Checkpoint rule + +```markdown +## Plugin Rule — Phase Checkpoint + +Action: Add + +Rule_ID: +Phase: Phase 4 — Lifecycle +Name: Observer Cleanup In Deinitialize +Severity: violation + +### What This Rule Checks +When a plugin calls Register() in Initialize() to attach an observer, it must call +Unregister() with the same observer in Deinitialize(). Failing to do so leaves a +dangling observer pointer causing a crash on the next event dispatch. + +### Extraction Target +Deinitialize() method body and Initialize() method body + +### Yes/No Question +Is Unregister() called in Deinitialize() for every observer registered in Initialize()? + +### Verification Steps +1. Read Initialize() and collect all Register() call arguments +2. If no Register() calls are found -> SKIP +3. Read Deinitialize() and collect all Unregister() call arguments +4. For each observer registered in step 1, check if a matching Unregister() exists +5. If any observer is missing its Unregister() -> VIOLATION +6. Otherwise -> PASS + +### Violation Pattern +Observer registered but not unregistered in Deinitialize() + +### Fix +// WRONG: +void MyPlugin::Deinitialize(PluginHost::IShell* service) +{ + _service->Release(); + _service = nullptr; +} + +// CORRECT: +void MyPlugin::Deinitialize(PluginHost::IShell* service) +{ + service->Unregister(&_notification); + _service->Release(); + _service = nullptr; +} + +### Conditional +Yes +Skip when: no Register() calls exist in Initialize() +``` + +--- + +### Add — Holistic Rule + +```markdown +## Plugin Rule — Holistic + +Action: Add + +Rule_ID: +Category: concurrency +Name: Shared State Protected by CriticalSection +Severity: violation + +### What This Rule Checks +Any member variable accessed from multiple threads (JSON-RPC handlers, +notification callbacks, WorkerPool jobs) must be protected by Core::CriticalSection. +Unprotected concurrent access causes data races and undefined behaviour. + +### Review Question +Is every shared member variable protected by Core::CriticalSection on all access paths? + +### Review Method +Read the full plugin class declaration to identify member variables. For each variable, +trace all access points across Initialize, Deinitialize, JSON-RPC handlers, notification +callbacks, and WorkerPool jobs. Verify each access path acquires _adminLock or equivalent. +Do not use pattern-only checks — reason about actual control flow and thread boundaries. + +### Evidence Requirement +Provide exact [File:line] citation for each unprotected shared member access, +including which threads can reach that code path concurrently. +``` + +--- + +### Update — partial (change severity and question only) + +```markdown +## Plugin Rule — Phase Checkpoint + +Action: Update + +Rule_ID: rule_06 + +Severity: violation + +### Yes/No Question +Is nullptr used in place of NULL everywhere in the plugin source files? +``` + +--- + +### Remove + +```markdown +## Plugin Rule — Phase Checkpoint + +Action: Remove + +Rule_ID: rule_06 +``` + +The manager confirms the rule name and phase before deleting. + +--- + +## Common mistakes + +| Mistake | Problem | Fix | +|---|---|---| +| Yes/No Question is open-ended | Cannot produce binary PASS/FAIL | Rewrite as: "Is X present immediately after Y?" | +| Verification Steps describe text search | Steps must reason semantically | Write "read and determine" not "search for" | +| Violation Pattern left empty | Citation has no summary | Always fill — under 10 words | +| `Conditional: Yes` without `Skip when:` | No condition to evaluate | Always complete `Skip when:` | +| Wrong `Rule_ID` on Update/Remove | Manager reports "not found" | Verify ID in YAML before submitting | +| All fields filled on Update | Unintended overwrites | Fill only changed fields | +| Phase rule requires cross-file reasoning | Should be Holistic | Use Template B | +| Holistic rule reducible to one yes/no | Should be Phase Checkpoint | Use Template A | + +--- + +## Quick reference + +``` +PHASE CHECKPOINT (Template A) HOLISTIC RULE (Template B) + +Action: Add | Update | Remove Action: Add | Update | Remove +Rule_ID: e.g. rule_17 Rule_ID: e.g. rule_52 +Phase: Phase 4 — Lifecycle Category: concurrency +Name: Title Case, 2-5 words Name: Title Case, 2-5 words +Severity: violation|warning|suggestion Severity: violation|warning|suggestion + +### What This Rule Checks ### What This Rule Checks +### Extraction Target ### Review Question +### Yes/No Question ### Review Method +### Verification Steps ### Evidence Requirement +### Violation Pattern +### Fix +### Conditional +``` \ No newline at end of file diff --git a/PluginQualityAdvisor/rules/thunder-interface-rules.yaml b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml new file mode 100644 index 00000000..8c8e3b28 --- /dev/null +++ b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml @@ -0,0 +1,616 @@ +title: "Thunder Interface Validation Rules" +description: | + Rules for validating Thunder COM interface headers in ThunderInterfaces/interfaces/. + Every rule uses semantic reasoning — read the interface header in full and reason + about the code as a human reviewer. Never use regex or text search as the primary + detection method. + +# =========================================================================================== +# CORE RULES (15) — severity: violation for all +# =========================================================================================== + +core_rules: + - id: "core_1_1" + name: "File and Namespace Structure" + severity: "violation" + description: | + Thunder interface headers must follow the standard file and namespace structure: + - File must be in ThunderInterfaces/interfaces/ (or qa_interfaces/ for QA) + - Interface must be declared inside the WPEFramework::Exchange namespace + - File name must match the interface name: IFoo.h for struct EXTERNAL IFoo + - No implementation code in interface headers — pure declarations only + extraction_logic: | + 1. Read the full interface header file + 2. Identify the namespace block(s) and their names + 3. Note the file name and the primary interface struct name + 4. Check for any non-declaration code (function implementations, static variables, etc.) + verification_logic: | + 1. Verify the file is inside the WPEFramework::Exchange namespace + 2. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) + 3. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations + 4. If any of these conditions fail → VIOLATION + violation_pattern: "Interface not in WPEFramework::Exchange namespace, file name mismatch, or implementation code present" + fix_template: | + // WRONG: + namespace WPEFramework { + // Missing Exchange namespace + struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; + } + + // Correct: + namespace WPEFramework { + namespace Exchange { + struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; + } + } + citation: | + ThunderInterfaces/interfaces/IDictionary.h — Exchange namespace and file naming + + - id: "core_2_1" + name: "Interface Declaration Shape" + severity: "violation" + description: | + Thunder COM interfaces must follow the correct declaration shape: + - Must be a struct (not class) with the EXTERNAL macro + - Must inherit virtually from Core::IUnknown (or from another Thunder interface) + - Must declare a nested enum with the ID value (RPC::ID_*) + - All methods must be pure virtual + extraction_logic: | + 1. Read the interface struct declaration + 2. Examine the struct keyword, EXTERNAL macro, and inheritance list + 3. Check for the nested enum { ID = RPC::ID_* } + 4. Examine all method declarations for pure virtual (= 0) + verification_logic: | + 1. Verify the declaration uses 'struct EXTERNAL IName' + 2. Verify inheritance is 'virtual public Core::IUnknown' or a Thunder interface + 3. Verify a nested enum contains ID = RPC::ID_* + 4. Verify all methods are pure virtual (= 0) + 5. If any fails → VIOLATION + violation_pattern: "Interface missing EXTERNAL macro, wrong inheritance, missing ID enum, or non-pure-virtual methods" + fix_template: | + // WRONG: + class IDictionary : public Core::IUnknown { + enum { ID = 0x100 }; // ← raw value, not RPC::ID_* + virtual void Get(const string& key) { } // ← not pure virtual + + // Correct: + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY }; + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + }; + citation: | + ThunderInterfaces/interfaces/IDictionary.h:LINE — struct EXTERNAL shape + + - id: "core_3_1" + name: "Interface ID Registration" + severity: "violation" + description: | + Every Thunder COM interface must have a unique numeric ID registered in + RPC::IDs (in ThunderInterfaces/interfaces/ids.h or equivalent). + The ID enum value in the struct must reference the registered RPC::ID_* constant. + Sub-interfaces (INotification, ICallback nested in a parent) must also have their + own unique IDs. + extraction_logic: | + 1. Read the interface struct declaration and note its ID value + 2. Check whether the ID value uses an RPC::ID_* constant + 3. Check ids.h (or the ID registration file) for the corresponding entry + verification_logic: | + 1. Verify the struct enum { ID = RPC::ID_* } uses a named constant, not a raw number + 2. Verify the ID is registered in ThunderInterfaces ids.h (or equivalent) + 3. Verify the ID is unique — no other interface uses the same value + 4. Nested INotification and ICallback interfaces must also have their own IDs + 5. If any condition fails → VIOLATION + violation_pattern: "Interface ID missing from IDs registration, uses raw number, or is not unique" + fix_template: | + // WRONG: + enum { ID = 0x100 }; // ← raw number, not registered + + // Correct: + enum { ID = RPC::ID_DICTIONARY }; + // AND in ids.h: + // ID_DICTIONARY = RPC_ID_OFFSET + N, + citation: | + ThunderInterfaces/interfaces/ids.h — ID_DICTIONARY registration + + - id: "core_4_1" + name: "Pure Virtual Methods Only" + severity: "violation" + description: | + Thunder COM interface methods must be pure virtual (= 0). No default + implementations, no inline code, no static methods, no non-virtual methods. + The interface is a pure abstract contract — all implementation is in the + implementing class, not in the interface. + extraction_logic: | + 1. Read all method declarations in the interface struct + 2. Check each method for the = 0 specifier + 3. Look for any inline code, default implementations, or static methods + verification_logic: | + 1. Every method in the interface must end with = 0 + 2. No method may have a body (even {}) + 3. No static methods allowed in interfaces + 4. No non-virtual methods (constructors, operators excepted) + 5. If any violation → VIOLATION + violation_pattern: "Non-pure-virtual method, inline implementation, or static method in COM interface" + fix_template: | + // WRONG: + virtual Core::hresult Get(const string& key, string& value) { + value = "default"; // ← inline implementation + return Core::ERROR_NONE; + } + + // Correct: + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — pure virtual methods + + - id: "core_5_1" + name: "Return Type Conventions" + severity: "violation" + description: | + In Thunder 5.0+, all COM interface methods annotated with @json (i.e. methods + that generate JSON-RPC code) MUST return Core::hresult. Void return types and + other return types are not allowed for JSON-RPC-generating methods. + Methods in pure COM interfaces (no @json) should also use Core::hresult for + error reporting where applicable. + extraction_logic: | + 1. Read all interface method declarations + 2. Note which methods are under a @json-tagged interface or have @json annotations + 3. Check the return type of each method + verification_logic: | + 1. For interfaces tagged with @json (or in Thunder 5.0+ contexts), all methods must return Core::hresult + 2. Void return types are not allowed for JSON-RPC methods + 3. Custom return types (not Core::hresult) for non-status purposes are not allowed — use @out parameters instead + 4. If any JSON-RPC method lacks Core::hresult return type → VIOLATION + violation_pattern: "Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+" + fix_template: | + // WRONG: + virtual void SetVolume(const uint8_t volume) = 0; + virtual string GetStatus() = 0; + + // Correct: + virtual Core::hresult SetVolume(const uint8_t volume) = 0; + virtual Core::hresult GetStatus(string& status /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IAudioOutput.h — Core::hresult return types + + - id: "core_6_1" + name: "Const Correctness" + severity: "violation" + description: | + Interface methods must use const correctly: + - Input-only parameters should be const (const string& key) + - Output parameters should be non-const references (string& value) + - Methods that do not modify the object should be const (though pure virtual const is rare) + - @out parameters must be non-const references to allow the implementation to write + extraction_logic: | + 1. Read all method parameter declarations + 2. Examine const qualifiers on each parameter + 3. Identify parameters marked @out and verify they are non-const references + 4. Identify input parameters and verify they are const where appropriate + verification_logic: | + 1. @out parameters must be non-const references (string& value, not const string& value) + 2. Input parameters that are passed by value or const ref are correct + 3. Non-const reference parameters without @out indicate potential API design issues + 4. If @out parameters are const (preventing write) → VIOLATION + violation_pattern: "@out parameter declared const preventing the implementation from writing the output value" + fix_template: | + // WRONG: + virtual Core::hresult Get(const string& key, const string& value /* @out */) = 0; + // ^^^^^ ← const prevents writing + + // Correct: + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — const correctness on @out params + + - id: "core_9_1" + name: "Thunder Type Conventions" + severity: "violation" + description: | + Thunder interfaces must use Thunder type aliases, not std:: types directly: + - string (not std::string) for text + - Core::hresult (not HRESULT or int) for error returns + - uint8_t, uint16_t, uint32_t, uint64_t (not int, long, short) + - bool (acceptable) but not BOOL + Using std::string in interfaces breaks cross-ABI compatibility. + extraction_logic: | + 1. Read all method parameter types and return types in the interface + 2. Identify any std:: type usage (std::string, std::vector without @restrict review) + 3. Check for non-Thunder integer types (int, long, short, BOOL, HRESULT) + verification_logic: | + 1. std::string in interface parameters → VIOLATION (use string) + 2. HRESULT or raw int for error codes → VIOLATION (use Core::hresult) + 3. Non-width-specific integer types (int, long) for interface params → check core_16_1 + 4. BOOL → VIOLATION (use bool) + 5. If std::string found → VIOLATION + violation_pattern: "std::string used in interface — must use Thunder string type alias" + fix_template: | + // WRONG: + virtual Core::hresult Get(const std::string& key, std::string& value) = 0; + + // Correct: + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — Thunder string type alias + + - id: "core_10_1" + name: "Register/Unregister Patterns" + severity: "violation" + description: | + Notification interfaces follow two patterns: + - INotification (1:many observer): Register(INotification*) and Unregister(INotification*) + for multiple simultaneous subscribers + - ICallback (1:1 callback): Callback(ICallback*) sets a single callback (nullptr to clear) + Register/Unregister must take a pointer to the notification interface. + ICallback::Callback() replaces the previous callback, so no Unregister needed. + extraction_logic: | + 1. Read the interface for Register/Unregister/Callback method declarations + 2. Identify whether it follows the INotification (1:many) or ICallback (1:1) pattern + 3. Check the method signatures + verification_logic: | + 1. INotification pattern: must have both Register(INotification*) and Unregister(INotification*) + 2. ICallback pattern: must have Callback(ICallback*) accepting nullptr to clear + 3. Register without matching Unregister → VIOLATION + 4. If notification registration pattern is non-standard → VIOLATION + violation_pattern: "Register(INotification*) present but Unregister(INotification*) missing, or non-standard notification pattern" + fix_template: | + // WRONG: (Unregister missing) + virtual Core::hresult Register(INotification* notification) = 0; + // Missing: Unregister + + // Correct — INotification (1:many): + virtual Core::hresult Register(INotification* notification) = 0; + virtual Core::hresult Unregister(const INotification* notification) = 0; + + // Correct — ICallback (1:1, nullptr clears): + virtual Core::hresult Callback(ICallback* callback) = 0; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — Register/Unregister pattern + + - id: "core_11_1" + name: "Nested Event Interfaces" + severity: "violation" + description: | + Event/notification interfaces nested inside a parent COM interface must: + - Have the @event tag (// @event) above the struct declaration + - Use EXTERNAL in the struct declaration + - Have their own unique ID in the RPC ID list + - Inherit from Core::IUnknown (not from the parent interface) + Missing @event prevents the code generator from emitting event dispatch code. + extraction_logic: | + 1. Read the interface for any nested struct declarations + 2. For each nested struct that represents a notification/event (INotification, ICallback, etc.) + 3. Check for the @event comment tag above the declaration + 4. Check for EXTERNAL in the declaration + 5. Check for a nested enum { ID = RPC::ID_* } + verification_logic: | + 1. Every nested event/notification interface must have // @event immediately above the struct + 2. Must use EXTERNAL in the declaration + 3. Must have its own ID in the RPC ID list + 4. Must inherit from Core::IUnknown + 5. If any condition fails → VIOLATION + violation_pattern: "@event tag missing on nested notification interface, or missing EXTERNAL/ID" + fix_template: | + // WRONG: + struct INotification : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY_NOTIFICATION }; // ← @event tag missing + + // Correct: + // @event + struct EXTERNAL INotification : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY_NOTIFICATION }; + virtual void ValueChanged(const string& key, const string& value) = 0; + }; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — @event tag on INotification + + - id: "core_12_1" + name: "@json Tag (CRITICAL)" + severity: "violation" + description: | + Without the @json tag, ZERO JSON-RPC code is generated for the interface. + This is the most common critical omission. The tag must appear immediately + above the struct declaration as: // @json 1.0.0 + No blank lines are allowed between the @json tag and the struct declaration. + If an interface intentionally does not need JSON-RPC (pure COM only), the + absence of @json is acceptable — but this must be an intentional design choice. + extraction_logic: | + 1. Read the lines immediately above the 'struct EXTERNAL I...' declaration + 2. Look for a // @json comment with a version number + 3. Check there are no blank lines between the tag and the struct declaration + verification_logic: | + 1. If the interface is intended to generate JSON-RPC code: verify // @json N.N.N appears immediately above the struct declaration + 2. No blank lines between the @json tag and the struct line + 3. If the interface is intentionally JSON-RPC-free (pure COM) → acceptable, note as design choice + 4. If @json is missing from an interface that should have it → VIOLATION + violation_pattern: "@json tag missing above interface struct declaration — no JSON-RPC code will be generated" + fix_template: | + // WRONG: + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + // ← @json tag missing — no RPC code will be generated + + // Correct: + // @json 1.0.0 + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + citation: | + ThunderInterfaces/interfaces/IDictionary.h:LINE — @json 1.0.0 above struct + + - id: "core_13_1" + name: "Binary Compatibility" + severity: "violation" + description: | + Thunder COM interfaces are binary interfaces — once released, they cannot be + changed in backward-incompatible ways without breaking all compiled clients: + - Methods must not be reordered (vtable order is fixed) + - Methods must not be removed (creates vtable holes) + - Method signatures must not change (parameter types/count/order) + - New methods must be added at the END of the interface + Binary-incompatible changes require creating a new interface version (IFoo2). + extraction_logic: | + 1. Read the interface method declarations in order + 2. If comparing against a previous version: note any additions, removals, or reorderings + 3. For new interfaces: verify method ordering follows a logical grouping with extensibility in mind + verification_logic: | + 1. When reviewing against a baseline: check for removed methods, reordered methods, or changed signatures + 2. New methods in a released interface must be at the end + 3. If a released interface has been structurally modified → VIOLATION + 4. For new (unreleased) interfaces: recommend logical method ordering for future extensibility + violation_pattern: "Released interface has methods removed, reordered, or signatures changed — breaks binary compatibility" + fix_template: | + // WRONG: (removed a method or changed signature in a released interface) + // v1: virtual Core::hresult Get(const string& key, string& value) = 0; + // v2: virtual Core::hresult Get(const string& key, string& value, bool caseSensitive) = 0; + // ^^^^^^^^^^^^^^^^^ ← changed signature + + // Correct: create a new interface version + struct EXTERNAL IDictionary2 : virtual public IDictionary { + enum { ID = RPC::ID_DICTIONARY2 }; + virtual Core::hresult GetCaseSensitive(const string& key, const bool caseSensitive, string& value /* @out */) = 0; + }; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — binary compatibility + + - id: "core_14_1" + name: "No AddRef/Release Redeclaration" + severity: "violation" + description: | + AddRef() and Release() are inherited from Core::IUnknown and must NOT be + redeclared in interface structs. Redeclaring them creates a separate vtable + entry, breaking COM reference counting and causing crashes. + extraction_logic: | + 1. Read all method declarations in the interface struct (including nested structs) + 2. Look for any AddRef() or Release() declarations + verification_logic: | + 1. The interface must not declare AddRef() or Release() + 2. These methods are inherited from Core::IUnknown via the virtual inheritance chain + 3. If AddRef() or Release() appears as an interface method → VIOLATION + violation_pattern: "AddRef() or Release() redeclared in interface — must be inherited from Core::IUnknown only" + fix_template: | + // WRONG: + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE + virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual Core::hresult Get(const string& key, string& value) = 0; + }; + + // Correct: + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + enum { ID = RPC::ID_DICTIONARY }; + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + }; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — AddRef/Release inherited from Core::IUnknown + + - id: "core_15_1" + name: "No std::map in Interfaces" + severity: "violation" + description: | + std::map (and other std:: associative containers) must not appear as interface + method parameters or return types. std::map is not serialisable across process + boundaries via Thunder's RPC mechanism and causes code generation failures. + Use structured types, repeated calls, or JSON containers instead. + extraction_logic: | + 1. Read all method parameter types in the interface + 2. Look for std::map, std::unordered_map, std::multimap, or similar associative containers + verification_logic: | + 1. Any std::map or similar associative container in method parameters → VIOLATION + 2. Consider whether a Core::JSON::Container or repeated method calls can replace it + 3. If std::map found in interface → VIOLATION + violation_pattern: "std::map used in interface parameter — not serialisable across process boundaries" + fix_template: | + // WRONG: + virtual Core::hresult GetAll(std::map& values /* @out */) = 0; + + // Correct: use repeated queries or a structured type + virtual Core::hresult GetKeys(RPC::IStringIterator*& keys /* @out */) = 0; + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IDictionary.h — no std::map in interfaces + + - id: "core_16_1" + name: "Explicit Integer Widths" + severity: "violation" + description: | + Interface method parameters and return values must use explicit-width integer + types (uint8_t, uint16_t, uint32_t, uint64_t, int32_t, etc.) rather than + platform-dependent types (int, long, short, size_t, unsigned int). + Implicit-width types change size between platforms and break binary compatibility. + extraction_logic: | + 1. Read all method parameter types and return types + 2. Identify any integer parameters using platform-dependent types + verification_logic: | + 1. int, long, short, unsigned int, unsigned long, size_t in interface parameters → VIOLATION + 2. char is acceptable for character data; bool is acceptable + 3. uint8_t, uint16_t, uint32_t, uint64_t, int32_t etc. are correct + 4. If platform-dependent integer type found → VIOLATION + violation_pattern: "Platform-dependent integer type (int, long, short) used in interface — must use explicit-width types (uint32_t, etc.)" + fix_template: | + // WRONG: + virtual Core::hresult SetTimeout(const int timeout) = 0; + virtual Core::hresult GetSize(unsigned int& size /* @out */) = 0; + + // Correct: + virtual Core::hresult SetTimeout(const uint32_t timeout) = 0; + virtual Core::hresult GetSize(uint32_t& size /* @out */) = 0; + citation: | + ThunderInterfaces/interfaces/IVolume.h — explicit integer widths + + - id: "core_17_1" + name: "@restrict Mandatory with std::vector" + severity: "violation" + description: | + Every interface method parameter of type std::vector (or Core::JSON::ArrayType equivalent) + MUST be annotated with /* @restrict:N */ where N is the maximum allowed element count. + Without @restrict, the code generator cannot produce safe bounds-checking code and may + generate unbounded deserialization that is exploitable. + Note: This rule specifically applies to std::vector. Non-vector parameters with + optional constraints are covered by advisory_m5_1. + extraction_logic: | + 1. Read all method parameter declarations + 2. Identify any parameters of type std::vector + 3. Check for the @restrict comment annotation on each vector parameter + verification_logic: | + 1. Every std::vector parameter must have /* @restrict:N */ annotation + 2. N must be a positive integer representing the maximum element count + 3. Missing @restrict on std::vector → VIOLATION + 4. @restrict on non-vector types is advisory (see advisory_m5_1) + violation_pattern: "std::vector parameter missing @restrict annotation — required for safe bounds checking" + fix_template: | + // WRONG: + virtual Core::hresult GetItems(std::vector& items /* @out */) = 0; + + // Correct: + virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; + citation: | + ThunderInterfaces/interfaces/IPackager.h — @restrict on std::vector + +# =========================================================================================== +# ADVISORY RULES (4) +# =========================================================================================== + +advisory_rules: + - id: "advisory_m1_1" + name: "Single Responsibility Principle" + severity: "violation" + description: | + Each COM interface should have a single, clearly defined responsibility. + An interface that mixes unrelated concerns (e.g. audio control + network management) + is harder to implement, test, and maintain. If an interface is doing too many + unrelated things, it should be split into multiple focused interfaces. + extraction_logic: | + 1. Read the full interface and identify all the method groups + 2. Reason about whether all methods serve a single coherent purpose + 3. Look for method groups that could logically belong to separate interfaces + verification_logic: | + 1. Reason about the interface's overall purpose from its name and method set + 2. If methods clearly belong to two or more distinct responsibilities → VIOLATION + 3. Minor convenience methods on an otherwise focused interface are acceptable + 4. Apply judgment: is the mixing of concerns gratuitous or is there a clear design reason? + violation_pattern: "Interface mixes multiple unrelated responsibilities — consider splitting into focused interfaces" + fix_template: | + // WRONG: IDictionaryAndNetwork mixes dictionary and network concerns + struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value) = 0; + virtual Core::hresult GetNetworkStatus(string& status) = 0; // ← unrelated + }; + + // Correct: split into IDictionary and INetwork + citation: | + ThunderInterfaces/interfaces/IHdmiCecSink.h — single responsibility + + - id: "advisory_m2_1" + name: "Enum Underlying Types" + severity: "warning" + description: | + Enums used in interface parameters or return types should use explicit underlying + types (: uint8_t, : uint32_t) for ABI stability. + Exception: the anonymous ID enum inside the interface struct (enum { ID = RPC::ID_* }) + must NOT have an explicit underlying type — this is by Thunder convention. + Only named enums that are used as parameter types need explicit underlying types. + extraction_logic: | + 1. Read all enum declarations in the interface header + 2. Identify named enums used as method parameter types + 3. Identify the anonymous ID enum { ID = RPC::ID_* } + 4. Check for explicit underlying types on named enums + verification_logic: | + 1. Anonymous ID enum (enum { ID = RPC::ID_* }) must NOT have explicit type — skip it + 2. Named enums used as parameter types should have explicit underlying types + 3. If a named enum used as a parameter lacks an explicit underlying type → WARNING + 4. Apply judgment: if the enum range clearly fits a known type, it is advisable + violation_pattern: "Named enum used in interface parameter lacks explicit underlying type — consider adding : uint8_t or : uint32_t" + fix_template: | + // WRONG: (named enum without explicit type) + enum State { IDLE, ACTIVE, ERROR }; + virtual Core::hresult SetState(const State state) = 0; + + // Correct: + enum State : uint8_t { IDLE = 0, ACTIVE = 1, ERROR = 2 }; + virtual Core::hresult SetState(const State state) = 0; + + // The ID enum stays anonymous (by convention): + enum { ID = RPC::ID_MY_INTERFACE }; + citation: | + ThunderInterfaces/interfaces/IAVInput.h — explicit enum underlying type + + - id: "advisory_m3_1" + name: "No Exceptions" + severity: "violation" + description: | + Thunder COM interfaces and their implementations must not use C++ exceptions. + Exceptions cannot cross COM/RPC process boundaries safely. + All error conditions must be reported via Core::hresult return values. + Exception specifications (throw(...), noexcept) are irrelevant — the real + issue is that throw statements must not appear in COM implementation code. + extraction_logic: | + 1. Read all interface method signatures and any associated implementation hints + 2. Check for exception specifications or throw annotations + 3. If implementation files are accessible, check for throw statements + verification_logic: | + 1. No exception specifications that imply throws (throw(...)) on interface methods + 2. noexcept specifications are acceptable (they prevent exceptions from propagating) + 3. In implementation code: no throw statements in COM method implementations + 4. If throw appears in COM code → VIOLATION + violation_pattern: "Exception specification or throw statement in COM interface or implementation — use Core::hresult for error reporting" + fix_template: | + // WRONG: + virtual Core::hresult Get(const string& key, string& value) throw(std::exception) = 0; + + // Correct: + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + // Implementation: return Core::ERROR_NOT_FOUND; instead of throwing + citation: | + ThunderInterfaces/interfaces/IDictionary.h — no exceptions in COM interfaces + + - id: "advisory_m5_1" + name: "@restrict for Non-vector Params" + severity: "warning" + description: | + Parameters with natural upper bounds (strings with max length, integers with + max value, buffers with max size) may benefit from @restrict annotations to + communicate constraints to callers and enable bounds checking in generated code. + NOTE: This rule applies to non-std::vector parameters only. + std::vector parameters are covered by core_17_1 where @restrict is MANDATORY. + This rule is advisory for non-vector types where bounds are meaningful. + extraction_logic: | + 1. Read all method parameter declarations + 2. Skip std::vector parameters (those are covered by core_17_1) + 3. For remaining parameters, reason about whether they have natural upper bounds + 4. Check for @restrict annotations on constrained parameters + verification_logic: | + 1. Do NOT apply this rule to std::vector parameters — core_17_1 covers those + 2. For string parameters used as identifiers, keys, or names: consider whether a max length makes sense + 3. For integer count/size parameters: consider whether a max value is meaningful + 4. If a non-vector parameter clearly has a natural bound but lacks @restrict → WARNING (advisory) + 5. Apply judgment: not every parameter needs @restrict — only those with meaningful constraints + violation_pattern: "Non-vector parameter with natural upper bound lacks @restrict annotation (advisory)" + fix_template: | + // Without restriction (acceptable for many cases): + virtual Core::hresult SetName(const string& name) = 0; + + // With @restrict for bounded parameters: + virtual Core::hresult SetName(const string& name /* @restrict:64 */) = 0; + + // Note: for std::vector, @restrict is MANDATORY (see core_17_1): + virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; + citation: | + ThunderInterfaces/interfaces/IVolumeControl.h — @restrict on bounded params diff --git a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml new file mode 100644 index 00000000..1f752adc --- /dev/null +++ b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml @@ -0,0 +1,2059 @@ +# =========================================================================================== +# Thunder Plugin Rules +# =========================================================================================== + +metadata: + description: "Unified plugin validation rules — 79 rules numbered sequentially (rule_01 to rule_79), organized into 17 phases" + approach: "semantic code review" + total_rules: 79 + organization: | + rule_01-rule_03: Module Structure (3) + rule_04-rule_13: Code Style (10) + rule_14-rule_16: Class Registration (3) + rule_17-rule_28: Lifecycle (12) + rule_29-rule_32: Implementation (4) + rule_33-rule_34: Out-of-Process (2) + rule_35-rule_37: Configuration (3) + rule_38: CMake (1) + rule_39: COM Interface (1) + rule_40,41,49,77: Conventions & Encapsulation (4) + rule_44,45,46,57,70,79: Lifecycle & State Integrity (6) + rule_47,50-54,65-68: Concurrency & Threading (10) + rule_48,63,71: COM Reference & Memory Safety (3) + rule_55,56,69: Resource Management (3) + rule_58-62,74,78: JSON-RPC Compliance (7) + rule_64,76: Inter-Plugin & OOP Design (2) + rule_42,43,72,73,75: Code Quality & Security (5) + + validation_approach: + principles: + - "ALL 79 rules follow the same review philosophy: understand the whole plugin first, then check specifics" + - "Step 1 is ALWAYS: read the full plugin code (all files) and build mental model of architecture, ownership, lifecycle, and threading" + - "Step 2 is: with that full understanding, examine the specific concern each rule asks about" + - "Never check a specific block in isolation — always reason about it in the context of the full plugin" + - "If the developer's approach is correct in the broader context (even if it technically violates the rule's literal question), downgrade severity" + - "Never use regex or pattern matching — always semantic understanding" + - "Severity is never escalated above the YAML-defined level" + workflow: + - "Step 1 UNDERSTAND: Read ALL plugin source files. Build a mental model of the plugin's architecture — its lifecycle, threading model, ownership patterns, and data flow" + - "Step 2 FOCUS: For the specific rule, examine the relevant code block WITH the full context already understood" + - "Step 3 REASON: Ask the rule's question. Reason about the answer using the full plugin context — not just the extracted block in isolation" + - "Step 4 JUDGE: If the answer suggests a violation, ask: 'Is the developer's actual approach correct and safe given the full context I already understand?' If yes → downgrade. If genuinely wrong → cite it" + - "Step 5 CITE: On violation, give exact file and line (e.g. [Dictionary.cpp:108])" + - "Step 6 FIX: Show the corrected code block, not the whole file" + - "Step 4 CITE: On violation, give exact file and line (e.g. [Dictionary.cpp:108])" + - "Step 5 FIX: Show the corrected code block, not the whole file" + +# =========================================================================================== +# PHASE CHECKPOINTS (39 rules) +# =========================================================================================== + +# ------------------------------------------------------------------------------------------- +# Phase 1: Module Structure +# ------------------------------------------------------------------------------------------- + +phase_1_checkpoints: + - rule_id: "rule_01" + name: "MODULE_NAME Plugin_ Prefix" + severity: "suggestion" + phase: "module_structure" + + extraction: + target: "#define MODULE_NAME in Module.h" + method: "Read Module.h and locate the #define MODULE_NAME line" + code_block: "The #define MODULE_NAME ... line from Module.h" + + bounded_query: + question: "Does the MODULE_NAME value start with the Plugin_ prefix (e.g. Plugin_Dictionary)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Module.h and find the #define MODULE_NAME line" + - "2. Examine the value assigned to MODULE_NAME" + - "3. Reason whether the value starts with Plugin_ as a prefix" + - "4. If the value does not start with Plugin_ → SUGGESTION" + + conditional: false + skip_condition: null + + violation_pattern: "MODULE_NAME does not start with Plugin_ prefix" + + fix_template: | + // WRONG: + #define MODULE_NAME Dictionary + + // Correct: + #define MODULE_NAME Plugin_Dictionary + + citation: + line_format: "[Module.h:LINE] MODULE_NAME value does not use Plugin_ prefix" + rule: "thunder-plugin-rules.yaml / rule_01" + + - rule_id: "rule_02" + name: "MODULE_NAME_DECLARATION" + severity: "violation" + phase: "module_structure" + + extraction: + target: "MODULE_NAME_DECLARATION(BUILD_REFERENCE) call in Module.cpp" + method: "Read Module.cpp and locate the MODULE_NAME_DECLARATION macro call" + code_block: "The MODULE_NAME_DECLARATION line in Module.cpp" + + bounded_query: + question: "Does Module.cpp contain MODULE_NAME_DECLARATION(BUILD_REFERENCE)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Module.cpp in full" + - "2. Look for the MODULE_NAME_DECLARATION macro invocation" + - "3. Verify the argument is BUILD_REFERENCE (not empty, not a hardcoded string)" + - "4. If the macro is absent → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "MODULE_NAME_DECLARATION(BUILD_REFERENCE) missing from Module.cpp" + + fix_template: | + // WRONG: (missing or using wrong argument) + // MODULE_NAME_DECLARATION(BUILD_REFERENCE) ← not present + + // Correct: + MODULE_NAME_DECLARATION(BUILD_REFERENCE) + + citation: + line_format: "[Module.cpp:LINE] MODULE_NAME_DECLARATION(BUILD_REFERENCE) not found" + rule: "thunder-plugin-rules.yaml / rule_02" + + - rule_id: "rule_03" + name: "Module.h Uses #pragma once" + severity: "warning" + phase: "module_structure" + + extraction: + target: "Include guard at top of Module.h" + method: "Read the first 10 lines of Module.h and examine the include guard approach" + code_block: "The header guard lines at the top of Module.h" + + bounded_query: + question: "Does Module.h use #pragma once instead of a legacy #ifndef/#define/#endif guard?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the first 10 lines of Module.h" + - "2. Determine whether the include guard is #pragma once or legacy #ifndef style" + - "3. If legacy #ifndef guard is used instead of #pragma once → WARNING" + + conditional: false + skip_condition: null + + violation_pattern: "Module.h uses legacy #ifndef include guard instead of #pragma once" + + fix_template: | + // WRONG: + #ifndef __MODULE_H + #define __MODULE_H + // ... header content ... + #endif + + // Correct: + #pragma once + // ... header content ... + + citation: + line_format: "[Module.h:LINE] Legacy #ifndef guard — use #pragma once" + rule: "thunder-plugin-rules.yaml / rule_03" + +# ------------------------------------------------------------------------------------------- +# Phase 2: Code Style +# ------------------------------------------------------------------------------------------- + +phase_2_checkpoints: + - rule_id: "rule_04" + name: "VARIABLE_IS_NOT_USED Accuracy" + severity: "violation" + phase: "code_style" + + extraction: + target: "All function signatures and bodies where VARIABLE_IS_NOT_USED annotation appears" + method: "Read each function in full — signature plus body — and reason about whether annotated parameters are actually used in the body" + code_block: "The complete function body for each function that uses VARIABLE_IS_NOT_USED" + + bounded_query: + question: "Are all parameters annotated with VARIABLE_IS_NOT_USED (or suppressed via the macro call in the body) genuinely unused in the function body?" + expected_answer: "Yes" + + verification_logic: + - "1. Read each function that uses VARIABLE_IS_NOT_USED — in any form: inline annotation in signature or macro call in body" + - "2. For each such parameter, read the complete function body and reason semantically about whether that parameter is referenced" + - "3. Understand Thunder's macro forms: VARIABLE_IS_NOT_USED(param) in the body and /* VARIABLE_IS_NOT_USED */ inline in the parameter list" + - "4. A parameter is 'used' if it appears in any expression in the function body that affects behavior" + - "5. If any annotated parameter IS actually used in the body → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "VARIABLE_IS_NOT_USED applied to a parameter that is actually used in the function body" + + fix_template: | + // WRONG: + void Callback(VARIABLE_IS_NOT_USED const string& name, const uint32_t value) { + ProcessValue(name, value); // ← 'name' IS used but annotated as unused + } + + // Correct: + void Callback(const string& name, const uint32_t value) { + ProcessValue(name, value); + } + + citation: + line_format: "[PluginName.cpp:LINE] VARIABLE_IS_NOT_USED on parameter that is actually used" + rule: "thunder-plugin-rules.yaml / rule_04" + + - rule_id: "rule_05" + name: "Error Code Preservation" + severity: "violation" + phase: "code_style" + + extraction: + target: "Function bodies that conditionally set an error code variable" + method: "Read each function body in full and reason about control flow — identify where an error code is conditionally assigned and then check whether it is subsequently unconditionally overwritten" + code_block: "The function body where conditional error code assignment occurs" + + bounded_query: + question: "In every function that conditionally sets an error code, is the error code preserved until the end — never unconditionally overwritten with ERROR_NONE or SUCCESS after being conditionally set?" + expected_answer: "Yes" + + verification_logic: + - "1. Read each function body that contains an error code variable (result, errorCode, ret, etc.)" + - "2. Reason about the control flow: where is the error code assigned conditionally (inside an if block)?" + - "3. Check whether the same variable is later unconditionally assigned a success/none value (e.g. result = Core::ERROR_NONE; outside any branch)" + - "4. If a conditional error assignment is followed by an unconditional ERROR_NONE assignment → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Error code conditionally set but then unconditionally overwritten with ERROR_NONE/SUCCESS" + + fix_template: | + // WRONG: + uint32_t result = Core::ERROR_NONE; + if (condition) { + result = Core::ERROR_GENERAL; + } + result = Core::ERROR_NONE; // ← unconditionally overwrites the conditional failure + + // Correct: + uint32_t result = Core::ERROR_NONE; + if (condition) { + result = Core::ERROR_GENERAL; + } + return result; // preserve the conditional error + + citation: + line_format: "[PluginName.cpp:LINE] Error code unconditionally overwritten after conditional failure assignment" + rule: "thunder-plugin-rules.yaml / rule_05" + + - rule_id: "rule_06" + name: "NULL vs nullptr" + severity: "warning" + phase: "code_style" + + extraction: + target: "All uses of NULL as a null pointer literal in function bodies and variable declarations" + method: "Read each function body and variable declaration as a human reviewer, reasoning about the semantic meaning of each null expression — exclude NULL inside string literals and comments by context understanding" + code_block: "Each line where NULL appears as a null pointer value" + + bounded_query: + question: "Is nullptr used exclusively as the null pointer literal — no uses of NULL as a pointer value in function bodies or declarations?" + expected_answer: "Yes" + + verification_logic: + - "1. Read through the source files as a human reviewer" + - "2. For each occurrence of NULL, reason about context: is it inside a string literal? a comment? or actual code?" + - "3. NULL inside string literals (e.g. 'value is NULL') and comments must be excluded" + - "4. NULL used as a null pointer value in code (assignments, comparisons, function arguments) → WARNING" + + conditional: false + skip_condition: null + + violation_pattern: "NULL used as null pointer literal — should use nullptr" + + fix_template: | + // WRONG: + IPlugin* plugin = NULL; + if (service == NULL) { ... } + + // Correct: + IPlugin* plugin = nullptr; + if (service == nullptr) { ... } + + citation: + line_format: "[PluginName.cpp:LINE] NULL used as null pointer — use nullptr" + rule: "thunder-plugin-rules.yaml / rule_06" + + - rule_id: "rule_07" + name: "No delete on COM Interface Pointers" + severity: "violation" + phase: "code_style" + + extraction: + target: "All delete or delete[] expressions in function bodies" + method: "Read each function body and reason about the type of the pointer being deleted — use the variable's declared type, naming conventions, and class hierarchy context to determine if it is a COM interface type" + code_block: "Each delete expression in the function body" + + bounded_query: + question: "Are there zero uses of delete or delete[] on COM interface pointer types (I* types)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read each function body and identify all delete/delete[] expressions" + - "2. For each delete, reason about the type of the deleted pointer from its declaration, name (I prefix convention), and usage context" + - "3. If the pointer is a COM interface type (IPlugin, IShell, IUnknown, or any I* interface) → VIOLATION" + - "4. COM interfaces must use Release() for cleanup, not delete" + + conditional: false + skip_condition: null + + violation_pattern: "delete used on a COM interface pointer — must use Release() instead" + + fix_template: | + // WRONG: + delete _service; // COM interfaces must NOT be deleted + + // Correct: + _service->Release(); // Use Release() for COM interfaces + _service = nullptr; + + citation: + line_format: "[PluginName.cpp:LINE] delete used on COM interface pointer — use Release()" + rule: "thunder-plugin-rules.yaml / rule_07" + + - rule_id: "rule_08" + name: "nullptr After Release" + severity: "violation" + phase: "code_style" + + extraction: + target: "All ->Release() calls on member variables in function bodies" + method: "Read each function body in full and reason about each Release() call — determine if the pointer is a member variable and whether nullptr is assigned immediately after" + code_block: "Each Release() call on a member variable and the statement(s) immediately following it" + + bounded_query: + question: "Is nullptr assigned to the member variable immediately after every ->Release() call on a member?" + expected_answer: "Yes" + + verification_logic: + - "1. Read each function body and identify all ->Release() calls" + - "2. For each Release() call, reason about whether the pointer is a member variable (prefixed with _ or this-> by Thunder convention)" + - "3. Check that the very next statement assigns nullptr to that pointer" + - "4. If the member pointer is not set to nullptr immediately after Release() → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Member pointer not set to nullptr immediately after ->Release()" + + fix_template: | + // WRONG: + _service->Release(); + // ... other code, no nullptr assignment + + // Correct: + _service->Release(); + _service = nullptr; + + citation: + line_format: "[PluginName.cpp:LINE] _service->Release() not followed by _service = nullptr" + rule: "thunder-plugin-rules.yaml / rule_08" + + - rule_id: "rule_09" + name: "No QueryInterfaceByCallsign as Member" + severity: "violation" + phase: "code_style" + + extraction: + target: "All QueryInterfaceByCallsign() call sites and the storage of their return values" + method: "Read function bodies and class declarations and reason about the lifetime of the QueryInterfaceByCallsign() result — transient local use (acquired, used, released in same scope) is acceptable; storing in a member variable is not" + code_block: "The QueryInterfaceByCallsign() call and any assignment of its result" + + bounded_query: + question: "Is QueryInterfaceByCallsign() result used transiently (acquired, used, and released within the same scope) — never stored as a member variable?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all QueryInterfaceByCallsign() call sites" + - "2. Reason about the lifetime of the returned pointer: is it stored in a local variable or a class member?" + - "3. If stored in a local variable and released before the function returns → acceptable" + - "4. If stored in a class member variable (persists across calls) → VIOLATION" + + conditional: true + skip_condition: "No QueryInterfaceByCallsign() calls found in the plugin" + + violation_pattern: "QueryInterfaceByCallsign() result stored as member variable — must be transient (acquired, used, released in same scope)" + + fix_template: | + // WRONG: + _remotePlugin = _service->QueryInterfaceByCallsign("RemotePlugin"); + // _remotePlugin stored as member, not released until Deinitialize + + // Correct: + IPlugin* plugin = _service->QueryInterfaceByCallsign("RemotePlugin"); + if (plugin != nullptr) { + plugin->SomeOperation(); + plugin->Release(); + plugin = nullptr; + } + + citation: + line_format: "[PluginName.cpp:LINE] QueryInterfaceByCallsign() result stored as member variable" + rule: "thunder-plugin-rules.yaml / rule_09" + + - rule_id: "rule_10" + name: "No Smart Pointers on COM Objects" + severity: "violation" + phase: "code_style" + + extraction: + target: "Class member declarations and local variable declarations using smart pointer types" + method: "Read class member declarations and function bodies and reason about whether any smart pointer wraps a COM interface type" + code_block: "Any smart_ptr or unique_ptr member declaration" + + bounded_query: + question: "Are there zero COM interface pointers (I* types) wrapped in shared_ptr or unique_ptr?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all class member declarations and local variable declarations" + - "2. Identify any shared_ptr or unique_ptr where T is a COM interface (I* type)" + - "3. COM interfaces manage their own lifetime via AddRef/Release — smart pointers cause double-delete" + - "4. If any COM interface is wrapped in a smart pointer → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "COM interface pointer wrapped in shared_ptr or unique_ptr — use raw pointer with explicit Release()" + + fix_template: | + // WRONG: + std::shared_ptr _plugin; + std::unique_ptr _dict; + + // Correct: + IPlugin* _plugin = nullptr; + IDictionary* _dict = nullptr; + // Release manually in Deinitialize() + + citation: + line_format: "[PluginName.h:LINE] COM interface wrapped in smart pointer" + rule: "thunder-plugin-rules.yaml / rule_10" + + - rule_id: "rule_11" + name: "No SmartLinkType for COMRPC Plugins" + severity: "violation" + phase: "code_style" + + extraction: + target: "Class member declarations and typedef/using aliases for SmartLinkType" + method: "Read class declarations and reason about member types — identify any SmartLinkType usage" + code_block: "Any SmartLinkType declaration or usage" + + bounded_query: + question: "Is SmartLinkType absent from the plugin — no member declarations or typedefs using SmartLinkType?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all class declarations, member variable definitions, and typedef/using statements" + - "2. Check for any use of SmartLinkType" + - "3. SmartLinkType is a deprecated mechanism — COMRPC plugins must use direct interface pointers" + - "4. If SmartLinkType is found → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "SmartLinkType used — deprecated mechanism, use direct COMRPC interface pointer instead" + + fix_template: | + // WRONG: + SmartLinkType> _link; + + // Correct: + IPlugin* _plugin = nullptr; + // Acquire via QueryInterface, release in Deinitialize + + citation: + line_format: "[PluginName.h:LINE] SmartLinkType used — deprecated, use direct interface pointer" + rule: "thunder-plugin-rules.yaml / rule_11" + + - rule_id: "rule_12" + name: "No delete on Plugin Object" + severity: "violation" + phase: "code_style" + + extraction: + target: "All function bodies in the plugin for delete this or delete on plugin-owned objects" + method: "Read all function bodies and reason about ownership — plugin object lifetime is managed by the Thunder framework" + code_block: "Any delete this or delete on a plugin-managed object" + + bounded_query: + question: "Is delete this absent from the plugin code — plugin lifetime is always managed by the Thunder framework?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all function bodies in the plugin" + - "2. Reason about any delete expression — particularly delete this" + - "3. The Thunder framework owns plugin object lifetime; plugins must never delete themselves" + - "4. If delete this is found → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "delete this used in plugin code — plugin lifetime is managed by the Thunder framework" + + fix_template: | + // WRONG: + void SomeMethod() { + if (done) { + delete this; // ← Thunder framework owns this object + } + } + + // Correct: signal to the framework instead + void SomeMethod() { + if (done) { + _service->Submit(PluginHost::IShell::DEACTIVATED, this); + } + } + + citation: + line_format: "[PluginName.cpp:LINE] delete this used — plugin lifetime is framework-managed" + rule: "thunder-plugin-rules.yaml / rule_12" + + - rule_id: "rule_13" + name: "No throw Keyword in Plugin Code" + severity: "violation" + phase: "code_style" + + extraction: + target: "All function bodies for throw statements" + method: "Read all function bodies as a human reviewer and reason about error-handling patterns — exclude throw inside string literals and comments by context understanding" + code_block: "Any throw statement in executable plugin code" + + bounded_query: + question: "Is the throw keyword absent from all executable plugin code (not inside string literals or comments)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all function bodies as a human reviewer" + - "2. For each occurrence of throw, reason about context: is it in executable code, a string literal, or a comment?" + - "3. Throw in string literals and comments must be excluded" + - "4. Exceptions cannot cross COM boundaries — any throw in plugin code is a protocol violation" + - "5. If throw appears in executable plugin code → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "throw keyword used in plugin code — exceptions cannot cross COM boundaries" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + if (!Init()) throw std::runtime_error("Init failed"); + return {}; + } + + // Correct: + string Initialize(PluginHost::IShell* service) { + if (!Init()) return "Initialization failed"; + return {}; + } + + citation: + line_format: "[PluginName.cpp:LINE] throw keyword used — use return error string instead" + rule: "thunder-plugin-rules.yaml / rule_13" + +# ------------------------------------------------------------------------------------------- +# Phase 3: Class Registration +# ------------------------------------------------------------------------------------------- + +phase_3_checkpoints: + - rule_id: "rule_14" + name: "Special Members Deleted (Main Class)" + severity: "warning" + phase: "class_registration" + + extraction: + target: "The main plugin class declaration (the class implementing PluginHost::IPlugin)" + method: "Read the main plugin class declaration in the plugin header file and check for deleted special members" + code_block: "The special member declarations (constructors, assignment operators) in the main class" + + bounded_query: + question: "Are all 4 special members deleted in the MAIN plugin class — copy constructor, copy assignment operator, move constructor, and move assignment operator?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin header file and identify the main plugin class (the one inheriting PluginHost::IPlugin)" + - "2. Internal helper classes (Notification, Sink, Config, JobWorker, etc.) are explicitly excluded" + - "3. Check that the main class has: PluginName(const PluginName&) = delete; PluginName& operator=(const PluginName&) = delete; PluginName(PluginName&&) = delete; PluginName& operator=(PluginName&&) = delete;" + - "4. If all 4 are present and deleted → PASS" + - "5. If any of the 4 are missing → WARNING" + + conditional: false + skip_condition: null + + violation_pattern: "One or more of the 4 special members are not explicitly deleted in the main plugin class" + + fix_template: | + // WRONG: (one or more missing) + class Dictionary : public PluginHost::IPlugin { + public: + Dictionary() = default; + Dictionary(const Dictionary&) = delete; + // Missing: move ctor, copy assign, move assign + + // Correct: + class Dictionary : public PluginHost::IPlugin { + public: + Dictionary() = default; + Dictionary(const Dictionary&) = delete; + Dictionary& operator=(const Dictionary&) = delete; + Dictionary(Dictionary&&) = delete; + Dictionary& operator=(Dictionary&&) = delete; + + citation: + line_format: "[PluginName.h:LINE] Not all 4 special members deleted in main plugin class" + rule: "thunder-plugin-rules.yaml / rule_14" + + - rule_id: "rule_15" + name: "Plugin Metadata Registration" + severity: "violation" + phase: "class_registration" + + extraction: + target: "Plugin::Metadata instantiation in the plugin .cpp file" + method: "Read the plugin .cpp file and look for the Plugin::Metadata static registration" + code_block: "The Plugin::Metadata static instance declaration" + + bounded_query: + question: "Does the plugin .cpp contain a Plugin::Metadata static registration?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin .cpp file (PluginName.cpp)" + - "2. Check for the presence of static Plugin::Metadata registration" + - "3. This registers the plugin with the Thunder framework — it must be present" + - "4. If absent → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Plugin::Metadata registration missing from plugin .cpp" + + fix_template: | + // WRONG: (missing metadata registration) + + // Correct — add to PluginName.cpp: + static Plugin::Metadata metadata( + Plugin::Information::Versions, + Plugin::Information::Instances, + Plugin::Information::Prefix, + Plugin::Information::Interfaces, + Plugin::Information::Configuration, + Plugin::Information::Extends + ); + + citation: + line_format: "[PluginName.cpp:LINE] Plugin::Metadata registration absent" + rule: "thunder-plugin-rules.yaml / rule_15" + + - rule_id: "rule_16" + name: "JSONRPC Inheritance When Used" + severity: "violation" + phase: "class_registration" + + extraction: + target: "Initialize() body for JSON-RPC Register() calls and the class inheritance list" + method: "Read Initialize() and the class declaration and reason about whether JSON-RPC handler registration is present and whether the class properly inherits JSONRPC" + code_block: "The Register() calls in Initialize() and the class inheritance declarations" + + bounded_query: + question: "If the plugin registers JSON-RPC handlers (Register() calls in Initialize()), does the class inherit PluginHost::JSONRPC?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Initialize() and check for Register() calls that register JSON-RPC handlers" + - "2. If no JSON-RPC Register() calls are found → SKIP this checkpoint" + - "3. If Register() calls exist, read the class declaration" + - "4. Verify the class inherits from PluginHost::JSONRPC (directly or via JSONRPC base)" + - "5. If JSON-RPC handlers are registered but the class does not inherit JSONRPC → VIOLATION" + + conditional: true + skip_condition: "Plugin does not register any JSON-RPC handlers in Initialize()" + + violation_pattern: "JSON-RPC Register() calls present in Initialize() but class does not inherit PluginHost::JSONRPC" + + fix_template: | + // WRONG: + class Dictionary : public PluginHost::IPlugin { + // Missing PluginHost::JSONRPC inheritance + + // Correct: + class Dictionary : public PluginHost::IPlugin, + public PluginHost::JSONRPC { + + citation: + line_format: "[PluginName.h:LINE] JSON-RPC used but JSONRPC inheritance missing" + rule: "thunder-plugin-rules.yaml / rule_16" + +# ------------------------------------------------------------------------------------------- +# Phase 4: Lifecycle +# ------------------------------------------------------------------------------------------- + +phase_4_checkpoints: + - rule_id: "rule_17" + name: "IShell AddRef in Initialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "IShell* member assignment and AddRef() call in Initialize()" + method: "Read Initialize() body looking for IShell* member assignment (e.g. _service = service;) and the AddRef() call immediately after" + code_block: "Lines in Initialize() that assign the service pointer and call AddRef" + + bounded_query: + question: "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. Check if the class has an IShell* member variable (e.g. _service, _shell)" + - "2. If no IShell* member exists → SKIP this checkpoint" + - "3. If IShell* member exists: find the assignment in Initialize() (e.g. _service = service;)" + - "4. Check that AddRef() is called on it immediately after: _service->AddRef();" + - "5. If AddRef() is missing → VIOLATION" + + conditional: true + skip_condition: "Class has no stored IShell* member variable" + + violation_pattern: "IShell* stored as member but AddRef() not called after assignment in Initialize()" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + _service = service; // ← AddRef() missing + // ... + } + + // Correct: + string Initialize(PluginHost::IShell* service) { + ASSERT(service != nullptr); + _service = service; + _service->AddRef(); // AddRef when storing the pointer + // ... + } + + citation: + line_format: "[PluginName.cpp:LINE] IShell* stored but AddRef() not called after assignment" + rule: "thunder-plugin-rules.yaml / rule_17" + + - rule_id: "rule_18" + name: "IShell Release in Deinitialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "_service->Release() and _service = nullptr in Deinitialize()" + method: "Read Deinitialize() body and check that the stored IShell* is released and nulled" + code_block: "The _service Release and nullptr assignment lines in Deinitialize()" + + bounded_query: + question: "Does Deinitialize() call Release() on the stored IShell* and immediately set it to nullptr?" + expected_answer: "Yes" + + verification_logic: + - "1. If the class has no stored IShell* member → SKIP" + - "2. Read Deinitialize() in full" + - "3. Check for _service->Release() (or equivalent member->Release())" + - "4. Check that _service = nullptr follows immediately after Release()" + - "5. If Release() or the nullptr assignment is missing → VIOLATION" + + conditional: true + skip_condition: "Class has no stored IShell* member variable" + + violation_pattern: "IShell* member not released or not nulled in Deinitialize()" + + fix_template: | + // WRONG: + void Deinitialize(PluginHost::IShell* service) { + // _service->Release() missing or _service = nullptr missing + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + // ... cleanup ... + _service->Release(); + _service = nullptr; + } + + citation: + line_format: "[PluginName.cpp:LINE] IShell* not released or not nulled in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_18" + + - rule_id: "rule_19" + name: "Information() Method" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "string Information() const method in the plugin .cpp" + method: "Read the plugin .cpp and check for the Information() const method implementation" + code_block: "The Information() method declaration and implementation" + + bounded_query: + question: "Does the plugin implement the string Information() const method?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin .cpp and .h files" + - "2. Check for a method with signature: string Information() const (or const string& Information() const)" + - "3. This method is part of PluginHost::IPlugin and MUST be implemented" + - "4. If absent → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "string Information() const method not implemented in plugin .cpp" + + fix_template: | + // WRONG: (method missing) + + // Correct — add to PluginName.cpp: + string PluginName::Information() const { + return string(); + } + + citation: + line_format: "[PluginName.cpp:LINE] Information() const method not implemented" + rule: "thunder-plugin-rules.yaml / rule_19" + + - rule_id: "rule_20" + name: "Root() Null Check" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "service->Root() call in Initialize() and the null check on its return" + method: "Read Initialize() in full and reason about Root() call sites — check whether the return value is tested for nullptr before use" + code_block: "The Root() call and the null check on the returned pointer" + + bounded_query: + question: "Is the return value of service->Root() checked for nullptr before it is used?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Initialize() and check for service->Root() calls" + - "2. If no Root() call → SKIP" + - "3. If Root() is called, check that the return value is stored and tested for nullptr" + - "4. Accessing a potentially-null pointer without checking → VIOLATION" + + conditional: true + skip_condition: "Initialize() does not call service->Root()" + + violation_pattern: "service->Root() return value used without nullptr check" + + fix_template: | + // WRONG: + _implementation = service->Root(); + _implementation->DoSomething(); // ← may crash if Root returns nullptr + + // Correct: + _implementation = service->Root(); + if (_implementation != nullptr) { + _implementation->DoSomething(); + } else { + return "Failed to acquire implementation"; + } + + citation: + line_format: "[PluginName.cpp:LINE] Root() return value not checked for nullptr" + rule: "thunder-plugin-rules.yaml / rule_20" + + - rule_id: "rule_21" + name: "Root() Release in Deinitialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "Release() and nullptr assignment for the Root() pointer in Deinitialize()" + method: "Read Deinitialize() and check that the pointer stored from Root() is released and nulled" + code_block: "The Release() call and nullptr assignment for the Root pointer in Deinitialize()" + + bounded_query: + question: "Is the pointer acquired via Root() released and set to nullptr in Deinitialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. If no Root() is called in Initialize() → SKIP" + - "2. Read Deinitialize() in full" + - "3. Identify the member that stores the Root() result" + - "4. Check that Release() is called on it and it is then set to nullptr" + - "5. If not released or not nulled → VIOLATION" + + conditional: true + skip_condition: "Plugin does not call service->Root() in Initialize()" + + violation_pattern: "Root() pointer not released or not nulled in Deinitialize()" + + fix_template: | + // WRONG: + void Deinitialize(PluginHost::IShell* service) { + // _implementation->Release() missing + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + if (_implementation != nullptr) { + _implementation->Release(); + _implementation = nullptr; + } + } + + citation: + line_format: "[PluginName.cpp:LINE] Root() pointer not released in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_21" + + - rule_id: "rule_22" + name: "Observer Cleanup in Deinitialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "Observer or notification registration in Initialize() and cleanup in Deinitialize()" + method: "Read Initialize() for Register/Subscribe calls and Deinitialize() for the matching Unregister/Unsubscribe cleanup" + code_block: "The Register call in Initialize() and the matching Unregister in Deinitialize()" + + bounded_query: + question: "Is every observer or notification registered in Initialize() properly unregistered in Deinitialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. If no observer registration in Initialize() → SKIP" + - "2. Read Initialize() and identify all Register/Subscribe calls for notifications" + - "3. Read Deinitialize() and check for the matching Unregister/Unsubscribe calls" + - "4. Every registered observer MUST be unregistered in Deinitialize() to avoid dangling callbacks" + - "5. If any registration lacks a corresponding cleanup → VIOLATION" + + conditional: true + skip_condition: "Plugin does not register any observers or notifications" + + violation_pattern: "Observer registered in Initialize() but not unregistered in Deinitialize()" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + service->Register(_notification); + // ... + } + void Deinitialize(PluginHost::IShell* service) { + // service->Unregister(_notification) missing + } + + // Correct: + string Initialize(PluginHost::IShell* service) { + service->Register(_notification); + } + void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); + } + + citation: + line_format: "[PluginName.cpp:LINE] Observer registered in Initialize() but not cleaned up in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_22" + + - rule_id: "rule_23" + name: "SubSystems() Release in Deinitialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "service->SubSystems() usage in Initialize() and cleanup in Deinitialize()" + method: "Read Initialize() for SubSystems() acquisition and Deinitialize() for the Release" + code_block: "The SubSystems() call and associated Release in Deinitialize()" + + bounded_query: + question: "If service->SubSystems() is acquired in Initialize(), is it released in Deinitialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. If Initialize() does not call service->SubSystems() → SKIP" + - "2. Read Initialize() and find the SubSystems() acquisition and storage" + - "3. Read Deinitialize() and check for Release() on the subsystems pointer" + - "4. If acquired but not released → VIOLATION" + + conditional: true + skip_condition: "Plugin does not use service->SubSystems() in Initialize()" + + violation_pattern: "service->SubSystems() acquired in Initialize() but not released in Deinitialize()" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + _subSystems = service->SubSystems(); + // ... + } + void Deinitialize(PluginHost::IShell* service) { + // _subSystems->Release() missing + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + _subSystems->Release(); + _subSystems = nullptr; + } + + citation: + line_format: "[PluginName.cpp:LINE] SubSystems() not released in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_23" + + - rule_id: "rule_24" + name: "Constructor Must Be Empty" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "Plugin class constructor body" + method: "Read the plugin class constructor implementation and reason about whether it performs any non-trivial initialization" + code_block: "The complete constructor body" + + bounded_query: + question: "Is the plugin constructor body empty (no initialization logic, only member initializer list with defaults if any)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin constructor definition" + - "2. Reason about whether the body performs any work beyond initializing members to null/default values" + - "3. Resource acquisition, system calls, and service interactions must be in Initialize(), not the constructor" + - "4. If the constructor body contains non-trivial logic → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Plugin constructor contains initialization logic — must be empty; initialization belongs in Initialize()" + + fix_template: | + // WRONG: + Dictionary::Dictionary() { + _config.FromString("..."); // ← initialization logic in constructor + _service = GetService(); + } + + // Correct: + Dictionary::Dictionary() + : _service(nullptr) + , _config() + { + // empty — all initialization in Initialize() + } + + citation: + line_format: "[PluginName.cpp:LINE] Constructor contains initialization logic" + rule: "thunder-plugin-rules.yaml / rule_24" + + - rule_id: "rule_25" + name: "service->Register/Unregister Pairing" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "service->Register() calls in Initialize() and matching service->Unregister() in Deinitialize()" + method: "Read Initialize() for service->Register() calls (for IPlugin notifications) and Deinitialize() for matching Unregister() calls" + code_block: "All service->Register() and service->Unregister() calls" + + bounded_query: + question: "Is every service->Register() call in Initialize() matched by a service->Unregister() call in Deinitialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. If no service->Register() in Initialize() → SKIP" + - "2. Read Initialize() and count all service->Register() calls" + - "3. Read Deinitialize() and count matching service->Unregister() calls" + - "4. Every registration must have a corresponding deregistration" + - "5. If any registration lacks a matching deregistration → VIOLATION" + + conditional: true + skip_condition: "Plugin does not call service->Register() in Initialize()" + + violation_pattern: "service->Register() in Initialize() not matched by service->Unregister() in Deinitialize()" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + service->Register(_notification); + } + void Deinitialize(PluginHost::IShell* service) { + // service->Unregister(_notification) missing + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + service->Unregister(_notification); + } + + citation: + line_format: "[PluginName.cpp:LINE] service->Register() without matching Unregister() in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_25" + + - rule_id: "rule_26" + name: "Initialize Returns Error String on Failure" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "Initialize() return statements and error handling" + method: "Read Initialize() in full and reason about all return paths — every failure path must return a non-empty error string" + code_block: "All return statements in Initialize()" + + bounded_query: + question: "Does Initialize() return a non-empty error string on every failure path (never returning empty string when an error condition is detected)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Initialize() in full" + - "2. Identify all error conditions (nullptr checks, failed operations, configuration errors)" + - "3. Verify each error path returns a non-empty string describing the failure" + - "4. Returning an empty string on failure prevents Thunder from knowing initialization failed" + - "5. If any error condition returns empty string or falls through to success → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Initialize() returns empty string on a failure condition — must return non-empty error description" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + if (service == nullptr) { + return string(); // ← empty string does not signal failure to Thunder + } + return string(); + } + + // Correct: + string Initialize(PluginHost::IShell* service) { + if (service == nullptr) { + return "Service pointer is null"; // non-empty string signals failure + } + return string(); // success — empty string + } + + citation: + line_format: "[PluginName.cpp:LINE] Initialize() returns empty string on failure condition" + rule: "thunder-plugin-rules.yaml / rule_26" + + - rule_id: "rule_27" + name: "No Manual Deinitialize() in Initialize" + severity: "violation" + phase: "lifecycle" + + extraction: + target: "Initialize() body for any call to Deinitialize()" + method: "Read Initialize() body and check for any direct call to Deinitialize()" + code_block: "The full Initialize() body, focusing on cleanup paths" + + bounded_query: + question: "Is Deinitialize() never called directly from within Initialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. Read Initialize() in full" + - "2. Check whether Initialize() calls Deinitialize() directly at any point" + - "3. If Initialize() needs to clean up on failure, it must do so explicitly rather than calling Deinitialize()" + - "4. Calling Deinitialize() from Initialize() can leave the system in an inconsistent state" + - "5. If Deinitialize() is called inside Initialize() → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Initialize() calls Deinitialize() directly — handle cleanup explicitly on failure paths" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + _service = service; + _service->AddRef(); + if (!Setup()) { + Deinitialize(service); // ← do not call Deinitialize from Initialize + return "Setup failed"; + } + return string(); + } + + // Correct: + string Initialize(PluginHost::IShell* service) { + _service = service; + _service->AddRef(); + if (!Setup()) { + _service->Release(); // explicit cleanup + _service = nullptr; + return "Setup failed"; + } + return string(); + } + + citation: + line_format: "[PluginName.cpp:LINE] Initialize() calls Deinitialize() — handle failures explicitly" + rule: "thunder-plugin-rules.yaml / rule_27" + + - rule_id: "rule_28" + name: "Destructor Must Be Empty" + 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" + 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())?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin destructor definition" + - "2. The destructor must be empty — all resource cleanup belongs in Deinitialize()" + - "3. If the destructor contains any logic or resource release → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Plugin destructor contains cleanup logic — all cleanup must be in Deinitialize()" + + fix_template: | + // WRONG: + Dictionary::~Dictionary() { + if (_service != nullptr) { + _service->Release(); // ← cleanup in destructor is wrong + } + } + + // Correct: + Dictionary::~Dictionary() { + // empty — cleanup is in Deinitialize() + } + + citation: + line_format: "[PluginName.cpp:LINE] Destructor contains cleanup logic — move to Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_28" + +# ------------------------------------------------------------------------------------------- +# Phase 5: Implementation +# ------------------------------------------------------------------------------------------- + +phase_5_checkpoints: + - rule_id: "rule_29" + name: "JSON-RPC Register/Unregister Pairing" + severity: "violation" + phase: "implementation" + + extraction: + target: "JSON-RPC Register() calls in Initialize() and matching Unregister() in Deinitialize()" + method: "Read Initialize() and Deinitialize() and reason about JSON-RPC handler registration balance" + code_block: "All JSONRPC Register() and Unregister() calls" + + bounded_query: + question: "Is every JSON-RPC handler registered in Initialize() unregistered in Deinitialize()?" + expected_answer: "Yes" + + verification_logic: + - "1. If no JSONRPC::Register() calls in Initialize() → SKIP" + - "2. Read Initialize() and list all JSONRPC::Register() calls and method names" + - "3. Read Deinitialize() and verify matching JSONRPC::Unregister() for each registered method" + - "4. Missing unregistration causes stale handler references after plugin deactivation" + - "5. If any registration lacks a matching unregistration → VIOLATION" + + conditional: true + skip_condition: "Plugin does not register JSON-RPC handlers" + + violation_pattern: "JSON-RPC handler registered in Initialize() but not unregistered in Deinitialize()" + + fix_template: | + // WRONG: + string Initialize(PluginHost::IShell* service) { + Register(_T("method"), &PluginName::Method, this); + } + void Deinitialize(PluginHost::IShell* service) { + // Unregister(_T("method")) missing + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + Unregister(_T("method")); + } + + citation: + line_format: "[PluginName.cpp:LINE] JSON-RPC handler registered but not unregistered" + rule: "thunder-plugin-rules.yaml / rule_29" + + - rule_id: "rule_30" + name: "SinkType Pattern for Subscribers" + severity: "violation" + phase: "implementation" + + extraction: + target: "Classes or structs that subscribe to interface notifications" + method: "Read class declarations and reason about notification subscriber classes — they should follow the SinkType pattern" + code_block: "Notification subscriber class declarations" + + bounded_query: + question: "Do notification subscriber classes follow the SinkType pattern (inheriting from the interface and implementing all required callbacks)?" + expected_answer: "Yes" + + verification_logic: + - "1. If no notification subscription in the plugin → SKIP" + - "2. Read all notification handler classes" + - "3. Verify they properly inherit from the notification interface (e.g. IPlugin::INotification)" + - "4. Verify all pure virtual methods are implemented" + - "5. If a subscriber class is missing required methods or inheritance → VIOLATION" + + conditional: true + skip_condition: "Plugin does not subscribe to any interface notifications" + + violation_pattern: "Notification subscriber class does not properly implement the SinkType pattern" + + fix_template: | + // WRONG: + class MyNotification { // ← not inheriting IPlugin::INotification + + // Correct: + class Notification : public PluginHost::IPlugin::INotification { + public: + BEGIN_INTERFACE_MAP(Notification) + INTERFACE_ENTRY(PluginHost::IPlugin::INotification) + END_INTERFACE_MAP + void Activated(const string& callsign, PluginHost::IShell* service) override; + void Deactivated(const string& callsign, PluginHost::IShell* service) override; + void Unavailable(const string& callsign, PluginHost::IShell* service) override; + }; + + citation: + line_format: "[PluginName.h:LINE] Notification subscriber does not follow SinkType pattern" + rule: "thunder-plugin-rules.yaml / rule_30" + + - rule_id: "rule_31" + name: "Unavailable() in SinkType Classes" + severity: "violation" + phase: "implementation" + + extraction: + target: "SinkType notification classes that implement IPlugin::INotification" + method: "Read SinkType notification class declarations and check for the Unavailable() method implementation" + code_block: "The method list in the SinkType class declaration" + + bounded_query: + question: "Does every SinkType class implementing IPlugin::INotification also implement the Unavailable() callback?" + expected_answer: "Yes" + + verification_logic: + - "1. If no SinkType notification classes exist → SKIP" + - "2. Read each class that inherits IPlugin::INotification" + - "3. Check that Unavailable() is implemented (not just Activated() and Deactivated())" + - "4. IPlugin::INotification has 3 pure virtuals: Activated, Deactivated, and Unavailable" + - "5. If Unavailable() is missing → VIOLATION" + + conditional: true + skip_condition: "Plugin has no SinkType classes implementing IPlugin::INotification" + + violation_pattern: "SinkType class missing Unavailable() implementation — IPlugin::INotification requires all 3 callbacks" + + fix_template: | + // WRONG: + class Notification : public PluginHost::IPlugin::INotification { + void Activated(const string& callsign, PluginHost::IShell* service) override { ... } + void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } + // Unavailable() missing + }; + + // Correct: + class Notification : public PluginHost::IPlugin::INotification { + void Activated(const string& callsign, PluginHost::IShell* service) override { ... } + void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } + void Unavailable(const string& callsign, PluginHost::IShell* service) override { ... } + }; + + citation: + line_format: "[PluginName.h:LINE] SinkType class missing Unavailable() implementation" + rule: "thunder-plugin-rules.yaml / rule_31" + + - rule_id: "rule_32" + name: "No Hardcoded Paths" + severity: "violation" + phase: "implementation" + + extraction: + target: "All string literals that contain filesystem paths" + method: "Read all function bodies and member initializers and reason about string literals — identify any hardcoded filesystem paths" + code_block: "Any string literal containing a filesystem path" + + bounded_query: + question: "Are there zero hardcoded filesystem paths in the plugin code (paths come from configuration or Thunder data paths)?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all function bodies and class member initializations" + - "2. Reason about string literals — identify any that look like filesystem paths (/usr/..., /etc/..., /tmp/..., C:\\...)" + - "3. Paths should come from configuration or be constructed from Thunder's data path API" + - "4. If any hardcoded path string literal is found in executable code → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Hardcoded filesystem path found in plugin code — use configuration or Thunder data paths" + + fix_template: | + // WRONG: + string configFile = "/etc/myapp/config.json"; + string dataDir = "/var/lib/thunder/data/"; + + // Correct: + string configFile = service->DataPath() + "config.json"; + // Or use config: _config.DataPath.Value() + + citation: + line_format: "[PluginName.cpp:LINE] Hardcoded filesystem path" + rule: "thunder-plugin-rules.yaml / rule_32" + +# ------------------------------------------------------------------------------------------- +# Phase 5C: Out-of-Process (OOP) +# ------------------------------------------------------------------------------------------- + +phase_5C_checkpoints: + - rule_id: "rule_33" + name: "OOP Connection Termination in Deinitialize" + severity: "violation" + phase: "oop" + + extraction: + target: "OOP connection handling in Deinitialize() — IRemoteConnection release and channel shutdown" + method: "Read Deinitialize() and reason about OOP connection cleanup — verify the remote connection is properly terminated" + code_block: "The OOP connection release and cleanup code in Deinitialize()" + + bounded_query: + question: "Does Deinitialize() properly terminate the OOP connection (release the remote implementation and close the connection channel)?" + expected_answer: "Yes" + + verification_logic: + - "1. Check CMakeLists.txt for PLUGIN__MODE set to 'Local' — if not present or not 'Local' → SKIP" + - "2. Read Deinitialize() in full" + - "3. Check that the remote implementation interface is released" + - "4. Check that the connection channel is terminated (Terminate() called or connection closed)" + - "5. If the OOP connection is not properly cleaned up → VIOLATION" + + conditional: true + skip_condition: "CMakeLists.txt does not have PLUGIN__MODE set to 'Local'" + + violation_pattern: "OOP connection not properly terminated in Deinitialize() — remote implementation and channel must be cleaned up" + + fix_template: | + // WRONG: + void Deinitialize(PluginHost::IShell* service) { + // OOP connection not cleaned up + } + + // Correct: + void Deinitialize(PluginHost::IShell* service) { + if (_implementation != nullptr) { + _implementation->Release(); + _implementation = nullptr; + } + if (_connection != nullptr) { + _connection->Terminate(); + _connection->Release(); + _connection = nullptr; + } + } + + citation: + line_format: "[PluginName.cpp:LINE] OOP connection not terminated in Deinitialize()" + rule: "thunder-plugin-rules.yaml / rule_33" + + - rule_id: "rule_34" + name: "connectionId Checked in IRemoteConnection Callbacks" + severity: "violation" + phase: "oop" + + extraction: + target: "IRemoteConnection::INotification callback implementations (Activated, Deactivated, Terminated)" + method: "Read the IRemoteConnection callback implementations and verify that connectionId is checked against the known connection ID before acting" + code_block: "The IRemoteConnection callback bodies" + + bounded_query: + question: "In every IRemoteConnection callback (Activated, Deactivated, Terminated), is the connectionId parameter checked against the plugin's known connection ID before taking action?" + expected_answer: "Yes" + + verification_logic: + - "1. If plugin has no IRemoteConnection::INotification implementation OR CMakeLists.txt does not have PLUGIN__MODE = 'Local' → SKIP" + - "2. Read each IRemoteConnection callback body" + - "3. Check that the first logic step guards against spurious callbacks by comparing connectionId to the stored connection ID" + - "4. Acting on any connectionId without checking causes incorrect behavior when multiple OOP connections exist" + - "5. If connectionId is not checked → VIOLATION" + + conditional: true + skip_condition: "CMakeLists.txt does not have PLUGIN__MODE = 'Local', or plugin does not implement IRemoteConnection::INotification" + + violation_pattern: "IRemoteConnection callback does not check connectionId before acting" + + fix_template: | + // WRONG: + void Terminated(const uint32_t id) override { + // acts without checking if id matches our connection + _parent.ConnectionTerminated(); + } + + // Correct: + void Terminated(const uint32_t id) override { + if (id == _parent._connectionId) { + _parent.ConnectionTerminated(); + } + } + + citation: + line_format: "[PluginName.cpp:LINE] connectionId not checked in IRemoteConnection callback" + rule: "thunder-plugin-rules.yaml / rule_34" + +# ------------------------------------------------------------------------------------------- +# Phase 6: Configuration +# ------------------------------------------------------------------------------------------- + +phase_6_checkpoints: + - rule_id: "rule_35" + name: "Startmode Declaration" + severity: "violation" + phase: "configuration" + + extraction: + target: "startmode field in PluginName.conf.in" + method: "Read PluginName.conf.in and check for the startmode field declaration" + code_block: "The startmode entry in the .conf.in file" + + bounded_query: + question: "Does the .conf.in file declare an explicit startmode (e.g. startmode = Activated or startmode = Deactivated)?" + expected_answer: "Yes" + + verification_logic: + - "1. If no .conf.in file exists → SKIP" + - "2. Read the .conf.in file" + - "3. Check for an explicit startmode declaration" + - "4. A missing startmode may cause unexpected startup behavior" + - "5. If startmode is absent → VIOLATION" + + conditional: true + skip_condition: "No .conf.in file present for this plugin" + + violation_pattern: "startmode not declared in .conf.in — explicit startmode is required" + + fix_template: | + # WRONG: (startmode absent from .conf.in) + + # Correct — add to PluginName.conf.in: + startmode = Activated + + citation: + line_format: "[PluginName.conf.in:LINE] startmode field missing" + rule: "thunder-plugin-rules.yaml / rule_35" + + - rule_id: "rule_36" + name: "Config Core::JSON::Container" + severity: "violation" + phase: "configuration" + + extraction: + target: "Plugin configuration class declaration" + method: "Read the plugin configuration class (typically Config inner class or separate Config header) and check if it inherits Core::JSON::Container" + code_block: "The Config class declaration" + + bounded_query: + question: "Does the plugin configuration class properly inherit Core::JSON::Container?" + expected_answer: "Yes" + + verification_logic: + - "1. If plugin has no configuration class → SKIP" + - "2. Read the Config class declaration" + - "3. Check that it inherits from Core::JSON::Container" + - "4. Config classes that don't inherit Core::JSON::Container won't parse configuration correctly" + - "5. If Core::JSON::Container inheritance is missing → VIOLATION" + + conditional: true + skip_condition: "Plugin has no configuration class" + + violation_pattern: "Config class does not inherit Core::JSON::Container" + + fix_template: | + // WRONG: + class Config { // ← missing Core::JSON::Container + public: + string Root; + }; + + // Correct: + class Config : public Core::JSON::Container { + public: + Config() : Core::JSON::Container() { + Add(_T("root"), &Root); + } + Core::JSON::String Root; + }; + + citation: + line_format: "[PluginName.h:LINE] Config class missing Core::JSON::Container inheritance" + rule: "thunder-plugin-rules.yaml / rule_36" + + - rule_id: "rule_37" + name: "No Hardcoded Numeric Tuning Parameters" + severity: "violation" + phase: "configuration" + + extraction: + target: "Magic number literals in function bodies and member initializations" + method: "Read function bodies and member initializations and reason about numeric literals — identify any that represent tunable parameters rather than structural constants" + code_block: "Any magic number that should come from configuration" + + bounded_query: + question: "Are there zero hardcoded numeric tuning parameters (timeouts, sizes, counts, thresholds) in the plugin code — these come from the Config class?" + expected_answer: "Yes" + + verification_logic: + - "1. Read all function bodies and class member initializations" + - "2. Reason about numeric literals: are they structural constants (0, 1, -1) or tunable parameters (timeouts like 5000, buffer sizes like 1024, retry counts like 3)?" + - "3. Tunable parameters belong in Config — structural constants (comparison values, array indices) are acceptable inline" + - "4. If magic numbers representing tunable values are found → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "Hardcoded numeric tuning parameter — should come from Config class" + + fix_template: | + // WRONG: + Core::Thread::Run(5000); // ← hardcoded 5-second timeout + if (retries > 3) { ... } // ← hardcoded retry count + + // Correct: + Core::Thread::Run(_config.Timeout.Value()); + if (retries > _config.MaxRetries.Value()) { ... } + + citation: + line_format: "[PluginName.cpp:LINE] Hardcoded numeric tuning parameter — move to Config" + rule: "thunder-plugin-rules.yaml / rule_37" + +# ------------------------------------------------------------------------------------------- +# Phase 7: CMake +# ------------------------------------------------------------------------------------------- + +phase_7_checkpoints: + - rule_id: "rule_38" + name: "CXX_STANDARD Uses Thunder Variable" + severity: "violation" + phase: "cmake" + + extraction: + target: "CXX_STANDARD property in CMakeLists.txt" + method: "Read CMakeLists.txt and check how CXX_STANDARD is set" + code_block: "The set_target_properties or set(CMAKE_CXX_STANDARD ...) line in CMakeLists.txt" + + bounded_query: + question: "If CXX_STANDARD is set in CMakeLists.txt, does it use the Thunder variable ${CXX_STD} rather than a hardcoded value?" + expected_answer: "Yes" + + verification_logic: + - "1. If CMakeLists.txt does not set CXX_STANDARD → SKIP" + - "2. Read CMakeLists.txt and find where CXX_STANDARD is configured" + - "3. Check whether it uses the ${CXX_STD} variable (defined by Thunder's build system)" + - "4. Using a hardcoded value (e.g. 14, 17) bypasses the Thunder build standard control" + - "5. If a hardcoded value is used instead of ${CXX_STD} → VIOLATION" + + conditional: true + skip_condition: "CMakeLists.txt does not set CXX_STANDARD" + + violation_pattern: "CXX_STANDARD set to hardcoded value — must use ${CXX_STD} variable" + + fix_template: | + # WRONG: + set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD 14) + + # Correct: + set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD ${CXX_STD}) + + citation: + line_format: "[CMakeLists.txt:LINE] CXX_STANDARD hardcoded — use ${CXX_STD}" + rule: "thunder-plugin-rules.yaml / rule_38" + +# ------------------------------------------------------------------------------------------- +# Phase 8: COM Interface Rules +# ------------------------------------------------------------------------------------------- + +phase_8_checkpoints: + - rule_id: "rule_39" + name: "COM Methods Return Core::hresult" + severity: "violation" + phase: "interfaces" + + extraction: + target: "COM interface method return types in plugin interface declarations" + method: "Read all interface method declarations in plugin header files and reason about return types — COM interface methods must return Core::hresult" + code_block: "All interface method declarations in the plugin's COM interface" + + bounded_query: + question: "Do all COM interface methods declared in the plugin use Core::hresult as their return type?" + expected_answer: "Yes" + + verification_logic: + - "1. Read the plugin's interface header file" + - "2. Identify all methods declared in COM interfaces (not class methods — interface pure virtuals)" + - "3. Reason about each method's return type: must be Core::hresult for Thunder 5.0+" + - "4. Void return types and other return types are not allowed in COM interfaces" + - "5. If any COM interface method does not return Core::hresult → VIOLATION" + + conditional: false + skip_condition: null + + violation_pattern: "COM interface method does not return Core::hresult — required for Thunder 5.0+" + + fix_template: | + // WRONG: + virtual void GetValue(const string& key, string& value) = 0; + virtual bool SetValue(const string& key, const string& value) = 0; + + // Correct: + virtual Core::hresult GetValue(const string& key, string& value /* @out */) = 0; + virtual Core::hresult SetValue(const string& key, const string& value) = 0; + + citation: + line_format: "[PluginName.h:LINE] COM interface method does not return Core::hresult" + rule: "thunder-plugin-rules.yaml / rule_39" + +# =========================================================================================== +# HOLISTIC RULES — 8 sub-phases (40 rules: rule_40 to rule_79) +# Conventions & Encapsulation | Lifecycle & State Integrity | Concurrency & Threading +# COM Reference & Memory Safety | Resource Management | JSON-RPC Compliance +# Inter-Plugin & OOP Design | Code Quality & Security +# =========================================================================================== + +general_rules: + - rule_id: "rule_40" + name: "#pragma once" + severity: "suggestion" + category: "conventions" + review_question: "Using semantic reasoning over all header files in the plugin, does every header file use #pragma once as its include guard?" + 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." + + - rule_id: "rule_41" + name: "Apache 2.0 Copyright Header" + severity: "suggestion" + category: "conventions" + review_question: "Using semantic reasoning over all source files, does every file contain a proper Apache 2.0 copyright header at the top?" + 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." + + - rule_id: "rule_42" + name: "STL Types" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over function signatures, class members, and return types, are Thunder type aliases (string, vector) used instead of their std:: equivalents (std::string, std::vector) wherever Thunder aliases are available?" + 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." + + - rule_id: "rule_43" + name: "ASSERT vs Error Handling" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over the full code context, is ASSERT used only for programmer invariants (things that must always be true) and not for runtime error handling (things that might fail in production)?" + 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." + + - rule_id: "rule_44" + name: "OOP Registration Order" + severity: "violation" + category: "lifecycle_integrity" + review_question: "In out-of-process plugins, using semantic reasoning over Initialize() and Deinitialize(), are service->Register() and service->Unregister() called in the correct order relative to acquiring and releasing the remote implementation?" + 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." + + - rule_id: "rule_45" + name: "Complete State Reset in Deinitialize" + severity: "violation" + category: "lifecycle_integrity" + review_question: "Using semantic reasoning over Deinitialize() and the class member variables, is every member variable that was set during Initialize() or operation properly reset to its default/null state in Deinitialize(), such that a subsequent Initialize() call would start from a clean state?" + 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." + + - rule_id: "rule_46" + name: "Reverse-Order Cleanup" + severity: "suggestion" + category: "lifecycle_integrity" + review_question: "Using semantic reasoning over Initialize() and Deinitialize(), does Deinitialize() release resources in reverse order of how they were acquired in 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." + + - rule_id: "rule_47" + name: "Observer Locking" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over the notification observer pattern implementation, are observer lists protected by appropriate locking when iterated and when observers are added/removed, to prevent data races?" + 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." + + - rule_id: "rule_48" + name: "AddRef/Release Balance" + severity: "violation" + category: "com_safety" + review_question: "Using semantic reasoning over all code paths including error paths, is every AddRef() call on a COM interface balanced by exactly one Release() call — no leaks and no double-releases?" + 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." + + - rule_id: "rule_49" + name: "CMake NAMESPACE Variable" + severity: "suggestion" + category: "conventions" + review_question: "Using semantic reasoning over CMakeLists.txt, does the CMake configuration use the ${NAMESPACE} variable for target naming and installation paths as per Thunder conventions?" + 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." + + - rule_id: "rule_50" + name: "Handlers Must Not Block" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all notification handler implementations (IPlugin::INotification callbacks, IRemoteConnection::INotification callbacks), do none of the handlers perform blocking operations (network calls, file I/O, sleep, synchronous waiting) on the framework's callback thread?" + 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." + + - rule_id: "rule_51" + name: "No Activate/Deactivate from Handlers" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all notification callback implementations, do none of the callbacks call service->Activate() or service->Deactivate() directly (which would cause deadlock on the framework thread)?" + 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." + + - rule_id: "rule_52" + name: "Shared State Protected by CriticalSection" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over the relevant code paths, is every member variable accessed from both the main thread and notification callbacks protected by a Core::CriticalSection lock?" + 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." + + - rule_id: "rule_53" + name: "No Lock Held During Framework Callbacks" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all lock acquisition and framework callback invocations, are all locks released before any call back into the Thunder framework (e.g. before calling service->Submit(), Activate(), or notification dispatch) to prevent deadlock?" + 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." + + - rule_id: "rule_54" + name: "Worker Jobs Check Deinitialize Guard" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all worker job (IJob, WorkerPool) implementations, do the job Dispatch() methods check a deinitialization guard flag at the start to avoid using stale pointers after Deinitialize() has run?" + 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." + + - rule_id: "rule_55" + name: "File Descriptors / Sockets Wrapped in RAII" + severity: "violation" + category: "resource_management" + review_question: "Using semantic reasoning over resource management, are all file descriptors, sockets, and OS handles wrapped in RAII types or explicitly closed in Deinitialize() — never leaked?" + 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." + + - rule_id: "rule_56" + name: "No Unbounded Memory Growth" + severity: "violation" + category: "resource_management" + review_question: "Using semantic reasoning over all data structures (maps, vectors, lists, queues) that are populated at runtime, is there a bounded size or eviction policy that prevents unbounded memory growth during sustained plugin operation?" + 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." + + - rule_id: "rule_57" + name: "Config Errors Return Non-Empty from Initialize" + severity: "violation" + category: "lifecycle_integrity" + review_question: "Using semantic reasoning over Initialize() and configuration parsing, do all configuration errors (missing required fields, invalid values, out-of-range values) result in Initialize() returning a non-empty error string?" + 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." + + - rule_id: "rule_58" + name: "interface->Register/Unregister Pairing" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over Initialize() and Deinitialize(), is every interface->Register() (registering a notification/callback on a non-service interface) matched by a corresponding interface->Unregister() in Deinitialize()?" + 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." + + - rule_id: "rule_59" + name: "Handler Registration Order in Initialize/Deinitialize" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over Initialize() and Deinitialize(), are notification handlers registered AFTER the interfaces they observe are fully acquired and set up, and unregistered BEFORE those interfaces are released, to avoid callbacks on partially-initialized state?" + 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." + + - rule_id: "rule_60" + name: "Use Core::ERROR_* for Handler Failure Codes" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over all JSON-RPC handler implementations and COM interface method implementations, are failures reported using Core::ERROR_* constants rather than raw numeric literals or Windows error codes?" + 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." + + - rule_id: "rule_61" + name: "Input Validation in JSON-RPC Handlers" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over all JSON-RPC handler implementations, does each handler validate its input parameters (null pointer checks, string length limits, enum range checks) before using them?" + 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." + + - rule_id: "rule_62" + name: "Event Constants and Typed JSON Payloads" + severity: "warning" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over JSON-RPC event dispatching, are event names defined as named constants rather than inline string literals, and are event payloads typed JSON objects rather than untyped free-form strings?" + 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." + + - rule_id: "rule_63" + name: "COM Reference Counting Correctness" + severity: "violation" + category: "com_safety" + review_question: "Using semantic reasoning over all code paths where COM interfaces are passed, returned, or stored, is COM reference counting correctly maintained — AddRef called when storing, Release called when done, and no double-release or leak on any code path including error paths?" + 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." + + - rule_id: "rule_64" + name: "No Hard Inter-Plugin Dependencies" + severity: "warning" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over all plugin interactions, does the plugin use the observer pattern and dynamic interface acquisition (QueryInterfaceByCallsign, IPlugin::INotification) for inter-plugin communication, rather than hard compile-time or startup-time dependencies on other specific plugins being present?" + 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." + + - rule_id: "rule_65" + name: "JSON-RPC Handlers Are Re-entrant Safe" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all JSON-RPC handler implementations and shared state they access, are all handlers safe for concurrent invocation — either accessing only local state or properly locking shared state?" + 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." + + - rule_id: "rule_66" + name: "IPlugin::INotification Callbacks Must Not Block" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all IPlugin::INotification callback implementations (Activated, Deactivated, Unavailable), do none of them block the calling thread — no synchronous waits, no mutex acquisitions that could be contended, no I/O?" + 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." + + - rule_id: "rule_67" + name: "Lock Scope Minimized" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over all locking patterns in the plugin, are all critical sections held for the minimum time necessary — covering only the actual shared state access, not blocking operations, callbacks, or I/O while holding the lock?" + 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." + + - rule_id: "rule_68" + name: "Plugin Threads Joined in Deinitialize" + severity: "violation" + category: "concurrency" + review_question: "Using semantic reasoning over thread lifecycle management, are all threads started by the plugin explicitly stopped and joined (or detached with a lifetime guarantee) in Deinitialize() before the plugin returns from that function?" + 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." + + - rule_id: "rule_69" + name: "Memory and Allocation Safety" + severity: "warning" + category: "resource_management" + review_question: "Using semantic reasoning over all dynamic memory allocation and deallocation, are there no memory leaks (every new has a corresponding delete on all code paths), no use-after-free, and no buffer overflows in the plugin?" + 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." + + - rule_id: "rule_70" + name: "Framework Pointers Not Accessed After Deinitialize" + severity: "violation" + category: "lifecycle_integrity" + review_question: "Using semantic reasoning over all worker threads, background jobs, and async callbacks, is there a guarantee that none of them can access framework pointers (IShell*, service pointers, notification interfaces) after Deinitialize() has completed and nulled those pointers?" + 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." + + - rule_id: "rule_71" + name: "hresult Return Values Checked" + severity: "violation" + category: "com_safety" + review_question: "Using semantic reasoning over all COM interface method calls, are return values of Core::hresult-returning methods checked and errors handled appropriately — not silently discarded?" + 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." + + - rule_id: "rule_72" + name: "ASSERT Only for Programmer Invariants" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over all ASSERT usages, is ASSERT used only for conditions that represent programmer errors (invariants that should never be false in correct code) and never for conditions that can legitimately occur at runtime (user input errors, network failures, missing configuration)?" + 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." + + - rule_id: "rule_73" + name: "Security: Logging, Shell, Path, and Error Exposure" + severity: "violation" + category: "code_quality_security" + review_question: "Using semantic reasoning over all logging, error reporting, and external command execution, does the plugin avoid: logging sensitive data (passwords, tokens, PII), executing shell commands with unsanitized input, exposing internal error details through JSON-RPC responses, and path traversal via external inputs?" + 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." + + - rule_id: "rule_74" + name: "JSON-RPC Input Validation for Bounds and Types" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over all JSON-RPC handler implementations, does each handler validate numeric inputs for range bounds and type correctness before using them in operations that could overflow, underflow, or cause undefined behavior?" + 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." + + - rule_id: "rule_75" + name: "Config Completeness and Resource Cleanup" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over the Config class and Initialize(), are all configuration fields registered with Add() in the Config constructor, and are all resources acquired based on configuration values properly released in Deinitialize()?" + 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." + + - rule_id: "rule_76" + name: "OOP Error Propagation and Method Naming" + severity: "warning" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over OOP plugin implementations, do out-of-process method implementations properly propagate errors from the remote process back to the plugin host, and do method names follow Thunder naming conventions (no abbreviations, clear verb+noun)?" + 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." + + - rule_id: "rule_77" + name: "Observer Classes Private and Nested" + severity: "suggestion" + category: "conventions" + review_question: "Using semantic reasoning over observer/notification class declarations, are observer/notification classes declared as private nested classes within the plugin class (not as separate public classes in the header), keeping the plugin's internal observer implementation encapsulated?" + 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." + + - rule_id: "rule_78" + name: "No Deprecated JSON-RPC APIs" + severity: "violation" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over all JSON-RPC registration and dispatch code, are there no uses of deprecated JSON-RPC APIs (e.g. the legacy Register overloads replaced by typed versions, deprecated event dispatch patterns)?" + 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." + + - rule_id: "rule_79" + name: "All Acquired Pointers Cleared After Deinitialize" + severity: "violation" + category: "lifecycle_integrity" + review_question: "Using semantic reasoning over all member pointer variables and Deinitialize(), is every pointer member that was set to a non-null value during the plugin's lifetime explicitly set to nullptr in Deinitialize() (in addition to being released), such that a double-Deinitialize would not cause a double-release?" + 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." diff --git a/PluginQualityAdvisor/setup-prompts.py b/PluginQualityAdvisor/setup-prompts.py new file mode 100644 index 00000000..e5bb2816 --- /dev/null +++ b/PluginQualityAdvisor/setup-prompts.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +""" +setup-prompts.py — Registers ThunderTools PluginQA prompts with VS Code. + +Adds "ThunderTools/PluginQA/Prompts": true to chat.promptFilesLocations +in VS Code settings.json. Works on Windows, macOS, and Linux. + +No external dependencies — stdlib only. +Safe to run multiple times (idempotent). Creates a timestamped backup. +""" + +import json +import os +import platform +import shutil +import sys +from datetime import datetime + +PROMPTS_KEY = "ThunderTools/PluginQA/Prompts" + + +def find_settings_path(): + """Detect VS Code settings.json path for the current platform.""" + system = platform.system() + + if system == "Windows": + appdata = os.environ.get("APPDATA", "") + candidates = [ + os.path.join(appdata, "Code", "User", "settings.json"), + os.path.join(appdata, "Code - Insiders", "User", "settings.json"), + ] + elif system == "Darwin": + home = os.path.expanduser("~") + candidates = [ + os.path.join(home, "Library", "Application Support", "Code", "User", "settings.json"), + os.path.join(home, "Library", "Application Support", "Code - Insiders", "User", "settings.json"), + ] + else: + # Linux + home = os.path.expanduser("~") + candidates = [ + os.path.join(home, ".config", "Code", "User", "settings.json"), + os.path.join(home, ".config", "Code - Insiders", "User", "settings.json"), + ] + + # Return the first candidate whose parent directory exists + for path in candidates: + parent = os.path.dirname(path) + if os.path.isdir(parent): + return path + + # Fall back to first candidate even if parent doesn't exist + return candidates[0] + + +def read_settings(settings_path): + """Read and parse settings.json, returning {} if missing or empty.""" + if not os.path.isfile(settings_path): + return {} + with open(settings_path, "r", encoding="utf-8") as f: + content = f.read().strip() + if not content: + return {} + try: + return json.loads(content) + except (json.JSONDecodeError, ValueError): + print("WARNING: Could not parse settings.json as JSON. Starting with empty settings.") + return {} + + +def create_backup(settings_path): + """Create a timestamped backup of settings.json. Returns backup path.""" + if not os.path.isfile(settings_path): + return None + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = f"{settings_path}.backup.{timestamp}" + if not os.path.exists(backup_path): + shutil.copy2(settings_path, backup_path) + return backup_path + return backup_path + + +def main(): + # Allow override via environment variable + settings_path = os.environ.get("VSCODE_SETTINGS_PATH") or find_settings_path() + print(f"VS Code settings.json: {settings_path}") + + # Ensure parent directory exists + settings_dir = os.path.dirname(settings_path) + os.makedirs(settings_dir, exist_ok=True) + + # Read existing settings + settings = read_settings(settings_path) + + # Check if already configured (idempotent) + prompt_locations = settings.get("chat.promptFilesLocations", {}) + if isinstance(prompt_locations, dict) and prompt_locations.get(PROMPTS_KEY) is True: + print() + print("Already configured — 'ThunderTools/PluginQA/Prompts' is already in chat.promptFilesLocations.") + print("No changes needed.") + print() + print("Available slash commands:") + print(" /thunder-plugin-review ") + print(" /thunder-interface-review ") + print(" /thunder-generate-plugin") + sys.exit(0) + + # Create backup before modifying + backup_path = create_backup(settings_path) + if backup_path: + print(f"Backup created: {backup_path}") + + # Merge the new entry + if not isinstance(prompt_locations, dict): + prompt_locations = {} + prompt_locations[PROMPTS_KEY] = True + settings["chat.promptFilesLocations"] = prompt_locations + + # Write back with indent=4 + with open(settings_path, "w", encoding="utf-8") as f: + json.dump(settings, f, indent=4) + f.write("\n") + + # Success output + print() + print("SUCCESS: settings.json updated.") + print() + if backup_path: + print(f"Backup location: {backup_path}") + print() + print("Next steps:") + print(" 1. In VS Code, press Ctrl+Shift+P (or Cmd+Shift+P on Mac)") + print(" 2. Type 'Developer: Reload Window' and press Enter") + print() + print("Available slash commands after reload:") + print(" /thunder-plugin-review ") + print(" /thunder-interface-review ") + print(" /thunder-generate-plugin") + print() + + +if __name__ == "__main__": + main() From d2ea36cf585d09b9bb6c5847c226a529a7b2d714 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Fri, 10 Jul 2026 14:38:59 +0530 Subject: [PATCH 11/15] Review comments have been addressed --- .../changes/thunder-plugin-qa/design.md | 24 +- .../changes/thunder-plugin-qa/proposal.md | 14 +- .../interface/Thunder-interface-rules.md | 687 +++++++++++------- .../thunder-plugin-qa/specs/interface/spec.md | 27 +- .../specs/plugin/Thunder-plugin-rules.md | 539 ++++---------- .../thunder-plugin-qa/specs/plugin/spec.md | 113 ++- .../thunder-plugin-qa/specs/reports/spec.md | 16 +- .../changes/thunder-plugin-qa/tasks.md | 199 +++-- .../thunder-interface-review.prompt.md | 71 +- .../thunder-interface-rule-manager.prompt.md | 17 +- .../Prompts/thunder-plugin-review.prompt.md | 340 ++------- .../thunder-plugin-rule-manager.prompt.md | 18 +- PluginQualityAdvisor/README.md | 16 +- .../interface-rule-template-guide.md | 14 +- .../plugin-rule-template-guide.md | 30 +- .../rules/thunder-interface-rules.yaml | 308 ++++---- .../rules/thunder-plugin-rules.yaml | 334 +++------ PluginQualityAdvisor/setup-prompts.py | 12 +- 18 files changed, 1196 insertions(+), 1583 deletions(-) diff --git a/.github/openspec/changes/thunder-plugin-qa/design.md b/.github/openspec/changes/thunder-plugin-qa/design.md index e7545717..62289915 100644 --- a/.github/openspec/changes/thunder-plugin-qa/design.md +++ b/.github/openspec/changes/thunder-plugin-qa/design.md @@ -14,7 +14,7 @@ User types /thunder-plugin-review Dictionary thunder-plugin-review.prompt.md │ ▼ -rules/thunder-plugin-rules.yaml ← 79 unified rules (rule_01 to rule_79) +rules/thunder-plugin-rules.yaml ← 70 unified rules (rule_01 to rule_70) │ ▼ Plugin files in ThunderNanoServices/Dictionary/ @@ -47,7 +47,7 @@ Atomically updates 4 files: **File type:** VS Code Copilot Chat prompt files (`.prompt.md`) **Registration:** `chat.promptFilesLocations` key in VS Code `settings.json` - set to `ThunderTools/PluginQA/Prompts: true` + set to `ThunderTools/PluginQualityAdvisor/Prompts: true` **Invocation:** User types `/thunder-plugin-review`, `/thunder-interface-review`, `/thunder-generate-plugin`, `/thunder-plugin-rule-manager`, or `/thunder-interface-rule-manager` in Copilot Chat @@ -61,10 +61,10 @@ description: Semantic code review for Thunder plugins — understand first, then --- ``` -## Unified Review Methodology (ALL 79 rules) +## Unified Review Methodology (ALL 70 rules) Every rule — whether it targets a specific block (rule_01–39) or a broader concern -(rule_40–79) — uses the same "understand first, then check" approach: +(rule_39–70) — uses the same "understand first, then check" approach: 1. **UNDERSTAND** — Read ALL plugin source files first. Build a complete mental model of the plugin's architecture: lifecycle flow, threading model, ownership patterns, @@ -102,7 +102,7 @@ defined in the YAML source. The YAML file contains two sections that produce identical report output: -**Phase checkpoints** (39 rules, under `phase_X_checkpoints` keys): +**Phase checkpoints** (38 rules, under `phase_X_checkpoints` keys): - Fields: `rule_id`, `name`, `severity`, `phase`, `extraction`, `bounded_query`, `verification_logic`, `conditional`, `skip_condition`, `citation`, `fix_template` **Holistic Rules (8 sub-phases)** (40 rules, under `general_rules` key): @@ -221,7 +221,6 @@ text like `{PluginName}` in output. - Internal helper classes (Notification, Sink, Config, etc.) are excluded **Conditional checkpoints:** -Checkpoints rule_09, rule_16, rule_17, rule_18, rule_20, rule_21, rule_22, rule_23, rule_25, rule_29, rule_30, rule_31, rule_33, rule_34, rule_35, rule_36, and rule_38 are conditional. If the prerequisite is not found (e.g. no stored IShell pointer for rule_17), the checkpoint SKIPS — it does not fail. ## Plugin Generator Design @@ -253,7 +252,7 @@ The `setup-prompts.py` script does the following: 1. Detect VS Code settings.json location (platform-specific paths, also checks VS Code Insiders) 2. Create a timestamped backup of existing settings.json 3. Parse the JSON safely (handle missing file, handle existing `chat.promptFilesLocations`) -4. Merge the new entry: `"ThunderTools/PluginQA/Prompts": true` +4. Merge the new entry: `"ThunderTools/PluginQualityAdvisor/Prompts": true` 5. Write back to settings.json 6. Print success message and next steps (Ctrl+Shift+P → Reload Window) @@ -263,15 +262,14 @@ The `setup-prompts.py` script does the following: ## YAML File Versioning - `thunder-plugin-rules.yaml`: version 3.3.0 - - 39 checkpoints, organisation: Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, + - 38 checkpoints, organisation: Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1 - New checkpoints added over v1.0.0: rule_08 (nullptr after Release), rule_09–10 (COM ownership + no-throw), rule_16 - (INTERFACE_MAP + JSONRPC), rule_20–rule_23 (full lifecycle correctness), rule_31 (Unavailable in SinkType), - rule_34 (connectionId guard), - rule_36 (JSON::Container configuration), rule_37 (no hardcoded numeric tuning parameters) + rule_33 (connectionId guard), + rule_35 (JSON::Container configuration), rule_36 (no hardcoded numeric tuning parameters) - `thunder-interface-rules.yaml`: version 3.2.2 - - 15 core rules + 4 advisory = 19 total + - 16 core rules + 3 advisory = 17 total - Core::hresult MANDATORY for Thunder 5.0+ @json interfaces - - advisory_m5_1 explicitly excludes std::vector (core_17_1 covers that) + - advisory_m3_1 explicitly excludes std::vector (core_16_1 covers that) - No `category` field — removed in v3.2.2 \ No newline at end of file diff --git a/.github/openspec/changes/thunder-plugin-qa/proposal.md b/.github/openspec/changes/thunder-plugin-qa/proposal.md index 214f728c..f992b277 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 PluginQA System +# Proposal: Thunder PluginQualityAdvisor System ## Intent @@ -22,7 +22,7 @@ automatically. - Keeps all affected files in sync atomically: YAML + prompt + README + spec **Plugin checkpoint validation (`/thunder-plugin-review`)** -- 79 unified rules numbered sequentially (rule_01 to rule_79) +- 70 unified rules numbered sequentially (rule_01 to rule_70) - Each rule: read code semantically -> decide pass/fail -> cite exact line on failure - Phases: Module Structure, Code Style, Class Registration, Lifecycle, Implementation, COM Interface Rules, Out-of-Process, Configuration, CMake, General @@ -30,10 +30,10 @@ automatically. - Report shows ONLY failures - PASS/SKIP appear as counts in summary table only **COM interface validator (`/thunder-interface-review`)** -- 19 rules: 15 core (VIOLATION level) + 4 advisory +- 19 rules: 16 core + 3 advisory - Validates Thunder COM interfaces in ThunderInterfaces/interfaces/ - Covers: file structure, @json tag, @restrict on vectors, return types, ID registration, - nested event interfaces, binary compatibility, no std::map, explicit integer widths + event interfaces, no std::map, explicit integer widths **Plugin skeleton generator (`/thunder-generate-plugin`)** - Interactive: collects parameters via VS Code `vscode_askQuestions` (NOT chat) @@ -46,7 +46,7 @@ automatically. - Safe: creates timestamped backup, preserves existing settings, idempotent **YAML rule definitions** (loaded by prompts at runtime, not embedded) -- `thunder-plugin-rules.yaml` — 79 unified rules (v3.3.0) +- `thunder-plugin-rules.yaml` — 70 unified rules (v3.3.0) - `thunder-interface-rules.yaml` — 19 interface rules (v3.2.2) **Review reports** (CSV, generated after each review run) @@ -68,7 +68,7 @@ automatically. Use VS Code `.prompt.md` files as slash commands. Each prompt loads its YAML rule definitions at runtime so rules can be updated without touching prompt logic. Plugin validation uses semantic code review: read code as a human -developer and reason about meaning. All 79 rules produce the same unified +developer and reason about meaning. All 70 rules produce the same unified output format — no distinction between "automated" and "manual" in the report. All checkpoint verification uses semantic reasoning — the validator reads code @@ -84,7 +84,7 @@ it is acceptable. Severity is never escalated above what the YAML defines. ## Delivery Structure ``` -ThunderTools/PluginQA/ +ThunderTools/PluginQualityAdvisor/ ├── README.md ├── setup-prompts.py (cross-platform, Python 3) ├── Prompts/ diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 2b483ecc..468fb94c 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -1,85 +1,114 @@ # Thunder Interface Validation Rules - -**Status:** For Review +**Status:** For Review ## Description -Rules for validating Thunder COM interface headers in `ThunderInterfaces/interfaces/`. +Rules for validating Thunder COM interface headers in `ThunderInterfaces/interfaces/`. + Every rule uses semantic reasoning — read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. --- -## Core Rules (15) — Severity: Violation +## Summary (19 rules: 16 core + 3 advisory) + +| ID | Name | Severity | +|----|------|----------| +| core_1_1 | File Structure | suggestion | +| core_2_1 | Interface Declaration Shape | warning | +| core_3_1 | Interface ID Registration | violation | +| core_4_1 | Pure Virtual Methods Only | warning | +| core_5_1 | Return Type Conventions | violation | +| core_6_1 | Const Correctness | violation | +| core_9_1 | Thunder Type Conventions | violation | +| core_10_1 | Register/Unregister Patterns | violation | +| core_11_1 | Event Interfaces | violation | +| core_12_1 | @json Tag (CRITICAL) | warning | +| core_13_1 | No IUnknown/IReferenceCounted Methods in Interfaces | violation | +| core_14_1 | No std::map in Interfaces | violation | +| core_15_1 | Explicit Integer Widths | violation | +| core_16_1 | @restrict Mandatory with std::vector | violation | +| core_17_1 | No Method Overloads in @json Interfaces | violation | +| core_18_1 | No Reserved JSON-RPC Method Names | violation | +| advisory_m1_1 | Single Responsibility Principle | warning | +| advisory_m2_1 | Enum Underlying Types | warning | +| advisory_m3_1 | No Exceptions | violation | + +--- + +## Core Rules (16) -### core_1_1 — File and Namespace Structure +### core_1_1 — File Structure -**Severity:** violation +**Severity:** suggestion -**Description:** -Thunder interface headers must follow the standard file and namespace structure: -- File must be in `ThunderInterfaces/interfaces/` (or `qa_interfaces/` for QA) -- Interface must be declared inside the `WPEFramework::Exchange` namespace -- File name must match the interface name: `IFoo.h` for `struct EXTERNAL IFoo` +**Description:** + +Thunder interface headers must follow a standard structure: +- File name should match the interface name (IFoo.h for struct EXTERNAL IFoo), + though exceptions may exist - No implementation code in interface headers — pure declarations only **Extraction Logic:** + 1. Read the full interface header file -2. Identify the namespace block(s) and their names -3. Note the file name and the primary interface struct name -4. Check for any non-declaration code (function implementations, static variables, etc.) +2. Note the file name and the primary interface struct name +3. Check for any non-declaration code (function implementations, static variables, etc.) **Verification Logic:** -1. Verify the file is inside the `WPEFramework::Exchange` namespace -2. Verify the primary interface struct name matches the file name (e.g. `IDictionary` in `IDictionary.h`) -3. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations -4. If any of these conditions fail → VIOLATION -**Violation Pattern:** Interface not in WPEFramework::Exchange namespace, file name mismatch, or implementation code present +1. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) + - Exception: multi-interface files or platform-specific groupings may have different naming +2. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations +3. If issues are found → WARNING + +**Violation Pattern:** File name does not match interface name, or implementation code present in interface header **Fix Example:** ```cpp -// WRONG: -namespace WPEFramework { - // Missing Exchange namespace - struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; -} - -// Correct: -namespace WPEFramework { - namespace Exchange { - struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; +// WRONG: Implementation code in interface header +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value) { + return Core::ERROR_NONE; // ← implementation code not allowed } -} +}; + +// Correct: pure declarations only +struct EXTERNAL IDictionary : virtual public Core::/t { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +}; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Exchange namespace and file naming +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — Exchange namespace and file naming --- ### core_2_1 — Interface Declaration Shape -**Severity:** violation +**Severity:** warning + +**Description:** -**Description:** Thunder COM interfaces must follow the correct declaration shape: -- Must be a struct (not class) with the `EXTERNAL` macro -- Must inherit virtually from `Core::IUnknown` (or from another Thunder interface) -- Must declare a nested enum with the ID value (`RPC::ID_*`) +- Must be a struct (not class) with the EXTERNAL macro +- Must inherit virtually from Core::IUnknown +- Must declare a nested enum with the ID value (RPC::ID_*) - All methods must be pure virtual **Extraction Logic:** + 1. Read the interface struct declaration 2. Examine the struct keyword, EXTERNAL macro, and inheritance list -3. Check for the nested `enum { ID = RPC::ID_* }` -4. Examine all method declarations for pure virtual (`= 0`) +3. Check for the nested enum { ID = RPC::ID_* } +4. Examine all method declarations for pure virtual (= 0) **Verification Logic:** -1. Verify the declaration uses `struct EXTERNAL IName` -2. Verify inheritance is `virtual public Core::IUnknown` or a Thunder interface -3. Verify a nested enum contains `ID = RPC::ID_*` -4. Verify all methods are pure virtual (`= 0`) + +1. Verify the declaration uses 'struct EXTERNAL IName' +2. Verify inheritance is 'virtual public Core::IUnknown' +3. Verify a nested enum contains ID = RPC::ID_* +4. Verify all methods are pure virtual (= 0) 5. If any fails → VIOLATION **Violation Pattern:** Interface missing EXTERNAL macro, wrong inheritance, missing ID enum, or non-pure-virtual methods @@ -99,7 +128,7 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { }; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — struct EXTERNAL shape +**Citation:** ThunderInterfaces/interfaces/IDictionary.h:LINE — struct EXTERNAL shape --- @@ -107,20 +136,40 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { **Severity:** violation -**Description:** -Every Thunder COM interface must have a unique numeric ID registered in `RPC::IDs` (in `ThunderInterfaces/interfaces/ids.h` or equivalent). The ID enum value in the struct must reference the registered `RPC::ID_*` constant. Sub-interfaces (`INotification`, `ICallback` nested in a parent) must also have their own unique IDs. +**Description:** + +Every Thunder COM interface must have a unique numeric ID registered in +RPC::IDs (in ThunderInterfaces/interfaces/ids.h or equivalent). +The ID enum value in the struct must reference the registered RPC::ID_* constant. +Sub-interfaces (INotification, ICallback nested in a parent) must also have their +own unique IDs. + +ID ranges (defined in Thunder/Source/com/Ids.h): +- Thunder core interfaces: ID_OFFSET_INTERNAL + 0x0001 .. 0x007F +- Extension interfaces: ID_EXTENSIONS_INTERFACE_OFFSET (ID_OFFSET_INTERNAL + 0x0080) +- External (ThunderInterfaces) interfaces: ID_EXTERNAL_INTERFACE_OFFSET (ID_OFFSET_INTERNAL + 0x1000) +- QA interfaces: ID_EXTERNAL_QA_INTERFACE_OFFSET (0xA000) +- Example interfaces: ID_EXTERNAL_EXAMPLE_INTERFACE_OFFSET (0xB000) +- CC interfaces (entservices-apis): ID_EXTERNAL_CC_INTERFACE_OFFSET (0xCC00 .. 0xDFFF) + +Interfaces in entservices-apis MUST use IDs within the CC range +(RPC::IDS::ID_EXTERNAL_CC_INTERFACE_OFFSET + offset, range 0xCC00–0xDFFF). **Extraction Logic:** + 1. Read the interface struct declaration and note its ID value -2. Check whether the ID value uses an `RPC::ID_*` constant -3. Check `ids.h` (or the ID registration file) for the corresponding entry +2. Check whether the ID value uses an RPC::ID_* constant +3. Check ids.h (or the ID registration file) for the corresponding entry +4. Determine whether the interface belongs to entservices-apis (CC range) or ThunderInterfaces (external range) **Verification Logic:** -1. Verify the struct `enum { ID = RPC::ID_* }` uses a named constant, not a raw number -2. Verify the ID is registered in ThunderInterfaces `ids.h` (or equivalent) + +1. Verify the struct enum { ID = RPC::ID_* } uses a named constant, not a raw number +2. Verify the ID is registered in ThunderInterfaces ids.h (or equivalent) 3. Verify the ID is unique — no other interface uses the same value -4. Nested `INotification` and `ICallback` interfaces must also have their own IDs -5. If any condition fails → VIOLATION +4. Nested INotification and ICallback interfaces must also have their own IDs +5. For entservices-apis interfaces: verify the ID falls within the CC range (0xCC00–0xDFFF) +6. If any condition fails → VIOLATION **Violation Pattern:** Interface ID missing from IDs registration, uses raw number, or is not unique @@ -136,25 +185,31 @@ enum { ID = RPC::ID_DICTIONARY }; // ID_DICTIONARY = RPC_ID_OFFSET + N, ``` -**Citation:** `ThunderInterfaces/interfaces/ids.h` — ID_DICTIONARY registration +**Citation:** ThunderInterfaces/interfaces/ids.h — ID_DICTIONARY registration --- ### core_4_1 — Pure Virtual Methods Only -**Severity:** violation +**Severity:** warning -**Description:** -Thunder COM interface methods must be pure virtual (`= 0`). No default implementations, no inline code, no static methods, no non-virtual methods. The interface is a pure abstract contract — all implementation is in the implementing class, not in the interface. +**Description:** + +Thunder COM interface methods must be pure virtual (= 0). No default +implementations, no inline code, no static methods, no non-virtual methods. +The interface is a pure abstract contract — all implementation is in the +implementing class, not in the interface. **Extraction Logic:** + 1. Read all method declarations in the interface struct -2. Check each method for the `= 0` specifier +2. Check each method for the = 0 specifier 3. Look for any inline code, default implementations, or static methods **Verification Logic:** -1. Every method in the interface must end with `= 0` -2. No method may have a body (even `{}`) + +1. Every method in the interface must end with = 0 +2. No method may have a body (even {}) 3. No static methods allowed in interfaces 4. No non-virtual methods (constructors, operators excepted) 5. If any violation → VIOLATION @@ -174,7 +229,7 @@ virtual Core::hresult Get(const string& key, string& value) { virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — pure virtual methods +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — pure virtual methods --- @@ -182,19 +237,34 @@ virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; **Severity:** violation -**Description:** -In Thunder 5.0+, all COM interface methods annotated with `@json` (i.e. methods that generate JSON-RPC code) MUST return `Core::hresult`. Void return types and other return types are not allowed for JSON-RPC-generating methods. Methods in pure COM interfaces (no `@json`) should also use `Core::hresult` for error reporting where applicable. +**Description:** + +In Thunder 5.0+, all COM interface methods annotated with @json (i.e. methods +that generate JSON-RPC code) MUST return Core::hresult. Void return types and +other return types are not allowed for JSON-RPC-generating methods. +Methods in pure COM interfaces (no @json) should also use Core::hresult for +error reporting where applicable. + +Exceptions: +- Notification/event methods (methods in interfaces tagged with @event, such as + INotification or ICallback) MUST return void, not Core::hresult. +- Legacy/old interfaces (pre-Thunder 5.0) may not adhere to this rule. **Extraction Logic:** + 1. Read all interface method declarations -2. Note which methods are under a `@json`-tagged interface or have `@json` annotations -3. Check the return type of each method +2. Note which methods are under a @json-tagged interface or have @json annotations +3. Identify notification/event interfaces (tagged with @event) +4. Check the return type of each method **Verification Logic:** -1. For interfaces tagged with `@json` (or in Thunder 5.0+ contexts), all methods must return `Core::hresult` -2. Void return types are not allowed for JSON-RPC methods -3. Custom return types (not `Core::hresult`) for non-status purposes are not allowed — use `@out` parameters instead -4. If any JSON-RPC method lacks `Core::hresult` return type → VIOLATION + +1. For interfaces tagged with @json (or in Thunder 5.0+ contexts), all methods must return Core::hresult +2. Exception: methods in @event-tagged notification interfaces MUST return void +3. Void return types are not allowed for non-notification JSON-RPC methods +4. Custom return types (not Core::hresult) for non-status purposes are not allowed — use @out parameters instead +5. Legacy interfaces (pre-Thunder 5.0) may not comply — flag as warning, not violation +6. If any non-notification JSON-RPC method lacks Core::hresult return type → VIOLATION **Violation Pattern:** Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+ @@ -210,7 +280,7 @@ virtual Core::hresult SetVolume(const uint8_t volume) = 0; virtual Core::hresult GetStatus(string& status /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IAudioOutput.h` — Core::hresult return types +**Citation:** ThunderInterfaces/interfaces/IAudioOutput.h — Core::hresult return types --- @@ -218,24 +288,29 @@ virtual Core::hresult GetStatus(string& status /* @out */) = 0; **Severity:** violation -**Description:** +**Description:** + Interface methods must use const correctly: -- Input-only parameters should be const (`const string& key`) -- Output parameters should be non-const references (`string& value`) +- Input-only parameters should be const (const string& key) +- Output parameters should be non-const references (string& value) - Methods that do not modify the object should be const (though pure virtual const is rare) -- `@out` parameters must be non-const references to allow the implementation to write +- @out parameters must be non-const references to allow the implementation to write +- Notification methods (methods in INotification/ICallback interfaces) should NEVER be const — + the implementation needs to modify state when handling notifications **Extraction Logic:** + 1. Read all method parameter declarations 2. Examine const qualifiers on each parameter -3. Identify parameters marked `@out` and verify they are non-const references +3. Identify parameters marked @out and verify they are non-const references 4. Identify input parameters and verify they are const where appropriate **Verification Logic:** -1. `@out` parameters must be non-const references (`string& value`, not `const string& value`) + +1. @out parameters must be non-const references (string& value, not const string& value) 2. Input parameters that are passed by value or const ref are correct -3. Non-const reference parameters without `@out` indicate potential API design issues -4. If `@out` parameters are const (preventing write) → VIOLATION +3. Non-const reference parameters without @out indicate potential API design issues +4. If @out parameters are const (preventing write) → VIOLATION **Violation Pattern:** @out parameter declared const preventing the implementation from writing the output value @@ -250,7 +325,7 @@ virtual Core::hresult Get(const string& key, const string& value /* @out */) = 0 virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — const correctness on @out params +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — const correctness on @out params --- @@ -258,26 +333,28 @@ virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; **Severity:** violation -**Description:** -Thunder interfaces must use Thunder type aliases, not `std::` types directly: -- `string` (not `std::string`) for text -- `Core::hresult` (not `HRESULT` or `int`) for error returns -- `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` (not `int`, `long`, `short`) -- `bool` (acceptable) but not `BOOL` +**Description:** -Using `std::string` in interfaces breaks cross-ABI compatibility. +Thunder interfaces must use Thunder type aliases, not std:: types directly: +- string (not std::string) for text +- Core::hresult (not HRESULT or int) for error returns +- uint8_t, uint16_t, uint32_t, uint64_t (not int, long, short) +- bool (acceptable) but not BOOL +Using std::string in interfaces breaks cross-ABI compatibility. **Extraction Logic:** + 1. Read all method parameter types and return types in the interface -2. Identify any `std::` type usage (`std::string`, `std::vector` without @restrict review) -3. Check for non-Thunder integer types (`int`, `long`, `short`, `BOOL`, `HRESULT`) +2. Identify any std:: type usage (std::string, std::vector without @restrict review) +3. Check for non-Thunder integer types (int, long, short, BOOL, HRESULT) **Verification Logic:** -1. `std::string` in interface parameters → VIOLATION (use `string`) -2. `HRESULT` or raw `int` for error codes → VIOLATION (use `Core::hresult`) -3. Non-width-specific integer types (`int`, `long`) for interface params → check core_16_1 -4. `BOOL` → VIOLATION (use `bool`) -5. If `std::string` found → VIOLATION + +1. std::string in interface parameters → VIOLATION (use string) +2. HRESULT or raw int for error codes → VIOLATION (use Core::hresult) +3. Non-width-specific integer types (int, long) for interface params → check core_15_1 +4. BOOL → VIOLATION (use bool) +5. If std::string found → VIOLATION **Violation Pattern:** std::string used in interface — must use Thunder string type alias @@ -291,7 +368,7 @@ virtual Core::hresult Get(const std::string& key, std::string& value) = 0; virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Thunder string type alias +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — Thunder string type alias --- @@ -299,21 +376,25 @@ virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; **Severity:** violation -**Description:** -Notification interfaces follow two patterns: -- **INotification (1:many observer):** `Register(INotification*)` and `Unregister(INotification*)` for multiple simultaneous subscribers -- **ICallback (1:1 callback):** `Callback(ICallback*)` sets a single callback (`nullptr` to clear) +**Description:** -Register/Unregister must take a pointer to the notification interface. ICallback::Callback() replaces the previous callback, so no Unregister needed. +Notification interfaces follow two patterns: +- INotification (1:many observer): Register(INotification*) and Unregister(INotification*) + for multiple simultaneous subscribers +- ICallback (1:1 callback): Callback(ICallback*) sets a single callback (nullptr to clear) +Register/Unregister must take a pointer to the notification interface. +ICallback::Callback() replaces the previous callback, so no Unregister needed. **Extraction Logic:** + 1. Read the interface for Register/Unregister/Callback method declarations 2. Identify whether it follows the INotification (1:many) or ICallback (1:1) pattern 3. Check the method signatures **Verification Logic:** -1. INotification pattern: must have both `Register(INotification*)` and `Unregister(INotification*)` -2. ICallback pattern: must have `Callback(ICallback*)` accepting nullptr to clear + +1. INotification pattern: must have both Register(INotification*) and Unregister(INotification*) +2. ICallback pattern: must have Callback(ICallback*) accepting nullptr to clear 3. Register without matching Unregister → VIOLATION 4. If notification registration pattern is non-standard → VIOLATION @@ -334,38 +415,39 @@ virtual Core::hresult Unregister(INotification* notification) = 0; virtual Core::hresult Callback(ICallback* callback) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — Register/Unregister pattern +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — Register/Unregister pattern --- -### core_11_1 — Nested Event Interfaces +### core_11_1 — Event Interfaces **Severity:** violation -**Description:** -Event/notification interfaces nested inside a parent COM interface must: -- Have the `@event` tag (`// @event`) above the struct declaration -- Use `EXTERNAL` in the struct declaration -- Have their own unique ID in the RPC ID list -- Inherit from `Core::IUnknown` (not from the parent interface) +**Description:** -Missing `@event` prevents the code generator from emitting event dispatch code. +Event/notification interfaces must: +- Have the @event tag (// @event) above the struct declaration +- Use EXTERNAL in the struct declaration +- Have their own unique ID in the RPC ID list +- Inherit from Core::IUnknown (not from the parent interface) +Missing @event prevents the code generator from emitting event dispatch code. **Extraction Logic:** -1. Read the interface for any nested struct declarations -2. For each nested struct that represents a notification/event (INotification, ICallback, etc.) -3. Check for the `@event` comment tag above the declaration -4. Check for `EXTERNAL` in the declaration -5. Check for a nested `enum { ID = RPC::ID_* }` + +1. Read the interface for any struct declarations that represent a notification/event (INotification, ICallback, etc.) +2. Check for the @event comment tag above the declaration +3. Check for EXTERNAL in the declaration +4. Check for an enum { ID = RPC::ID_* } **Verification Logic:** -1. Every nested event/notification interface must have `// @event` immediately above the struct -2. Must use `EXTERNAL` in the declaration + +1. Every event/notification interface must have // @event immediately above the struct +2. Must use EXTERNAL in the declaration 3. Must have its own ID in the RPC ID list -4. Must inherit from `Core::IUnknown` +4. Must inherit from Core::IUnknown 5. If any condition fails → VIOLATION -**Violation Pattern:** @event tag missing on nested notification interface, or missing EXTERNAL/ID +**Violation Pattern:** @event tag missing on notification interface, or missing EXTERNAL/ID **Fix Example:** @@ -382,29 +464,42 @@ struct EXTERNAL INotification : virtual public Core::IUnknown { }; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — @event tag on INotification +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — @event tag on INotification --- ### core_12_1 — @json Tag (CRITICAL) -**Severity:** violation +**Severity:** warning + +**Description:** -**Description:** -Without the `@json` tag, ZERO JSON-RPC code is generated for the interface. This is the most common critical omission. The tag must appear immediately above the struct declaration as: `// @json 1.0.0`. No blank lines are allowed between the `@json` tag and the struct declaration. +Without the @json tag, ZERO JSON-RPC code is generated for the interface. +This is the most common critical omission. The tag must appear immediately +above the struct declaration as: // @json 1.0.0 +No blank lines are allowed between the @json tag and the struct declaration. +If an interface intentionally does not need JSON-RPC (pure COM only), the +absence of @json is acceptable — but this must be an intentional design choice. -If an interface intentionally does not need JSON-RPC (pure COM only), the absence of `@json` is acceptable — but this must be an intentional design choice. +Hints that an interface was meant to be a JSON-RPC interface: +- Presence of tags like @alias, @text, @opaque, @bitmask, @index in comments +- Methods using @in/@out parameter annotations +- Interface resembles a service API (properties, events, methods) +If such hints are present but @json is missing, flag as warning and ask +whether the interface was intended to generate JSON-RPC code. **Extraction Logic:** -1. Read the lines immediately above the `struct EXTERNAL I...` declaration -2. Look for a `// @json` comment with a version number + +1. Read the lines immediately above the 'struct EXTERNAL I...' declaration +2. Look for a // @json comment with a version number 3. Check there are no blank lines between the tag and the struct declaration **Verification Logic:** -1. If the interface is intended to generate JSON-RPC code: verify `// @json N.N.N` appears immediately above the struct declaration -2. No blank lines between the `@json` tag and the struct line + +1. If the interface is intended to generate JSON-RPC code: verify // @json N.N.N appears immediately above the struct declaration +2. No blank lines between the @json tag and the struct line 3. If the interface is intentionally JSON-RPC-free (pure COM) → acceptable, note as design choice -4. If `@json` is missing from an interface that should have it → VIOLATION +4. If @json is missing from an interface that should have it → VIOLATION **Violation Pattern:** @json tag missing above interface struct declaration — no JSON-RPC code will be generated @@ -420,80 +515,42 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { struct EXTERNAL IDictionary : virtual public Core::IUnknown { ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h:LINE` — @json 1.0.0 above struct +**Citation:** ThunderInterfaces/interfaces/IDictionary.h:LINE — @json 1.0.0 above struct --- -### core_13_1 — Binary Compatibility +### core_13_1 — No IUnknown/IReferenceCounted Methods in Interfaces **Severity:** violation -**Description:** -Thunder COM interfaces are binary interfaces — once released, they cannot be changed in backward-incompatible ways without breaking all compiled clients: -- Methods must not be reordered (vtable order is fixed) -- Methods must not be removed (creates vtable holes) -- Method signatures must not change (parameter types/count/order) -- New methods must be added at the END of the interface +**Description:** -Binary-incompatible changes require creating a new interface version (`IFoo2`). +Methods inherited from Core::IUnknown or Core::IReferenceCounted (AddRef, Release, +QueryInterface) must NOT be redeclared in interface structs. These are provided by +the base class and redeclaring them creates separate vtable entries, breaking COM +reference counting and causing crashes. **Extraction Logic:** -1. Read the interface method declarations in order -2. If comparing against a previous version: note any additions, removals, or reorderings -3. For new interfaces: verify method ordering follows a logical grouping with extensibility in mind - -**Verification Logic:** -1. When reviewing against a baseline: check for removed methods, reordered methods, or changed signatures -2. New methods in a released interface must be at the end -3. If a released interface has been structurally modified → VIOLATION -4. For new (unreleased) interfaces: recommend logical method ordering for future extensibility - -**Violation Pattern:** Released interface has methods removed, reordered, or signatures changed — breaks binary compatibility - -**Fix Example:** - -```cpp -// WRONG: (removed a method or changed signature in a released interface) -// v1: virtual Core::hresult Get(const string& key, string& value) = 0; -// v2: virtual Core::hresult Get(const string& key, string& value, bool caseSensitive) = 0; -// ^^^^^^^^^^^^^^^^^ ← changed signature - -// Correct: create a new interface version -struct EXTERNAL IDictionary2 : virtual public IDictionary { - enum { ID = RPC::ID_DICTIONARY2 }; - virtual Core::hresult GetCaseSensitive(const string& key, const bool caseSensitive, string& value /* @out */) = 0; -}; -``` - -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — binary compatibility - ---- -### core_14_1 — No AddRef/Release Redeclaration - -**Severity:** violation - -**Description:** -`AddRef()` and `Release()` are inherited from `Core::IUnknown` and must NOT be redeclared in interface structs. Redeclaring them creates a separate vtable entry, breaking COM reference counting and causing crashes. - -**Extraction Logic:** 1. Read all method declarations in the interface struct (including nested structs) -2. Look for any `AddRef()` or `Release()` declarations +2. Look for any AddRef(), Release(), or QueryInterface() declarations **Verification Logic:** -1. The interface must not declare `AddRef()` or `Release()` -2. These methods are inherited from `Core::IUnknown` via the virtual inheritance chain -3. If `AddRef()` or `Release()` appears as an interface method → VIOLATION -**Violation Pattern:** AddRef() or Release() redeclared in interface — must be inherited from Core::IUnknown only +1. The interface must not declare AddRef(), Release(), or QueryInterface() +2. These methods are inherited from Core::IUnknown via the virtual inheritance chain +3. If any IUnknown/IReferenceCounted method appears as an interface method → VIOLATION + +**Violation Pattern:** IUnknown/IReferenceCounted method (AddRef, Release, QueryInterface) declared in interface — must be inherited only **Fix Example:** ```cpp // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { - virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE - virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE + virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual void* QueryInterface(uint32_t) = 0; // ← DO NOT REDECLARE virtual Core::hresult Get(const string& key, string& value) = 0; }; @@ -504,25 +561,32 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { }; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — AddRef/Release inherited from Core::IUnknown +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — IUnknown methods inherited only +ThunderInterfaces/interfaces/IDictionary.h — AddRef/Release inherited from Core::IUnknown --- -### core_15_1 — No std::map in Interfaces +### core_14_1 — No std::map in Interfaces **Severity:** violation -**Description:** -`std::map` (and other `std::` associative containers) must not appear as interface method parameters or return types. `std::map` is not serialisable across process boundaries via Thunder's RPC mechanism and causes code generation failures. Use structured types, repeated calls, or JSON containers instead. +**Description:** + +std::map (and other std:: associative containers) must not appear as interface +method parameters or return types. std::map is not serialisable across process +boundaries via Thunder's RPC mechanism and causes code generation failures. +Use structured types, repeated calls, or JSON containers instead. **Extraction Logic:** + 1. Read all method parameter types in the interface -2. Look for `std::map`, `std::unordered_map`, `std::multimap`, or similar associative containers +2. Look for std::map, std::unordered_map, std::multimap, or similar associative containers **Verification Logic:** -1. Any `std::map` or similar associative container in method parameters → VIOLATION -2. Consider whether a `Core::JSON::Container` or repeated method calls can replace it -3. If `std::map` found in interface → VIOLATION + +1. Any std::map or similar associative container in method parameters → VIOLATION +2. Consider whether a Core::JSON::Container or repeated method calls can replace it +3. If std::map found in interface → VIOLATION **Violation Pattern:** std::map used in interface parameter — not serialisable across process boundaries @@ -537,25 +601,31 @@ virtual Core::hresult GetKeys(RPC::IStringIterator*& keys /* @out */) = 0; virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — no std::map in interfaces +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — no std::map in interfaces --- -### core_16_1 — Explicit Integer Widths +### core_15_1 — Explicit Integer Widths **Severity:** violation -**Description:** -Interface method parameters and return values must use explicit-width integer types (`uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `int32_t`, etc.) rather than platform-dependent types (`int`, `long`, `short`, `size_t`, `unsigned int`). Implicit-width types change size between platforms and break binary compatibility. +**Description:** + +Interface method parameters and return values must use explicit-width integer +types (uint8_t, uint16_t, uint32_t, uint64_t, int32_t, etc.) rather than +platform-dependent types (int, long, short, size_t, unsigned int). +Implicit-width types change size between platforms and break binary compatibility. **Extraction Logic:** + 1. Read all method parameter types and return types 2. Identify any integer parameters using platform-dependent types **Verification Logic:** -1. `int`, `long`, `short`, `unsigned int`, `unsigned long`, `size_t` in interface parameters → VIOLATION -2. `char` is acceptable for character data; `bool` is acceptable -3. `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `int32_t` etc. are correct + +1. int, long, short, unsigned int, unsigned long, size_t in interface parameters → VIOLATION +2. char is acceptable for character data; bool is acceptable +3. uint8_t, uint16_t, uint32_t, uint64_t, int32_t etc. are correct 4. If platform-dependent integer type found → VIOLATION **Violation Pattern:** Platform-dependent integer type (int, long, short) used in interface — must use explicit-width types (uint32_t, etc.) @@ -572,29 +642,34 @@ virtual Core::hresult SetTimeout(const uint32_t timeout) = 0; virtual Core::hresult GetSize(uint32_t& size /* @out */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IVolume.h` — explicit integer widths +**Citation:** ThunderInterfaces/interfaces/IVolume.h — explicit integer widths --- -### core_17_1 — @restrict Mandatory with std::vector +### core_16_1 — @restrict Mandatory with std::vector **Severity:** violation -**Description:** -Every interface method parameter of type `std::vector` (or `Core::JSON::ArrayType` equivalent) MUST be annotated with `/* @restrict:N */` where N is the maximum allowed element count. Without `@restrict`, the code generator cannot produce safe bounds-checking code and may generate unbounded deserialization that is exploitable. +**Description:** -> **Note:** This rule specifically applies to `std::vector`. Non-vector parameters with optional constraints are covered by advisory_m5_1. +Every interface method parameter of type std::vector (or Core::JSON::ArrayType equivalent) +MUST be annotated with /* @restrict:N */ where N is the maximum allowed element count. +Without @restrict, the code generator cannot produce safe bounds-checking code and may +generate unbounded deserialization that is exploitable. +Note: This rule specifically applies to std::vector. **Extraction Logic:** + 1. Read all method parameter declarations -2. Identify any parameters of type `std::vector` -3. Check for the `@restrict` comment annotation on each vector parameter +2. Identify any parameters of type std::vector +3. Check for the @restrict comment annotation on each vector parameter **Verification Logic:** -1. Every `std::vector` parameter must have `/* @restrict:N */` annotation + +1. Every std::vector parameter must have /* @restrict:N */ annotation 2. N must be a positive integer representing the maximum element count -3. Missing `@restrict` on `std::vector` → VIOLATION -4. `@restrict` on non-vector types is advisory (see advisory_m5_1) +3. Missing @restrict on std::vector → VIOLATION +4. @restrict on non-vector types is not enforced by this rule **Violation Pattern:** std::vector parameter missing @restrict annotation — required for safe bounds checking @@ -608,25 +683,138 @@ virtual Core::hresult GetItems(std::vector& items /* @out */) = 0; virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; ``` -**Citation:** `ThunderInterfaces/interfaces/IPackager.h` — @restrict on std::vector +**Citation:** ThunderInterfaces/interfaces/IPackager.h — @restrict on std::vector --- -## Advisory Rules (4) +### core_17_1 — No Method Overloads in @json Interfaces -### advisory_m1_1 — Single Responsibility Principle +**Severity:** violation + +**Description:** + +In interfaces tagged with @json, method names must be unique — no C++ overloads +are allowed because JSON-RPC dispatches by method name string only (no signature +overload resolution). Methods that would be overloads in C++ will produce duplicate +JSON-RPC method names, causing code generation failures or undefined dispatch behavior. +Note: @text tags can change the JSON-RPC method name. When checking for duplicates, +use the effective JSON-RPC name (i.e. the @text value if present, otherwise the C++ name). +Two methods with different C++ names but the same @text value also collide. + +**Extraction Logic:** + +1. Read all method declarations in @json-tagged interfaces +2. For each method, determine the effective JSON-RPC name: + - If @text annotation is present, use that as the name + - Otherwise use the C++ method name +3. Collect all effective names into a list + +**Verification Logic:** + +1. Check for any duplicate effective JSON-RPC method names +2. Two methods with the same C++ name (overloads) → VIOLATION +3. Two methods with different C++ names but the same @text value → VIOLATION +4. If any duplicates are found → VIOLATION + +**Violation Pattern:** Duplicate JSON-RPC method name in @json interface — overloads not allowed + +**Fix Example:** + +```cpp +// WRONG — overloaded methods produce duplicate JSON-RPC names: +// @json 1.0.0 +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + virtual Core::hresult Get(const uint32_t index, string& value /* @out */) = 0; // ← overload! +}; + +// Correct — use distinct names: +// @json 1.0.0 +struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + virtual Core::hresult GetByIndex(const uint32_t index, string& value /* @out */) = 0; +}; + +// Also WRONG — @text collision: +virtual Core::hresult GetValue(const string& key, string& value /* @out */) = 0; // @text get +virtual Core::hresult GetStatus(string& status /* @out */) = 0; // @text get ← collision! +``` + +**Citation:** ThunderInterfaces/interfaces/ — no overloads in @json interfaces + +--- + +### core_18_1 — No Reserved JSON-RPC Method Names **Severity:** violation -**Description:** -Each COM interface should have a single, clearly defined responsibility. An interface that mixes unrelated concerns (e.g. audio control + network management) is harder to implement, test, and maintain. If an interface is doing too many unrelated things, it should be split into multiple focused interfaces. +**Description:** + +Interfaces tagged with @json must not declare methods with names that collide +with built-in JSON-RPC framework methods. The following names are reserved by +the Thunder JSON-RPC infrastructure and will conflict: +- Version / Versions (built-in version query) +- exists (built-in method existence check) +These names will cause conflicts with the framework's built-in handlers, +leading to undefined dispatch behavior or shadowing. +Check both the C++ method name and any @text annotation for collisions. + +**Extraction Logic:** + +1. Read all method declarations in @json-tagged interfaces +2. For each method, determine the effective JSON-RPC name (@text or C++ name) +3. Compare against the reserved name list: version, versions, exists (case-insensitive) + +**Verification Logic:** + +1. If any method's effective JSON-RPC name matches a reserved name (case-insensitive) → VIOLATION +2. Reserved names: "version", "versions", "exists" +3. Check both the bare method name and any @text annotation +4. If collision found → VIOLATION + +**Violation Pattern:** Interface method uses a reserved JSON-RPC name (version/versions/exists) — conflicts with built-in framework handlers + +**Fix Example:** + +```cpp +// WRONG — collides with built-in JSON-RPC 'exists' method: +virtual Core::hresult Exists(const string& key, bool& result /* @out */) = 0; + +// Correct — use a non-reserved name: +virtual Core::hresult Contains(const string& key, bool& result /* @out */) = 0; + +// WRONG — collides with built-in 'version': +virtual Core::hresult Version(string& version /* @out */) = 0; + +// Correct: +virtual Core::hresult GetVersion(string& version /* @out */) = 0; +``` + +**Citation:** Thunder JSON-RPC framework — reserved method names + +--- + +## Advisory Rules (3) + +### advisory_m1_1 — Single Responsibility Principle + +**Severity:** warning + +**Description:** + +Each COM interface should have a single, clearly defined responsibility. +An interface that mixes unrelated concerns (e.g. audio control + network management) +is harder to implement, test, and maintain. If an interface is doing too many +unrelated things, it should be split into multiple focused interfaces. **Extraction Logic:** + 1. Read the full interface and identify all the method groups 2. Reason about whether all methods serve a single coherent purpose 3. Look for method groups that could logically belong to separate interfaces **Verification Logic:** + 1. Reason about the interface's overall purpose from its name and method set 2. If methods clearly belong to two or more distinct responsibilities → VIOLATION 3. Minor convenience methods on an otherwise focused interface are acceptable @@ -646,7 +834,7 @@ struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { // Correct: split into IDictionary and INetwork ``` -**Citation:** `ThunderInterfaces/interfaces/IHdmiCecSink.h` — single responsibility +**Citation:** ThunderInterfaces/interfaces/IHdmiCecSink.h — single responsibility --- @@ -654,19 +842,24 @@ struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { **Severity:** warning -**Description:** -Enums used in interface parameters or return types should use explicit underlying types (`: uint8_t`, `: uint32_t`) for ABI stability. +**Description:** -**Exception:** the anonymous ID enum inside the interface struct (`enum { ID = RPC::ID_* }`) must NOT have an explicit underlying type — this is by Thunder convention. Only named enums that are used as parameter types need explicit underlying types. +Enums used in interface parameters or return types should use explicit underlying +types (: uint8_t, : uint32_t) for ABI stability. +Exception: the anonymous ID enum inside the interface struct (enum { ID = RPC::ID_* }) +must NOT have an explicit underlying type — this is by Thunder convention. +Only named enums that are used as parameter types need explicit underlying types. **Extraction Logic:** + 1. Read all enum declarations in the interface header 2. Identify named enums used as method parameter types -3. Identify the anonymous ID enum `{ ID = RPC::ID_* }` +3. Identify the anonymous ID enum { ID = RPC::ID_* } 4. Check for explicit underlying types on named enums **Verification Logic:** -1. Anonymous ID enum (`enum { ID = RPC::ID_* }`) must NOT have explicit type — skip it + +1. Anonymous ID enum (enum { ID = RPC::ID_* }) must NOT have explicit type — skip it 2. Named enums used as parameter types should have explicit underlying types 3. If a named enum used as a parameter lacks an explicit underlying type → WARNING 4. Apply judgment: if the enum range clearly fits a known type, it is advisable @@ -688,7 +881,7 @@ virtual Core::hresult SetState(const State state) = 0; enum { ID = RPC::ID_MY_INTERFACE }; ``` -**Citation:** `ThunderInterfaces/interfaces/IAVInput.h` — explicit enum underlying type +**Citation:** ThunderInterfaces/interfaces/IAVInput.h — explicit enum underlying type --- @@ -696,17 +889,24 @@ enum { ID = RPC::ID_MY_INTERFACE }; **Severity:** violation -**Description:** -Thunder COM interfaces and their implementations must not use C++ exceptions. Exceptions cannot cross COM/RPC process boundaries safely. All error conditions must be reported via `Core::hresult` return values. Exception specifications (`throw(...)`, `noexcept`) are irrelevant — the real issue is that `throw` statements must not appear in COM implementation code. +**Description:** + +Thunder COM interfaces and their implementations must not use C++ exceptions. +Exceptions cannot cross COM/RPC process boundaries safely. +All error conditions must be reported via Core::hresult return values. +Exception specifications (throw(...), noexcept) are irrelevant — the real +issue is that throw statements must not appear in COM implementation code. **Extraction Logic:** + 1. Read all interface method signatures and any associated implementation hints 2. Check for exception specifications or throw annotations 3. If implementation files are accessible, check for throw statements **Verification Logic:** -1. No exception specifications that imply throws (`throw(...)`) on interface methods -2. `noexcept` specifications are acceptable (they prevent exceptions from propagating) + +1. No exception specifications that imply throws (throw(...)) on interface methods +2. noexcept specifications are acceptable (they prevent exceptions from propagating) 3. In implementation code: no throw statements in COM method implementations 4. If throw appears in COM code → VIOLATION @@ -723,45 +923,6 @@ virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; // Implementation: return Core::ERROR_NOT_FOUND; instead of throwing ``` -**Citation:** `ThunderInterfaces/interfaces/IDictionary.h` — no exceptions in COM interfaces - ---- - -### advisory_m5_1 — @restrict for Non-vector Params - -**Severity:** warning - -**Description:** -Parameters with natural upper bounds (strings with max length, integers with max value, buffers with max size) may benefit from `@restrict` annotations to communicate constraints to callers and enable bounds checking in generated code. - -> **NOTE:** This rule applies to non-`std::vector` parameters only. `std::vector` parameters are covered by core_17_1 where `@restrict` is MANDATORY. This rule is advisory for non-vector types where bounds are meaningful. - -**Extraction Logic:** -1. Read all method parameter declarations -2. Skip `std::vector` parameters (those are covered by core_17_1) -3. For remaining parameters, reason about whether they have natural upper bounds -4. Check for `@restrict` annotations on constrained parameters - -**Verification Logic:** -1. Do NOT apply this rule to `std::vector` parameters — core_17_1 covers those -2. For string parameters used as identifiers, keys, or names: consider whether a max length makes sense -3. For integer count/size parameters: consider whether a max value is meaningful -4. If a non-vector parameter clearly has a natural bound but lacks `@restrict` → WARNING (advisory) -5. Apply judgment: not every parameter needs `@restrict` — only those with meaningful constraints - -**Violation Pattern:** Non-vector parameter with natural upper bound lacks @restrict annotation (advisory) - -**Fix Example:** - -```cpp -// Without restriction (acceptable for many cases): -virtual Core::hresult SetName(const string& name) = 0; - -// With @restrict for bounded parameters: -virtual Core::hresult SetName(const string& name /* @restrict:64 */) = 0; - -// Note: for std::vector, @restrict is MANDATORY (see core_17_1): -virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; -``` +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — no exceptions in COM interfaces -**Citation:** `ThunderInterfaces/interfaces/IVolumeControl.h` — @restrict on bounded params \ No newline at end of file +--- \ 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 419e0a2c..26cbb5d8 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -6,7 +6,7 @@ ### Requirement: COM interface validator command The system MUST provide a `/thunder-interface-review` slash command that validates -a Thunder COM interface header against 19 rules (15 core + 4 advisory). +a Thunder COM interface header against 19 rules (16 core + 3 advisory). #### Scenario: Interface with critical violations - GIVEN a Thunder interface file with a missing `@json` tag @@ -17,7 +17,7 @@ a Thunder COM interface header against 19 rules (15 core + 4 advisory). #### Scenario: Interface with vector without @restrict - GIVEN an interface method with a `std::vector` parameter lacking `@restrict` -- WHEN validation runs checkpoint core_17_1 +- WHEN validation runs checkpoint core_16_1 - THEN it reports VIOLATION: `std::vector without @restrict (MANDATORY)` - AND shows the fix: add `/* @restrict:... */` annotation @@ -25,7 +25,7 @@ a Thunder COM interface header against 19 rules (15 core + 4 advisory). - GIVEN any Thunder interface file - WHEN the validator runs - THEN it applies all 19 rules loaded from `thunder-interface-rules.yaml` in order: - core_1_1 through core_17_1 (15 core), then advisory_m1_1 through advisory_m5_1 (4 advisory) + core_1_1 through core_18_1 (16 core), then advisory_m1_1 through advisory_m3_1 (3 advisory) - AND groups output into: 🔴 Violations, 🟡 Warnings, 🟢 Suggestions, ✅ Validated, Compatibility Notes --- @@ -53,21 +53,22 @@ After a downgrade the downgraded level is used. Emoji prefix must match: 🔴 VI 🟡 WARNING, 🟢 SUGGESTION. #### Scenario: Core rules covered -- 15 core rules at violation level: file/namespace structure, interface declaration shape, +- 16 core rules: file/namespace structure, interface declaration shape, interface ID registration (nested INotification + ICallback), pure virtual methods, return type conventions (Core::hresult mandatory for @json interfaces in Thunder 5.0+), const correctness, Thunder type conventions (string not std::string), - Register/Unregister patterns, nested event interfaces (@event tag required), - @json tag (CRITICAL — without it zero RPC code is generated), binary compatibility, - no AddRef/Release redeclaration, no std::map, explicit integer widths (uint32_t not int), - @restrict mandatory with all std::vector parameters + Register/Unregister patterns, event interfaces (@event tag required), + @json tag (CRITICAL — without it zero RPC code is generated), + no IUnknown/IReferenceCounted method redeclaration, no std::map, + explicit integer widths (uint32_t not int), + @restrict mandatory with all std::vector parameters, + no method overloads in @json interfaces (JSON-RPC dispatches by name only), + no reserved JSON-RPC method names (version/versions/exists) #### Scenario: Advisory rules covered -- advisory_m1_1: Single responsibility principle (violation) +- advisory_m1_1: Single responsibility principle (warning) - advisory_m2_1: Enum underlying types — exclude anonymous ID enum (warning) - advisory_m3_1: No C++ exceptions (violation) -- advisory_m5_1: @restrict for non-vector parameter constraints — explicitly excludes - std::vector (that is covered by core_17_1) (warning) --- @@ -107,7 +108,7 @@ PluginSkeletonGenerator.py in interactive mode. ### Requirement: Setup script registers prompts with VS Code The `setup-prompts.py` script MUST modify VS Code settings.json to add -`chat.promptFilesLocations` pointing to `ThunderTools/PluginQA/Prompts`. +`chat.promptFilesLocations` pointing to `ThunderTools/PluginQualityAdvisor/Prompts`. #### Scenario: Python cross-platform setup - GIVEN any OS with Python 3 @@ -136,7 +137,7 @@ Adding/removing rules may require updating prompt documentation (e.g., the Quick #### Scenario: Updating an existing interface rule - GIVEN a developer needs to strengthen `core_5_1` (return type convention) -- WHEN they open `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` +- WHEN they open `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` - THEN they edit the relevant fields directly in the rule entry: ```yaml diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md index bc8ecd6f..b82eb758 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md @@ -33,66 +33,56 @@ | [rule_17](#rule_17) | IShell AddRef in Initialize | violation | Lifecycle | | [rule_18](#rule_18) | IShell Release in Deinitialize | violation | Lifecycle | | [rule_19](#rule_19) | Information() Method | violation | Lifecycle | -| [rule_20](#rule_20) | Root\() Null Check | violation | Lifecycle | +| [rule_20](#rule_20) | Root\() nullptr Check | violation | Lifecycle | | [rule_21](#rule_21) | Root\() Release in Deinitialize | violation | Lifecycle | | [rule_22](#rule_22) | Observer Cleanup in Deinitialize | violation | Lifecycle | | [rule_23](#rule_23) | SubSystems() Release in Deinitialize | violation | Lifecycle | -| [rule_24](#rule_24) | Constructor Must Be Empty | violation | Lifecycle | +| [rule_24](#rule_24) | Constructor Must Be Empty | suggestion | Lifecycle | | [rule_25](#rule_25) | service->Register/Unregister Pairing | violation | Lifecycle | | [rule_26](#rule_26) | Initialize Returns Error String on Failure | violation | Lifecycle | | [rule_27](#rule_27) | No Manual Deinitialize() in Initialize | violation | Lifecycle | | [rule_28](#rule_28) | Destructor Must Be Empty | violation | Lifecycle | | [rule_29](#rule_29) | JSON-RPC Register/Unregister Pairing | violation | Implementation | | [rule_30](#rule_30) | SinkType Pattern for Subscribers | violation | Implementation | -| [rule_31](#rule_31) | Unavailable() in SinkType Classes | violation | Implementation | -| [rule_32](#rule_32) | No Hardcoded Paths | violation | Implementation | -| [rule_33](#rule_33) | OOP Connection Termination in Deinitialize | violation | Out-of-Process | -| [rule_34](#rule_34) | connectionId Checked in IRemoteConnection Callbacks | violation | Out-of-Process | -| [rule_35](#rule_35) | Startmode Declaration | violation | Configuration | -| [rule_36](#rule_36) | Config Core::JSON::Container | violation | Configuration | -| [rule_37](#rule_37) | No Hardcoded Numeric Tuning Parameters | violation | Configuration | -| [rule_38](#rule_38) | CXX_STANDARD Uses Thunder Variable | violation | CMake | -| [rule_39](#rule_39) | COM Methods Return Core::hresult | violation | COM Interface | -| [rule_40](#rule_40) | #pragma once (all headers) | suggestion | Conventions | -| [rule_41](#rule_41) | Apache 2.0 Copyright Header | suggestion | Conventions | -| [rule_42](#rule_42) | STL Types | warning | Code Quality | -| [rule_43](#rule_43) | ASSERT vs Error Handling | warning | Code Quality | -| [rule_44](#rule_44) | OOP Registration Order | violation | Lifecycle Integrity | -| [rule_45](#rule_45) | Complete State Reset in Deinitialize | violation | Lifecycle Integrity | -| [rule_46](#rule_46) | Reverse-Order Cleanup | suggestion | Lifecycle Integrity | -| [rule_47](#rule_47) | Observer Locking | violation | Concurrency | -| [rule_48](#rule_48) | AddRef/Release Balance | violation | COM Safety | -| [rule_49](#rule_49) | CMake NAMESPACE Variable | suggestion | Conventions | -| [rule_50](#rule_50) | Handlers Must Not Block | violation | Concurrency | -| [rule_51](#rule_51) | No Activate/Deactivate from Handlers | violation | Concurrency | -| [rule_52](#rule_52) | Shared State Protected by CriticalSection | violation | Concurrency | -| [rule_53](#rule_53) | No Lock Held During Framework Callbacks | violation | Concurrency | -| [rule_54](#rule_54) | Worker Jobs Check Deinitialize Guard | violation | Concurrency | -| [rule_55](#rule_55) | File Descriptors / Sockets Wrapped in RAII | violation | Resource Management | -| [rule_56](#rule_56) | No Unbounded Memory Growth | violation | Resource Management | -| [rule_57](#rule_57) | Config Errors Return Non-Empty from Initialize | violation | Lifecycle Integrity | -| [rule_58](#rule_58) | interface->Register/Unregister Pairing | violation | JSON-RPC Compliance | -| [rule_59](#rule_59) | Handler Registration Order | violation | JSON-RPC Compliance | -| [rule_60](#rule_60) | Use Core::ERROR_* for Handler Failure Codes | violation | JSON-RPC Compliance | -| [rule_61](#rule_61) | Input Validation in JSON-RPC Handlers | violation | JSON-RPC Compliance | -| [rule_62](#rule_62) | Event Constants and Typed JSON Payloads | warning | JSON-RPC Compliance | -| [rule_63](#rule_63) | COM Reference Counting Correctness | violation | COM Safety | -| [rule_64](#rule_64) | No Hard Inter-Plugin Dependencies | warning | Inter-Plugin Design | -| [rule_65](#rule_65) | JSON-RPC Handlers Are Re-entrant Safe | violation | Concurrency | -| [rule_66](#rule_66) | IPlugin::INotification Callbacks Must Not Block | violation | Concurrency | -| [rule_67](#rule_67) | Lock Scope Minimized | violation | Concurrency | -| [rule_68](#rule_68) | Plugin Threads Joined in Deinitialize | violation | Concurrency | -| [rule_69](#rule_69) | Memory and Allocation Safety | warning | Resource Management | -| [rule_70](#rule_70) | Framework Pointers Not Accessed After Deinitialize | violation | Lifecycle Integrity | -| [rule_71](#rule_71) | hresult Return Values Checked | violation | COM Safety | -| [rule_72](#rule_72) | ASSERT Only for Programmer Invariants | warning | Code Quality | -| [rule_73](#rule_73) | Security: Logging, Shell, Path, and Error Exposure | violation | Code Quality | -| [rule_74](#rule_74) | JSON-RPC Input Validation for Bounds and Types | violation | JSON-RPC Compliance | -| [rule_75](#rule_75) | Config Completeness and Resource Cleanup | warning | Code Quality | -| [rule_76](#rule_76) | OOP Error Propagation and Method Naming | warning | Inter-Plugin Design | -| [rule_77](#rule_77) | Observer Classes Private and Nested | suggestion | Conventions | -| [rule_78](#rule_78) | No Deprecated JSON-RPC APIs | violation | JSON-RPC Compliance | -| [rule_79](#rule_79) | All Acquired Pointers Cleared After Deinitialize | violation | Lifecycle Integrity | +| [rule_31](#rule_31) | No Hardcoded Paths | violation | Implementation | +| [rule_32](#rule_32) | OOP Connection Termination in Deinitialize | violation | Out-of-Process | +| [rule_33](#rule_33) | connectionId Checked in IRemoteConnection Callbacks | violation | Out-of-Process | +| [rule_34](#rule_34) | Startmode Declaration | violation | Configuration | +| [rule_35](#rule_35) | Config Core::JSON::Container | violation | Configuration | +| [rule_36](#rule_36) | No Hardcoded Numeric Tuning Parameters | suggestion | Configuration | +| [rule_37](#rule_37) | CXX_STANDARD Uses Thunder Variable | violation | CMake | +| [rule_38](#rule_38) | COM Methods Return Core::hresult | violation | COM Interface | +| [rule_39](#rule_39) | #pragma once (all headers) | suggestion | Conventions | +| [rule_40](#rule_40) | Apache 2.0 Copyright Header | suggestion | Conventions | +| [rule_41](#rule_41) | Reverse-Order Cleanup | suggestion | Lifecycle Integrity | +| [rule_42](#rule_42) | Observer Locking | violation | Concurrency | +| [rule_43](#rule_43) | AddRef/Release Balance | violation | COM Safety | +| [rule_44](#rule_44) | CMake NAMESPACE Variable | suggestion | Conventions | +| [rule_45](#rule_45) | Handlers Must Not Block | violation | Concurrency | +| [rule_46](#rule_46) | No Activate/Deactivate from Handlers | violation | Concurrency | +| [rule_47](#rule_47) | Shared State Protected by CriticalSection | violation | Concurrency | +| [rule_48](#rule_48) | No Lock Held During Framework Callbacks | violation | Concurrency | +| [rule_49](#rule_49) | Worker Jobs Safe After Deinitialize | warning | Concurrency | +| [rule_50](#rule_50) | File Descriptors / Sockets Wrapped in RAII | violation | Resource Management | +| [rule_51](#rule_51) | Config Errors Return Non-Empty from Initialize | violation | Lifecycle Integrity | +| [rule_52](#rule_52) | interface->Register/Unregister Pairing | violation | JSON-RPC Compliance | +| [rule_53](#rule_53) | Handler Registration Order | violation | JSON-RPC Compliance | +| [rule_54](#rule_54) | Use Core::ERROR_* for Handler Failure Codes | violation | JSON-RPC Compliance | +| [rule_55](#rule_55) | Event Constants and Typed JSON Payloads | warning | JSON-RPC Compliance | +| [rule_56](#rule_56) | COM Reference Counting Correctness | violation | COM Safety | +| [rule_57](#rule_57) | No Hard Inter-Plugin Dependencies | warning | Inter-Plugin Design | +| [rule_58](#rule_58) | JSON-RPC Handlers Are Re-entrant Safe | violation | Concurrency | +| [rule_59](#rule_59) | IPlugin::INotification Callbacks Must Not Block | violation | Concurrency | +| [rule_60](#rule_60) | Lock Scope Minimized | violation | Concurrency | +| [rule_61](#rule_61) | Plugin Threads Joined in Deinitialize | violation | Concurrency | +| [rule_63](#rule_63) | hresult Return Values Checked | violation | COM Safety | +| [rule_64](#rule_64) | ASSERT Only for Programmer Invariants | warning | Code Quality | +| [rule_65](#rule_65) | Security: Logging, Shell, Path, and Error Exposure | violation | Code Quality | +| [rule_66](#rule_66) | Config Completeness and Resource Cleanup | warning | Code Quality | +| [rule_67](#rule_67) | Observer Classes Private and Nested | suggestion | Conventions | +| [rule_68](#rule_68) | No Deprecated JSON-RPC APIs | violation | JSON-RPC Compliance | +| [rule_69](#rule_69) | INTERFACE_MAP Mandatory | violation | Conventions | +| [rule_70](#rule_70) | No printf — Use Thunder Tracing | warning | Code Quality | --- @@ -249,7 +239,7 @@ IPlugin* plugin = nullptr; if (service == nullptr) { ... } ``` -**Citation format:** `[PluginName.cpp:LINE] NULL used as null pointer — use nullptr` +**Citation format:** `[PluginName.cpp:LINE] NULL used as null pointer — NULL vs nullptr` --- @@ -476,11 +466,11 @@ public: **Plugin Metadata Registration** | `violation` -**What to check:** The plugin `.cpp` file must contain a `static Plugin::Metadata` registration. This registers the plugin with the Thunder framework. +**What to check:** The plugin `.cpp` file must contain exactly ONE `static Plugin::Metadata` registration. This registers the plugin with the Thunder framework. Plugin::Metadata must appear only once for the entire plugin — all other registration points (including OOP implementation files) must use the `SERVICE_REGISTRATION` macro instead. For OOP plugins, at least one `SERVICE_REGISTRATION` must exist in the OOP part. **Where to look:** `PluginName.cpp`. -**Violation pattern:** `Plugin::Metadata` registration absent. +**Violation pattern:** `Plugin::Metadata` registration missing, duplicated, or OOP part missing `SERVICE_REGISTRATION`. ```cpp // Correct — add to PluginName.cpp: @@ -502,11 +492,11 @@ static Plugin::Metadata metadata( **JSONRPC Inheritance When Used** | `violation` | *Conditional: skip if plugin registers no JSON-RPC handlers* -**What to check:** If the plugin registers JSON-RPC handlers (`Register()` calls in `Initialize()`), the plugin class must inherit `PluginHost::JSONRPC`. +**What to check:** If the plugin registers JSON-RPC handlers (`Register()` calls in `Initialize()`), the plugin class must inherit `PluginHost::JSONRPC` or a class derived from it. **Where to look:** `Initialize()` for `Register()` calls; the class declaration for JSONRPC inheritance. -**Violation pattern:** `Register()` calls present in `Initialize()` but class does not inherit `PluginHost::JSONRPC`. +**Violation pattern:** `Register()` calls present in `Initialize()` but class does not inherit `PluginHost::JSONRPC` or a derived class (e.g. `PluginHost::JSONRPCSupportsStatusListener`). ```cpp // WRONG: @@ -606,7 +596,7 @@ string PluginName::Information() const { ### rule_20 -**Root\() Null Check** | `violation` | *Conditional: skip if `Initialize()` does not call `Root()`* +**Root\() nullptr Check** | `violation` | *Conditional: skip if `Initialize()` does not call `Root()`* **What to check:** The return value of `service->Root()` must be checked for `nullptr` before use. @@ -722,7 +712,7 @@ void Deinitialize(PluginHost::IShell* service) { ### rule_24 -**Constructor Must Be Empty** | `violation` +**Constructor Must Be Empty** | `suggestion` **What to check:** The plugin class constructor body must be empty — no initialization logic, no resource acquisition, no system calls. All initialization belongs in `Initialize()`. Member initializer lists with null/default values are acceptable. @@ -943,36 +933,6 @@ public: ### rule_31 -**Unavailable() in SinkType Classes** | `violation` | *Conditional: skip if plugin has no SinkType classes* - -**What to check:** Every class implementing `IPlugin::INotification` must also implement `Unavailable()`. `IPlugin::INotification` has 3 pure virtuals: `Activated`, `Deactivated`, and `Unavailable`. - -**Where to look:** All classes inheriting `IPlugin::INotification`. - -**Violation pattern:** SinkType class missing `Unavailable()` implementation. - -```cpp -// WRONG: -class Notification : public PluginHost::IPlugin::INotification { - void Activated(const string& callsign, PluginHost::IShell* service) override { ... } - void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } - // Unavailable() missing -}; - -// Correct: -class Notification : public PluginHost::IPlugin::INotification { - void Activated(const string& callsign, PluginHost::IShell* service) override { ... } - void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } - void Unavailable(const string& callsign, PluginHost::IShell* service) override { ... } -}; -``` - -**Citation format:** `[PluginName.h:LINE] SinkType class missing Unavailable() implementation` - ---- - -### rule_32 - **No Hardcoded Paths** | `violation` **What to check:** No hardcoded filesystem paths in plugin code. Paths must come from configuration or be constructed from Thunder's data path API. @@ -999,7 +959,7 @@ string configFile = service->DataPath() + "config.json"; --- -### rule_33 +### rule_32 **OOP Connection Termination in Deinitialize** | `violation` | *Conditional: skip if plugin is not out-of-process* @@ -1033,7 +993,7 @@ void Deinitialize(PluginHost::IShell* service) { --- -### rule_34 +### rule_33 **connectionId Checked in IRemoteConnection Callbacks** | `violation` | *Conditional: skip if plugin does not implement `IRemoteConnection::INotification`* @@ -1066,7 +1026,7 @@ void Terminated(const uint32_t id) override { --- -### rule_35 +### rule_34 **Startmode Declaration** | `violation` | *Conditional: skip if no `.conf.in` file exists* @@ -1085,7 +1045,7 @@ startmode = Activated --- -### rule_36 +### rule_35 **Config Core::JSON::Container** | `violation` | *Conditional: skip if plugin has no configuration class* @@ -1116,9 +1076,9 @@ public: --- -### rule_37 +### rule_36 -**No Hardcoded Numeric Tuning Parameters** | `violation` +**No Hardcoded Numeric Tuning Parameters** | `suggestion` **What to check:** Numeric tuning parameters (timeouts, buffer sizes, retry counts, thresholds) must come from the `Config` class, not be hardcoded inline. Structural constants (`0`, `1`, `-1`, array indices) are acceptable inline. @@ -1144,7 +1104,7 @@ if (retries > _config.MaxRetries.Value()) { ... } --- -### rule_38 +### rule_37 **CXX_STANDARD Uses Thunder Variable** | `violation` | *Conditional: skip if `CMakeLists.txt` does not set `CXX_STANDARD`* @@ -1170,11 +1130,11 @@ set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD ${CXX_STD}) --- -### rule_39 +### rule_38 **COM Methods Return Core::hresult** | `violation` -**What to check:** All COM interface methods declared in the plugin must return `Core::hresult`. Void return types and other return types are not allowed in COM interfaces (Thunder 5.0+). +**What to check:** For JSON-RPC interfaces (@json tagged): all methods MUST return `Core::hresult` (violation). For COM-RPC only interfaces (no @json): `Core::hresult` is recommended but not mandatory (suggestion). Exception: notification/event methods (@event) should return void. **Where to look:** All interface method declarations in plugin header files — focus on pure virtual methods in COM interfaces. @@ -1198,11 +1158,11 @@ virtual Core::hresult SetValue(const string& key, const string& value) = 0; The following rules require reading the full plugin context before evaluating. They span multiple files and patterns. -> **YAML source:** `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` → `general_rules` section +> **YAML source:** `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` → `general_rules` section --- -### rule_40 +### rule_39 **#pragma once** | `suggestion` | Category: Conventions @@ -1228,7 +1188,7 @@ The following rules require reading the full plugin context before evaluating. T --- -### rule_41 +### rule_40 **Apache 2.0 Copyright Header** | `suggestion` | Category: Conventions @@ -1255,130 +1215,7 @@ The following rules require reading the full plugin context before evaluating. T --- -### rule_42 - -**STL Types** | `warning` | Category: Code Quality - -**What to check:** Thunder type aliases (`string`, `vector`) are used instead of their `std::` equivalents (`std::string`, `std::vector`) wherever Thunder aliases are available. - -**Where to look:** Function signatures, class member declarations, return types, and local variables. - -**Violation pattern:** `std::string`, `std::vector`, or other STL types used where Thunder aliases exist. - -```cpp -// WRONG: -std::string GetName() const; -std::vector _items; - -// Correct: -string GetName() const; -std::vector _items; // Thunder aliases 'string' but not all containers -``` - -**Citation format:** `[PluginName.h:LINE] std::string used — use Thunder alias 'string'` - ---- - -### rule_43 - -**ASSERT vs Error Handling** | `warning` | Category: Code Quality - -**What to check:** `ASSERT` is used only for programmer invariants (conditions that must always be true in correct code). It must never be used for runtime error handling — things that can legitimately fail (user input, network, configuration). - -**Where to look:** All `ASSERT(...)` calls in the plugin. - -**Violation pattern:** `ASSERT` used on a condition that can fail at runtime. - -```cpp -// WRONG: -string Initialize(PluginHost::IShell* service) { - _impl = service->Root(); - ASSERT(_impl != nullptr); // Root() CAN return nullptr at runtime - _impl->DoWork(); -} - -// Correct: -string Initialize(PluginHost::IShell* service) { - _impl = service->Root(); - if (_impl == nullptr) { - return "Failed to acquire implementation"; - } - ASSERT(_service != nullptr); // OK — _service was set above and must be valid here - _impl->DoWork(); -} -``` - -**Citation format:** `[PluginName.cpp:LINE] ASSERT used on runtime condition — use proper error handling` - ---- - -### rule_44 - -**OOP Registration Order** | `violation` | Category: Lifecycle Integrity - -**What to check:** In out-of-process plugins, `service->Register()` must be called **after** the remote implementation is acquired, and `service->Unregister()` must be called **before** the remote implementation is released. - -**Where to look:** `Initialize()` and `Deinitialize()` in OOP plugins. - -**Violation pattern:** `service->Register()` called before `Root()`, or `service->Unregister()` called after implementation release. - -```cpp -// WRONG: -string Initialize(PluginHost::IShell* service) { - service->Register(_notification); // too early - _impl = service->Root(); -} - -// Correct: -string Initialize(PluginHost::IShell* service) { - _impl = service->Root(); - if (_impl != nullptr) { - service->Register(_notification); // after acquisition - } -} -void Deinitialize(PluginHost::IShell* service) { - service->Unregister(_notification); // before release - _impl->Release(); - _impl = nullptr; -} -``` - -**Citation format:** `[PluginName.cpp:LINE] Registration order incorrect relative to implementation acquisition` - ---- - -### rule_45 - -**Complete State Reset in Deinitialize** | `violation` | Category: Lifecycle Integrity - -**What to check:** Every member variable set during `Initialize()` or during operation must be reset to its default/null state in `Deinitialize()`. A subsequent `Initialize()` call must start from a completely clean state. - -**Where to look:** Compare class member declarations with `Deinitialize()` body — every non-trivial member must be reset. - -**Violation pattern:** Member variable set during plugin operation but not reset in `Deinitialize()`. - -```cpp -// WRONG: -void Deinitialize(PluginHost::IShell* service) { - _service->Release(); - _service = nullptr; - // _connectionId not reset, _isActive not reset -} - -// Correct: -void Deinitialize(PluginHost::IShell* service) { - _service->Release(); - _service = nullptr; - _connectionId = 0; - _isActive = false; -} -``` - -**Citation format:** `[PluginName.cpp:LINE] Member '_connectionId' not reset in Deinitialize()` - ---- - -### rule_46 +### rule_41 **Reverse-Order Cleanup** | `suggestion` | Category: Lifecycle Integrity @@ -1389,8 +1226,8 @@ void Deinitialize(PluginHost::IShell* service) { **Violation pattern:** Resources released in the same order as acquired (not reversed). ```cpp -// Initialize acquires: _service → _impl → _notification -// Deinitialize should release: _notification → _impl → _service +// Initialize acquires: _service -> _impl -> _notification +// Deinitialize should release: _notification -> _impl -> _service // WRONG: void Deinitialize(PluginHost::IShell* service) { @@ -1410,7 +1247,7 @@ void Deinitialize(PluginHost::IShell* service) { --- -### rule_47 +### rule_42 **Observer Locking** | `violation` | Category: Concurrency @@ -1443,7 +1280,7 @@ void NotifyObservers() { --- -### rule_48 +### rule_43 **AddRef/Release Balance** | `violation` | Category: COM Safety @@ -1477,7 +1314,7 @@ if (_impl != nullptr) { --- -### rule_49 +### rule_44 **CMake NAMESPACE Variable** | `suggestion` | Category: Conventions @@ -1489,7 +1326,7 @@ if (_impl != nullptr) { ```cmake # WRONG: -install(TARGETS ${MODULE_NAME} DESTINATION lib/wpeframework/plugins) +install(TARGETS ${MODULE_NAME} DESTINATION lib/thunder/plugins) # Correct: install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${NAMESPACE}/plugins) @@ -1499,7 +1336,7 @@ install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${NAMESPA --- -### rule_50 +### rule_45 **Handlers Must Not Block** | `violation` | Category: Concurrency @@ -1526,7 +1363,7 @@ void Activated(const string& callsign, PluginHost::IShell* shell) override { --- -### rule_51 +### rule_46 **No Activate/Deactivate from Handlers** | `violation` | Category: Concurrency @@ -1553,7 +1390,7 @@ void Deactivated(const string& callsign, PluginHost::IShell* shell) override { --- -### rule_52 +### rule_47 **Shared State Protected by CriticalSection** | `violation` | Category: Concurrency @@ -1586,7 +1423,7 @@ uint32_t GetStatus(...) { --- -### rule_53 +### rule_48 **No Lock Held During Framework Callbacks** | `violation` | Category: Concurrency @@ -1614,11 +1451,11 @@ _service->Submit(PluginHost::IShell::ACTIVATED, this); // lock released first --- -### rule_54 +### rule_49 -**Worker Jobs Check Deinitialize Guard** | `violation` | Category: Concurrency +**Worker Jobs Safe After Deinitialize** | `warning` | Category: Concurrency -**What to check:** All worker job `Dispatch()` methods must check a deinitialization guard flag (e.g. `_deinitialized`) at the start to avoid using stale/null framework pointers after `Deinitialize()` has run. +**What to check:** Worker job `Dispatch()` methods must not access stale/null framework pointers after `Deinitialize()` has run. The specific mechanism is implementation-dependent (flag check, job cancellation, thread join, atomic state, etc.) — what matters is the outcome is guaranteed. **Where to look:** All `IJob::Dispatch()` or WorkerPool job implementations. @@ -1647,11 +1484,11 @@ void Dispatch() override { --- -### rule_55 +### rule_50 **File Descriptors / Sockets Wrapped in RAII** | `violation` | Category: Resource Management -**What to check:** All file descriptors, sockets, and OS handles are wrapped in RAII types or explicitly closed in `Deinitialize()`. They must never be leaked on any code path. +**What to check:** All file descriptors, sockets, and OS handles are wrapped in RAII types or explicitly closed in `Deinitialize()`. They must never be leaked on any code path. More broadly, prefer Thunder-provided abstractions (`Core::DataElementFile`, `Core::SocketPort`, etc.) over raw system calls (`open`, `socket`, `fopen`) wherever Thunder provides an equivalent. **Where to look:** All `open()`, `socket()`, `fopen()` calls and their corresponding close paths. @@ -1674,36 +1511,17 @@ void Deinitialize(...) { --- -### rule_56 - -**No Unbounded Memory Growth** | `violation` | Category: Resource Management - -**What to check:** All data structures (`map`, `vector`, `list`, `queue`) populated at runtime have a bounded size or eviction policy. Without bounds, long-running plugins exhaust memory. -**Where to look:** All `push_back`, `insert`, `emplace` calls on member containers — check for corresponding eviction, size cap, or periodic cleanup. - -**Violation pattern:** Runtime-populated container with no size limit or eviction. ```cpp -// WRONG: -void OnEvent(const Event& e) { - _eventLog.push_back(e); // grows forever -} -// Correct: -void OnEvent(const Event& e) { - if (_eventLog.size() >= MAX_EVENTS) { - _eventLog.erase(_eventLog.begin()); - } - _eventLog.push_back(e); -} ``` -**Citation format:** `[PluginName.cpp:LINE] Container '_eventLog' grows unbounded — add size limit` + --- -### rule_57 +### rule_51 **Config Errors Return Non-Empty from Initialize** | `violation` | Category: Lifecycle Integrity @@ -1734,7 +1552,7 @@ return string(); --- -### rule_58 +### rule_52 **interface->Register/Unregister Pairing** | `violation` | Category: JSON-RPC Compliance @@ -1765,7 +1583,7 @@ void Deinitialize(...) { --- -### rule_59 +### rule_53 **Handler Registration Order** | `violation` | Category: JSON-RPC Compliance @@ -1793,7 +1611,7 @@ void Deinitialize(...) { --- -### rule_60 +### rule_54 **Use Core::ERROR_\* for Handler Failure Codes** | `violation` | Category: JSON-RPC Compliance @@ -1821,42 +1639,21 @@ uint32_t SetValue(...) { --- -### rule_61 - -**Input Validation in JSON-RPC Handlers** | `violation` | Category: JSON-RPC Compliance -**What to check:** Every JSON-RPC handler validates its input parameters (null checks, string length limits, enum range checks) before using them. Unvalidated external input leads to crashes or undefined behavior. - -**Where to look:** All JSON-RPC handler method bodies — the first thing after parameter extraction. - -**Violation pattern:** Input parameter used without validation. ```cpp -// WRONG: -uint32_t SetName(const string& name) { - _name = name; // no length check — could be 10MB string - return Core::ERROR_NONE; -} -// Correct: -uint32_t SetName(const string& name) { - if (name.empty() || name.length() > 256) { - return Core::ERROR_BAD_REQUEST; - } - _name = name; - return Core::ERROR_NONE; -} ``` -**Citation format:** `[PluginName.cpp:LINE] JSON-RPC input not validated before use` + --- -### rule_62 +### rule_55 **Event Constants and Typed JSON Payloads** | `warning` | Category: JSON-RPC Compliance -**What to check:** Event names are defined as named constants (not inline string literals). Event payloads use typed `JsonData::*` classes, not untyped free-form strings. +**What to check:** Event names are defined as named constants (not inline string literals). Do NOT use `JsonData::*` classes directly - these are internal generated glue code that can change. Use the generated event/notify methods instead of raw `Notify()` with manually constructed JSON. **Where to look:** All `Notify()` or event dispatch calls. @@ -1868,16 +1665,16 @@ Notify("stateChanged", "{\"state\":\"active\"}"); // inline string, untyped // Correct: static constexpr auto EVT_STATE_CHANGED = _T("stateChanged"); -JsonData::StateInfo info; -info.State = "active"; -Notify(EVT_STATE_CHANGED, info); +// Use the generated event method instead: +// The generated J.h provides type-safe event dispatch +// e.g. Event_StateChanged(state); // auto-generated, type-safe ``` **Citation format:** `[PluginName.cpp:LINE] Event name is inline string — define as named constant` --- -### rule_63 +### rule_56 **COM Reference Counting Correctness** | `violation` | Category: COM Safety @@ -1907,7 +1704,7 @@ copy->Release(); --- -### rule_64 +### rule_57 **No Hard Inter-Plugin Dependencies** | `warning` | Category: Inter-Plugin Design @@ -1934,7 +1731,7 @@ if (other != nullptr) { --- -### rule_65 +### rule_58 **JSON-RPC Handlers Are Re-entrant Safe** | `violation` | Category: Concurrency @@ -1963,7 +1760,7 @@ uint32_t GetCount(...) { --- -### rule_66 +### rule_59 **IPlugin::INotification Callbacks Must Not Block** | `violation` | Category: Concurrency @@ -1992,7 +1789,7 @@ void Deactivated(const string& callsign, PluginHost::IShell*) override { --- -### rule_67 +### rule_60 **Lock Scope Minimized** | `violation` | Category: Concurrency @@ -2020,7 +1817,7 @@ _adminLock.Unlock(); --- -### rule_68 +### rule_61 **Plugin Threads Joined in Deinitialize** | `violation` | Category: Concurrency @@ -2049,41 +1846,34 @@ void Deinitialize(...) { --- -### rule_69 +### rule_62 -**Memory and Allocation Safety** | `warning` | Category: Resource Management +**Deinitialize Pointer Safety** | `violation` | Category: Lifecycle & State Integrity -**What to check:** No memory leaks (every `new` has a corresponding `delete` on all paths), no use-after-free, no buffer overflows, no large stack allocations or VLAs. +**What to check:** (1) Every pointer member is explicitly set to nullptr in Deinitialize() after being released, so a double-Deinitialize cannot cause a double-release. (2) No worker thread, background job, or async callback can access framework pointers (IShell*, service pointers, notification interfaces) after Deinitialize() has completed and nulled them. -**Where to look:** All `new`/`delete` pairs, raw buffer operations, and stack-allocated arrays. +**Where to look:** Deinitialize() for pointer clearing, and all background tasks/timers/callbacks that use framework pointers. -**Violation pattern:** `new` without matching `delete` on error path, or access to freed memory. +**Violation pattern:** Pointer released but not set to nullptr, or background task accesses framework pointer without null check. ```cpp -// WRONG: -char* buffer = new char[size]; -if (error) return Core::ERROR_GENERAL; // buffer leaked -delete[] buffer; +// WRONG — pointer not cleared: +void Deinitialize(PluginHost::IShell* service) override { + _service->Release(); // released but not nulled — double-Deinitialize = double-release +} -// Correct: -std::unique_ptr buffer(new char[size]); -if (error) return Core::ERROR_GENERAL; // auto-freed -// Or use stack for small, known-size buffers +// Correct — pointer cleared after release: +void Deinitialize(PluginHost::IShell* service) override { + _service->Release(); + _service = nullptr; // safe against double-Deinitialize +} ``` -**Citation format:** `[PluginName.cpp:LINE] Memory leak on error path — 'buffer' not freed` +**Citation format:** `[PluginName.cpp:LINE] Pointer released but not set to nullptr in Deinitialize()` --- -### rule_70 -**Framework Pointers Not Accessed After Deinitialize** | `violation` | Category: Lifecycle Integrity - -**What to check:** No worker thread, background job, or async callback can access framework pointers (`IShell*`, service pointers, notification interfaces) after `Deinitialize()` has completed and nulled them. - -**Where to look:** Worker jobs, timers, and async callbacks that use `_service` or other framework pointers. - -**Violation pattern:** Background task accesses `_service` without checking if it's been nulled. ```cpp // WRONG: @@ -2107,7 +1897,7 @@ void TimerExpired() { --- -### rule_71 +### rule_63 **hresult Return Values Checked** | `violation` | Category: COM Safety @@ -2133,7 +1923,7 @@ if (result != Core::ERROR_NONE) { --- -### rule_72 +### rule_64 **ASSERT Only for Programmer Invariants** | `warning` | Category: Code Quality @@ -2143,13 +1933,13 @@ if (result != Core::ERROR_NONE) { **Violation pattern:** `ASSERT` on a condition that can fail at runtime (network result, user input, config value). -*(Same principle as rule_43 — this rule targets holistic patterns across the full plugin.)* +*(Same principle as rule_42 — this rule targets holistic patterns across the full plugin.)* **Citation format:** `[PluginName.cpp:LINE] ASSERT on runtime condition` --- -### rule_73 +### rule_65 **Security: Logging, Shell, Path, and Error Exposure** | `violation` | Category: Code Quality @@ -2173,35 +1963,17 @@ SYSLOG(Logging::Info, "Auth token received (length=%d)", token.length()); --- -### rule_74 - -**JSON-RPC Input Validation for Bounds and Types** | `violation` | Category: JSON-RPC Compliance -**What to check:** Every JSON-RPC handler validates numeric inputs for range bounds and type correctness before use in operations that could overflow, underflow, or cause undefined behavior. - -**Where to look:** All JSON-RPC handlers that accept numeric parameters. - -**Violation pattern:** Numeric input used in arithmetic or array indexing without bounds check. ```cpp -// WRONG: -uint32_t SetVolume(const uint8_t volume) { - _device->SetVol(volume); // no check — what if volume > 100? -} -// Correct: -uint32_t SetVolume(const uint8_t volume) { - if (volume > 100) return Core::ERROR_BAD_REQUEST; - _device->SetVol(volume); - return Core::ERROR_NONE; -} ``` -**Citation format:** `[PluginName.cpp:LINE] Numeric input not bounds-checked before use` + --- -### rule_75 +### rule_66 **Config Completeness and Resource Cleanup** | `warning` | Category: Code Quality @@ -2233,34 +2005,17 @@ Config() : Core::JSON::Container() { --- -### rule_76 -**OOP Error Propagation and Method Naming** | `warning` | Category: Inter-Plugin Design - -**What to check:** Out-of-process method implementations properly propagate errors from the remote process back to the plugin host. Method names follow Thunder conventions — no abbreviations, clear verb+noun form. - -**Where to look:** OOP implementation wrapper methods and their return value handling. - -**Violation pattern:** Remote error swallowed (returns `ERROR_NONE` even when remote call failed), or method named with abbreviations. ```cpp -// WRONG: -Core::hresult GetVol(uint8_t& vol) { // abbreviated name - _impl->GetVolume(vol); // ignores return value - return Core::ERROR_NONE; -} -// Correct: -Core::hresult GetVolume(uint8_t& volume) { - return _impl->GetVolume(volume); // propagates error -} ``` -**Citation format:** `[PluginName.cpp:LINE] OOP error not propagated / method name uses abbreviation` + --- -### rule_77 +### rule_67 **Observer Classes Private and Nested** | `suggestion` | Category: Conventions @@ -2286,7 +2041,7 @@ private: --- -### rule_78 +### rule_68 **No Deprecated JSON-RPC APIs** | `violation` | Category: JSON-RPC Compliance @@ -2308,53 +2063,41 @@ Register(_T("method"), &Plugin::Method --- -### rule_79 -**All Acquired Pointers Cleared After Deinitialize** | `violation` | Category: Lifecycle Integrity -**What to check:** Every pointer member set to a non-null value during the plugin's lifetime is explicitly set to `nullptr` in `Deinitialize()` (in addition to being released). This ensures a double-`Deinitialize()` call cannot cause a double-release. +```cpp -**Where to look:** All pointer members vs. `Deinitialize()` body. +``` -**Violation pattern:** Pointer member released but not nulled, or not touched at all in `Deinitialize()`. -```cpp -// WRONG: -void Deinitialize(...) { - _impl->Release(); // released but not nulled - _service->Release(); - _service = nullptr; - // _connection not touched at all -} -// Correct: -void Deinitialize(...) { - _impl->Release(); _impl = nullptr; - _service->Release(); _service = nullptr; - _connection = nullptr; -} -``` +--- + +### rule_69 + +**INTERFACE_MAP Mandatory** | `violation` | Category: Conventions + +**What to check:** Every class that implements COM interfaces must have a proper INTERFACE_MAP (`BEGIN_INTERFACE_MAP` / `END_INTERFACE_MAP`). If the class implements a JSON-RPC interface, `IDispatch` must be listed in the interface map. -**Citation format:** `[PluginName.cpp:LINE] Pointer member not set to nullptr after Release()` +**Where to look:** Plugin class declaration and any COM-implementing classes. + +**Violation pattern:** Class implements COM interfaces but lacks `BEGIN_INTERFACE_MAP` / `END_INTERFACE_MAP`, or JSON-RPC class missing `IDispatch` in map. + +**Citation format:** `[PluginName.h:LINE] Class missing INTERFACE_MAP` --- -## Summary by Phase +### rule_70 + +**No printf — Use Thunder Tracing** | `warning` | Category: Code Quality + +**What to check:** The plugin must be free of `printf`, `fprintf`, `std::cout`, `std::cerr`, and similar direct output calls — using Thunder tracing (`TRACE`, `TRACE_L1`, `SYSLOG`) exclusively. + +**Where to look:** All source files in the plugin. + +**Violation pattern:** Direct output call (`printf`, `std::cout`, etc.) found in plugin code. -| Phase | Rules | Violations | Warnings | Suggestions | -|---|---|---|---|---| -| Module Structure | rule_01 – rule_03 | 1 | 1 | 1 | -| Code Style | rule_04 – rule_13 | 9 | 1 | 0 | -| Class Registration | rule_14 – rule_16 | 2 | 1 | 0 | -| Lifecycle | rule_17 – rule_28 | 12 | 0 | 0 | -| Implementation | rule_29 – rule_32 | 4 | 0 | 0 | -| Out-of-Process | rule_33 – rule_34 | 2 | 0 | 0 | -| Configuration | rule_35 – rule_37 | 3 | 0 | 0 | -| CMake | rule_38 | 1 | 0 | 0 | -| COM Interface | rule_39 | 1 | 0 | 0 | -| Holistic (rule_40 – rule_79) | 40 | 29 | 7 | 4 | -| **Total** | **79** | **64** | **10** | **5** | +**Citation format:** `[PluginName.cpp:LINE] printf/cout used — use Thunder TRACE/SYSLOG` --- -*Document generated from `thunder-plugin-rules.yaml` v3.3.0. For questions or rule updates, raise a PR against the spec folder.* \ No newline at end of file diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index 8e355f14..a685dd4d 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -4,17 +4,17 @@ --- -### Requirement: PluginQA folder created inside ThunderTools -A `PluginQA/` directory MUST be created inside the existing `ThunderTools/` folder, +### Requirement: PluginQualityAdvisor folder created inside ThunderTools +A `PluginQualityAdvisor/` directory MUST be created inside the existing `ThunderTools/` folder, containing all prompts, rules, setup script, and documentation for the QA system. #### Scenario: Directory structure created - GIVEN the ThunderTools repository -- WHEN the PluginQA system is delivered +- WHEN the PluginQualityAdvisor system is delivered - THEN the following structure MUST exist inside `ThunderTools/`: ``` -ThunderTools/PluginQA/ +ThunderTools/PluginQualityAdvisor/ ├── README.md ├── PLUGIN_GENERATOR_GUIDE.md ├── setup-prompts.py (cross-platform Python 3) @@ -28,16 +28,16 @@ ThunderTools/PluginQA/ ``` #### Scenario: Rules files are the source of truth -- GIVEN the two YAML files under `ThunderTools/PluginQA/rules/` +- GIVEN the two YAML files under `ThunderTools/PluginQualityAdvisor/rules/` - WHEN any prompt runs - THEN it MUST load rules at runtime from those YAML files - AND rules MUST NOT be embedded directly inside the prompt files - AND updating a YAML file updates validation behaviour without touching prompt logic #### Scenario: Prompt files are registered with VS Code -- GIVEN `ThunderTools/PluginQA/Prompts/` containing the three `.prompt.md` files +- GIVEN `ThunderTools/PluginQualityAdvisor/Prompts/` containing the three `.prompt.md` files - WHEN `setup-prompts.py` is run -- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQA/Prompts` +- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQualityAdvisor/Prompts` - AND the three slash commands (`/thunder-plugin-review`, `/thunder-interface-review`, `/thunder-generate-plugin`) become available in VS Code Copilot Chat - AND the script is safe to run multiple times (idempotent, creates backup of settings) @@ -45,7 +45,7 @@ ThunderTools/PluginQA/ --- ### Requirement: Setup script modifies VS Code settings.json to register prompt location -The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add `"ThunderTools/PluginQA/Prompts": true` under `chat.promptFilesLocations`. +The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add `"ThunderTools/PluginQualityAdvisor/Prompts": true` under `chat.promptFilesLocations`. #### Scenario: Resulting settings.json structure - GIVEN VS Code `settings.json` before the script runs (may be empty `{}` or have existing entries) @@ -55,7 +55,7 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` ```json { "chat.promptFilesLocations": { - "ThunderTools/PluginQA/Prompts": true + "ThunderTools/PluginQualityAdvisor/Prompts": true } } ``` @@ -81,7 +81,7 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` - AND if the backup already exists from a previous run, it MUST NOT be overwritten #### Scenario: Script is idempotent -- GIVEN `chat.promptFilesLocations` already contains `"ThunderTools/PluginQA/Prompts": true` +- GIVEN `chat.promptFilesLocations` already contains `"ThunderTools/PluginQualityAdvisor/Prompts": true` - WHEN the setup script is run again - THEN it MUST NOT add a duplicate entry - AND it MUST print a message indicating the entry is already present @@ -98,19 +98,19 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` --- -### Requirement: thunder-plugin-rules.yaml (v3.3.0) created under PluginQA/rules/ -The file `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` MUST exist -with version `3.3.0` and contain all 79 rules numbered sequentially (rule_01 to rule_79). +### Requirement: thunder-plugin-rules.yaml (v3.3.0) created under PluginQualityAdvisor/rules/ +The file `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` MUST exist +with version `3.3.0` and contain all 70 rules numbered sequentially (rule_01 to rule_70). #### Scenario: Metadata block - GIVEN the YAML file - THEN it MUST contain a `metadata` block with: - `version: "3.3.0"`, `total_rules: 79`, `total_general_rules: 40`, + `version: "3.3.0"`, `total_rules: 70`, `total_general_rules: 32`, `approach: "semantic code review — understand whole plugin first, then check specifics"`, and a `validation_approach` block listing the 5-step workflow (understand whole plugin → focus on specific concern → reason in context → cite if genuinely wrong → fix) -#### Scenario: All 39 phase checkpoints present with required fields +#### Scenario: All 38 phase checkpoints present with required fields - GIVEN each phase checkpoint entry in the YAML (under phase sections) - THEN it MUST contain: `rule_id`, `name` (Title Case), `severity`, `phase`, `extraction` block (target / method / code_block), @@ -122,9 +122,9 @@ with version `3.3.0` and contain all 79 rules numbered sequentially (rule_01 to - AND conditional rules MUST include a `conditional: true` flag and a `skip_condition` describing when to skip -#### Scenario: All 40 Holistic Rules (8 sub-phases) present with required fields +#### Scenario: All 32 holistic rules (8 sub-phases) present with required fields - GIVEN each General rule entry in the YAML (under `general_rules` section) -- THEN it MUST contain: `rule_id` (e.g. "rule_40"), `name` (Title Case), +- THEN it MUST contain: `rule_id` (e.g. "rule_39"), `name` (Title Case), `severity`, `category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security)`, `review_question` (semantic question about what to verify), `review_method` (must state "Read the full relevant code context... @@ -133,14 +133,14 @@ with version `3.3.0` and contain all 79 rules numbered sequentially (rule_01 to - AND Holistic Rules (8 sub-phases) MUST NOT have `extraction`, `bounded_query`, or `verification_logic` fields #### Scenario: Report output is unified — no "automated" vs "manual" split -- GIVEN any review run completing all 79 rules +- GIVEN any review run completing all 70 rules - THEN the report MUST present ONE unified list of findings grouped by file - AND there MUST NOT be separate "Part 1" / "Part 2" or "Automated" / "Manual" sections -- AND the summary table MUST include all 79 rules in a single table with rows for each - phase plus "Holistic Rules (8 sub-phases)" and a "Total (79 rules)" footer row +- AND the summary table MUST include all 70 rules in a single table with rows for each + phase plus "Holistic Rules (8 sub-phases)" and a "Total (70 rules)" footer row #### Scenario: Phase breakdown matches spec -- GIVEN the 39 checkpoints distributed across phases +- GIVEN the 38 checkpoints distributed across phases - THEN the counts MUST be: Phase 1 (module_structure): 3 — `rule_01`, `rule_02`, `rule_03` Phase 2 (code_style): 10 — `rule_04`, `rule_05`, `rule_06`, @@ -154,17 +154,16 @@ with version `3.3.0` and contain all 79 rules numbered sequentially (rule_01 to `rule_24`, `rule_25`, `rule_26`, `rule_27`, `rule_28` Phase 5 (implementation): 4 — `rule_29`, `rule_30`, - `rule_31`, `rule_32` - Phase 5C (oop): 2 — `rule_33`, `rule_34` - Phase 6 (configuration): 3 — `rule_35`, `rule_36`, `rule_37` - Phase 7 (cmake): 1 — `rule_38` - Phase 8 (interfaces): 1 — `rule_39` + Phase 5C (oop): 2 — `rule_32`, `rule_33` + Phase 6 (configuration): 3 — `rule_34`, `rule_35`, `rule_36` + Phase 7 (cmake): 1 — `rule_37` + Phase 8 (interfaces): 1 — `rule_38` --- -### Requirement: thunder-interface-rules.yaml (v3.2.2) created under PluginQA/rules/ -The file `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` MUST exist -with version `3.2.2` and contain all 19 interface rule definitions (15 core + 4 advisory). +### Requirement: thunder-interface-rules.yaml (v3.2.2) created under PluginQualityAdvisor/rules/ +The file `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` MUST exist +with version `3.2.2` and contain all 19 interface rule definitions (16 core + 3 advisory). #### Scenario: File header present - GIVEN the YAML file @@ -172,23 +171,21 @@ with version `3.2.2` and contain all 19 interface rule definitions (15 core + 4 `version: 3.2.2`, `title`, `description` including the full CHANGELOG covering v3.2.2, v3.2.1, v3.2.0, v3.1.0, and v3.0.2 -#### Scenario: All 15 core rules present with required fields +#### Scenario: All 16 core rules present with required fields - GIVEN the `core_rules` list - THEN it MUST contain exactly these IDs in order: `core_1_1`, `core_2_1`, `core_3_1`, `core_4_1`, `core_5_1`, `core_6_1`, - `core_9_1`, `core_10_1`, `core_11_1`, `core_12_1`, `core_13_1`, `core_14_1`, - `core_15_1`, `core_16_1`, `core_17_1` -- AND each rule MUST contain: `id`, `name`, `severity: violation`, + `core_9_1`, `core_10_1`, `core_11_1`, `core_12_1`, `core_13_1`, + `core_14_1`, `core_15_1`, `core_16_1` +- AND each rule MUST contain: `id`, `name`, `severity`, `description`, `extraction_logic`, `verification_logic`, `violation_pattern`, `fix_template`, `citation` (with real Thunder interface file references) -#### Scenario: All 4 advisory rules present with required fields +#### Scenario: All 3 advisory rules present with required fields - GIVEN the `advisory_rules` list - THEN it MUST contain exactly these IDs: - `advisory_m1_1` (SRP — violation), `advisory_m2_1` (enum underlying types — warning), - `advisory_m3_1` (no exceptions — violation), - `advisory_m5_1` (@restrict for non-vector params — warning, - explicitly stating it does NOT apply to `std::vector`) + `advisory_m1_1` (SRP — warning), `advisory_m2_1` (enum underlying types — warning), + `advisory_m3_1` (no exceptions — violation) - AND each advisory rule MUST contain the same fields as core rules --- @@ -196,7 +193,7 @@ with version `3.2.2` and contain all 19 interface rule definitions (15 core + 4 ### Requirement: Plugin validation command with unified output The system MUST provide a `/thunder-plugin-review ` slash command -in VS Code Copilot Chat that validates a Thunder plugin against all 79 rules +in VS Code Copilot Chat that validates a Thunder plugin against all 70 rules using semantic code review, producing a single unified report. #### Scenario: Plugin found and reviewed @@ -205,7 +202,7 @@ using semantic code review, producing a single unified report. - THEN it locates `ThunderNanoServices/Dictionary/` automatically - AND identifies Dictionary.h, Dictionary.cpp, Module.h, Module.cpp, CMakeLists.txt, Dictionary.conf.in, and any OOP implementation files -- AND executes all 79 rules in order (phase checkpoints first, then General) +- AND executes all 70 rules in order (phase checkpoints first, then General) - AND outputs a single unified report showing ONLY failures with exact line citations #### Scenario: No plugin name provided @@ -280,14 +277,14 @@ after contextual judgment — not always the raw YAML severity. --- -### Requirement: 79 unified rules organised across phase groups and General concerns +### Requirement: 70 unified rules organised across phase groups and General concerns The prompt MUST load rule definitions from -`ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` at runtime. +`ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` at runtime. Each rule definition includes sufficient information for semantic validation. -All 79 rules produce the same output format. +All 70 rules produce the same output format. Phase breakdown: Phase 1: 3, Phase 2: 10, Phase 3: 3, Phase 4: 12, Phase 5: 4, -Phase 5C: 2, Phase 6: 3, Phase 7: 1, Phase 8: 1 = 39 phase rules + 40 holistic rules = 79 total. +Phase 5C: 2, Phase 6: 3, Phase 7: 1, Phase 8: 1 = 38 phase rules + 32 holistic rules = 70 total. --- @@ -437,17 +434,16 @@ NOT by running regular expressions or keyword searches against raw text. (e.g. `class NetworkNotification : public Exchange::INetwork::INotification`) to listen to an external service. That inner class member MUST be `Core::SinkType`, NOT a raw pointer (`ClassName* _member`) -- WHEN checkpoint rule_31 runs (violation, conditional) - IF any `Core::SinkType<>` subscriber class found: checks each class implements `void Unavailable()`; the validator MUST read the full class declaration and reason about whether the method exists — NOT by searching for the string "Unavailable" - SKIP if no Core::SinkType<> subscriber classes in plugin -- WHEN checkpoint rule_32 runs (violation) +- WHEN checkpoint rule_31 runs (violation) - THEN it checks for hardcoded absolute paths; the validator MUST read function bodies and reason about how paths are constructed from context — NOT by searching for path substrings #### Scenario: Phase 8 - COM Interface Rules (1 checkpoint) - GIVEN interface struct definitions in the plugin -- WHEN checkpoint rule_39 runs (violation) +- WHEN checkpoint rule_38 runs (violation) - THEN it checks that action/non-getter virtual methods on COM interfaces return `Core::hresult` - NOTE: The "no delete on COM interface pointers" rule is enforced in Phase 2 as checkpoint `rule_07` (applies to all .cpp files, not only interface structs) @@ -455,22 +451,22 @@ NOT by running regular expressions or keyword searches against raw text. #### Scenario: Phase 5C - Out-of-Process (2 checkpoints, conditional) - GIVEN Deinitialize() and IRemoteConnection::INotification callbacks of an OOP plugin - IF plugin is in-process: SKIP the entire phase -- WHEN checkpoint rule_33 runs +- WHEN checkpoint rule_32 runs - THEN it checks `service->RemoteConnection()` and `connection->Terminate()` and `connection->Release()` all present in Deinitialize() -- WHEN checkpoint rule_34 runs (violation, conditional) +- WHEN checkpoint rule_33 runs (violation, conditional) - THEN it checks that the very first action in every `Activated()` and `Deactivated()` callback is a guard checking `connection->Id() == _connectionId` - SKIP if plugin has no IRemoteConnection::INotification implementation -- WHEN checkpoint rule_35 runs (violation, conditional) +- WHEN checkpoint rule_34 runs (violation, conditional) the validator MUST read the file in full and reason about the startup configuration — note that `autostart` is not the same as `startmode` -- WHEN checkpoint rule_36 runs (violation, conditional) +- WHEN checkpoint rule_35 runs (violation, conditional) - IF `service->ConfigLine()` is called in Initialize(): checks that a typed `Config : public Core::JSON::Container` struct exists and its `FromString()` is called - SKIP if plugin does not call `service->ConfigLine()` -- WHEN checkpoint rule_37 runs (violation) +- WHEN checkpoint rule_36 runs (violation) - THEN it checks for hardcoded numeric tuning parameters (timeouts, retry counts, buffer sizes) as inline literals; the validator MUST read Initialize() in full and reason about the semantic meaning of each numeric value in context — NOT by searching for number patterns; @@ -478,20 +474,20 @@ NOT by running regular expressions or keyword searches against raw text. #### Scenario: Phase 7 - CMake (1 checkpoint) - GIVEN CMakeLists.txt -- WHEN checkpoint rule_38 runs +- WHEN checkpoint rule_37 runs - THEN it checks `CXX_STANDARD` is set to `${CXX_STD}` (Thunder variable), not a literal like `11` or `14`; the validator MUST read CMakeLists.txt in full and reason about each target's property settings --- -### Requirement: 40 Holistic Rules (8 sub-phases) loaded from YAML and reported in unified output -After the 39 phase checkpoints, the validator MUST also run the 40 Holistic Rules (8 sub-phases) +### Requirement: 32 holistic rules (8 sub-phases) loaded from YAML and reported in unified output +After the 38 phase checkpoints, the validator MUST also run the 32 holistic rules (8 sub-phases) loaded from the `general_rules` section of `thunder-plugin-rules.yaml`. All rules produce the same output format — there is no separate section for these rules. #### Scenario: Holistic Rules (8 sub-phases) integrated into unified output - GIVEN the phase checkpoint evaluation is complete -- WHEN the validator runs the 40 Holistic Rules (8 sub-phases) +- WHEN the validator runs the 32 holistic rules (8 sub-phases) - THEN any failures appear in the same file-grouped findings list as phase checkpoint failures - AND the summary table includes a "Holistic Rules (8 sub-phases)" row with PASS/FAIL/SKIP counts - AND there is NO separate "Part 2" or "Manual Review" heading in the output @@ -499,7 +495,7 @@ All rules produce the same output format — there is no separate section for th `extracted_code` (with [File:line] prefix where applicable), `violation_line`, `citation`, `fix`, `reasoning` - AND PASS rules are NOT listed individually — they appear only as counts in the summary table -- Holistic Rules (rule_40–rule_79) cover: +- Holistic Rules (rule_39–rule_70) cover: 1. `#pragma once` in every .h file (suggestion) 2. Apache 2.0 copyright headers in all source files (suggestion) 3. No STL types where Thunder equivalents exist (warning) @@ -510,7 +506,7 @@ All rules produce the same output format — there is no separate section for th 8. Observer AddRef before Register, Release after Unregister (violation) 9. AddRef/Release balance across plugin lifetime (violation) 10. Config struct used for all configuration (warning) - 11. CMake NAMESPACE variable set to WPEFramework (violation) + 11. CMake NAMESPACE variable set to Thunder (violation) 12. write_config() called LAST in CMakeLists.txt (suggestion) 13. Handlers must not block — dispatch to WorkerPool (violation) 14. No Activate/Deactivate calls from notification handlers (violation) @@ -518,7 +514,6 @@ All rules produce the same output format — there is no separate section for th 16. No framework callbacks (Notify, Submit, Register) called while holding _adminLock (violation) 17. WorkerPool jobs guard against post-Deinitialize execution (violation) 18. File descriptors and sockets wrapped in RAII (violation) - 19. No unbounded memory growth in event-fed containers (violation) 20. Config validation failures return non-empty string from Initialize (violation) 21. Notify() only called after Initialize() has completed (violation) 22. interface->Register paired with interface->Unregister in Deinitialize (violation) 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 5b9f3370..7f3abc61 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -1,4 +1,4 @@ -# Spec: Thunder PluginQA Report Generation +# Spec: Thunder PluginQualityAdvisor Report Generation ## Purpose @@ -12,9 +12,9 @@ with all fields needed to understand, prioritise, and track fixes. ### REQ-R1 — Plugin review CSV -**Scenario:** `/thunder-plugin-review` completes all 79 rules +**Scenario:** `/thunder-plugin-review` completes all 70 rules - The system MUST generate a CSV file at: - `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` + `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) - The CSV MUST contain one header row followed by one data row per VIOLATION or WARNING or SUGGESTION - PASS and SKIP rows are NOT included in the CSV (they appear only in the phase summary in chat) @@ -23,7 +23,7 @@ with all fields needed to understand, prioritise, and track fixes. **Scenario:** `/thunder-interface-review` completes all 19 rules - The system MUST generate a CSV file at: - `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` + `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` - Same no-overwrite rule applies - One row per violated rule only @@ -83,7 +83,7 @@ No,Interface,Date,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Des ### REQ-R6 — Folder creation -- `ThunderTools/PluginQA/Reports/plugin/` and `ThunderTools/PluginQA/Reports/interface/` +- `ThunderTools/PluginQualityAdvisor/Reports/plugin/` and `ThunderTools/PluginQualityAdvisor/Reports/interface/` MUST be created if they do not exist before writing the file ### REQ-R7 — Post-generation message @@ -92,11 +92,11 @@ After saving the CSV, the prompt MUST display in chat: ``` 📊 Report saved: - ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv + ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions To open in Excel (Windows): - Start-Process "ThunderTools\PluginQA\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" + Start-Process "ThunderTools\PluginQualityAdvisor\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" ``` ### REQ-R8 — Empty report (all PASS) @@ -113,7 +113,7 @@ No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_ ## Folder structure ``` -ThunderTools/PluginQA/ +ThunderTools/PluginQualityAdvisor/ └── Reports/ ├── plugin/ │ ├── HdmiCecSink_2026-06-05.csv diff --git a/.github/openspec/changes/thunder-plugin-qa/tasks.md b/.github/openspec/changes/thunder-plugin-qa/tasks.md index e73f31b3..f62fcc64 100644 --- a/.github/openspec/changes/thunder-plugin-qa/tasks.md +++ b/.github/openspec/changes/thunder-plugin-qa/tasks.md @@ -2,9 +2,9 @@ ## Phase 1: YAML rule definitions -- [x] 1.1 Create `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` (v3.3.0) +- [x] 1.1 Create `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` (v3.3.0) - metadata block: version, description, approach, - total_rules: 79, total_general_rules: 40, + total_rules: 70, total_general_rules: 32, organization: "Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1" - validation_approach block: principles list + 5-step workflow (including Step 3b JUDGE: contextual judgment — if developer's approach technically @@ -14,7 +14,7 @@ Never pattern-match. Always read the full relevant code context (control flow, ownership, lifecycle, threading) before deciding PASS/FAIL. - YAML STRUCTURE — PHASE RULES (39 rules, rule_01 to rule_39): + YAML STRUCTURE — PHASE RULES (38 rules, rule_01 to rule_38): - rule_id, name (Title Case), severity, phase - extraction: { target, method, code_block } - bounded_query: { question: "...", expected_answer: "Yes" } ← MUST be object, NOT plain string @@ -27,7 +27,7 @@ - citation: { line_format: "[PluginName.cpp:LINE] ...", rule: "thunder-plugin-rules.yaml / {id}" } YAML STRUCTURE — MANUAL RULES (semantic review, 40 rules): - - rule_id (e.g. "rule_40"), name (Title Case), severity, category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security) + - rule_id (e.g. "rule_39"), name (Title Case), severity, category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security) - review_question: semantic question describing what to verify - review_method: "Read the full relevant code context (control flow, ownership, lifecycle, and threading where applicable) before deciding PASS/FAIL/SUGGEST. @@ -60,7 +60,7 @@ rule_17 (violation conditional): "IShell AddRef in Initialize" rule_18 (violation conditional): "IShell Release in Deinitialize" rule_19 (violation): "Information() Method" — string Information() const implemented - rule_20 (violation conditional): "Root() Null Check" + rule_20 (violation conditional): "Root() nullptr Check" rule_21 (violation conditional): "Root() Release in Deinitialize" rule_22 (violation conditional): "Observer Cleanup in Deinitialize" rule_23 (violation conditional): "SubSystems() Release in Deinitialize" @@ -72,81 +72,71 @@ - phase_5_checkpoints (4): rule_29 (violation conditional): "JSON-RPC Register/Unregister Pairing" rule_30 (violation conditional): "SinkType Pattern for Subscribers" - rule_31 (violation conditional): "Unavailable() in SinkType Classes" - rule_32 (violation): "No Hardcoded Paths" + rule_31 (violation): "No Hardcoded Paths" - phase_5C_checkpoints (2): - rule_33 (violation conditional): OOP connection termination in Deinitialize - rule_34 (violation conditional): connectionId checked first in IRemoteConnection callbacks + rule_32 (violation conditional): OOP connection termination in Deinitialize + rule_33 (violation conditional): connectionId checked first in IRemoteConnection callbacks - phase_6_checkpoints (3): - rule_35 (violation conditional): startmode declaration - rule_36 (violation conditional): Config Core::JSON::Container - rule_37 (violation): no hardcoded numeric tuning params - - phase_7_checkpoints (1): rule_38 (violation conditional): CXX_STANDARD uses ${CXX_STD} - - phase_8_checkpoints (1): rule_39 (violation): "COM Methods Return Core::hresult" + rule_34 (violation conditional): startmode declaration + rule_35 (violation conditional): Config Core::JSON::Container + rule_36 (violation): no hardcoded numeric tuning params + - phase_7_checkpoints (1): rule_37 (violation conditional): CXX_STANDARD uses ${CXX_STD} + - phase_8_checkpoints (1): rule_38 (violation): "COM Methods Return Core::hresult" Holistic Rules (8 sub-phases) (40, all in general_rules section): - rule_40 (suggestion): "#pragma once" - rule_41 (suggestion): "Apache 2.0 Copyright Header" - rule_42 (warning): "STL Types" - rule_43 (warning): "ASSERT vs Error Handling" - rule_44 (violation): "OOP Registration Order" - rule_45 (violation): "Complete State Reset in Deinitialize" - rule_46 (suggestion): "Reverse-Order Cleanup" - rule_47 (violation): "Observer Locking" - rule_48 (violation): "AddRef/Release Balance" - rule_49 (suggestion): "CMake NAMESPACE Variable" - rule_50 (violation): "Handlers Must Not Block" - rule_51 (violation): "No Activate/Deactivate from Handlers" - rule_52 (violation): "Shared State Protected by CriticalSection" - rule_53 (violation): "No Lock Held During Framework Callbacks" - rule_54 (violation): "Worker Jobs Check Deinitialize Guard" - rule_55 (violation): "File Descriptors / Sockets Wrapped in RAII" - rule_56 (violation): "No Unbounded Memory Growth" - rule_57 (violation): "Config Errors Return Non-Empty from Initialize" - rule_58 (violation): "interface->Register/Unregister Pairing" - rule_59 (violation): "Handler Registration Order in Initialize/Deinitialize" - rule_60 (violation): "Use Core::ERROR_* for Handler Failure Codes" - rule_61 (violation): "Input Validation in JSON-RPC Handlers" - rule_62 (warning): "Event Constants and Typed JSON Payloads" - rule_63 (violation): "COM Reference Counting Correctness" - rule_64 (warning): "No Hard Inter-Plugin Dependencies" - rule_65 (violation): "JSON-RPC Handlers Are Re-entrant Safe" - rule_66 (violation): "IPlugin::INotification Callbacks Must Not Block" - rule_67 (violation): "Lock Scope Minimized" - rule_68 (violation): "Plugin Threads Joined in Deinitialize" - rule_69 (warning): "Memory and Allocation Safety" - rule_70 (violation): "Framework Pointers Not Accessed After Deinitialize" - rule_71 (violation): "hresult Return Values Checked" - rule_72 (warning): "ASSERT Only for Programmer Invariants" - rule_73 (violation): "Security: Logging, Shell, Path, and Error Exposure" - rule_74 (violation): "JSON-RPC Input Validation for Bounds and Types" - rule_75 (warning): "Config Completeness and Resource Cleanup" - rule_76 (warning): "OOP Error Propagation and Method Naming" - rule_77 (suggestion): "Observer Classes Private and Nested" - rule_78 (violation): "No Deprecated JSON-RPC APIs" - rule_79 (violation): "All Acquired Pointers Cleared After Deinitialize" + rule_39 (suggestion): "#pragma once" + rule_40 (suggestion): "Apache 2.0 Copyright Header" + rule_41 (warning): "STL Types" + rule_42 (warning): "ASSERT vs Error Handling" + rule_43 (violation): "OOP Registration Order" + rule_44 (violation): "Complete State Reset in Deinitialize" + rule_41 (suggestion): "Reverse-Order Cleanup" + rule_42 (violation): "Observer Locking" + rule_43 (violation): "AddRef/Release Balance" + rule_44 (suggestion): "CMake NAMESPACE Variable" + rule_45 (violation): "Handlers Must Not Block" + rule_46 (violation): "No Activate/Deactivate from Handlers" + rule_47 (violation): "Shared State Protected by CriticalSection" + rule_48 (violation): "No Lock Held During Framework Callbacks" + rule_49 (violation): "Worker Jobs Safe After Deinitialize" + rule_50 (violation): "File Descriptors / Sockets Wrapped in RAII" + rule_51 (violation): "Config Errors Return Non-Empty from Initialize" + rule_52 (violation): "interface->Register/Unregister Pairing" + rule_53 (violation): "Handler Registration Order in Initialize/Deinitialize" + rule_54 (violation): "Use Core::ERROR_* for Handler Failure Codes" + rule_55 (warning): "Event Constants and Typed JSON Payloads" + rule_56 (violation): "COM Reference Counting Correctness" + rule_57 (warning): "No Hard Inter-Plugin Dependencies" + rule_58 (violation): "JSON-RPC Handlers Are Re-entrant Safe" + rule_59 (violation): "IPlugin::INotification Callbacks Must Not Block" + rule_60 (violation): "Lock Scope Minimized" + rule_61 (violation): "Plugin Threads Joined in Deinitialize" + rule_63 (violation): "hresult Return Values Checked" + rule_64 (warning): "ASSERT Only for Programmer Invariants" + rule_65 (violation): "Security: Logging, Shell, Path, and Error Exposure" + rule_66 (warning): "Config Completeness and Resource Cleanup" + rule_67 (suggestion): "Observer Classes Private and Nested" + rule_68 (violation): "No Deprecated JSON-RPC APIs" -- [x] 1.2 Create `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` (v3.2.2) +- [x] 1.2 Create `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` (v3.2.2) - File header: version (unquoted number: 3.2.2, NOT quoted string "3.2.2"), title, description with full CHANGELOG using format: `v3.2.2 (current):` (NOT dates like `v3.2.2 (2026-06-08):`) - Section headers use `# ===...===` style (NOT `# ───...───`) - Rule names use Title Case (e.g. "File and Namespace Structure" not "File and namespace structure") - - core_rules list (15 rules, all severity: violation): - core_1_1 ("File and Namespace Structure"), core_2_1 ("Interface Declaration Shape"), - core_3_1 ("Interface ID Registration"), core_4_1 ("Pure Virtual Methods Only"), + - core_rules list (14 rules): + core_1_1 ("File and Namespace Structure"), core_2_1 ("Interface Declaration Shape" — warning), + core_3_1 ("Interface ID Registration"), core_4_1 ("Pure Virtual Methods Only" — warning), core_5_1 ("Return Type Conventions" — Core::hresult mandatory for @json in Thunder 5.0+), core_6_1 ("Const Correctness"), core_9_1 ("Thunder Type Conventions" — string not std::string), core_10_1 ("Register/Unregister Patterns" — INotification 1:many + ICallback 1:1), - core_11_1 ("Nested Event Interfaces" — @event tag, EXTERNAL, ID required), - core_12_1 ("@json Tag (CRITICAL)"), core_13_1 ("Binary Compatibility"), - core_14_1 ("No AddRef/Release Redeclaration"), core_15_1 ("No std::map in Interfaces"), - core_16_1 ("Explicit Integer Widths"), core_17_1 ("@restrict Mandatory with std::vector") - - advisory_rules list (4 rules): - advisory_m1_1 ("Single Responsibility Principle" — violation), + core_11_1 ("Event Interfaces" — @event tag, EXTERNAL, ID required), + core_12_1 ("@json Tag (CRITICAL)" — warning), + core_13_1 ("No IUnknown/IReferenceCounted Methods in Interfaces"), core_14_1 ("No std::map in Interfaces"), + core_15_1 ("Explicit Integer Widths"), core_16_1 ("@restrict Mandatory with std::vector") + - advisory_rules list (3 rules): + advisory_m1_1 ("Single Responsibility Principle" — warning), advisory_m2_1 ("Enum Underlying Types" — warning, exclude anonymous ID enum), - advisory_m3_1 ("No Exceptions" — violation), - advisory_m5_1 ("@restrict for Non-vector Params" — warning, - explicitly states: does NOT apply to std::vector, that is covered by core_17_1) + advisory_m3_1 ("No Exceptions" — violation) - Each rule structure: id, name, severity, description (multiline), extraction_logic (numbered steps), verification_logic (numbered steps), violation_pattern (single-line string NOT bullet list), fix_template (shows // WRONG: + // Correct: pattern), citation (reference to real Thunder file) @@ -154,10 +144,10 @@ ## Phase 2: Prompt files -- [x] 2.1 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` - Frontmatter: title: "Thunder Plugin Rule Review", description (mention 79 unified rules, semantic review) +- [x] 2.1 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` + Frontmatter: title: "Thunder Plugin Rule Review", description (mention 70 unified rules, semantic review) Sections (in order): - - Core Principle: semantic code review for all 79 rules. NEVER pattern-match. + - Core Principle: semantic code review for all 70 rules. NEVER pattern-match. ❌ open-ended vs ✅ bounded examples. All rules produce the same output format. - Report Output Philosophy: CRITICAL note — only report issues; PASS/SKIP as summary counts only; line numbers always required; always use ACTUAL plugin name in citations, NEVER {PluginName} @@ -181,8 +171,8 @@ - For any file: run applicable Holistic Rules (8 sub-phases) that match the file type 5-step workflow (accept name + optional file → locate folder → identify target files → run applicable rules only → report) - - Methodology: Step 1 (load YAML from ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml — - contains all 79 rules in phase_X_checkpoints and general_rules sections), + - Methodology: Step 1 (load YAML from ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml — + contains all 70 rules in phase_X_checkpoints and general_rules sections), Step 2 (identify plugin files — primary: ThunderNanoServices/{PluginName}/, fallback: workspace search, last resort: ask user; files table: Module.cpp, Module.h, {PluginName}.h, {PluginName}.cpp, CMakeLists.txt, {PluginName}.conf.in (optional), {PluginName}Implementation.h/cpp (optional)), @@ -191,7 +181,7 @@ Class Registration (Phase 3) applies ONLY to main plugin class — internal helpers excluded - Contextual Judgment section: severity downgrade table with concrete example showing reasoning field (required on downgrade, omitted otherwise), no escalation rule - - A shortened inline quick-reference list of all 79 rules is included in the prompt (rule_id + severity + high-level target only). + - A shortened inline quick-reference list of all 70 rules is included in the prompt (rule_id + severity + high-level target only). Full rule definitions (extraction, bounded_query, verification_logic, fix_template) are loaded from the YAML files at runtime (source of truth). Phase counts: Phase 1 Module Structure (3): @@ -217,7 +207,7 @@ rule_17 (violation conditional): "IShell AddRef in Initialize" rule_18 (violation conditional): "IShell Release in Deinitialize" rule_19 (violation): "Information() Method" - rule_20 (violation conditional): "Root() Null Check" + rule_20 (violation conditional): "Root() nullptr Check" rule_21 (violation conditional): "Root() Release in Deinitialize" rule_22 (violation conditional): "Observer Cleanup in Deinitialize" rule_23 (violation conditional): "SubSystems() Release in Deinitialize" @@ -229,20 +219,19 @@ Phase 5 Implementation (4): rule_29 (violation conditional): "JSON-RPC Register/Unregister Pairing" rule_30 (violation conditional): "SinkType Pattern for Subscribers" - rule_31 (violation conditional): "Unavailable() in SinkType Classes" - rule_32 (violation): "No Hardcoded Paths" + rule_31 (violation): "No Hardcoded Paths" Phase 5C Out-of-Process (2 conditional): - rule_33: "OOP Connection Termination in Deinitialize" - rule_34: "connectionId Checked in IRemoteConnection Callbacks" + rule_32: "OOP Connection Termination in Deinitialize" + rule_33: "connectionId Checked in IRemoteConnection Callbacks" Phase 6 Configuration (3): - rule_35 (violation conditional): startmode declaration - rule_36 (violation conditional): Config Core::JSON::Container - rule_37 (violation): no hardcoded numeric tuning params + rule_34 (violation conditional): startmode declaration + rule_35 (violation conditional): Config Core::JSON::Container + rule_36 (violation): no hardcoded numeric tuning params Phase 7 CMake (1): - rule_38 (violation conditional): "CXX_STANDARD Uses Thunder Variable" + rule_37 (violation conditional): "CXX_STANDARD Uses Thunder Variable" Phase 8 COM Interface Rules (1): - rule_39 (violation): "COM Methods Return Core::hresult" - - Output Format: UNIFIED FILE-WISE grouping — all issues from all 79 rules grouped by + rule_38 (violation): "COM Methods Return Core::hresult" + - Output Format: UNIFIED FILE-WISE grouping — all issues from all 70 rules grouped by source file with a header "### {FileName} — N issue(s)" for each file that has failures; within each file group, each failing rule is a YAML block with fields: rule_id, status (FAIL/PASS/SKIP), severity (violation/warning/suggestion), @@ -250,18 +239,18 @@ citation using ACTUAL plugin filename, fix, reasoning. NO separate "Part 1" / "Part 2" sections — all findings in one list. - Summary Format: Single unified Summary TABLE with columns Phase | PASS | FAIL | SKIP - (one row per phase + one row for "Holistic Rules (8 sub-phases)" + a Total row showing all 79), + (one row per phase + one row for "Holistic Rules (8 sub-phases)" + a Total row showing all 70), followed by a numbered Next Steps list referencing [File:line] for each action item - Key Advantages section, Important Notes section - Command Examples at end -- [x] 2.2 Create `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` - Frontmatter: title: "Thunder Interface Validator", description (mention 19 rules, 15 core + 4 advisory) +- [x] 2.2 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` + Frontmatter: title: "Thunder Interface Validator", description (mention 19 rules, 16 core + 3 advisory) Sections (in order): - Context: Thunder uses COM-style interfaces, rules in thunder-interface-rules.yaml (v3.2.2) - - Quick Reference table: all 19 rules (15 core + 4 advisory) as a markdown table + - Quick Reference table: all 19 rules (16 core + 3 advisory) as a markdown table with ID, Rule name (Title Case matching YAML), Key Point - - CRITICAL note: load ThunderTools/PluginQA/rules/thunder-interface-rules.yaml for full + - CRITICAL note: load ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml for full rule definitions, extraction logic, verification logic, and fix templates before validating - Your Task: 5 steps (identify file → load YAML → validate all 19 rules → report with 🔴/🟡/🟢/✅/Compatibility Notes sections → provide specific fixes) @@ -274,7 +263,7 @@ - Important Notes: Thunder docs link, validation priorities numbered list (@json first through type conventions last) -- [x] 2.3 Create `ThunderTools/PluginQA/Prompts/thunder-plugin-rule-manager.prompt.md` +- [x] 2.3 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md` Frontmatter: title: "Thunder Plugin Rule Manager", description: "Add, update, or remove plugin rules (automated or manual) via guided questionnaire — keeps YAML, prompt, README, and spec in sync" Sections (in order): @@ -310,7 +299,7 @@ - Phase vs General Classification table at end: 5 phase checkpoint criteria vs 6 General criteria; ambiguous cases prompt user before proceeding -- [x] 2.4 Create `ThunderTools/PluginQA/Prompts/thunder-interface-rule-manager.prompt.md` +- [x] 2.4 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md` Frontmatter: title: "Thunder Interface Rule Manager", description: "Add, update, or remove COM interface validation rules via guided questionnaire — keeps YAML, prompt, and spec in sync" Sections (in order): @@ -338,7 +327,7 @@ - Core vs Advisory Classification table at end: core = codegen/ABI/crash failures; advisory = best practice; wrong list auto-corrected with explanation -- [x] 2.5 Create `ThunderTools/PluginQA/Prompts/thunder-generate-plugin.prompt.md` +- [x] 2.5 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-generate-plugin.prompt.md` Frontmatter: title: "Thunder Plugin Generator", description: "Interactive Thunder plugin skeleton generator using PluginSkeletonGenerator.py" Sections: @@ -360,13 +349,13 @@ ## Phase 3: Setup script -- [x] 3.1 Create `ThunderTools/PluginQA/setup-prompts.py` (cross-platform Python 3) +- [x] 3.1 Create `ThunderTools/PluginQualityAdvisor/setup-prompts.py` (cross-platform Python 3) - No external dependencies (stdlib only: json, os, sys, shutil, platform, datetime) - Detect platform (Windows/Mac/Linux) and find settings.json accordingly - Also detect VS Code Insiders variant - Backup: copy settings.json to settings.json.backup.{timestamp} - Parse JSON (handle missing or empty file: default to {}) - - Merge: add "ThunderTools/PluginQA/Prompts": true under chat.promptFilesLocations + - Merge: add "ThunderTools/PluginQualityAdvisor/Prompts": true under chat.promptFilesLocations - Write back with indent=4 - Idempotent: skip if key already present - Print clear next steps @@ -374,15 +363,15 @@ ## Phase 4: Documentation -- [x] 4.1 Create `ThunderTools/PluginQA/README.md` - Sections (in order, matching final PluginQA README exactly): - - Title: Thunder PluginQA +- [x] 4.1 Create `ThunderTools/PluginQualityAdvisor/README.md` + Sections (in order, matching final PluginQualityAdvisor README exactly): + - Title: Thunder PluginQualityAdvisor - Overview: automated validation tools, bullet list: Thunder COM Interfaces (19 rules), - Thunder Plugins (39 checkpoints, 8 phases) + Thunder Plugins (38 checkpoints, 8 phases) - Quick Start: Setup (3 platform options with code blocks), Reload VS Code (2 steps + note), Use the Prompts (/thunder-interface-review, /thunder-plugin-review, /thunder-generate-plugin) each with description - - Directory Structure: tree diagram of ThunderTools/PluginQA/ + - Directory Structure: tree diagram of ThunderTools/PluginQualityAdvisor/ - Interface Validation section: intro, Core Rules table (15 rows: ID, Rule, Critical Issues), Advisory Rules list (4 bullets), link to YAML - Plugin Validation section: intro, Validation Phases table by phase name with checkpoint count, @@ -401,7 +390,7 @@ - Support: 3 bullet points including Thunder docs URL - Footer: Made with ⚡ for Thunder developers -- [x] 4.2 Create `ThunderTools/PluginQA/PLUGIN_GENERATOR_GUIDE.md` +- [x] 4.2 Create `ThunderTools/PluginQualityAdvisor/PLUGIN_GENERATOR_GUIDE.md` Sections: - Title, Overview paragraph - Quick Start: 5 numbered steps @@ -412,16 +401,16 @@ - Troubleshooting: common issues (PSG not found, include path errors) - Integration with validation commands -- [x] 4.3 Create `ThunderTools/PluginQA/Prompts/README-interface-rules-manager.md` +- [x] 4.3 Create `ThunderTools/PluginQualityAdvisor/Prompts/README-interface-rules-manager.md` - Documentation for the interface rules manager (optional supplemental prompt) - Covers: how to add new rules to thunder-interface-rules.yaml, rule structure reference, testing new rules against example interfaces ## Phase 5: Report generation -- [x] 5.1 Add Step 6 (CSV report) to `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` +- [x] 5.1 Add Step 6 (CSV report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` - Appended after Command Examples section - - File path: `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` + - File path: `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - Create folder if absent; never overwrite (append _2, _3 suffix) - Columns (14, exact order): No, Plugin, Date, Phase, Rule_ID, Rule_Name, Status, Severity, File, Line, Citation, Issue_Description, Fix_Summary, Reasoning @@ -432,9 +421,9 @@ - Empty report (all pass): header row + one comment row - Post-generation chat message: file path, issue counts, Start-Process command for Excel -- [x] 5.2 Add Step 6 (CSV report) to `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` +- [x] 5.2 Add Step 6 (CSV report) to `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` - Appended after Important Notes section - - File path: `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` + - File path: `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` - Same no-overwrite rule, same formatting rules - Columns (13, exact order): No, Interface, Date, Rule_ID, Rule_Name, Status, Severity, File, Line, Citation, Issue_Description, Fix_Summary, Reasoning diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md index 5a30aae3..ab37e195 100644 --- a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md @@ -2,7 +2,7 @@ Thunder uses COM-style interfaces defined in `ThunderInterfaces/interfaces/`. These interfaces are binary contracts used across process boundaries. All validation uses semantic reasoning — the validator reads the interface header in full as a human reviewer would, never using regex or text search as the primary detection method. -Rules are loaded at runtime from: `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` +Rules are loaded at runtime from: `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` --- @@ -10,37 +10,37 @@ Rules are loaded at runtime from: `ThunderTools/PluginQA/rules/thunder-interface | ID | Rule | Key Point | |----|------|-----------| -| core_1_1 | File and Namespace Structure | Must be in WPEFramework::Exchange namespace; file name matches interface name | -| core_2_1 | Interface Declaration Shape | struct EXTERNAL, virtual Core::IUnknown, ID enum, all pure virtual | +| core_1_1 | File Structure | File name matches interface name; no implementation code (suggestion) | +| core_2_1 | Interface Declaration Shape | struct EXTERNAL, virtual Core::IUnknown, ID enum, all pure virtual (warning) | | core_3_1 | Interface ID Registration | ID must be RPC::ID_* constant registered in ids.h | -| core_4_1 | Pure Virtual Methods Only | All methods = 0, no inline code, no static methods | -| core_5_1 | Return Type Conventions | Core::hresult mandatory for @json interfaces in Thunder 5.0+ | -| core_6_1 | Const Correctness | @out parameters must be non-const references | +| core_4_1 | Pure Virtual Methods Only | All methods = 0, no inline code, no static methods (warning) | +| core_5_1 | Return Type Conventions | Core::hresult mandatory for @json interfaces in Thunder 5.0+; notification methods MUST return void | +| core_6_1 | Const Correctness | @out params must be non-const refs; notification methods must NEVER be const | | core_9_1 | Thunder Type Conventions | string not std::string; explicit integer widths | | core_10_1 | Register/Unregister Patterns | INotification: Register+Unregister; ICallback: Callback(nullptr clears) | -| core_11_1 | Nested Event Interfaces | @event tag required; EXTERNAL; own ID; inherits Core::IUnknown | -| core_12_1 | @json Tag (CRITICAL) | Without @json, ZERO RPC code generated | -| core_13_1 | Binary Compatibility | No method removal, reordering, or signature changes in released interfaces | -| core_14_1 | No AddRef/Release Redeclaration | Inherited from Core::IUnknown — never redeclare | -| core_15_1 | No std::map in Interfaces | Not serialisable across process boundaries | -| core_16_1 | Explicit Integer Widths | uint32_t not int; no platform-dependent types | -| core_17_1 | @restrict Mandatory with std::vector | Every std::vector parameter must have @restrict:N | +| core_11_1 | Event Interfaces | @event tag required; EXTERNAL; own ID; inherits Core::IUnknown | +| core_12_1 | @json Tag (CRITICAL) | Without @json, ZERO RPC code generated (warning; check for @alias/@text hints) | +| core_13_1 | No IUnknown/IReferenceCounted Methods in Interfaces | Inherited from Core::IUnknown — never redeclare | +| core_14_1 | No std::map in Interfaces | Not serialisable across process boundaries | +| core_15_1 | Explicit Integer Widths | uint32_t not int; no platform-dependent types | +| core_16_1 | @restrict Mandatory with std::vector | Every std::vector parameter must have @restrict:N | +| core_17_1 | No Method Overloads in @json Interfaces | JSON-RPC dispatches by name only; check @text for collisions | +| core_18_1 | No Reserved JSON-RPC Method Names | version/versions/exists are reserved by framework | | advisory_m1_1 | Single Responsibility Principle | One coherent purpose per interface | | advisory_m2_1 | Enum Underlying Types | Named enums used as params need explicit type; anonymous ID enum excluded | | advisory_m3_1 | No Exceptions | No throw; use Core::hresult for errors | -| advisory_m5_1 | @restrict for Non-vector Params | Advisory for non-vector params with natural bounds (not std::vector — covered by core_17_1) | --- -> **CRITICAL:** Load `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` before validating. The YAML contains the full rule definitions, extraction logic, verification logic, and fix templates. This quick reference is a navigation aid only. +> **CRITICAL:** Load `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` before validating. The YAML contains the full rule definitions, extraction logic, verification logic, and fix templates. This quick reference is a navigation aid only. --- ## Your Task 1. **Identify** the interface file to validate (from user's command or ask if not provided) -2. **Load** `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` for full rule definitions -3. **Validate** the interface against all 19 rules in order (core_1_1 → core_17_1, then advisory_m1_1 → advisory_m5_1) +2. **Load** `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` for full rule definitions +3. **Validate** the interface against All 19 Rules in order (core_1_1 → core_18_1, then advisory_m1_1 → advisory_m3_1) 4. **Report** with grouped sections using the output format below 5. **Provide** specific fix examples using the `fix_template` from the YAML @@ -48,13 +48,36 @@ For each rule, apply contextual judgment (JUDGE step): if the developer's approa --- +## Step 3 — Execute Rules (CRITICAL: Understand First, Then Check) + +**Thunder Version Detection:** +- `namespace WPEFramework` → **pre-Thunder 5.0** interface +- `namespace Thunder` → **Thunder 5.0+** interface + +This affects which rules apply: +- **core_5_1** (Return Type Conventions — Core::hresult mandatory): Only applies to Thunder 5.0+ interfaces. Pre-5.0 interfaces correctly use `uint32_t` — do NOT flag as a violation. + +**Review philosophy for ALL 19 rules:** + +1. **UNDERSTAND FIRST** — Read the ENTIRE interface header file. Build a complete mental model of the interface's purpose, method contracts, notification patterns, type usage, and how Register/Unregister relate to each other. Do this ONCE before checking any rule. +2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. +3. **REASON** — Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full interface context make this approach correct and safe?" If yes → downgrade severity. +4. **CITE** — If genuinely wrong (not just technically different), cite exact `[InterfaceName.h:LINE]` +5. **FIX** — Show corrected code using `fix_template` + +**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole interface — not because a single block viewed in isolation doesn't match a pattern. + +**CRITICAL:** Never use regex or pattern matching as the primary detection method — always use semantic understanding. Read the interface as a human reviewer would, reasoning about the meaning and intent of each declaration. + +--- + ## Step 6 — Generate CSV Report After reporting results in chat, generate a CSV file: -**File path:** `ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` +**File path:** `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` -- Create `ThunderTools/PluginQA/Reports/interface/` if it does not exist +- Create `ThunderTools/PluginQualityAdvisor/Reports/interface/` if it does not exist - Never overwrite an existing file — append `_2`, `_3` etc. if needed **CSV columns (exact order):** @@ -92,11 +115,11 @@ No,Interface,Date,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Des **Post-generation message:** ``` 📊 Report saved: - ThunderTools/PluginQA/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv + ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions To open in Excel (Windows): - Start-Process "ThunderTools\PluginQA\Reports\interface\{InterfaceName}_{YYYY-MM-DD}.csv" + Start-Process "ThunderTools\PluginQualityAdvisor\Reports\interface\{InterfaceName}_{YYYY-MM-DD}.csv" ``` --- @@ -120,7 +143,7 @@ Fix: ### 🟡 Warnings (Should Fix) -**core_16_1 — Explicit Integer Widths** +**core_15_1 — Explicit Integer Widths** Status: WARNING Citation: [IHdmiCecSink.h:72] int parameter — use uint32_t Issue: int type is platform-dependent and may differ in size across architectures. @@ -137,8 +160,8 @@ Fix: Replace `const int volume` with `const uint32_t volume` ### ✅ Validated (N rules passed) core_1_1, core_2_1, core_3_1, core_4_1, core_5_1, core_6_1, core_9_1, -core_10_1, core_11_1, core_13_1, core_14_1, core_15_1, core_17_1, -advisory_m1_1, advisory_m2_1, advisory_m3_1, advisory_m5_1 +core_10_1, core_11_1, core_13_1, core_14_1, core_16_1, +advisory_m1_1, advisory_m2_1, advisory_m3_1 --- diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md index ce16662c..25479893 100644 --- a/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md @@ -1,15 +1,10 @@ ---- -title: "Thunder Interface Rule Manager" -description: "Add, update, or remove COM interface validation rules via guided questionnaire — keeps YAML, prompt, and spec in sync" ---- - ## Purpose -This prompt manages rules in `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: +This prompt manages rules in `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: -1. `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` — rule data -2. `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` — Quick Reference table + rule detail blocks -3. `ThunderTools/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` — spec requirements +1. `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` — rule data +2. `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` — Quick Reference table + rule detail blocks +3. `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` — spec requirements --- @@ -201,6 +196,4 @@ After completing all changes, display: | Best practice / style improvement | ❌ | ✅ | | Improves maintainability or SRP | ❌ | ✅ | -**Wrong-list correction:** If a submitted rule clearly belongs in the other list (e.g. a crash-causing rule submitted as advisory), move it to the correct list and explain the reclassification to the user before applying changes. - - +**Wrong-list correction:** If a submitted rule clearly belongs in the other list (e.g. a crash-causing rule submitted as advisory), move it to the correct list and explain the reclassification to the user before applying changes. \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md index ceaf80bd..609ae9ac 100644 --- a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md @@ -2,12 +2,12 @@ This prompt performs **semantic code review** — reading plugin source code as a human developer and reasoning about its meaning. -**Phase checkpoint rules (39):** Each uses a bounded yes/no query on a specific code block. +**Phase checkpoint rules (38):** Each uses a bounded yes/no query on a specific code block. ❌ Open-ended (never do this): "Check this file for issues" ✅ Bounded (always this): "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" -**Holistic rules (40):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. +**Holistic rules (32):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. --- @@ -64,9 +64,9 @@ Examples: ### Step 1 — Load Rules -Load `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml`. This file contains all 79 rules: -- `phase_1_checkpoints` through `phase_8_checkpoints` — 39 rules with bounded queries -- `general_rules` — 40 holistic rules across 8 sub-phases (rule_40 to rule_79) +Load `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml`. This file contains all 70 rules: +- `phase_1_checkpoints` through `phase_8_checkpoints` — 38 rules with bounded queries +- `general_rules` — 32 holistic rules across 8 sub-phases (rule_39 to rule_70) All rules produce the same output format. There is no distinction between "phase checkpoint" and "holistic" in the report. @@ -89,7 +89,15 @@ Last resort: Ask user for location ### Step 3 — Execute Rules (CRITICAL: Understand First, Then Check) -**Review philosophy for ALL 79 rules:** +**Thunder Version Detection:** +- `namespace WPEFramework` → **pre-Thunder 5.0** plugin +- `namespace Thunder` → **Thunder 5.0+** plugin + +This affects which rules apply: +- **rule_38** (COM Methods Return Core::hresult): Only applies to Thunder 5.0+ plugins. Pre-5.0 plugins correctly use `uint32_t` — do NOT flag as a violation. +- **rule_31** (No Hardcoded Paths): Linux kernel virtual filesystems (`/proc/`, `/sys/`, `/dev/`) are fixed OS paths, not deployment-specific — do NOT flag these. + +**Review philosophy for ALL 70 rules:** 1. **UNDERSTAND FIRST** — Read ALL plugin source files. Build a complete mental model of the plugin's architecture: its lifecycle flow, threading model, ownership patterns, data flow, and how Initialize/Deinitialize relate to each other. Do this ONCE before checking any rule. 2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. @@ -139,262 +147,19 @@ fix: "_service->AddRef(); // add immediately after _service = service;" reasoning: "The rule exists to prevent dangling pointers when the IShell* is stored across calls. In this plugin, _service is used only within Initialize() and does not persist — it is not a true member field storing the pointer for later use. The developer's approach is valid in this context with no residual lifetime risk." ``` ---- - -## Rules Reference (79 rules, sequential) - -### Phase 1 — Module Structure (rule_01 to rule_03) - -**rule_01** (suggestion) — `MODULE_NAME Plugin_ Prefix` -- Target: `Module.h` only -- Query: Does MODULE_NAME value start with `Plugin_` prefix? - -**rule_02** (violation) — `MODULE_NAME_DECLARATION` -- Target: `Module.cpp` only -- Query: Does Module.cpp contain `MODULE_NAME_DECLARATION(BUILD_REFERENCE)`? - -**rule_03** (warning) — `Module.h Uses #pragma once` -- Target: `Module.h` only -- Query: Does Module.h use `#pragma once` instead of a legacy `#ifndef` guard? - ---- - -### Phase 2 — Code Style (rule_04 to rule_13) - -**rule_04** (violation) — `VARIABLE_IS_NOT_USED Accuracy` -- Query: Are all VARIABLE_IS_NOT_USED-annotated parameters genuinely unused? - -**rule_05** (violation) — `Error Code Preservation` -- Query: Is the error code preserved on all paths — never unconditionally overwritten? - -**rule_06** (warning) — `NULL vs nullptr` -- Query: Is nullptr used exclusively — no NULL as a pointer value? - -**rule_07** (violation) — `No delete on COM Interface Pointers` -- Query: Are there zero uses of delete on COM interface pointer types? - -**rule_08** (violation) — `nullptr After Release` -- Query: Is nullptr assigned immediately after every ->Release() on a member? - -**rule_09** (violation, conditional) — `No QueryInterfaceByCallsign as Member` -- SKIP if no QueryInterfaceByCallsign() calls -- Query: Is the result always used transiently (never stored as member)? - -**rule_10** (violation) — `No Smart Pointers on COM Objects` -- Query: Are there zero COM interface pointers wrapped in shared_ptr or unique_ptr? - -**rule_11** (violation) — `No SmartLinkType for COMRPC Plugins` -- Query: Is SmartLinkType absent from the plugin? - -**rule_12** (violation) — `No delete on Plugin Object` -- Query: Is delete this absent from all plugin code? - -**rule_13** (violation) — `No throw Keyword in Plugin Code` -- Query: Is throw absent from all executable plugin code? - ---- - -### Phase 3 — Class Registration (rule_14 to rule_16) - -**rule_14** (warning) — `Special Members Deleted (Main Class)` -- MAIN class only — helper classes excluded -- Query: Are all 4 special members deleted in the main plugin class? - -**rule_15** (violation) — `Plugin Metadata Registration` -- Query: Does the plugin .cpp contain Plugin::Metadata registration? - -**rule_16** (violation, conditional) — `JSONRPC Inheritance When Used` -- SKIP if no JSON-RPC Register() calls -- Query: Does the class inherit PluginHost::JSONRPC when JSON-RPC is used? - ---- - -### Phase 4 — Lifecycle (rule_17 to rule_28) - -**rule_17** (violation, conditional) — `IShell AddRef in Initialize` -- SKIP if no stored IShell* member -- Query: Is AddRef() called immediately after assignment? - -**rule_18** (violation, conditional) — `IShell Release in Deinitialize` -- SKIP if no stored IShell* member -- Query: Is Release() + nullptr called in Deinitialize()? - -**rule_19** (violation) — `Information() Method` -- Query: Does the plugin implement string Information() const? - -**rule_20** (violation, conditional) — `Root() Null Check` -- SKIP if no Root() calls -- Query: Is the return value checked for nullptr before use? - -**rule_21** (violation, conditional) — `Root() Release in Deinitialize` -- SKIP if no Root() stored -- Query: Is the pointer Released and nulled in Deinitialize()? - -**rule_22** (violation, conditional) — `Observer Cleanup in Deinitialize` -- SKIP if no observer registration -- Query: Is every registered observer unregistered in Deinitialize()? - -**rule_23** (violation, conditional) — `SubSystems() Release in Deinitialize` -- SKIP if no SubSystems() call -- Query: Is SubSystems() released in Deinitialize()? - -**rule_24** (violation) — `Constructor Must Be Empty` -- Query: Is the constructor body empty (no logic)? - -**rule_25** (violation, conditional) — `service->Register/Unregister Pairing` -- SKIP if no service->Register() calls -- Query: Is every Register() matched by Unregister() in Deinitialize()? - -**rule_26** (violation) — `Initialize Returns Error String on Failure` -- Query: Does Initialize() return non-empty error string on every failure path? - -**rule_27** (violation) — `No Manual Deinitialize() in Initialize` -- Query: Is Deinitialize() never called from within Initialize()? - -**rule_28** (violation) — `Destructor Must Be Empty` -- Query: Is the destructor body completely empty? - ---- - -### Phase 5 — Implementation (rule_29 to rule_32) - -**rule_29** (violation, conditional) — `JSON-RPC Register/Unregister Pairing` -- SKIP if no JSON-RPC handlers -- Query: Is every handler registered in Initialize() unregistered in Deinitialize()? - -**rule_30** (violation, conditional) — `SinkType Pattern for Subscribers` -- SKIP if no subscriber classes -- Query: Do notification subscribers follow the SinkType pattern? - -**rule_31** (violation, conditional) — `Unavailable() in SinkType Classes` -- SKIP if no SinkType classes -- Query: Does every SinkType class implement Unavailable()? - -**rule_32** (violation) — `No Hardcoded Paths` -- Query: Are there zero hardcoded filesystem paths? - ---- - -### Phase 5C — Out-of-Process (rule_33 to rule_34) - -**rule_33** (violation, conditional) — `OOP Connection Termination in Deinitialize` -- SKIP if CMakeLists.txt does NOT have PLUGIN__MODE set to "Local" -- Query: Does Deinitialize() terminate the OOP connection? - -**rule_34** (violation, conditional) — `connectionId Checked in IRemoteConnection Callbacks` -- SKIP if no IRemoteConnection::INotification implementation -- Query: Is connectionId checked before acting in every callback? - ---- - -### Phase 6 — Configuration (rule_35 to rule_37) - -**rule_35** (violation, conditional) — `Startmode Declaration` -- SKIP if no .conf.in file -- Query: Does .conf.in declare an explicit startmode? - -**rule_36** (violation, conditional) — `Config Core::JSON::Container` -- SKIP if no Config class -- Query: Does Config inherit Core::JSON::Container? - -**rule_37** (violation) — `No Hardcoded Numeric Tuning Parameters` -- Query: Are there zero hardcoded tuning parameters (timeouts, sizes, counts)? - ---- - -### Phase 7 — CMake (rule_38) - -**rule_38** (violation, conditional) — `CXX_STANDARD Uses Thunder Variable` -- SKIP if CMakeLists.txt does not set CXX_STANDARD -- Query: Does CXX_STANDARD use `${CXX_STD}`? - ---- - -### Phase 8 — COM Interface (rule_39) - -**rule_39** (violation) — `COM Methods Return Core::hresult` -- Query: Do all COM interface methods return Core::hresult? - ---- - -### Holistic Rules (rule_40 to rule_79) — 8 sub-phases - -Rules 40–79 require reading broader code context across multiple methods/files. -Full definitions in `thunder-plugin-rules.yaml`. - -#### Conventions & Encapsulation (4 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_40 | #pragma once in all headers | suggestion | -| rule_41 | Apache 2.0 Copyright Header | suggestion | -| rule_49 | CMake NAMESPACE Variable | suggestion | -| rule_77 | Observer Classes Private and Nested | suggestion | - -#### Lifecycle & State Integrity (6 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_44 | OOP Registration Order | violation | -| rule_45 | Complete State Reset in Deinitialize | violation | -| rule_46 | Reverse-Order Cleanup | suggestion | -| rule_57 | Config Errors Return Non-Empty from Initialize | violation | -| rule_70 | Framework Pointers Not Accessed After Deinitialize | violation | -| rule_79 | All Acquired Pointers Cleared After Deinitialize | violation | - -#### Concurrency & Threading (10 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_47 | Observer Locking | violation | -| rule_50 | Handlers Must Not Block | violation | -| rule_51 | No Activate/Deactivate from Handlers | violation | -| rule_52 | Shared State Protected by CriticalSection | violation | -| rule_53 | No Lock Held During Framework Callbacks | violation | -| rule_54 | Worker Jobs Check Deinitialize Guard | violation | -| rule_65 | JSON-RPC Handlers Are Re-entrant Safe | violation | -| rule_66 | IPlugin::INotification Callbacks Must Not Block | violation | -| rule_67 | Lock Scope Minimized | violation | -| rule_68 | Plugin Threads Joined in Deinitialize | violation | - -#### COM Reference & Memory Safety (3 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_48 | AddRef/Release Balance | violation | -| rule_63 | COM Reference Counting Correctness | violation | -| rule_71 | hresult Return Values Checked | violation | - -#### Resource Management (3 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_55 | File Descriptors / Sockets Wrapped in RAII | violation | -| rule_56 | No Unbounded Memory Growth | violation | -| rule_69 | Memory and Allocation Safety | warning | - -#### JSON-RPC Compliance (7 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_58 | interface->Register/Unregister Pairing | violation | -| rule_59 | Handler Registration Order | violation | -| rule_60 | Use Core::ERROR_* for Handler Failure Codes | violation | -| rule_61 | Input Validation in JSON-RPC Handlers | violation | -| rule_62 | Event Constants and Typed JSON Payloads | warning | -| rule_74 | JSON-RPC Input Validation for Bounds and Types | violation | -| rule_78 | No Deprecated JSON-RPC APIs | violation | - -#### Inter-Plugin & OOP Design (2 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_64 | No Hard Inter-Plugin Dependencies | warning | -| rule_76 | OOP Error Propagation and Method Naming | warning | - -#### Code Quality & Security (5 rules) -| ID | Name | Severity | -|----|------|----------| -| rule_42 | STL Types | warning | -| rule_43 | ASSERT vs Error Handling | warning | -| rule_72 | ASSERT Only for Programmer Invariants | warning | -| rule_73 | Security: Logging, Shell, Path, and Error Exposure | violation | -| rule_75 | Config Completeness and Resource Cleanup | warning | - ---- +```yaml +rule_id: rule_06 +status: WARNING +severity: warning +question: "Is nullptr used exclusively — no NULL as a pointer value in code?" +answer: "No" +extracted_code: | + [Dictionary.cpp:108] IPlugin* plugin = NULL; +violation_line: "[Dictionary.cpp:108]" +citation: "[Dictionary.cpp:108] NULL used as null pointer — NULL vs nullptr" +fix: "IPlugin* plugin = nullptr;" +reasoning: # omit if no severity downgrade +``` ## Output Format @@ -406,28 +171,33 @@ Group all issues (from any rule) by source file. For files with failures: ### {ActualFileName} — N issue(s) ``` -For each failing rule (same format for all 79 rules): +Under each file heading, list every failing rule as a YAML block (same format for all 70 rules): -```yaml -rule_id: rule_06 -status: WARNING -severity: warning -question: "Is nullptr used exclusively — no NULL as a pointer value in code?" +``yaml +rule_id: rule_XX +status: ❌ VIOLATION # or ⚠️ WARNING or 💡 SUGGESTION +severity: violation # original YAML severity (never changes) +question: "The rule's yes/no question" answer: "No" extracted_code: | - [Dictionary.cpp:108] IPlugin* plugin = NULL; -violation_line: "[Dictionary.cpp:108]" -citation: "[Dictionary.cpp:108] NULL used as null pointer — use nullptr" -fix: "IPlugin* plugin = nullptr;" -reasoning: # omit if no severity downgrade -``` + [ActualFile.cpp:LINE] relevant code snippet +violation_line: "[ActualFile.cpp:LINE]" +citation: "[ActualFile.cpp:LINE] Short issue description" +fix: "corrected code or one-line instruction" +reasoning: # ONLY if severity was downgraded; omit otherwise +`` + + + +Status symbols (prefix the `status:` field value with these): +- ❌ `VIOLATION` — blocking issue, must fix +- ⚠️ `WARNING` — should fix +- 💡 `SUGGESTION` — optional improvement -Severity heading prefixes: -- ❌ = VIOLATION -- ⚠️ = WARNING -- 💡 = SUGGESTION +**Format:** `status: ❌ VIOLATION` or `status: ⚠️ WARNING` or `status: 💡 SUGGESTION` -### Summary Table (single unified table for all 79 rules) +When severity is downgraded (e.g. violation → suggestion), the symbol matches the **effective** (downgraded) status, not the original YAML severity. +### Summary Table (single unified table for all 70 rules) | Phase | PASS | FAIL | SKIP | |-------|------|------|------| @@ -441,7 +211,7 @@ Severity heading prefixes: | Phase 7 — CMake | N | N | N | | Phase 8 — COM Interfaces | N | N | N | | Holistic Rules (8 sub-phases) | N | N | N | -| **Total (79 rules)** | **N** | **N** | **N** | +| **Total (70 rules)** | **N** | **N** | **N** | Followed by a numbered **Next Steps** list citing `[File:line]` for each action item. @@ -459,7 +229,7 @@ Followed by a numbered **Next Steps** list citing `[File:line]` for each action 1. Load `thunder-plugin-rules.yaml` at the start of every review run — rules may have been updated 2. Never embed rule data in this prompt — always load from YAML at runtime 3. If a plugin is not found in `ThunderNanoServices/`, search workspace before asking -4. Total: 79 rules (rule_01 to rule_79), all sequential, all producing unified output +4. Total: 70 rules (rule_01 to rule_70), all sequential, all producing unified output 5. Rule IDs in reports use the format `rule_XX` — no phase prefixes ## Command Examples @@ -478,9 +248,9 @@ Followed by a numbered **Next Steps** list citing `[File:line]` for each action After reporting all results in chat, generate a CSV file for tracking and Excel analysis. -**File path:** `ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` +**File path:** `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` -- Create `ThunderTools/PluginQA/Reports/plugin/` if it does not exist +- Create `ThunderTools/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 **CSV columns (exact order, 14 columns):** @@ -526,9 +296,9 @@ No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_ **Post-generation message:** ``` 📊 Report saved: - ThunderTools/PluginQA/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv + ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions To open in Excel (Windows): - Start-Process "ThunderTools\PluginQA\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" + Start-Process "ThunderTools\PluginQualityAdvisor\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" ``` \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md index dc94e901..caa3c3ea 100644 --- a/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md @@ -1,16 +1,11 @@ ---- -title: "Thunder Plugin Rule Manager" -description: "Add, update, or remove plugin rules (phase checkpoint or holistic) via guided questionnaire — keeps YAML, prompt, README, and spec in sync" ---- - ## Purpose -This prompt manages rules in `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: +This prompt manages rules in `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: -1. `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` — rule data -2. `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` — checkpoint descriptions -3. `ThunderTools/PluginQA/README.md` — documentation -4. `ThunderTools/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` — spec requirements +1. `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` — rule data +2. `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` — checkpoint descriptions +3. `ThunderTools/PluginQualityAdvisor/README.md` — documentation +4. `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` — spec requirements --- @@ -271,5 +266,4 @@ After completing all changes, display: 5. Requires reading multiple code paths to decide? 6. Cannot be reduced to a single bounded yes/no query? -**Ambiguous cases:** If the classification is not clear, ask the user before proceeding. Display the classification criteria above and explain which criteria pull each way. - +**Ambiguous cases:** If the classification is not clear, ask the user before proceeding. Display the classification criteria above and explain which criteria pull each way. \ No newline at end of file diff --git a/PluginQualityAdvisor/README.md b/PluginQualityAdvisor/README.md index 224dc8af..694b6e68 100644 --- a/PluginQualityAdvisor/README.md +++ b/PluginQualityAdvisor/README.md @@ -15,12 +15,12 @@ AI-driven validation tools for Thunder plugin and COM interface development, pow ```json { "chat.promptFilesLocations": { - "ThunderTools/PluginQA/Prompts": true + "ThunderTools/PluginQualityAdvisor/Prompts": true } } ``` -The path should point to the `Prompts` folder inside PluginQA. Use the path relative to your workspace root, or an absolute path if ThunderTools is outside the workspace. +The path should point to the `Prompts` folder inside PluginQualityAdvisor. Use the path relative to your workspace root, or an absolute path if ThunderTools is outside the workspace. 4. **Reload VS Code** — press `Ctrl+Shift+P` -> `Developer: Reload Window` @@ -106,7 +106,7 @@ Add, update, or remove plugin validation rules. **Two ways to provide input:** - **Interactive** — answers questions via VS Code dropdowns -- **Document template** — paste a filled template from `plugin-rule-template-guide.md` +- **Document template** — paste a filled template from `Update-Template-Guide/plugin-rule-template-guide.md` Updates `thunder-plugin-rules.yaml` and `thunder-plugin-review.prompt.md` atomically. @@ -123,7 +123,7 @@ Add, update, or remove interface validation rules. **Two ways to provide input:** - **Interactive** — answers questions via VS Code dropdowns -- **Document template** — paste a filled template from `interface-rule-template-guide.md` +- **Document template** — paste a filled template from `Update-Template-Guide/interface-rule-template-guide.md` Updates `thunder-interface-rules.yaml` and `thunder-interface-review.prompt.md` atomically. @@ -132,12 +132,12 @@ Updates `thunder-interface-rules.yaml` and `thunder-interface-review.prompt.md` ## Project Structure ``` -ThunderTools/PluginQA/ +ThunderTools/PluginQualityAdvisor/ +-- README.md +-- setup-prompts.py -+-- plugin-rule-template-guide.md -+-- interface-rule-template-guide.md -+-- wiki.md ++-- Update-Template-Guide/ +| +-- plugin-rule-template-guide.md +| +-- interface-rule-template-guide.md +-- Prompts/ | +-- thunder-plugin-review.prompt.md | +-- thunder-interface-review.prompt.md diff --git a/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md index 088091b7..8deb4105 100644 --- a/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md +++ b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md @@ -4,9 +4,9 @@ Use this guide to fill in a rule template and pass it to `/thunder-interface-rul A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, validates it, and updates all three files in one shot: -- `ThunderTools/PluginQA/rules/thunder-interface-rules.yaml` -- `ThunderTools/PluginQA/Prompts/thunder-interface-review.prompt.md` -- `ThunderTools/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` +- `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` +- `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` +- `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` --- @@ -96,8 +96,8 @@ ThunderInterfaces/interfaces/IExample.h — description Format: `core_X_1` for core rules or `advisory_mX_1` for advisory rules. -Current core rules: `core_1_1` through `core_17_1` (15 rules) -Current advisory rules: `advisory_m1_1`, `advisory_m2_1`, `advisory_m3_1`, `advisory_m5_1` (4 rules) +Current core rules: `core_1_1` through `core_18_1` (16 rules) +Current advisory rules: `advisory_m1_1`, `advisory_m2_1`, `advisory_m3_1` (3 rules) - **Add** — leave blank to auto-assign the next available ID, or specify your own - **Update/Remove** — mandatory; this is how the manager finds the rule @@ -130,7 +130,7 @@ Short descriptive title in Title Case. 2-5 words. | `warning` | Should fix — best practice with real risk | | `suggestion` | Optional — style or convention preference | -Note: All 15 core rules currently use `violation`. Advisory rules use mixed severities. +Note: Core rules use mixed severities (suggestion, warning, or violation). Advisory rules also use mixed severities. --- @@ -361,4 +361,4 @@ Severity: violation | warning | suggestion ### Violation Pattern — single-line summary (required for Add) ### Fix — WRONG / Correct code (required for Add) ### Example Citation — real Thunder interface reference (required for Add) -``` +``` \ No newline at end of file diff --git a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md index 65c177a1..ffd54f9d 100644 --- a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md +++ b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md @@ -4,10 +4,10 @@ Use this guide to fill in a rule template and pass it to `/thunder-plugin-rule-m A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, validates it, and updates all four files in one shot: -- `ThunderTools/PluginQA/rules/thunder-plugin-rules.yaml` -- `ThunderTools/PluginQA/Prompts/thunder-plugin-review.prompt.md` -- `ThunderTools/PluginQA/README.md` -- `ThunderTools/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` +- `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` +- `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` +- `ThunderTools/PluginQualityAdvisor/README.md` +- `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` --- @@ -40,7 +40,7 @@ If your rule could go either way, the manager will offer you both options and as ## Template A — Phase Checkpoint Rule -Phase checkpoint rules are organized under phase sections (rule_01 to rule_39). +Phase checkpoint rules are organized under phase sections (rule_01 to rule_38). ### Blank template @@ -85,7 +85,7 @@ Skip when: ## Template B — Holistic Rule -Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_40 to rule_79). +Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_39 to rule_70). ### Blank template @@ -128,14 +128,14 @@ Severity: violation ### `Rule_ID` *(required for Update/Remove; optional for Add)* -Sequential identifier in the format `rule_XX` (e.g. `rule_17`, `rule_45`). +Sequential identifier in the format `rule_XX` (e.g. `rule_17`, `rule_41`). - **Add** — leave blank to auto-assign the next available number, or specify your own - **Update/Remove** — mandatory; this is how the manager finds the rule Current rule ranges: -- rule_01 to rule_39: Phase checkpoints -- rule_40 to rule_79: Holistic rules +- rule_01 to rule_38: Phase checkpoints +- rule_39 to rule_70: Holistic rules --- @@ -148,11 +148,11 @@ Phase 1 — Module Structure (currently: rule_01 to rule_03) Phase 2 — Code Style (currently: rule_04 to rule_13) Phase 3 — Class Registration (currently: rule_14 to rule_16) Phase 4 — Lifecycle (currently: rule_17 to rule_28) -Phase 5 — Implementation (currently: rule_29 to rule_32) -Phase 5C — Out-of-Process (currently: rule_33 to rule_34) -Phase 6 — Configuration (currently: rule_35 to rule_37) -Phase 7 — CMake (currently: rule_38) -Phase 8 — COM Interface Rules (currently: rule_39) +Phase 5 — Implementation (currently: rule_29 to rule_31) +Phase 5C — Out-of-Process (currently: rule_32 to rule_33) +Phase 6 — Configuration (currently: rule_34 to rule_36) +Phase 7 — CMake (currently: rule_37) +Phase 8 — COM Interface Rules (currently: rule_38) ``` --- @@ -368,7 +368,7 @@ The manager confirms the rule name and phase before deleting. PHASE CHECKPOINT (Template A) HOLISTIC RULE (Template B) Action: Add | Update | Remove Action: Add | Update | Remove -Rule_ID: e.g. rule_17 Rule_ID: e.g. rule_52 +Rule_ID: e.g. rule_17 Rule_ID: e.g. rule_48 Phase: Phase 4 — Lifecycle Category: concurrency Name: Title Case, 2-5 words Name: Title Case, 2-5 words Severity: violation|warning|suggestion Severity: violation|warning|suggestion diff --git a/PluginQualityAdvisor/rules/thunder-interface-rules.yaml b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml index 8c8e3b28..18fdb6f1 100644 --- a/PluginQualityAdvisor/rules/thunder-interface-rules.yaml +++ b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml @@ -6,53 +6,50 @@ description: | detection method. # =========================================================================================== -# CORE RULES (15) — severity: violation for all +# CORE RULES (16) # =========================================================================================== core_rules: - id: "core_1_1" - name: "File and Namespace Structure" - severity: "violation" + name: "File Structure" + severity: "suggestion" description: | - Thunder interface headers must follow the standard file and namespace structure: - - File must be in ThunderInterfaces/interfaces/ (or qa_interfaces/ for QA) - - Interface must be declared inside the WPEFramework::Exchange namespace - - File name must match the interface name: IFoo.h for struct EXTERNAL IFoo + Thunder interface headers must follow a standard structure: + - File name should match the interface name (IFoo.h for struct EXTERNAL IFoo), + though exceptions may exist - No implementation code in interface headers — pure declarations only extraction_logic: | 1. Read the full interface header file - 2. Identify the namespace block(s) and their names - 3. Note the file name and the primary interface struct name - 4. Check for any non-declaration code (function implementations, static variables, etc.) + 2. Note the file name and the primary interface struct name + 3. Check for any non-declaration code (function implementations, static variables, etc.) verification_logic: | - 1. Verify the file is inside the WPEFramework::Exchange namespace - 2. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) - 3. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations - 4. If any of these conditions fail → VIOLATION - violation_pattern: "Interface not in WPEFramework::Exchange namespace, file name mismatch, or implementation code present" + 1. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) + - Exception: multi-interface files or platform-specific groupings may have different naming + 2. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations + 3. If issues are found → SUGGESTION + violation_pattern: "File name does not match interface name, or implementation code present in interface header" fix_template: | - // WRONG: - namespace WPEFramework { - // Missing Exchange namespace - struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; - } - - // Correct: - namespace WPEFramework { - namespace Exchange { - struct EXTERNAL IDictionary : virtual public Core::IUnknown { ... }; + // WRONG: Implementation code in interface header + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value) { + return Core::ERROR_NONE; // ← implementation code not allowed } - } + }; + + // Correct: pure declarations only + struct EXTERNAL IDictionary : virtual public Core::/t { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + }; citation: | ThunderInterfaces/interfaces/IDictionary.h — Exchange namespace and file naming - id: "core_2_1" name: "Interface Declaration Shape" - severity: "violation" + severity: "warning" description: | Thunder COM interfaces must follow the correct declaration shape: - Must be a struct (not class) with the EXTERNAL macro - - Must inherit virtually from Core::IUnknown (or from another Thunder interface) + - Must inherit virtually from Core::IUnknown - Must declare a nested enum with the ID value (RPC::ID_*) - All methods must be pure virtual extraction_logic: | @@ -62,7 +59,7 @@ core_rules: 4. Examine all method declarations for pure virtual (= 0) verification_logic: | 1. Verify the declaration uses 'struct EXTERNAL IName' - 2. Verify inheritance is 'virtual public Core::IUnknown' or a Thunder interface + 2. Verify inheritance is 'virtual public Core::IUnknown' 3. Verify a nested enum contains ID = RPC::ID_* 4. Verify all methods are pure virtual (= 0) 5. If any fails → VIOLATION @@ -90,16 +87,29 @@ core_rules: The ID enum value in the struct must reference the registered RPC::ID_* constant. Sub-interfaces (INotification, ICallback nested in a parent) must also have their own unique IDs. + + ID ranges (defined in Thunder/Source/com/Ids.h): + - Thunder core interfaces: ID_OFFSET_INTERNAL + 0x0001 .. 0x007F + - Extension interfaces: ID_EXTENSIONS_INTERFACE_OFFSET (ID_OFFSET_INTERNAL + 0x0080) + - External (ThunderInterfaces) interfaces: ID_EXTERNAL_INTERFACE_OFFSET (ID_OFFSET_INTERNAL + 0x1000) + - QA interfaces: ID_EXTERNAL_QA_INTERFACE_OFFSET (0xA000) + - Example interfaces: ID_EXTERNAL_EXAMPLE_INTERFACE_OFFSET (0xB000) + - CC interfaces (entservices-apis): ID_EXTERNAL_CC_INTERFACE_OFFSET (0xCC00 .. 0xDFFF) + + Interfaces in entservices-apis MUST use IDs within the CC range + (RPC::IDS::ID_EXTERNAL_CC_INTERFACE_OFFSET + offset, range 0xCC00–0xDFFF). extraction_logic: | 1. Read the interface struct declaration and note its ID value 2. Check whether the ID value uses an RPC::ID_* constant 3. Check ids.h (or the ID registration file) for the corresponding entry + 4. Determine whether the interface belongs to entservices-apis (CC range) or ThunderInterfaces (external range) verification_logic: | 1. Verify the struct enum { ID = RPC::ID_* } uses a named constant, not a raw number 2. Verify the ID is registered in ThunderInterfaces ids.h (or equivalent) 3. Verify the ID is unique — no other interface uses the same value 4. Nested INotification and ICallback interfaces must also have their own IDs - 5. If any condition fails → VIOLATION + 5. For entservices-apis interfaces: verify the ID falls within the CC range (0xCC00–0xDFFF) + 6. If any condition fails → VIOLATION violation_pattern: "Interface ID missing from IDs registration, uses raw number, or is not unique" fix_template: | // WRONG: @@ -114,7 +124,7 @@ core_rules: - id: "core_4_1" name: "Pure Virtual Methods Only" - severity: "violation" + severity: "warning" description: | Thunder COM interface methods must be pure virtual (= 0). No default implementations, no inline code, no static methods, no non-virtual methods. @@ -152,15 +162,23 @@ core_rules: other return types are not allowed for JSON-RPC-generating methods. Methods in pure COM interfaces (no @json) should also use Core::hresult for error reporting where applicable. + + Exceptions: + - Notification/event methods (methods in interfaces tagged with @event, such as + INotification or ICallback) MUST return void, not Core::hresult. + - Legacy/old interfaces (pre-Thunder 5.0) may not adhere to this rule. extraction_logic: | 1. Read all interface method declarations 2. Note which methods are under a @json-tagged interface or have @json annotations - 3. Check the return type of each method + 3. Identify notification/event interfaces (tagged with @event) + 4. Check the return type of each method verification_logic: | 1. For interfaces tagged with @json (or in Thunder 5.0+ contexts), all methods must return Core::hresult - 2. Void return types are not allowed for JSON-RPC methods - 3. Custom return types (not Core::hresult) for non-status purposes are not allowed — use @out parameters instead - 4. If any JSON-RPC method lacks Core::hresult return type → VIOLATION + 2. Exception: methods in @event-tagged notification interfaces MUST return void + 3. Void return types are not allowed for non-notification JSON-RPC methods + 4. Custom return types (not Core::hresult) for non-status purposes are not allowed — use @out parameters instead + 5. Legacy interfaces (pre-Thunder 5.0) may not comply — flag as warning, not violation + 6. If any non-notification JSON-RPC method lacks Core::hresult return type → VIOLATION violation_pattern: "Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+" fix_template: | // WRONG: @@ -182,6 +200,8 @@ core_rules: - Output parameters should be non-const references (string& value) - Methods that do not modify the object should be const (though pure virtual const is rare) - @out parameters must be non-const references to allow the implementation to write + - Notification methods (methods in INotification/ICallback interfaces) should NEVER be const — + the implementation needs to modify state when handling notifications extraction_logic: | 1. Read all method parameter declarations 2. Examine const qualifiers on each parameter @@ -220,7 +240,7 @@ core_rules: verification_logic: | 1. std::string in interface parameters → VIOLATION (use string) 2. HRESULT or raw int for error codes → VIOLATION (use Core::hresult) - 3. Non-width-specific integer types (int, long) for interface params → check core_16_1 + 3. Non-width-specific integer types (int, long) for interface params → check core_15_1 4. BOOL → VIOLATION (use bool) 5. If std::string found → VIOLATION violation_pattern: "std::string used in interface — must use Thunder string type alias" @@ -260,7 +280,7 @@ core_rules: // Correct — INotification (1:many): virtual Core::hresult Register(INotification* notification) = 0; - virtual Core::hresult Unregister(const INotification* notification) = 0; + virtual Core::hresult Unregister(INotification* notification) = 0; // Correct — ICallback (1:1, nullptr clears): virtual Core::hresult Callback(ICallback* callback) = 0; @@ -268,28 +288,27 @@ core_rules: ThunderInterfaces/interfaces/IDictionary.h — Register/Unregister pattern - id: "core_11_1" - name: "Nested Event Interfaces" + name: "Event Interfaces" severity: "violation" description: | - Event/notification interfaces nested inside a parent COM interface must: + Event/notification interfaces must: - Have the @event tag (// @event) above the struct declaration - Use EXTERNAL in the struct declaration - Have their own unique ID in the RPC ID list - Inherit from Core::IUnknown (not from the parent interface) Missing @event prevents the code generator from emitting event dispatch code. extraction_logic: | - 1. Read the interface for any nested struct declarations - 2. For each nested struct that represents a notification/event (INotification, ICallback, etc.) - 3. Check for the @event comment tag above the declaration - 4. Check for EXTERNAL in the declaration - 5. Check for a nested enum { ID = RPC::ID_* } + 1. Read the interface for any struct declarations that represent a notification/event (INotification, ICallback, etc.) + 2. Check for the @event comment tag above the declaration + 3. Check for EXTERNAL in the declaration + 4. Check for an enum { ID = RPC::ID_* } verification_logic: | - 1. Every nested event/notification interface must have // @event immediately above the struct + 1. Every event/notification interface must have // @event immediately above the struct 2. Must use EXTERNAL in the declaration 3. Must have its own ID in the RPC ID list 4. Must inherit from Core::IUnknown 5. If any condition fails → VIOLATION - violation_pattern: "@event tag missing on nested notification interface, or missing EXTERNAL/ID" + violation_pattern: "@event tag missing on notification interface, or missing EXTERNAL/ID" fix_template: | // WRONG: struct INotification : virtual public Core::IUnknown { @@ -306,7 +325,7 @@ core_rules: - id: "core_12_1" name: "@json Tag (CRITICAL)" - severity: "violation" + severity: "warning" description: | Without the @json tag, ZERO JSON-RPC code is generated for the interface. This is the most common critical omission. The tag must appear immediately @@ -314,6 +333,13 @@ core_rules: No blank lines are allowed between the @json tag and the struct declaration. If an interface intentionally does not need JSON-RPC (pure COM only), the absence of @json is acceptable — but this must be an intentional design choice. + + Hints that an interface was meant to be a JSON-RPC interface: + - Presence of tags like @alias, @text, @opaque, @bitmask, @index in comments + - Methods using @in/@out parameter annotations + - Interface resembles a service API (properties, events, methods) + If such hints are present but @json is missing, flag as warning and ask + whether the interface was intended to generate JSON-RPC code. extraction_logic: | 1. Read the lines immediately above the 'struct EXTERNAL I...' declaration 2. Look for a // @json comment with a version number @@ -336,60 +362,27 @@ core_rules: ThunderInterfaces/interfaces/IDictionary.h:LINE — @json 1.0.0 above struct - id: "core_13_1" - name: "Binary Compatibility" - severity: "violation" - description: | - Thunder COM interfaces are binary interfaces — once released, they cannot be - changed in backward-incompatible ways without breaking all compiled clients: - - Methods must not be reordered (vtable order is fixed) - - Methods must not be removed (creates vtable holes) - - Method signatures must not change (parameter types/count/order) - - New methods must be added at the END of the interface - Binary-incompatible changes require creating a new interface version (IFoo2). - extraction_logic: | - 1. Read the interface method declarations in order - 2. If comparing against a previous version: note any additions, removals, or reorderings - 3. For new interfaces: verify method ordering follows a logical grouping with extensibility in mind - verification_logic: | - 1. When reviewing against a baseline: check for removed methods, reordered methods, or changed signatures - 2. New methods in a released interface must be at the end - 3. If a released interface has been structurally modified → VIOLATION - 4. For new (unreleased) interfaces: recommend logical method ordering for future extensibility - violation_pattern: "Released interface has methods removed, reordered, or signatures changed — breaks binary compatibility" - fix_template: | - // WRONG: (removed a method or changed signature in a released interface) - // v1: virtual Core::hresult Get(const string& key, string& value) = 0; - // v2: virtual Core::hresult Get(const string& key, string& value, bool caseSensitive) = 0; - // ^^^^^^^^^^^^^^^^^ ← changed signature - - // Correct: create a new interface version - struct EXTERNAL IDictionary2 : virtual public IDictionary { - enum { ID = RPC::ID_DICTIONARY2 }; - virtual Core::hresult GetCaseSensitive(const string& key, const bool caseSensitive, string& value /* @out */) = 0; - }; - citation: | - ThunderInterfaces/interfaces/IDictionary.h — binary compatibility - - - id: "core_14_1" - name: "No AddRef/Release Redeclaration" + name: "No IUnknown/IReferenceCounted Methods in Interfaces" severity: "violation" description: | - AddRef() and Release() are inherited from Core::IUnknown and must NOT be - redeclared in interface structs. Redeclaring them creates a separate vtable - entry, breaking COM reference counting and causing crashes. + Methods inherited from Core::IUnknown or Core::IReferenceCounted (AddRef, Release, + QueryInterface) must NOT be redeclared in interface structs. These are provided by + the base class and redeclaring them creates separate vtable entries, breaking COM + reference counting and causing crashes. extraction_logic: | 1. Read all method declarations in the interface struct (including nested structs) - 2. Look for any AddRef() or Release() declarations + 2. Look for any AddRef(), Release(), or QueryInterface() declarations verification_logic: | - 1. The interface must not declare AddRef() or Release() + 1. The interface must not declare AddRef(), Release(), or QueryInterface() 2. These methods are inherited from Core::IUnknown via the virtual inheritance chain - 3. If AddRef() or Release() appears as an interface method → VIOLATION - violation_pattern: "AddRef() or Release() redeclared in interface — must be inherited from Core::IUnknown only" + 3. If any IUnknown/IReferenceCounted method appears as an interface method → VIOLATION + violation_pattern: "IUnknown/IReferenceCounted method (AddRef, Release, QueryInterface) declared in interface — must be inherited only" fix_template: | // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { - virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE - virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual uint32_t AddRef() const = 0; // ← DO NOT REDECLARE + virtual uint32_t Release() const = 0; // ← DO NOT REDECLARE + virtual void* QueryInterface(uint32_t) = 0; // ← DO NOT REDECLARE virtual Core::hresult Get(const string& key, string& value) = 0; }; @@ -399,9 +392,10 @@ core_rules: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; }; citation: | + ThunderInterfaces/interfaces/IDictionary.h — IUnknown methods inherited only ThunderInterfaces/interfaces/IDictionary.h — AddRef/Release inherited from Core::IUnknown - - id: "core_15_1" + - id: "core_14_1" name: "No std::map in Interfaces" severity: "violation" description: | @@ -427,7 +421,7 @@ core_rules: citation: | ThunderInterfaces/interfaces/IDictionary.h — no std::map in interfaces - - id: "core_16_1" + - id: "core_15_1" name: "Explicit Integer Widths" severity: "violation" description: | @@ -455,7 +449,7 @@ core_rules: citation: | ThunderInterfaces/interfaces/IVolume.h — explicit integer widths - - id: "core_17_1" + - id: "core_16_1" name: "@restrict Mandatory with std::vector" severity: "violation" description: | @@ -463,8 +457,7 @@ core_rules: MUST be annotated with /* @restrict:N */ where N is the maximum allowed element count. Without @restrict, the code generator cannot produce safe bounds-checking code and may generate unbounded deserialization that is exploitable. - Note: This rule specifically applies to std::vector. Non-vector parameters with - optional constraints are covered by advisory_m5_1. + Note: This rule specifically applies to std::vector. extraction_logic: | 1. Read all method parameter declarations 2. Identify any parameters of type std::vector @@ -473,7 +466,7 @@ core_rules: 1. Every std::vector parameter must have /* @restrict:N */ annotation 2. N must be a positive integer representing the maximum element count 3. Missing @restrict on std::vector → VIOLATION - 4. @restrict on non-vector types is advisory (see advisory_m5_1) + 4. @restrict on non-vector types is not enforced by this rule violation_pattern: "std::vector parameter missing @restrict annotation — required for safe bounds checking" fix_template: | // WRONG: @@ -484,14 +477,95 @@ core_rules: citation: | ThunderInterfaces/interfaces/IPackager.h — @restrict on std::vector + - id: "core_17_1" + name: "No Method Overloads in @json Interfaces" + severity: "violation" + description: | + In interfaces tagged with @json, method names must be unique — no C++ overloads + are allowed because JSON-RPC dispatches by method name string only (no signature + overload resolution). Methods that would be overloads in C++ will produce duplicate + JSON-RPC method names, causing code generation failures or undefined dispatch behavior. + Note: @text tags can change the JSON-RPC method name. When checking for duplicates, + use the effective JSON-RPC name (i.e. the @text value if present, otherwise the C++ name). + Two methods with different C++ names but the same @text value also collide. + extraction_logic: | + 1. Read all method declarations in @json-tagged interfaces + 2. For each method, determine the effective JSON-RPC name: + - If @text annotation is present, use that as the name + - Otherwise use the C++ method name + 3. Collect all effective names into a list + verification_logic: | + 1. Check for any duplicate effective JSON-RPC method names + 2. Two methods with the same C++ name (overloads) → VIOLATION + 3. Two methods with different C++ names but the same @text value → VIOLATION + 4. If any duplicates are found → VIOLATION + violation_pattern: "Duplicate JSON-RPC method name in @json interface — overloads not allowed" + fix_template: | + // WRONG — overloaded methods produce duplicate JSON-RPC names: + // @json 1.0.0 + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + virtual Core::hresult Get(const uint32_t index, string& value /* @out */) = 0; // ← overload! + }; + + // Correct — use distinct names: + // @json 1.0.0 + struct EXTERNAL IDictionary : virtual public Core::IUnknown { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; + virtual Core::hresult GetByIndex(const uint32_t index, string& value /* @out */) = 0; + }; + + // Also WRONG — @text collision: + virtual Core::hresult GetValue(const string& key, string& value /* @out */) = 0; // @text get + virtual Core::hresult GetStatus(string& status /* @out */) = 0; // @text get ← collision! + citation: | + ThunderInterfaces/interfaces/ — no overloads in @json interfaces + + - id: "core_18_1" + name: "No Reserved JSON-RPC Method Names" + severity: "violation" + description: | + Interfaces tagged with @json must not declare methods with names that collide + with built-in JSON-RPC framework methods. The following names are reserved by + the Thunder JSON-RPC infrastructure and will conflict: + - Version / Versions (built-in version query) + - exists (built-in method existence check) + These names will cause conflicts with the framework's built-in handlers, + leading to undefined dispatch behavior or shadowing. + Check both the C++ method name and any @text annotation for collisions. + extraction_logic: | + 1. Read all method declarations in @json-tagged interfaces + 2. For each method, determine the effective JSON-RPC name (@text or C++ name) + 3. Compare against the reserved name list: version, versions, exists (case-insensitive) + verification_logic: | + 1. If any method's effective JSON-RPC name matches a reserved name (case-insensitive) → VIOLATION + 2. Reserved names: "version", "versions", "exists" + 3. Check both the bare method name and any @text annotation + 4. If collision found → VIOLATION + violation_pattern: "Interface method uses a reserved JSON-RPC name (version/versions/exists) — conflicts with built-in framework handlers" + fix_template: | + // WRONG — collides with built-in JSON-RPC 'exists' method: + virtual Core::hresult Exists(const string& key, bool& result /* @out */) = 0; + + // Correct — use a non-reserved name: + virtual Core::hresult Contains(const string& key, bool& result /* @out */) = 0; + + // WRONG — collides with built-in 'version': + virtual Core::hresult Version(string& version /* @out */) = 0; + + // Correct: + virtual Core::hresult GetVersion(string& version /* @out */) = 0; + citation: | + Thunder JSON-RPC framework — reserved method names + # =========================================================================================== -# ADVISORY RULES (4) +# ADVISORY RULES (3) # =========================================================================================== advisory_rules: - id: "advisory_m1_1" name: "Single Responsibility Principle" - severity: "violation" + severity: "warning" description: | Each COM interface should have a single, clearly defined responsibility. An interface that mixes unrelated concerns (e.g. audio control + network management) @@ -580,37 +654,3 @@ advisory_rules: // Implementation: return Core::ERROR_NOT_FOUND; instead of throwing citation: | ThunderInterfaces/interfaces/IDictionary.h — no exceptions in COM interfaces - - - id: "advisory_m5_1" - name: "@restrict for Non-vector Params" - severity: "warning" - description: | - Parameters with natural upper bounds (strings with max length, integers with - max value, buffers with max size) may benefit from @restrict annotations to - communicate constraints to callers and enable bounds checking in generated code. - NOTE: This rule applies to non-std::vector parameters only. - std::vector parameters are covered by core_17_1 where @restrict is MANDATORY. - This rule is advisory for non-vector types where bounds are meaningful. - extraction_logic: | - 1. Read all method parameter declarations - 2. Skip std::vector parameters (those are covered by core_17_1) - 3. For remaining parameters, reason about whether they have natural upper bounds - 4. Check for @restrict annotations on constrained parameters - verification_logic: | - 1. Do NOT apply this rule to std::vector parameters — core_17_1 covers those - 2. For string parameters used as identifiers, keys, or names: consider whether a max length makes sense - 3. For integer count/size parameters: consider whether a max value is meaningful - 4. If a non-vector parameter clearly has a natural bound but lacks @restrict → WARNING (advisory) - 5. Apply judgment: not every parameter needs @restrict — only those with meaningful constraints - violation_pattern: "Non-vector parameter with natural upper bound lacks @restrict annotation (advisory)" - fix_template: | - // Without restriction (acceptable for many cases): - virtual Core::hresult SetName(const string& name) = 0; - - // With @restrict for bounded parameters: - virtual Core::hresult SetName(const string& name /* @restrict:64 */) = 0; - - // Note: for std::vector, @restrict is MANDATORY (see core_17_1): - virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; - citation: | - ThunderInterfaces/interfaces/IVolumeControl.h — @restrict on bounded params diff --git a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml index 1f752adc..28db8e84 100644 --- a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml +++ b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml @@ -3,31 +3,31 @@ # =========================================================================================== metadata: - description: "Unified plugin validation rules — 79 rules numbered sequentially (rule_01 to rule_79), organized into 17 phases" + description: "Unified plugin validation rules — 70 rules numbered sequentially (rule_01 to rule_70), organized into 17 phases" approach: "semantic code review" - total_rules: 79 + total_rules: 70 organization: | rule_01-rule_03: Module Structure (3) rule_04-rule_13: Code Style (10) rule_14-rule_16: Class Registration (3) rule_17-rule_28: Lifecycle (12) - rule_29-rule_32: Implementation (4) - rule_33-rule_34: Out-of-Process (2) - rule_35-rule_37: Configuration (3) - rule_38: CMake (1) - rule_39: COM Interface (1) - rule_40,41,49,77: Conventions & Encapsulation (4) - rule_44,45,46,57,70,79: Lifecycle & State Integrity (6) - rule_47,50-54,65-68: Concurrency & Threading (10) - rule_48,63,71: COM Reference & Memory Safety (3) - rule_55,56,69: Resource Management (3) - rule_58-62,74,78: JSON-RPC Compliance (7) - rule_64,76: Inter-Plugin & OOP Design (2) - rule_42,43,72,73,75: Code Quality & Security (5) + rule_29-rule_31: Implementation (3) + rule_32-rule_33: Out-of-Process (2) + rule_34-rule_36: Configuration (3) + rule_37: CMake (1) + rule_38: COM Interface (1) + rule_39,40,44,67,69: Conventions & Encapsulation (5) + rule_41,51,62: Lifecycle & State Integrity (3) + rule_42,45-49,58-61: Concurrency & Threading (10) + rule_43,56,63: COM Reference & Memory Safety (3) + rule_50: Resource Management (1) + rule_52-55,68: JSON-RPC Compliance (5) + rule_57: Inter-Plugin & OOP Design (1) + rule_64-66,70: Code Quality & Security (4) validation_approach: principles: - - "ALL 79 rules follow the same review philosophy: understand the whole plugin first, then check specifics" + - "ALL 70 rules follow the same review philosophy: understand the whole plugin first, then check specifics" - "Step 1 is ALWAYS: read the full plugin code (all files) and build mental model of architecture, ownership, lifecycle, and threading" - "Step 2 is: with that full understanding, examine the specific concern each rule asks about" - "Never check a specific block in isolation — always reason about it in the context of the full plugin" @@ -41,11 +41,9 @@ metadata: - "Step 4 JUDGE: If the answer suggests a violation, ask: 'Is the developer's actual approach correct and safe given the full context I already understand?' If yes → downgrade. If genuinely wrong → cite it" - "Step 5 CITE: On violation, give exact file and line (e.g. [Dictionary.cpp:108])" - "Step 6 FIX: Show the corrected code block, not the whole file" - - "Step 4 CITE: On violation, give exact file and line (e.g. [Dictionary.cpp:108])" - - "Step 5 FIX: Show the corrected code block, not the whole file" # =========================================================================================== -# PHASE CHECKPOINTS (39 rules) +# PHASE CHECKPOINTS (38 rules) # =========================================================================================== # ------------------------------------------------------------------------------------------- @@ -630,29 +628,33 @@ phase_3_checkpoints: phase: "class_registration" extraction: - target: "Plugin::Metadata instantiation in the plugin .cpp file" - method: "Read the plugin .cpp file and look for the Plugin::Metadata static registration" - code_block: "The Plugin::Metadata static instance declaration" + target: "Plugin::Metadata instantiation in the plugin .cpp file and SERVICE_REGISTRATION usage" + method: "Read the plugin .cpp file and any OOP implementation files; check for Plugin::Metadata and SERVICE_REGISTRATION" + code_block: "The Plugin::Metadata static instance declaration and any SERVICE_REGISTRATION calls" bounded_query: - question: "Does the plugin .cpp contain a Plugin::Metadata static registration?" + question: "Does the plugin .cpp contain exactly one Plugin::Metadata static registration, and do all other registration points (including OOP parts) use SERVICE_REGISTRATION instead?" expected_answer: "Yes" verification_logic: - "1. Read the plugin .cpp file (PluginName.cpp)" - - "2. Check for the presence of static Plugin::Metadata registration" - - "3. This registers the plugin with the Thunder framework — it must be present" - - "4. If absent → VIOLATION" + - "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. All other places (e.g. OOP implementation files) must use SERVICE_REGISTRATION macro instead" + - "5. For OOP plugins (PLUGIN__MODE = 'Local'): verify at least one SERVICE_REGISTRATION exists in the OOP part" + - "6. If Plugin::Metadata is absent → VIOLATION" + - "7. If Plugin::Metadata appears more than once → VIOLATION" + - "8. If OOP part lacks SERVICE_REGISTRATION → VIOLATION" conditional: false skip_condition: null - violation_pattern: "Plugin::Metadata registration missing from plugin .cpp" + violation_pattern: "Plugin::Metadata registration missing, duplicated, or OOP part missing SERVICE_REGISTRATION" fix_template: | - // WRONG: (missing metadata registration) + // WRONG: (missing metadata registration or duplicated) - // Correct — add to PluginName.cpp: + // Correct — exactly ONE in PluginName.cpp: static Plugin::Metadata metadata( Plugin::Information::Versions, Plugin::Information::Instances, @@ -662,8 +664,11 @@ phase_3_checkpoints: Plugin::Information::Extends ); + // For OOP implementation files, use SERVICE_REGISTRATION instead: + SERVICE_REGISTRATION(DictionaryImplementation, 1, 0) + citation: - line_format: "[PluginName.cpp:LINE] Plugin::Metadata registration absent" + line_format: "[PluginName.cpp:LINE] Plugin::Metadata registration issue" rule: "thunder-plugin-rules.yaml / rule_15" - rule_id: "rule_16" @@ -677,30 +682,34 @@ phase_3_checkpoints: code_block: "The Register() calls in Initialize() and the class inheritance declarations" bounded_query: - question: "If the plugin registers JSON-RPC handlers (Register() calls in Initialize()), does the class inherit PluginHost::JSONRPC?" + question: "If the plugin registers JSON-RPC handlers (Register() calls in Initialize()), does the class inherit PluginHost::JSONRPC or a class derived from it?" expected_answer: "Yes" verification_logic: - "1. Read Initialize() and check for Register() calls that register JSON-RPC handlers" - "2. If no JSON-RPC Register() calls are found → SKIP this checkpoint" - "3. If Register() calls exist, read the class declaration" - - "4. Verify the class inherits from PluginHost::JSONRPC (directly or via JSONRPC base)" - - "5. If JSON-RPC handlers are registered but the class does not inherit JSONRPC → VIOLATION" + - "4. Verify the class inherits from PluginHost::JSONRPC directly OR from a class derived from PluginHost::JSONRPC" + - "5. If JSON-RPC handlers are registered but the class does not inherit JSONRPC (or a derived class) → VIOLATION" conditional: true skip_condition: "Plugin does not register any JSON-RPC handlers in Initialize()" - violation_pattern: "JSON-RPC Register() calls present in Initialize() but class does not inherit PluginHost::JSONRPC" + violation_pattern: "JSON-RPC Register() calls present in Initialize() but class does not inherit PluginHost::JSONRPC or a derived class" fix_template: | // WRONG: class Dictionary : public PluginHost::IPlugin { // Missing PluginHost::JSONRPC inheritance - // Correct: + // Correct (direct inheritance): class Dictionary : public PluginHost::IPlugin, public PluginHost::JSONRPC { + // Also correct (inheriting from a JSONRPC-derived class): + class Dictionary : public PluginHost::IPlugin, + public MyCustomJSONRPCBase { // where MyCustomJSONRPCBase derives from PluginHost::JSONRPC + citation: line_format: "[PluginName.h:LINE] JSON-RPC used but JSONRPC inheritance missing" rule: "thunder-plugin-rules.yaml / rule_16" @@ -836,7 +845,7 @@ phase_4_checkpoints: rule: "thunder-plugin-rules.yaml / rule_19" - rule_id: "rule_20" - name: "Root() Null Check" + name: "Root() nullptr Check" severity: "violation" phase: "lifecycle" @@ -1016,7 +1025,7 @@ phase_4_checkpoints: - rule_id: "rule_24" name: "Constructor Must Be Empty" - severity: "violation" + severity: "suggestion" phase: "lifecycle" extraction: @@ -1339,51 +1348,6 @@ phase_5_checkpoints: rule: "thunder-plugin-rules.yaml / rule_30" - rule_id: "rule_31" - name: "Unavailable() in SinkType Classes" - severity: "violation" - phase: "implementation" - - extraction: - target: "SinkType notification classes that implement IPlugin::INotification" - method: "Read SinkType notification class declarations and check for the Unavailable() method implementation" - code_block: "The method list in the SinkType class declaration" - - bounded_query: - question: "Does every SinkType class implementing IPlugin::INotification also implement the Unavailable() callback?" - expected_answer: "Yes" - - verification_logic: - - "1. If no SinkType notification classes exist → SKIP" - - "2. Read each class that inherits IPlugin::INotification" - - "3. Check that Unavailable() is implemented (not just Activated() and Deactivated())" - - "4. IPlugin::INotification has 3 pure virtuals: Activated, Deactivated, and Unavailable" - - "5. If Unavailable() is missing → VIOLATION" - - conditional: true - skip_condition: "Plugin has no SinkType classes implementing IPlugin::INotification" - - violation_pattern: "SinkType class missing Unavailable() implementation — IPlugin::INotification requires all 3 callbacks" - - fix_template: | - // WRONG: - class Notification : public PluginHost::IPlugin::INotification { - void Activated(const string& callsign, PluginHost::IShell* service) override { ... } - void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } - // Unavailable() missing - }; - - // Correct: - class Notification : public PluginHost::IPlugin::INotification { - void Activated(const string& callsign, PluginHost::IShell* service) override { ... } - void Deactivated(const string& callsign, PluginHost::IShell* service) override { ... } - void Unavailable(const string& callsign, PluginHost::IShell* service) override { ... } - }; - - citation: - line_format: "[PluginName.h:LINE] SinkType class missing Unavailable() implementation" - rule: "thunder-plugin-rules.yaml / rule_31" - - - rule_id: "rule_32" name: "No Hardcoded Paths" severity: "violation" phase: "implementation" @@ -1419,14 +1383,14 @@ phase_5_checkpoints: citation: line_format: "[PluginName.cpp:LINE] Hardcoded filesystem path" - rule: "thunder-plugin-rules.yaml / rule_32" + rule: "thunder-plugin-rules.yaml / rule_31" # ------------------------------------------------------------------------------------------- # Phase 5C: Out-of-Process (OOP) # ------------------------------------------------------------------------------------------- phase_5C_checkpoints: - - rule_id: "rule_33" + - rule_id: "rule_32" name: "OOP Connection Termination in Deinitialize" severity: "violation" phase: "oop" @@ -1473,9 +1437,9 @@ phase_5C_checkpoints: citation: line_format: "[PluginName.cpp:LINE] OOP connection not terminated in Deinitialize()" - rule: "thunder-plugin-rules.yaml / rule_33" + rule: "thunder-plugin-rules.yaml / rule_32" - - rule_id: "rule_34" + - rule_id: "rule_33" name: "connectionId Checked in IRemoteConnection Callbacks" severity: "violation" phase: "oop" @@ -1517,14 +1481,14 @@ phase_5C_checkpoints: citation: line_format: "[PluginName.cpp:LINE] connectionId not checked in IRemoteConnection callback" - rule: "thunder-plugin-rules.yaml / rule_34" + rule: "thunder-plugin-rules.yaml / rule_33" # ------------------------------------------------------------------------------------------- # Phase 6: Configuration # ------------------------------------------------------------------------------------------- phase_6_checkpoints: - - rule_id: "rule_35" + - rule_id: "rule_34" name: "Startmode Declaration" severity: "violation" phase: "configuration" @@ -1558,9 +1522,9 @@ phase_6_checkpoints: citation: line_format: "[PluginName.conf.in:LINE] startmode field missing" - rule: "thunder-plugin-rules.yaml / rule_35" + rule: "thunder-plugin-rules.yaml / rule_34" - - rule_id: "rule_36" + - rule_id: "rule_35" name: "Config Core::JSON::Container" severity: "violation" phase: "configuration" @@ -1604,11 +1568,11 @@ phase_6_checkpoints: citation: line_format: "[PluginName.h:LINE] Config class missing Core::JSON::Container inheritance" - rule: "thunder-plugin-rules.yaml / rule_36" + rule: "thunder-plugin-rules.yaml / rule_35" - - rule_id: "rule_37" + - rule_id: "rule_36" name: "No Hardcoded Numeric Tuning Parameters" - severity: "violation" + severity: "suggestion" phase: "configuration" extraction: @@ -1624,7 +1588,8 @@ phase_6_checkpoints: - "1. Read all function bodies and class member initializations" - "2. Reason about numeric literals: are they structural constants (0, 1, -1) or tunable parameters (timeouts like 5000, buffer sizes like 1024, retry counts like 3)?" - "3. Tunable parameters belong in Config — structural constants (comparison values, array indices) are acceptable inline" - - "4. If magic numbers representing tunable values are found → VIOLATION" + - "4. Universally known constants (e.g. MAC address size = 6, IP address lengths, protocol-defined fixed sizes) are acceptable inline — no reason to store these in config" + - "5. If magic numbers representing tunable values are found → SUGGESTION" conditional: false skip_condition: null @@ -1642,14 +1607,14 @@ phase_6_checkpoints: citation: line_format: "[PluginName.cpp:LINE] Hardcoded numeric tuning parameter — move to Config" - rule: "thunder-plugin-rules.yaml / rule_37" + rule: "thunder-plugin-rules.yaml / rule_36" # ------------------------------------------------------------------------------------------- # Phase 7: CMake # ------------------------------------------------------------------------------------------- phase_7_checkpoints: - - rule_id: "rule_38" + - rule_id: "rule_37" name: "CXX_STANDARD Uses Thunder Variable" severity: "violation" phase: "cmake" @@ -1684,14 +1649,14 @@ phase_7_checkpoints: citation: line_format: "[CMakeLists.txt:LINE] CXX_STANDARD hardcoded — use ${CXX_STD}" - rule: "thunder-plugin-rules.yaml / rule_38" + rule: "thunder-plugin-rules.yaml / rule_37" # ------------------------------------------------------------------------------------------- # Phase 8: COM Interface Rules # ------------------------------------------------------------------------------------------- phase_8_checkpoints: - - rule_id: "rule_39" + - rule_id: "rule_38" name: "COM Methods Return Core::hresult" severity: "violation" phase: "interfaces" @@ -1708,9 +1673,10 @@ phase_8_checkpoints: verification_logic: - "1. Read the plugin's interface header file" - "2. Identify all methods declared in COM interfaces (not class methods — interface pure virtuals)" - - "3. Reason about each method's return type: must be Core::hresult for Thunder 5.0+" - - "4. Void return types and other return types are not allowed in COM interfaces" - - "5. If any COM interface method does not return Core::hresult → VIOLATION" + - "3. Determine if the interface is tagged with @json (JSON-RPC generating)" + - "4. For @json-tagged interfaces: Core::hresult is MANDATORY — non-hresult return types → VIOLATION" + - "5. For COM-RPC only interfaces (no @json): Core::hresult is recommended but not mandatory — non-hresult return types → SUGGESTION" + - "6. Exception: notification/event methods (@event) should return void, not Core::hresult" conditional: false skip_condition: null @@ -1728,17 +1694,17 @@ phase_8_checkpoints: citation: line_format: "[PluginName.h:LINE] COM interface method does not return Core::hresult" - rule: "thunder-plugin-rules.yaml / rule_39" + rule: "thunder-plugin-rules.yaml / rule_38" # =========================================================================================== -# HOLISTIC RULES — 8 sub-phases (40 rules: rule_40 to rule_79) +# HOLISTIC RULES — 8 sub-phases (32 rules: rule_39 to rule_70) # Conventions & Encapsulation | Lifecycle & State Integrity | Concurrency & Threading # COM Reference & Memory Safety | Resource Management | JSON-RPC Compliance # Inter-Plugin & OOP Design | Code Quality & Security # =========================================================================================== general_rules: - - rule_id: "rule_40" + - rule_id: "rule_39" name: "#pragma once" severity: "suggestion" category: "conventions" @@ -1746,7 +1712,7 @@ general_rules: 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." - - rule_id: "rule_41" + - rule_id: "rule_40" name: "Apache 2.0 Copyright Header" severity: "suggestion" category: "conventions" @@ -1754,39 +1720,7 @@ general_rules: 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." - - rule_id: "rule_42" - name: "STL Types" - severity: "warning" - category: "code_quality_security" - review_question: "Using semantic reasoning over function signatures, class members, and return types, are Thunder type aliases (string, vector) used instead of their std:: equivalents (std::string, std::vector) wherever Thunder aliases are available?" - 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." - - - rule_id: "rule_43" - name: "ASSERT vs Error Handling" - severity: "warning" - category: "code_quality_security" - review_question: "Using semantic reasoning over the full code context, is ASSERT used only for programmer invariants (things that must always be true) and not for runtime error handling (things that might fail in production)?" - 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." - - - rule_id: "rule_44" - name: "OOP Registration Order" - severity: "violation" - category: "lifecycle_integrity" - review_question: "In out-of-process plugins, using semantic reasoning over Initialize() and Deinitialize(), are service->Register() and service->Unregister() called in the correct order relative to acquiring and releasing the remote implementation?" - 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." - - - rule_id: "rule_45" - name: "Complete State Reset in Deinitialize" - severity: "violation" - category: "lifecycle_integrity" - review_question: "Using semantic reasoning over Deinitialize() and the class member variables, is every member variable that was set during Initialize() or operation properly reset to its default/null state in Deinitialize(), such that a subsequent Initialize() call would start from a clean state?" - 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." - - - rule_id: "rule_46" + - rule_id: "rule_41" name: "Reverse-Order Cleanup" severity: "suggestion" category: "lifecycle_integrity" @@ -1794,7 +1728,7 @@ general_rules: 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." - - rule_id: "rule_47" + - rule_id: "rule_42" name: "Observer Locking" severity: "violation" category: "concurrency" @@ -1802,7 +1736,7 @@ general_rules: 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." - - rule_id: "rule_48" + - rule_id: "rule_43" name: "AddRef/Release Balance" severity: "violation" category: "com_safety" @@ -1810,7 +1744,7 @@ general_rules: 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." - - rule_id: "rule_49" + - rule_id: "rule_44" name: "CMake NAMESPACE Variable" severity: "suggestion" category: "conventions" @@ -1818,7 +1752,7 @@ general_rules: 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." - - rule_id: "rule_50" + - rule_id: "rule_45" name: "Handlers Must Not Block" severity: "violation" category: "concurrency" @@ -1826,7 +1760,7 @@ general_rules: 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." - - rule_id: "rule_51" + - rule_id: "rule_46" name: "No Activate/Deactivate from Handlers" severity: "violation" category: "concurrency" @@ -1834,7 +1768,7 @@ general_rules: 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." - - rule_id: "rule_52" + - rule_id: "rule_47" name: "Shared State Protected by CriticalSection" severity: "violation" category: "concurrency" @@ -1842,7 +1776,7 @@ general_rules: 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." - - rule_id: "rule_53" + - rule_id: "rule_48" name: "No Lock Held During Framework Callbacks" severity: "violation" category: "concurrency" @@ -1850,31 +1784,23 @@ general_rules: 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." - - rule_id: "rule_54" - name: "Worker Jobs Check Deinitialize Guard" - severity: "violation" + - rule_id: "rule_49" + name: "Worker Jobs Safe After Deinitialize" + severity: "warning" category: "concurrency" - review_question: "Using semantic reasoning over all worker job (IJob, WorkerPool) implementations, do the job Dispatch() methods check a deinitialization guard flag at the start to avoid using stale pointers after Deinitialize() has run?" + review_question: "Using semantic reasoning over all worker job (IJob, WorkerPool) implementations, is there a mechanism to ensure Dispatch() methods cannot access stale/null framework pointers after Deinitialize() has run? The specific mechanism is implementation-dependent (flag check, job cancellation, thread join, atomic state, etc.) — what matters is that the outcome is guaranteed." 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." - - rule_id: "rule_55" + - rule_id: "rule_50" name: "File Descriptors / Sockets Wrapped in RAII" severity: "violation" category: "resource_management" - review_question: "Using semantic reasoning over resource management, are all file descriptors, sockets, and OS handles wrapped in RAII types or explicitly closed in Deinitialize() — never leaked?" - 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." - - - rule_id: "rule_56" - name: "No Unbounded Memory Growth" - severity: "violation" - category: "resource_management" - review_question: "Using semantic reasoning over all data structures (maps, vectors, lists, queues) that are populated at runtime, is there a bounded size or eviction policy that prevents unbounded memory growth during sustained plugin operation?" + review_question: "Using semantic reasoning over resource management, are all file descriptors, sockets, and OS handles wrapped in RAII types or explicitly closed in Deinitialize() — never leaked? More broadly, prefer Thunder-provided abstractions (Core::DataElementFile, Core::SocketPort, etc.) over raw system calls (open, socket, fopen) wherever Thunder provides an equivalent." 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." - - rule_id: "rule_57" + - rule_id: "rule_51" name: "Config Errors Return Non-Empty from Initialize" severity: "violation" category: "lifecycle_integrity" @@ -1882,7 +1808,7 @@ general_rules: 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." - - rule_id: "rule_58" + - rule_id: "rule_52" name: "interface->Register/Unregister Pairing" severity: "violation" category: "jsonrpc_compliance" @@ -1890,7 +1816,7 @@ general_rules: 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." - - rule_id: "rule_59" + - rule_id: "rule_53" name: "Handler Registration Order in Initialize/Deinitialize" severity: "violation" category: "jsonrpc_compliance" @@ -1898,7 +1824,7 @@ general_rules: 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." - - rule_id: "rule_60" + - rule_id: "rule_54" name: "Use Core::ERROR_* for Handler Failure Codes" severity: "violation" category: "jsonrpc_compliance" @@ -1906,23 +1832,15 @@ general_rules: 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." - - rule_id: "rule_61" - name: "Input Validation in JSON-RPC Handlers" - severity: "violation" - category: "jsonrpc_compliance" - review_question: "Using semantic reasoning over all JSON-RPC handler implementations, does each handler validate its input parameters (null pointer checks, string length limits, enum range checks) before using them?" - 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." - - - rule_id: "rule_62" + - rule_id: "rule_55" name: "Event Constants and Typed JSON Payloads" severity: "warning" category: "jsonrpc_compliance" - review_question: "Using semantic reasoning over JSON-RPC event dispatching, are event names defined as named constants rather than inline string literals, and are event payloads typed JSON objects rather than untyped free-form strings?" + review_question: "Using semantic reasoning over JSON-RPC event dispatching, are event names defined as named constants rather than inline string literals? Do NOT recommend using JsonData::* classes directly — these are internal generated glue code that can change. Let the generated event methods handle serialization; plugin code should use the generated Notify/Event methods, not raw Notify() with manually constructed JSON." 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." - - rule_id: "rule_63" + - rule_id: "rule_56" name: "COM Reference Counting Correctness" severity: "violation" category: "com_safety" @@ -1930,7 +1848,7 @@ general_rules: 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." - - rule_id: "rule_64" + - rule_id: "rule_57" name: "No Hard Inter-Plugin Dependencies" severity: "warning" category: "inter_plugin_design" @@ -1938,7 +1856,7 @@ general_rules: 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." - - rule_id: "rule_65" + - rule_id: "rule_58" name: "JSON-RPC Handlers Are Re-entrant Safe" severity: "violation" category: "concurrency" @@ -1946,7 +1864,7 @@ general_rules: 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." - - rule_id: "rule_66" + - rule_id: "rule_59" name: "IPlugin::INotification Callbacks Must Not Block" severity: "violation" category: "concurrency" @@ -1954,7 +1872,7 @@ general_rules: 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." - - rule_id: "rule_67" + - rule_id: "rule_60" name: "Lock Scope Minimized" severity: "violation" category: "concurrency" @@ -1962,7 +1880,7 @@ general_rules: 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." - - rule_id: "rule_68" + - rule_id: "rule_61" name: "Plugin Threads Joined in Deinitialize" severity: "violation" category: "concurrency" @@ -1970,23 +1888,15 @@ general_rules: 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." - - rule_id: "rule_69" - name: "Memory and Allocation Safety" - severity: "warning" - category: "resource_management" - review_question: "Using semantic reasoning over all dynamic memory allocation and deallocation, are there no memory leaks (every new has a corresponding delete on all code paths), no use-after-free, and no buffer overflows in the plugin?" - 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." - - - rule_id: "rule_70" - name: "Framework Pointers Not Accessed After Deinitialize" + - rule_id: "rule_62" + name: "Deinitialize Pointer Safety" severity: "violation" category: "lifecycle_integrity" - review_question: "Using semantic reasoning over all worker threads, background jobs, and async callbacks, is there a guarantee that none of them can access framework pointers (IShell*, service pointers, notification interfaces) after Deinitialize() has completed and nulled those pointers?" + review_question: "Using semantic reasoning over Deinitialize() and all background tasks: (1) Is every pointer member explicitly set to nullptr in Deinitialize() after being released, so a double-Deinitialize cannot cause a double-release? (2) Is there a guarantee that no worker thread, background job, or async callback can access framework pointers (IShell*, service pointers, notification interfaces) after Deinitialize() has completed and nulled them?" 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." - - rule_id: "rule_71" + - rule_id: "rule_63" name: "hresult Return Values Checked" severity: "violation" category: "com_safety" @@ -1994,7 +1904,7 @@ general_rules: 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." - - rule_id: "rule_72" + - rule_id: "rule_64" name: "ASSERT Only for Programmer Invariants" severity: "warning" category: "code_quality_security" @@ -2002,7 +1912,7 @@ general_rules: 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." - - rule_id: "rule_73" + - rule_id: "rule_65" name: "Security: Logging, Shell, Path, and Error Exposure" severity: "violation" category: "code_quality_security" @@ -2010,15 +1920,7 @@ general_rules: 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." - - rule_id: "rule_74" - name: "JSON-RPC Input Validation for Bounds and Types" - severity: "violation" - category: "jsonrpc_compliance" - review_question: "Using semantic reasoning over all JSON-RPC handler implementations, does each handler validate numeric inputs for range bounds and type correctness before using them in operations that could overflow, underflow, or cause undefined behavior?" - 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." - - - rule_id: "rule_75" + - rule_id: "rule_66" name: "Config Completeness and Resource Cleanup" severity: "warning" category: "code_quality_security" @@ -2026,15 +1928,7 @@ general_rules: 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." - - rule_id: "rule_76" - name: "OOP Error Propagation and Method Naming" - severity: "warning" - category: "inter_plugin_design" - review_question: "Using semantic reasoning over OOP plugin implementations, do out-of-process method implementations properly propagate errors from the remote process back to the plugin host, and do method names follow Thunder naming conventions (no abbreviations, clear verb+noun)?" - 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." - - - rule_id: "rule_77" + - rule_id: "rule_67" name: "Observer Classes Private and Nested" severity: "suggestion" category: "conventions" @@ -2042,7 +1936,7 @@ general_rules: 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." - - rule_id: "rule_78" + - rule_id: "rule_68" name: "No Deprecated JSON-RPC APIs" severity: "violation" category: "jsonrpc_compliance" @@ -2050,10 +1944,18 @@ general_rules: 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." - - rule_id: "rule_79" - name: "All Acquired Pointers Cleared After Deinitialize" + - rule_id: "rule_69" + name: "INTERFACE_MAP Mandatory" severity: "violation" - category: "lifecycle_integrity" - review_question: "Using semantic reasoning over all member pointer variables and Deinitialize(), is every pointer member that was set to a non-null value during the plugin's lifetime explicitly set to nullptr in Deinitialize() (in addition to being released), such that a double-Deinitialize would not cause a double-release?" + category: "conventions" + review_question: "Using semantic reasoning over the plugin class declaration, does every class that implements COM interfaces have a proper INTERFACE_MAP (BEGIN_INTERFACE_MAP / END_INTERFACE_MAP), and if the class implements a JSON-RPC interface, is IDispatch listed in the interface map?" + 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." + + - rule_id: "rule_70" + name: "No printf — Use Thunder Tracing" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over all source files, is the plugin free of printf, fprintf, std::cout, std::cerr, and similar direct output calls — using Thunder tracing (TRACE, TRACE_L1, etc.) exclusively for logging and debug output?" 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." diff --git a/PluginQualityAdvisor/setup-prompts.py b/PluginQualityAdvisor/setup-prompts.py index e5bb2816..34d76a2a 100644 --- a/PluginQualityAdvisor/setup-prompts.py +++ b/PluginQualityAdvisor/setup-prompts.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ -setup-prompts.py — Registers ThunderTools PluginQA prompts with VS Code. +setup-prompts.py — Registers ThunderTools PluginQualityAdvisor prompts with VS Code. -Adds "ThunderTools/PluginQA/Prompts": true to chat.promptFilesLocations +Adds "ThunderTools/PluginQualityAdvisor/Prompts": true to chat.promptFilesLocations in VS Code settings.json. Works on Windows, macOS, and Linux. No external dependencies — stdlib only. @@ -16,7 +16,7 @@ import sys from datetime import datetime -PROMPTS_KEY = "ThunderTools/PluginQA/Prompts" +PROMPTS_KEY = "ThunderTools/PluginQualityAdvisor/Prompts" def find_settings_path(): @@ -96,13 +96,15 @@ def main(): prompt_locations = settings.get("chat.promptFilesLocations", {}) if isinstance(prompt_locations, dict) and prompt_locations.get(PROMPTS_KEY) is True: print() - print("Already configured — 'ThunderTools/PluginQA/Prompts' is already in chat.promptFilesLocations.") + print("Already configured — 'ThunderTools/PluginQualityAdvisor/Prompts' is already in chat.promptFilesLocations.") print("No changes needed.") print() print("Available slash commands:") print(" /thunder-plugin-review ") print(" /thunder-interface-review ") print(" /thunder-generate-plugin") + print(" /thunder-plugin-rule-manager") + print(" /thunder-interface-rule-manager") sys.exit(0) # Create backup before modifying @@ -136,6 +138,8 @@ def main(): print(" /thunder-plugin-review ") print(" /thunder-interface-review ") print(" /thunder-generate-plugin") + print(" /thunder-plugin-rule-manager") + print(" /thunder-interface-rule-manager") print() From a39685bc12a2821dfa198f923c45f0bc3f86c0a3 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Fri, 10 Jul 2026 15:59:46 +0530 Subject: [PATCH 12/15] Adding 14 holistic plugin rules --- .../changes/thunder-plugin-qa/design.md | 4 +- .../changes/thunder-plugin-qa/proposal.md | 6 +- .../specs/plugin/Thunder-plugin-rules.md | 222 +++++++++++++++++- .../thunder-plugin-qa/specs/plugin/spec.md | 30 +-- .../thunder-plugin-qa/specs/reports/spec.md | 2 +- .../changes/thunder-plugin-qa/tasks.md | 12 +- .../Prompts/thunder-plugin-review.prompt.md | 16 +- .../plugin-rule-template-guide.md | 4 +- .../rules/thunder-plugin-rules.yaml | 138 ++++++++++- 9 files changed, 385 insertions(+), 49 deletions(-) diff --git a/.github/openspec/changes/thunder-plugin-qa/design.md b/.github/openspec/changes/thunder-plugin-qa/design.md index 62289915..0b9c5ae3 100644 --- a/.github/openspec/changes/thunder-plugin-qa/design.md +++ b/.github/openspec/changes/thunder-plugin-qa/design.md @@ -14,7 +14,7 @@ User types /thunder-plugin-review Dictionary thunder-plugin-review.prompt.md │ ▼ -rules/thunder-plugin-rules.yaml ← 70 unified rules (rule_01 to rule_70) +rules/thunder-plugin-rules.yaml ← 84 unified rules (rule_01 to rule_84) │ ▼ Plugin files in ThunderNanoServices/Dictionary/ @@ -61,7 +61,7 @@ description: Semantic code review for Thunder plugins — understand first, then --- ``` -## Unified Review Methodology (ALL 70 rules) +## Unified Review Methodology (ALL 84 rules) Every rule — whether it targets a specific block (rule_01–39) or a broader concern (rule_39–70) — uses the same "understand first, then check" approach: diff --git a/.github/openspec/changes/thunder-plugin-qa/proposal.md b/.github/openspec/changes/thunder-plugin-qa/proposal.md index f992b277..5bb58c94 100644 --- a/.github/openspec/changes/thunder-plugin-qa/proposal.md +++ b/.github/openspec/changes/thunder-plugin-qa/proposal.md @@ -22,7 +22,7 @@ automatically. - Keeps all affected files in sync atomically: YAML + prompt + README + spec **Plugin checkpoint validation (`/thunder-plugin-review`)** -- 70 unified rules numbered sequentially (rule_01 to rule_70) +- 84 unified rules numbered sequentially (rule_01 to rule_84) - Each rule: read code semantically -> decide pass/fail -> cite exact line on failure - Phases: Module Structure, Code Style, Class Registration, Lifecycle, Implementation, COM Interface Rules, Out-of-Process, Configuration, CMake, General @@ -46,7 +46,7 @@ automatically. - Safe: creates timestamped backup, preserves existing settings, idempotent **YAML rule definitions** (loaded by prompts at runtime, not embedded) -- `thunder-plugin-rules.yaml` — 70 unified rules (v3.3.0) +- `thunder-plugin-rules.yaml` — 84 unified rules (v3.3.0) - `thunder-interface-rules.yaml` — 19 interface rules (v3.2.2) **Review reports** (CSV, generated after each review run) @@ -68,7 +68,7 @@ automatically. Use VS Code `.prompt.md` files as slash commands. Each prompt loads its YAML rule definitions at runtime so rules can be updated without touching prompt logic. Plugin validation uses semantic code review: read code as a human -developer and reason about meaning. All 70 rules produce the same unified +developer and reason about meaning. All 84 rules produce the same unified output format — no distinction between "automated" and "manual" in the report. All checkpoint verification uses semantic reasoning — the validator reads code diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md index b82eb758..cd6d82dc 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md @@ -1,4 +1,4 @@ -# Thunder Plugin Rules — v3.3.0 +# Thunder Plugin Rules — v3.4.0 ### Severity Levels @@ -75,6 +75,7 @@ | [rule_59](#rule_59) | IPlugin::INotification Callbacks Must Not Block | violation | Concurrency | | [rule_60](#rule_60) | Lock Scope Minimized | violation | Concurrency | | [rule_61](#rule_61) | Plugin Threads Joined in Deinitialize | violation | Concurrency | +| [rule_62](#rule_62) | Deinitialize Pointer Safety | violation | Lifecycle Integrity | | [rule_63](#rule_63) | hresult Return Values Checked | violation | COM Safety | | [rule_64](#rule_64) | ASSERT Only for Programmer Invariants | warning | Code Quality | | [rule_65](#rule_65) | Security: Logging, Shell, Path, and Error Exposure | violation | Code Quality | @@ -83,6 +84,20 @@ | [rule_68](#rule_68) | No Deprecated JSON-RPC APIs | violation | JSON-RPC Compliance | | [rule_69](#rule_69) | INTERFACE_MAP Mandatory | violation | Conventions | | [rule_70](#rule_70) | No printf — Use Thunder Tracing | warning | Code Quality | +| [rule_71](#rule_71) | No ILocalDispatcher Usage | violation | Inter-Plugin Design | +| [rule_72](#rule_72) | No AddRef/Release/QueryInterface Override | violation | COM Safety | +| [rule_73](#rule_73) | Use PluginSmartInterfaceType for Persistent COMRPC Proxies | warning | Inter-Plugin Design | +| [rule_74](#rule_74) | Prefer WorkerPool for Background Tasks | warning | Concurrency | +| [rule_75](#rule_75) | No COMRPC Call Inside COMRPC Notification | warning | Concurrency | +| [rule_76](#rule_76) | No IController::Persist Calls | warning | Inter-Plugin Design | +| [rule_77](#rule_77) | No Direct curl / libcurl Usage | warning | Code Quality | +| [rule_78](#rule_78) | No Process Spawning from Plugins | violation | Code Quality | +| [rule_79](#rule_79) | No Hardcoded Plugin Callsigns | warning | Inter-Plugin Design | +| [rule_80](#rule_80) | Use ProxyType for COM Interface Parameters | warning | COM Safety | +| [rule_81](#rule_81) | Use Core::GetEnvironment / SetEnvironment | warning | Code Quality | +| [rule_82](#rule_82) | No sleep() in Plugin Code | warning | Concurrency | +| [rule_83](#rule_83) | No Heavy Work in Initialize() | violation | Lifecycle Integrity | +| [rule_84](#rule_84) | No Override of JSONRPC Dispatch Methods | warning | JSON-RPC Compliance | --- @@ -2101,3 +2116,208 @@ Register(_T("method"), &Plugin::Method --- + +--- + +## Extended Rules (rule_71 – rule_84) + +These rules were added from field review and cover advanced patterns: ILocalDispatcher misuse, AddRef/Release override, COMRPC proxy safety, WorkerPool preference, COMRPC re-entrancy, IController misuse, no curl, no process spawning, no hardcoded callsigns, ProxyType, Core env APIs, no sleep, heavy Initialize, and JSONRPC dispatch override. + +--- + +### rule_71 + +**No ILocalDispatcher Usage** | `violation` | Category: Inter-Plugin Design + +**What to check:** The plugin must not use ILocalDispatcher for any communication. ILocalDispatcher bypasses Thunder's security token validation, removes per-plugin permission enforcement, and adds unnecessary JSON serialise/deserialise overhead. + +**Where to look:** All COMRPC and JSON-RPC call paths. + +**Violation pattern:** ILocalDispatcher used for plugin communication. + +**Citation format:** `[PluginName.cpp:LINE] ILocalDispatcher used — use direct COMRPC interface` + +--- + +### rule_72 + +**No AddRef/Release/QueryInterface Override** | `violation` | Category: COM Safety + +**What to check:** The plugin must not provide its own implementations of AddRef(), Release(), or QueryInterface(). These are provided by Thunder's reference counting base classes and overriding them breaks COM lifetime correctness. + +**Where to look:** All class declarations in the plugin. + +**Violation pattern:** Custom AddRef(), Release(), or QueryInterface() implementation found. + +**Citation format:** `[PluginName.h:LINE] Custom AddRef/Release/QueryInterface override — use framework base class` + +--- + +### rule_73 + +**Use PluginSmartInterfaceType for Persistent COMRPC Proxies** | `warning` | Category: Inter-Plugin Design + +**What to check:** Persistent COMRPC proxy storage (member variables holding a COMRPC interface to another plugin) should use PluginSmartInterfaceType rather than a raw pointer. PluginSmartInterfaceType automatically handles dangling when the remote plugin deactivates. + +**Where to look:** All member variables storing COMRPC interfaces to other plugins. + +**Violation pattern:** Raw pointer member stores a persistent COMRPC proxy to another plugin. + +**Citation format:** `[PluginName.h:LINE] Raw COMRPC proxy member — use PluginSmartInterfaceType` + +--- + +### rule_74 + +**Prefer WorkerPool for Background Tasks** | `warning` | Category: Concurrency + +**What to check:** The plugin should prefer Core::WorkerPool jobs over raw thread spawning (std::thread, pthread_create) for one-shot or periodic background tasks. WorkerPool jobs are managed, thread-pooled, and integrate with Thunder's shutdown. + +**Where to look:** All background work and thread usage. + +**Violation pattern:** Raw thread used where WorkerPool job would be more appropriate. + +**Citation format:** `[PluginName.cpp:LINE] Raw thread — prefer Core::WorkerPool job` + +--- + +### rule_75 + +**No COMRPC Call Inside COMRPC Notification** | `warning` | Category: Concurrency + +**What to check:** The plugin must avoid making outbound COMRPC calls from within a COMRPC notification callback. This causes re-entrancy on the same channel, leading to deadlock. Dispatch asynchronously via WorkerPool if needed. + +**Where to look:** All COMRPC notification/callback implementations. + +**Violation pattern:** Outbound COMRPC call made from within inbound COMRPC callback. + +**Citation format:** `[PluginName.cpp:LINE] COMRPC call inside COMRPC notification — dispatch async` + +--- + +### rule_76 + +**No IController::Persist Calls** | `warning` | Category: Inter-Plugin Design + +**What to check:** The plugin must avoid calling IController::Persist(). The Persist/Override feature is disabled on non-debug builds — calling it silently does nothing in production. + +**Where to look:** All IController method calls. + +**Violation pattern:** IController::Persist() called. + +**Citation format:** `[PluginName.cpp:LINE] IController::Persist() — disabled in production builds` + +--- + +### rule_77 + +**No Direct curl / libcurl Usage** | `warning` | Category: Code Quality + +**What to check:** The plugin must not use curl/libcurl directly. Use Thunder's HTTP utilities (Web::Request, Web::Response, Web::WebClient) which provide consistent TLS configuration, proxy handling, and error reporting. + +**Where to look:** All HTTP and network calls. + +**Violation pattern:** Direct curl/libcurl usage found. + +**Citation format:** `[PluginName.cpp:LINE] Direct curl usage — use Thunder Web:: utilities` + +--- + +### rule_78 + +**No Process Spawning from Plugins** | `violation` | Category: Code Quality + +**What to check:** The plugin must not spawn child processes via popen(), system(), fork(), exec() or equivalent. Process spawning blocks the Thunder main thread, leaks file descriptors across OOP boundaries, and is a security risk with unsanitized input. + +**Where to look:** All system interaction code. + +**Violation pattern:** popen(), system(), fork(), or exec() called from plugin code. + +**Citation format:** `[PluginName.cpp:LINE] Process spawning (popen/system/fork) — not allowed in plugins` + +--- + +### rule_79 + +**No Hardcoded Plugin Callsigns** | `warning` | Category: Inter-Plugin Design + +**What to check:** The plugin must not hardcode callsign strings (e.g. "org.rdk.DeviceInfo", "LocationSync"). Hardcoded callsigns create brittle dependencies. Callsigns should come from configuration. + +**Where to look:** All inter-plugin communication code. + +**Violation pattern:** Hardcoded callsign string literal found. + +**Citation format:** `[PluginName.cpp:LINE] Hardcoded callsign — use configuration` + +--- + +### rule_80 + +**Use ProxyType for COM Interface Parameters** | `warning` | Category: COM Safety + +**What to check:** When a method returns or accepts a COM interface pointer as an out-parameter, Core::ProxyType should be used to ensure reference-counted lifetime management. + +**Where to look:** All COM interface method signatures and implementations. + +**Violation pattern:** Bare COM interface pointer returned without ProxyType. + +**Citation format:** `[PluginName.cpp:LINE] Bare COM pointer returned — use Core::ProxyType` + +--- + +### rule_81 + +**Use Core::GetEnvironment / SetEnvironment** | `warning` | Category: Code Quality + +**What to check:** The plugin must use Core::GetEnvironment() and Core::SetEnvironment() instead of POSIX getenv()/setenv(). The POSIX versions are not thread-safe; Thunder's wrappers are thread-safe. + +**Where to look:** All environment variable access. + +**Violation pattern:** getenv() or setenv() used instead of Core::GetEnvironment/SetEnvironment. + +**Citation format:** `[PluginName.cpp:LINE] getenv/setenv — use Core::GetEnvironment/SetEnvironment` + +--- + +### rule_82 + +**No sleep() in Plugin Code** | `warning` | Category: Concurrency + +**What to check:** The plugin must not use sleep(), usleep(), std::this_thread::sleep_for(), or equivalent busy-wait delays. Use Core::WorkerPool timers, condition variables, or framework event notifications instead. + +**Where to look:** All time-delay or polling patterns. + +**Violation pattern:** sleep/usleep/sleep_for found in plugin code. + +**Citation format:** `[PluginName.cpp:LINE] sleep() used — use WorkerPool timer or event` + +--- + +### rule_83 + +**No Heavy Work in Initialize()** | `violation` | Category: Lifecycle Integrity + +**What to check:** Initialize() and Configure() must not perform heavy blocking operations (synchronous network calls, popen, long filesystem scans). Initialize() runs on the Thunder main thread and blocks all other plugin activation. Heavy work must be deferred to a WorkerPool job. + +**Where to look:** Initialize() and Configure() implementations. + +**Violation pattern:** Blocking operation in Initialize() that could take more than a few milliseconds. + +**Citation format:** `[PluginName.cpp:LINE] Heavy blocking work in Initialize() — defer to WorkerPool` + +--- + +### rule_84 + +**No Override of JSONRPC Dispatch Methods** | `warning` | Category: JSON-RPC Compliance + +**What to check:** The plugin must not override internal JSONRPC dispatch or callback methods (Dispatch(), Callback(), or internal handler hooks) from the JSONRPC base class. The correct approach is to register handlers via Register() in Initialize(). + +**Where to look:** Plugin class hierarchy and method overrides. + +**Violation pattern:** Override of internal JSONRPC dispatch method found. + +**Citation format:** `[PluginName.cpp:LINE] JSONRPC Dispatch override — use Register() pattern` + +--- + diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index a685dd4d..1aa120a8 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -100,12 +100,12 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` ### Requirement: thunder-plugin-rules.yaml (v3.3.0) created under PluginQualityAdvisor/rules/ The file `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` MUST exist -with version `3.3.0` and contain all 70 rules numbered sequentially (rule_01 to rule_70). +with version `3.3.0` and contain all 84 rules numbered sequentially (rule_01 to rule_84). #### Scenario: Metadata block - GIVEN the YAML file - THEN it MUST contain a `metadata` block with: - `version: "3.3.0"`, `total_rules: 70`, `total_general_rules: 32`, + `version: "3.3.0"`, `total_rules: 84`, `total_general_rules: 46`, `approach: "semantic code review — understand whole plugin first, then check specifics"`, and a `validation_approach` block listing the 5-step workflow (understand whole plugin → focus on specific concern → reason in context → cite if genuinely wrong → fix) @@ -122,7 +122,7 @@ with version `3.3.0` and contain all 70 rules numbered sequentially (rule_01 to - AND conditional rules MUST include a `conditional: true` flag and a `skip_condition` describing when to skip -#### Scenario: All 32 holistic rules (8 sub-phases) present with required fields +#### Scenario: All 46 holistic rules (8 sub-phases) present with required fields - GIVEN each General rule entry in the YAML (under `general_rules` section) - THEN it MUST contain: `rule_id` (e.g. "rule_39"), `name` (Title Case), `severity`, `category: "" (conventions|lifecycle_integrity|concurrency|com_safety|resource_management|jsonrpc_compliance|inter_plugin_design|code_quality_security)`, @@ -133,11 +133,11 @@ with version `3.3.0` and contain all 70 rules numbered sequentially (rule_01 to - AND Holistic Rules (8 sub-phases) MUST NOT have `extraction`, `bounded_query`, or `verification_logic` fields #### Scenario: Report output is unified — no "automated" vs "manual" split -- GIVEN any review run completing all 70 rules +- GIVEN any review run completing all 84 rules - THEN the report MUST present ONE unified list of findings grouped by file - AND there MUST NOT be separate "Part 1" / "Part 2" or "Automated" / "Manual" sections -- AND the summary table MUST include all 70 rules in a single table with rows for each - phase plus "Holistic Rules (8 sub-phases)" and a "Total (70 rules)" footer row +- AND the summary table MUST include all 84 rules in a single table with rows for each + phase plus "Holistic Rules (8 sub-phases)" and a "Total (84 rules)" footer row #### Scenario: Phase breakdown matches spec - GIVEN the 38 checkpoints distributed across phases @@ -193,7 +193,7 @@ with version `3.2.2` and contain all 19 interface rule definitions (16 core + 3 ### Requirement: Plugin validation command with unified output The system MUST provide a `/thunder-plugin-review ` slash command -in VS Code Copilot Chat that validates a Thunder plugin against all 70 rules +in VS Code Copilot Chat that validates a Thunder plugin against all 84 rules using semantic code review, producing a single unified report. #### Scenario: Plugin found and reviewed @@ -202,7 +202,7 @@ using semantic code review, producing a single unified report. - THEN it locates `ThunderNanoServices/Dictionary/` automatically - AND identifies Dictionary.h, Dictionary.cpp, Module.h, Module.cpp, CMakeLists.txt, Dictionary.conf.in, and any OOP implementation files -- AND executes all 70 rules in order (phase checkpoints first, then General) +- AND executes all 84 rules in order (phase checkpoints first, then General) - AND outputs a single unified report showing ONLY failures with exact line citations #### Scenario: No plugin name provided @@ -277,14 +277,14 @@ after contextual judgment — not always the raw YAML severity. --- -### Requirement: 70 unified rules organised across phase groups and General concerns +### Requirement: 84 unified rules organised across phase groups and General concerns The prompt MUST load rule definitions from `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` at runtime. Each rule definition includes sufficient information for semantic validation. -All 70 rules produce the same output format. +All 84 rules produce the same output format. Phase breakdown: Phase 1: 3, Phase 2: 10, Phase 3: 3, Phase 4: 12, Phase 5: 4, -Phase 5C: 2, Phase 6: 3, Phase 7: 1, Phase 8: 1 = 38 phase rules + 32 holistic rules = 70 total. +Phase 5C: 2, Phase 6: 3, Phase 7: 1, Phase 8: 1 = 38 phase rules + 46 holistic rules = 84 total. --- @@ -480,14 +480,14 @@ NOT by running regular expressions or keyword searches against raw text. --- -### Requirement: 32 holistic rules (8 sub-phases) loaded from YAML and reported in unified output -After the 38 phase checkpoints, the validator MUST also run the 32 holistic rules (8 sub-phases) +### Requirement: 46 holistic rules (8 sub-phases) loaded from YAML and reported in unified output +After the 38 phase checkpoints, the validator MUST also run the 46 holistic rules (8 sub-phases) loaded from the `general_rules` section of `thunder-plugin-rules.yaml`. All rules produce the same output format — there is no separate section for these rules. #### Scenario: Holistic Rules (8 sub-phases) integrated into unified output - GIVEN the phase checkpoint evaluation is complete -- WHEN the validator runs the 32 holistic rules (8 sub-phases) +- WHEN the validator runs the 46 holistic rules (8 sub-phases) - THEN any failures appear in the same file-grouped findings list as phase checkpoint failures - AND the summary table includes a "Holistic Rules (8 sub-phases)" row with PASS/FAIL/SKIP counts - AND there is NO separate "Part 2" or "Manual Review" heading in the output @@ -495,7 +495,7 @@ All rules produce the same output format — there is no separate section for th `extracted_code` (with [File:line] prefix where applicable), `violation_line`, `citation`, `fix`, `reasoning` - AND PASS rules are NOT listed individually — they appear only as counts in the summary table -- Holistic Rules (rule_39–rule_70) cover: +- Holistic Rules (rule_39–rule_84) cover: 1. `#pragma once` in every .h file (suggestion) 2. Apache 2.0 copyright headers in all source files (suggestion) 3. No STL types where Thunder equivalents exist (warning) 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 7f3abc61..40637c95 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -12,7 +12,7 @@ with all fields needed to understand, prioritise, and track fixes. ### REQ-R1 — Plugin review CSV -**Scenario:** `/thunder-plugin-review` completes all 70 rules +**Scenario:** `/thunder-plugin-review` completes all 84 rules - The system MUST generate a CSV file at: `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` - If a file with that name already exists, append `_2`, `_3` etc. (never overwrite) diff --git a/.github/openspec/changes/thunder-plugin-qa/tasks.md b/.github/openspec/changes/thunder-plugin-qa/tasks.md index f62fcc64..2eeacdb6 100644 --- a/.github/openspec/changes/thunder-plugin-qa/tasks.md +++ b/.github/openspec/changes/thunder-plugin-qa/tasks.md @@ -4,7 +4,7 @@ - [x] 1.1 Create `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` (v3.3.0) - metadata block: version, description, approach, - total_rules: 70, total_general_rules: 32, + total_rules: 84, total_general_rules: 46, organization: "Phase1:3, Phase2:10, Phase3:3, Phase4:12, Phase5:4, Phase5C:2, Phase6:3, Phase7:1, Phase8:1" - validation_approach block: principles list + 5-step workflow (including Step 3b JUDGE: contextual judgment — if developer's approach technically @@ -145,9 +145,9 @@ ## Phase 2: Prompt files - [x] 2.1 Create `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` - Frontmatter: title: "Thunder Plugin Rule Review", description (mention 70 unified rules, semantic review) + Frontmatter: title: "Thunder Plugin Rule Review", description (mention 84 unified rules, semantic review) Sections (in order): - - Core Principle: semantic code review for all 70 rules. NEVER pattern-match. + - Core Principle: semantic code review for all 84 rules. NEVER pattern-match. ❌ open-ended vs ✅ bounded examples. All rules produce the same output format. - Report Output Philosophy: CRITICAL note — only report issues; PASS/SKIP as summary counts only; line numbers always required; always use ACTUAL plugin name in citations, NEVER {PluginName} @@ -172,7 +172,7 @@ 5-step workflow (accept name + optional file → locate folder → identify target files → run applicable rules only → report) - Methodology: Step 1 (load YAML from ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml — - contains all 70 rules in phase_X_checkpoints and general_rules sections), + contains all 84 rules in phase_X_checkpoints and general_rules sections), Step 2 (identify plugin files — primary: ThunderNanoServices/{PluginName}/, fallback: workspace search, last resort: ask user; files table: Module.cpp, Module.h, {PluginName}.h, {PluginName}.cpp, CMakeLists.txt, {PluginName}.conf.in (optional), {PluginName}Implementation.h/cpp (optional)), @@ -181,7 +181,7 @@ Class Registration (Phase 3) applies ONLY to main plugin class — internal helpers excluded - Contextual Judgment section: severity downgrade table with concrete example showing reasoning field (required on downgrade, omitted otherwise), no escalation rule - - A shortened inline quick-reference list of all 70 rules is included in the prompt (rule_id + severity + high-level target only). + - A shortened inline quick-reference list of all 84 rules is included in the prompt (rule_id + severity + high-level target only). Full rule definitions (extraction, bounded_query, verification_logic, fix_template) are loaded from the YAML files at runtime (source of truth). Phase counts: Phase 1 Module Structure (3): @@ -231,7 +231,7 @@ rule_37 (violation conditional): "CXX_STANDARD Uses Thunder Variable" Phase 8 COM Interface Rules (1): rule_38 (violation): "COM Methods Return Core::hresult" - - Output Format: UNIFIED FILE-WISE grouping — all issues from all 70 rules grouped by + - Output Format: UNIFIED FILE-WISE grouping — all issues from all 84 rules grouped by source file with a header "### {FileName} — N issue(s)" for each file that has failures; within each file group, each failing rule is a YAML block with fields: rule_id, status (FAIL/PASS/SKIP), severity (violation/warning/suggestion), diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md index 609ae9ac..42c3c3cd 100644 --- a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md @@ -7,7 +7,7 @@ This prompt performs **semantic code review** — reading plugin source code as ❌ Open-ended (never do this): "Check this file for issues" ✅ Bounded (always this): "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" -**Holistic rules (32):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. +**Holistic rules (46):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. --- @@ -64,9 +64,9 @@ Examples: ### Step 1 — Load Rules -Load `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml`. This file contains all 70 rules: +Load `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml`. This file contains all 84 rules: - `phase_1_checkpoints` through `phase_8_checkpoints` — 38 rules with bounded queries -- `general_rules` — 32 holistic rules across 8 sub-phases (rule_39 to rule_70) +- `general_rules` — 46 holistic rules across 8 sub-phases (rule_39 to rule_84) All rules produce the same output format. There is no distinction between "phase checkpoint" and "holistic" in the report. @@ -97,7 +97,7 @@ This affects which rules apply: - **rule_38** (COM Methods Return Core::hresult): Only applies to Thunder 5.0+ plugins. Pre-5.0 plugins correctly use `uint32_t` — do NOT flag as a violation. - **rule_31** (No Hardcoded Paths): Linux kernel virtual filesystems (`/proc/`, `/sys/`, `/dev/`) are fixed OS paths, not deployment-specific — do NOT flag these. -**Review philosophy for ALL 70 rules:** +**Review philosophy for ALL 84 rules:** 1. **UNDERSTAND FIRST** — Read ALL plugin source files. Build a complete mental model of the plugin's architecture: its lifecycle flow, threading model, ownership patterns, data flow, and how Initialize/Deinitialize relate to each other. Do this ONCE before checking any rule. 2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. @@ -171,7 +171,7 @@ Group all issues (from any rule) by source file. For files with failures: ### {ActualFileName} — N issue(s) ``` -Under each file heading, list every failing rule as a YAML block (same format for all 70 rules): +Under each file heading, list every failing rule as a YAML block (same format for all 84 rules): ``yaml rule_id: rule_XX @@ -197,7 +197,7 @@ Status symbols (prefix the `status:` field value with these): **Format:** `status: ❌ VIOLATION` or `status: ⚠️ WARNING` or `status: 💡 SUGGESTION` When severity is downgraded (e.g. violation → suggestion), the symbol matches the **effective** (downgraded) status, not the original YAML severity. -### Summary Table (single unified table for all 70 rules) +### Summary Table (single unified table for all 84 rules) | Phase | PASS | FAIL | SKIP | |-------|------|------|------| @@ -211,7 +211,7 @@ When severity is downgraded (e.g. violation → suggestion), the symbol matches | Phase 7 — CMake | N | N | N | | Phase 8 — COM Interfaces | N | N | N | | Holistic Rules (8 sub-phases) | N | N | N | -| **Total (70 rules)** | **N** | **N** | **N** | +| **Total (84 rules)** | **N** | **N** | **N** | Followed by a numbered **Next Steps** list citing `[File:line]` for each action item. @@ -229,7 +229,7 @@ Followed by a numbered **Next Steps** list citing `[File:line]` for each action 1. Load `thunder-plugin-rules.yaml` at the start of every review run — rules may have been updated 2. Never embed rule data in this prompt — always load from YAML at runtime 3. If a plugin is not found in `ThunderNanoServices/`, search workspace before asking -4. Total: 70 rules (rule_01 to rule_70), all sequential, all producing unified output +4. Total: 84 rules (rule_01 to rule_84), all sequential, all producing unified output 5. Rule IDs in reports use the format `rule_XX` — no phase prefixes ## Command Examples diff --git a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md index ffd54f9d..0e007a8a 100644 --- a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md +++ b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md @@ -85,7 +85,7 @@ Skip when: ## Template B — Holistic Rule -Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_39 to rule_70). +Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_39 to rule_84). ### Blank template @@ -135,7 +135,7 @@ Sequential identifier in the format `rule_XX` (e.g. `rule_17`, `rule_41`). Current rule ranges: - rule_01 to rule_38: Phase checkpoints -- rule_39 to rule_70: Holistic rules +- rule_39 to rule_84: Holistic rules --- diff --git a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml index 28db8e84..4c0e3c5f 100644 --- a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml +++ b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml @@ -3,9 +3,9 @@ # =========================================================================================== metadata: - description: "Unified plugin validation rules — 70 rules numbered sequentially (rule_01 to rule_70), organized into 17 phases" + description: "Unified plugin validation rules — 84 rules numbered sequentially (rule_01 to rule_84), organized into 17 phases plus extended rules" approach: "semantic code review" - total_rules: 70 + total_rules: 84 organization: | rule_01-rule_03: Module Structure (3) rule_04-rule_13: Code Style (10) @@ -17,17 +17,17 @@ metadata: rule_37: CMake (1) rule_38: COM Interface (1) rule_39,40,44,67,69: Conventions & Encapsulation (5) - rule_41,51,62: Lifecycle & State Integrity (3) - rule_42,45-49,58-61: Concurrency & Threading (10) - rule_43,56,63: COM Reference & Memory Safety (3) + rule_41,51,62,83: Lifecycle & State Integrity (4) + rule_42,45-49,58-61,74,75,82: Concurrency & Threading (13) + rule_43,56,63,72,80: COM Reference & Memory Safety (5) rule_50: Resource Management (1) - rule_52-55,68: JSON-RPC Compliance (5) - rule_57: Inter-Plugin & OOP Design (1) - rule_64-66,70: Code Quality & Security (4) + rule_52-55,68,84: JSON-RPC Compliance (6) + rule_57,71,73,76,79: Inter-Plugin & OOP Design (5) + rule_64-66,70,77,78,81: Code Quality & Security (7) validation_approach: principles: - - "ALL 70 rules follow the same review philosophy: understand the whole plugin first, then check specifics" + - "ALL 84 rules follow the same review philosophy: understand the whole plugin first, then check specifics" - "Step 1 is ALWAYS: read the full plugin code (all files) and build mental model of architecture, ownership, lifecycle, and threading" - "Step 2 is: with that full understanding, examine the specific concern each rule asks about" - "Never check a specific block in isolation — always reason about it in the context of the full plugin" @@ -383,7 +383,10 @@ phase_2_checkpoints: - "1. Read all QueryInterfaceByCallsign() call sites" - "2. Reason about the lifetime of the returned pointer: is it stored in a local variable or a class member?" - "3. If stored in a local variable and released before the function returns → acceptable" - - "4. If stored in a class member variable (persists across calls) → VIOLATION" + - "4. If stored in a class member variable as a raw pointer (persists across calls) → VIOLATION" + - "5. If stored via PluginSmartInterfaceType → acceptable (handles plugin deactivation/dangling automatically)" + - "6. Also check: is the result checked for nullptr before use? Null result means the plugin is unavailable." + - "7. If QueryInterfaceByCallsign() result is used without a nullptr check → flag as additional finding" conditional: true skip_condition: "No QueryInterfaceByCallsign() calls found in the plugin" @@ -1697,7 +1700,8 @@ phase_8_checkpoints: rule: "thunder-plugin-rules.yaml / rule_38" # =========================================================================================== -# HOLISTIC RULES — 8 sub-phases (32 rules: rule_39 to rule_70) +# HOLISTIC RULES — 8 sub-phases (46 rules: rule_39 to rule_84) +# EXTENDED RULES — rule_71 to rule_84 (14 rules added from field review) # Conventions & Encapsulation | Lifecycle & State Integrity | Concurrency & Threading # COM Reference & Memory Safety | Resource Management | JSON-RPC Compliance # Inter-Plugin & OOP Design | Code Quality & Security @@ -1959,3 +1963,115 @@ general_rules: review_question: "Using semantic reasoning over all source files, is the plugin free of printf, fprintf, std::cout, std::cerr, and similar direct output calls — using Thunder tracing (TRACE, TRACE_L1, etc.) exclusively for logging and debug output?" 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." + + - rule_id: "rule_71" + name: "No ILocalDispatcher Usage" + severity: "violation" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over all COMRPC and JSON-RPC call paths, does the plugin avoid using ILocalDispatcher for any communication? ILocalDispatcher bypasses Thunder's security token validation, removes the ability to enforce per-plugin permissions, and adds unnecessary JSON serialise/deserialise overhead for what should be a direct in-process COMRPC call." + 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." + + - rule_id: "rule_72" + name: "No AddRef/Release/QueryInterface Override" + severity: "violation" + category: "com_safety" + review_question: "Using semantic reasoning over all class declarations in the plugin, does the plugin avoid providing its own implementations of AddRef(), Release(), or QueryInterface()? These methods are provided by Thunder's reference counting base classes (Core::Unknown, RPC::Administrator) and overriding them breaks COM lifetime correctness and interferes with Thunder's internal bookkeeping." + 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." + + - rule_id: "rule_73" + name: "Use PluginSmartInterfaceType for Persistent COMRPC Proxies" + severity: "warning" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over all persistent COMRPC proxy storage (member variables holding a COMRPC interface to another plugin), does the plugin use PluginSmartInterfaceType to store such proxies rather than a raw pointer? PluginSmartInterfaceType automatically handles the case where the remote plugin is deactivated (dangling proxy), invalidating the local reference safely. Exception: proxies to IController interfaces do not require this." + 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." + + - rule_id: "rule_74" + name: "Prefer WorkerPool for Background Tasks" + severity: "warning" + category: "concurrency" + review_question: "Using semantic reasoning over all background work and thread usage, does the plugin prefer Core::WorkerPool jobs over raw thread spawning (std::thread, pthread_create) for one-shot or periodic background tasks? WorkerPool jobs are managed, thread-pooled, and integrate with Thunder's shutdown sequence. Raw threads require manual join/detach in Deinitialize and do not participate in Thunder's cooperative shutdown." + 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." + + - rule_id: "rule_75" + name: "No COMRPC Call Inside COMRPC Notification" + severity: "warning" + category: "concurrency" + review_question: "Using semantic reasoning over all COMRPC notification/callback implementations, does the plugin avoid making outbound COMRPC calls from within a COMRPC notification callback? An outbound COMRPC call from within an inbound COMRPC callback can cause re-entrancy in the same direction on the same channel, leading to deadlock or undefined behaviour. If such a call is genuinely needed, it must be dispatched asynchronously via WorkerPool." + 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." + + - rule_id: "rule_76" + name: "No IController::Persist Calls" + severity: "warning" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over all IController method calls in the plugin, does the plugin avoid calling IController::Persist()? The Persist/Override feature is disabled on non-debug builds — calling Persist() in production code silently does nothing, masking a broken assumption about plugin configuration persistence." + 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." + + - rule_id: "rule_77" + name: "No Direct curl / libcurl Usage" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over all HTTP and network calls, does the plugin avoid using curl/libcurl directly? Direct libcurl usage bypasses Thunder's HTTP utilities (Web::Request, Web::Response, Web::WebClient), which provide consistent TLS configuration, proxy handling, and error reporting. Direct curl also adds an untracked external library dependency." + 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." + + - rule_id: "rule_78" + name: "No Process Spawning from Plugins" + severity: "violation" + category: "code_quality_security" + review_question: "Using semantic reasoning over all system interaction in the plugin, does the plugin avoid spawning child processes via popen(), system(), fork(), exec() or equivalent? Process spawning from a plugin (especially in Initialize() or Configure()) blocks the Thunder main thread, leaks file descriptors across OOP process boundaries, introduces unmanaged child process lifetimes, and is a security risk if the command includes any unsanitized input." + 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." + + - rule_id: "rule_79" + name: "No Hardcoded Plugin Callsigns" + severity: "warning" + category: "inter_plugin_design" + review_question: "Using semantic reasoning over all inter-plugin communication, does the plugin avoid hardcoding callsign strings (e.g. \"org.rdk.DeviceInfo\", \"LocationSync\") directly in code? Hardcoded callsigns create brittle dependencies on specific plugin names that may differ across platforms or deployments. Callsigns should come from plugin configuration." + 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." + + - rule_id: "rule_80" + name: "Use ProxyType for COM Interface Parameters" + severity: "warning" + category: "com_safety" + review_question: "Using semantic reasoning over all COM interface method signatures and implementations, when a method returns or accepts a COM interface pointer as an out-parameter, is Core::ProxyType used to ensure reference-counted lifetime management? Returning a bare COM interface pointer from a method without ProxyType is a memory safety hazard — the caller may not know whether to AddRef or whether the pointer's lifetime is already managed." + 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." + + - rule_id: "rule_81" + name: "Use Core::GetEnvironment / SetEnvironment" + severity: "warning" + category: "code_quality_security" + review_question: "Using semantic reasoning over all environment variable access in the plugin, does the plugin use Core::GetEnvironment() and Core::SetEnvironment() instead of the C standard library getenv() and setenv()? The POSIX getenv/setenv are not thread-safe; Thunder's Core::GetEnvironment and Core::SetEnvironment are thread-safe wrappers that are now mandated by the framework." + 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." + + - rule_id: "rule_82" + name: "No sleep() in Plugin Code" + severity: "warning" + category: "concurrency" + review_question: "Using semantic reasoning over all time-delay or polling patterns, does the plugin avoid sleep(), usleep(), std::this_thread::sleep_for(), or equivalent busy-wait delays? sleep() in a plugin indicates polling or busy-waiting instead of event-driven design. Use Core::WorkerPool timers, condition variables, or framework event notifications instead." + 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." + + - rule_id: "rule_83" + 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, 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()." + 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." + + - rule_id: "rule_84" + name: "No Override of JSONRPC Dispatch Methods" + severity: "warning" + category: "jsonrpc_compliance" + review_question: "Using semantic reasoning over the plugin class hierarchy and method overrides, does the plugin avoid overriding internal JSONRPC dispatch or callback methods (such as Dispatch(), Callback(), or internal JSONRPC handler hooks) from the JSONRPC base class? Such overrides are rarely valid and typically indicate a misunderstanding of the JSONRPC registration pattern. The correct approach is to register handlers via Register() in Initialize() and Unregister() in Deinitialize()." + 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." From fee89946def7ff61817b98ac6520e15c1260f72b Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Tue, 14 Jul 2026 11:51:50 +0530 Subject: [PATCH 13/15] fix: use absolute path for Prompts in setup-prompts.py and README --- PluginQualityAdvisor/README.md | 17 ++++++++++++----- PluginQualityAdvisor/setup-prompts.py | 11 ++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/PluginQualityAdvisor/README.md b/PluginQualityAdvisor/README.md index 694b6e68..74fe2392 100644 --- a/PluginQualityAdvisor/README.md +++ b/PluginQualityAdvisor/README.md @@ -10,17 +10,24 @@ AI-driven validation tools for Thunder plugin and COM interface development, pow 2. **ThunderNanoServices** and **ThunderInterfaces** repositories in your workspace (or specify the path when prompted) 3. Register the prompt file location in VS Code: - Open your VS Code `settings.json` (`Ctrl+Shift+P` -> `Preferences: Open User Settings (JSON)`) and add: + **Option A — Manual (recommended):** + Open your VS Code `settings.json` (`Ctrl+Shift+P` -> `Preferences: Open User Settings (JSON)`) and add the absolute path to the Prompts folder: -```json + ```json { "chat.promptFilesLocations": { - "ThunderTools/PluginQualityAdvisor/Prompts": true + "/full/path/to/ThunderTools/PluginQualityAdvisor/Prompts": true } } -``` + ``` + + The path must be the **absolute path** to the `Prompts` folder. -The path should point to the `Prompts` folder inside PluginQualityAdvisor. Use the path relative to your workspace root, or an absolute path if ThunderTools is outside the workspace. + **Option B — Automated (Python script):** + ```bash + python3 PluginQualityAdvisor/setup-prompts.py + ``` + This detects the absolute path to the `Prompts` folder and writes it to your VS Code `settings.json` automatically. 4. **Reload VS Code** — press `Ctrl+Shift+P` -> `Developer: Reload Window` diff --git a/PluginQualityAdvisor/setup-prompts.py b/PluginQualityAdvisor/setup-prompts.py index 34d76a2a..ac3d64a6 100644 --- a/PluginQualityAdvisor/setup-prompts.py +++ b/PluginQualityAdvisor/setup-prompts.py @@ -2,7 +2,7 @@ """ setup-prompts.py — Registers ThunderTools PluginQualityAdvisor prompts with VS Code. -Adds "ThunderTools/PluginQualityAdvisor/Prompts": true to chat.promptFilesLocations +Adds the absolute path to the Prompts folder to chat.promptFilesLocations in VS Code settings.json. Works on Windows, macOS, and Linux. No external dependencies — stdlib only. @@ -15,8 +15,13 @@ import shutil import sys from datetime import datetime +from pathlib import Path -PROMPTS_KEY = "ThunderTools/PluginQualityAdvisor/Prompts" +# Compute absolute path to the Prompts folder relative to this script's location +_SCRIPT_DIR = Path(__file__).resolve().parent +_PROMPTS_DIR = _SCRIPT_DIR / "Prompts" +# Use forward slashes for VS Code compatibility on all platforms +PROMPTS_KEY = str(_PROMPTS_DIR).replace("\\", "/") def find_settings_path(): @@ -96,7 +101,7 @@ def main(): prompt_locations = settings.get("chat.promptFilesLocations", {}) if isinstance(prompt_locations, dict) and prompt_locations.get(PROMPTS_KEY) is True: print() - print("Already configured — 'ThunderTools/PluginQualityAdvisor/Prompts' is already in chat.promptFilesLocations.") + print(f"Already configured — '{PROMPTS_KEY}' is already in chat.promptFilesLocations.") print("No changes needed.") print() print("Available slash commands:") From bfc2145281e9d4a1788ff8c99c43af396cfe4f78 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Wed, 15 Jul 2026 15:09:18 +0530 Subject: [PATCH 14/15] fix: address copilot review comments --- .../interface/Thunder-interface-rules.md | 12 +- .../thunder-plugin-qa/specs/plugin/spec.md | 8 +- .../thunder-interface-review.prompt.md | 79 ++++++----- .../thunder-interface-rule-manager.prompt.md | 43 +++--- .../Prompts/thunder-plugin-review.prompt.md | 131 +++++++++--------- .../thunder-plugin-rule-manager.prompt.md | 67 ++++----- .../interface-rule-template-guide.md | 95 +++++-------- .../plugin-rule-template-guide.md | 128 +++++++---------- .../rules/thunder-interface-rules.yaml | 112 +++++++-------- 9 files changed, 322 insertions(+), 353 deletions(-) diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md index 468fb94c..02e457ed 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -6,7 +6,7 @@ Rules for validating Thunder COM interface headers in `ThunderInterfaces/interfaces/`. -Every rule uses semantic reasoning — read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. +Every rule uses semantic reasoning - read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. --- @@ -38,7 +38,7 @@ Every rule uses semantic reasoning — read the interface header in full and rea ## Core Rules (16) -### core_1_1 — File Structure +### core_1_1 - File Structure **Severity:** suggestion @@ -47,7 +47,7 @@ Every rule uses semantic reasoning — read the interface header in full and rea Thunder interface headers must follow a standard structure: - File name should match the interface name (IFoo.h for struct EXTERNAL IFoo), though exceptions may exist -- No implementation code in interface headers — pure declarations only +- No implementation code in interface headers - pure declarations only **Extraction Logic:** @@ -59,8 +59,8 @@ Thunder interface headers must follow a standard structure: 1. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) - Exception: multi-interface files or platform-specific groupings may have different naming -2. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations -3. If issues are found → WARNING +2. Verify there is no implementation code - only forward declarations, typedefs, struct/enum declarations +3. If issues are found → SUGGESTION **Violation Pattern:** File name does not match interface name, or implementation code present in interface header @@ -75,7 +75,7 @@ struct EXTERNAL IDictionary : virtual public Core::IUnknown { }; // Correct: pure declarations only -struct EXTERNAL IDictionary : virtual public Core::/t { +struct EXTERNAL IDictionary : virtual public Core::IUnknown { virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; }; ``` diff --git a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md index 1aa120a8..f29878a9 100644 --- a/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -37,7 +37,7 @@ ThunderTools/PluginQualityAdvisor/ #### Scenario: Prompt files are registered with VS Code - GIVEN `ThunderTools/PluginQualityAdvisor/Prompts/` containing the three `.prompt.md` files - WHEN `setup-prompts.py` is run -- THEN it modifies VS Code `settings.json` to add `ThunderTools/PluginQualityAdvisor/Prompts` +- THEN it modifies VS Code `settings.json` to add the absolute path to `PluginQualityAdvisor/Prompts` - AND the three slash commands (`/thunder-plugin-review`, `/thunder-interface-review`, `/thunder-generate-plugin`) become available in VS Code Copilot Chat - AND the script is safe to run multiple times (idempotent, creates backup of settings) @@ -45,7 +45,7 @@ ThunderTools/PluginQualityAdvisor/ --- ### Requirement: Setup script modifies VS Code settings.json to register prompt location -The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add `"ThunderTools/PluginQualityAdvisor/Prompts": true` under `chat.promptFilesLocations`. +The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` to add the absolute path to the `PluginQualityAdvisor/Prompts` folder (using forward slashes), with value `true`, under `chat.promptFilesLocations`. #### Scenario: Resulting settings.json structure - GIVEN VS Code `settings.json` before the script runs (may be empty `{}` or have existing entries) @@ -55,7 +55,7 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` ```json { "chat.promptFilesLocations": { - "ThunderTools/PluginQualityAdvisor/Prompts": true + "/full/path/to/ThunderTools/PluginQualityAdvisor/Prompts": true } } ``` @@ -81,7 +81,7 @@ The `setup-prompts.py` script MUST modify the user-level VS Code `settings.json` - AND if the backup already exists from a previous run, it MUST NOT be overwritten #### Scenario: Script is idempotent -- GIVEN `chat.promptFilesLocations` already contains `"ThunderTools/PluginQualityAdvisor/Prompts": true` +- GIVEN `chat.promptFilesLocations` already contains the absolute `PluginQualityAdvisor/Prompts` path with value `true` - WHEN the setup script is run again - THEN it MUST NOT add a duplicate entry - AND it MUST print a message indicating the entry is already present diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md index ab37e195..29d6d2f0 100644 --- a/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md @@ -1,12 +1,17 @@ +--- +title: "Thunder Interface Review" +description: "Semantic review of Thunder COM interfaces against the interface rule set." +--- + ## Context -Thunder uses COM-style interfaces defined in `ThunderInterfaces/interfaces/`. These interfaces are binary contracts used across process boundaries. All validation uses semantic reasoning — the validator reads the interface header in full as a human reviewer would, never using regex or text search as the primary detection method. +Thunder uses COM-style interfaces defined in `ThunderInterfaces/interfaces/`. These interfaces are binary contracts used across process boundaries. All validation uses semantic reasoning - the validator reads the interface header in full as a human reviewer would, never using regex or text search as the primary detection method. -Rules are loaded at runtime from: `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` +Rules are loaded at runtime from: `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` --- -## Quick Reference — All 19 Rules +## Quick Reference - All 19 Rules | ID | Rule | Key Point | |----|------|-----------| @@ -20,7 +25,7 @@ Rules are loaded at runtime from: `ThunderTools/PluginQualityAdvisor/rules/thund | core_10_1 | Register/Unregister Patterns | INotification: Register+Unregister; ICallback: Callback(nullptr clears) | | core_11_1 | Event Interfaces | @event tag required; EXTERNAL; own ID; inherits Core::IUnknown | | core_12_1 | @json Tag (CRITICAL) | Without @json, ZERO RPC code generated (warning; check for @alias/@text hints) | -| core_13_1 | No IUnknown/IReferenceCounted Methods in Interfaces | Inherited from Core::IUnknown — never redeclare | +| core_13_1 | No IUnknown/IReferenceCounted Methods in Interfaces | Inherited from Core::IUnknown - never redeclare | | core_14_1 | No std::map in Interfaces | Not serialisable across process boundaries | | core_15_1 | Explicit Integer Widths | uint32_t not int; no platform-dependent types | | core_16_1 | @restrict Mandatory with std::vector | Every std::vector parameter must have @restrict:N | @@ -32,14 +37,14 @@ Rules are loaded at runtime from: `ThunderTools/PluginQualityAdvisor/rules/thund --- -> **CRITICAL:** Load `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` before validating. The YAML contains the full rule definitions, extraction logic, verification logic, and fix templates. This quick reference is a navigation aid only. +> **CRITICAL:** Load `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` before validating. The YAML contains the full rule definitions, extraction logic, verification logic, and fix templates. This quick reference is a navigation aid only. --- ## Your Task 1. **Identify** the interface file to validate (from user's command or ask if not provided) -2. **Load** `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` for full rule definitions +2. **Load** `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` for full rule definitions 3. **Validate** the interface against All 19 Rules in order (core_1_1 → core_18_1, then advisory_m1_1 → advisory_m3_1) 4. **Report** with grouped sections using the output format below 5. **Provide** specific fix examples using the `fix_template` from the YAML @@ -48,37 +53,37 @@ For each rule, apply contextual judgment (JUDGE step): if the developer's approa --- -## Step 3 — Execute Rules (CRITICAL: Understand First, Then Check) +## Step 3 - Execute Rules (CRITICAL: Understand First, Then Check) **Thunder Version Detection:** - `namespace WPEFramework` → **pre-Thunder 5.0** interface - `namespace Thunder` → **Thunder 5.0+** interface This affects which rules apply: -- **core_5_1** (Return Type Conventions — Core::hresult mandatory): Only applies to Thunder 5.0+ interfaces. Pre-5.0 interfaces correctly use `uint32_t` — do NOT flag as a violation. +- **core_5_1** (Return Type Conventions - Core::hresult mandatory): Only applies to Thunder 5.0+ interfaces. Pre-5.0 interfaces correctly use `uint32_t` - do NOT flag as a violation. **Review philosophy for ALL 19 rules:** -1. **UNDERSTAND FIRST** — Read the ENTIRE interface header file. Build a complete mental model of the interface's purpose, method contracts, notification patterns, type usage, and how Register/Unregister relate to each other. Do this ONCE before checking any rule. -2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. -3. **REASON** — Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full interface context make this approach correct and safe?" If yes → downgrade severity. -4. **CITE** — If genuinely wrong (not just technically different), cite exact `[InterfaceName.h:LINE]` -5. **FIX** — Show corrected code using `fix_template` +1. **UNDERSTAND FIRST** - Read the ENTIRE interface header file. Build a complete mental model of the interface's purpose, method contracts, notification patterns, type usage, and how Register/Unregister relate to each other. Do this ONCE before checking any rule. +2. **FOCUS** - For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand - never in isolation. +3. **REASON** - Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full interface context make this approach correct and safe?" If yes → downgrade severity. +4. **CITE** - If genuinely wrong (not just technically different), cite exact `[InterfaceName.h:LINE]` +5. **FIX** - Show corrected code using `fix_template` -**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole interface — not because a single block viewed in isolation doesn't match a pattern. +**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole interface - not because a single block viewed in isolation doesn't match a pattern. -**CRITICAL:** Never use regex or pattern matching as the primary detection method — always use semantic understanding. Read the interface as a human reviewer would, reasoning about the meaning and intent of each declaration. +**CRITICAL:** Never use regex or pattern matching as the primary detection method - always use semantic understanding. Read the interface as a human reviewer would, reasoning about the meaning and intent of each declaration. --- -## Step 6 — Generate CSV Report +## Step 6 - Generate CSV Report After reporting results in chat, generate a CSV file: -**File path:** `ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` +**File path:** `PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` -- Create `ThunderTools/PluginQualityAdvisor/Reports/interface/` if it does not exist -- Never overwrite an existing file — append `_2`, `_3` etc. if needed +- Create `PluginQualityAdvisor/Reports/interface/` if it does not exist +- Never overwrite an existing file - append `_2`, `_3` etc. if needed **CSV columns (exact order):** @@ -103,23 +108,23 @@ After reporting results in chat, generate a CSV file: - Fields containing commas must be wrapped in double quotes - Embedded double quotes escaped as `""` - Empty fields: leave blank (two consecutive commas) -- One row per violated rule only — PASS rows excluded +- One row per violated rule only - PASS rows excluded **If no issues found:** Generate header row + comment row: ```csv No,Interface,Date,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning -,,,,,,,,,,All rules passed — no issues found,, +,,,,,,,,,,All rules passed - no issues found,, ``` **Post-generation message:** ``` 📊 Report saved: - ThunderTools/PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv - {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions + PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv + {N} issue(s) logged - {violations} violations, {warnings} warnings, {suggestions} suggestions To open in Excel (Windows): - Start-Process "ThunderTools\PluginQualityAdvisor\Reports\interface\{InterfaceName}_{YYYY-MM-DD}.csv" + Start-Process "PluginQualityAdvisor\Reports\interface\{InterfaceName}_{YYYY-MM-DD}.csv" ``` --- @@ -127,11 +132,11 @@ To open in Excel (Windows): ## Example Output Structure ``` -## Thunder Interface Validation — IHdmiCecSink +## Thunder Interface Validation - IHdmiCecSink ### 🔴 Violations (Must Fix) -**core_12_1 — @json Tag (CRITICAL)** +**core_12_1 - @json Tag (CRITICAL)** Status: VIOLATION Citation: [IHdmiCecSink.h:45] @json tag missing above struct declaration Issue: Without @json 1.0.0, ZERO JSON-RPC code will be generated for this interface. @@ -143,9 +148,9 @@ Fix: ### 🟡 Warnings (Should Fix) -**core_15_1 — Explicit Integer Widths** +**core_15_1 - Explicit Integer Widths** Status: WARNING -Citation: [IHdmiCecSink.h:72] int parameter — use uint32_t +Citation: [IHdmiCecSink.h:72] int parameter - use uint32_t Issue: int type is platform-dependent and may differ in size across architectures. Fix: Replace `const int volume` with `const uint32_t volume` @@ -167,7 +172,7 @@ advisory_m1_1, advisory_m2_1, advisory_m3_1 ### Compatibility Notes -(Binary compatibility assessment — note if interface appears to be newly created vs released) +(Binary compatibility assessment - note if interface appears to be newly created vs released) ``` --- @@ -176,7 +181,7 @@ advisory_m1_1, advisory_m2_1, advisory_m3_1 | Scenario | Status field | Reasoning field | |----------|-------------|-----------------| -| Rule satisfied | `PASS` — include in ✅ section | Omit | +| Rule satisfied | `PASS` - include in ✅ section | Omit | | Rule violated, no mitigation | `VIOLATION` / `WARNING` / `SUGGESTION` | Omit | | Rule technically violated but developer's approach is valid | Downgrade one level + `Status` field shows downgraded level | **Required** | @@ -191,11 +196,11 @@ Severity is **never escalated** above the YAML-defined level. ## Common Critical Issues -- **Missing @json tag** — the #1 cause of "why is there no JSON-RPC for my interface?" — results in zero generated code -- **std::vector without @restrict** — required; missing @restrict produces unsafe unbounded deserialization -- **std::map in interface** — not serialisable across process boundaries; use iterators instead -- **Missing nested IDs** — INotification/ICallback without their own RPC::ID_* values -- **Ambiguous integer types** — `int` and `long` change size on different platforms; always use uint32_t etc. +- **Missing @json tag** - the #1 cause of "why is there no JSON-RPC for my interface?" - results in zero generated code +- **std::vector without @restrict** - required; missing @restrict produces unsafe unbounded deserialization +- **std::map in interface** - not serialisable across process boundaries; use iterators instead +- **Missing nested IDs** - INotification/ICallback without their own RPC::ID_* values +- **Ambiguous integer types** - `int` and `long` change size on different platforms; always use uint32_t etc. --- @@ -203,5 +208,5 @@ 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 are in `ThunderInterfaces/interfaces/` — search there first \ No newline at end of file +- Load the YAML before every validation run - rules may have been updated since this prompt was created +- Interface headers are in `ThunderInterfaces/interfaces/` - search there first \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md index 25479893..d7641ba3 100644 --- a/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md @@ -1,14 +1,19 @@ +--- +title: "Thunder Interface Rule Manager" +description: "Add, update, or remove Thunder interface validation rules and keep related files in sync." +--- + ## Purpose -This prompt manages rules in `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: +This prompt manages rules in `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: -1. `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` — rule data -2. `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` — Quick Reference table + rule detail blocks -3. `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` — spec requirements +1. `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` - rule data +2. `PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` - Quick Reference table + rule detail blocks +3. `.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` - spec requirements --- -## Step 0 — Document Template Fast Path +## Step 0 - Document Template Fast Path If the user pastes a filled template with the following sections, skip the questionnaire and proceed directly to Step 3/4: @@ -20,7 +25,7 @@ If the user pastes a filled template with the following sections, skip the quest 1. 2. -## How to Verify It +## How to Verify It (Verification Logic) 1. 2. @@ -34,7 +39,7 @@ If the user pastes a filled template with the following sections, skip the quest // Correct: -## Example +## Example Citation ``` @@ -42,7 +47,7 @@ After receiving a template, also run **Core vs Advisory Classification** to veri --- -## Step 1 — Collect Action +## Step 1 - Collect Action Ask the user the following questions using `vscode_askQuestions`: @@ -60,8 +65,8 @@ Options: - Advisory rule (best practice or style) Message: | - **Core rules** are violations that cause codegen failures, ABI breakage, runtime crashes, - or incorrect RPC behaviour. All 15 current core rules are severity: violation. + **Core rules** cover codegen failures, ABI breakage, runtime crashes, + or incorrect RPC behaviour. There are currently 16 core rules with mixed severities. Examples: missing @json tag, wrong return type, missing @restrict on std::vector. **Advisory rules** are best-practice or style guidance that improves interface quality @@ -70,7 +75,7 @@ Message: | --- -## Step 2 — Branch by Action +## Step 2 - Branch by Action ### Branch: REMOVE @@ -96,7 +101,7 @@ Ask for the rule ID only (e.g. `core_1_1` or `advisory_m2_1`) then go to **Step Note: There is **no `category` field**. -**(2c)** Ask the user (via `vscode_askQuestions`, multi-select): Which fields do you want to change? (select from [1]–[9]) +**(2c)** Ask the user (via `vscode_askQuestions`, multi-select): Which fields do you want to change? (select from [1]-[9]) **(2d)** For each selected field number, ask only the new value. @@ -117,11 +122,11 @@ Ask all fields via `vscode_askQuestions`: - **description**: Describe what the rule checks and why it matters. (multiline) - **extraction_logic**: How should the validator find the relevant code? (numbered steps) - Message: Describe reading and understanding the code — not searching for patterns. + Message: Describe reading and understanding the code - not searching for patterns. Example: "1. Read the full interface declaration\n2. Identify all method parameter types" - **verification_logic**: What are the verification steps? (numbered steps) - Message: Each step must describe semantic reasoning — "Reason about X", never "search for Y". + Message: Each step must describe semantic reasoning - "Reason about X", never "search for Y". - **violation_pattern**: Single-line description of the wrong pattern. @@ -131,7 +136,7 @@ Ask all fields via `vscode_askQuestions`: --- -## Step 3 — Apply Changes (ADD / UPDATE) +## Step 3 - Apply Changes (ADD / UPDATE) ### Update `thunder-interface-rules.yaml`: @@ -150,7 +155,7 @@ Ask all fields via `vscode_askQuestions`: --- -## Step 4 — Remove Rule +## Step 4 - Remove Rule ### From `thunder-interface-rules.yaml`: - Remove the entire rule block from the `core_rules` or `advisory_rules` list @@ -164,15 +169,15 @@ Ask all fields via `vscode_askQuestions`: --- -## Step 5 — Confirmation Report +## Step 5 - Confirmation Report After completing all changes, display: ``` -## Interface Rule Manager — Changes Applied +## Interface Rule Manager - Changes Applied **Action:** [Add/Update/Remove] -**Rule:** [id] — [name] +**Rule:** [id] - [name] **List:** [Core/Advisory] ### Files Updated diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md index 42c3c3cd..53eb354a 100644 --- a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md @@ -1,43 +1,46 @@ +--- +title: "Thunder Plugin Review" +description: "Semantic review of Thunder plugins against the Thunder plugin rule set." +--- + ## Core Principle -This prompt performs **semantic code review** — reading plugin source code as a human developer and reasoning about its meaning. +This prompt performs **semantic code review** - reading plugin source code as a human developer and reasoning about its meaning. **Phase checkpoint rules (38):** Each uses a bounded yes/no query on a specific code block. ❌ Open-ended (never do this): "Check this file for issues" ✅ Bounded (always this): "Is AddRef() called on the stored IShell* pointer immediately after assignment in Initialize()?" -**Holistic rules (46):** Each requires holistic analysis — understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. +**Holistic rules (46):** Each requires holistic analysis - understanding control flow, ownership, lifecycle, and threading across multiple code paths. Cannot be reduced to a single bounded query. --- ## Report Output Philosophy -> **CRITICAL:** Only report issues. PASS and SKIP appear as **summary counts only** in phase tables — never as individual checkpoint details. -> Every citation must use the **ACTUAL plugin name** from the user's command — NEVER use `{PluginName}` as a placeholder in output. -> **Every reported issue requires a `[File:line]` citation** — no exceptions. +> **CRITICAL:** Only report issues. PASS and SKIP appear as **summary counts only** in phase tables - never as individual checkpoint details. +> Every citation must use the **ACTUAL plugin name** from the user's command - NEVER use `{PluginName}` as a placeholder in output. +> **Every reported issue requires a `[File:line]` citation** - no exceptions. --- ## Usage -**Mode 1 — Full plugin review:** +**Mode 1 - Full plugin review:** ``` /thunder-plugin-review ``` Reviews ALL files in the plugin folder against all applicable rules. -Examples: -- `/thunder-plugin-review Dictionary` -- `/thunder-plugin-review HdmiCecSink` - -**Mode 2 — Single file review:** +**Mode 2 - Single file review:** ``` /thunder-plugin-review ``` Reviews ONLY the specified file against rules applicable to that file type. Examples: +- `/thunder-plugin-review Dictionary` +- `/thunder-plugin-review HdmiCecSink` - `/thunder-plugin-review Dictionary Dictionary.cpp` - `/thunder-plugin-review HdmiCecSink Module.h` @@ -62,15 +65,15 @@ Examples: ## Methodology -### Step 1 — Load Rules +### Step 1 - Load Rules -Load `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml`. This file contains all 84 rules: -- `phase_1_checkpoints` through `phase_8_checkpoints` — 38 rules with bounded queries -- `general_rules` — 46 holistic rules across 8 sub-phases (rule_39 to rule_84) +Load `PluginQualityAdvisor/rules/thunder-plugin-rules.yaml`. This file contains all 84 rules: +- `phase_1_checkpoints` through `phase_8_checkpoints` - 38 rules with bounded queries +- `general_rules` - 46 holistic rules across 8 sub-phases (rule_39 to rule_84) All rules produce the same output format. There is no distinction between "phase checkpoint" and "holistic" in the report. -### Step 2 — Identify Plugin Files +### Step 2 - Identify Plugin Files Primary search: `ThunderNanoServices/{PluginName}/` Fallback: Search workspace for a folder named `{PluginName}` @@ -87,32 +90,32 @@ Last resort: Ask user for location | `{PluginName}Implementation.h` | Phase 5C (only if PLUGIN__MODE = "Local" in CMakeLists.txt) | | `{PluginName}Implementation.cpp` | Phase 5C (only if PLUGIN__MODE = "Local" in CMakeLists.txt) | -### Step 3 — Execute Rules (CRITICAL: Understand First, Then Check) +### Step 3 - Execute Rules (CRITICAL: Understand First, Then Check) **Thunder Version Detection:** - `namespace WPEFramework` → **pre-Thunder 5.0** plugin - `namespace Thunder` → **Thunder 5.0+** plugin This affects which rules apply: -- **rule_38** (COM Methods Return Core::hresult): Only applies to Thunder 5.0+ plugins. Pre-5.0 plugins correctly use `uint32_t` — do NOT flag as a violation. -- **rule_31** (No Hardcoded Paths): Linux kernel virtual filesystems (`/proc/`, `/sys/`, `/dev/`) are fixed OS paths, not deployment-specific — do NOT flag these. +- **rule_38** (COM Methods Return Core::hresult): Only applies to Thunder 5.0+ plugins. Pre-5.0 plugins correctly use `uint32_t` - do NOT flag as a violation. +- **rule_31** (No Hardcoded Paths): Linux kernel virtual filesystems (`/proc/`, `/sys/`, `/dev/`) are fixed OS paths, not deployment-specific - do NOT flag these. **Review philosophy for ALL 84 rules:** -1. **UNDERSTAND FIRST** — Read ALL plugin source files. Build a complete mental model of the plugin's architecture: its lifecycle flow, threading model, ownership patterns, data flow, and how Initialize/Deinitialize relate to each other. Do this ONCE before checking any rule. -2. **FOCUS** — For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand — never in isolation. -3. **REASON** — Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full plugin context make this approach correct and safe?" If yes → downgrade severity. -4. **CITE** — If genuinely wrong (not just technically different), cite exact `[ActualPluginName.cpp:LINE]` -5. **FIX** — Show corrected code using `fix_template` +1. **UNDERSTAND FIRST** - Read ALL plugin source files. Build a complete mental model of the plugin's architecture: its lifecycle flow, threading model, ownership patterns, data flow, and how Initialize/Deinitialize relate to each other. Do this ONCE before checking any rule. +2. **FOCUS** - For each rule, look at the specific concern it asks about. But reason about it WITH the full context you already understand - never in isolation. +3. **REASON** - Ask the rule's question. If the specific block looks like a violation when viewed alone, ask yourself: "Does the full plugin context make this approach correct and safe?" If yes → downgrade severity. +4. **CITE** - If genuinely wrong (not just technically different), cite exact `[ActualPluginName.cpp:LINE]` +5. **FIX** - Show corrected code using `fix_template` -**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole plugin — not because a single block viewed in isolation doesn't match a pattern. +**Key:** A rule should FAIL only when the code is genuinely unsafe or incorrect in the context of the whole plugin - not because a single block viewed in isolation doesn't match a pattern. --- ## CRITICAL FILE SCOPING RULES -- **Phase 1** (Module Structure) applies ONLY to `Module.h` and `Module.cpp` — never to the main plugin class files -- **Phase 3** (Class Registration) applies ONLY to the **main plugin class** (the one implementing `PluginHost::IPlugin`) — internal helper classes (Notification, Sink, Config, etc.) are explicitly excluded +- **Phase 1** (Module Structure) applies ONLY to `Module.h` and `Module.cpp` - never to the main plugin class files +- **Phase 3** (Class Registration) applies ONLY to the **main plugin class** (the one implementing `PluginHost::IPlugin`) - internal helper classes (Notification, Sink, Config, etc.) are explicitly excluded --- @@ -124,7 +127,7 @@ After applying verification logic, if the answer is "No" (rule technically viola |----------|-----------------|-------------------| | Rule satisfied | `PASS` | Omit | | Rule violated, no mitigation | `VIOLATION` / `WARNING` / `SUGGESTION` | Omit | -| Rule violated but developer's approach is valid in context | Downgrade one level | **Required** — explain rule, developer's approach, why it is acceptable | +| Rule violated but developer's approach is valid in context | Downgrade one level | **Required** - explain rule, developer's approach, why it is acceptable | | Violated with residual risk but approach is reasonable | `WARNING` | **Required** | The `reasoning` field is **required** when severity is downgraded; it is **omitted** when no downgrade occurs. @@ -140,40 +143,40 @@ question: "Is AddRef() called on the stored IShell* pointer immediately after as answer: "No" extracted_code: | [Dictionary.cpp:45] _service = service; - [Dictionary.cpp:46] // AddRef not called here — service pointer is used locally only + [Dictionary.cpp:46] // AddRef not called here - service pointer is used locally only violation_line: "[Dictionary.cpp:45]" citation: "[Dictionary.cpp:45] IShell* assigned without AddRef()" fix: "_service->AddRef(); // add immediately after _service = service;" -reasoning: "The rule exists to prevent dangling pointers when the IShell* is stored across calls. In this plugin, _service is used only within Initialize() and does not persist — it is not a true member field storing the pointer for later use. The developer's approach is valid in this context with no residual lifetime risk." +reasoning: "The rule exists to prevent dangling pointers when the IShell* is stored across calls. In this plugin, _service is used only within Initialize() and does not persist - it is not a true member field storing the pointer for later use. The developer's approach is valid in this context with no residual lifetime risk." ``` ```yaml rule_id: rule_06 status: WARNING severity: warning -question: "Is nullptr used exclusively — no NULL as a pointer value in code?" +question: "Is nullptr used exclusively - no NULL as a pointer value in code?" answer: "No" extracted_code: | [Dictionary.cpp:108] IPlugin* plugin = NULL; violation_line: "[Dictionary.cpp:108]" -citation: "[Dictionary.cpp:108] NULL used as null pointer — NULL vs nullptr" +citation: "[Dictionary.cpp:108] NULL used as null pointer - NULL vs nullptr" fix: "IPlugin* plugin = nullptr;" reasoning: # omit if no severity downgrade ``` ## Output Format -### Issue Report — Grouped by File +### Issue Report - Grouped by File Group all issues (from any rule) by source file. For files with failures: ``` -### {ActualFileName} — N issue(s) +### {ActualFileName} - N issue(s) ``` Under each file heading, list every failing rule as a YAML block (same format for all 84 rules): -``yaml +```yaml rule_id: rule_XX status: ❌ VIOLATION # or ⚠️ WARNING or 💡 SUGGESTION severity: violation # original YAML severity (never changes) @@ -185,14 +188,12 @@ violation_line: "[ActualFile.cpp:LINE]" citation: "[ActualFile.cpp:LINE] Short issue description" fix: "corrected code or one-line instruction" reasoning: # ONLY if severity was downgraded; omit otherwise -`` - - +``` Status symbols (prefix the `status:` field value with these): -- ❌ `VIOLATION` — blocking issue, must fix -- ⚠️ `WARNING` — should fix -- 💡 `SUGGESTION` — optional improvement +- ❌ `VIOLATION` - blocking issue, must fix +- ⚠️ `WARNING` - should fix +- 💡 `SUGGESTION` - optional improvement **Format:** `status: ❌ VIOLATION` or `status: ⚠️ WARNING` or `status: 💡 SUGGESTION` @@ -201,15 +202,15 @@ When severity is downgraded (e.g. violation → suggestion), the symbol matches | Phase | PASS | FAIL | SKIP | |-------|------|------|------| -| Phase 1 — Module Structure | N | N | N | -| Phase 2 — Code Style | N | N | N | -| Phase 3 — Class Registration | N | N | N | -| Phase 4 — Lifecycle | N | N | N | -| Phase 5 — Implementation | N | N | N | -| Phase 5C — Out-of-Process | N | N | N | -| Phase 6 — Configuration | N | N | N | -| Phase 7 — CMake | N | N | N | -| Phase 8 — COM Interfaces | N | N | N | +| Phase 1 - Module Structure | N | N | N | +| Phase 2 - Code Style | N | N | N | +| Phase 3 - Class Registration | N | N | N | +| Phase 4 - Lifecycle | N | N | N | +| Phase 5 - Implementation | N | N | N | +| Phase 5C - Out-of-Process | N | N | N | +| Phase 6 - Configuration | N | N | N | +| Phase 7 - CMake | N | N | N | +| Phase 8 - COM Interfaces | N | N | N | | Holistic Rules (8 sub-phases) | N | N | N | | **Total (84 rules)** | **N** | **N** | **N** | @@ -220,17 +221,17 @@ Followed by a numbered **Next Steps** list citing `[File:line]` for each action ## Key Advantages - **Reproducible:** Same bounded questions → same answers on the same code -- **Fast:** One code block per checkpoint — no whole-file analysis per rule +- **Fast:** One code block per checkpoint - no whole-file analysis per rule - **Actionable:** Each failure has an exact line, an explanation, and a fix - **Contextual:** Severity downgrade logic handles valid alternative approaches ## Important Notes -1. Load `thunder-plugin-rules.yaml` at the start of every review run — rules may have been updated -2. Never embed rule data in this prompt — always load from YAML at runtime +1. Load `thunder-plugin-rules.yaml` at the start of every review run - rules may have been updated +2. Never embed rule data in this prompt - always load from YAML at runtime 3. If a plugin is not found in `ThunderNanoServices/`, search workspace before asking 4. Total: 84 rules (rule_01 to rule_84), all sequential, all producing unified output -5. Rule IDs in reports use the format `rule_XX` — no phase prefixes +5. Rule IDs in reports use the format `rule_XX` - no phase prefixes ## Command Examples @@ -244,14 +245,14 @@ Followed by a numbered **Next Steps** list citing `[File:line]` for each action --- -## Step 6 — Generate CSV Report +## Step 6 - Generate CSV Report After reporting all results in chat, generate a CSV file for tracking and Excel analysis. -**File path:** `ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` +**File path:** `PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` -- Create `ThunderTools/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 +- 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 **CSV columns (exact order, 14 columns):** @@ -260,7 +261,7 @@ After reporting all results in chat, generate a CSV file for tracking and Excel | No | Row number starting at 1 | `1` | | Plugin | Plugin name from command argument | `HdmiCecSink` | | Date | ISO date of review | `2026-06-05` | -| Phase | Full phase label | `Phase 2 — Code Style` | +| Phase | Full phase label | `Phase 2 - Code Style` | | Rule_ID | Sequential rule ID | `rule_06` | | Rule_Name | Checkpoint name from YAML | `NULL vs nullptr` | | Status | Effective status after JUDGE step | `VIOLATION` / `WARNING` / `SUGGESTION` | @@ -278,11 +279,11 @@ No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_ ``` **Rules:** -- One row per FAIL / WARNING / SUGGESTION only — PASS and SKIP rows are excluded +- One row per FAIL / WARNING / SUGGESTION only - PASS and SKIP rows are excluded - `Status` reflects the **effective** severity after the JUDGE step - `Reasoning` column is populated only when severity was downgraded; empty otherwise - UTF-8, no BOM, CRLF line endings -- Fields containing commas must be wrapped in double quotes: `"Phase 2 — Code Style"` +- Fields containing commas must be wrapped in double quotes: `"Phase 2 - Code Style"` - Embedded double quotes escaped as `""`: `"Use ""nullptr"" not NULL"` - Empty fields: leave blank (two consecutive commas: `,,`) @@ -290,15 +291,15 @@ No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_ ```csv No,Plugin,Date,Phase,Rule_ID,Rule_Name,Status,Severity,File,Line,Citation,Issue_Description,Fix_Summary,Reasoning -,,,,,,,,,,,,All checkpoints passed — no issues found, +,,,,,,,,,,,,All checkpoints passed - no issues found, ``` **Post-generation message:** ``` 📊 Report saved: - ThunderTools/PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv - {N} issue(s) logged — {violations} violations, {warnings} warnings, {suggestions} suggestions + PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv + {N} issue(s) logged - {violations} violations, {warnings} warnings, {suggestions} suggestions To open in Excel (Windows): - Start-Process "ThunderTools\PluginQualityAdvisor\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" + Start-Process "PluginQualityAdvisor\Reports\plugin\{PluginName}_{YYYY-MM-DD}.csv" ``` \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md index caa3c3ea..da383d58 100644 --- a/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md @@ -1,15 +1,20 @@ +--- +title: "Thunder Plugin Rule Manager" +description: "Add, update, or remove Thunder plugin validation rules and keep related files in sync." +--- + ## Purpose -This prompt manages rules in `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: +This prompt manages rules in `PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: -1. `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` — rule data -2. `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` — checkpoint descriptions -3. `ThunderTools/PluginQualityAdvisor/README.md` — documentation -4. `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` — spec requirements +1. `PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` - rule data +2. `PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` - checkpoint descriptions +3. `PluginQualityAdvisor/README.md` - documentation +4. `.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` - spec requirements --- -## Step 0 — Document Template Fast Path +## Step 0 - Document Template Fast Path If the user pastes a filled template with the following sections, skip the questionnaire and go directly to Step 3/4: @@ -34,14 +39,14 @@ If the user pastes a filled template with the following sections, skip the quest ## Conditional -Yes/No — and if Yes, what is the skip condition? +Yes/No - and if Yes, what is the skip condition? ``` -After receiving a template, also run **Phase Checkpoint vs Holistic Classification** to verify the `Type` field is correct before proceeding. +After receiving a template, also run **Phase Checkpoint vs Holistic Classification** to verify the template classification is correct before proceeding. --- -## Step 1 — Collect Action +## Step 1 - Collect Action Ask the user the following questions using `vscode_askQuestions`: @@ -66,27 +71,27 @@ Message: | or reading multiple code paths that cannot be bounded to a single yes/no question. Example: "Are all notification callbacks safe from deadlock across the full plugin lifecycle?" -**Question 3** (header: `phase`) — ask only if rule_kind = Phase checkpoint: +**Question 3** (header: `phase`) - ask only if rule_kind = Phase checkpoint: Which phase does this rule belong to? Options: -- Phase 1 — Module Structure (module_1_*) -- Phase 2 — Code Style (style_2_*) -- Phase 3 — Class Registration (registration_3_*) -- Phase 4 — Lifecycle (lifecycle_4_*) -- Phase 5 — Implementation (implementation_5_*) -- Phase 5C — Out-of-Process (oop_9_*) -- Phase 6 — Configuration (config_6_*) -- Phase 7 — CMake (cmake_7_*) -- Phase 8 — COM Interface Rules (com_8_*) +- Phase 1 - Module Structure (currently: rule_01 to rule_03) +- Phase 2 - Code Style (currently: rule_04 to rule_13) +- Phase 3 - Class Registration (currently: rule_14 to rule_16) +- Phase 4 - Lifecycle (currently: rule_17 to rule_28) +- Phase 5 - Implementation (currently: rule_29 to rule_31) +- Phase 5C - Out-of-Process (currently: rule_32 to rule_33) +- Phase 6 - Configuration (currently: rule_34 to rule_36) +- Phase 7 - CMake (currently: rule_37) +- Phase 8 - COM Interface Rules (currently: rule_38) --- -## Step 2 — Branch by Action +## Step 2 - Branch by Action ### Branch: REMOVE -Ask for the rule ID (rule_id for phase checkpoint, rule_XX) then go to **Step 4**. +Ask for the rule ID (rule_XX) then go to **Step 4**. ### Branch: UPDATE @@ -95,7 +100,7 @@ Ask for the rule ID (rule_id for phase checkpoint, rule_XX) then go to **Step 4* **(2b)** Read the current rule from `thunder-plugin-rules.yaml` and display it annotated with numbered field labels: ``` -[1] rule_id / rule_id: +[1] rule_id: [2] name: [3] severity: [4] phase: @@ -109,7 +114,7 @@ Ask for the rule ID (rule_id for phase checkpoint, rule_XX) then go to **Step 4* [12] skip_condition: (phase checkpoint only) [13] citation.line_format: (phase checkpoint only) [14] review_question: (holistic only) -[15] review_method: (holistic only) +[15] review_method: (holistic only) [16] evidence_requirement: (holistic only) ``` @@ -123,8 +128,8 @@ Then go to **Step 3**. Ask the following via `vscode_askQuestions`: -- **rule_id**: What is the checkpoint ID? (e.g. `rule_041`) - Message: Use the phase prefix + sequential number: `module_1_*`, `style_2_*`, `lifecycle_4_*`, etc. +- **rule_id**: What is the checkpoint ID? (e.g. `rule_17`) + Message: Use sequential `rule_XX` numbering matching the existing phase ranges. - **name**: What is the rule name? (Title Case, e.g. "No Raw Pointers After Move") @@ -143,7 +148,7 @@ Ask the following via `vscode_askQuestions`: ❌ "Does the code have nullptr after Release?" (too vague) - **verification_steps**: What are the verification steps? (numbered list) - Message: Each step must describe semantic reasoning — "Read X and reason about Y". Never "search for" or "match pattern". + Message: Each step must describe semantic reasoning - "Read X and reason about Y". Never "search for" or "match pattern". - **violation_pattern**: Single-line description of what's wrong. @@ -166,7 +171,7 @@ Ask only: --- -## Step 3 — Apply Changes (ADD / UPDATE) +## Step 3 - Apply Changes (ADD / UPDATE) ### Update `thunder-plugin-rules.yaml`: @@ -199,7 +204,7 @@ Ask only: --- -## Step 4 — Remove Rule +## Step 4 - Remove Rule ### From `thunder-plugin-rules.yaml`: - Remove the entire rule block from the appropriate section @@ -218,15 +223,15 @@ Ask only: --- -## Step 5 — Confirmation Report +## Step 5 - Confirmation Report After completing all changes, display: ``` -## Rule Manager — Changes Applied +## Rule Manager - Changes Applied **Action:** [Add/Update/Remove] -**Rule:** [rule_id or rule_id] — [name] +**Rule:** [rule_id] - [name] **Type:** [Phase checkpoint/Holistic] ### Files Updated diff --git a/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md index 8deb4105..5ba008d9 100644 --- a/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md +++ b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md @@ -1,12 +1,12 @@ -# Interface Rule Manager — Rule Template Guide +# Interface Rule Manager - Rule Template Guide Use this guide to fill in a rule template and pass it to `/thunder-interface-rule-manager`. -A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, +A filled template lets you skip the interactive questionnaire entirely - the manager parses your document, validates it, and updates all three files in one shot: -- `ThunderTools/PluginQualityAdvisor/rules/thunder-interface-rules.yaml` -- `ThunderTools/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` -- `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` +- `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` +- `PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` +- `.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md` --- @@ -17,29 +17,6 @@ validates it, and updates all three files in one shot: | Does the violation break code generation (e.g. no JSON-RPC output)? | Yes | No | | Does it cause ABI/binary compatibility issues? | Yes | No | | Does it cause crashes or undefined behaviour at runtime? | Yes | No | -| Is it a structural requirement for Thunder COM interfaces? | Yes | No | -| Is it a design best practice that improves quality but does not break anything? | No | Yes | -| Is it a style/convention preference? | No | Yes | - -**Core rules** enforce strict COM interface correctness — structural requirements that MUST be satisfied for the interface to function correctly with Thunder's code generators and runtime. - -**Advisory rules** are design-quality checks that address broader best practices (single responsibility, no exceptions, enum types, @restrict usage) — violations here indicate design concerns rather than broken code generation. - -If the manager determines your rule is in the wrong list, it will auto-correct with an explanation. - ---- - -## How to use this template - -1. Copy the blank template below into a new `.md` file -2. Set `Action` to `Add`, `Update`, or `Remove` -3. Fill in the fields that apply to your action (see field reference below) -4. Attach the file — or paste its contents — when you open `/thunder-interface-rule-manager` -5. The manager confirms what it parsed before making any changes - ---- - -## Blank template ```markdown ## Interface Rule @@ -75,14 +52,14 @@ Severity: violation ### Example Citation -ThunderInterfaces/interfaces/IExample.h — description +ThunderInterfaces/interfaces/IExample.h - description ``` --- ## Field Reference -### `Action` *(required — must be the first field)* +### `Action` *(required - must be the first field)* | Value | When to use | |---|---| @@ -99,8 +76,8 @@ Format: `core_X_1` for core rules or `advisory_mX_1` for advisory rules. Current core rules: `core_1_1` through `core_18_1` (16 rules) Current advisory rules: `advisory_m1_1`, `advisory_m2_1`, `advisory_m3_1` (3 rules) -- **Add** — leave blank to auto-assign the next available ID, or specify your own -- **Update/Remove** — mandatory; this is how the manager finds the rule +- **Add** - leave blank to auto-assign the next available ID, or specify your own +- **Update/Remove** - mandatory; this is how the manager finds the rule --- @@ -126,9 +103,9 @@ Short descriptive title in Title Case. 2-5 words. | Value | Meaning | |---|---| -| `violation` | Must fix — breaks code generation, ABI, or causes crashes | -| `warning` | Should fix — best practice with real risk | -| `suggestion` | Optional — style or convention preference | +| `violation` | Must fix - breaks code generation, ABI, or causes crashes | +| `warning` | Should fix - best practice with real risk | +| `suggestion` | Optional - style or convention preference | Note: Core rules use mixed severities (suggestion, warning, or violation). Advisory rules also use mixed severities. @@ -162,7 +139,7 @@ Must describe reading and understanding the code, NOT searching for text pattern ### `### How to Verify It (Verification Logic)` *(required for Add; optional for Update)* Numbered steps describing what constitutes a pass vs fail. -Must use semantic reasoning — understand the code, don't pattern match. +Must use semantic reasoning - understand the code, don't pattern match. --- @@ -170,7 +147,7 @@ Must use semantic reasoning — understand the code, don't pattern match. Single-line string describing the wrong pattern. Shown in the violation output. -**Good:** `No @json comment found above interface struct — no RPC code will be generated` +**Good:** `No @json comment found above interface struct - no RPC code will be generated` **Avoid:** Multi-line descriptions or bullet lists --- @@ -184,7 +161,7 @@ Show corrected code using `// WRONG:` and `// Correct:` markers. ### `### Example Citation` *(required for Add; optional for Update)* Reference to a real Thunder interface file showing where this rule applies. -Format: `ThunderInterfaces/interfaces/IFileName.h — description` +Format: `ThunderInterfaces/interfaces/IFileName.h - description` --- @@ -194,18 +171,18 @@ Format: `ThunderInterfaces/interfaces/IFileName.h — description` |---|---|---|---| | `Action` | Required | Required | Required | | `Rule_ID` | Optional | Required | Required | -| `Rule_List` | Required | Only if moving | — | -| `Name` | Required | Only if changing | — | -| `Severity` | Required | Only if changing | — | -| Content sections | All | Only changed ones | — | +| `Rule_List` | Required | Only if moving | - | +| `Name` | Required | Only if changing | - | +| `Severity` | Required | Only if changing | - | +| Content sections | All | Only changed ones | - | -**Update rule:** fill only the fields you want to change — leave everything else blank. +**Update rule:** fill only the fields you want to change - leave everything else blank. --- ## Examples -### Add — Core rule +### Add - Core rule ```markdown ## Interface Rule @@ -219,7 +196,7 @@ Severity: violation ### Description Interface methods must not return raw pointers. Raw pointer returns create ownership -ambiguity — callers don't know whether to call Release() on the result. All pointer +ambiguity - callers don't know whether to call Release() on the result. All pointer returns must use the COM pattern: pass an output pointer parameter and return Core::hresult. ### How to Find It (Extraction Logic) @@ -228,13 +205,13 @@ returns must use the COM pattern: pass an output pointer parameter and return Co ### How to Verify It (Verification Logic) 1. For each virtual method, check if the return type is a raw pointer (T*) -2. Exclude Core::hresult and void returns — those are correct -3. Exclude string_view and similar value types — those are not ownership transfers +2. Exclude Core::hresult and void returns - those are correct +3. Exclude string_view and similar value types - those are not ownership transfers 4. If any method returns a raw pointer type -> VIOLATION 5. Otherwise -> PASS ### Violation Pattern -Interface method returns raw pointer — ownership ambiguity +Interface method returns raw pointer - ownership ambiguity ### Fix // WRONG: @@ -244,12 +221,12 @@ virtual IConnection* GetConnection(const uint32_t id) = 0; virtual Core::hresult GetConnection(const uint32_t id, IConnection*& connection /* @out */) = 0; ### Example Citation -ThunderInterfaces/interfaces/INetworkControl.h — method return types +ThunderInterfaces/interfaces/INetworkControl.h - method return types ``` --- -### Add — Advisory rule +### Add - Advisory rule ```markdown ## Interface Rule @@ -276,7 +253,7 @@ test, and version safely. 3. If count is 12 or fewer -> PASS ### Violation Pattern -Interface has more than 12 methods — consider splitting +Interface has more than 12 methods - consider splitting ### Fix // WRONG: @@ -293,12 +270,12 @@ struct EXTERNAL IMediaPlayerMetadata : virtual public Core::IUnknown { }; ### Example Citation -ThunderInterfaces/interfaces/IMediaPlayer.h — interface method count +ThunderInterfaces/interfaces/IMediaPlayer.h - interface method count ``` --- -### Update — change severity and verification logic +### Update - change severity and verification logic ```markdown ## Interface Rule @@ -355,10 +332,10 @@ Rule_List: core | advisory Name: Title Case, 2-5 words Severity: violation | warning | suggestion -### Description — the WHY (required for Add) -### How to Find It — extraction steps (required for Add) -### How to Verify It — verification steps (required for Add) -### Violation Pattern — single-line summary (required for Add) -### Fix — WRONG / Correct code (required for Add) -### Example Citation — real Thunder interface reference (required for Add) +### Description - the WHY (required for Add) +### How to Find It - extraction steps (required for Add) +### How to Verify It - verification steps (required for Add) +### Violation Pattern - single-line summary (required for Add) +### Fix - WRONG / Correct code (required for Add) +### Example Citation - real Thunder interface reference (required for Add) ``` \ No newline at end of file diff --git a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md index 0e007a8a..2b853a23 100644 --- a/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md +++ b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md @@ -1,13 +1,13 @@ -# Plugin Rule Manager — Rule Template Guide +# Plugin Rule Manager - Rule Template Guide Use this guide to fill in a rule template and pass it to `/thunder-plugin-rule-manager`. -A filled template lets you skip the interactive questionnaire entirely — the manager parses your document, +A filled template lets you skip the interactive questionnaire entirely - the manager parses your document, validates it, and updates all four files in one shot: -- `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` -- `ThunderTools/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` -- `ThunderTools/PluginQualityAdvisor/README.md` -- `ThunderTools/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` +- `PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` +- `PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` +- `PluginQualityAdvisor/README.md` +- `.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md` --- @@ -17,40 +17,14 @@ Use this table to pick the right template. Both follow the same semantic-review | Question | Template A | Template B | |---|---|---| -| Does the rule focus on a specific function or declaration? | Yes | No | -| Does the rule check behavior across multiple parts of the plugin? | No | Yes | -| Does it involve thread safety, resource cleanup flow, or security? | No | Yes | - -**Mostly Template A answers -> use Template A (Phase Checkpoint).** -**Any Template B answer -> use Template B (Holistic Rule).** - -If your rule could go either way, the manager will offer you both options and ask you to confirm. - ---- - -## How to use this template - -1. Copy the appropriate blank template below into a new `.md` file -2. Set `Action` to `Add`, `Update`, or `Remove` -3. Fill in the fields that apply to your action (see field reference below) -4. Attach the file — or paste its contents — when you open `/thunder-plugin-rule-manager` -5. The manager confirms what it parsed before making any changes - ---- - -## Template A — Phase Checkpoint Rule - -Phase checkpoint rules are organized under phase sections (rule_01 to rule_38). - -### Blank template ```markdown -## Plugin Rule — Phase Checkpoint +## Plugin Rule - Phase Checkpoint Action: Add Rule_ID: -Phase: Phase 4 — Lifecycle +Phase: Phase 4 - Lifecycle Name: Severity: violation @@ -83,14 +57,14 @@ Skip when: --- -## Template B — Holistic Rule +## Template B - Holistic Rule Holistic rules check broader plugin behavior such as thread safety, resource handling, and cleanup flow by looking at related code together. They are organized under 8 sub-phases (rule_39 to rule_84). ### Blank template ```markdown -## Plugin Rule — Holistic +## Plugin Rule - Holistic Action: Add @@ -116,7 +90,7 @@ Severity: violation ## Field Reference -### `Action` *(required — must be the first field)* +### `Action` *(required - must be the first field)* | Value | When to use | |---|---| @@ -130,8 +104,8 @@ Severity: violation Sequential identifier in the format `rule_XX` (e.g. `rule_17`, `rule_41`). -- **Add** — leave blank to auto-assign the next available number, or specify your own -- **Update/Remove** — mandatory; this is how the manager finds the rule +- **Add** - leave blank to auto-assign the next available number, or specify your own +- **Update/Remove** - mandatory; this is how the manager finds the rule Current rule ranges: - rule_01 to rule_38: Phase checkpoints @@ -144,15 +118,15 @@ Current rule ranges: Pick exactly one: ``` -Phase 1 — Module Structure (currently: rule_01 to rule_03) -Phase 2 — Code Style (currently: rule_04 to rule_13) -Phase 3 — Class Registration (currently: rule_14 to rule_16) -Phase 4 — Lifecycle (currently: rule_17 to rule_28) -Phase 5 — Implementation (currently: rule_29 to rule_31) -Phase 5C — Out-of-Process (currently: rule_32 to rule_33) -Phase 6 — Configuration (currently: rule_34 to rule_36) -Phase 7 — CMake (currently: rule_37) -Phase 8 — COM Interface Rules (currently: rule_38) +Phase 1 - Module Structure (currently: rule_01 to rule_03) +Phase 2 - Code Style (currently: rule_04 to rule_13) +Phase 3 - Class Registration (currently: rule_14 to rule_16) +Phase 4 - Lifecycle (currently: rule_17 to rule_28) +Phase 5 - Implementation (currently: rule_29 to rule_31) +Phase 5C - Out-of-Process (currently: rule_32 to rule_33) +Phase 6 - Configuration (currently: rule_34 to rule_36) +Phase 7 - CMake (currently: rule_37) +Phase 8 - COM Interface Rules (currently: rule_38) ``` --- @@ -185,26 +159,26 @@ Short descriptive title in Title Case. 2-5 words. | Value | Meaning | Report symbol | |---|---|---| -| `violation` | Must fix — causes bugs, crashes, or codegen failures | VIOLATION | -| `warning` | Should fix — best practice with real risk | WARNING | -| `suggestion` | Optional — style or consistency improvement | SUGGESTION | +| `violation` | Must fix - causes bugs, crashes, or codegen failures | VIOLATION | +| `warning` | Should fix - best practice with real risk | WARNING | +| `suggestion` | Optional - style or consistency improvement | SUGGESTION | --- ### Phase Checkpoint-specific fields -- **Extraction Target** — exact function/section/lines to read (e.g. "Initialize() method body") -- **Yes/No Question** — single binary question; Yes = PASS, No = VIOLATION -- **Verification Steps** — 3-8 numbered steps using semantic reasoning (never regex) -- **Violation Pattern** — one-line citation summary, under 10 words -- **Fix** — `// WRONG:` and `// CORRECT:` code snippets -- **Conditional** — `Yes` or `No`; if Yes, include `Skip when:` condition +- **Extraction Target** - exact function/section/lines to read (e.g. "Initialize() method body") +- **Yes/No Question** - single binary question; Yes = PASS, No = VIOLATION +- **Verification Steps** - 3-8 numbered steps using semantic reasoning (never regex) +- **Violation Pattern** - one-line citation summary, under 10 words +- **Fix** - `// WRONG:` and `// CORRECT:` code snippets +- **Conditional** - `Yes` or `No`; if Yes, include `Skip when:` condition ### Holistic Rule-specific fields -- **Review Question** — semantic question spanning multiple code paths -- **Review Method** — must describe reading full code context, not pattern matching -- **Evidence Requirement** — must require exact `[File:line]` citation +- **Review Question** - semantic question spanning multiple code paths +- **Review Method** - must describe reading full code context, not pattern matching +- **Evidence Requirement** - must require exact `[File:line]` citation --- @@ -214,27 +188,27 @@ Short descriptive title in Title Case. 2-5 words. |---|---|---|---|---| | `Action` | Required | Required | Required | Required | | `Rule_ID` | Optional | Optional | Required | Required | -| `Phase` | Required | — | Only if changing | — | -| `Category` | — | Required | Only if changing | — | -| `Name` | Required | Required | Only if changing | — | -| `Severity` | Required | Required | Only if changing | — | -| Content sections | All | All | Only changed ones | — | +| `Phase` | Required | - | Only if changing | - | +| `Category` | - | Required | Only if changing | - | +| `Name` | Required | Required | Only if changing | - | +| `Severity` | Required | Required | Only if changing | - | +| Content sections | All | All | Only changed ones | - | -**Update rule:** fill only the fields you want to change — leave everything else blank. +**Update rule:** fill only the fields you want to change - leave everything else blank. --- ## Examples -### Add — Phase Checkpoint rule +### Add - Phase Checkpoint rule ```markdown -## Plugin Rule — Phase Checkpoint +## Plugin Rule - Phase Checkpoint Action: Add Rule_ID: -Phase: Phase 4 — Lifecycle +Phase: Phase 4 - Lifecycle Name: Observer Cleanup In Deinitialize Severity: violation @@ -283,10 +257,10 @@ Skip when: no Register() calls exist in Initialize() --- -### Add — Holistic Rule +### Add - Holistic Rule ```markdown -## Plugin Rule — Holistic +## Plugin Rule - Holistic Action: Add @@ -307,7 +281,7 @@ Is every shared member variable protected by Core::CriticalSection on all access Read the full plugin class declaration to identify member variables. For each variable, trace all access points across Initialize, Deinitialize, JSON-RPC handlers, notification callbacks, and WorkerPool jobs. Verify each access path acquires _adminLock or equivalent. -Do not use pattern-only checks — reason about actual control flow and thread boundaries. +Do not use pattern-only checks - reason about actual control flow and thread boundaries. ### Evidence Requirement Provide exact [File:line] citation for each unprotected shared member access, @@ -316,10 +290,10 @@ including which threads can reach that code path concurrently. --- -### Update — partial (change severity and question only) +### Update - partial (change severity and question only) ```markdown -## Plugin Rule — Phase Checkpoint +## Plugin Rule - Phase Checkpoint Action: Update @@ -336,7 +310,7 @@ Is nullptr used in place of NULL everywhere in the plugin source files? ### Remove ```markdown -## Plugin Rule — Phase Checkpoint +## Plugin Rule - Phase Checkpoint Action: Remove @@ -353,7 +327,7 @@ The manager confirms the rule name and phase before deleting. |---|---|---| | Yes/No Question is open-ended | Cannot produce binary PASS/FAIL | Rewrite as: "Is X present immediately after Y?" | | Verification Steps describe text search | Steps must reason semantically | Write "read and determine" not "search for" | -| Violation Pattern left empty | Citation has no summary | Always fill — under 10 words | +| Violation Pattern left empty | Citation has no summary | Always fill - under 10 words | | `Conditional: Yes` without `Skip when:` | No condition to evaluate | Always complete `Skip when:` | | Wrong `Rule_ID` on Update/Remove | Manager reports "not found" | Verify ID in YAML before submitting | | All fields filled on Update | Unintended overwrites | Fill only changed fields | @@ -369,7 +343,7 @@ PHASE CHECKPOINT (Template A) HOLISTIC RULE (Template B) Action: Add | Update | Remove Action: Add | Update | Remove Rule_ID: e.g. rule_17 Rule_ID: e.g. rule_48 -Phase: Phase 4 — Lifecycle Category: concurrency +Phase: Phase 4 - Lifecycle Category: concurrency Name: Title Case, 2-5 words Name: Title Case, 2-5 words Severity: violation|warning|suggestion Severity: violation|warning|suggestion diff --git a/PluginQualityAdvisor/rules/thunder-interface-rules.yaml b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml index 18fdb6f1..7dd6c894 100644 --- a/PluginQualityAdvisor/rules/thunder-interface-rules.yaml +++ b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml @@ -1,7 +1,8 @@ +version: 3.2.2 title: "Thunder Interface Validation Rules" description: | Rules for validating Thunder COM interface headers in ThunderInterfaces/interfaces/. - Every rule uses semantic reasoning — read the interface header in full and reason + Every rule uses semantic reasoning - read the interface header in full and reason about the code as a human reviewer. Never use regex or text search as the primary detection method. @@ -17,7 +18,7 @@ core_rules: Thunder interface headers must follow a standard structure: - File name should match the interface name (IFoo.h for struct EXTERNAL IFoo), though exceptions may exist - - No implementation code in interface headers — pure declarations only + - No implementation code in interface headers - pure declarations only extraction_logic: | 1. Read the full interface header file 2. Note the file name and the primary interface struct name @@ -25,7 +26,7 @@ core_rules: verification_logic: | 1. Verify the primary interface struct name matches the file name (e.g. IDictionary in IDictionary.h) - Exception: multi-interface files or platform-specific groupings may have different naming - 2. Verify there is no implementation code — only forward declarations, typedefs, struct/enum declarations + 2. Verify there is no implementation code - only forward declarations, typedefs, struct/enum declarations 3. If issues are found → SUGGESTION violation_pattern: "File name does not match interface name, or implementation code present in interface header" fix_template: | @@ -37,11 +38,11 @@ core_rules: }; // Correct: pure declarations only - struct EXTERNAL IDictionary : virtual public Core::/t { + struct EXTERNAL IDictionary : virtual public Core::IUnknown { virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; }; citation: | - ThunderInterfaces/interfaces/IDictionary.h — Exchange namespace and file naming + ThunderInterfaces/interfaces/IDictionary.h - Exchange namespace and file naming - id: "core_2_1" name: "Interface Declaration Shape" @@ -69,6 +70,7 @@ core_rules: class IDictionary : public Core::IUnknown { enum { ID = 0x100 }; // ← raw value, not RPC::ID_* virtual void Get(const string& key) { } // ← not pure virtual + }; // Correct: struct EXTERNAL IDictionary : virtual public Core::IUnknown { @@ -76,7 +78,7 @@ core_rules: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; }; citation: | - ThunderInterfaces/interfaces/IDictionary.h:LINE — struct EXTERNAL shape + ThunderInterfaces/interfaces/IDictionary.h:LINE - struct EXTERNAL shape - id: "core_3_1" name: "Interface ID Registration" @@ -106,7 +108,7 @@ core_rules: verification_logic: | 1. Verify the struct enum { ID = RPC::ID_* } uses a named constant, not a raw number 2. Verify the ID is registered in ThunderInterfaces ids.h (or equivalent) - 3. Verify the ID is unique — no other interface uses the same value + 3. Verify the ID is unique - no other interface uses the same value 4. Nested INotification and ICallback interfaces must also have their own IDs 5. For entservices-apis interfaces: verify the ID falls within the CC range (0xCC00–0xDFFF) 6. If any condition fails → VIOLATION @@ -120,7 +122,7 @@ core_rules: // AND in ids.h: // ID_DICTIONARY = RPC_ID_OFFSET + N, citation: | - ThunderInterfaces/interfaces/ids.h — ID_DICTIONARY registration + ThunderInterfaces/interfaces/ids.h - ID_DICTIONARY registration - id: "core_4_1" name: "Pure Virtual Methods Only" @@ -128,7 +130,7 @@ core_rules: description: | Thunder COM interface methods must be pure virtual (= 0). No default implementations, no inline code, no static methods, no non-virtual methods. - The interface is a pure abstract contract — all implementation is in the + The interface is a pure abstract contract - all implementation is in the implementing class, not in the interface. extraction_logic: | 1. Read all method declarations in the interface struct @@ -151,7 +153,7 @@ core_rules: // Correct: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IDictionary.h — pure virtual methods + ThunderInterfaces/interfaces/IDictionary.h - pure virtual methods - id: "core_5_1" name: "Return Type Conventions" @@ -176,10 +178,10 @@ core_rules: 1. For interfaces tagged with @json (or in Thunder 5.0+ contexts), all methods must return Core::hresult 2. Exception: methods in @event-tagged notification interfaces MUST return void 3. Void return types are not allowed for non-notification JSON-RPC methods - 4. Custom return types (not Core::hresult) for non-status purposes are not allowed — use @out parameters instead - 5. Legacy interfaces (pre-Thunder 5.0) may not comply — flag as warning, not violation + 4. Custom return types (not Core::hresult) for non-status purposes are not allowed - use @out parameters instead + 5. Legacy interfaces (pre-Thunder 5.0) may not comply - flag as warning, not violation 6. If any non-notification JSON-RPC method lacks Core::hresult return type → VIOLATION - violation_pattern: "Interface method does not return Core::hresult — required for @json interfaces in Thunder 5.0+" + violation_pattern: "Interface method does not return Core::hresult - required for @json interfaces in Thunder 5.0+" fix_template: | // WRONG: virtual void SetVolume(const uint8_t volume) = 0; @@ -189,7 +191,7 @@ core_rules: virtual Core::hresult SetVolume(const uint8_t volume) = 0; virtual Core::hresult GetStatus(string& status /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IAudioOutput.h — Core::hresult return types + ThunderInterfaces/interfaces/IAudioOutput.h - Core::hresult return types - id: "core_6_1" name: "Const Correctness" @@ -200,7 +202,7 @@ core_rules: - Output parameters should be non-const references (string& value) - Methods that do not modify the object should be const (though pure virtual const is rare) - @out parameters must be non-const references to allow the implementation to write - - Notification methods (methods in INotification/ICallback interfaces) should NEVER be const — + - Notification methods (methods in INotification/ICallback interfaces) should NEVER be const - the implementation needs to modify state when handling notifications extraction_logic: | 1. Read all method parameter declarations @@ -221,7 +223,7 @@ core_rules: // Correct: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IDictionary.h — const correctness on @out params + ThunderInterfaces/interfaces/IDictionary.h - const correctness on @out params - id: "core_9_1" name: "Thunder Type Conventions" @@ -243,7 +245,7 @@ core_rules: 3. Non-width-specific integer types (int, long) for interface params → check core_15_1 4. BOOL → VIOLATION (use bool) 5. If std::string found → VIOLATION - violation_pattern: "std::string used in interface — must use Thunder string type alias" + violation_pattern: "std::string used in interface - must use Thunder string type alias" fix_template: | // WRONG: virtual Core::hresult Get(const std::string& key, std::string& value) = 0; @@ -251,7 +253,7 @@ core_rules: // Correct: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IDictionary.h — Thunder string type alias + ThunderInterfaces/interfaces/IDictionary.h - Thunder string type alias - id: "core_10_1" name: "Register/Unregister Patterns" @@ -278,14 +280,14 @@ core_rules: virtual Core::hresult Register(INotification* notification) = 0; // Missing: Unregister - // Correct — INotification (1:many): + // Correct - INotification (1:many): virtual Core::hresult Register(INotification* notification) = 0; virtual Core::hresult Unregister(INotification* notification) = 0; - // Correct — ICallback (1:1, nullptr clears): + // Correct - ICallback (1:1, nullptr clears): virtual Core::hresult Callback(ICallback* callback) = 0; citation: | - ThunderInterfaces/interfaces/IDictionary.h — Register/Unregister pattern + ThunderInterfaces/interfaces/IDictionary.h - Register/Unregister pattern - id: "core_11_1" name: "Event Interfaces" @@ -321,7 +323,7 @@ core_rules: virtual void ValueChanged(const string& key, const string& value) = 0; }; citation: | - ThunderInterfaces/interfaces/IDictionary.h — @event tag on INotification + ThunderInterfaces/interfaces/IDictionary.h - @event tag on INotification - id: "core_12_1" name: "@json Tag (CRITICAL)" @@ -332,7 +334,7 @@ core_rules: above the struct declaration as: // @json 1.0.0 No blank lines are allowed between the @json tag and the struct declaration. If an interface intentionally does not need JSON-RPC (pure COM only), the - absence of @json is acceptable — but this must be an intentional design choice. + absence of @json is acceptable - but this must be an intentional design choice. Hints that an interface was meant to be a JSON-RPC interface: - Presence of tags like @alias, @text, @opaque, @bitmask, @index in comments @@ -349,17 +351,17 @@ core_rules: 2. No blank lines between the @json tag and the struct line 3. If the interface is intentionally JSON-RPC-free (pure COM) → acceptable, note as design choice 4. If @json is missing from an interface that should have it → VIOLATION - violation_pattern: "@json tag missing above interface struct declaration — no JSON-RPC code will be generated" + violation_pattern: "@json tag missing above interface struct declaration - no JSON-RPC code will be generated" fix_template: | // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { - // ← @json tag missing — no RPC code will be generated + // ← @json tag missing - no RPC code will be generated // Correct: // @json 1.0.0 struct EXTERNAL IDictionary : virtual public Core::IUnknown { citation: | - ThunderInterfaces/interfaces/IDictionary.h:LINE — @json 1.0.0 above struct + ThunderInterfaces/interfaces/IDictionary.h:LINE - @json 1.0.0 above struct - id: "core_13_1" name: "No IUnknown/IReferenceCounted Methods in Interfaces" @@ -376,7 +378,7 @@ core_rules: 1. The interface must not declare AddRef(), Release(), or QueryInterface() 2. These methods are inherited from Core::IUnknown via the virtual inheritance chain 3. If any IUnknown/IReferenceCounted method appears as an interface method → VIOLATION - violation_pattern: "IUnknown/IReferenceCounted method (AddRef, Release, QueryInterface) declared in interface — must be inherited only" + violation_pattern: "IUnknown/IReferenceCounted method (AddRef, Release, QueryInterface) declared in interface - must be inherited only" fix_template: | // WRONG: struct EXTERNAL IDictionary : virtual public Core::IUnknown { @@ -392,8 +394,8 @@ core_rules: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; }; citation: | - ThunderInterfaces/interfaces/IDictionary.h — IUnknown methods inherited only - ThunderInterfaces/interfaces/IDictionary.h — AddRef/Release inherited from Core::IUnknown + ThunderInterfaces/interfaces/IDictionary.h - IUnknown methods inherited only + ThunderInterfaces/interfaces/IDictionary.h - AddRef/Release inherited from Core::IUnknown - id: "core_14_1" name: "No std::map in Interfaces" @@ -410,7 +412,7 @@ core_rules: 1. Any std::map or similar associative container in method parameters → VIOLATION 2. Consider whether a Core::JSON::Container or repeated method calls can replace it 3. If std::map found in interface → VIOLATION - violation_pattern: "std::map used in interface parameter — not serialisable across process boundaries" + violation_pattern: "std::map used in interface parameter - not serialisable across process boundaries" fix_template: | // WRONG: virtual Core::hresult GetAll(std::map& values /* @out */) = 0; @@ -419,7 +421,7 @@ core_rules: virtual Core::hresult GetKeys(RPC::IStringIterator*& keys /* @out */) = 0; virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IDictionary.h — no std::map in interfaces + ThunderInterfaces/interfaces/IDictionary.h - no std::map in interfaces - id: "core_15_1" name: "Explicit Integer Widths" @@ -437,7 +439,7 @@ core_rules: 2. char is acceptable for character data; bool is acceptable 3. uint8_t, uint16_t, uint32_t, uint64_t, int32_t etc. are correct 4. If platform-dependent integer type found → VIOLATION - violation_pattern: "Platform-dependent integer type (int, long, short) used in interface — must use explicit-width types (uint32_t, etc.)" + violation_pattern: "Platform-dependent integer type (int, long, short) used in interface - must use explicit-width types (uint32_t, etc.)" fix_template: | // WRONG: virtual Core::hresult SetTimeout(const int timeout) = 0; @@ -447,7 +449,7 @@ core_rules: virtual Core::hresult SetTimeout(const uint32_t timeout) = 0; virtual Core::hresult GetSize(uint32_t& size /* @out */) = 0; citation: | - ThunderInterfaces/interfaces/IVolume.h — explicit integer widths + ThunderInterfaces/interfaces/IVolume.h - explicit integer widths - id: "core_16_1" name: "@restrict Mandatory with std::vector" @@ -467,7 +469,7 @@ core_rules: 2. N must be a positive integer representing the maximum element count 3. Missing @restrict on std::vector → VIOLATION 4. @restrict on non-vector types is not enforced by this rule - violation_pattern: "std::vector parameter missing @restrict annotation — required for safe bounds checking" + violation_pattern: "std::vector parameter missing @restrict annotation - required for safe bounds checking" fix_template: | // WRONG: virtual Core::hresult GetItems(std::vector& items /* @out */) = 0; @@ -475,13 +477,13 @@ core_rules: // Correct: virtual Core::hresult GetItems(std::vector& items /* @out @restrict:256 */) = 0; citation: | - ThunderInterfaces/interfaces/IPackager.h — @restrict on std::vector + ThunderInterfaces/interfaces/IPackager.h - @restrict on std::vector - id: "core_17_1" name: "No Method Overloads in @json Interfaces" severity: "violation" description: | - In interfaces tagged with @json, method names must be unique — no C++ overloads + In interfaces tagged with @json, method names must be unique - no C++ overloads are allowed because JSON-RPC dispatches by method name string only (no signature overload resolution). Methods that would be overloads in C++ will produce duplicate JSON-RPC method names, causing code generation failures or undefined dispatch behavior. @@ -499,27 +501,27 @@ core_rules: 2. Two methods with the same C++ name (overloads) → VIOLATION 3. Two methods with different C++ names but the same @text value → VIOLATION 4. If any duplicates are found → VIOLATION - violation_pattern: "Duplicate JSON-RPC method name in @json interface — overloads not allowed" + violation_pattern: "Duplicate JSON-RPC method name in @json interface - overloads not allowed" fix_template: | - // WRONG — overloaded methods produce duplicate JSON-RPC names: + // WRONG - overloaded methods produce duplicate JSON-RPC names: // @json 1.0.0 struct EXTERNAL IDictionary : virtual public Core::IUnknown { virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; virtual Core::hresult Get(const uint32_t index, string& value /* @out */) = 0; // ← overload! }; - // Correct — use distinct names: + // Correct - use distinct names: // @json 1.0.0 struct EXTERNAL IDictionary : virtual public Core::IUnknown { virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; virtual Core::hresult GetByIndex(const uint32_t index, string& value /* @out */) = 0; }; - // Also WRONG — @text collision: + // Also WRONG - @text collision: virtual Core::hresult GetValue(const string& key, string& value /* @out */) = 0; // @text get virtual Core::hresult GetStatus(string& status /* @out */) = 0; // @text get ← collision! citation: | - ThunderInterfaces/interfaces/ — no overloads in @json interfaces + ThunderInterfaces/interfaces/ - no overloads in @json interfaces - id: "core_18_1" name: "No Reserved JSON-RPC Method Names" @@ -542,21 +544,21 @@ core_rules: 2. Reserved names: "version", "versions", "exists" 3. Check both the bare method name and any @text annotation 4. If collision found → VIOLATION - violation_pattern: "Interface method uses a reserved JSON-RPC name (version/versions/exists) — conflicts with built-in framework handlers" + violation_pattern: "Interface method uses a reserved JSON-RPC name (version/versions/exists) - conflicts with built-in framework handlers" fix_template: | - // WRONG — collides with built-in JSON-RPC 'exists' method: + // WRONG - collides with built-in JSON-RPC 'exists' method: virtual Core::hresult Exists(const string& key, bool& result /* @out */) = 0; - // Correct — use a non-reserved name: + // Correct - use a non-reserved name: virtual Core::hresult Contains(const string& key, bool& result /* @out */) = 0; - // WRONG — collides with built-in 'version': + // WRONG - collides with built-in 'version': virtual Core::hresult Version(string& version /* @out */) = 0; // Correct: virtual Core::hresult GetVersion(string& version /* @out */) = 0; citation: | - Thunder JSON-RPC framework — reserved method names + Thunder JSON-RPC framework - reserved method names # =========================================================================================== # ADVISORY RULES (3) @@ -580,7 +582,7 @@ advisory_rules: 2. If methods clearly belong to two or more distinct responsibilities → VIOLATION 3. Minor convenience methods on an otherwise focused interface are acceptable 4. Apply judgment: is the mixing of concerns gratuitous or is there a clear design reason? - violation_pattern: "Interface mixes multiple unrelated responsibilities — consider splitting into focused interfaces" + violation_pattern: "Interface mixes multiple unrelated responsibilities - consider splitting into focused interfaces" fix_template: | // WRONG: IDictionaryAndNetwork mixes dictionary and network concerns struct EXTERNAL IDictionaryAndNetwork : virtual public Core::IUnknown { @@ -590,7 +592,7 @@ advisory_rules: // Correct: split into IDictionary and INetwork citation: | - ThunderInterfaces/interfaces/IHdmiCecSink.h — single responsibility + ThunderInterfaces/interfaces/IHdmiCecSink.h - single responsibility - id: "advisory_m2_1" name: "Enum Underlying Types" @@ -599,7 +601,7 @@ advisory_rules: Enums used in interface parameters or return types should use explicit underlying types (: uint8_t, : uint32_t) for ABI stability. Exception: the anonymous ID enum inside the interface struct (enum { ID = RPC::ID_* }) - must NOT have an explicit underlying type — this is by Thunder convention. + must NOT have an explicit underlying type - this is by Thunder convention. Only named enums that are used as parameter types need explicit underlying types. extraction_logic: | 1. Read all enum declarations in the interface header @@ -607,11 +609,11 @@ advisory_rules: 3. Identify the anonymous ID enum { ID = RPC::ID_* } 4. Check for explicit underlying types on named enums verification_logic: | - 1. Anonymous ID enum (enum { ID = RPC::ID_* }) must NOT have explicit type — skip it + 1. Anonymous ID enum (enum { ID = RPC::ID_* }) must NOT have explicit type - skip it 2. Named enums used as parameter types should have explicit underlying types 3. If a named enum used as a parameter lacks an explicit underlying type → WARNING 4. Apply judgment: if the enum range clearly fits a known type, it is advisable - violation_pattern: "Named enum used in interface parameter lacks explicit underlying type — consider adding : uint8_t or : uint32_t" + violation_pattern: "Named enum used in interface parameter lacks explicit underlying type - consider adding : uint8_t or : uint32_t" fix_template: | // WRONG: (named enum without explicit type) enum State { IDLE, ACTIVE, ERROR }; @@ -624,7 +626,7 @@ advisory_rules: // The ID enum stays anonymous (by convention): enum { ID = RPC::ID_MY_INTERFACE }; citation: | - ThunderInterfaces/interfaces/IAVInput.h — explicit enum underlying type + ThunderInterfaces/interfaces/IAVInput.h - explicit enum underlying type - id: "advisory_m3_1" name: "No Exceptions" @@ -633,7 +635,7 @@ advisory_rules: Thunder COM interfaces and their implementations must not use C++ exceptions. Exceptions cannot cross COM/RPC process boundaries safely. All error conditions must be reported via Core::hresult return values. - Exception specifications (throw(...), noexcept) are irrelevant — the real + Exception specifications (throw(...), noexcept) are irrelevant - the real issue is that throw statements must not appear in COM implementation code. extraction_logic: | 1. Read all interface method signatures and any associated implementation hints @@ -644,7 +646,7 @@ advisory_rules: 2. noexcept specifications are acceptable (they prevent exceptions from propagating) 3. In implementation code: no throw statements in COM method implementations 4. If throw appears in COM code → VIOLATION - violation_pattern: "Exception specification or throw statement in COM interface or implementation — use Core::hresult for error reporting" + violation_pattern: "Exception specification or throw statement in COM interface or implementation - use Core::hresult for error reporting" fix_template: | // WRONG: virtual Core::hresult Get(const string& key, string& value) throw(std::exception) = 0; @@ -653,4 +655,4 @@ advisory_rules: virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; // Implementation: return Core::ERROR_NOT_FOUND; instead of throwing citation: | - ThunderInterfaces/interfaces/IDictionary.h — no exceptions in COM interfaces + ThunderInterfaces/interfaces/IDictionary.h - no exceptions in COM interfaces From 4dbaa88947a4eeb17f4d11886de4dbf8f5aee179 Mon Sep 17 00:00:00 2001 From: workkavint-ship-it Date: Wed, 15 Jul 2026 15:30:52 +0530 Subject: [PATCH 15/15] Update openspec config --- .github/openspec/config.yaml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/openspec/config.yaml b/.github/openspec/config.yaml index 392946c6..2029f010 100644 --- a/.github/openspec/config.yaml +++ b/.github/openspec/config.yaml @@ -1,20 +1,2 @@ schema: spec-driven -# Project context (optional) -# This is shown to AI when creating artifacts. -# Add your tech stack, conventions, style guides, domain knowledge, etc. -# Example: -# context: | -# Tech stack: TypeScript, React, Node.js -# We use conventional commits -# Domain: e-commerce platform - -# Per-artifact rules (optional) -# Add custom rules for specific artifacts. -# Example: -# rules: -# proposal: -# - Keep proposals under 500 words -# - Always include a "Non-goals" section -# tasks: -# - Break tasks into chunks of max 2 hours