Do the Work, Then Capture It
Build Your Software Factory — Article 1 of 20
You need a repository method. Active products in a category, most recently updated first, projected to a DTO. Open your AI IDE and ask for it.
Add a method to
ProductRepositorythat returns the 10 most recently updated active products in a given category, projected toProductListItemDtoincluding the category name.
The assistant gives you something like this:
public async Task<List<ProductListItemDto>> GetRecentActiveAsync(int categoryId) => await _db.Products .Where(p => p.CategoryId == categoryId && p.IsActive) .OrderByDescending(p => p.UpdatedAt) .Take(10) .Select(p => new ProductListItemDto { Id = p.Id, Name = p.Name, CategoryName = p.Category.Name, UpdatedAt = p.UpdatedAt }) .ToListAsync();It works. It’s also wrong… at least by your team’s standards.
Correction 1: Query syntax, not method chaining
Section titled “Correction 1: Query syntax, not method chaining”Your team reads LINQ as query syntax. Joins, filters, projections. They all use from, where, orderby, select. Method calls are reserved for terminal operations like ToListAsync. You tell the assistant:
We use query syntax on this team. Rewrite the filtering, ordering, and projection as a query expression. Method calls are only for terminal operations.
The assistant rewrites:
public async Task<List<ProductListItemDto>> GetRecentActiveAsync(int categoryId) => await ( from p in _db.Products where p.CategoryId == categoryId && p.IsActive orderby p.UpdatedAt descending select new ProductListItemDto { Id = p.Id, Name = p.Name, CategoryName = p.Category.Name, UpdatedAt = p.UpdatedAt }) .Take(10) .ToListAsync();Better. But it’s still one expression. The query definition and the execution are tangled together.
Correction 2: Separate the query from execution
Section titled “Correction 2: Separate the query from execution”Your team builds the IQueryable as a local variable, then executes it on the next line. This makes queries composable. You can layer on .AsNoTracking(), .Skip(), or pass the query to a shared paging helper without untangling a fluent chain. You tell the assistant:
Assign the query to a local variable first, then apply
TakeandToListAsyncseparately.
Now the assistant produces what you want:
public async Task<List<ProductListItemDto>> GetRecentActiveAsync(int categoryId){ var query = from p in _db.Products where p.CategoryId == categoryId && p.IsActive orderby p.UpdatedAt descending select new ProductListItemDto { Id = p.Id, Name = p.Name, CategoryName = p.Category.Name, UpdatedAt = p.UpdatedAt };
return await query.Take(10).ToListAsync();}That meets your standard. Two corrections, two patterns. Now capture them.
Ask the assistant to capture it
Section titled “Ask the assistant to capture it”The assistant already has the context — the corrections you made, the reasoning behind them, the working example. It’s not going to imagine a skill from nothing. It’s going to document what just happened. Say:
Create a skill that captures the LINQ standards you just followed.
The assistant produces a skill file:
---description: Guidelines for writing LINQ queries using Entity Framework Core. Use when writing LINQ queries.---
## Separate specification from execution
Always use query syntax (`from...select`) and separateIQueryable definition from async execution:
```csharp// Preferredvar customersSpec = from customer in context.Customer where customer.CustomerGUID == customerGuid select new { customer.CustomerID, customer.Name };var customers = await customersSpec.ToListAsync();
// Avoid: Method chaining with immediate executionvar customers = await context.Customer .Where(customer => customer.CustomerGUID == customerGuid) .Select(customer => new { customer.CustomerID, customer.Name }) .ToListAsync();```Two corrections became two rules. The example is drawn from real work, not invented. That’s the whole point.
Save and commit
Section titled “Save and commit”Put the file in .claude/skills/linq-standards/SKILL.md and commit it. Every developer who clones the repo now has the skill. When they ask the assistant to write a LINQ query, it loads this skill and follows your team’s standards — without anyone having to correct it again.
The first draft is rough
Section titled “The first draft is rough”Look at that skill again. It covers the two patterns you corrected, but it doesn’t cover joins, grouping, or subqueries. The description says “use when writing LINQ queries.” Is that too broad? Too narrow? You don’t know yet.
That’s fine. You just finished step one of the skill creation cycle:
Capture → Test → Retrospective → Refine → Optimize
The skill exists. It encodes real standards from real work. In the next article, we test it in a clean room — a fresh conversation with no prior context — to see if it holds up without hand-holding.
Next: Article 2 — Test Your Skill in a Clean Room
For background on skill structure and folder locations, see the Skills page. For the formal skill specification, see agentskills.io.