Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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,13 +42,35 @@ 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`:

1. Create `.template.config` directory next to the project
2. Generate `template.json` with `identity` (reverse-DNS), `name`, `shortName`, `sourceName` (project name for replacement), `classifications`, and `tags`
3. Preserve from source — generic `dotnet new` templates frequently get these wrong, so verify each is carried over from the original `.csproj`:
1. Copy the source project into a dedicated template-source directory by default, preserving
the original project untouched. Modify the original in place only when the user explicitly
asks for that layout.
2. Create `.template.config` inside the template-source directory.
3. Generate `template.json` with `identity` (reverse-DNS), `name`, `shortName`, `sourceName` (project name for replacement), `classifications`, and `tags`
4. Preserve from source — generic `dotnet new` templates frequently get these wrong, so verify each is carried over from the original `.csproj`:
1. **SDK type** — `Microsoft.NET.Sdk`, `Microsoft.NET.Sdk.Web`, `Microsoft.NET.Sdk.Worker`, etc.
2. **Analyzer/package reference metadata** — `PrivateAssets`, `IncludeAssets`, `ExcludeAssets`
3. **`OutputType` and other key properties** — `TreatWarningsAsErrors`, `Nullable`, `LangVersion`
Expand Down Expand Up @@ -77,7 +99,7 @@ Minimal example:
| SDK (`Microsoft.NET.Sdk.*`) | ✅ | template content `.csproj` uses same SDK |
| `TreatWarningsAsErrors` / `Nullable` / `LangVersion` | ✅ | preserved verbatim in template `.csproj` |
| PackageReference `PrivateAssets` / `IncludeAssets` / `ExcludeAssets` | ✅ | metadata kept on each reference |
| CPM (`Directory.Packages.props` present) | ✅ | no inline `Version` attributes emitted |
| CPM (`Directory.Packages.props` present) | ✅ | `ManagePackageVersionsCentrally` remains enabled and no inline `Version` attributes are emitted |

Mark any row you intentionally omitted as ⚠️ with a reason — never leave it implicit.

Expand All @@ -95,20 +117,77 @@ 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>net8.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**\*" />
<Content Include="templates\**\*" Pack="true" PackagePath="content\" />
</ItemGroup>
</Project>
```

The pack project's target framework applies only to the content-only packaging project; it
does not retarget projects inside `templates/`. Prefer a broadly available supported framework
unless the packaging project itself uses newer build features.

For a self-contained CPM template, the packaged `Directory.Packages.props` must include
`<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>` and every versionless
`PackageReference` must have a matching `PackageVersion`. Keep the props file at the intended
generated repository root; do not place a duplicate nearer the project where it changes lookup.

## Validation

- [ ] `template.json` passes manual validation with zero errors
Expand All @@ -117,6 +196,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 +209,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,15 @@ 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.

When the user asks about **generated dependencies** without allowing project creation,
inspect the installed template package's source `.csproj` files. `--help` and `--dry-run`
do not reveal package references. Do not create temporary projects merely to inspect them,
and do not guess current package IDs or test-platform defaults.

Example shape:

| Aspect | `webapi` | `webapp` |
Expand All @@ -88,7 +107,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 +117,28 @@ 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 |

These constraints override the shorthand above:

- 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`.
- For **offline support**, choose `blazorwasm` and explain the PWA/service-worker requirement,
cached-after-first-load behavior, and lack of a required live server for execution.
- For a **durable queue processor**, choose `worker` and tie the decision to Generic Host
lifecycle, dependency injection, configuration, logging, graceful shutdown, and a real
durable queue rather than an in-memory loop.

## 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
41 changes: 30 additions & 11 deletions plugins/dotnet-template-engine/skills/template-discovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ This skill helps an agent find, inspect, and select the right `dotnet new` templ
- User wants smart cross-parameter defaults during creation — route to `template-smart-defaults` skill
- User is troubleshooting build issues — route to `dotnet-msbuild` plugin

> **Answer first, confirm second — required, in this order.** The Step 1 intent → template
> and keyword → parameter mappings are a complete answer on their own. **Your first action is
> to write** a concrete template + parameter recommendation (with a ready-to-run `dotnet new`
> command) from the mapping, **before you run any `dotnet new` command**. Only then use the CLI
> to *confirm* exact names/choices and update the answer. **Never make a `dotnet new` call your
> final action** — the engine's global mutex can make it fail with an empty "persistence"/"mutex"
> 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.
> **Recommendation requests: answer first, confirm second. Inspection requests: inspect
> first.** For a general "which template?" question, start from the Step 1 mappings so a
> transient CLI failure cannot leave the user without an answer. When the user explicitly
> asks what is installed, requests exact options/defaults, or asks for dry-run output, run
> the relevant `dotnet new` command before writing the final answer. Never end a turn on a
> `dotnet new` call or a "let me confirm..." 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

Expand All @@ -54,8 +57,8 @@ This skill helps an agent find, inspect, and select the right `dotnet new` templ

## Workflow

> **Do Step 1 and write the recommendation to the user before running Step 2–4 commands.**
> Steps 2–4 only *confirm* the answer; a `dotnet new` failure must never leave the turn empty.
> For recommendations, use Step 1 before Steps 2–4. For explicit inspection requests,
> execute the requested inspection first and use Step 1 only as a fallback.

### Step 1: Resolve intent to template candidates

Expand Down Expand Up @@ -148,6 +151,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 +164,15 @@ 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 command execution is unavailable, do not stop at "run this yourself." Give the
representative tree and key-file explanations from the known template family, clearly labeled
as unconfirmed, so the user still receives a useful preview.

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 +195,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 +207,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
Loading