AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Code Quality Analysis

skill-cumulocity-iot-cumulocity-skills-code-quality-analysis · by Cumulocity-IoT

|

— No reviews yet
0 installs
20 views
0.0% view→install

Install

$ agentstack add skill-cumulocity-iot-cumulocity-skills-code-quality-analysis

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ● Network access Used
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-cumulocity-iot-cumulocity-skills-code-quality-analysis)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Code Quality Analysis? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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
  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"])

  1. 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.


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:

// 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.

// 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.


@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.

// 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
// 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()).

// 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
// 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.

// 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
// 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).

// 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.

// 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).

// 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:

// 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):

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.

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

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.