diff --git a/.codex/skills/change-delivery-workflow/SKILL.md b/.codex/skills/change-delivery-workflow/SKILL.md index 97dc5cd..def7922 100644 --- a/.codex/skills/change-delivery-workflow/SKILL.md +++ b/.codex/skills/change-delivery-workflow/SKILL.md @@ -34,7 +34,7 @@ If the repo does not provide a clear versioning contract, default to a `CHANGELO Use this sequence before editing: -1. Read the repository guidance and inspect the working tree. +1. Read the repository guidance, inspect the working tree, fetch and prune `origin`, require a usable upstream, and confirm the branch contains `origin/main`. Stop without editing, pulling, rebasing, or merging if any freshness check fails. 2. If the current branch is `main` or `master`, create and switch to a working branch before making changes. 3. Review the relevant implementation, tests, docs, and existing validators before choosing the smallest safe change. 4. Decide whether the repo already defines a release workflow that should be respected for this change. diff --git a/.codex/skills/downstream-guidance-sync/SKILL.md b/.codex/skills/downstream-guidance-sync/SKILL.md index 38f830b..07361c0 100644 --- a/.codex/skills/downstream-guidance-sync/SKILL.md +++ b/.codex/skills/downstream-guidance-sync/SKILL.md @@ -52,7 +52,7 @@ If the downstream repo is missing `scripts/Initialize-DownstreamRepo.ps1` or `.c When the user requests sync changes, use this sequence: -1. Confirm the downstream repo has a clean working tree. +1. Confirm both the template and downstream repositories have clean working trees, usable upstreams, and branches containing `origin/main`; fetch and prune each `origin`. Stop without applying, pulling, rebasing, or merging if any check fails. 2. Create or switch to a non-main branch in the downstream repo, for example: ```powershell @@ -121,4 +121,4 @@ It must not update downstream source, tests, Pester configuration, PSScriptAnaly ## Agent Role -Treat the script as the source of truth for deterministic behavior. The agent role is to orchestrate: run the script, interpret output, inspect diffs, identify whether cleanup assets were delivered, run validation, prepare commits, and draft PR text. \ No newline at end of file +Treat the script as the source of truth for deterministic behavior. The agent role is to orchestrate: run the script, interpret output, inspect diffs, identify whether cleanup assets were delivered, run validation, prepare commits, and draft PR text. diff --git a/.codex/skills/downstream-repo-cleanup/SKILL.md b/.codex/skills/downstream-repo-cleanup/SKILL.md index 8ee5a7b..b218afc 100644 --- a/.codex/skills/downstream-repo-cleanup/SKILL.md +++ b/.codex/skills/downstream-repo-cleanup/SKILL.md @@ -54,7 +54,7 @@ powershell.exe -NoProfile -File .\scripts\Initialize-DownstreamRepo.ps1 -OutputF When the user requests cleanup changes, use this sequence: -1. Confirm the repository is still in the immediate post-create window. +1. Confirm the repository is still in the immediate post-create window, has a clean working tree and usable upstream, and contains the latest `origin/main` after fetching and pruning. Stop without applying, pulling, rebasing, or merging if the freshness preflight fails. 2. Run the cleanup script with the intended project shape. 3. Preserve or insert the README template version badge during cleanup. 4. Inspect the diff and confirm changes are limited to template-maintainer artifact removal, inherited guidance rewrites, and project-type scaffold normalization. diff --git a/.codex/skills/powershell-authoring/SKILL.md b/.codex/skills/powershell-authoring/SKILL.md new file mode 100644 index 0000000..9c0439e --- /dev/null +++ b/.codex/skills/powershell-authoring/SKILL.md @@ -0,0 +1,10 @@ +--- +name: powershell-authoring +description: Author Windows PowerShell 5.1 Desktop functions, modules, and scripts with the template conventions. +--- + +# Windows PowerShell Authoring + +- Target Windows PowerShell 5.1 Desktop, full .NET Framework, and Windows-only behavior; do not use PowerShell 6+ syntax or platform variables. +- Use advanced functions, approved verbs, PascalCase parameters, useful validation, comment-based help, terminating errors, and structured output. +- Place `SupportsShouldProcess` around only the mutation; avoid hidden side effects and `Write-Host` for functional output. diff --git a/.codex/skills/powershell-authoring/agents/openai.yaml b/.codex/skills/powershell-authoring/agents/openai.yaml new file mode 100644 index 0000000..d1f8245 --- /dev/null +++ b/.codex/skills/powershell-authoring/agents/openai.yaml @@ -0,0 +1,3 @@ +display_name: "Windows PowerShell Authoring" +short_description: "Author Windows PowerShell 5.1 code" +default_prompt: "Use $powershell-authoring to implement this Windows PowerShell 5.1 change." diff --git a/.codex/skills/powershell-external-services/SKILL.md b/.codex/skills/powershell-external-services/SKILL.md new file mode 100644 index 0000000..751cc80 --- /dev/null +++ b/.codex/skills/powershell-external-services/SKILL.md @@ -0,0 +1,11 @@ +--- +name: powershell-external-services +description: Implement or review Windows PowerShell 5.1 integrations with Graph, REST, credentials, deprecations, and testable boundaries. +--- + +# Windows PowerShell External Services + +- Prefer Microsoft Graph PowerShell when supported; justify raw REST calls. +- Verify service contracts rather than inventing cmdlets, parameters, or API behavior. +- Keep credentials and tokens out of source and output; isolate integration boundaries for mocking and tests. +- Preserve Windows PowerShell 5.1 compatibility and document deprecated service migrations. diff --git a/.codex/skills/powershell-external-services/agents/openai.yaml b/.codex/skills/powershell-external-services/agents/openai.yaml new file mode 100644 index 0000000..552f7a2 --- /dev/null +++ b/.codex/skills/powershell-external-services/agents/openai.yaml @@ -0,0 +1,3 @@ +display_name: "Windows PowerShell External Services" +short_description: "Implement Windows PowerShell service integrations" +default_prompt: "Use $powershell-external-services to implement this Windows PowerShell 5.1 integration." diff --git a/.codex/skills/powershell-testing-review/SKILL.md b/.codex/skills/powershell-testing-review/SKILL.md new file mode 100644 index 0000000..d9fa156 --- /dev/null +++ b/.codex/skills/powershell-testing-review/SKILL.md @@ -0,0 +1,10 @@ +--- +name: powershell-testing-review +description: Test or review Windows PowerShell 5.1 changes using Pester, mocks, analyzer expectations, and repository validation. +--- + +# Windows PowerShell Testing And Review + +- Test observable contracts and failure behavior with Pester under Windows PowerShell 5.1 Desktop. +- Mock filesystem, network, service, time, and environment access; do not require live services or credentials. +- Review help, `ShouldProcess`, validation, errors, secrets, stable output, and Windows PowerShell compatibility. diff --git a/.codex/skills/powershell-testing-review/agents/openai.yaml b/.codex/skills/powershell-testing-review/agents/openai.yaml new file mode 100644 index 0000000..10f7af2 --- /dev/null +++ b/.codex/skills/powershell-testing-review/agents/openai.yaml @@ -0,0 +1,3 @@ +display_name: "Windows PowerShell Testing And Review" +short_description: "Test and review Windows PowerShell 5.1 changes" +default_prompt: "Use $powershell-testing-review to validate this Windows PowerShell 5.1 change." diff --git a/.codex/skills/readme-alignment/SKILL.md b/.codex/skills/readme-alignment/SKILL.md index 8062b42..8b21871 100644 --- a/.codex/skills/readme-alignment/SKILL.md +++ b/.codex/skills/readme-alignment/SKILL.md @@ -52,7 +52,7 @@ powershell.exe -NoProfile -File .\scripts\Invoke-ReadmeAlignment.ps1 -OutputForm When the user requests alignment changes, use this sequence: -1. Confirm the repo is a supported downstream repository. +1. Confirm the repo is a supported downstream repository with a clean working tree and usable upstream, then fetch and prune `origin` and confirm it contains `origin/main`. Stop without applying, pulling, rebasing, or merging if the freshness preflight fails. 2. Create or switch to a non-main branch before applying changes. 3. Run the alignment script: diff --git a/.codex/skills/runtime-policy-update/SKILL.md b/.codex/skills/runtime-policy-update/SKILL.md index 0fd80a4..bf12ca6 100644 --- a/.codex/skills/runtime-policy-update/SKILL.md +++ b/.codex/skills/runtime-policy-update/SKILL.md @@ -31,13 +31,14 @@ If a requested version or compatibility decision is unclear, inspect the current Use this order: -1. Update `eng/runtime-policy.json` first. +1. Confirm a clean working tree and usable upstream, fetch and prune `origin`, and verify the branch contains `origin/main`. Stop without editing, pulling, rebasing, or merging if the freshness preflight fails. +2. Update `eng/runtime-policy.json` first. 2. Align policy-managed configuration: - `.github/workflows/ci.yml` - `.vscode/settings.json` - `src/TemplateModule.psd1` - `templates/module/ModuleName/ModuleName.psd1` -3. Update generated Markdown: +4. Update generated Markdown: ```powershell powershell.exe -NoProfile -File .\scripts\Update-GeneratedMarkdown.ps1 diff --git a/.codex/skills/template-version-release/SKILL.md b/.codex/skills/template-version-release/SKILL.md index aa1e9c3..ebbbded 100644 --- a/.codex/skills/template-version-release/SKILL.md +++ b/.codex/skills/template-version-release/SKILL.md @@ -29,6 +29,8 @@ Before acting, identify: If the next version or bump level is unclear, inspect the change impact and ask before editing release metadata. +Before modifying release metadata, confirm a clean working tree and usable upstream, fetch and prune `origin`, and verify the branch contains `origin/main`. Stop without editing, pulling, rebasing, or merging if the freshness preflight fails. + ## Pre-Merge Release Prep When preparing a release PR: @@ -100,7 +102,8 @@ The workflow is complete when: - `scripts/Test-TemplateVersion.ps1` passes before the release PR is opened or updated - after merge, `main` is fast-forwarded and the lightweight `vX.Y.Z` tag is pushed - `scripts/Test-TemplateVersion.ps1 -CheckTag` passes after tagging -- a GitHub Release exists for `vX.Y.Z` with notes derived from the matching `CHANGELOG.md` section`n- GitHub shows the pushed tag as verified and associated with the intended merged release commit +- a GitHub Release exists for `vX.Y.Z` with notes derived from the matching `CHANGELOG.md` section +- GitHub shows the pushed tag as verified and associated with the intended merged release commit - the merged local branch has been cleaned up after the remote branch deletion or merge is confirmed ## Stop Conditions diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4318589..7589181 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,167 +1,11 @@ -# AI Coding Instructions For This Repository +# AI Coding Instructions -## AI Governance Model +Apply these requirements to all repository work. -This repository follows a structured AI governance model: - -- `/docs/ai-behavioral-contract.md` -- `/docs/ai-interaction-loop.md` - -Those documents are the canonical source for broad AI behavior, human accountability, and the interaction workflow used to evaluate AI output. - -This file translates that model into enforceable repository rules for generated code, tests, reviews, automation, documentation, and commit messages. - ---- - -This repository is a GitHub template for PowerShell projects. Use these instructions for authored PowerShell project code, tests, automation, pull request review comments, and documentation. Do not use these instructions for container bootstrap behavior, editor configuration, or environment initialization unless this file, `README.md`, `/docs`, or the task-specific template explicitly includes that area. - -When examples in `/examples`, `/templates`, `/docs`, `README.md`, or comment-based help differ from this file, this file wins unless `README.md` or `/docs/EXCEPTIONS.md` documents a maintainer-approved exception. PowerShell version requirements are resolved by the rule in `PowerShell Compatibility`. - -## Conflict Handling - -Resolve conflicts in this order: - -1. Safety and security. -2. Deterministic automation behavior. -3. Documented PowerShell version and supported platforms. -4. Repository conventions and closest matching template. -5. User preference. - -- Ask the user or maintainer for clarification when a prompt is too ambiguous for a safe implementation. -- If clarification is unavailable, make the assumption that requires the least deviation from this file and state it. -- Do not resolve ambiguity by guessing. State assumptions explicitly when required. -- Briefly explain any decision that rejects compatibility, convention, template guidance, or user preference. - -## Simplicity And Complexity Management - -Code is a liability. Every line of code adds maintenance cost, testing requirements, and potential failure modes. - -When generating code: - -- Prefer the simplest solution that safely satisfies the requirement. -- Prefer native PowerShell features and standard language capabilities over additional abstractions, wrappers, frameworks, or dependencies. -- Do not introduce helper functions, classes, configuration layers, design patterns, or reusable abstractions unless they provide clear value for the stated requirement. -- Optimize for readability and maintainability over cleverness or theoretical extensibility. -- Prefer code that is clear enough not to need explanatory inline comments. If a section needs heavy explanation, simplify the code first. -- Keep the happy path easy to follow. -- Apply error handling and validation where risk exists, but avoid unnecessary defensive code that obscures intent. -- When modifying existing code, solve the requested problem with the smallest reasonable change and avoid unrelated refactoring. -- Do not create future-proofing, scalability mechanisms or architectural layers unless explicitly requested or clearly justified by the requirement. - -When multiple valid implementations exist, prefer the solution with the lowest operational and cognitive complexity. - -## Generation Checklist - -For new or changed PowerShell code, prefer this checklist over adding one-off patterns: - -- Function shape: use production-quality advanced functions with `CmdletBinding()`, a `param()` block, approved verbs, PascalCase parameters, clear names, and small composable public functions. -- State and output: add `SupportsShouldProcess` for mutations, wrap only the mutation, and return structured objects rather than display-formatted text. -- Errors and security: use terminating errors with useful context, avoid undocumented `Write-Host`, validate external input, and never hardcode or log secrets, credentials, tenant IDs, or tokens. -- Tests and help: include comment-based help for public functions and scripts, and focused Pester tests that mock file I/O, network calls, service calls, time, and environment access. -- Comments: do not add comments that merely restate obvious code behavior. Add targeted comments only when they preserve context the code cannot express directly, such as rationale, compatibility constraints, environmental quirks, safety assumptions, or other non-obvious behavior. -- Verifiable output: prefer behavior and output contracts that can be tested, reviewed, or validated. - -## Repository Structure And Templates - -Place source code in `/src`, tests in `/tests`, docs in `/docs`, and examples in `/examples` unless an existing project structure clearly uses another convention. Do not place executable business logic in the repository root or at module import time, except for required dependency loading, configuration setup, or module initialization. - -Start from the closest matching template in `/templates` when it fits the task: - -- `/templates/functions/read-only-function-template.ps1` -- `/templates/functions/state-changing-function-template.ps1` -- `/templates/patterns/retry-pattern-template.ps1` -- `/templates/tests/read-only-function-tests-template.ps1` -- `/templates/tests/state-changing-function-tests-template.ps1` -- `/templates/module/ModuleName/ModuleName.psd1` -- `/templates/module/ModuleName/ModuleName.psm1` -- `/templates/scripts/advanced-script-template.ps1` - -If multiple templates apply or conflict, choose in this order: state-changing function, read-only function, tests, retry pattern, module, script. Document deviations from any template considered for the task. If no applicable template exists, follow clear repository conventions. If no applicable template exists and conventions are unclear, ask for maintainer guidance; when guidance is unavailable, base the implementation on the most similar existing code and document the reasoning for that choice. - -## Repo-Local Skills - -Repo-local Codex skills are stored under `.codex/skills/`. When asked to synchronize downstream AI guidance, use `.codex/skills/downstream-guidance-sync/SKILL.md` and operate `scripts/Invoke-TemplateGuidanceSync.ps1` through the documented audit, branch, validation, commit, and pull request workflow. Do not manually edit downstream guidance files outside the sync script allowlist unless the user explicitly asks for manual repair after a script failure. - -When asked to update the Windows PowerShell runtime, GitHub Actions runner, or pinned PowerShell tooling, use `.codex/skills/runtime-policy-update/SKILL.md`. Treat `eng/runtime-policy.json` as the source of truth, use `scripts/Update-GeneratedMarkdown.ps1` for generated Markdown blocks, and validate with `scripts/Test-VersionPolicy.ps1`. - -When asked to prepare, validate, tag, publish, or clean up a template release version, use `.codex/skills/template-version-release/SKILL.md`. Keep `VERSION`, the README template-version badge, and `CHANGELOG.md` aligned, validate with `scripts/Test-TemplateVersion.ps1`, and create lightweight `vX.Y.Z` tags plus GitHub Releases only after the release PR is merged to `main`. - -When adding or updating repo-local skills, add or update Pester coverage in `tests/unit/SkillScaffold.Tests.ps1` for the skill file, metadata, required references, and agent discoverability. The Codex `quick_validate.py` helper may be used as an optional authoring check, but Pester is the repository validation standard. - -When asked to perform ordinary repository changes, use `.codex/skills/change-delivery-workflow/SKILL.md` to coordinate sandbox escalation, non-main branches, changelog updates, release decisions, commits, pull requests, and post-merge cleanup. - -When asked to normalize a newly created downstream repository from this template, use `.codex/skills/downstream-repo-cleanup/SKILL.md` and operate `scripts/Initialize-DownstreamRepo.ps1` through the documented audit, diff review, validation, and commit workflow. Preserve the downstream README workflow assets and stop once the repository has moved beyond the immediate post-create window. - -When asked to audit or align a downstream README to the shared portfolio skeleton, use `.codex/skills/readme-alignment/SKILL.md` and operate `scripts/Invoke-ReadmeAlignment.ps1` through the documented audit, branch, validation, diff-review, and commit workflow. -## PowerShell Compatibility - -Target Windows PowerShell 5.1 Desktop unless `README.md`, `/docs`, or the task-specific template declares another version under `PowerShell Version`, `Requirements`, or `Compatibility`. For version conflicts, use this precedence: `README.md`, then `/docs`, then task-specific template. If version requirements are missing, invalid, outdated, or unsupported, provide a Windows PowerShell 5.1 Desktop-compatible fallback and document the assumption. - -Avoid syntax, APIs, cmdlets, or modules that conflict with Windows PowerShell 5.1, the Desktop edition, the full .NET Framework, or Windows. This template is intentionally Windows-only; do not use PowerShell 6+ automatic variables such as `$IsWindows`, `$IsLinux`, or `$IsMacOS`. When downstream code needs an operating-system check, use a Windows PowerShell 5.1-compatible mechanism and test it. - -Avoid deprecated cmdlets, modules, and features. If deprecated behavior is unavoidable, isolate it, add a nearby warning comment, justify the limitation, and include a supported alternative, workaround, or future migration note when one exists. - -## External Services - -Prefer the Microsoft Graph PowerShell SDK over raw REST calls when the SDK provides equivalent functionality for the required operation. If raw REST is required, document why. - -Do not invent external service behavior, cmdlets, parameters, or API contracts. When service behavior is uncertain, provide a way to verify it before relying on it. - -Wrap external service interactions in helper functions when it improves consistency, mocking, or testability. Generated external-service code must support unit tests without live service calls. - -Apply the deprecation guidance in `PowerShell Compatibility` to external service modules and service-specific cmdlets. Keep unavoidable deprecated service interactions behind helper functions and document the migration path or limitation. - -## Formatting And Review - -Use 4 spaces for PowerShell indentation, same-line opening braces, single quotes unless interpolation is required, comments above the code they describe, no trailing whitespace, and LF line endings. - -When editing text files, preserve the file's existing line-ending behavior and avoid CRLF/LF-sensitive string replacements, especially in Markdown and documentation files. Prefer line-based or parser-aware edits when practical. Before committing text-file changes, run `git diff --check` and fix extra blank lines at EOF, missing expected blank lines, and trailing whitespace. - -In review, flag missing tests, missing comment-based help, analyzer violations, weak validation, missing `ShouldProcess`, unsafe secret handling, unmockable external calls, unstable output contracts, unverified claims, and speculative refactors outside the requested scope. - -Unless explicitly requested and justified, do not generate `Invoke-Expression`, empty catch blocks, plaintext secret handling, hardcoded credentials, live external service calls in tests, silent breaking changes, or unrelated formatting-only refactors. - -## Commit Messages - -When asked to generate, review, or revise commit messages, use the Conventional Commits format: - -```Markdown - -[optional scope]: - -[optional body] - -[optional footer(s)] - -``` - -Prefer these commit types unless the repository documents another standard: - -- `feat`: new user-facing or maintainer-facing functionality -- `fix`: bug fixes or corrected behavior -- `docs`: documentation-only changes -- `test`: adding or updating tests -- `refactor`: code restructuring that does not change behavior -- `style`: formatting, whitespace, or other non-behavioral style changes -- `ci`: CI workflow or automation changes -- `build`: build system, packaging, or dependency changes -- `chore`: maintenance changes that do not fit another type - -Use a concise, imperative, lowercase description without a trailing period. - -Prefer a scoped commit when it improves clarity, for example: - -- feat(templates): add state-changing function scaffold -- fix(tests): mock filesystem access in module tests -- docs(copilot): add simplicity guidance -- ci(pester): run tests on pull requests - -Use `!` after the type or scope for breaking changes: - -feat(module)!: require Windows PowerShell 5.1 - -Include a footer beginning with `BREAKING CHANGE:` when the change breaks existing behavior. - -Generate commit messages from the actual change being made. Avoid generic descriptions such as "update code", "fix issue", "changes", or "improvements". - -Do not invent commit details that are not supported by the diff, staged changes, or user-provided context. If the change is ambiguous, provide the best commit message candidate and briefly state the assumption. +- Follow `AGENTS.md`; prioritize safety, explicit user direction, deterministic repository behavior, Windows PowerShell 5.1 Desktop compatibility, then local convention. +- Before modifying tracked content, complete the freshness gate in `AGENTS.md`. Do not pull, rebase, merge, or edit when it fails. +- Be truthful about commands, APIs, and validation. State material assumptions and never expose secrets. +- Make the smallest maintainable change that satisfies the request. Avoid speculative abstractions, unrelated refactors, and unverified claims. +- Target Windows PowerShell 5.1 Desktop on Windows and full .NET Framework. Do not use PowerShell 6+ automatic variables such as `$IsWindows`, `$IsLinux`, or `$IsMacOS`. +- Use `.github/instructions/` and task-scoped skills for detailed file- and task-specific rules. +- Run relevant validation, inspect the diff, and use Conventional Commits for commits. diff --git a/.github/Instructions/environment-setup.md b/.github/instructions/environment-setup.md similarity index 100% rename from .github/Instructions/environment-setup.md rename to .github/instructions/environment-setup.md diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md new file mode 100644 index 0000000..d823118 --- /dev/null +++ b/.github/instructions/markdown.instructions.md @@ -0,0 +1,9 @@ +--- +applyTo: "**/*.md" +--- + +# Markdown Rules + +- Keep claims factual, concise, and aligned with implemented Windows PowerShell 5.1 behavior. +- Preserve generated Markdown blocks; change their source and run the generator instead of editing their contents. +- Use blank lines around headings and lists; validate Markdown after editing. diff --git a/.github/instructions/powershell.instructions.md b/.github/instructions/powershell.instructions.md new file mode 100644 index 0000000..321a452 --- /dev/null +++ b/.github/instructions/powershell.instructions.md @@ -0,0 +1,9 @@ +--- +applyTo: "**/*.ps1,**/*.psm1,**/*.psd1" +--- + +# PowerShell Rules + +- Target Windows PowerShell 5.1 Desktop, full .NET Framework, and Windows-only behavior; do not use PowerShell 6+ syntax or automatic platform variables. +- Prefer small advanced functions, terminating errors with useful context, structured output, and `ShouldProcess` around mutations. +- Keep code testable: isolate external effects and avoid live service calls in tests. diff --git a/AGENTS.md b/AGENTS.md index 4d2ee42..762f2ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,42 +1,26 @@ # AGENTS.md -## Repository Instructions +## Entry Point -This repository's primary AI guidance is maintained in: +Read this file before repository work. It supplies cross-agent rules; use the layered guidance below for task detail. -```text -.github/copilot-instructions.md -``` +Precedence is: safety and security, explicit user direction, deterministic repository behavior and supported platforms, then local convention. Stop and ask when requirements are materially ambiguous. Keep changes scoped, protect secrets, and verify claims with the relevant validation. -Before performing coding, review, documentation, test, automation, or repository-maintenance work, coding agents must read and follow `.github/copilot-instructions.md`. +## Mandatory Freshness Gate -That file is the authoritative instruction source for: +Before any modification, confirm the working tree is clean, fetch and prune the tracking remote, and verify the branch contains the latest `origin/main`. Stop without pulling, rebasing, merging, or editing if no usable tracking remote exists, the tree is dirty, the branch is behind or diverged, or it lacks the latest `origin/main`. -- AI governance requirements -- conflict resolution rules -- code generation standards -- complexity management guidance -- PowerShell compatibility requirements -- external service guidance -- commit message conventions -- review expectations +## Guidance Layers -If `.github/copilot-instructions.md` is missing or unavailable, stop and report that the repository guidance cannot be loaded rather than guessing. +- `.github/copilot-instructions.md` contains Copilot-compatible always-on rules. +- `.github/instructions/` contains path-specific Markdown and PowerShell rules. +- `.codex/skills/` contains task-scoped detail; read the matching skill before conditional work. -If guidance in this file conflicts with `.github/copilot-instructions.md`, `.github/copilot-instructions.md` is authoritative. +## Skill Routing -## Repo-Local Skills +- `powershell-authoring`: Windows PowerShell 5.1 Desktop functions, modules, and scripts. +- `powershell-testing-review`: Pester, analyzer, help, review, and validation. +- `powershell-external-services`: Graph, REST, credentials, deprecation, and integration boundaries. +- Workflow skills: `.codex/skills/change-delivery-workflow/SKILL.md`, `.codex/skills/downstream-repo-cleanup/SKILL.md`, `.codex/skills/downstream-guidance-sync/SKILL.md`, `.codex/skills/readme-alignment/SKILL.md`, `.codex/skills/runtime-policy-update/SKILL.md`, and `.codex/skills/template-version-release/SKILL.md`. -Repo-local Codex skills are stored under `.codex/skills/`. - -For general repository change delivery, agents should use `.codex/skills/change-delivery-workflow/SKILL.md` to handle sandbox escalation, non-`main` branches, changelog updates, release decisions, commits, pull requests, and post-merge cleanup consistently. - -For immediate post-create normalization of repositories created from this template, agents should use `.codex/skills/downstream-repo-cleanup/SKILL.md` together with `scripts/Initialize-DownstreamRepo.ps1` before adding project-specific implementation, docs, tests, ADRs, or CI changes. - -For downstream AI guidance synchronization and delivery of newly added cleanup and README workflow assets into older downstream repositories, agents should use `.codex/skills/downstream-guidance-sync/SKILL.md` together with `scripts/Invoke-TemplateGuidanceSync.ps1` instead of manually copying guidance files. Cleanup itself still runs from the downstream repository through `scripts/Initialize-DownstreamRepo.ps1`. - -For shared README alignment after cleanup, agents should use `.codex/skills/readme-alignment/SKILL.md` together with `scripts/Invoke-ReadmeAlignment.ps1`. - -For template runtime and tooling policy updates, agents should use `.codex/skills/runtime-policy-update/SKILL.md` together with `eng/runtime-policy.json`, `scripts/Update-GeneratedMarkdown.ps1`, and `scripts/Test-VersionPolicy.ps1`. - -For template version release preparation, validation, tagging, GitHub Release publishing, and cleanup, agents should use `.codex/skills/template-version-release/SKILL.md` together with `scripts/Test-TemplateVersion.ps1`. +If `.github/copilot-instructions.md` is unavailable, stop and report that repository guidance cannot be loaded. diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a30a5..acd21cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,18 @@ This project uses Semantic Versioning for the template itself. ### Changed -- Clarified the README opening positioning so the template reads as both a practical PowerShell foundation and a documented exploration of effective PowerShell engineering. +## 0.4.0 - 2026-08-29 + +### Added + +- Added layered task-scoped AI guidance, including Markdown and Windows PowerShell 5.1 path rules and three discoverable PowerShell skills. +- Added mandatory remote-freshness preflight checks to mutable template and downstream workflows. +- Added ADR 0003 documenting the task-scoped guidance and freshness policy. + +### Changed + +- Updated downstream guidance sync and cleanup to deliver and retain the new guidance baseline. +- Updated workflow tests, documentation, and release guidance for the layered model. ## 0.3.0 - 2026-07-10 diff --git a/README.md b/README.md index 04ba53c..6fdbf7d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![PowerShell 5.1](https://img.shields.io/badge/PowerShell-5.1-blue) -![Template Version](https://img.shields.io/badge/template-0.3.0-blue) +![Template Version](https://img.shields.io/badge/template-0.4.0-blue) A repeatable Windows PowerShell development template for building scripts, modules, and automation projects. @@ -72,7 +72,7 @@ That same philosophy also shapes how AI assistance is used in this template and 6. Add scripts, functions, modules, or automation under `src`. 7. Add project-specific Pester tests under `tests`. 8. Copy and adapt scaffolds from `templates` for new functions, scripts, modules, and tests when they fit the work. -9. Review the AI-assisted development guidance in `AGENTS.md` and `.github/copilot-instructions.md` before using AI-generated changes. +9. Review `AGENTS.md`, then use the always-on Copilot instructions, matching path-specific rules, and task-scoped skills before using AI-generated changes. 10. Run local validation: ```powershell @@ -130,7 +130,8 @@ Core repository structure: - `docs/`: operating model, durable decisions, and maintainer guidance - `scripts/Invoke-RepoChecks.ps1`: local and CI validation entrypoint - `eng/runtime-policy.json`: runtime, runner, and tooling source of truth -- `.github/copilot-instructions.md`: authoritative AI coding guidance +- `AGENTS.md`: cross-agent entrypoint and mandatory freshness gate +- `.github/instructions/` and `.codex/skills/`: path-specific and task-scoped guidance Template starting points include: @@ -188,7 +189,7 @@ powershell.exe -NoProfile -File .\scripts\Invoke-TemplateGuidanceSync.ps1 -Path Audit mode is the default and reports drift without changing files. To apply the safe sync set, create or switch the downstream repo to a non-main branch first, then run: ```powershell -git -C ..\downstream-repo switch -c chore/sync-template-guidance-0.3.0 +git -C ..\downstream-repo switch -c chore/sync-template-guidance-0.4.0 powershell.exe -NoProfile -File .\scripts\Invoke-TemplateGuidanceSync.ps1 -Path ..\downstream-repo -Apply ``` diff --git a/VERSION b/VERSION index 9325c3c..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 \ No newline at end of file +0.4.0 diff --git a/docs/agent-workflows.md b/docs/agent-workflows.md index 42e730c..e904eb4 100644 --- a/docs/agent-workflows.md +++ b/docs/agent-workflows.md @@ -6,6 +6,10 @@ The workflows are designed around a simple rule: agents may coordinate the work, Repo-local skills live under `.codex/skills/`. They tell compatible agents how to use the repository's existing scripts, validation commands, and review expectations. They are not a substitute for reading the repository guidance, inspecting diffs, or opening reviewed pull requests. +Before any mutable workflow, inspect the working tree, fetch and prune `origin`, confirm a usable upstream, and confirm the branch contains `origin/main`. Stop without pulling, rebasing, merging, or editing when that preflight fails. + +The guidance is layered: `AGENTS.md` contains cross-agent governance, `.github/copilot-instructions.md` contains always-on requirements, `.github/instructions/` contains path-specific rules, and `.codex/skills/` contains task-scoped detail. + ## Workflow Index | Workflow | Use When | Skill | Control | Validation | diff --git a/docs/copilot-instructions-reference.md b/docs/copilot-instructions-reference.md index 94c76a9..1e96249 100644 --- a/docs/copilot-instructions-reference.md +++ b/docs/copilot-instructions-reference.md @@ -8,7 +8,7 @@ The active repository-wide instructions live at: - `/.github/copilot-instructions.md` -That file is the source of truth for Copilot generation and review behavior in repositories created from this template. +That file is the Copilot-compatible always-on layer for generation and review behavior in repositories created from this template. Do not copy the full instruction text into this reference document. Keeping one canonical file reduces drift and makes the applied rules easier to audit. @@ -54,7 +54,7 @@ Before changing the Copilot instructions: ## Additional Instruction Files -Use `/.github/Instructions/` only for narrower guidance that should not live in the repository-wide instruction file. +Use `/.github/instructions/` only for narrower guidance that should not live in the repository-wide instruction file. Use `.codex/skills/` for conditional, task-scoped guidance rather than making it always-on. Good candidates: diff --git a/docs/decisions/0003-task-scoped-ai-guidance.md b/docs/decisions/0003-task-scoped-ai-guidance.md new file mode 100644 index 0000000..5d92f9e --- /dev/null +++ b/docs/decisions/0003-task-scoped-ai-guidance.md @@ -0,0 +1,19 @@ +# 0003 - Task-Scoped AI Guidance And Freshness Preflight + +## Status + +Accepted + +## Context + +The template needs detailed Windows PowerShell 5.1 authoring, testing, and external-service guidance, but making every rule universal makes instructions difficult to maintain and less relevant to non-PowerShell work. Mutable workflows also need a consistent guard against starting from stale or ambiguous remote state. + +## Decision + +Use layered guidance: lean always-on rules in `AGENTS.md` and `.github/copilot-instructions.md`, path-specific rules under `.github/instructions/`, and task-scoped Codex skills for PowerShell authoring, testing and review, and external services. Retain GitHub Copilot compatibility while designing skill routing for Codex. + +Every modifying workflow must check for a clean tree, fetch and prune `origin`, require an upstream, and verify that the branch contains the latest `origin/main`. It must stop without pulling, rebasing, merging, or editing if that preflight fails. + +## Consequences + +Detailed PowerShell guidance is discoverable when relevant while the universal instruction surface stays concise. Downstream sync and cleanup must deliver and retain the new files, and test fixtures must model a tracking remote. diff --git a/docs/template-evolution.md b/docs/template-evolution.md index 10cfc63..3b917a9 100644 --- a/docs/template-evolution.md +++ b/docs/template-evolution.md @@ -46,6 +46,10 @@ Use Architecture Decision Records under `docs/decisions/` for durable template d ADRs are for significant capabilities, workflow policies, ownership boundaries, or non-obvious tradeoffs. They are not required for routine plans, patch fixes, or release metadata updates. Pull requests remain the place for implementation plans and review discussion, and `CHANGELOG.md` remains outcome-focused. +## AI Guidance Model + +AI guidance is intentionally layered. `AGENTS.md` provides cross-agent governance and the freshness preflight; Copilot instructions contain concise always-on rules; path-specific instruction files apply by file type; and Codex skills carry detailed, task-scoped PowerShell and workflow guidance. This keeps Windows PowerShell 5.1 compatibility requirements available where needed without burdening unrelated work. + ## Template Versioning This repository versions the template itself with Semantic Versioning. The version describes the reusable baseline maintained by this repository, not the version of any downstream project created from it. diff --git a/scripts/Initialize-DownstreamRepo.ps1 b/scripts/Initialize-DownstreamRepo.ps1 index f0f7e27..04fcbcb 100644 --- a/scripts/Initialize-DownstreamRepo.ps1 +++ b/scripts/Initialize-DownstreamRepo.ps1 @@ -91,6 +91,25 @@ function Test-GitRepository { return ($LASTEXITCODE -eq 0) } +function Assert-RemoteFreshness { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$RepoPath) + + if (@(& git -C $RepoPath status --porcelain).Count -gt 0) { throw ('Refusing to apply changes because uncommitted changes exist: {0}' -f $RepoPath) } + $origin = & git -C $RepoPath remote get-url origin 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($origin)) { throw ('Refusing to apply changes because origin is not configured: {0}' -f $RepoPath) } + & git -C $RepoPath fetch --quiet --prune origin + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because origin cannot be fetched: {0}' -f $RepoPath) } + $upstream = & git -C $RepoPath rev-parse --abbrev-ref '@{upstream}' 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($upstream)) { throw ('Refusing to apply changes because the current branch has no upstream: {0}' -f $RepoPath) } + $counts = @((& git -C $RepoPath rev-list --left-right --count HEAD...$upstream) -split '\s+') + if ($LASTEXITCODE -ne 0 -or $counts.Count -ne 2) { throw ('Refusing to apply changes because upstream freshness cannot be determined: {0}' -f $RepoPath) } + if ([int]$counts[0] -gt 0 -and [int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch has diverged from its upstream: {0}' -f $RepoPath) } + if ([int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch is behind its upstream: {0}' -f $RepoPath) } + & git -C $RepoPath merge-base --is-ancestor origin/main HEAD + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because the branch does not contain the latest origin/main: {0}' -f $RepoPath) } +} + function Get-FileContent { [CmdletBinding()] param( @@ -244,6 +263,7 @@ function Get-TemplateVersionBadgeLine { function Get-SafeHelperName { [CmdletBinding()] + [OutputType([string])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] @@ -289,50 +309,38 @@ function Get-DownstreamReadmeContent { function Get-DownstreamAgentsContent { [CmdletBinding()] + [OutputType([string])] param() return @' # AGENTS.md -## Repository Instructions - -This repository's primary AI guidance is maintained in: - -```text -.github/copilot-instructions.md -``` - -Before performing coding, review, documentation, test, automation, or repository-maintenance work, coding agents must read and follow `.github/copilot-instructions.md`. +## Entry Point -That file is the authoritative instruction source for: +Read this file before repository work. It supplies cross-agent rules; use the layered guidance below for task detail. -- AI governance requirements -- conflict resolution rules -- code generation standards -- complexity management guidance -- PowerShell compatibility requirements -- external service guidance -- commit message conventions -- review expectations +## Mandatory Freshness Gate -If `.github/copilot-instructions.md` is missing or unavailable, stop and report that the repository guidance cannot be loaded rather than guessing. +Before any modification, confirm the working tree is clean, fetch and prune the tracking remote, and verify the branch contains the latest `origin/main`. Stop without pulling, rebasing, merging, or editing if no usable tracking remote exists, the tree is dirty, the branch is behind or diverged, or it lacks the latest `origin/main`. -If guidance in this file conflicts with `.github/copilot-instructions.md`, `.github/copilot-instructions.md` is authoritative. +## Guidance Layers -## Repo-Local Skills +- `.github/copilot-instructions.md` contains Copilot-compatible always-on rules. +- `.github/instructions/` contains path-specific Markdown and PowerShell rules. +- `.codex/skills/` contains task-scoped detail. -Repo-local Codex skills are stored under `.codex/skills/`. +## Skill Routing -For immediate post-create normalization of this downstream repository, agents should use `.codex/skills/downstream-repo-cleanup/SKILL.md` together with `scripts/Initialize-DownstreamRepo.ps1`. - -For downstream AI guidance synchronization from `powershell-dev-template`, agents should use `.codex/skills/downstream-guidance-sync/SKILL.md` together with `scripts/Invoke-TemplateGuidanceSync.ps1` instead of manually copying guidance files. - -For shared README alignment after cleanup, agents should use `.codex/skills/readme-alignment/SKILL.md` together with `scripts/Invoke-ReadmeAlignment.ps1`. +- `powershell-authoring`, `powershell-testing-review`, and `powershell-external-services` apply to matching Windows PowerShell 5.1 work. +- Use `downstream-repo-cleanup` with `scripts/Initialize-DownstreamRepo.ps1` only during immediate post-create cleanup. +- Use `downstream-guidance-sync` with `scripts/Invoke-TemplateGuidanceSync.ps1` to deliver template-owned guidance. +- Use `readme-alignment` with `scripts/Invoke-ReadmeAlignment.ps1` for the shared README skeleton. '@ } function Get-DownstreamAgentWorkflowsContent { [CmdletBinding()] + [OutputType([string])] param() return @' @@ -340,6 +348,8 @@ function Get-DownstreamAgentWorkflowsContent { This repository includes repo-local agent workflows for downstream setup and ongoing template guidance alignment. +Use the layered guidance model: `AGENTS.md` provides governance and the mandatory freshness gate, `.github/copilot-instructions.md` provides always-on rules, `.github/instructions/` provides path-specific rules, and `.codex/skills/` provides task-scoped detail. Before any modifying workflow, require a clean tree, usable upstream, a fetch/prune of `origin`, and a branch containing `origin/main`; stop without pulling, rebasing, merging, or editing when this fails. + The workflows are designed around a simple rule: agents may coordinate the work, but deterministic scripts, Pester tests, and human review remain the controls that make the work reliable. Repo-local skills live under `.codex/skills/`. They tell compatible agents how to use the repository's existing scripts, validation commands, and review expectations. They are not a substitute for reading the repository guidance, inspecting diffs, or opening reviewed pull requests. @@ -373,6 +383,7 @@ The downstream guidance sync workflow is intentionally narrow. It may update AI function Get-DownstreamDecisionsReadmeContent { [CmdletBinding()] + [OutputType([string])] param() return @' @@ -417,40 +428,23 @@ Keep ADRs brief and outcome-focused. They should explain durable reasoning, not function Get-DownstreamCopilotInstruction { [CmdletBinding()] - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string]$ExistingContent - ) - - $updatedContent = $ExistingContent - $updatedContent = $updatedContent -replace 'This repository is a GitHub template for PowerShell projects\.', 'This repository was created from the powershell-dev-template GitHub template and is now treated as its own PowerShell project.' - - $pattern = '(?s)## Repo-Local Skills\r?\n.*?\r?\n## PowerShell Compatibility' - $replacement = @' -## Repo-Local Skills - -Repo-local Codex skills are stored under `.codex/skills/`. When asked to normalize a newly created downstream repository from `powershell-dev-template`, use `.codex/skills/downstream-repo-cleanup/SKILL.md` and operate `scripts/Initialize-DownstreamRepo.ps1` through the documented audit, apply, validation, and diff-review workflow. Use this only as an immediate post-create cleanup step before project-specific work begins. - -When asked to synchronize downstream AI guidance from `powershell-dev-template`, use `.codex/skills/downstream-guidance-sync/SKILL.md` and operate `scripts/Invoke-TemplateGuidanceSync.ps1` through the documented audit, branch, validation, commit, and pull request workflow. Do not manually edit downstream guidance files outside the sync script allowlist unless the user explicitly asks for manual repair after a script failure. - -When asked to audit or align a downstream README to the shared portfolio skeleton, use `.codex/skills/readme-alignment/SKILL.md` and operate `scripts/Invoke-ReadmeAlignment.ps1` through the documented audit, branch, validation, diff-review, and commit workflow. - -Runtime policy, generated Markdown, CI, tests, scaffolds, and release metadata become downstream-owned after cleanup. Treat changes to those surfaces as normal repository work, except when downstream guidance sync is intentionally delivering the README workflow assets that depend on `eng/runtime-policy.json` and `scripts/Update-GeneratedMarkdown.ps1`. - -For ordinary downstream repository changes after cleanup, use .codex/skills/change-delivery-workflow/SKILL.md to coordinate sandbox escalation, non-main branches, changelog updates, release decisions, commits, pull requests, and post-merge cleanup. - -When adding or updating repo-local skills, add or update Pester coverage in `tests/unit/SkillScaffold.Tests.ps1` for the skill file, metadata, required references, and agent discoverability. The Codex `quick_validate.py` helper may be used as an optional authoring check, but Pester is the repository validation standard. + [OutputType([string])] + param() -## PowerShell Compatibility + return @' +# AI Coding Instructions + +Apply these requirements to all repository work. + +- This repository was created from the powershell-dev-template GitHub template and is now its own PowerShell project. +- Follow `AGENTS.md`; prioritize safety, explicit user direction, deterministic repository behavior, Windows PowerShell 5.1 Desktop compatibility, then local convention. +- Before modifying tracked content, complete the freshness gate in `AGENTS.md`. Do not pull, rebase, merge, or edit when it fails. +- Be truthful about commands, APIs, and validation. State material assumptions and never expose secrets. +- Make the smallest maintainable change that satisfies the request. Avoid speculative abstractions, unrelated refactors, and unverified claims. +- Target Windows PowerShell 5.1 Desktop on Windows and full .NET Framework. Do not use PowerShell 6+ automatic variables such as `$IsWindows`, `$IsLinux`, or `$IsMacOS`. +- Use `.github/instructions/` and task-scoped skills for detailed file- and task-specific rules. +- Run relevant validation, inspect the diff, and use Conventional Commits for commits. '@ - - if (-not [regex]::IsMatch($updatedContent, $pattern)) { - throw 'Unable to rewrite the repo-local skills section in .github/copilot-instructions.md.' - } - - $updatedContent = [regex]::Replace($updatedContent, $pattern, $replacement) - return ($updatedContent.TrimEnd("`r", "`n") + "`n") } function Get-DownstreamModuleManifestContent { @@ -591,7 +585,7 @@ if ($null -eq $readmeContent -or $readmeContent -notmatch 'Windows PowerShell De $agentsPath = Get-RepoPath -BasePath $resolvedRepoRoot -RelativePath 'AGENTS.md' $agentsContent = Get-FileContent -Path $agentsPath -if ($null -eq $agentsContent -or $agentsContent -notmatch 'template version release') { +if ($null -eq $agentsContent -or $agentsContent -notmatch 'Guidance Layers') { $stopReasons.Add('AGENTS.md no longer appears to be in the expected immediate post-create state.') } @@ -670,6 +664,10 @@ $keepPaths = @( '.codex/skills/downstream-guidance-sync' '.codex/skills/downstream-repo-cleanup' '.codex/skills/readme-alignment' + '.codex/skills/powershell-authoring' + '.codex/skills/powershell-testing-review' + '.codex/skills/powershell-external-services' + '.github/instructions' ) foreach ($path in $removePaths) { @@ -701,6 +699,8 @@ foreach ($item in $manualFollowUp) { } if ($Apply) { + Assert-RemoteFreshness -RepoPath $resolvedRepoRoot + foreach ($path in $removePaths) { Remove-RepoItem -Path (Get-RepoPath -BasePath $resolvedRepoRoot -RelativePath $path) } @@ -721,7 +721,7 @@ if ($Apply) { } if ($PSCmdlet.ShouldProcess($copilotInstructionsPath, 'Rewrite downstream Copilot instructions')) { - Write-Utf8File -Path $copilotInstructionsPath -Content (Get-DownstreamCopilotInstruction -ExistingContent $copilotContent) + Write-Utf8File -Path $copilotInstructionsPath -Content (Get-DownstreamCopilotInstruction) } $agentWorkflowsPath = Get-RepoPath -BasePath $resolvedRepoRoot -RelativePath 'docs/agent-workflows.md' diff --git a/scripts/Invoke-ReadmeAlignment.ps1 b/scripts/Invoke-ReadmeAlignment.ps1 index abdb130..273a67d 100644 --- a/scripts/Invoke-ReadmeAlignment.ps1 +++ b/scripts/Invoke-ReadmeAlignment.ps1 @@ -93,6 +93,7 @@ function Write-Utf8File { function Get-NewLine { [CmdletBinding()] + [OutputType([string])] param( [Parameter()] [AllowEmptyString()] @@ -118,6 +119,24 @@ function Test-GitRepository { return ($LASTEXITCODE -eq 0 -and ($output | Select-Object -First 1) -eq 'true') } +function Assert-RemoteFreshness { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$RepoPath) + if (@(& git -C $RepoPath status --porcelain).Count -gt 0) { throw ('Refusing to apply changes because the working tree is dirty: {0}' -f $RepoPath) } + $origin = & git -C $RepoPath remote get-url origin 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($origin)) { throw ('Refusing to apply changes because origin is not configured: {0}' -f $RepoPath) } + & git -C $RepoPath fetch --quiet --prune origin + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because origin cannot be fetched: {0}' -f $RepoPath) } + $upstream = & git -C $RepoPath rev-parse --abbrev-ref '@{upstream}' 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($upstream)) { throw ('Refusing to apply changes because the current branch has no upstream: {0}' -f $RepoPath) } + $counts = @((& git -C $RepoPath rev-list --left-right --count HEAD...$upstream) -split '\s+') + if ($LASTEXITCODE -ne 0 -or $counts.Count -ne 2) { throw ('Refusing to apply changes because upstream freshness cannot be determined: {0}' -f $RepoPath) } + if ([int]$counts[0] -gt 0 -and [int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch has diverged from its upstream: {0}' -f $RepoPath) } + if ([int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch is behind its upstream: {0}' -f $RepoPath) } + & git -C $RepoPath merge-base --is-ancestor origin/main HEAD + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because the branch does not contain the latest origin/main: {0}' -f $RepoPath) } +} + function Get-ReadmeDocument { [CmdletBinding()] param( @@ -204,6 +223,7 @@ function Get-TemplateBadgeLine { function Get-LeadSummaryBlock { [CmdletBinding()] + [OutputType([object[]])] param( [Parameter(Mandatory)] [AllowEmptyString()] @@ -484,6 +504,8 @@ $result = [pscustomobject]@{ } if ($Apply -and $hasChanges) { + Assert-RemoteFreshness -RepoPath $resolvedRepoRoot + if ($PSCmdlet.ShouldProcess($readmePath, 'Align README to shared downstream skeleton')) { Write-Utf8File -Path $readmePath -Content $alignedContent } diff --git a/scripts/Invoke-RepoChecks.ps1 b/scripts/Invoke-RepoChecks.ps1 index 264b761..77e9ae9 100644 --- a/scripts/Invoke-RepoChecks.ps1 +++ b/scripts/Invoke-RepoChecks.ps1 @@ -71,6 +71,7 @@ if (-not $SkipAnalyzer) { if (-not $SkipTests) { try { + Import-Module Microsoft.PowerShell.Utility -ErrorAction Stop Import-Module Pester -RequiredVersion $runtimePolicy.tooling.pesterVersion -ErrorAction Stop } catch { diff --git a/scripts/Invoke-TemplateGuidanceSync.ps1 b/scripts/Invoke-TemplateGuidanceSync.ps1 index 150fac8..cebfa1d 100644 --- a/scripts/Invoke-TemplateGuidanceSync.ps1 +++ b/scripts/Invoke-TemplateGuidanceSync.ps1 @@ -23,10 +23,6 @@ Applies safe guidance synchronization changes. Without this switch, the script reports drift without changing files. -.PARAMETER AllowDirty - Allows applying changes when the downstream repository already has - uncommitted changes. - .PARAMETER FailOnDrift Returns a nonzero exit code when drift is detected. @@ -47,9 +43,6 @@ param( [Parameter()] [switch]$Apply, - [Parameter()] - [switch]$AllowDirty, - [Parameter()] [switch]$FailOnDrift, @@ -67,6 +60,14 @@ if ([string]::IsNullOrWhiteSpace($TemplatePath)) { $guidanceFiles = @( 'AGENTS.md' '.github/copilot-instructions.md' + '.github/instructions/markdown.instructions.md' + '.github/instructions/powershell.instructions.md' + '.codex/skills/powershell-authoring/SKILL.md' + '.codex/skills/powershell-authoring/agents/openai.yaml' + '.codex/skills/powershell-testing-review/SKILL.md' + '.codex/skills/powershell-testing-review/agents/openai.yaml' + '.codex/skills/powershell-external-services/SKILL.md' + '.codex/skills/powershell-external-services/agents/openai.yaml' '.codex/skills/change-delivery-workflow/SKILL.md' '.codex/skills/change-delivery-workflow/agents/openai.yaml' '.codex/skills/downstream-repo-cleanup/SKILL.md' @@ -132,6 +133,7 @@ function Invoke-Git { function Test-GitRepository { [CmdletBinding()] + [OutputType([bool])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] @@ -148,6 +150,24 @@ function Test-GitRepository { return (($isWorkTree | Select-Object -First 1) -eq 'true') } +function Assert-RemoteFreshness { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$RepoPath) + if (@(& git -C $RepoPath status --porcelain).Count -gt 0) { throw ('Refusing to apply changes because uncommitted changes exist: {0}' -f $RepoPath) } + $origin = & git -C $RepoPath remote get-url origin 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($origin)) { throw ('Refusing to apply changes because origin is not configured: {0}' -f $RepoPath) } + & git -C $RepoPath fetch --quiet --prune origin + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because origin cannot be fetched: {0}' -f $RepoPath) } + $upstream = & git -C $RepoPath rev-parse --abbrev-ref '@{upstream}' 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($upstream)) { throw ('Refusing to apply changes because the current branch has no upstream: {0}' -f $RepoPath) } + $counts = @((& git -C $RepoPath rev-list --left-right --count HEAD...$upstream) -split '\s+') + if ($LASTEXITCODE -ne 0 -or $counts.Count -ne 2) { throw ('Refusing to apply changes because upstream freshness cannot be determined: {0}' -f $RepoPath) } + if ([int]$counts[0] -gt 0 -and [int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch has diverged from its upstream: {0}' -f $RepoPath) } + if ([int]$counts[1] -gt 0) { throw ('Refusing to apply changes because the branch is behind its upstream: {0}' -f $RepoPath) } + & git -C $RepoPath merge-base --is-ancestor origin/main HEAD + if ($LASTEXITCODE -ne 0) { throw ('Refusing to apply changes because the branch does not contain the latest origin/main: {0}' -f $RepoPath) } +} + function Get-FileHashText { [CmdletBinding()] param( @@ -186,6 +206,7 @@ function Get-TemplateVersion { function Get-NewLine { [CmdletBinding()] + [OutputType([string])] param( [Parameter(Mandatory)] [AllowEmptyString()] @@ -365,6 +386,7 @@ function Copy-GuidanceFile { function Get-GuidanceFileState { [CmdletBinding()] + [OutputType([object[]])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] @@ -431,6 +453,7 @@ function Write-TextReport { } $resolvedTemplatePath = Resolve-DirectoryPath -LiteralPath $TemplatePath +$templateFreshnessChecked = $false $templateVersion = Get-TemplateVersion -RepoPath $resolvedTemplatePath $results = [System.Collections.Generic.List[object]]::new() @@ -446,11 +469,6 @@ foreach ($targetPathInput in $Path) { throw ('Could not determine current branch for target repository: {0}' -f $targetPath) } - $gitStatus = @(Invoke-Git -RepoPath $targetPath -Arguments @('status', '--porcelain')) - if ($Apply -and -not $AllowDirty -and $gitStatus.Count -gt 0) { - throw ('Refusing to apply changes because target repository has uncommitted changes: {0}' -f $targetPath) - } - if ($Apply -and $branch -in @('main', 'master')) { $branchName = 'chore/sync-template-guidance-{0}' -f $templateVersion throw ('Refusing to apply changes on protected branch "{0}". Create or switch to a working branch first: git -C "{1}" switch -c {2}' -f $branch, $targetPath, $branchName) @@ -460,6 +478,12 @@ foreach ($targetPathInput in $Path) { $readmeBadge = Get-ReadmeBadgeState -RepoPath $targetPath -TemplateVersion $templateVersion if ($Apply) { + if (-not $templateFreshnessChecked) { + Assert-RemoteFreshness -RepoPath $resolvedTemplatePath + $templateFreshnessChecked = $true + } + Assert-RemoteFreshness -RepoPath $targetPath + foreach ($fileState in $fileStates | Where-Object { $_.Status -ne 'Current' }) { Copy-GuidanceFile -TemplateRepoPath $resolvedTemplatePath -TargetRepoPath $targetPath -RelativePath $fileState.Path $fileState.Applied = $true diff --git a/scripts/Test-VersionPolicy.ps1 b/scripts/Test-VersionPolicy.ps1 index bfbe5d5..9bf4344 100644 --- a/scripts/Test-VersionPolicy.ps1 +++ b/scripts/Test-VersionPolicy.ps1 @@ -154,7 +154,7 @@ Test-PolicyRegexValue -RelativePath '.github/workflows/ci.yml' -Pattern '^\s*Ins Test-PolicyText -RelativePath '.vscode/settings.json' -ExpectedText $policy.runtime.executable -Description 'VS Code Windows PowerShell executable' Test-PolicyText -RelativePath 'README.md' -ExpectedText ('Windows PowerShell {0}' -f $policy.runtime.powershellVersion) -Description 'README Windows PowerShell version' -Test-PolicyText -RelativePath '.github/Instructions/environment-setup.md' -ExpectedText ('Windows PowerShell {0}' -f $policy.runtime.powershellVersion) -Description 'Environment setup Windows PowerShell version' +Test-PolicyText -RelativePath '.github/instructions/environment-setup.md' -ExpectedText ('Windows PowerShell {0}' -f $policy.runtime.powershellVersion) -Description 'Environment setup Windows PowerShell version' Test-PolicyText -RelativePath '.github/copilot-instructions.md' -ExpectedText ('Windows PowerShell {0}' -f $policy.runtime.powershellVersionLabel) -Description 'Copilot instruction PowerShell compatibility target' Test-PolicyText -RelativePath 'src/TemplateModule.psd1' -ExpectedText "PowerShellVersion = '5.1'" -Description 'Template module PowerShell version' Test-PolicyText -RelativePath 'src/TemplateModule.psd1' -ExpectedText "CompatiblePSEditions = @('Desktop')" -Description 'Template module compatible edition' @@ -166,4 +166,4 @@ if ($failures.Count -gt 0) { throw ('Version policy drift detected in {0} location(s).' -f $failures.Count) } -Write-Verbose ('Version policy validated: {0}' -f $resolvedPolicyPath) \ No newline at end of file +Write-Verbose ('Version policy validated: {0}' -f $resolvedPolicyPath) diff --git a/scripts/Update-GeneratedMarkdown.ps1 b/scripts/Update-GeneratedMarkdown.ps1 index a20db8f..1ac855d 100644 --- a/scripts/Update-GeneratedMarkdown.ps1 +++ b/scripts/Update-GeneratedMarkdown.ps1 @@ -27,6 +27,27 @@ $policy = Get-Content -Raw -LiteralPath $resolvedPolicyPath | ConvertFrom-Json $checkOnly = $Check.IsPresent $pendingChanges = [System.Collections.Generic.List[object]]::new() +function Assert-RemoteFreshness { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$RepoPath) + + if (@(& git -C $RepoPath status --porcelain).Count -gt 0) { throw 'Refusing to update generated Markdown because the working tree is dirty.' } + $origin = & git -C $RepoPath remote get-url origin 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($origin)) { throw 'Refusing to update generated Markdown because origin is not configured.' } + & git -C $RepoPath fetch --quiet --prune origin + if ($LASTEXITCODE -ne 0) { throw 'Refusing to update generated Markdown because origin cannot be fetched.' } + $upstream = & git -C $RepoPath rev-parse --abbrev-ref '@{upstream}' 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($upstream)) { throw 'Refusing to update generated Markdown because the current branch has no upstream.' } + $counts = @((& git -C $RepoPath rev-list --left-right --count HEAD...$upstream) -split '\s+') + if ($LASTEXITCODE -ne 0 -or $counts.Count -ne 2 -or [int]$counts[1] -gt 0 -or ([int]$counts[0] -gt 0 -and [int]$counts[1] -gt 0)) { throw 'Refusing to update generated Markdown because the branch is not current with its upstream.' } + & git -C $RepoPath merge-base --is-ancestor origin/main HEAD + if ($LASTEXITCODE -ne 0) { throw 'Refusing to update generated Markdown because the branch does not contain the latest origin/main.' } +} + +if (-not $checkOnly -and -not $WhatIfPreference) { + Assert-RemoteFreshness -RepoPath $repoRoot +} + function Set-GeneratedMarkdownBlock { [CmdletBinding(SupportsShouldProcess)] param( @@ -130,15 +151,15 @@ Set-GeneratedMarkdownBlock -RelativePath 'templates/downstream/README.md' -Block Set-GeneratedMarkdownBlock -RelativePath 'templates/downstream/README.md' -BlockName 'readme-runtime-philosophy' -Lines @( $baseRuntimeLine ) -Set-GeneratedMarkdownBlock -RelativePath '.github/Instructions/environment-setup.md' -BlockName 'environment-runtime-stack' -Lines @( +Set-GeneratedMarkdownBlock -RelativePath '.github/instructions/environment-setup.md' -BlockName 'environment-runtime-stack' -Lines @( $runtimeLine ) -Set-GeneratedMarkdownBlock -RelativePath '.github/Instructions/environment-setup.md' -BlockName 'environment-tooling-stack' -Lines @( +Set-GeneratedMarkdownBlock -RelativePath '.github/instructions/environment-setup.md' -BlockName 'environment-tooling-stack' -Lines @( $toolingLine ) -Set-GeneratedMarkdownBlock -RelativePath '.github/Instructions/environment-setup.md' -BlockName 'environment-runtime-principle' -Lines @( +Set-GeneratedMarkdownBlock -RelativePath '.github/instructions/environment-setup.md' -BlockName 'environment-runtime-principle' -Lines @( $controlledRuntimeLine ) diff --git a/tests/unit/Initialize-DownstreamRepo.Tests.ps1 b/tests/unit/Initialize-DownstreamRepo.Tests.ps1 index 42d6ec1..5d9ab2c 100644 --- a/tests/unit/Initialize-DownstreamRepo.Tests.ps1 +++ b/tests/unit/Initialize-DownstreamRepo.Tests.ps1 @@ -22,7 +22,9 @@ Describe 'Initialize-DownstreamRepo' { 'AGENTS.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath 'AGENTS.md') 'README.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath 'README.md') '.github/copilot-instructions.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.github/copilot-instructions.md') - '.github/Instructions/environment-setup.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.github/Instructions/environment-setup.md') + '.github/instructions/environment-setup.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.github/instructions/environment-setup.md') + '.github/instructions/markdown.instructions.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.github/instructions/markdown.instructions.md') + '.github/instructions/powershell.instructions.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.github/instructions/powershell.instructions.md') 'docs/agent-workflows.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath 'docs/agent-workflows.md') 'docs/decisions/README.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath 'docs/decisions/README.md') 'docs/template-evolution.md' = 'template evolution' @@ -64,6 +66,12 @@ Describe 'Initialize-DownstreamRepo' { '.codex/skills/runtime-policy-update/agents/openai.yaml' = 'display_name: "Runtime Policy Update"' '.codex/skills/template-version-release/SKILL.md' = 'release skill' '.codex/skills/template-version-release/agents/openai.yaml' = 'display_name: "Template Version Release"' + '.codex/skills/powershell-authoring/SKILL.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-authoring/SKILL.md') + '.codex/skills/powershell-authoring/agents/openai.yaml' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-authoring/agents/openai.yaml') + '.codex/skills/powershell-testing-review/SKILL.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-testing-review/SKILL.md') + '.codex/skills/powershell-testing-review/agents/openai.yaml' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-testing-review/agents/openai.yaml') + '.codex/skills/powershell-external-services/SKILL.md' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-external-services/SKILL.md') + '.codex/skills/powershell-external-services/agents/openai.yaml' = Get-Content -Raw -LiteralPath (Join-Path -Path $script:RepoRoot -ChildPath '.codex/skills/powershell-external-services/agents/openai.yaml') 'tests/unit/Invoke-TemplateGuidanceSync.Tests.ps1' = 'Describe "x" {}' 'tests/unit/TemplateHealth.Tests.ps1' = 'Describe "x" {}' 'tests/unit/TemplateScaffold.Tests.ps1' = 'Describe "x" {}' @@ -94,10 +102,14 @@ Describe 'Initialize-DownstreamRepo' { } & git -C $Path init -q -b main | Out-Null + & git -C $Path config core.autocrlf false | Out-Null & git -C $Path config user.email 'test@example.invalid' | Out-Null & git -C $Path config user.name 'Test User' | Out-Null & git -C $Path add . | Out-Null & git -C $Path commit -m 'initial downstream template copy' | Out-Null + & git -C $Path remote add origin $Path | Out-Null + & git -C $Path fetch --quiet origin | Out-Null + & git -C $Path branch --set-upstream-to=origin/main main | Out-Null } function Invoke-CleanupScript { @@ -212,8 +224,7 @@ Describe 'Initialize-DownstreamRepo' { $copilot = Get-Content -Raw -LiteralPath (Join-Path -Path $script:DownstreamRepo -ChildPath '.github/copilot-instructions.md') $copilot | Should -Match 'created from the powershell-dev-template GitHub template' - $copilot | Should -Match 'downstream-repo-cleanup' - $copilot | Should -Match 'readme-alignment' + $copilot | Should -Match '\.github/instructions/' $copilot | Should -Not -Match 'runtime-policy-update' $copilot | Should -Not -Match 'template-version-release' diff --git a/tests/unit/Invoke-ReadmeAlignment.Tests.ps1 b/tests/unit/Invoke-ReadmeAlignment.Tests.ps1 index 6e6bdf9..986b0cc 100644 --- a/tests/unit/Invoke-ReadmeAlignment.Tests.ps1 +++ b/tests/unit/Invoke-ReadmeAlignment.Tests.ps1 @@ -57,10 +57,15 @@ Repository-specific extra section. } & git -C $Path init -q -b work/readme | Out-Null + & git -C $Path config core.autocrlf false | Out-Null & git -C $Path config user.email 'test@example.invalid' | Out-Null & git -C $Path config user.name 'Test User' | Out-Null & git -C $Path add . | Out-Null & git -C $Path commit -m 'initial repo' | Out-Null + & git -C $Path branch main | Out-Null + & git -C $Path remote add origin $Path | Out-Null + & git -C $Path fetch --quiet origin | Out-Null + & git -C $Path branch --set-upstream-to=origin/work/readme work/readme | Out-Null } function Invoke-AlignmentScript { diff --git a/tests/unit/Invoke-TemplateGuidanceSync.Tests.ps1 b/tests/unit/Invoke-TemplateGuidanceSync.Tests.ps1 index e994317..8381266 100644 --- a/tests/unit/Invoke-TemplateGuidanceSync.Tests.ps1 +++ b/tests/unit/Invoke-TemplateGuidanceSync.Tests.ps1 @@ -5,12 +5,20 @@ Describe 'Invoke-TemplateGuidanceSync' { $script:GuidanceFiles = @( 'AGENTS.md' '.github/copilot-instructions.md' + '.github/instructions/markdown.instructions.md' + '.github/instructions/powershell.instructions.md' '.codex/skills/change-delivery-workflow/SKILL.md' '.codex/skills/change-delivery-workflow/agents/openai.yaml' '.codex/skills/downstream-repo-cleanup/SKILL.md' '.codex/skills/downstream-repo-cleanup/agents/openai.yaml' '.codex/skills/readme-alignment/SKILL.md' '.codex/skills/readme-alignment/agents/openai.yaml' + '.codex/skills/powershell-authoring/SKILL.md' + '.codex/skills/powershell-authoring/agents/openai.yaml' + '.codex/skills/powershell-testing-review/SKILL.md' + '.codex/skills/powershell-testing-review/agents/openai.yaml' + '.codex/skills/powershell-external-services/SKILL.md' + '.codex/skills/powershell-external-services/agents/openai.yaml' 'docs/agent-workflows.md' 'docs/ai-behavioral-contract.md' 'docs/ai-interaction-loop.md' @@ -65,6 +73,16 @@ Describe 'Invoke-TemplateGuidanceSync' { Set-Content -LiteralPath $templatePath -Value ('template content for {0}' -f $relativePath) -Encoding utf8 } + & git -C $script:TemplateRepo init -q -b main | Out-Null + & git -C $script:TemplateRepo config core.autocrlf false | Out-Null + & git -C $script:TemplateRepo config user.email 'test@example.invalid' | Out-Null + & git -C $script:TemplateRepo config user.name 'Test User' | Out-Null + & git -C $script:TemplateRepo add . | Out-Null + & git -C $script:TemplateRepo commit -m 'initial template' | Out-Null + & git -C $script:TemplateRepo remote add origin $script:TemplateRepo | Out-Null + & git -C $script:TemplateRepo fetch --quiet origin | Out-Null + & git -C $script:TemplateRepo branch --set-upstream-to=origin/main main | Out-Null + Set-Content -LiteralPath (Join-Path -Path $script:TargetRepo -ChildPath 'README.md') -Value @( '# Downstream Repo' '' @@ -72,10 +90,15 @@ Describe 'Invoke-TemplateGuidanceSync' { ) -Encoding utf8 & git -C $script:TargetRepo init -q -b work/sync | Out-Null + & git -C $script:TargetRepo config core.autocrlf false | Out-Null & git -C $script:TargetRepo config user.email 'test@example.invalid' | Out-Null & git -C $script:TargetRepo config user.name 'Test User' | Out-Null & git -C $script:TargetRepo add . | Out-Null & git -C $script:TargetRepo commit -m 'initial target' | Out-Null + & git -C $script:TargetRepo branch main | Out-Null + & git -C $script:TargetRepo remote add origin $script:TargetRepo | Out-Null + & git -C $script:TargetRepo fetch --quiet origin | Out-Null + & git -C $script:TargetRepo branch --set-upstream-to=origin/work/sync work/sync | Out-Null } AfterEach { @@ -135,6 +158,8 @@ Describe 'Invoke-TemplateGuidanceSync' { Set-Content -LiteralPath $targetDecisionPath -Value 'downstream-owned decision' -Encoding utf8 & git -C $script:TargetRepo add docs/decisions/0001-downstream-decision.md | Out-Null & git -C $script:TargetRepo commit -m 'add downstream decision' | Out-Null + & git -C $script:TemplateRepo add docs/decisions/0001-template-decision.md | Out-Null + & git -C $script:TemplateRepo commit -m 'add template decision' | Out-Null & $script:InvokeSyncScript -ExtraArguments @('-Apply') | Out-Null @@ -183,7 +208,7 @@ Describe 'Invoke-TemplateGuidanceSync' { } It 'refuses to apply on main' { - & git -C $script:TargetRepo switch -q -c main | Out-Null + & git -C $script:TargetRepo switch -q main | Out-Null { & $script:InvokeSyncScript -ExtraArguments @('-Apply') } | Should -Throw -ExpectedMessage '*protected branch "main"*' } diff --git a/tests/unit/SkillScaffold.Tests.ps1 b/tests/unit/SkillScaffold.Tests.ps1 index 449e4ae..34a2a0e 100644 --- a/tests/unit/SkillScaffold.Tests.ps1 +++ b/tests/unit/SkillScaffold.Tests.ps1 @@ -61,7 +61,7 @@ Describe 'Repo-local skills' { $readmeContent | Should -Match '\.codex/skills/change-delivery-workflow/SKILL\.md' $agentsContent | Should -Match '\.codex/skills/change-delivery-workflow/SKILL\.md' - $copilotContent | Should -Match '\.codex/skills/change-delivery-workflow/SKILL\.md' + $copilotContent | Should -Match '\.github/instructions/' $workflowContent | Should -Match '\.codex/skills/change-delivery-workflow/SKILL\.md' } It 'includes the downstream repo cleanup skill' { @@ -102,9 +102,8 @@ Describe 'Repo-local skills' { $readmeContent | Should -Match 'Initialize-DownstreamRepo\.ps1' $readmeContent | Should -Match 'template version badge' $agentsContent | Should -Match '\.codex/skills/downstream-repo-cleanup/SKILL\.md' - $agentsContent | Should -Match 'Initialize-DownstreamRepo\.ps1' - $copilotContent | Should -Match '\.codex/skills/downstream-repo-cleanup/SKILL\.md' - $copilotContent | Should -Match 'Initialize-DownstreamRepo\.ps1' + $workflowContent | Should -Match 'Initialize-DownstreamRepo\.ps1' + $copilotContent | Should -Match '\.github/instructions/' $workflowContent | Should -Match '\.codex/skills/change-delivery-workflow/SKILL\.md' $workflowContent | Should -Match '\.codex/skills/downstream-repo-cleanup/SKILL\.md' $workflowContent | Should -Match 'scripts/Initialize-DownstreamRepo\.ps1' @@ -171,11 +170,8 @@ Describe 'Repo-local skills' { $copilotContent = Get-Content -Raw -LiteralPath $copilotPath $agentsContent | Should -Match '\.codex/skills/downstream-guidance-sync/SKILL\.md' - $agentsContent | Should -Match 'Invoke-TemplateGuidanceSync\.ps1' - $agentsContent | Should -Match 'Initialize-DownstreamRepo\.ps1' - $copilotContent | Should -Match '\.codex/skills/downstream-guidance-sync/SKILL\.md' - $copilotContent | Should -Match 'Invoke-TemplateGuidanceSync\.ps1' - $copilotContent | Should -Match 'Initialize-DownstreamRepo\.ps1' + (Get-Content -Raw -LiteralPath $script:AgentWorkflowsPath) | Should -Match 'Invoke-TemplateGuidanceSync\.ps1' + $copilotContent | Should -Match '\.github/instructions/' } It 'includes the README alignment skill' { @@ -216,7 +212,7 @@ Describe 'Repo-local skills' { $readmeContent | Should -Match '\.codex/skills/readme-alignment/SKILL\.md' $agentsContent | Should -Match '\.codex/skills/readme-alignment/SKILL\.md' - $copilotContent | Should -Match '\.codex/skills/readme-alignment/SKILL\.md' + $copilotContent | Should -Match '\.github/instructions/' $workflowContent | Should -Match '\.codex/skills/readme-alignment/SKILL\.md' $workflowContent | Should -Match 'scripts/Invoke-ReadmeAlignment\.ps1' $workflowContent | Should -Match 'templates/downstream/README\.md' @@ -267,9 +263,8 @@ Describe 'Repo-local skills' { $copilotContent = Get-Content -Raw -LiteralPath $copilotPath $agentsContent | Should -Match '\.codex/skills/runtime-policy-update/SKILL\.md' - $agentsContent | Should -Match 'eng/runtime-policy\.json' - $copilotContent | Should -Match '\.codex/skills/runtime-policy-update/SKILL\.md' - $copilotContent | Should -Match 'eng/runtime-policy\.json' + (Get-Content -Raw -LiteralPath $script:RuntimeSkillPath) | Should -Match 'eng/runtime-policy\.json' + $copilotContent | Should -Match '\.github/instructions/' } It 'includes the template version release skill' { @@ -316,11 +311,11 @@ Describe 'Repo-local skills' { $readmeContent | Should -Match '\.codex/skills/template-version-release/SKILL\.md' $evolutionContent | Should -Match '\.codex/skills/template-version-release/SKILL\.md' $agentsContent | Should -Match '\.codex/skills/template-version-release/SKILL\.md' - $copilotContent | Should -Match '\.codex/skills/template-version-release/SKILL\.md' + $copilotContent | Should -Match '\.github/instructions/' $readmeContent | Should -Match 'GitHub Release' $evolutionContent | Should -Match 'GitHub Release' - $agentsContent | Should -Match 'GitHub Release' - $copilotContent | Should -Match 'GitHub Release' + $evolutionContent | Should -Match 'GitHub Release' + $copilotContent | Should -Match '\.github/instructions/' } It 'documents repo-local agent workflows for human discovery' { @@ -435,4 +430,21 @@ Describe 'Repo-local skills' { $versionContent | Should -Not -Match '## Why This Exists' } + + It 'includes focused Windows PowerShell guidance skills with discoverable metadata' { + $skills = @( + @{ Name = 'powershell-authoring'; Heading = 'Windows PowerShell Authoring' } + @{ Name = 'powershell-testing-review'; Heading = 'Windows PowerShell Testing And Review' } + @{ Name = 'powershell-external-services'; Heading = 'Windows PowerShell External Services' } + ) + + foreach ($skill in $skills) { + $skillPath = Join-Path -Path $script:RepoRoot -ChildPath ('.codex/skills/{0}/SKILL.md' -f $skill.Name) + $metadataPath = Join-Path -Path $script:RepoRoot -ChildPath ('.codex/skills/{0}/agents/openai.yaml' -f $skill.Name) + Test-Path -LiteralPath $skillPath -PathType Leaf | Should -BeTrue + Test-Path -LiteralPath $metadataPath -PathType Leaf | Should -BeTrue + (Get-Content -Raw -LiteralPath $skillPath) | Should -Match ('name: {0}' -f $skill.Name) + (Get-Content -Raw -LiteralPath $metadataPath) | Should -Match ('display_name: "{0}"' -f $skill.Heading) + } + } } diff --git a/tests/unit/TemplateScaffold.Tests.ps1 b/tests/unit/TemplateScaffold.Tests.ps1 index 64ea343..8b4b3d0 100644 --- a/tests/unit/TemplateScaffold.Tests.ps1 +++ b/tests/unit/TemplateScaffold.Tests.ps1 @@ -45,9 +45,10 @@ Describe 'Template scaffold' { It 'documents the Windows PowerShell baseline decision as the first repository ADR' { $decisionsPath = Join-Path -Path $script:RepoRoot -ChildPath 'docs/decisions' $numberedAdrs = @(Get-ChildItem -LiteralPath $decisionsPath -Filter '*.md' -File | Where-Object { $_.Name -match '^\d{4}-' }) - $numberedAdrs.Count | Should -Be 2 + $numberedAdrs.Count | Should -Be 3 $numberedAdrs[0].Name | Should -Be '0001-windows-powershell-development-baseline.md' $numberedAdrs[1].Name | Should -Be '0002-downstream-readme-workflow.md' + $numberedAdrs[2].Name | Should -Be '0003-task-scoped-ai-guidance.md' $adrPath = Join-Path -Path $decisionsPath -ChildPath '0001-windows-powershell-development-baseline.md' Test-Path -LiteralPath $adrPath -PathType Leaf | Should -BeTrue