Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ This skill helps an agent create and validate custom `dotnet new` templates. It

## Workflow

### Rules that change the answer

Use these structures exactly; do not invent fields from other template features.

**Deliver the requested artifact.** When the user says "show", "write", or "give me the
content", put the complete JSON/XML in the final response even if you also wrote it to disk.
Never edit this skill's `SKILL.md` or plugin documentation as a substitute for authoring the
user's template. Only create or modify template files when the user requested file changes and
the target template/project is present.

| Need | Correct structure | Never use |
|------|-------------------|-----------|
| Conditional XML in a `.csproj` | XML comments such as `<!--#if (database == "SqlServer") -->` and `<!--#endif -->` around the complete element | bare `#if` lines, which make the XML invalid |
| Restore generated projects | restore action `210D431B-A78B-4D2F-B762-4ED3E3EA9025`; use `primaryOutputs`, or `args.files` containing source-template paths/globs | run-script fields such as `executable` on the restore action |
| Restrict to the SDK host | a `host` constraint whose `args` is an array containing `{ "hostname": "dotnetcli" }`; its optional `version` restricts the host/CLI version | using host `version` when the requirement is specifically the active SDK version, the invalid host ID `dotnet-cli`, or unrelated `pattern` / `value` fields |
| Restrict the active SDK version | an `sdk-version` constraint with a NuGet version/range string in `args` | a machine-specific exact patch unless the template truly requires it |
| Preserve CPM | keep generated `PackageReference` items versionless and package the owning `Directory.Packages.props` when the template is self-contained | adding inline `Version` attributes |
| Package templates | a pack project with `<PackageType>Template</PackageType>` and template content packed below `content/` | describing a layout without showing the requested project file |

### Step 1: Bootstrap from existing project

Analyze the source `.csproj` and create a `.template.config/template.json`:
Expand Down Expand Up @@ -95,20 +114,68 @@ Quick summary of what gets checked:
Based on validation results and user requirements:

1. **Add parameters** with appropriate types (string, bool, choice), defaults, and descriptions
2. **Add conditional content** using `#if` preprocessor directives for optional features
2. **Add conditional content** using the file type's valid syntax. In XML use template
directives inside XML comments, not bare preprocessor lines:

```xml
<!--#if (database == "SqlServer") -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<!--#endif -->
<!--#if (database == "Postgres") -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<!--#endif -->
```
3. **Configure post-actions** for solution add, restore, or custom scripts
4. **Set constraints** to restrict which SDKs or workloads the template supports
5. **Add classifications** and tags for discoverability

For a restore post-action, prefer `primaryOutputs` when the project path is known:

```json
"primaryOutputs": [{ "path": "MyProject.csproj" }],
"postActions": [{
"description": "Restore NuGet packages.",
"manualInstructions": [{ "text": "Run 'dotnet restore'." }],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}]
```

If `args.files` is needed, its paths are matched against the **source template** before
renames, for example `"files": ["**/*.csproj"]`. Explain that distinction.

### Step 4: Test the template locally

For a create-from-existing-project request, this step is required rather than optional:
install the authored template, run a dry-run, instantiate it into a temporary output folder,
and build the generated project. Report each observed result; inspecting `template.json` alone
does not prove the reusable template works.

```bash
dotnet new install ./path/to/template/root
dotnet new mylib --name TestProject --dry-run
dotnet new mylib --name TestProject --output ./test-output
dotnet build ./test-output/TestProject
```

When packaging is requested, include the complete pack project, not only a directory tree:

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Contoso.ProjectTemplates</PackageId>
<PackageType>Template</PackageType>
<TargetFramework>net10.0</TargetFramework>
Comment thread
Evangelink marked this conversation as resolved.
Outdated
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**\*" />
<Content Include="templates\**\*" Pack="true" PackagePath="content\" />
</ItemGroup>
</Project>
```

## Validation

- [ ] `template.json` passes manual validation with zero errors
Expand All @@ -117,6 +184,8 @@ dotnet build ./test-output/TestProject
- [ ] Template can be installed, dry-run, and instantiated successfully
- [ ] Created projects build cleanly with `dotnet build`
- [ ] Conditional content produces correct output for all parameter combinations
- [ ] XML template directives are wrapped in XML comments and the generated project parses
- [ ] Host constraints use `args[].hostname`; restore actions use `primaryOutputs` or `args.files`

## Common Pitfalls

Expand All @@ -128,6 +197,7 @@ dotnet build ./test-output/TestProject
| Not testing all parameter combinations | Use `dotnet new <template> --dry-run` with different parameter values to verify conditional content works correctly. |
| Hardcoded versions in template | Use `sourceName` replacement for project names and consider parameterizing framework versions. |
| Not setting classifications | Add appropriate `classifications` (e.g., `["Web", "API"]`) for template discovery. |
| Reusing fields from a different constraint or post-action | Follow the exact schema: `host.args[].hostname`, and restore `args.files` rather than run-script fields. |

## More Info

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ a comparison table.

## Workflow

**Evidence contract:** a side-by-side table is useful only when every option claim is
grounded in the currently installed templates. Run each `--help` command sequentially,
capture the same requested dimensions for each template, and label an unavailable option
as `Not exposed` rather than guessing or borrowing a flag from another template.

**Decision contract:** optimize the comparison for the user's stated decision, not table
size. Cover every requested dimension, omit unrelated option rows, give a scenario-specific
reason, and include one safe `--dry-run` command for the recommended starting point when it
would make the recommendation actionable.

### Step 1: Inspect each template

Run `dotnet new <template> --help` for each template being compared to collect its
Expand All @@ -49,8 +59,8 @@ dotnet new webapi --help
dotnet new webapp --help
```

If a template is not installed, find and install it first (`dotnet new search <keyword>`,
then `dotnet new install <package>`).
If a template is not installed, search for its provider and report the missing prerequisite.
Install it only when the user asked you to modify the environment or approved the install.

> **Run `--help` calls sequentially.** The template engine uses a global mutex, so running
> several `dotnet new <template> --help` commands concurrently can fail with a transient
Expand All @@ -67,6 +77,10 @@ Produce a side-by-side table covering:
- **Available frameworks** — e.g., net8.0, net9.0, net10.0
- **Classifications** — categories the template advertises (Web, API, Blazor, etc.)

Use one row per requested decision dimension and cite the observed option name in the cell.
Do not fill a requested row with general framework knowledge when it is specifically about
what the template generates or exposes.

Example shape:

| Aspect | `webapi` | `webapp` |
Expand All @@ -88,7 +102,8 @@ Then link to `template-instantiation` to create it. A comparison that ends witho

### Decision shortcuts for common pairs

Use these as the opinionated default when the user hasn't given a countervailing constraint. Still inspect with `--help` to confirm parameters, but lead with the verdict:
Use these only for the recommendation, not as evidence of current parameter support. Still
inspect with `--help` before filling the comparison table:

| Pair | Default pick | Because |
|------|-------------|---------|
Expand All @@ -97,12 +112,23 @@ Use these as the opinionated default when the user hasn't given a countervailing
| `worker` vs `console` | **`worker`** for long-lived/queue/background processing | Generic Host: DI, logging, config, graceful shutdown, `IHostedService` lifecycle |
| `mvc` vs `webapp` | **`webapp`** (Razor Pages) for page-focused apps; `mvc` for controller/view separation at scale | Razor Pages is lighter for CRUD-style pages |

Two constraints override the shorthand above:
Comment thread
Evangelink marked this conversation as resolved.
Outdated

- Choose **`mvc`** when the user explicitly anticipates a large application or shared
controller logic, even if its first pages are CRUD-focused.
- Choose **`blazor` with Server interactivity** over `webapp` when rich interactive forms
are central but useful HTML must arrive on the first response. Explain that the initial
render is server-produced and that interactive components use the Blazor form/component
model rather than Razor Pages `PageModel`.

## Validation

- [ ] Every template requested was inspected via `dotnet new <template> --help`
- [ ] The comparison covers parameters, feature support, frameworks, and classifications
- [ ] Differences relevant to the user's scenario are called out explicitly
- [ ] A recommendation (or clear trade-off) is provided
- [ ] Unsupported or absent options are labeled instead of guessed
- [ ] The final recommendation is a single decisive `Recommendation:` line

## Common Pitfalls

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ This skill helps an agent find, inspect, and select the right `dotnet new` templ
> result under load, leaving the user nothing. Always close with the written recommendation, and
> never end a turn on a "let me confirm from the CLI…" teaser.

> **Inspection requests require inspection.** If the user asks for installed choices,
> exact parameters/defaults, compatibility constraints, or the exact dry-run file list,
> run the corresponding `dotnet new` command. Do not replace observed data with remembered
> flags. Report a flag only when the current template's `--help` output contains it.

## Inputs

| Input | Required | Description |
Expand Down Expand Up @@ -148,6 +153,11 @@ Use `dotnet new <template> --help` to get full parameter details for a specific
dotnet new webapi --help
```

Copy the observed option names, choices, defaults, and compatibility notes into the answer.
For example, Windows Service support is not universally a worker-template flag. If the
installed `worker --help` does not expose one, say so and distinguish template creation from
post-creation hosting configuration; never invent `--windows` or `--use-windows-service`.

### Step 4: Preview output

Use `dotnet new <template> --dry-run` to show what files and directories a template would create without writing anything to disk:
Expand All @@ -156,7 +166,11 @@ Use `dotnet new <template> --dry-run` to show what files and directories a templ
dotnet new webapi --name MyApi --auth Individual --dry-run
```

If the dry-run fails (transient "mutex"/"persistence" error), retry once; if it still fails, give a **representative** structure (template *family* and typical file kinds) and note it isn't CLI-confirmed. Do not invent specific values, choices, or file paths. When the dry-run **succeeds**, present the actual file list from its output faithfully — don't summarize, regroup, or invent files — and add a one-line purpose for the key entry points (e.g. `Program.cs`, `App.razor`).
If the dry-run fails (transient "mutex"/"persistence" error), retry once; if it still fails, give a **representative** structure (template *family* and typical file kinds) and note it isn't CLI-confirmed. Do not invent specific values, choices, or file paths. When the dry-run **succeeds**, preserve every actual path from its output. For a long list, render those paths as a directory tree rather than a flat wall of full paths; do not omit or invent entries. Follow the tree with a one-line purpose for each key entry point (for example `Program.cs`, `App.razor`, and the project file). A file list without those explanations is incomplete.

If the user says not to create files, every copy-pasteable creation command must include
`--dry-run`. A plain `dotnet new ...` command contradicts that request even when you did not
execute it yourself.

### Step 5: Present findings

Expand All @@ -179,6 +193,8 @@ An answer without a concrete, copy-pasteable command is what makes this skill ti
- [ ] At least one template match was found for the user's intent
- [ ] Template parameters are explained with types and defaults
- [ ] User understands what the template produces before proceeding to creation
- [ ] Exact-option claims came from this template's observed `--help` output
- [ ] Advice-only commands that must not create files include `--dry-run`

## Common Pitfalls

Expand All @@ -189,6 +205,7 @@ An answer without a concrete, copy-pasteable command is what makes this skill ti
| Not checking template constraints | Some templates require specific SDKs or workloads. Use `dotnet new <template> --help` to surface constraints before recommending. |
| Recommending a template without previewing output | Always use `dotnet new <template> --dry-run` to confirm the template produces what the user expects. |
| A `dotnet new` call fails with a "mutex"/"persistence" error and you return nothing | These are transient (often from concurrent invocations). Run `dotnet new` calls sequentially, retry once, then fall back to the Step 1 intent mapping and still give the user a concrete answer. |
| Guessing a Windows Service or AOT flag from another SDK/template | Quote only options observed in `dotnet new <template> --help`; otherwise explain the post-creation path. |

## More Info

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ This skill creates .NET projects from templates using `dotnet new` CLI commands,

> **Match the workspace, then stop.** The highest-value move is aligning the new project with the repo it lands in: detect **CPM** (`Directory.Packages.props`) and the **target framework** used by neighbouring `.csproj` files, and mirror both. **Treat the discovered target framework as an explicit choice** — pass it as `--framework` so `template-smart-defaults` won't override it; deviate only when it's incompatible with a requested feature (then flag the conflict). Do this in as few steps as possible — a `--dry-run`, the create, and one `dotnet build` to confirm is usually enough. Extra exploratory turns add cost without improving the result.

| Situation | Required action |
|-----------|-----------------|
| Simple standalone project | inspect only the requested template, create at the exact path, then build |
| Existing neighboring projects | read their TFMs first and pass the matching supported `--framework` explicitly |
| `Directory.Packages.props` found | create with `--no-restore` when supported, normalize generated package references, then restore/build once |
| Multi-project solution | create each project at its final path, add references, add all projects to the solution, then build the solution once |

Do not predict the generated target framework. If the user did not request one and the
workspace does not supply one, let the template choose, then read the generated project and
report the actual TFM. Never announce an intermediate framework guess that contradicts the
generated `.csproj`.

## When to Use

- User asks to create a new .NET project, app, or service
Expand Down Expand Up @@ -79,6 +91,12 @@ Use `dotnet new` with the template name and all parameters:
dotnet new webapi --name MyApi --output ./src/MyApi --framework net10.0 --auth Individual
```

Before running it, emit one compact decision line:

`Creating <template> at <path>; framework=<value> (<user|workspace|template>); CPM=<on|off>.`

This makes workspace adaptations explicit without adding a long report.

#### Common parameter combinations

| Template | Parameters | Example |
Expand All @@ -93,14 +111,16 @@ Note: Use `dotnet new <template> --help` to see all available parameters for any

After creation, adapt the project to Central Package Management and refresh stale versions:

1. **Detect CPM** — walk up the directory tree from the new project looking for a `Directory.Packages.props`.
2. **Strip inline versions** — if found, for each `<PackageReference Include="X" Version="Y" />` the template generated, remove the `Version` attribute from the `.csproj` (leaving `<PackageReference Include="X" />`).
3. **Centralize the version** — add or merge a `<PackageVersion Include="X" Version="Y" />` entry in `Directory.Packages.props`.
4. **Optionally refresh stale template-default versions** — templates often hardcode old versions. Keep the template's versions by default (safest for reproducibility and controlled upgrades). Only refresh when the user asks, and when you do:
1. **Detect CPM before creation** — walk up from the destination looking for `Directory.Packages.props`.
2. **Avoid a doomed automatic restore** — when CPM is active and the template exposes
`--no-restore`, pass it during creation so package centralization happens first.
3. **Strip inline versions** — for each generated `<PackageReference Include="X" Version="Y" />`, remove the `Version` attribute (leaving `<PackageReference Include="X" />`).
4. **Centralize the version** — add or merge a `<PackageVersion Include="X" Version="Y" />` entry in `Directory.Packages.props`; preserve unrelated existing entries.
5. **Optionally refresh stale template-default versions** — templates often hardcode old versions. Keep the template's versions by default (safest for reproducibility and controlled upgrades). Only refresh when the user asks, and when you do:
- Prefer a tooling-driven flow: run `dotnet list package --outdated` and confirm the proposed bumps with the user before changing anything.
- Constrain upgrades to the same **major** (or major/minor) version unless the user explicitly opts into larger upgrades, since cross-major bumps can introduce breaking changes.
- When checking the latest **stable** version of a package conceptually, the NuGet V3 flat-container `index.json` endpoint for that package ID lists published versions; never select a prerelease unless requested.
5. **Build** — run `dotnet build` to confirm the centralized/refreshed versions resolve.
6. **Build** — run `dotnet build` once to restore and confirm the centralized/refreshed versions resolve.

### Step 5: Multi-project composition (optional)

Expand Down Expand Up @@ -141,6 +161,7 @@ dotnet new uninstall Microsoft.DotNet.Web.ProjectTemplates.10.0
| Pitfall | Solution |
|---------|----------|
| Not checking for CPM before creating a project | If `Directory.Packages.props` exists, `dotnet new` creates projects with inline versions that conflict. After creation, move versions to `Directory.Packages.props` and remove them from `.csproj`. |
| Letting template restore fail before adapting CPM | Detect CPM first and use the template's `--no-restore` option when available; centralize versions before the first restore/build. |
| Creating projects without specifying the framework | Always specify `--framework` when the template supports multiple TFMs to avoid defaulting to an older version. |
| Not adding the project to the solution | After creation, run `dotnet sln add` to include the project in the solution. |
| Not verifying the project builds | Always run `dotnet build` after creation to catch missing dependencies or parameter issues early. |
Expand Down
Loading