PowerShell style guide
This guide defines the PowerShell engineering conventions for Copy GitHub Repository. It records conventions already established by the codebase and distinguishes enforceable rules from readability preferences.
The repository targets PowerShell 7.4 or newer. PSScriptAnalyzerSettings.psd1 is the machine-enforced policy; this guide is the human-readable contract.
Product terminology
Required
- Human-facing text uses repository copy, copy, or publication for the product operation. Do not introduce generic
migrationwording in new wizard headings, status text, reports, or help when a copy/publication term is accurate. - The public content-mode values are exactly
SnapshotandFullHistory. Snapshotmeans clean current-state publication: the approved current default-branch content is published as one new unrelated root commit without prior history, other branches, or tags.FullHistorymeans history-preserving copy: approved history, branches, tags, and reachable Git LFS objects are preserved.- Use Repository copy plan for the human-readable reviewed plan heading.
- Compatibility-sensitive command names, parameter names,
PSTypeNamevalues, internal helper names, and schema fields that already containMigrationmay remain until a deliberately versioned breaking change. Do not expose those internal names as normal wizard prose merely for implementation convenience. - User-facing protection descriptions must explain what will be restored or why protection cannot be transferred. Do not surface raw nested-object formatting or diagnostic phrases such as
captured=Falseas normal plan text.
Rule categories
- Required conventions protect correctness, public API consistency, safety, or maintainability. PSScriptAnalyzer or Pester should enforce them when practical.
- Preferred conventions improve readability where PowerShell permits more than one reasonable form. Reviewers should use judgment rather than demand churn with no functional or maintenance benefit.
Naming
Commands and functions
Required
- Use approved PowerShell verbs.
- Use singular nouns for public commands.
- Public commands use the normal
Verb-Nounform and expose only the supported module command surface. - Private helpers retain the repository-specific
Cgrprefix after the verb, for exampleGet-CgrRepositoryandInvoke-CgrRepositorySnapshotVerification. - Do not introduce generic private helper names that could collide with commands from other modules.
The Cgr prefix is an intentional project convention and must not be expanded or renamed merely to satisfy a generic naming preference.
Parameters and variables
Required
- Parameter names use PascalCase and descriptive words, for example
DestinationRepositoryandContentMode. - Avoid single-character parameter and variable names except for conventional, tightly scoped cases where the meaning is unmistakable.
- Do not reuse PowerShell automatic-variable names.
Preferred
- Local variables use descriptive camelCase names, for example
$destinationVisibilityWasProvided. - Names should describe domain meaning rather than implementation mechanics.
Formatting
Indentation and braces
Required
- Indent with four spaces. Do not use tabs for indentation.
- Opening braces remain on the same line as the statement or declaration they belong to.
- Closing braces align with the beginning of the construct they close.
- Put spaces around operators and after separators, and use normal spacing around braces and pipelines.
- Existing compact PowerShell forms such as
param()are acceptable; the project does not require inserting a space before every opening parenthesis.
The four-space/no-tabs indentation convention is a required review standard, but PSUseConsistentIndentation is intentionally not enabled. Evaluation against the existing repository showed that the rule also normalizes established multiline continuation layouts and produced widespread warnings even with pipeline indentation normalization disabled. Enabling it would therefore require broad cosmetic rewrites unrelated to correctness or maintainability. The deliberately scoped PSUseConsistentWhitespace configuration enforces the objective whitespace checks that fit the existing codebase without that churn.
Multiline parameter declarations
Preferred
- Put one parameter declaration per line when a parameter block spans multiple lines.
- Put type and validation attributes immediately above the parameter they describe.
- Keep related validation and type information visually attached to the declaration.
Example:
[ValidateSet('Snapshot', 'FullHistory')]
[string] $ContentMode = 'Snapshot'
Multiline command invocation and splatting
Preferred
- Use named parameters for nontrivial command calls.
- A short fixed invocation may use normal PowerShell line continuation when it remains easy to scan.
- Prefer splatting when an invocation has many arguments, arguments are conditional, or the same argument set is reused. Splatting should make ownership of values clearer rather than simply move a long call into a long hashtable.
- Do not convert stable readable calls to splatting solely for stylistic uniformity.
Language forms
Quoting
Preferred
- Use single-quoted strings for literals.
- Use double-quoted strings when interpolation or PowerShell escape processing is required.
- Do not add interpolation where a literal string is sufficient.
Boolean negation
Required
- Use
-notrather than the!alias.PSAvoidExclaimOperatorenforces this convention.
$null comparisons
Required
- Put
$nullon the left side of equality comparisons, for example$null -eq $valueand$null -ne $value. - Prefer explicit null handling over relying on collection or scalar coercion.
Early returns and guard clauses
Preferred
- Use early returns or terminating errors for invalid/precondition cases when doing so keeps the successful path shallow and readable.
- Avoid deeply nesting the main operation under conditions that can be handled up front.
Public and private command design
Public/Private separation
Required
- Public commands live under
src/CopyGitHubRepo/Publicand define the supported command surface. - Internal helpers live under
src/CopyGitHubRepo/Privateand use theCgrprefix. - Public commands own public parameter semantics, safety decisions, user-facing output selection, and dispatch. Internal helpers own cohesive implementation behavior.
- Do not export private helpers.
Types and validation attributes
Required
- Use parameter types and validation attributes when they make the accepted contract objective and machine-checkable.
- Prefer
ValidateSet,ValidatePattern,ValidateNotNullOrEmpty, and similarly focused attributes over manual validation when the attribute expresses the rule accurately. - Do not use a validation attribute that changes an intentional product contract merely to conform to generic guidance.
Structured output and errors
Required
- Return structured objects on the success pipeline for programmatic behavior.
- Keep presentation/serialization separate from repository-copy behavior.
- Use stable
PSTypeNamevalues where the public contract depends on an object shape. - Use structured terminating errors with meaningful error IDs and categories at safety, authentication, Git/GitHub, verification, and integrity boundaries.
- Deliberate application failures must be distinguishable from unexpected implementation/runtime failures at user-facing boundaries. Use a stable project error ID and a project-owned marker on the exception/error record rather than relying on message-text matching.
- User-facing boundary handlers may present deliberate application failures concisely without exception type or stack trace. Unexpected failures must preserve diagnostic information such as exception type, error ID, location, inner exceptions, and script stack trace, and must remain terminating after diagnostics are emitted.
- Do not use
Write-Hostas a substitute for structured output.
For standalone bootstrap scripts that can be invoked repeatedly with irm ... | iex, prefer a marked .NET exception/ErrorRecord over declaring a PowerShell class solely to identify application-generated failures. PowerShell type definitions persist for the session and can make repeated bootstrap execution brittle. CopyGitHubRepo bootstrap errors use the CopyGitHubRepo.ApplicationError marker together with stable CopyGitHubRepo.* error IDs.
Console presentation
Required
- Treat console styling as presentation only. Never add ANSI escape sequences, status decoration, or host-only text to structured success-pipeline objects.
- Use PowerShell-native
$PSStylefor terminal color rather than embedding raw ANSI escape sequences. - Respect
NO_COLORand plain-text output rendering. Output must remain understandable when color is unavailable, disabled, redirected, or captured by CI. - Never use color as the only carrier of meaning. Pair status color with explicit text and a stable symbol.
- Use the project status vocabulary consistently:
✓ PASS/✓ SUCCESSfor successful states,✗ FAIL/✗ ERRORfor failures,! WARNfor warnings, and• INFOfor informational status where a marker improves scanning. - Use green for success, red for failure/error, yellow for warnings, and restrained cyan or neutral text for informational status.
- Prefer simple Unicode symbols with predictable terminal width. Do not depend on emoji-style glyphs for status. Where Unicode is not practical, the status word alone must preserve the meaning.
- Keep formatting views and interactive host presentation separate from repository-copy and verification logic. Public result types must remain suitable for assignment, filtering, serialization, and automation.
- Use custom PowerShell formatting data for richer default display of structured objects when appropriate rather than replacing returned objects with strings.
Preferred
- Color only the status marker/label rather than entire lines or paragraphs.
- Use short headings, whitespace, and concise aligned labels to create hierarchy. Prefer this over decorative boxes, heavy separator art, cursor-position tricks, spinners, or full-screen TUI behavior.
- Keep normal output task-focused. Put raw SHAs, IDs, expected/actual evidence, and other diagnostics beneath the failed check or in a detailed view rather than making every successful run verbose.
- Use standard
-Verbosesemantics for diagnostic execution detail. Add a product-specific detailed view only when the normal result would otherwise be too noisy. - Interactive helpers may write directly to the host when they are genuinely part of the wizard/user-interface boundary, but they must remain mockable and must not replace structured command output.
Example status presentation:
✓ PASS Destination repository exists
✗ FAIL Repository content matches
! WARN Snapshot mode creates one unrelated root commit.
• INFO Executing the reviewed repository copy plan...
The status word is intentionally redundant with both symbol and color. This keeps output scannable while remaining accessible in monochrome terminals and plain CI logs.
Comment-based help
Required
- Every public command must provide useful comment-based help for synopsis, description, every explicitly declared public parameter, examples, inputs, outputs, and related links.
- Help must document product-specific defaults and safety semantics rather than restating syntax alone.
- Private helpers need comments when intent, safety boundaries, or non-obvious behavior cannot be understood readily from the code and tests; full public-style help is not required for every private helper.
ShouldProcess
Required
- Public state-changing operations use
SupportsShouldProcessand call$PSCmdlet.ShouldProcess()before mutation. -WhatIfmust remain non-mutating.-Confirm:$falsemust not bypass independent product safety requirements such as exact same-name replacement confirmation.-Forcemay satisfy explicitly documented guards, but must not become a generic bypass for product safety invariants.
Native commands and security-sensitive code
Required
- Do not use
Invoke-Expression. - Do not use aliases in production source where they obscure the invoked command.
- Never place tokens or secrets in diagnostics, returned objects, committed fixtures, or command arguments when a safer mechanism exists.
- Preserve GitHub CLI and Git authentication isolation semantics when modifying native-command execution.
- Fail closed at unsupported-host, identity, checksum, verification, and destructive-action boundaries.
Tests
Required
- Use Pester for repository tests.
- Add or update tests with behavior, safety-contract, analyzer-policy, or documentation-contract changes.
- Keep unit tests deterministic and avoid real external mutation unless the test is explicitly an end-to-end harness.
- Cover failure paths at destructive or security-sensitive boundaries, not only successful paths.
- Assert stable public behavior such as output shape, error ID, verification result, or mutation ordering instead of incidental implementation details.
- Do not weaken an assertion merely to make a failing implementation pass.
Preferred
- Organize tests around behavior (
Describe) and scenario (It) names that explain the contract being protected. - Mock at external or orchestration boundaries when that produces clearer failure isolation than mocking every internal call.
PSScriptAnalyzer policy
PSScriptAnalyzerSettings.psd1 retains the default Error/Warning rule set and explicitly enables two additional rules:
| Rule | Why it is enabled |
|---|---|
PSUseConsistentWhitespace |
Enforces useful whitespace consistency around braces, operators, separators, and pipelines. The opening-parenthesis check is intentionally disabled because established, readable forms such as param() are widespread and changing them would create cosmetic churn without improving safety or maintainability. Parameter-alignment checks are also disabled to avoid alignment-only rewrites. |
PSAvoidExclaimOperator |
Prevents the ! alias and keeps boolean negation explicit as -not. |
PSUseConsistentIndentation was evaluated but is not enabled. With the repository’s existing multiline continuations it reports widespread indentation warnings even when pipeline normalization is disabled, so enforcing it would primarily mandate cosmetic rewrites rather than prevent a concrete defect class. Four-space, space-based indentation remains a required project convention and review expectation.
PSUseCorrectCasing is not enabled because it reports at Information severity while this repository intentionally gates Error and Warning findings. Expanding the quality gate to Information severity solely for casing would make the analyzer policy substantially noisier without a corresponding correctness benefit.
Formatting rules or subchecks that would mainly force alignment or broad cosmetic rewrites are not enabled merely because they exist. Additional rules should be evaluated against this repository, enabled only when they prevent a concrete class of defect or materially improve maintainability, and documented here when adopted.
Suppressions
The repository uses narrow function-level PSUseShouldProcessForStateChangingFunctions suppressions on the internal GitHub mutation boundaries New-CgrGitHubRepository, Rename-CgrGitHubRepository, and Set-CgrGitHubRepositorySetting. These helpers intentionally do not perform independent ShouldProcess checks because the public Copy-GitHubRepository command owns ShouldProcess and, for same-name replacement, the stronger exact-confirmation safety contract before dispatching to those private mutation helpers. Each suppression is attached directly to the affected function and includes a justification describing that boundary.
Any suppression must continue to follow these rules:
- Scope it to the narrowest function, parameter, or statement practical.
- Name the exact rule being suppressed.
- Include a nearby comment explaining why the project contract intentionally differs from the rule.
- Do not disable a useful rule globally to silence one exceptional case.
- Add a test when the exception protects an important product behavior or compatibility contract.
A suppression is an explicit engineering decision, not a shortcut for making the quality gate green.