diff --git a/.github/openspec/changes/thunder-plugin-qa/design.md b/.github/openspec/changes/thunder-plugin-qa/design.md new file mode 100644 index 00000000..0b9c5ae3 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/design.md @@ -0,0 +1,275 @@ +# 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 ← 84 unified rules (rule_01 to rule_84) + │ + ▼ +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/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 + +**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 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: + +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** (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): +- 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 Plugin Rule Structure + +Each rule in `thunder-plugin-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. 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) + 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:** +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 + +**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 + +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/PluginQualityAdvisor/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.3.0 + - 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 + rule_33 (connectionId guard), + rule_35 (JSON::Container configuration), rule_36 (no hardcoded numeric tuning parameters) + +- `thunder-interface-rules.yaml`: version 3.2.2 + - 16 core rules + 3 advisory = 17 total + - Core::hresult MANDATORY for Thunder 5.0+ @json interfaces + - 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 new file mode 100644 index 00000000..5bb58c94 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/proposal.md @@ -0,0 +1,104 @@ +# Proposal: Thunder PluginQualityAdvisor 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`)** +- 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 +- 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: 16 core + 3 advisory +- Validates Thunder COM interfaces in ThunderInterfaces/interfaces/ +- Covers: file structure, @json tag, @restrict on vectors, return types, ID registration, + event interfaces, 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` — 84 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 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 +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/PluginQualityAdvisor/ +├── 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 +``` \ No newline at end of file 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 new file mode 100644 index 00000000..02e457ed --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/Thunder-interface-rules.md @@ -0,0 +1,928 @@ +# Thunder Interface Validation Rules + +**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. + +--- + +## 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 Structure + +**Severity:** suggestion + +**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. 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 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 Example:** + +```cpp +// 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::IUnknown { + virtual Core::hresult Get(const string& key, string& value /* @out */) = 0; +}; +``` + +**Citation:** ThunderInterfaces/interfaces/IDictionary.h — Exchange namespace and file naming + +--- + +### core_2_1 — Interface Declaration Shape + +**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 +- 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' +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:LINE — 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. + +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. 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 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:** 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. + +**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. + +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. 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. 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 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 +- 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 +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_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 + +**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(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 — Event Interfaces + +**Severity:** violation + +**Description:** + +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 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 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 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:** 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 +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. + +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 +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 — No IUnknown/IReferenceCounted Methods in Interfaces + +**Severity:** violation + +**Description:** + +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(), Release(), or QueryInterface() declarations + +**Verification Logic:** + +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 void* QueryInterface(uint32_t) = 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 — IUnknown methods inherited only +ThunderInterfaces/interfaces/IDictionary.h — AddRef/Release inherited from Core::IUnknown + +--- + +### 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. + +**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_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. + +**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_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. +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 + +**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 not enforced by this rule + +**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 + +--- + +### core_17_1 — 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 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:** + +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 +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 + +--- \ 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 new file mode 100644 index 00000000..26cbb5d8 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/specs/interface/spec.md @@ -0,0 +1,247 @@ +# Delta for Interface Validation and Plugin Generation + +## ADDED Requirements + +--- + +### 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 (16 core + 3 advisory). + +#### Scenario: Interface with critical violations +- GIVEN a Thunder interface file with a missing `@json` tag +- 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 + +#### Scenario: Interface with vector without @restrict +- GIVEN an interface method with a `std::vector` parameter lacking `@restrict` +- WHEN validation runs checkpoint core_16_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_18_1 (16 core), then advisory_m1_1 through advisory_m3_1 (3 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 +- 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, 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 (warning) +- advisory_m2_1: Enum underlying types — exclude anonymous ID enum (warning) +- advisory_m3_1: No C++ exceptions (violation) + +--- + +### 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 script registers prompts with VS Code +The `setup-prompts.py` script MUST modify VS Code settings.json to add +`chat.promptFilesLocations` pointing to `ThunderTools/PluginQualityAdvisor/Prompts`. + +#### 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, 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) +- WHEN they open `ThunderTools/PluginQualityAdvisor/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-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`) +- 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_80" + 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/.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 new file mode 100644 index 00000000..cd6d82dc --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/Thunder-plugin-rules.md @@ -0,0 +1,2323 @@ +# Thunder Plugin Rules — v3.4.0 + +### 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\() 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 | 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) | 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_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 | +| [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 | +| [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 | + +--- + +## 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 — NULL vs 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 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 missing, duplicated, or OOP part missing `SERVICE_REGISTRATION`. + +```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` 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` or a derived class (e.g. `PluginHost::JSONRPCSupportsStatusListener`). + +```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\() 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. + +**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** | `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. + +**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 + +**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_32 + +**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_33 + +**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_34 + +**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_35 + +**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_36 + +**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. + +**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_37 + +**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_38 + +**COM Methods Return Core::hresult** | `violation` + +**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. + +**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. + +> **YAML source:** `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` → `general_rules` section + +--- + +### rule_39 + +**#pragma once** | `suggestion` | Category: Conventions + +**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` + +--- + +### rule_40 + +**Apache 2.0 Copyright Header** | `suggestion` | Category: Conventions + +**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` + +--- + +### rule_41 + +**Reverse-Order Cleanup** | `suggestion` | Category: Lifecycle Integrity + +**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` + +--- + +### rule_42 + +**Observer Locking** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_43 + +**AddRef/Release Balance** | `violation` | Category: COM Safety + +**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()` + +--- + +### rule_44 + +**CMake NAMESPACE Variable** | `suggestion` | Category: 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/thunder/plugins) + +# Correct: +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${NAMESPACE}/plugins) +``` + +**Citation format:** `[CMakeLists.txt:LINE] Hardcoded namespace — use ${NAMESPACE}` + +--- + +### rule_45 + +**Handlers Must Not Block** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_46 + +**No Activate/Deactivate from Handlers** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_47 + +**Shared State Protected by CriticalSection** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_48 + +**No Lock Held During Framework Callbacks** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_49 + +**Worker Jobs Safe After Deinitialize** | `warning` | Category: Concurrency + +**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. + +**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` + +--- + +### 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. 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. + +**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` + +--- + + + +```cpp + +``` + + + +--- + +### rule_51 + +**Config Errors Return Non-Empty from Initialize** | `violation` | Category: Lifecycle Integrity + +**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` + +--- + +### rule_52 + +**interface->Register/Unregister Pairing** | `violation` | Category: JSON-RPC Compliance + +**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_53 + +**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 +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Handler unregistered after interface release — wrong order` + +--- + +### rule_54 + +**Use Core::ERROR_\* for Handler Failure Codes** | `violation` | Category: JSON-RPC Compliance + +**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` + +--- + + + +```cpp + +``` + + + +--- + +### 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). 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. + +**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"); +// 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_56 + +**COM Reference Counting Correctness** | `violation` | Category: COM Safety + +**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` + +--- + +### rule_57 + +**No Hard Inter-Plugin Dependencies** | `warning` | Category: Inter-Plugin Design + +**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` + +--- + +### rule_58 + +**JSON-RPC Handlers Are Re-entrant Safe** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_59 + +**IPlugin::INotification Callbacks Must Not Block** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_60 + +**Lock Scope Minimized** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_61 + +**Plugin Threads Joined in Deinitialize** | `violation` | Category: Concurrency + +**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` + +--- + +### rule_62 + +**Deinitialize Pointer Safety** | `violation` | Category: Lifecycle & State Integrity + +**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:** Deinitialize() for pointer clearing, and all background tasks/timers/callbacks that use framework pointers. + +**Violation pattern:** Pointer released but not set to nullptr, or background task accesses framework pointer without null check. + +```cpp +// WRONG — pointer not cleared: +void Deinitialize(PluginHost::IShell* service) override { + _service->Release(); // released but not nulled — double-Deinitialize = double-release +} + +// Correct — pointer cleared after release: +void Deinitialize(PluginHost::IShell* service) override { + _service->Release(); + _service = nullptr; // safe against double-Deinitialize +} +``` + +**Citation format:** `[PluginName.cpp:LINE] Pointer released but not set to nullptr in Deinitialize()` + +--- + + + +```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()` + +--- + +### rule_63 + +**hresult Return Values Checked** | `violation` | Category: COM Safety + +**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` + +--- + +### rule_64 + +**ASSERT Only for Programmer Invariants** | `warning` | Category: Code Quality + +**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_42 — this rule targets holistic patterns across the full plugin.)* + +**Citation format:** `[PluginName.cpp:LINE] ASSERT on runtime condition` + +--- + +### rule_65 + +**Security: Logging, Shell, Path, and Error Exposure** | `violation` | Category: Code Quality + +**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` + +--- + + + +```cpp + +``` + + + +--- + +### rule_66 + +**Config Completeness and Resource Cleanup** | `warning` | Category: Code Quality + +**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()` + +--- + + + +```cpp + +``` + + + +--- + +### rule_67 + +**Observer Classes Private and Nested** | `suggestion` | Category: Conventions + +**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` + +--- + +### rule_68 + +**No Deprecated JSON-RPC APIs** | `violation` | Category: JSON-RPC Compliance + +**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` + +--- + + + +```cpp + +``` + + + +--- + +### 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. + +**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` + +--- + +### 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. + +**Citation format:** `[PluginName.cpp:LINE] printf/cout used — use Thunder TRACE/SYSLOG` + +--- + + +--- + +## 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 new file mode 100644 index 00000000..f29878a9 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/specs/plugin/spec.md @@ -0,0 +1,600 @@ +# Delta for Plugin Validation + +## ADDED Requirements + +--- + +### 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 PluginQualityAdvisor system is delivered +- THEN the following structure MUST exist inside `ThunderTools/`: + +``` +ThunderTools/PluginQualityAdvisor/ +├── 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/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/PluginQualityAdvisor/Prompts/` containing the three `.prompt.md` files +- WHEN `setup-prompts.py` is run +- 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) + +--- + +### 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 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) +- WHEN `setup-prompts.py` completes successfully +- THEN `settings.json` MUST contain the following merged entry: + +```json +{ + "chat.promptFilesLocations": { + "/full/path/to/ThunderTools/PluginQualityAdvisor/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 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 + +#### 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 PluginQualityAdvisor/rules/ +The file `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` MUST exist +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: 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) + +#### 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), + `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 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)`, + `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 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 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 +- 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`, + 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 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 +- 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 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` +- 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 3 advisory rules present with required fields +- GIVEN the `advisory_rules` list +- THEN it MUST contain exactly these IDs: + `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 + +--- + + +### 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 84 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 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 +- 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: 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 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 + 46 holistic rules = 84 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`) +- 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_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_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) + +#### 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_32 runs +- THEN it checks `service->RemoteConnection()` and `connection->Terminate()` and + `connection->Release()` all present in Deinitialize() +- 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_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_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_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; + 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_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: 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 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 + `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 (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) + 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 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) + 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) + 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: + +~~~~text +### {FileName} — N issue(s) + +~~~yaml +rule_id: +status: +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/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md new file mode 100644 index 00000000..40637c95 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/specs/reports/spec.md @@ -0,0 +1,134 @@ +# Spec: Thunder PluginQualityAdvisor 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 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) +- 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 + +**Scenario:** `/thunder-interface-review` completes all 19 rules +- The system MUST generate a CSV file at: + `ThunderTools/PluginQualityAdvisor/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/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 + +After saving the CSV, the prompt MUST display in chat: + +``` +📊 Report saved: + 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\PluginQualityAdvisor\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/PluginQualityAdvisor/ +└── 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/.github/openspec/changes/thunder-plugin-qa/tasks.md b/.github/openspec/changes/thunder-plugin-qa/tasks.md new file mode 100644 index 00000000..2eeacdb6 --- /dev/null +++ b/.github/openspec/changes/thunder-plugin-qa/tasks.md @@ -0,0 +1,443 @@ +# Tasks: Thunder Plugin QA System + +## Phase 1: YAML rule definitions + +- [x] 1.1 Create `ThunderTools/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` (v3.3.0) + - metadata block: version, description, approach, + 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 + 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 (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 + - 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_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. + 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() 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" + 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): "No Hardcoded Paths" + - phase_5C_checkpoints (2): + rule_32 (violation conditional): OOP connection termination in Deinitialize + rule_33 (violation conditional): connectionId checked first in IRemoteConnection callbacks + - phase_6_checkpoints (3): + 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_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/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 (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 ("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) + - 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/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` + Frontmatter: title: "Thunder Plugin Rule Review", description (mention 84 unified rules, semantic review) + Sections (in order): + - 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} + - 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/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml — + 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)), + 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 + - 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): + 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() 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" + 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): "No Hardcoded Paths" + Phase 5C Out-of-Process (2 conditional): + rule_32: "OOP Connection Termination in Deinitialize" + rule_33: "connectionId Checked in IRemoteConnection Callbacks" + Phase 6 Configuration (3): + 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_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 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), + 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 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/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 (16 core + 3 advisory) as a markdown table + with ID, Rule name (Title Case matching YAML), Key Point + - 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) + - 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.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): + - 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.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): + - 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.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: + - 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/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/PluginQualityAdvisor/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/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 (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/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, + 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/PluginQualityAdvisor/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/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/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md` + - Appended after Command Examples section + - 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 + - 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 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 + +- [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/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 + - 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/.github/openspec/config.yaml b/.github/openspec/config.yaml new file mode 100644 index 00000000..2029f010 --- /dev/null +++ b/.github/openspec/config.yaml @@ -0,0 +1,2 @@ +schema: spec-driven + 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..29d6d2f0 --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md @@ -0,0 +1,212 @@ +--- +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. + +Rules are loaded at runtime from: `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` + +--- + +## Quick Reference - All 19 Rules + +| ID | Rule | Key Point | +|----|------|-----------| +| 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 (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 | 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 | + +--- + +> **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** `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 + +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 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:** `PluginQualityAdvisor/Reports/interface/{InterfaceName}_{YYYY-MM-DD}.csv` + +- Create `PluginQualityAdvisor/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: + 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 "PluginQualityAdvisor\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_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. +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_16_1, +advisory_m1_1, advisory_m2_1, advisory_m3_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..d7641ba3 --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-interface-rule-manager.prompt.md @@ -0,0 +1,204 @@ +--- +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 `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` and keeps all related files in sync atomically: + +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 + +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 (Verification Logic) +1. +2. + +## Violation Pattern + + +## Fix +// WRONG: + + +// Correct: + + +## Example Citation + +``` + +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** 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 + 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. \ No newline at end of file diff --git a/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md new file mode 100644 index 00000000..53eb354a --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-review.prompt.md @@ -0,0 +1,305 @@ +--- +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. + +**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. + +--- + +## 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. + +**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` + +**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 `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 + +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) + +**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 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` + +**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." +``` + +```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 + +### Issue Report - Grouped by File + +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 84 rules): + +```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: | + [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 + +**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 84 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 (84 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: 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 + +``` +/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:** `PluginQualityAdvisor/Reports/plugin/{PluginName}_{YYYY-MM-DD}.csv` + +- 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):** + +| 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: + 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 "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 new file mode 100644 index 00000000..da383d58 --- /dev/null +++ b/PluginQualityAdvisor/Prompts/thunder-plugin-rule-manager.prompt.md @@ -0,0 +1,274 @@ +--- +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 `PluginQualityAdvisor/rules/thunder-plugin-rules.yaml` and keeps all related files in sync atomically: + +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 + +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 template classification 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 (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 + +### Branch: REMOVE + +Ask for the rule ID (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: +[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_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") + +- **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] - [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. \ No newline at end of file diff --git a/PluginQualityAdvisor/README.md b/PluginQualityAdvisor/README.md new file mode 100644 index 00000000..74fe2392 --- /dev/null +++ b/PluginQualityAdvisor/README.md @@ -0,0 +1,178 @@ +# 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: + + **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 + { + "chat.promptFilesLocations": { + "/full/path/to/ThunderTools/PluginQualityAdvisor/Prompts": true + } + } + ``` + + The path must be the **absolute path** to the `Prompts` folder. + + **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` + +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 `Update-Template-Guide/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 `Update-Template-Guide/interface-rule-template-guide.md` + +Updates `thunder-interface-rules.yaml` and `thunder-interface-review.prompt.md` atomically. + +--- + +## Project Structure + +``` +ThunderTools/PluginQualityAdvisor/ ++-- README.md ++-- setup-prompts.py ++-- Update-Template-Guide/ +| +-- plugin-rule-template-guide.md +| +-- interface-rule-template-guide.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..5ba008d9 --- /dev/null +++ b/PluginQualityAdvisor/Update-Template-Guide/interface-rule-template-guide.md @@ -0,0 +1,341 @@ +# 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: + +- `PluginQualityAdvisor/rules/thunder-interface-rules.yaml` +- `PluginQualityAdvisor/Prompts/thunder-interface-review.prompt.md` +- `.github/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 | + +```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_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 + +--- + +### `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: Core rules use mixed severities (suggestion, warning, or violation). Advisory rules also 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) +``` \ 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 new file mode 100644 index 00000000..2b853a23 --- /dev/null +++ b/PluginQualityAdvisor/Update-Template-Guide/plugin-rule-template-guide.md @@ -0,0 +1,357 @@ +# 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: + +- `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` + +--- + +## 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 | +|---|---|---| + +```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_39 to rule_84). + +### 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_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_38: Phase checkpoints +- rule_39 to rule_84: 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_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) +``` + +--- + +### `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_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 + +### 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..7dd6c894 --- /dev/null +++ b/PluginQualityAdvisor/rules/thunder-interface-rules.yaml @@ -0,0 +1,658 @@ +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 + about the code as a human reviewer. Never use regex or text search as the primary + detection method. + +# =========================================================================================== +# CORE RULES (16) +# =========================================================================================== + +core_rules: + - id: "core_1_1" + name: "File Structure" + severity: "suggestion" + 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. 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 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: 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::IUnknown { + 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: "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 + - 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' + 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. + + 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. 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: + 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: "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. + 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. + + 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. 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. 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: + 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 + - 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 + 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_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" + 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(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: "Event Interfaces" + severity: "violation" + description: | + 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 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 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 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: "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 + 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. + + 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 + 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: "No IUnknown/IReferenceCounted Methods in Interfaces" + severity: "violation" + description: | + 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(), Release(), or QueryInterface() declarations + verification_logic: | + 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_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 void* QueryInterface(uint32_t) = 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 - IUnknown methods inherited only + ThunderInterfaces/interfaces/IDictionary.h - AddRef/Release inherited from Core::IUnknown + + - id: "core_14_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_15_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_16_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. + 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 not enforced by this rule + 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 + + - 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 (3) +# =========================================================================================== + +advisory_rules: + - id: "advisory_m1_1" + name: "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 + 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 diff --git a/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml new file mode 100644 index 00000000..4c0e3c5f --- /dev/null +++ b/PluginQualityAdvisor/rules/thunder-plugin-rules.yaml @@ -0,0 +1,2077 @@ +# =========================================================================================== +# Thunder Plugin Rules +# =========================================================================================== + +metadata: + 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: 84 + 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_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,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,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 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" + - "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" + +# =========================================================================================== +# PHASE CHECKPOINTS (38 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 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" + + 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 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 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 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, duplicated, or OOP part missing SERVICE_REGISTRATION" + + fix_template: | + // WRONG: (missing metadata registration or duplicated) + + // Correct — exactly ONE in PluginName.cpp: + static Plugin::Metadata metadata( + Plugin::Information::Versions, + Plugin::Information::Instances, + Plugin::Information::Prefix, + Plugin::Information::Interfaces, + Plugin::Information::Configuration, + 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 issue" + 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 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 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 or a derived class" + + fix_template: | + // WRONG: + class Dictionary : public PluginHost::IPlugin { + // Missing PluginHost::JSONRPC inheritance + + // 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" + +# ------------------------------------------------------------------------------------------- +# 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() nullptr 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: "suggestion" + 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: "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_31" + +# ------------------------------------------------------------------------------------------- +# Phase 5C: Out-of-Process (OOP) +# ------------------------------------------------------------------------------------------- + +phase_5C_checkpoints: + - rule_id: "rule_32" + 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_32" + + - rule_id: "rule_33" + 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_33" + +# ------------------------------------------------------------------------------------------- +# Phase 6: Configuration +# ------------------------------------------------------------------------------------------- + +phase_6_checkpoints: + - rule_id: "rule_34" + 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_34" + + - rule_id: "rule_35" + 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_35" + + - rule_id: "rule_36" + name: "No Hardcoded Numeric Tuning Parameters" + severity: "suggestion" + 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. 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 + + 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_36" + +# ------------------------------------------------------------------------------------------- +# Phase 7: CMake +# ------------------------------------------------------------------------------------------- + +phase_7_checkpoints: + - rule_id: "rule_37" + 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_37" + +# ------------------------------------------------------------------------------------------- +# Phase 8: COM Interface Rules +# ------------------------------------------------------------------------------------------- + +phase_8_checkpoints: + - rule_id: "rule_38" + 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. 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 + + 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_38" + +# =========================================================================================== +# 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 +# =========================================================================================== + +general_rules: + - rule_id: "rule_39" + 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_40" + 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_41" + 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_42" + 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_43" + 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_44" + 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_45" + 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_46" + 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_47" + 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_48" + 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_49" + name: "Worker Jobs Safe After Deinitialize" + severity: "warning" + category: "concurrency" + 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_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? 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_51" + 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_52" + 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_53" + 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_54" + 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_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? 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_56" + 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_57" + 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_58" + 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_59" + 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_60" + 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_61" + 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_62" + name: "Deinitialize Pointer Safety" + severity: "violation" + category: "lifecycle_integrity" + 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_63" + 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_64" + 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_65" + 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_66" + 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_67" + 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_68" + 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_69" + name: "INTERFACE_MAP Mandatory" + severity: "violation" + 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." + + - 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." diff --git a/PluginQualityAdvisor/setup-prompts.py b/PluginQualityAdvisor/setup-prompts.py new file mode 100644 index 00000000..ac3d64a6 --- /dev/null +++ b/PluginQualityAdvisor/setup-prompts.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +setup-prompts.py — Registers ThunderTools PluginQualityAdvisor prompts with VS Code. + +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. +Safe to run multiple times (idempotent). Creates a timestamped backup. +""" + +import json +import os +import platform +import shutil +import sys +from datetime import datetime +from pathlib import Path + +# 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(): + """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(f"Already configured — '{PROMPTS_KEY}' 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 + 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(" /thunder-plugin-rule-manager") + print(" /thunder-interface-rule-manager") + print() + + +if __name__ == "__main__": + main()