Drop-in operating instructions for coding agents. Read this file before every task.
Working code only. Finish the job. Plausibility is not correctness.
SPEC.md in the repository root is the project contract — read it for what WinUtil is and how it's architected. This file covers how to work on it.
These rules override everything else in this file when in conflict:
- Do not edit
winutil.ps1directly. It is generated build output (see SPEC.md's Build Model). Change source files and compile. - Do not commit
winutil.ps1. It is ignored locally and generated by GitHub Actions for releases. - Never touch
docs/src/content/docs/code-reference/tweaks/ordocs/src/content/docs/code-reference/features/. Both are auto-generated (see SPEC.md's Docs Site). Edit the source JSON (config/tweaks.json,config/feature.json) or the relevant PowerShell function file instead. Other hand-written pages undercode-reference/(e.g.architecture.mdx) are not touched by the generator and may be edited directly. - Never fabricate. Do not invent file paths, function names, command output, test results, commit hashes, or API behavior. Read the file or run the command.
- Disagree when the premise is wrong. Say what is wrong before acting on it.
- Stop when genuinely ambiguous. If two interpretations would produce materially different diffs, ask before editing.
- Touch only what the task requires. No drive-by refactors, formatting sweeps, or unrelated cleanup.
- Verify before saying done. A plausible-looking diff is not proof.
- Compile:
.\Compile.ps1
- Compile and run GUI:
.\Compile.ps1 -Run - Install the supported Pester version (one-time).
-SkipPublisherCheckis required because Windows ships an inbox Pester 3.4.0 that is catalog-signed, and PowerShell Gallery's Pester 5.8.0 is Authenticode-signed —Install-Modulerefuses the upgrade without it. This does not skip download integrity (still HTTPS + NuGet package hash verification);-Repository PSGallerypins the trusted source explicitly rather than relying on whatever repositories happen to be registered:Install-Module -Name Pester -RequiredVersion 5.8.0 -Repository PSGallery -Scope CurrentUser -Force -SkipPublisherCheck
- Run tests:
Import-Module Pester -RequiredVersion 5.8.0 -Force Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CI
- Run Script Analyzer with project settings when available. If a locally compiled
winutil.ps1exists, delete it first —lint/PSScriptAnalyser.ps1only excludes rules, not files, so-Recursewould also lint the generated script and produce noise against line numbers that don't map to any source file:Invoke-ScriptAnalyzer -Path . -Settings .\lint\PSScriptAnalyser.ps1 -Recurse
- Docs site dev server (run from
docs/; see Section 2 for why this goes through Docker):docker compose up winutil-astro - Docs site production build (run from
docs/):docker compose run --rm winutil-astro npm run build
Prefer the narrowest useful verification while iterating. Use the full relevant check before finishing.
Given the current wave of npm/pnpm/yarn supply-chain worms (malicious postinstall/preinstall scripts, credential-stealing packages): never run npm/pnpm/yarn/npx directly on the host, full stop. The docs site (docs/) is the only npm-based project in this repo; always run its tooling inside Docker via docs/Dockerfile and docs/docker-compose.yml (service winutil-astro).
- Never run
npm install,npm run <script>,npx <pkg>,pnpm, oryarndirectly on the host shell indocs/. Usedocker compose run --rm winutil-astro <command>/docker compose up winutil-astroinstead (see Section 1 for the exact commands). - If a task needs a new docs dependency, add it to
docs/package.jsonyourself, then rebuild the image and drop thenode_modulesvolume so it repopulates from the new image (run fromdocs/):docker compose build winutil-astro, thendocker compose down -v. Docker only seeds a named volume from the image the first time it's created, so a plain rebuild silently leaves the oldnode_modulesin place. Don't install packages on the host, even temporarily, "just to check something." - If Docker isn't available on the host, propose the install command for the current OS and wait for confirmation before running it — don't fall back to running npm on the host instead. If the daemon just isn't running (Docker is installed but not started), tell the user rather than trying to start it yourself.
- Treat any
postinstall/preinstalllifecycle script in a new dependency as worth flagging to the user before installing — summarize what it does. - Don't put real secrets anywhere under
docs/.docs/.dockerignoreonly trims whatdocker buildcopies into the image — it does not affect thedocker composebind mount, which exposes the entiredocs/directory (including any.envfile) inside the container for every dev/build/preview command (see the next bullet). There is no "keep it out unless mounted" middle ground here. - The container mounts
docs/as a volume, so file edits on the host are reflected inside the container immediately — no rebuild needed for normal code changes, only whendocs/package.json/docs/package-lock.jsonchange (see the rebuild-and-drop-volume steps above). - This Docker requirement is specific to
docs/. PowerShell tooling runs directly on the host per Section 1. The Python project undertools/title-screen/runs with uv as documented in its README.
For changes that affect the compiled WinUtil script, make them only in the source files described in SPEC.md's Repository Layout — never in winutil.ps1 itself. If behavior changes require the compiled script to change, update the source files and run .\Compile.ps1 only to verify generation.
This scoping applies to compiled-script behavior only. Repository metadata — AGENTS.md, SPEC.md, CLAUDE.md/GEMINI.md/.github/copilot-instructions.md, .github/workflows/, and the root .gitignore — is edited directly when a task requires it, per the other sections of this file.
- State the plan in one or two sentences before editing. For non-trivial work, include the verification you intend to run.
- Read the files you will touch and the files that call them.
- Match existing patterns even when a different greenfield design would be cleaner.
- Surface assumptions when they affect behavior, compatibility, or user data.
- If two approaches have meaningful tradeoffs, name them before choosing. Trivial tasks can proceed directly.
- Prefer the minimum code that solves the stated problem.
- Keep PowerShell functions in one function file when practical, with the file name matching the primary function name.
- Use approved PowerShell verb-noun names and follow the existing
WPF/WinUtilnaming conventions; keep UI event handler names aligned with XAML element names per SPEC.md's UI And Event Contract. - Use
$syncfor shared state and UI references, consistent with SPEC.md's Runtime Model. - Update WPF controls through the UI dispatcher when running work in a background runspace.
- Keep config-driven features in JSON when they fit the existing schema instead of hard-coding lists in PowerShell; follow SPEC.md's Configuration Contract for required fields and key-renaming rules.
- Preserve undo/original-state data for tweaks so users can reverse changes.
- Do not add abstractions, configurability, hooks, or "future extensibility" unless the task needs them now.
- Clean up orphans created by your own changes, such as unused variables or functions made obsolete by the edit.
- Avoid broad formatting-only edits, especially in JSON config files, XAML, docs, and generated output.
- WinUtil performs system-level Windows changes; treat registry, services, AppX removal, package manager, Windows Update, ISO, and unattended setup changes as high-risk (see SPEC.md's Safety Requirements).
- Prefer existing helper functions for WinGet, Chocolatey, registry, services, progress, and UI updates.
- Keep tweaks reversible where the schema supports it by including original values or original states.
- Never modify a user's original ISO in-place; follow existing copy/mount/export patterns.
- Avoid storing credentials, secrets, or machine-specific paths in repo files.
- Preserve logging and user feedback patterns for long-running or destructive operations.
- Do not improve adjacent code, comments, formatting, imports, or docs unless required.
- Do not refactor working code because you are already in the file.
- Do not delete pre-existing dead code unless asked; mention it in the summary if relevant.
- Keep diffs reviewable. Every changed line should trace to the user's request.
- If a change starts spreading across unrelated areas, pause and reassess the plan.
Define success in terms that can be checked, then check it.
- For compile/build changes, run
.\Compile.ps1. - For GUI behavior changes, run
.\Compile.ps1 -Runwhen practical and verify the affected path manually. - For config changes, run the compile check and relevant Pester tests.
- For function changes, run the relevant Pester tests or add/update focused tests when practical.
- For docs-only changes, proofread the changed files and skip runtime tests unless docs generation is affected.
- Read command output. Do not report tests as passing unless they actually passed.
- If verification fails, fix the cause rather than weakening the test.
If a check cannot be run, say exactly why and what residual risk remains. See SPEC.md's Testing And CI for what GitHub Actions runs on every push.
- Treat local
winutil.ps1changes as disposable compile output. - Never stage or commit
winutil.ps1,binary/, or anything else ignored by the root.gitignoreordocs/.gitignore— read those files rather than assuming.docs/public/is tracked source for static assets, not generated output. docs/src/assets/branding/title-screen.pngis a tracked generated asset. Do not edit it manually. Updatetools/title-screen/or run the title-screen workflow.- Do not remove
.gitignorerules that keep generated artifacts out of Git. - Before finishing, check
git status --shortand separate your changes from pre-existing user changes. - Do not revert user changes unless explicitly asked.
- Commit messages, when requested, should be descriptive: short subject under 72 characters, body explaining why when needed.
- When committing, split changes into small, logical commits rather than one large commit, so each commit's diff is reviewable as a single group of related changes.
- Update
docs/src/content/docs/guides/when user-facing behavior changes. - Update
docs/src/content/docs/code-reference/architecture.mdxand other hand-written developer docs when architecture, build flow, config schema, or contribution workflow changes — but never hand-edit the auto-generatedcode-reference/tweaks/orcode-reference/features/subfolders (see Non-Negotiables). - Keep sidebar entries in
docs/astro.config.mjsin sync with page slugs (see SPEC.md's Docs Site). - Keep README changes brief and high-level.
- Put detailed user and developer documentation under
docs/. - Keep SPEC.md aligned with project/architecture changes, and this file aligned with process changes.
- Be direct and concise. Start with the answer or action.
- No flattery, filler, ceremonial closings, or fake certainty.
- Use bullets only when they improve scanning.
- Report what changed, how it was verified, and anything not done.
- If the user asks for a review, lead with findings and file/line references.
Ask before proceeding when:
- The request has two plausible interpretations and the choice materially changes behavior or files touched.
- The change affects release generation, generated artifacts, migrations, or high-risk Windows behavior in a way the user did not specify.
- You need credentials, secrets, production resources, or access you do not have.
- The user's stated goal conflicts with the literal request.
Proceed without asking when:
- The task is trivial and reversible.
- Ambiguity can be resolved by reading the code or running a local command.
- The user already answered the question in this session.
When the user corrects an agent approach, add or tighten one concrete rule here before ending the session. Keep this section short and prune rules that no longer matter.
- Keep
winutil.ps1generated-only: change source files, compile to verify, and never stage the generated script. - Keep WinUtil runtime logging in the existing timestamped
%LocalAppData%\winutil\logs\winutil_*.logsession file; do not create a separate rootwinutil.log. - Import Pester 5.8.0 before running tests so
Invoke-Pester -Output Detailed -CIdoes not resolve to Windows' inbox Pester 3.4.0. - Keep package install/uninstall process launches simple unless explicitly requested; do not add a separate stdout/stderr process logging helper for winget or Chocolatey.
- When the active log file is owned by
Start-Transcript, do not callAdd-Contentagainst that file; write to host output so the transcript captures the line in the same log file without recording a terminating-error diagnostic. - Keep UI helpers such as
Invoke-WPFUIThreadandStep-WinUtilJobsafe to call without a window; the-Presetand-Configpaths run the workflows before the form is created and before PresentationCore is loaded. AskTest-WinUtilUIAliverather than writing the$sync.Form/ dispatcher /HasShutdownStartedcheck out by hand. - Put long operations on the job layer with
Start-WinUtilJoband report from them withStep-WinUtilJob; a job body must not set the busy flag, print its own banner, or carry its own try/catch/finally around the interface.Invoke-WPFRunspacedirectly is for fire-and-forget work that is not a job. - Drain interface work that nobody is waiting for through
Start-WinUtilBackgroundQueue; do not hand-roll another dequeue-and-re-post pump. - Values a posted scriptblock needs travel as the dispatcher's argument or through
-Parameters, never captured from the caller: a plain block resolves them when the dispatcher gets to it, andGetNewClosurebinds command lookup to a copied scope. For the same reason, prefer a compiled[action]overInvoke-WPFUIThread -Asyncon hot re-posting paths, which marshals its body as text and recompiles it per post. - Diagnostic scaffolding does not ship. Measure with it, then delete it.
- Have each Pester file load the assemblies and dot-source the functions it needs; several passed only because an earlier file in alphabetical order happened to load them.
- Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity.
- For Win11 Creator, start each new ISO modification in a fresh
WinUtil_Win11ISO_*temp directory; existing-work detection is only for resuming/exporting already modified media. - For Win11 Creator driver injection, keep offline WIM servicing to one mount and one commit: add each root package folder with its own
/Add-Driver /Recurseso a single bad driver cannot fail the rest, and skip any folder whose ancestor is already in the set, since that ancestor's/Recursecovers it. Warn per failure and commit only when at least one package was added; when none were, warn and discard rather than throwing, so the run still produces an ISO. The discard in the cleanup block carries both orphaned mounts and that intentional zero-added case; keep it. Do not export editions or run unrelated WIM cleanup, and reject damaged metadata before ISO export. Use-LiteralPathfor driver export paths, since%TEMP%can contain wildcard characters. - For Script Analyzer cleanup, fix actionable source warnings first and do not globally suppress accepted convention warnings such as plural names,
ShouldProcesson UI helpers,$global:sync, or compile-time cross-file false positives. - For DNS DHCP reset, keep the cmdlet reset and explicitly set IPv4 and IPv6 DNS source to DHCP.
- Public pull-request diffs may be sent to configured external review services without a separate privacy approval; do not block the review loop on upload authorization for this public repository.
- Keep install-tab favicon loading overlapped with app-entry rendering; do not replace native WPF loading with a deferred second phase unless visible completion time is proven no slower than
main.