Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "scopy-plugins",
"owner": { "name": "Analog Devices Inc." },
"metadata": {
"description": "Scopy internal Claude Code plugins"
},
"plugins": [
{
"name": "scopy_dev_plugin",
"source": "./tools/scopy_dev_plugin",
"description": "Scopy development tools: code generation, documentation, testing, quality checks, and styling for IIO plugin development."
}
]
}
6 changes: 6 additions & 0 deletions tools/scopy_dev_plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "scopy_dev_plugin",
"version": "1.0.0",
"description": "Scopy development tools: code generation, documentation, testing, quality checks, and styling for IIO plugin development.",
"author": { "name": "Muthi Ionut Adrian" }
}
64 changes: 64 additions & 0 deletions tools/scopy_dev_plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# scopy_dev_plugin

Claude Code plugin for Scopy development. Provides slash commands, knowledge skills, and hooks for building IIO device plugins.

The plugin lives inside the repository at `tools/scopy_dev_plugin/`. It is distributed via the local marketplace manifest at `.claude-plugin/marketplace.json`.

## Setup (per developer)

Run these three commands inside Claude Code from the repo root:

```
/plugin marketplace add ./
/plugin install scopy_dev_plugin@scopy-plugins --scope local
/reload-plugins
```

1. `/plugin marketplace add ./` — registers the local marketplace (reads `.claude-plugin/marketplace.json`)
2. `/plugin install scopy_dev_plugin@scopy-plugins --scope local` — installs the plugin into your local settings
3. Restart Claude Code

> `settings.local.json` is gitignored — each developer runs the install once. Do not commit it.

## Requirements

- `clang-format` — required by the formatting hook (C++/header files)
- `cmake-format` — required by the formatting hook (CMake files)

Both must be on your `PATH`. On Ubuntu: `sudo apt install clang-format` and `pip install cmake-format`.

## Slash Commands

| Command | Description |
|---------|-------------|
| `/scopy_dev_plugin:create-api <plugin>` | Generate JavaScript API class for test automation |
| `/scopy_dev_plugin:create-docs <plugin>` | Generate RST plugin user-guide documentation |
| `/scopy_dev_plugin:create-test-docs <plugin>` | Generate RST test case documentation |
| `/scopy_dev_plugin:create-automated-tests <plugin>` | Create JS automated test scripts |
| `/scopy_dev_plugin:create-whatsnew <plugin> <version>` | Generate What's New HTML page |
| `/scopy_dev_plugin:check-code-quality <package>` | Static code quality analysis |
| `/scopy_dev_plugin:verify-package <package>` | CI pre-flight validation (format + license) |
| `/scopy_dev_plugin:validate-api <plugin>` | Validate API class implementation (checks A1–A7) |
| `/scopy_dev_plugin:validate-automated-tests <plugin>` | Validate JS automated test scripts (checks T1–T7) |

## Knowledge Skills (auto-load)

These skills are loaded automatically when relevant context is detected:

- **iiowidget-patterns** — How to create IIOWidgets using IIOWidgetBuilder
- **scopy-style-rules** — Theme system, Style::setStyle, background colors
- **scopy-plugin-patterns** — Plugin lifecycle, ToolTemplate, refresh buttons
- **scopy-doc-format** — RST documentation conventions
- **scopy-test-format** — Test case UID and RBP conventions
- **scopy-api-patterns** — API class structure and Q_INVOKABLE patterns

## Hooks

Hooks run automatically after file edits — no manual invocation needed.

- **scopy-format.sh** — Formats C++ and CMake files after any Edit/Write/MultiEdit
- **scopy-license.sh** — Adds GPL license header to newly created source files

### Hook deduplication

If you have the same hooks configured globally in `~/.claude/settings.local.json`, they will fire twice. Remove the duplicates from your global settings to avoid this.
121 changes: 121 additions & 0 deletions tools/scopy_dev_plugin/commands/check-code-quality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Code Quality Analysis for Scopy Package

You are performing static code quality analysis on the Scopy package: `$ARGUMENTS`

## Step 1: Discover Source Files

Use the Glob tool to find all `.h`, `.hpp`, and `.cpp` files in `scopy/packages/$ARGUMENTS/`.

**Exclude** these generated/build files:
- `*_export.h`
- `*_config.h`
- `moc_*.cpp`
- `ui_*.h`
- Anything under `build*/` directories

Read ALL discovered files before starting analysis. Cross-file analysis is essential for lifecycle and dead code checks.

## Step 2: Analyze Against Check Categories

Analyze every file against these 7 categories, ordered by severity:

### CRITICAL Severity

**[C1] Logic Errors**
- Always-true/false conditions
- Wrong operators (`=` vs `==`, `&` vs `&&`, `|` vs `||`)
- Missing `break` in switch-case (unless fallthrough is intentional and commented)
- Off-by-one errors in loops/indexing
- Unreachable code after return/break/continue
- Integer overflow/truncation in arithmetic

**[C2] Possible Bugs**
- Null pointer dereference — especially after IIO calls like `iio_context_find_device()`, `iio_device_find_channel()`, `iio_device_find_attr()` which can return NULL
- Use-after-delete or use-after-deleteLater
- Uninitialized member variables (check constructor initializer lists)
- Raw pointer set to dangling state after delete (missing `= nullptr`)
- Ignored return values from IIO functions that indicate errors
- Double-free or double-delete scenarios
- Race conditions with signals/slots across threads

**[C3] Plugin Lifecycle**
- **onConnect/onDisconnect asymmetry**: every resource acquired in `onConnect()` or `init()` must be released in `onDisconnect()` or `deinit()`
- Every `new` needs a corresponding `delete` (unless Qt parent-child ownership handles it)
- Every `open()` needs `close()`
- Every `connect()` (signal) needs `disconnect()` (unless the object is deleted, which auto-disconnects)
- IIOWidgetGroup creation must have matching cleanup
- API objects registered via `registerApiObject()` should be unregistered
- Check that `onDisconnect()` properly reverses `onConnect()`

### WARNING Severity

**[W1] Qt Issues**
- Missing `Q_OBJECT` macro in QObject-derived classes that use signals/slots
- Old-style `SIGNAL()`/`SLOT()` string-based connections (prefer new-style `&Class::method`)
- Missing parent parameter in QObject/QWidget constructors (potential memory leak)
- Missing virtual destructor on non-QObject base classes used polymorphically
- `QTimer::singleShot` with raw `this` pointer without preventing dangling calls

**[W2] Dead Code**
- Private methods declared but never called within the class
- Member variables declared but never read
- Commented-out code blocks (3+ consecutive lines)
- Empty non-virtual function bodies
- Unused local variables
- Unused `#include` of project headers

### INFO Severity

**[I1] Naming Conventions**
- Member variables should use `m_` prefix
- Methods should be `camelCase()`
- Classes should be `PascalCase`
- File names should be lowercase
- Use `Q_SLOTS` not `slots`, `Q_SIGNALS` not `signals`

**[I2] Include Hygiene**
- Includes that appear unused
- Headers that could use forward declarations instead
- Missing include guards or `#pragma once`

## Step 3: Generate Report

### Rules for Reporting
- **Be conservative**: if unsure, downgrade severity
- **Be Qt-ownership-aware**: don't flag missing `delete` when Qt parent handles it
- **Be specific**: every finding must include file path, line number, and fix suggestion
- **No false positives**: only report issues you are confident about

### Report Format

```
## Code Quality Report: $ARGUMENTS

### Summary
| Category | Critical | Warning | Info |
|----------|----------|---------|------|
| Logic Errors | X | - | - |
| Possible Bugs | X | - | - |
| Plugin Lifecycle | X | - | - |
| Qt Issues | - | X | - |
| Dead Code | - | X | - |
| Naming Conventions | - | - | X |
| Include Hygiene | - | - | X |
| **Total** | **X** | **X** | **X** |

### Critical Issues
**[C2] Null pointer not checked after IIO call**
`src/plugin.cpp:142` — `iio_context_find_device()` return value used without null check.
> **Fix:** Add null check after the call.

### Warnings
...

### Info
...

### Verdict
[PASS/FAIL] — [one sentence summary]
```

PASS = zero critical issues. FAIL = one or more critical issues.
160 changes: 160 additions & 0 deletions tools/scopy_dev_plugin/commands/create-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# /create-api — Generate JavaScript API class for a Scopy plugin

You are creating a JavaScript API class for a Scopy plugin so it can be controlled from test automation scripts.

**Plugin:** `$ARGUMENTS`

## Step 0: Load context

Use the Read tool to check if a port state file exists:
- Path: `tasks/$ARGUMENTS-port-state.md`
- If the file does not exist, note "No state file — will discover from source files directly." and continue.

## Step 1: Discovery

Locate the plugin's source files by checking these paths in priority order:

**If state file exists**, use it as the inventory: extract the plugin class name, namespace, tool list IDs, and section/attribute inventory.

**If no state file**, discover directly:
- Use the Glob tool to find the plugin header: `scopy/packages/$ARGUMENTS/plugins/*/include/*/$ARGUMENTS*plugin.h`
- Use the Glob tool to find the plugin source: `scopy/packages/$ARGUMENTS/plugins/*/src/$ARGUMENTS*plugin.cpp`
- Read tool class files in the same directory
- Read `manifest.json.cmakein` for device name and display name
- Read CMakeLists.txt for library and export macro naming

**Also read the tool implementation** (`<plugin>.cpp` in `src/`) to catalogue every
IIOWidget's conversion setup. For each widget, note:
- `setDataToUIConversion` lambda → determines what the **getter** must do (e.g. ÷1e6 for Hz→MHz)
- `setUItoDataConversion` lambda → determines what the **setter** must do (e.g. ×1e6 for MHz→Hz)
- IIO attribute suffix patterns (e.g. hardwaregain returns "-10.000000 dB" — suffix must be stripped in getter)
- Combo widget available-attribute name → extract valid enum string list for setter validation

Also check if an API already exists (use the Glob tool to search for `*_api.h` in the plugin's package dir). If found, report it to the user and stop.

## Step 2: Design — WAIT FOR APPROVAL

After reading the plugin header, source, and instrument classes, present:

1. **API pattern chosen** (Pattern A: tool-based, Pattern B: IIOWidgetGroup-based, or both)
2. **Complete method list** grouped by instrument/category:
- `getTools()` always included
- getter/setter pairs for each widget member
- run-control methods
- IIOWidget access methods (if applicable)

For each getter/setter pair, also document a **conversion row**:
| Method | IIO unit | API unit | Getter conversion | Setter conversion |
3. **Missing Functionality Report** — things that CAN'T be exposed without base-class changes
4. **Files to create/modify**:
- `<plugin>_api.h` (new)
- `<plugin>_api.cpp` (new)
- Plugin header (add friend + `m_api` member + `initApi()`)
- Plugin source (add `initApi()` impl + call in `onConnect()` + delete in `onDisconnect()`)
- CMakeLists.txt (only if it uses explicit file lists — check first)

**Wait for user approval before writing any code.**

## Step 3: Implement

Follow the API patterns described in the `scopy-api-patterns` knowledge skill:
- Use `friend class` for private member access (do NOT add public getters to instrument classes)
- Null-check every instrument pointer
- Call `initApi()` as the last line of `onConnect()` before `return true`
- Delete `m_api` at the BEGINNING of `onDisconnect()` before any other cleanup
- Set the JS object name with `setObjectName("pluginname")` — lowercase, short
- Register with `ScopyJS::GetInstance()->registerApi(m_api)`
- End the `.cpp` file with `#include "moc_<plugin>_api.cpp"`

## IIOWidget Conversion Rules

`IIOWidget::writeAsync()` writes directly to the IIO data strategy — it does **NOT** apply
`m_UItoDS`. Similarly, `read().first` returns the raw IIO value, not the UI-displayed value.
Getters and setters MUST replicate the conversion logic from `setDataToUIConversion` /
`setUItoDataConversion` lambdas found in the tool class.

**Frequency (Hz ↔ MHz):**
```cpp
// Getter: divide raw Hz string by 1e6
QString getXxx() {
QString raw = readFromWidget(key);
if(raw.isEmpty()) return raw;
return QString::number(raw.toDouble() / 1e6, 'f', 3);
}
// Setter: multiply MHz value by 1e6 before writing
void setXxx(const QString &val) {
writeToWidget(key, QString::number(val.toDouble() * 1e6, 'f', 0));
}
```

**IIO unit suffix (e.g. " dB" appended by IIO driver to hardwaregain):**
```cpp
// Getter: strip the trailing suffix before returning
QString getXxx() {
QString raw = readFromWidget(key);
int idx = raw.indexOf(" dB");
if(idx != -1) raw = raw.left(idx).trimmed();
return raw;
}
// Setter: write numeric string only (IIO accepts without suffix)
void setXxx(const QString &val) { writeToWidget(key, val); }
```

**dBFS ↔ linear scale (e.g. IIO stores 0.5 linear, UI shows 6 dBFS):**
```cpp
#include <cmath>
// Getter: linear → dBFS (round to nearest integer)
QString getXxx() {
QString raw = readFromWidget(key);
if(raw.isEmpty()) return raw;
double linear = raw.toDouble();
if(linear <= 0.0) return QString("0");
return QString::number(static_cast<int>(20.0 * std::log10(1.0 / linear) + 0.5));
}
// Setter: dBFS → linear
void setXxx(const QString &val) {
double linear = std::pow(10.0, -val.toDouble() / 20.0);
writeToWidget(key, QString::number(linear, 'g', 10));
}
```

**Combo/enum widgets — validate before writing:**
```cpp
void setXxx(const QString &val)
{
static const QStringList options = {"opt1", "opt2", "opt3"};
if(!options.contains(val)) {
qWarning(CAT_...) << "Invalid value:" << val << "Valid:" << options;
return;
}
writeToWidget(key, val);
}
```

> **Note:** If any attribute uses `std::log10` or `std::pow` (dBFS↔linear scale conversions),
> add `#include <cmath>` to the `.cpp` file.

## Step 4: Validate

- [ ] API class inherits from `ApiObject`
- [ ] All `Q_INVOKABLE` methods null-check before accessing members
- [ ] `friend class` declared in plugin header
- [ ] `initApi()` called at end of `onConnect()`, `delete m_api` at start of `onDisconnect()`
- [ ] `.cpp` ends with `#include "moc_<plugin>_api.cpp"`
- [ ] JS object name is lowercase and consistent with similar plugins
- [ ] No modifications to instrument, widget, or utility classes

## Step 5: Update state file (if it exists)

```markdown
## Status
- Phase: API_COMPLETE
```

## Rules

- Do NOT modify any instrument, widget, or utility class — only the plugin header/source
- Do NOT create API methods that require new public methods in base classes
- Do NOT add friend declarations unless private member access is actually needed
- Keep the JS object name lowercase and consistent with similar plugins
- Every method must null-check before accessing members
Loading
Loading