From dotnet
LiveCharts2 development guide — installation, XAML source generator integration, theme config, gotchas, and sample index with exact repo file paths. Use when implementing any LiveCharts2 chart (line, area, bar, pie, gauge, heatmap, scatter, polar, financial). Covers all platforms (WinUI, Uno, Avalonia, MAUI, WPF, Blazor, WinForms, Eto).
How this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet:dotnet-livecharts2The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use when implementing charts, gauges, heatmaps, or any LiveCharts2 visualization.
Use when implementing charts, gauges, heatmaps, or any LiveCharts2 visualization.
NuGet package per platform:
| Platform | Package |
|---|---|
| Uno Platform | LiveChartsCore.SkiaSharpView.Uno.WinUI |
| WinUI 3 | LiveChartsCore.SkiaSharpView.WinUI |
| WPF | LiveChartsCore.SkiaSharpView.WPF |
| MAUI | LiveChartsCore.SkiaSharpView.Maui |
| Avalonia | LiveChartsCore.SkiaSharpView.Avalonia |
| Blazor | LiveChartsCore.SkiaSharpView.Blazor |
You MUST use the latest prerelease Nuget, E.g. for Uno Platform:
dotnet add package LiveChartsCore.SkiaSharpView.Uno.WinUI --prerelease
MUST use LiveChartsCore.SkiaSharpView.WinUI — NOT LiveChartsCore.SkiaSharpView.Uno.WinUI:
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.WinUI"
The Uno package re-exports from the WinUI namespace. Using the wrong namespace causes UXAML0001 build errors.
Choose the right approach per chart type:
Build ISeries[] / IReadOnlyList<ISeries> in the ViewModel; XAML just binds Series, XAxes, YAxes:
<lvc:CartesianChart Series="{Binding Series}" XAxes="{Binding XAxes}" YAxes="{Binding YAxes}"
TooltipPosition="Top" AnimationsSpeed="00:00:00.400" ZoomMode="X"/>
[ObservableProperty] public partial IReadOnlyList<ISeries> Series { get; set; }
public IReadOnlyList<Axis> XAxes { get; }
public IReadOnlyList<Axis> YAxes { get; }
Use ViewModel-first when:
Paint objects need programmatic construction with helpersUse SeriesCollection / AxesCollection wrappers with Xaml* types:
<lvc:CartesianChart>
<lvc:CartesianChart.Series>
<lvc:SeriesCollection>
<lvc:XamlLineSeries Values="{Binding Values}" Fill="{x:Null}" GeometrySize="20"/>
</lvc:SeriesCollection>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.XAxes>
<lvc:AxesCollection>
<lvc:XamlAxis Labels="{Binding Labels}" MinStep="1" ForceStepToMin="True"/>
</lvc:AxesCollection>
</lvc:CartesianChart.XAxes>
</lvc:CartesianChart>
Use XAML-first when:
Even in XAML-first gauges, Paint objects can be bound from ViewModel (Fill="{Binding GaugeFill}") for dynamic colors.
XAML series types: XamlLineSeries, XamlColumnSeries, XamlRowSeries, XamlStackedAreaSeries, XamlStackedColumnSeries, XamlStackedRowSeries, XamlStackedStepAreaSeries, XamlScatterSeries, XamlStepLineSeries, XamlPieSeries, XamlHeatSeries, XamlBoxSeries, XamlCandlesticksSeries, XamlPolarLineSeries, XamlGaugeSeries, XamlGaugeBackgroundSeries, XamlAngularGaugeSeries, XamlNeedle, XamlAngularTicks.
XAML axis types: XamlAxis, XamlDateTimeAxis, XamlTimeSpanAxis, XamlLogarithmicAxis, XamlPolarAxis.
XAML collection wrappers: SeriesCollection (for Chart.Series), AxesCollection (for Chart.XAxes/Chart.YAxes).
{lvc:SolidColorPaint Color='#AARRGGBB'} — solid fill/stroke paint{lvc:Float Value='25'} — float literal{lvc:Padding Value='L,T,R,B'} or {lvc:Padding Value='15'} — padding{lvc:Margin Value='L,T,R,B'} — chart DrawMargin override{lvc:ColorArray Values='#FF4FC3F7, #FFFFF176, #FFFF5722'} — color gradient (for HeatMap property)CRITICAL: {lvc:SolidColorPaint} only accepts the Color parameter. Do NOT add StrokeThickness or other properties inside the markup extension (e.g. Color='FF107C10', StrokeThickness=2). The comma causes UXAML0001 parse errors. Set stroke thickness separately on the series or in C#.
AddDefaultTheme overwrites explicit colorsAddDefaultTheme adds HasRuleForLineSeries/HasRuleForBarSeries etc. that unconditionally overwrite Fill/Stroke with palette colors, breaking explicit XAML {lvc:SolidColorPaint} values and ViewModel-set semantic colors. Use HasTheme directly with HasDefaultTooltip/HasDefaultLegend + selective HasRuleFor* to preserve explicit colors.
Choose the right data type for your series:
| Type | When to use | Example chart |
|---|---|---|
double / int | Simple categorical data, gauge values, sparklines | Column chart, gauge GaugeValue, sparkline |
ObservableValue | Single value needing live update via .Value setter | Row bar with real-time noise (use with ObservableCollection<ObservableValue>) |
ObservablePoint | Explicit X/Y coordinates | Scatter, custom-positioned points |
DateTimePoint | Time-series data (X = DateTime) | Power flow, energy balance, range forecast |
WeightedPoint | X/Y/weight (3 dimensions) | Heatmap (HeatSeries), bubble scatter |
FinancialPoint | OHLC candle data | Candlestick chart |
CRITICAL: XamlGaugeSeries.GaugeValue expects double, NOT ObservableValue. Binding to an ObservableValue object shows "0". Use [ObservableProperty] double GaugeValue with PropertyChanged notification. ObservableValue is a LiveCharts data container for ISeries-level data collections, not for XAML DependencyProperty bindings.
Call LiveCharts.Configure() once in App.xaml.cs (or a static method called from there) before any chart control renders. Typically in OnLaunched before InitializeComponent.
Built-in palettes e.g. .FluentDesign are defined in LiveChartsCore.Themes.ColorPalletes (note double 'l' in Palletes).
Use theme.OnInitialized(() => { ... }) inside HasTheme callback (fluent API). Use it to populate palette colors, animation defaults, and axis/legend/tooltip styling for the theme instance.
If you replace the default theme with HasTheme(...), keep the tooltip/legend factories and register the Hover visual state yourself.
Why:
HasTheme(...) starts from an empty ThemeHasDefaultTooltip(() => new SKDefaultTooltip()), hover can produce no tooltipseries.HasState("Hover", ...), Series.OnPointerEnter() can throw KeyNotFoundException: 'Hover' before tooltips renderSafe pattern:
using LiveChartsCore.Drawing;
using LiveChartsCore.SkiaSharpView.SKCharts;
using LiveChartsCore.VisualStates;
LiveCharts.Configure(config => config
.HasTheme(theme =>
theme
.OnInitialized(() =>
{
// axis/tooltip styling here
})
.HasDefaultTooltip(() => new SKDefaultTooltip())
.HasDefaultLegend(() => new SKDefaultLegend())
.HasRuleForAnySeries(series =>
{
_ = series.HasState("Hover",
[
(nameof(DrawnGeometry.Opacity), 0.8f)
]);
})
)
);
If tooltips still do not appear:
HasDefaultTooltipHover stateTooltipPositionUse selective rules with HasTheme to control which series types get themed:
| Method | Purpose |
|---|---|
HasRuleForAxes(Action<IPlane>) | Style all axes (labels, separators, name paint) |
HasRuleForAnySeries(Action<ISeries>) | Style all series (data labels, hover state) |
HasRuleForLineSeries(...) | Style line series — overwrites Fill/Stroke |
HasRuleForBarSeries(...) | Style bar/column series — overwrites Fill/Stroke |
HasRuleForGaugeSeries(...) | Style gauge foreground (DataLabelsPosition, CornerRadius) |
HasRuleForGaugeFillSeries(...) | Style gauge background arc (Fill) |
HasRuleFor<BaseLabelVisual>(...) | Style label visual elements |
Only include HasRuleForLineSeries/HasRuleForBarSeries etc. if you want palette auto-coloring. Omit them to preserve explicit series colors set in XAML or ViewModel.
LiveCharts2 charts render inside a XAML control. The control's pixel size determines everything. The chart engine divides the control area into DrawMargin (the plot area) and surrounding space (title, legend, axes). All drawing happens in device-independent pixels on a SkiaSharp canvas.
┌─────────────────────────────── ControlSize ─────────────────────────────┐
│ ┌─Title──────────────────────────────────────────────────────────────┐ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ ┌──────────────── DrawMargin ───────────────────┐ │
│ Y-Axis │ │ │
│ Labels │ Series are drawn here │ │
│ ◄─ls─► │ (plot area) │ │
│ │ │ │
│ └───────────────────────────────────────────────┘ │
│ X-Axis Labels ◄─bs─► │
│ ┌─Legend─────────────────────────────────────────────────────────────┐ │
│ └────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
ControlSize - (title + legend + axis labels + axis names)DrawMargin="left, top, right, bottom" on the chart (values in px; use Auto per side to keep auto-calculation for that side)Key properties affecting CartesianChart size/position:
| Property | On | Effect |
|---|---|---|
DrawMargin | Chart | Override auto margin (px). "Auto" per side = auto |
LegendPosition | Chart | Hidden / Top / Bottom / Left / Right — consumes space |
Title | Chart | Title visual element — consumes top space |
TextSize | Axis | Larger text = more margin consumed by labels |
LabelsRotation | Axis | Rotated labels change measured height/width |
ShowSeparatorLines | Axis | Visual only; no layout impact |
Name / NamePaint | Axis | Axis name label — consumes additional margin |
IsVisible | Axis | Hidden axis consumes no space |
Padding | Axis (Cartesian) | Inner padding between labels and plot edge |
┌──────────────── ControlSize ──────────────────┐
│ ┌─Title─────────────────────────────────────┐ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌── DrawMargin ──┐ │
│ │ ╭─────────╮ │ │
│ │ ╱ ╲ │ │
│ │ │ (cx, cy) ││ │
│ │ ╲ ╱ │ │
│ │ ╰─────────╯ │ │
│ └────────────────┘ │
│ │
│ ┌─Legend────────────────────────────────────┐ │
│ └───────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
The pie/gauge is always centered in DrawMargin at (cx, cy):
cx = DrawMarginLocation.X + DrawMarginSize.Width * 0.5cy = DrawMarginLocation.Y + DrawMarginSize.Height * 0.5radius = min(DrawMarginSize.Width, DrawMarginSize.Height) / 2 (square-fitted)The pie always fits inside a square inscribed in DrawMargin. If DrawMargin is rectangular, extra space appears on the longer axis. This is why a 180° gauge wastes the bottom half.
Key properties affecting PieChart size/position:
| Property | On | Effect |
|---|---|---|
DrawMargin | Chart | Override auto margin (px) |
LegendPosition | Chart | Consumes space → shrinks DrawMargin → smaller radius |
Title | Chart | Consumes top space |
InitialRotation | Chart | Rotation offset in degrees (0° = 3 o'clock) |
MaxAngle | Chart | Total sweep angle (360 = full, 180 = half) |
MaxValue / MinValue | Chart | Scale range for gauge series |
InnerRadius | Series | Absolute inner radius (px). Creates donut hole |
RelativeInnerRadius | Series | Additional inner offset between stacked rings (px) |
RelativeOuterRadius | Series | Outer offset between stacked rings (px) |
MaxRadialColumnWidth | Series | Max arc thickness (px). Excess → inner/outer offset per RadialAlign |
RadialAlign | Series | Outer / Center / Inner — where to put excess when MaxRadialColumnWidth constrains |
OuterRadiusOffset | Series | Shrink outer radius by this many px |
Pushout | Series | Explode slice outward by this many px |
CornerRadius | Series | Round the arc endpoints |
DataLabels are text elements drawn on the SkiaSharp canvas (not XAML elements).
Each label has a position (X, Y) computed from DataLabelsPosition, and a bounding box sized from the text + DataLabelsPadding. The box is aligned on (X, Y) using HorizontalAlign and VerticalAlign (both default to Middle):
X (label position)
│
┌─────┼─────┐ ← box top = Y - box.Height/2
│ pad │ pad │
│ "74" │ ← text at Padding.Top from box top
│ │
│ (padding │
│ bottom) │ ← extra bottom padding pushes text UP
└───────────┘ ← box bottom = Y + box.Height/2
box.Height = textHeight + Padding.Top + Padding.BottomVerticalAlign=Middle: box is centered on Y → boxTop = Y - box.Height/2boxTop + Padding.TopPadding.Bottom → taller box → text moves upDataLabels properties (all series types):
| Property | Type | Effect |
|---|---|---|
ShowDataLabels | bool | Enable/disable labels |
DataLabelsPaint | Paint | Color/style. Use {lvc:SolidColorPaint} in XAML (bindings may not work on XamlGaugeSeries) |
DataLabelsSize | double | Font size in px |
DataLabelsRotation | double | Rotation in degrees |
DataLabelsPadding | Padding | Space around text. Asymmetric values shift the visible text relative to the anchor point. Use {lvc:Padding Value='L,T,R,B'} in XAML |
DataLabelsMaxWidth | double | Max width before wrapping |
DataLabelsFormatter | Func | Custom text formatting (C# only, not settable in XAML) |
DataLabelsPosition | enum | Anchor point computation (see below) |
DataLabelsPosition — Cartesian charts (DataLabelsPosition enum):
| Value | Anchor point |
|---|---|
End | End of bar/point in axis direction |
Start | Start of bar/point in axis direction |
Middle | Center of bar/point |
Top / Bottom / Left / Right | Absolute direction |
DataLabelsPosition — Pie/Gauge/Polar charts (PolarLabelsPosition enum):
| Value | Anchor point |
|---|---|
ChartCenter | (cx, cy) — geometric center of the chart |
Middle | Midpoint of arc thickness, at mid-sweep angle |
Start | Inner radius, at sweep start angle |
End | Outer radius, at sweep end angle |
Outer | Outside the arc, at mid-sweep angle |
When DataLabelsPosition doesn't place the label where you need it, combine:
DrawMargin with negative values to shift the chart geometry (cx, cy, radius)DataLabelsPadding with asymmetric values to shift the visible text relative to the computed anchorExample: 180° gauge filling a card with value text above the arc's flat edge:
DrawMargin="{lvc:Margin Value='-20,0,-20,-120'}" — large arc, cy near bottomDataLabelsPadding="{lvc:Padding Value='6,6,6,50'}" — 50px bottom padding pushes text up from cyDataLabelsPosition="ChartCenter" — anchor at (cx, cy)┌─────── Card (200×130 chart area) ────┐
│ ╭━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╮ │ ← arc (DrawMargin widens it)
│ ┃ value arc bg arc ┃ │
│ ╰━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╯ │
│ "74" │ ← text shifted up by Padding
│ ···(cy + padding bottom: clipped)··· │
└──────────────────────────────────────┘
Anti-pattern: manual PieSeries gauge. Do NOT create gauges with new PieSeries<double> { Values = [75] } + new PieSeries<double> { Values = [25] } (manually calculating the background remainder). This is fragile. Use XamlGaugeSeries + XamlGaugeBackgroundSeries instead — the background auto-fills the remaining arc. See sample Pies/Gauge2.
InitialRotation="180", MaxAngle="180". NOT -90 (that produces a right-side half). 0° = 3 o'clock in LiveCharts2.InnerRadius on the series (not the PieChart) to create the donut arc gap.DrawMargin + DataLabelsPadding — see "Combining DrawMargin + DataLabelsPadding" in the Chart Layout Model section above.LegendPosition="Hidden" and TooltipPosition="Hidden" on gauge PieCharts — default legend/tooltip are meaningless for gauges.AnimationsSpeed as XAML TimeSpan string: AnimationsSpeed="00:00:00.500" (500ms). Set on the PieChart control.GaugeValue expects double — NOT ObservableValue. Use [ObservableProperty] double GaugeValue in ViewModel.<lvc:PieChart DrawMargin="{lvc:Margin Value='-20,0,-20,-120'}"
InitialRotation="180" MaxAngle="180" MinValue="0" MaxValue="100"
LegendPosition="Hidden" TooltipPosition="Hidden"
AnimationsSpeed="00:00:00.500">
<lvc:PieChart.Series>
<lvc:SeriesCollection>
<lvc:XamlGaugeSeries
GaugeValue="{Binding GaugeValue}"
InnerRadius="55"
ShowDataLabels="True" DataLabelsSize="24"
DataLabelsPadding="{lvc:Padding Value='6,6,6,50'}"
DataLabelsPosition="ChartCenter"/>
<lvc:XamlGaugeBackgroundSeries
InnerRadius="55"/>
</lvc:SeriesCollection>
</lvc:PieChart.Series>
</lvc:PieChart>
HeatSeries has no Stroke property — don't set it.WeightedPoint(x, y, weight) where weight maps to color gradient.HeatMap = [coldColor.AsLvcColor(), ..., hotColor.AsLvcColor()].Use PeriodicTimer with CancellationToken and IDisposable for proper lifecycle:
public sealed partial class PowerFlowChartViewModel : ObservableObject, IDisposable
{
const int WindowMinutes = 240;
readonly PeriodicTimer timer = new(TimeSpan.FromMilliseconds(5000));
readonly CancellationTokenSource cts = new();
public ObservableCollection<DateTimePoint> GenerationValues { get; } = [];
[ObservableProperty] public partial IReadOnlyList<ISeries> Series { get; set; }
public object Sync { get; } = new(); // thread-safety lock
public PowerFlowChartViewModel()
{
SeedHistoricData();
Series = BuildSeries();
_ = TickLoop();
}
async Task TickLoop()
{
try { while (await timer.WaitForNextTickAsync(cts.Token)) AddDataPoint(); }
catch (OperationCanceledException) { }
}
void AddDataPoint()
{
var now = DateTime.Now;
GenerationValues.Add(new DateTimePoint(now, ComputeValue()));
// Sliding window: trim old points
var cutoff = now.AddMinutes(-WindowMinutes);
while (GenerationValues.Count > 0 && GenerationValues[0].DateTime < cutoff)
GenerationValues.RemoveAt(0);
}
public void Dispose() { cts.Cancel(); cts.Dispose(); timer.Dispose(); }
}
<lvc:CartesianChart Series="{Binding Series}" SyncContext="{Binding Sync}" ZoomMode="X"/>
Key points:
ObservableCollection<DateTimePoint> for auto-updating time seriesSyncContext="{Binding Sync}" on the chart, where Sync is a shared objectPeriodicTimer is preferred over Task.Delay loop (proper cancellation semantics)DateTimeAxis in C# (ViewModel-first approach):
new DateTimeAxis(TimeSpan.FromHours(2), date => date.ToString("HH:mm", CultureInfo.InvariantCulture))
{
TextSize = 11,
}
DateTimeAxis in XAML (XamlDateTimeAxis):
<lvc:XamlDateTimeAxis Interval="0:0:1"
DateFormatter="{Binding TimeFormatter}"
AnimationsSpeed="0"
CustomSeparators="{Binding Separators}"/>
AnimationsSpeed="0" on axis: disables axis label animation for real-time charts (prevents jitter)CustomSeparators: bind to double[] of tick valuesCategorical axis (named labels):
<lvc:XamlAxis Labels="{Binding CategoryLabels}" MinStep="1" ForceStepToMin="True"/>
MinStep="1" + ForceStepToMin="True": ensures one label per category (no interpolation)Common axis properties:
| Property | Effect |
|---|---|
MinLimit / MaxLimit | Clamp axis range |
MinStep | Minimum step between labels |
ForceStepToMin | Force step to exactly MinStep (no auto-scaling) |
IsVisible | Hide axis (consumes no space) |
ShowSeparatorLines | Toggle gridlines |
LabelsRotation | Rotate labels in degrees (e.g. -45) |
AnimationsSpeed | Axis animation speed ("0" to disable) |
null (no vertical gridlines); Y-axis gets horizontal gridlines.Scan this index for keywords matching your chart type, then read the sample files using the paths in section 7.
Clone once per machine (reuse across sessions):
[ -d /tmp/LiveCharts2 ] || git clone --depth 1 https://github.com/Live-Charts/LiveCharts2.git /tmp/LiveCharts2
cat /tmp/LiveCharts2/samples/ViewModelsSamples/{Category}/{Sample}/ViewModel.csls /tmp/LiveCharts2/samples/WinUISample/WinUISample/Samples/{Category}/{Sample}/cat /tmp/LiveCharts2/docs/{topic}/{page}.md/tmp/LiveCharts2/samples/ViewModelsSamples/{Category}/{Sample}/ViewModel.cs
| Platform | Path (under /tmp/LiveCharts2/) | Extension |
|---|---|---|
| WinUI / Uno | samples/WinUISample/WinUISample/Samples/{Category}/{Sample}/View.xaml | .xaml |
| Avalonia | samples/AvaloniaSample/{Category}/{Sample}/View.axaml | .axaml |
| MAUI | samples/MauiSample/{Category}/{Sample}/View.xaml | .xaml |
| WPF | samples/WPFSample/{Category}/{Sample}/View.xaml | .xaml |
| WinForms | samples/WinFormsSample/{Category}/{Sample}/View.cs | .cs |
| Blazor | samples/BlazorSample/Pages/{Category}/{Sample}.razor | .razor |
| Eto | samples/EtoFormsSample/{Category}/{Sample}/View.cs | .cs |
Note: Uno Platform reuses the WinUI sample XAML (same namespace WinUISample.*).
Key doc files in /tmp/LiveCharts2/docs/:
overview/1.2.install.md — Installation, first chart, SeriesSource/SeriesTemplateoverview/1.12.themes.md — Theme configurationoverview/1.6.paint tasks.md — SolidColorPaint, LinearGradient, RadialGradientoverview/1.4.automatic updates.md — ObservableCollection auto-updateoverview/1.9.animations.md — Animation speed, easingcartesianChart/overview.md — CartesianChart control propertiescartesianChart/lineseries.md — LineSeries propertiescartesianChart/columnseries.md — ColumnSeries/RowSeries propertiescartesianChart/heatseries.md — HeatSeries, HeatMap gradient, WeightedPointcartesianChart/axes.md — Axis config, DateTimeAxis, labels, separatorspiechart/overview.md — PieChart control propertiespiechart/pieseries.md — PieSeries propertiespiechart/gauges.md — Gauge patterns (XamlGaugeSeries, angular gauge, needle)polarchart/overview.md — PolarChart, PolarAxissrc/skiasharp/_Shared.WinUI/ — CartesianChart.cs, PieChart.cs, ThemeListener.cssrc/skiasharp/_Shared.Xaml/Series.cs — All Xaml series typessrc/skiasharp/_Shared/ — Source generators (SourceGenCartesianChart, SourceGenPieChart)src/LiveChartsCore/Themes/ — ColorPalletes.cs, Theme.cssrc/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs — AddDefaultTheme, HasRuleFor*If you need content only available on the website (e.g. rendered screenshots, API browser):
# livecharts.dev has an incomplete TLS certificate chain.
# Download the intermediate cert first (only if /tmp/sectigo-intermediate.pem doesn't exist or is outdated):
curl -s http://crt.sectigo.com/SectigoPublicServerAuthenticationCADVR36.crt -o /tmp/sectigo-intermediate.crt
openssl x509 -inform DER -in /tmp/sectigo-intermediate.crt -out /tmp/sectigo-intermediate.pem
# Then fetch any page:
curl -s --cacert /tmp/sectigo-intermediate.pem "https://livecharts.dev/docs/UnoWinUi/{library-version}/{page}"
Website platform URL prefixes: UnoWinUi, avalonia, blazor, maui, wpf, winforms, winui, eto
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub vincenth-net/dotnet-agentic-engineering --plugin dotnet