diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 75553bd..0b31840 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,469 +1,96 @@ # GitHub Copilot Instructions -This file provides context and guidance for GitHub Copilot when working with this PowerShell module project. +See [AGENTS.md](../AGENTS.md) for the full coding guidelines. This file is a focused summary for inline code generation. -## TL;DR for AI Agents - -- Create small, focused functions -- Always add tests -- Never weaken validation or security -- Follow existing patterns -- Prefer explicit, boring, auditable code - -## Project Context - -This is a **PowerShell Script Module Template** project designed for building production-ready PowerShell modules with: - -- **Build System**: Invoke-Build for task automation -- **Testing**: Pester 5+ for unit testing -- **Code Quality**: PSScriptAnalyzer for static analysis -- **Security**: InjectionHunter for vulnerability scanning -- **Documentation**: PlatyPS for markdown-based help generation -- **Versioning**: GitVersion with semantic versioning -- **CI/CD**: GitHub Actions for automated testing and publishing - -## Project Structure - -``` -src/ -├── PSScriptModule.psd1 # Module manifest (auto-updated) -├── Public/ # Exported functions -├── Private/ # Internal helper functions -└── Classes/ # PowerShell classes - -tests/ -├── PSScriptAnalyzer/ # Code analysis configuration -└── InjectionHunter/ # Security test configuration - -docs/ -├── getting-started.md # Setup and initial development -├── development.md # Development workflow guide -├── ci-cd.md # CI/CD and publishing guide -└── help/ # Auto-generated function help -``` - -## AI Intent - -Code generated in this repository is production-facing and reused by others. -Prefer correctness, clarity, and testability over brevity or cleverness. - -## Protected Areas - -The following areas should not be modified without explicit instruction: - -- CI/CD workflows -- Versioning configuration -- Publishing logic -- Security scanning configuration - -## When Uncertain - -If requirements are ambiguous: -- Follow existing patterns in the repository -- Prefer private helper functions over changing public APIs -- Add tests that document assumptions - -## Development Principles - -This project follows key software engineering principles: - -### Fail Fast - -**Detect and report errors as early as possible** - -- Use strict parameter validation (`[ValidateNotNullOrEmpty()]`, `[ValidateScript()]`, etc.) -- Validate inputs at function boundaries before processing -- Throw meaningful exceptions immediately when invalid conditions are detected -- Don't allow bad data to propagate through the system -- Use `$ErrorActionPreference = 'Stop'` in critical sections when appropriate - -**Example:** - -```powershell -function Get-UserData { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string]$UserId - ) - - # Fail fast: Validate before proceeding - if ($UserId -notmatch '^\d{5}$') { - throw "UserId must be exactly 5 digits. Received: $UserId" - } - - # Continue with processing only if validation passes - # ... -} -``` - -### Open-Closed Principle - -**Open for extension, closed for modification** - -- Design functions to be extensible without modifying existing code -- Use parameter sets to add new functionality -- Leverage pipeline input for composability -- Create new functions rather than modifying working ones -- Use private helper functions to keep public APIs stable - -**Example:** - -```powershell -# Good: Extensible through parameters -function Get-Data { - [CmdletBinding(DefaultParameterSetName = 'Default')] - param ( - [Parameter(ParameterSetName = 'Default')] - [string]$Name, - - [Parameter(ParameterSetName = 'ById')] - [int]$Id, - - [Parameter(ParameterSetName = 'Advanced')] - [hashtable]$Filter - ) - - # Extension through parameter sets without modifying core logic -} - -# Bad: Modifying function signature breaks existing users -# Instead of changing Get-Data, create Get-DataAdvanced or add parameter set -``` - -## Code Style and Conventions - -### Function Naming and Structure - -**Public Functions** (exported to users): -- File location: `src/Public/` -- Naming: `Verb-Noun.ps1` (e.g., `Get-Something.ps1`) -- Must use approved PowerShell verbs (run `Get-Verb` to check) -- Always include `[CmdletBinding()]` -- Must have comprehensive comment-based help -- Automatically exported from the module - -**Private Functions** (internal only): -- File location: `src/Private/` -- Naming: `VerbNoun.ps1` or descriptive name -- Not exported to module users - -### Function Template - -When creating new functions, use this template: - -```powershell -function Verb-Noun { - <# - .SYNOPSIS - Brief one-line description - - .DESCRIPTION - Detailed description of functionality - - .PARAMETER Name - Parameter description - - .EXAMPLE - Verb-Noun -Name 'Value' - Description of what this example does - - .OUTPUTS - Type of output returned - - .NOTES - Additional notes or requirements - #> - [CmdletBinding(SupportsShouldProcess)] - param ( - [Parameter(Mandatory, ValueFromPipeline)] - [ValidateNotNullOrEmpty()] - [string] - $Name - ) - - begin { - Write-Verbose "Starting $($MyInvocation.MyCommand)" - } - - process { - try { - if ($PSCmdlet.ShouldProcess($Name, 'Action')) { - # Implementation here - } - } - catch { - Write-Error "Error: $_" - throw - } - } - - end { - Write-Verbose "Completed $($MyInvocation.MyCommand)" - } -} -``` - -### Test File Template - -Every function must have a corresponding `.Tests.ps1` file: - -```powershell -BeforeAll { - . $PSCommandPath.Replace('.Tests.ps1', '.ps1') -} - -Describe 'Verb-Noun' { - Context 'Parameter Validation' { - It 'Should require mandatory parameters' { - { Verb-Noun } | Should -Throw - } - - It 'Should not accept null or empty values' { - { Verb-Noun -Name '' } | Should -Throw - } - } - - Context 'Functionality' { - It 'Should return expected result' { - $result = Verb-Noun -Name 'Test' - $result | Should -Not -BeNullOrEmpty - } - } - - Context 'Pipeline Support' { - It 'Should accept pipeline input' { - { 'Test' | Verb-Noun } | Should -Not -Throw - } - } -} -``` - -## Key Guidelines - -### DO: - -✅ Use approved PowerShell verbs (`Get-Verb` to list) -✅ Include comprehensive comment-based help -✅ Add parameter validation attributes -✅ Create `.Tests.ps1` file for every function -✅ Use `Write-Verbose` for debugging output -✅ Use `Write-Error` for error messages -✅ Support `-WhatIf` for destructive operations (`SupportsShouldProcess`) -✅ Support pipeline input where appropriate -✅ Follow begin/process/end pattern for pipeline processing -✅ Test both success and failure scenarios -✅ Mock external dependencies in tests - -### DON'T: - -❌ Use aliases in production code (e.g., `gci` instead of `Get-ChildItem`) -❌ Hard-code paths, credentials, or environment-specific values -❌ Suppress errors without good reason -❌ Skip parameter validation -❌ Forget to create test files -❌ Use `Write-Host` unless absolutely necessary -❌ Mix output types from the same function -❌ Manually update `FunctionsToExport` in manifest (it's automatic) - -## Building and Testing - -### Common Commands - -```powershell -# Build the module -Invoke-Build # Clean + Build - -# Run all tests -Invoke-Build Test - -# Run specific test types -Invoke-Build Invoke-UnitTests # Pester tests only -Invoke-Build Invoke-PSScriptAnalyzer # Code analysis -Invoke-Build Invoke-InjectionHunter # Security scan - -# Generate help documentation -Invoke-Build Export-CommandHelp - -# Test the built module -Import-Module ./build/out/PSScriptModule/PSScriptModule.psd1 -Force -Get-Command -Module PSScriptModule -``` - -### Test Expectations - -- **Coverage Target**: 80%+ code coverage -- **All tests must pass** before committing -- **PSScriptAnalyzer** must pass with no errors -- **Security scans** must pass - -## Version Control - -### Commit Message Format - -Include semantic versioning keywords in commit messages: - -```bash -# Minor version bump (new feature) -git commit -m "Add Get-Something function +semver: minor" - -# Patch version bump (bug fix) -git commit -m "Fix parameter validation +semver: patch" - -# Major version bump (breaking change) -git commit -m "Remove deprecated function +semver: major" - -# No version change (documentation only) -git commit -m "Update README +semver: none" -``` - -## Common Patterns - -### Error Handling - -```powershell -try { - $result = Invoke-Operation -} -catch { - Write-Verbose "$($MyInvocation.MyCommand) Operation failed: $_" - Write-Verbose "StackTrace: $($_.ScriptStackTrace)" - throw $_ -} -``` +--- -### Parameter Validation +## Bias -```powershell -[Parameter(Mandatory)] -[ValidateNotNullOrEmpty()] -[ValidateScript({ Test-Path $_ })] -[ValidateSet('Option1', 'Option2', 'Option3')] -[string] -$ParameterName -``` +- **Prefer**: explicit, simple, pipeline-friendly, and testable functions +- **Avoid**: aliases, complex abstractions, environment-specific assumptions, and secrets in code -### Pipeline Support +--- -```powershell -[CmdletBinding()] -param ( - [Parameter( - Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName - )] - [string] - $Name -) +## Design Principles -begin { - $results = [System.Collections.Generic.List[object]]::new() -} +- **Fail Fast** — validate at the function boundary, throw immediately on invalid input; never let bad data propagate deeper into the call stack +- **Single Responsibility** — one function, one purpose; if a function needs an "and" to describe what it does, split it +- **Open-Closed** — extend behavior via parameter sets and new private helpers; do not modify working public APIs +- **Least Surprise** — functions must behave like built-in cmdlets: support pipeline where it makes sense, return objects not strings, respect `-WhatIf`, and write to the correct streams -process { - # Process each pipeline item - $results.Add($processedItem) -} +--- -end { - return $results -} -``` +## DO / DO NOT -### WhatIf/Confirm Support +### DO -```powershell -[CmdletBinding(SupportsShouldProcess)] -param() +- Use `[CmdletBinding()]` and `[OutputType()]` on every function +- Use approved PowerShell verbs — verify with `Get-Verb` +- Validate parameters with `[ValidateNotNullOrEmpty()]`, `[ValidateSet()]`, `[ValidateScript()]`, or `[ValidatePattern()]` +- Use `begin`/`process`/`end` blocks for pipeline-accepting functions; collect in `begin`, add in `process`, return in `end` +- Add `SupportsShouldProcess` and call `$PSCmdlet.ShouldProcess()` for destructive operations; omit for read-only functions +- Include comment-based help with `.SYNOPSIS`, `.DESCRIPTION`, `.PARAMETER`, `.INPUTS`, `.OUTPUTS`, and `.EXAMPLE` +- In `catch`, log context with `Write-Verbose "$($MyInvocation.MyCommand) failed for '$Input': $_"` then `throw $_` +- Create a `.Tests.ps1` file alongside every new function -if ($PSCmdlet.ShouldProcess($Target, 'Action to perform')) { - # Perform the action -} -``` +### DO NOT -## Testing Patterns +- Never throw strings — always re-throw the original exception (`throw $_`) +- Never remove or bypass tests, PSScriptAnalyzer, or security scans +- Never use aliases (`Get-ChildItem`, not `gci`) +- Never write secrets, credentials, or environment-specific paths in code +- Never ignore or silently swallow errors +- Never use `Write-Host` — use `Write-Verbose`, `Write-Warning`, or `Write-Error` instead +- Never mix output types from the same function +- Never manually update `FunctionsToExport` in the module manifest — it is populated automatically by the build -### Mocking External Commands +--- -```powershell -BeforeAll { - Mock Invoke-RestMethod { - return @{ Status = 'Success'; Data = 'Mocked' } - } -} +## File Layout -It 'Should call external API' { - $result = Get-Something - Should -Invoke Invoke-RestMethod -Exactly 1 -} -``` +| Type | Location | Naming | +|-------------------------|-----------------------------|-----------------------------------------------------------------| +| Public function + test | `src/Public/Verb-Noun.ps1` | `Verb-Noun` — approved verb, hyphen required | +| Private function + test | `src/Private/VerbNoun.ps1` | `VerbNoun` — PascalCase, no hyphen, not exported | +| Classes | `src/Classes/ClassName.ps1` | `ClassName` — PascalCase; loaded before functions by the build | -### Testing Exceptions +Public functions are automatically exported by the build. Private functions must **not** follow `Verb-Noun` naming to avoid accidental export. Classes are available to all module consumers but are not listed in `FunctionsToExport`. -```powershell -It 'Should throw on invalid input' { - { Get-Something -Name $null } | Should -Throw - { Get-Something -Name '' } | Should -Throw '*cannot be empty*' -} -``` +--- -### Testing Output Types +## Build & Test Commands ```powershell -It 'Should return correct type' { - $result = Get-Something - $result | Should -BeOfType [PSCustomObject] - $result.PropertyName | Should -BeOfType [string] -} +Invoke-Build -Task Build # Build module +Invoke-Build -Task Test # All tests (unit + analysis + security) +Invoke-Build -Task UnitTests # Pester unit tests only +Invoke-Build -Task PSScriptAnalyzer # Static analysis +Invoke-Build -Task InjectionHunter # Security scan +Invoke-Build -Task Export-CommandHelp # Generate help documentation +Invoke-Build -Task Clean # Remove build output ``` -## Documentation - -### Help Documentation - -- Update comment-based help in function when changing parameters -- Run `Invoke-Build Export-CommandHelp` to regenerate help files -- Markdown help is generated in `docs/help/` -- MAML help (`.xml`) is generated for `Get-Help` command - -### Project Documentation - -Comprehensive guides are available: -- `docs/getting-started.md` - Setup and first steps -- `docs/development.md` - Development workflow -- `docs/ci-cd.md` - CI/CD and publishing - -## CI/CD Integration - -### GitHub Actions Workflow - -- **Triggers**: Pull requests, pushes to main, manual dispatch -- **Quality Gates**: Unit tests, code analysis, security scans -- **Automated**: Version bumping, releases, publishing - -### Pipeline Support Guidance - -- Support pipeline input only when it improves usability -- Do not force pipeline support for configuration or control functions -- Pipeline support must be tested explicitly +--- -## Quick Reference +## Testing -### File Locations +- Use Pester 5+ +- Mock all external dependencies +- Cover parameter validation, success, and failure scenarios +- Target ≥ 70% code coverage -- **New public function**: `src/Public/Verb-Noun.ps1` -- **New private function**: `src/Private/VerbNoun.ps1` -- **Test file**: Same location as function, add `.Tests.ps1` suffix -- **Build script**: `PSScriptModule.build.ps1` -- **Module manifest**: `src/PSScriptModule.psd1` +--- -### Need Help? +## When Uncertain -- Check `docs/` for comprehensive documentation -- Run `Invoke-Build ?` to list all available tasks -- Review existing functions for patterns and examples +- Follow existing patterns in the repository +- Prefer private helper functions over changing public APIs +- Add tests that document assumptions --- -**Remember**: This is a template project. Code you generate will be used by other developers, so prioritize clarity, testing, and documentation. +## Protected — Do Not Modify Without Explicit Instruction + +- `.github/workflows/` — CI/CD pipelines +- `GitVersion.yml` — versioning configuration +- `tests/PSScriptAnalyzer/PSScriptAnalyzerSettings.psd1` — analysis rules diff --git a/AGENTS.md b/AGENTS.md index 01c91ae..85fc87d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,208 +1,114 @@ # AI Coding Agent Guidelines -This document provides guidance for AI coding assistants (Copilot, Claude, etc.) when generating code in this repository. -All code should be **production-ready, maintainable, tested, and auditable**. +All generated code must be **production-ready, maintainable, tested, and auditable**. --- -## Purpose +## Bias -AI agents must generate code that: - -- Preserves existing behavior and public APIs -- Follows PowerShell best practices -- Includes proper testing, validation, and error handling -- Is readable, secure, and auditable -- Can be integrated into CI/CD pipelines without modification +- **Prefer**: explicit, simple, pipeline-friendly, and testable functions +- **Avoid**: aliases, complex abstractions, environment-specific assumptions, and secrets in code --- -## Hard Rules (Must Follow) +## Core Principles -- Preserve original exception types and stack traces; never throw strings instead of exceptions -- Maintain function parameter validation (`[ValidateNotNullOrEmpty()]`, `[ValidateScript()]`, etc.) -- Do not remove or bypass existing tests, analyzers, or security scans -- Avoid writing secrets, credentials, or environment-specific paths in code -- Use approved PowerShell verbs (`Get-Verb`) and proper file naming -- Always create a `.Tests.ps1` file for each new function -- Follow begin/process/end pattern for functions that support pipeline input -- Do not use aliases in production code (e.g., `gci` instead of `Get-ChildItem`) -- Always include `[CmdletBinding()]` in public functions +- **Reliability over cleverness** — code must be predictable and behave correctly across all supported platforms +- **Explicit over implicit** — no hidden side effects; validate parameters at every function boundary +- **Tested and auditable** — every function must have tests and pass static analysis before merging --- -## Coding Patterns and Conventions - -### Function Structure - -**Public Functions** (exported): +## Build & Test Commands -- Location: `src/Public/Verb-Noun.ps1` -- Naming: `Verb-Noun` (approved verbs only) -- Must have comment-based help -- Must support pipeline input and `-WhatIf` for destructive operations +```powershell +# Build module +Invoke-Build -Task Build -**Private Functions** (internal): - -- Location: `src/Private/VerbNoun.ps1` -- Naming: descriptive; not exported -- Used to keep public APIs stable - ---- +# Run all tests (unit + static analysis + security scan) +Invoke-Build -Task Test -### Error Handling (Recommended Pattern) +# Run unit tests only +Invoke-Build -Task UnitTests -Use this simple pattern for all functions: - - try { - $result = Invoke-Operation - } - catch { - # Human-readable message - Write-Verbose "$($MyInvocation.MyCommand) Operation failed: $_" - # Full stack trace for debugging - Write-Verbose "StackTrace: $($_.ScriptStackTrace)" - # Preserve original exception - throw $_ - } - -**Notes:** - -- Preserves exception type and stack trace -- Safe for nested function calls -- Verbose logging provides context without double logging -- Optional: append function name chain if deeper context is needed - ---- +# Run static analysis +Invoke-Build -Task PSScriptAnalyzer -### Parameter Validation +# Run security scan +Invoke-Build -Task InjectionHunter - param ( - [Parameter(Mandatory, ValueFromPipeline)] - [ValidateNotNullOrEmpty()] - [string]$Name - ) +# Generate help documentation +Invoke-Build -Task Export-CommandHelp -- Always validate inputs at function boundaries -- Use `[ValidateSet()]`, `[ValidateScript()]`, or `[ValidatePattern()]` when applicable +# Clean build output +Invoke-Build -Task Clean +``` --- -### Pipeline Support +## Design Principles -- Use `begin {}`, `process {}`, `end {}` blocks for pipeline processing -- Collect results in `begin { $results = [System.Collections.Generic.List[object]]::new() }` -- Return results in `end { return $results }` +- **Fail Fast** — validate at the function boundary, throw immediately on invalid input; never let bad data propagate deeper into the call stack +- **Single Responsibility** — one function, one purpose; if a function needs an "and" to describe what it does, split it +- **Open-Closed** — extend behavior via parameter sets and new private helpers; do not modify working public APIs +- **Least Surprise** — functions must behave like built-in cmdlets: support pipeline where it makes sense, return objects not strings, respect `-WhatIf`, and write to the correct streams --- -### Comment-Based Help Template +## DO / DO NOT - function Verb-Noun { - <# - .SYNOPSIS - One-line description +### DO - .DESCRIPTION - Detailed description +- Use `[CmdletBinding()]` and `[OutputType()]` on every function +- Use approved PowerShell verbs — verify with `Get-Verb` +- Validate parameters with `[ValidateNotNullOrEmpty()]`, `[ValidateSet()]`, `[ValidateScript()]`, or `[ValidatePattern()]` +- Use `begin`/`process`/`end` blocks for pipeline-accepting functions; collect in `begin`, add in `process`, return in `end` +- Add `SupportsShouldProcess` and call `$PSCmdlet.ShouldProcess()` for destructive operations; omit for read-only functions +- Include comment-based help with `.SYNOPSIS`, `.DESCRIPTION`, `.PARAMETER`, `.INPUTS`, `.OUTPUTS`, and `.EXAMPLE` +- In `catch`, log context with `Write-Verbose "$($MyInvocation.MyCommand) failed for '$Input': $_"` then `throw $_` +- Create a `.Tests.ps1` file alongside every new function - .PARAMETER Name - Description of parameter +### DO NOT - .EXAMPLE - Verb-Noun -Name 'Test' - - .OUTPUTS - Type returned - - .NOTES - Additional notes - #> - } +- Never throw strings — always re-throw the original exception (`throw $_`) +- Never remove or bypass tests, PSScriptAnalyzer, or security scans +- Never use aliases (`Get-ChildItem`, not `gci`) +- Never write secrets, credentials, or environment-specific paths in code +- Never ignore or silently swallow errors +- Never use `Write-Host` — use `Write-Verbose`, `Write-Warning`, or `Write-Error` instead +- Never mix output types from the same function +- Never manually update `FunctionsToExport` in the module manifest — it is populated automatically by the build --- -## Testing Guidelines - -- Every function must have a corresponding `.Tests.ps1` file -- Use Pester 5+ for unit tests -- Test **both success and failure scenarios** -- Mock external dependencies as needed -- Ensure code coverage ≥ 80% -- Do not bypass tests or analyzers - -**Example Test Structure:** - - Describe 'Verb-Noun' { - Context 'Parameter Validation' { - It 'Should require mandatory parameters' { { Verb-Noun } | Should -Throw } - } - Context 'Functionality' { - It 'Should return expected result' { - $result = Verb-Noun -Name 'Test' - $result | Should -Not -BeNullOrEmpty - } - } - } - ---- +## File Layout -## CI/CD and Quality +| Type | Location | Naming | +|-------------------------|-------------------------------|-----------------------------------------------------------------| +| Public function + test | `src/Public/Verb-Noun.ps1` | `Verb-Noun` — approved verb, hyphen required | +| Private function + test | `src/Private/VerbNoun.ps1` | `VerbNoun` — PascalCase, no hyphen, not exported | +| Classes | `src/Classes/ClassName.ps1` | `ClassName` — PascalCase; loaded before functions by the build | -- Ensure PSScriptAnalyzer passes with no errors -- Security scans must pass -- Automated pipelines should run tests and build checks on every pull request -- Maintain semantic versioning with proper commit messages +Public functions are automatically exported by the build. Private functions must **not** follow `Verb-Noun` naming to avoid accidental export. Classes are available to all module consumers but are not listed in `FunctionsToExport`. --- -## Quick Reference for AI Agents - -- Preserve **behavior, types, and stack traces** -- Always include **parameter validation** -- Use `[CmdletBinding()]` and `-WhatIf` support -- Throw original exceptions; log context in `Write-Verbose` -- Keep code **readable, maintainable, and auditable** -- Write tests for every new function -- Follow approved PowerShell verbs and naming conventions -- Use `begin/process/end` for pipeline support -- Do not write secrets or environment-specific values - ---- - -## Example AI-Friendly Function Template - - function Get-Something { - [CmdletBinding()] - param ( - [Parameter(Mandatory, ValueFromPipeline)] - [ValidateNotNullOrEmpty()] - [string]$Name - ) - - begin { Write-Verbose "Starting $($MyInvocation.MyCommand)" } - - process { - try { - # Implementation goes here - } - catch { - Write-Verbose "$($MyInvocation.MyCommand) failed: $_" - Write-Verbose "StackTrace: $($_.ScriptStackTrace)" - throw $_ - } - } +## Testing - end { Write-Verbose "Completed $($MyInvocation.MyCommand)" } - } +- Use Pester 5+ +- Mock all external dependencies +- Cover parameter validation, success, and failure scenarios +- Target ≥ 70% code coverage --- -✅ **Summary:** +## Commit Messages -Follow this document when generating code in this repository. This ensures: +Use `+semver:` tags to control version bumps: -- Consistent, maintainable, and auditable code -- Proper error handling and logging -- Pipeline-friendly, tested, and secure functions -- AI agents generate **safe and production-ready** code without human rework +| Tag | Effect | +|-----------------------------------------|--------------------| +| `+semver: breaking` or `+semver: major` | Major version bump | +| `+semver: feature` or `+semver: minor` | Minor version bump | +| `+semver: fix` or `+semver: patch` | Patch version bump | +| `+semver: none` or `+semver: skip` | No bump |