Install
$ agentstack add skill-cumulocity-iot-cumulocity-skills-code-quality-analysis ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →About
Code Quality Analysis Skill
Overview
This skill performs comprehensive code quality analysis on Angular + Cumulocity Web SDK codebases. It combines guidance from:
- TypeScript best practices — load the
mastering-typescriptskill (from https://github.com/SpillwaveSolutions/mastering-typescript-skill/tree/main/mastering-typescript) - Angular documentation — fetch
https://angular.dev/assets/context/llms-full.txt - 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:
- Read
skills/mastering-typescript/SKILL.md - Fetch
https://angular.dev/assets/context/llms-full.txt(use thefetch_webpagetool) - Call
mcp_c8y-docs_get-codex-structureto get the full Codex map - Call
mcp_c8y-docs_query-codexwith queries relevant to the features used in the
file under review (e.g. ["css utility classes spacing", "color tokens", "widgets lazy loading"])
- Call
mcp_c8y-docs_query-codexwith 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.tsfiles - All
*.service.tsfiles - All
*.module.tsfiles - All
*.component.htmlfiles
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
Subjectthat is immediately.next()-ed once and never reused- Wrapping a one-shot
Promiseresult in anObservablewithout 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):
- Use the
asyncpipe in the template — Angular unsubscribes automatically - Use
takeUntilDestroyed(this.destroyRef)(Angular 16+) - Collect in a
Subscriptionand callsubscription.unsubscribe()inngOnDestroy
// 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
loadComponentinstead ofcomponent - Use
loadConfigComponentinstead ofconfigComponent
// 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, orfont-sizewith 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 + mapchain 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 MyDeviceTypewithout 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.
- Author: Cumulocity-IoT
- Source: Cumulocity-IoT/cumulocity-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.