# Code Quality Analysis

> |

- **Type:** Skill
- **Install:** `agentstack add skill-cumulocity-iot-cumulocity-skills-code-quality-analysis`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Cumulocity-IoT](https://agentstack.voostack.com/s/cumulocity-iot)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [Cumulocity-IoT](https://github.com/Cumulocity-IoT)
- **Source:** https://github.com/Cumulocity-IoT/cumulocity-skills/tree/main/skills/code-quality-analysis

## Install

```sh
agentstack add skill-cumulocity-iot-cumulocity-skills-code-quality-analysis
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Code Quality Analysis Skill

## Overview

This skill performs comprehensive code quality analysis on Angular + Cumulocity Web SDK
codebases. It combines guidance from:

1. **TypeScript best practices** — load the `mastering-typescript` skill (from https://github.com/SpillwaveSolutions/mastering-typescript-skill/tree/main/mastering-typescript)
2. **Angular documentation** — fetch `https://angular.dev/assets/context/llms-full.txt`
3. **Cumulocity Codex** — use the `mcp_c8y-docs_*` MCP tools to retrieve component and API references

> **MCP server required:** The `mcp_c8y-docs_*` tools are served by
> `https://c8y-codex-mcp.schplitt.workers.dev/` (server name: `c8y-docs`, transport: HTTP).
> Register it once with your agent:
> ```bash
> claude mcp add --transport http c8y-docs https://c8y-codex-mcp.schplitt.workers.dev/
> ```
> See `AGENTS.md` for full setup instructions and alternative config formats.

---

## How to Run an Analysis

### Step 1 — Gather Reference Material

Before analyzing any file, load the following resources in parallel:

1. Read [`skills/mastering-typescript/SKILL.md`](https://github.com/SpillwaveSolutions/mastering-typescript-skill/tree/main/mastering-typescript)
2. Fetch `https://angular.dev/assets/context/llms-full.txt` (use the `fetch_webpage` tool)
3. Call `mcp_c8y-docs_get-codex-structure` to get the full Codex map
4. Call `mcp_c8y-docs_query-codex` with queries relevant to the features used in the
   file under review (e.g. `["css utility classes spacing", "color tokens", "widgets lazy loading"]`)
5. Call `mcp_c8y-docs_query-codex` with queries for the C8Y SDK services in scope
   (e.g. `["InventoryService", "AlarmService", "MeasurementService client"]`) to validate API usage

### Step 2 — Identify Files to Analyze

If no specific file was provided, search for:
- All `*.component.ts` files
- All `*.service.ts` files  
- All `*.module.ts` files
- All `*.component.html` files

### Step 3 — Analyze Each File

Run each check below against the target file(s). For every finding:

- State the **file path and line number**
- State which **anti-pattern** was found
- Show the **problematic snippet**
- Provide a **corrected / recommended** version

---

## Anti-Patterns Catalogue

### AP-01 · `*ngIf` instead of `@if` (Angular Control Flow)

**Rule:** Never use the structural directive `*ngIf`. Use the built-in `@if / @else` block
syntax introduced in Angular 17+.

**Also applies to:** `*ngFor` → `@for`, `*ngSwitch` → `@switch`.

```html

Loading…
{{ item.name }}

@if (loading) {
  Loading…
}
@for (item of items; track item.id) {
  {{ item.name }}
}
```

---

### AP-02 · Too Much Logic Inside Components

**Rule:** Components are responsible for the *view* only. Business logic, data
transformation, and orchestration belong in dedicated services.

**Signals of violation:**
- Methods longer than ~20 lines inside a component
- Complex data mapping or aggregation inside a component
- Direct HTTP/SDK calls made from inside a component class (not delegated to a service)
- Multiple levels of nested async/await inside lifecycle hooks

**Refactor pattern:** Extract logic into an `@Injectable()` service. The component calls
the service and binds the result.

---

### AP-03 · Using `any` Everywhere

**Rule:** Explicit `any` is forbidden except at genuine boundaries (e.g. 3rd-party library
interop with no types). Prefer `unknown`, proper interfaces, or generic type parameters.

**Detection:**
- Property typed as `: any`
- Cast with `as any`
- Function parameter typed `: any`

**Fix examples:**
```typescript
// BAD
function process(data: any): any { … }

// GOOD
function process>(data: T): ProcessedResult { … }
```

Cross-reference the **mastering-typescript** skill (section "Type Guards and Narrowing") for
patterns that eliminate the need for `any`.

---

### AP-04 · Ignoring OnPush Change Detection

**Rule:** Every component that does not mutate shared mutable state should use
`ChangeDetectionStrategy.OnPush`. Default change detection causes unnecessary re-renders
across the entire component tree.

```typescript
// BAD
@Component({ selector: 'app-sensor', templateUrl: '…' })
export class SensorComponent { … }

// GOOD
@Component({
  selector: 'app-sensor',
  templateUrl: '…',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SensorComponent { … }
```

**Note:** With `OnPush`, use `async` pipe or `markForCheck()` / signals to trigger updates.

---

### AP-05 · Missing `trackBy` / `track` in Loops

**Rule:** Every `@for` loop (or legacy `*ngFor`) that renders a list of objects **must**
specify a track expression so Angular can diff items by identity rather than re-rendering
the entire list.

```html

@for (sensor of sensors) {
  
}

@for (sensor of sensors; track sensor.id) {
  
}
```

---

### AP-06 · Non-Standalone Components (NgModule / `standalone: false`)

**Rule:** All newly created components, directives, and pipes must be standalone
(`standalone: true`). Do not create new NgModules for features. Existing NgModules can
be kept only when wrapping third-party code that requires it. `standalone: false` should
never appear in new code.

```typescript
// BAD
@Component({ selector: 'app-foo', templateUrl: '…', standalone: false })
export class FooComponent { … }

// GOOD
@Component({ selector: 'app-foo', templateUrl: '…', standalone: true, imports: [CommonModule] })
export class FooComponent { … }
```

---

### AP-07 · Overusing RxJS When a Simple Value Works

**Rule:** Do not create an `Observable` or `Subject` just to hold a synchronous or
non-reactive value. Use plain variables, `signal()`, or `computed()` instead.

**Signals of violation:**
- `BehaviorSubject(false)` used as a simple flag with no subscribers outside
  the same class
- `Subject` that is immediately `.next()`-ed once and never reused
- Wrapping a one-shot `Promise` result in an `Observable` without a good reason

```typescript
// BAD — isLoading is never observed reactively outside this class
private isLoading$ = new BehaviorSubject(false);

// GOOD
isLoading = signal(false);
```

---

### AP-08 · Heavy Logic / Method Calls in Templates

**Rule:** Angular templates re-evaluate every expression on each change-detection cycle.
Method calls in templates are called every cycle, even if their inputs have not changed.

**Signals of violation:**
- `{{ formatDate(item.timestamp) }}` — function call in interpolation
- `[class]="getClass(item)"` — function call in binding
- `*ngIf="shouldShow(item)"` / `@if (shouldShow(item))` — function call in condition

**Fix:** Move transformation logic to a **pure Pipe** or pre-compute in the component
class (e.g. derived `signal()` or `computed()`).

```typescript
// BAD (template): {{ formatValue(measurement) }}

// GOOD — create a pipe
@Pipe({ name: 'formatValue', pure: true, standalone: true })
export class FormatValuePipe implements PipeTransform {
  transform(measurement: IMeasurement): string { … }
}
```

---

### AP-09 · Subscribing Without Unsubscribing

**Rule:** Any `Observable.subscribe()` call made inside a component or service that is
not `providedIn: 'root'` must be cleaned up to avoid memory leaks.

**Preferred patterns (in order):**
1. Use the `async` pipe in the template — Angular unsubscribes automatically
2. Use `takeUntilDestroyed(this.destroyRef)` (Angular 16+)
3. Collect in a `Subscription` and call `subscription.unsubscribe()` in `ngOnDestroy`

```typescript
// BAD
ngOnInit(): void {
  this.service.data$.subscribe(d => this.data = d);
}

// GOOD
private destroyRef = inject(DestroyRef);

ngOnInit(): void {
  this.service.data$
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe(d => this.data = d);
}
```

---

### AP-10 · Using `FetchClient` for Calls Covered by `@c8y/client`

**Rule:** Never use Angular's `HttpClient` or `FetchClient` for Cumulocity REST API calls
that are already wrapped by a service in `@c8y/client`. Only ok if used for microservice queries (baseUrl contains `/service`).

Use `mcp_c8y-docs_query-codex` to look up the relevant `@c8y/client` service for the domain in
question (e.g. `["InventoryService REST"]`, `["AlarmService client"]`). Covered domains include:
inventory, alarms, events, measurements, operations, binary, users, and more.

```typescript
// BAD — HttpClient used for inventory
constructor(private http: HttpClient) {}
getDevice(id: string) {
  return this.http.get(`/inventory/managedObjects/${id}`);
}

// GOOD — use the typed SDK service
constructor(private inventory: InventoryService) {}
async getDevice(id: string) {
  const { data } = await this.inventory.detail(id);
  return data;
}
```

**Exception:** Raw HTTP is acceptable only for external APIs or endpoints not covered by
`@c8y/client`.

---

### AP-11 · Eager-Loading Widget / Plugin Modules

**Rule:** Widget plugin modules that are registered via the C8Y hook mechanism must use
**lazy loading** so they are only downloaded when needed. Implies configuring components as standalone: true.

- Use `loadComponent` instead of `component`
- Use `loadConfigComponent` instead of `configComponent`

```typescript
// BAD
{
  component: MyWidgetComponent,
  configComponent: MyWidgetConfigComponent,
}

// GOOD
{
  loadComponent: () => import('./my-widget/my-widget.component').then(m => m.MyWidgetComponent),
  loadConfigComponent: () => import('./my-widget/my-widget-config.component').then(m => m.MyWidgetConfigComponent),
}
```

---

### AP-12 · Heavy Custom Styles / Hard-Coded Colors

**Rule:** Do not introduce custom CSS/LESS rules for spacing, typography, or color when
the Cumulocity Design System already provides utility classes or CSS custom properties.
Hard-coded hex/rgb color values break tenant branding.

**Action:** Before writing any custom style, query the Codex:
```
mcp_c8y-docs_query-codex(["css utility classes spacing padding margin", "color tokens design system", "typography font size"])
```

**Signals of violation:**
- Inline styles in templates
- LESS rules that set `margin`, `padding`, or `font-size` with raw pixel values when a
  utility class exists
- Hard-coded hex/rgb/hsl color values (e.g. `color: #1776BF`, `background: rgb(23,118,191)`)
- Overriding `--c8y-*` design tokens with fixed values

**Fix:** Use the Cumulocity utility classes (`m-t-8`, `p-l-16`, `text-muted`, etc.) or
CSS custom properties (`var(--c8y-brand-primary)`) documented in the Codex.

---

### AP-13 · Repetitive Code — Missing Utilities or Shared Components

**Rule:** If the same logical block or template pattern appears more than twice across
different files, extract it into:
- A shared **util service** (for logic)
- A shared **component** in `src/modules/shared/` (for templates)
- A shared **pure pipe** (for value transformations)

**Signals of violation:**
- The same `filter + map` chain repeated in multiple services
- Copy-pasted loading-state or error-handling boilerplate across components
- Identical table/list template fragments in more than one component

---

### AP-14 · Model Interfaces Defined Inside Components

**Rule:** Do not define TypeScript interfaces or types inside a component, directive, or
service file if those types are also used by other files. Define them in a dedicated
model file (e.g. `src/models/sensor.model.ts` or adjacent `*.model.ts`).

```typescript
// BAD — inside sensor-list.component.ts
export interface SensorFilter { type: string; active: boolean; }

// GOOD — src/models/sensor-filter.model.ts
export interface SensorFilter { type: string; active: boolean; }
```

---

### AP-15 · Every Service Decorated with `providedIn: 'root'`

**Rule:** Only services that genuinely need application-wide singleton state should use
`providedIn: 'root'`. If a service is tightly coupled to a single component (e.g. it
holds local UI state), remove `providedIn` and provide it in the component's `providers`
array instead — this also ensures it is destroyed along with the component.

```typescript
// BAD — global singleton for a component-local concern
@Injectable({ providedIn: 'root' })
export class SensorTableStateService { … }

// GOOD — provided by the component that owns it
@Injectable()
export class SensorTableStateService { … }

@Component({
  …
  providers: [SensorTableStateService],
})
export class SensorTableComponent { … }
```

---

### AP-16 · Long Async Tasks Inside Components

**Rule:** Components are tied to the Angular view lifecycle and can be destroyed at any
time. Do not perform long-running async operations (polling loops, large data fetches,
chained network requests) directly inside a component.

**Fix:** Delegate long-running work to a root-level service that is not bound to a
component's lifetime. The component subscribes to the service's output observable or
signal and unsubscribes cleanly on destroy (see AP-09).

```typescript
// BAD — long poll inside a component
ngOnInit(): void {
  this.pollFirmwareStatus(); // may run forever if component is destroyed mid-flight
}

// GOOD — root service drives the async work
@Injectable({ providedIn: 'root' })
export class FirmwareStatusService {
  readonly status$ = timer(0, 5000).pipe(
    switchMap(() => this.fetchStatus()),
    shareReplay(1),
  );
}
```

---

### AP-17 · Untyped / Unguarded Access to `IManagedObject` Extra Attributes

**Rule:** `IManagedObject` is a generic model with an open index signature (`[key: string]: any`).
Any fragment or custom attribute on are not guaranteed to be present or correctly shaped at
runtime. Every access to a non-standard `IManagedObject` property **must** be guarded with a
type predicate function or a Zod schema so that the code is both type-safe and validated at
runtime.

Reference: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates

**Signals of violation:**
- Direct property access without narrowing: `device['c8y_Hardware'].serialNumber`
- Cast with `as MyDeviceType` without runtime validation
- Optional-chaining without a type guard when the shape is non-trivial: `(obj as any)?.nested?.prop`

**Fix — Type Predicate:**
```typescript
// BAD
function getSerialNumber(mo: IManagedObject): string {
  return mo['c8y_Hardware'].serialNumber; // no guarantee the fragment or field exists
}

// GOOD — define the expected shape and a type predicate
interface C8yHardware {
  model: string;
  revision: string;
  serialNumber: string;
}

interface DeviceWithHardware extends IManagedObject {
  c8y_Hardware: C8yHardware;
}

function hasHardwareInfo(mo: IManagedObject): mo is DeviceWithHardware {
  const hw = (mo as DeviceWithHardware).c8y_Hardware;
  return (
    typeof hw === 'object' &&
    hw !== null &&
    typeof hw.serialNumber === 'string'
  );
}

// Usage
if (hasHardwareInfo(device)) {
  // device.c8y_Hardware is now fully typed and validated
  return device.c8y_Hardware.serialNumber;
}
```

**Fix — Zod Schema (preferred for complex/nested shapes):**
```typescript
import { z } from 'zod';

const C8yHardwareSchema = z.object({
  model: z.string(),
  revision: z.string(),
  serialNumber: z.string(),
});

// Parse at the boundary (e.g. when receiving the MO from the API)
const parseHardwareInfo = (mo: IManagedObject) => C8yHardwareSchema.safeParse(mo['c8y_Hardware']);

const result = parseHardwareInfo(device);
if (result.success) {
  const { model, revision, serialNumber } = result.data; // fully typed
}
```

Cross-reference the **mastering-typescript** skill (section "Zod Validation") for
schema composition patterns.

---

### AP-18 · Missing Localization (`| translate`)

**Rule:** Every user-visible string in a template **must** be piped through `| translate`
so it can be localized via the `.po` translation files. Hard-coded English strings in
templates are not translatable and will break non-English deployments.

**Also applies to:**
- Strings passed to `AlertService`, `ModalService`, toast notifications, etc. — use
  `TranslateService.instant()` or pass keys.
- Dynamic strings assembled in TypeScript — construct the string key and

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Cumulocity-IoT](https://github.com/Cumulocity-IoT)
- **Source:** [Cumulocity-IoT/cumulocity-skills](https://github.com/Cumulocity-IoT/cumulocity-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-cumulocity-iot-cumulocity-skills-code-quality-analysis
- Seller: https://agentstack.voostack.com/s/cumulocity-iot
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
