Prompt Workflows — Turning AI Assistants Into Developer Productivity Commands
READER BEWARE: THE FOLLOWING WRITTEN ENTIRELY BY AI WITHOUT HUMAN EDITING.
Introduction
Every developer who has used GitHub Copilot or a similar AI coding assistant has lived through this pattern: you open a chat window, carefully describe the task — “generate unit tests for this file, following our naming conventions, mocking all external dependencies, and covering the edge cases” — wait for the response, review it, and move on. The next day, for a different file, you do the same thing. Word for word.
The repetition is the problem. The task is not novel; the workflow is not novel. What is novel each time is the target — the file, the function, the pull request. The instructions themselves are stable enough to commit to source control.
That is exactly what prompt files are for. A prompt is a predefined, parameterized task stored as a Markdown file in your repository. When a developer types /generate-tests into the Copilot chat input, the assistant loads the full prompt — with all its requirements, conventions, and constraints — and applies it to the current context. No copy-pasting, no re-explaining, no drift between teammates.
This post explains how prompts work, how to write them well, and why they are one of the highest-leverage customisation tools available for AI-assisted development.
What a Prompt File Actually Is
A prompt file is a Markdown document that lives inside your repository, typically in a .github/prompts/ directory. VS Code’s GitHub Copilot platform discovers these files automatically and surfaces them as slash commands in the Copilot Chat input.
The filename becomes the command name. A file named generate-tests.prompt.md becomes the /generate-tests command. A file named security-review.prompt.md becomes /security-review.
The contents of the file are injected verbatim into the model’s context when the command is invoked — alongside any additional context the developer provides (an open file, a selected code range, a follow-up message). The prompt does not replace the user’s message; it frames it.
A minimal prompt file looks like this:
Generate unit tests using Jest following the testing patterns in the `tests/unit` directory.
Requirements:
- Test edge cases
- Mock external dependencies
- Follow existing test naming conventions
That is a working prompt. When a developer opens src/services/auth.ts, types /generate-tests, and presses Enter, Copilot reads this file and generates tests targeting the open file.
Four Prompts Every Engineering Team Should Have
/generate-tests
Test generation is the canonical use case for prompts. The task is structurally identical every time — pick a file, produce tests — but the quality of the output depends heavily on context that the model would otherwise have to guess: which test framework, which assertion style, which mocking library, what the naming convention looks like.
Open source projects like Jest have well-established patterns: describe blocks, it / test functions, beforeEach setup hooks, jest.mock() for module-level mocking, and jest.fn() / jest.spyOn() for finer-grained control. Without telling the model which of these patterns your codebase prefers, it will make a reasonable guess — and that guess will be inconsistent across teammates.
A production-quality /generate-tests prompt for a Jest codebase:
Generate unit tests for the currently open file using Jest.
Follow the patterns established in `tests/unit/`:
- Use `describe` blocks to group related tests
- Use `it('should ...')` naming — not `test(...)`
- Use `beforeEach` for shared setup; avoid `beforeAll` unless the setup is genuinely expensive
- Mock all external service calls with `jest.mock()` at the module level
- Use `jest.spyOn()` when you need to assert call arguments or restore the original after the test
- Assert on specific return values, not just that functions were called
Requirements:
- Cover the happy path and at least two edge cases per public function
- Include a test for each documented error condition
- Do not test private methods directly; reach them through their public callers
/generate-pr-summary
Pull request descriptions are written under time pressure and often omit the information reviewers actually need: why a change was made, what the reviewer should focus on, and whether there are any non-obvious risks.
Projects like Probot demonstrate how automation can enforce consistency in GitHub workflows. A /generate-pr-summary prompt brings that same consistency to description authoring — not by replacing the developer’s judgment, but by ensuring the model asks all the right questions and structures the output predictably.
Generate a pull request description for the changes in the current diff.
Structure the output as follows:
## Summary
One paragraph explaining *why* this change is being made — the problem it solves or the feature it enables.
## What Changed
A bullet list of the meaningful changes. Group related changes together.
Do not list every file; focus on logical units of change.
## Reviewer Focus Areas
Call out anything that deserves extra scrutiny:
- Logic that is hard to unit-test and was manually verified
- Performance-sensitive paths
- Security-relevant changes (auth, permissions, input handling)
- Decisions that are intentionally unconventional, with a brief rationale
## Testing
Describe how the change was tested. Reference specific test files if they were added or modified.
/convert-to-async
Migrating synchronous code to async/await is tedious, error-prone, and mechanical enough that it is a perfect candidate for automation. The transformation is structurally well-defined — add async, await the blocking calls, propagate the async up the call chain — but getting the propagation right in a real codebase requires reading several files at once.
Convert the selected function (or the entire open file if nothing is selected) from synchronous to async/await.
Rules:
- Add `async` to the function signature
- Wrap all I/O calls (database queries, HTTP requests, file reads) with `await`
- Propagate `async` up to every caller in the same file
- Do not change the function's external interface unless async propagation makes it unavoidable
- If a `Promise` is constructed manually (e.g., `new Promise((resolve, reject) => ...)`),
replace it with a proper `async` function and `try/catch`
- Add a note in a comment for every caller outside this file that will also need updating
After making changes, list all external callers (outside this file) that will need to be updated manually.
/security-review
Security review is exactly the kind of task that benefits from a structured checklist. Ad-hoc reviews miss things. A prompt that encodes your team’s checklist — covering input validation, authentication boundaries, secret handling, and dependency hygiene — turns a subjective review into a repeatable process.
This aligns with the spirit of structured specification validation, as practiced in projects like the OpenAPI Specification, where well-defined schemas enforce contract correctness automatically. A security prompt does something analogous for code review.
Perform a security review of the currently open file.
Check for the following categories of issues and report findings in each:
**Input Validation**
- Are all external inputs (query params, request bodies, headers, environment variables) validated before use?
- Are SQL queries parameterized or built with a safe query builder?
- Are file paths sanitised to prevent directory traversal?
**Authentication & Authorisation**
- Are protected routes checking authentication before running business logic?
- Are permission checks applied at the data layer, not just the route layer?
- Are there any privilege escalation paths?
**Secret Handling**
- Are secrets read from environment variables or a secrets manager — never hardcoded?
- Are secrets excluded from logs and error messages?
**Dependency Risk**
- Are any third-party packages called in ways that deviate from their documented safe usage?
**Output Encoding**
- Is user-supplied content encoded before being written to HTML, SQL, shell commands, or log output?
For each finding, provide:
1. The line number(s) involved
2. A description of the risk
3. A suggested remediation
Why Prompts Excel at Specific Developer Tasks
Pull Request Cleanup
A pull request that arrives for review is a finished artifact — but the description often is not. Developers write descriptions at the end of a long coding session, when cognitive load is highest. A /generate-pr-summary prompt offloads the structured thinking to the model. The developer still owns the content; the model handles the scaffolding and completeness checking.
Documentation Generation
Documentation has a well-known problem: it is written once, quickly, under deadline, and then never updated. A /document-module prompt that reads the current file, generates JSDoc or docstring annotations, and writes a corresponding README section turns documentation from a one-time act into a repeatable command. ESLint’s plugin authoring documentation is a good example of the level of structure that automated generation can maintain when given a clear template.
Test Generation
The test generation case is covered above, but the underlying principle bears repeating. Prompts eliminate the two biggest barriers to test coverage: the time cost of writing boilerplate and the cognitive cost of remembering the correct patterns. When /generate-tests produces tests that already follow the team’s conventions, the developer’s job is reduced to reviewing business logic correctness rather than formatting.
Code Refactoring
Refactoring tasks like /convert-to-async, /extract-interface, or /migrate-to-v2-api are well-scoped transformations. The model knows what async/await looks like; the prompt tells it how your codebase uses it and what to watch out for. The result is a transformation that requires significantly less cleanup than one produced by a generic prompt.
Prompts vs. Skills: Understanding the Distinction
The terms prompt and skill are sometimes used interchangeably, but they describe meaningfully different primitives in the GitHub Copilot customisation model.
| Prompts | Skills | |
|---|---|---|
| Invocation | Explicit — developer types /command-name | Dynamic — agent loads the skill when it determines it is relevant |
| Scope | A single, bounded task | A reusable capability the agent can apply across many tasks |
| Context loading | Always loaded when invoked | Loaded on demand by the agent’s planning layer |
| Best for | Predictable, repeatable workflows | Domain knowledge the agent needs selectively |
| Developer control | High — developer decides when to run it | Lower — agent decides when to apply it |
Use a prompt when:
- The task has a clear start and end
- The developer always knows in advance that they want to run it
- The output is a deliverable: tests, a PR description, a migration script
Use a skill when:
- The capability should be available across many different task types
- The agent should be able to invoke it without explicit instruction
- The knowledge is specialised enough that it would waste context if always loaded (unlike an instruction file)
A practical example: your team’s ESLint autofix workflow is a good prompt candidate — a developer explicitly runs /autofix-lint-errors against a file. The knowledge of which ESLint rules your team uses is a better fit for an instruction file or a skill, because it is needed across many different tasks (writing new code, reviewing existing code, generating tests) without always being invoked explicitly.
Writing Prompts That Scale Across a Team
Commit Them to Source Control
Prompt files belong in .github/prompts/. Committing them to the repository means every developer on the team gets the same commands, they evolve with the codebase through pull requests, and changes are tracked in the commit history. A prompt that lives only on one developer’s machine is not a team asset.
Write Requirements, Not Instructions
The most common mistake in prompt authoring is writing a description of what the model should do rather than a specification of what the output must satisfy. Instructions tell the model how to think; requirements describe what correctness looks like.
# ❌ Instruction-style (fragile)
First, read the file. Then identify the functions. Then for each function, write a test.
# ✅ Requirement-style (robust)
Generate tests for every public function in the file.
Each test must:
- Include at least one happy-path case
- Include at least one case for each documented error condition
- Use jest.mock() for any module-level dependencies
Reference Your Actual File Paths
Generic prompts produce generic output. A prompt that says “follow the patterns in tests/unit/” produces significantly better results than one that says “follow standard testing patterns”. The model can read your actual test files as part of its context; give it a reason to do so.
Version the Prompts With the Code
When you upgrade a library — say, migrating from Jest 27 to Jest 29 — update the /generate-tests prompt at the same time. A prompt that references the wrong API version is worse than no prompt, because it produces confident but incorrect output.
Conclusion
Prompts are the bridge between AI capabilities and team-specific process. They take the most repetitive, most context-heavy tasks in a developer’s day — test writing, PR descriptions, security reviews, refactoring — and reduce them to a single slash command that encodes everything the model needs to know.
The investment is small. A well-written prompt file takes twenty minutes to draft and an hour to tune. The return compounds over every developer, every day, for the lifetime of the repository. If your team has a workflow you describe in Slack or Confluence more than once a week, it is a prompt waiting to be written.
Start with /generate-tests. Commit it, share it, and watch how quickly it becomes the first thing a new team member learns.