You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The coding and spec standards now answer four authoring questions they
were silent on: how to make images accessible, which PowerShell operator
to reach for when matching text, when to prefer .NET over cmdlets, and
how requirement identifiers are written. This closes the gaps left when
the PSModule instruction files were retired into a docs site, so
contributors and agents keep one complete source of truth.
## New: Image accessibility and link conventions in the Markdown
standard
The [Markdown
standard](https://github.com/MSXOrg/docs/blob/main/src/docs/Coding-Standards/Markdown.md)
now states that every image carries descriptive alt text — so it serves
screen readers and still says something when the image fails to load —
and that a repeated or long link may be written reference-style. It also
spells out that code, commands, filenames, and identifiers belong in
backticks rather than emphasis.
## New: PowerShell standard — matching, constants, and preferring .NET
The [PowerShell
standard](https://github.com/MSXOrg/docs/blob/main/src/docs/Coding-Standards/PowerShell/index.md)
now covers text matching — `-like` for wildcards, `-match` for regular
expressions, and the `-c` prefix for case-sensitive comparison — how to
declare a value that must not change with `Set-Variable -Option
ReadOnly`, and a **Prefer .NET for the actual work** principle: reach
for the .NET base class library on hot paths and where behaviour must be
exact, and keep cmdlets for readable glue. This mirrors the ai-platform
PowerShell standard.
## New: Requirement identifiers and normative language in Spec-Driven
Development
[Spec-Driven
Development](https://github.com/MSXOrg/docs/blob/main/src/docs/Ways-of-Working/Spec-Driven-Development.md)
now defines how requirements are identified and written: functional
requirements are `FR1`, `FR2`, `FR3` and non-functional ones `NFR1`,
`NFR2`, `NFR3`, each given its own heading with a stable, explicit
anchor (`### FR1 — <statement> { #fr1 }`) so it can be referenced as
`[FR1](#fr1)` and reworded without breaking the link. Identifiers are
append-only — never renumbered or reused, and a removed requirement
simply disappears (git holds the history). Requirements are written with
the BCP 14 keywords (MUST / SHOULD / MAY, and the rest of the set) so
obligations are unambiguous.
## Technical Details
Additions land in existing pages, so the standards stay navigable and
the generated index is unchanged:
- `src/docs/Coding-Standards/Markdown.md` — the "Style beyond the
linter" section.
- `src/docs/Coding-Standards/PowerShell/index.md` — the "Idioms and
pitfalls" section plus a new "Prefer .NET for the actual work" section.
- `src/docs/Ways-of-Working/Spec-Driven-Development.md` — the
"Requirements" section.
- `.github/scripts/Test-DocumentationLink.ps1` — the link checker now
recognises explicit `{ #id }` anchors and validates reference-style link
definitions, and it uses native .NET (`[System.IO.File]::Exists`,
`[System.IO.Path]::Combine`) on its per-link hot path, following the new
principle.
Requirement identifiers use the `FR`/`NFR` scheme with text-independent
[attr_list](https://python-markdown.github.io/extensions/attr_list/)
anchors (`{ #fr1 }`), which Zensical renders natively, and normative
language anchored to [BCP 14](https://www.rfc-editor.org/info/bcp14)
(RFC 2119 + RFC 8174). This supersedes the earlier `F1`/`N1` idea — the
PSModule `FR-001`/`NFR-001` non-breaking-hyphen rule does not apply,
since these anchors are text-independent. No lint configuration changes
— the deliberate markdownlint relaxations (line length, list-marker
style, blank-line rules) stand. Verified locally: markdownlint,
PSScriptAnalyzer, `Update-DocumentationIndex.ps1 -Check`, and
`Test-DocumentationLink.ps1` all pass.
<details>
<summary>Related issues</summary>
- Closes#15
- Origin PSModule/Process-PSModule#349
- Origin PSModule/docs#43
</details>
Copy file name to clipboardExpand all lines: src/docs/Coding-Standards/Documentation.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -47,6 +47,8 @@ Every public function, command, module, or API carries documentation at its boun
47
47
48
48
Include, at minimum: what it does, its parameters, what it returns, and at least one example. Examples are worth a paragraph of prose each.
49
49
50
+
This is a floor, not a ceiling. Internal and private units — helper functions and the scripts that call them — carry the same native-format documentation, so the next maintainer or agent can understand a helper without reading its whole implementation.
51
+
50
52
## The README is the front door
51
53
52
54
Every repository has a README that is the single source of truth for what the repository is and does. It is **evergreen** — updated in the same pull request that changes behavior, never as a separate task. A feature that ships without a README update is not done.
Copy file name to clipboardExpand all lines: src/docs/Coding-Standards/Markdown.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,7 +53,10 @@ These rules are disabled or widened so they do not flag valid documentation —
53
53
-**Use sentence-style headings.**
54
54
-**Surround headings, lists, and fenced blocks with a blank line** for readability, even though the linter no longer enforces it.
55
55
-**Prefer relative links** within a repository; use the canonical published URL for cross-repository references.
56
+
-**Give a repeated or long link a reference-style definition** (`[text][ref]`, with `[ref]: url` listed below) so the prose stays readable and one edit updates every use.
56
57
-**Tag every code fence with a language** (` ```bash `, ` ```yaml `) so it is highlighted and converts cleanly when published.
58
+
-**Wrap code, commands, filenames, and identifiers in backticks** rather than bold or italic, so they read as code and do not lean on the emphasis the linter now allows freely.
59
+
-**Give every image descriptive alt text** — `` — so it serves screen readers and still says something when the image fails to load; use a relative path for images kept in the repository.
Copy file name to clipboardExpand all lines: src/docs/Coding-Standards/PowerShell/Functions.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,7 +54,9 @@ function Get-UserData {
54
54
## Parameters
55
55
56
56
-**Type every parameter** and validate at the boundary — `[Parameter(Mandatory)]`, `[ValidateSet(...)]`, `[ValidateNotNullOrEmpty()]` — so bad input is rejected early, not deep in the call stack.
57
+
-**Give every parameter a `[Parameter()]` attribute**, even when it carries no arguments — it is where `Mandatory`, `ValueFromPipeline`, and the rest attach, and it keeps every parameter declared the same way.
57
58
-**Attribute order**, each on its own line: `[Parameter()]`, then validation attributes, then `[ArgumentCompleter()]`, then `[Alias()]`, then the typed declaration.
59
+
-**Separate parameters with a blank line**, so each one's inline doc comment, attributes, and typed declaration read as a single block.
58
60
-**`[switch]` for boolean flags** — never a `[bool]` parameter.
59
61
-**Name every parameter set** with an intent-revealing name when a function has more than one mode; never `Default` or `__AllParameterSets`. Set `DefaultParameterSetName` to the most common intent.
60
62
@@ -84,4 +86,4 @@ Send each kind of message to the stream built for it, so a caller can capture, r
84
86
85
87
## Comment-based help (required)
86
88
87
-
Every public function carries comment-based help, first inside the body, with sections in this order: `.SYNOPSIS` (one imperative sentence), `.DESCRIPTION`, at least one `.EXAMPLE` per behaviour, then `.INPUTS`, `.OUTPUTS` (matching `[OutputType()]`), `.NOTES`, `.LINK`. Document each parameter with an inline comment above it rather than a `.PARAMETER` block, and let comments explain *why*, not *what*.
89
+
Every function carries comment-based help — including internal and private helpers, not only the public surface. It is what lets a reader or an agent understand what the function does and how to call it without reading its body, and a private helper needs that as much as a public command does. Put it first inside the body, with sections in this order: `.SYNOPSIS` (one imperative sentence), `.DESCRIPTION`, at least one `.EXAMPLE` per behaviour, then `.INPUTS`, `.OUTPUTS` (matching `[OutputType()]`), `.NOTES`, `.LINK`. Document each parameter with an inline comment above it rather than a `.PARAMETER` block, and let comments explain *why*, not *what*.
Copy file name to clipboardExpand all lines: src/docs/Coding-Standards/PowerShell/Scripts.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ A script (`.ps1`) is an entry point, not a home for logic. Keep scripts **thin**
12
12
A script file is laid out top to bottom in this order:
13
13
14
14
1.**`#Requires`** statements — PowerShell version and module dependencies with minimum versions.
15
-
2.**Comment-based help** — `.SYNOPSIS`, `.DESCRIPTION`, and at least one `.EXAMPLE`.
15
+
2.**Comment-based help** — the same sections and order as a [function's](Functions.md#comment-based-help-required), only without the enclosing `function` block: `.SYNOPSIS`, `.DESCRIPTION`, at least one `.EXAMPLE`, then `.INPUTS`, `.OUTPUTS`, `.NOTES`, and `.LINK` as they apply. Document each parameter with an inline comment above it, just as a function does.
16
16
3.**`[CmdletBinding()]` + `param()`** — typed and validated, mandatory first; add `SupportsShouldProcess` when the script changes state.
Copy file name to clipboardExpand all lines: src/docs/Coding-Standards/PowerShell/index.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -57,11 +57,13 @@ Beyond the basics, these language-specific habits keep PowerShell correct and fa
57
57
-**Single-quote strings unless you need expansion.** Use `'literal'` by default; reserve `"...$var..."` for interpolation or escape sequences, and here-strings (`@'...'@`, `@"..."@`) for multi-line text — literal-versus-interpolated intent then stays obvious.
58
58
-**Splat calls that carry many parameters.** Build a `@{}` of parameters and splat it (`Get-Thing @params`) instead of a long line of `-Param value` pairs or backtick continuations; it reads better and diffs cleanly.
59
59
-**Put `$null` on the left of a comparison** — `$null -eq $x`, never `$x -eq $null`. Against a collection the right-hand form *filters* rather than tests. Use `-contains` / `-in` for membership, never `-eq`.
60
+
-**Match text with the operator built for it.** Use `-like` for wildcard patterns and `-match` for regular expressions instead of hand-rolled string surgery; both default to case-insensitive, so add the `-c` prefix (`-clike`, `-cmatch`, `-ceq`) when a comparison must be case-sensitive.
60
61
-**Reuse before you build.** Work down the [reuse order](../Functions.md#reuse-before-you-build) — a built-in cmdlet or operator, then an existing function (public or private), then a trusted module (`#Requires -Modules` / `RequiredModules`), then your own code (small logic inline, a larger capability as its own module).
61
62
-**PowerShell already *is* .NET; work at that level rather than wrapping it.** Casts, type accelerators (`[datetime]`, `[int]`), the `-split` / `-replace` / `-match` operators, and member methods (`.Trim()`, `.Where()`) all resolve to the base class library — using .NET means reaching for BCL types and methods for the computation, not restating everything as `[Namespace.Type]::Method(...)`. Where idiomatic PowerShell already resolves to the same .NET call, leave it; reach for explicit .NET only where it is measurably faster or more precise, and keep cmdlets and the pipeline where you need them for glue or readability.
62
63
-**Do the work in .NET when you implement it.** When you write the logic yourself — or fix an internal function that is too slow or imprecise on a hot path — call the .NET base class library directly instead of a cmdlet pipeline: `[System.IO.File]::ReadAllText($path)` over `Get-Content -Raw`, `[System.IO.Path]::Combine(...)` for paths, `[System.Text.StringBuilder]` for repeated concatenation, `[int]::TryParse(...)` for parsing. .NET methods are faster and their contracts are precise; keep cmdlets where their clarity is worth more than the speed. The next two rules are specific cases.
63
64
-**Suppress unwanted output with `$null = ...`** (or `[void]` for method calls), not `| Out-Null` — the pipeline form is markedly slower on hot paths.
64
65
-**Build collections with a typed list, not `+=` in a loop.**`$a += $x` reallocates the whole array every iteration; use `[System.Collections.Generic.List[T]]` with `.Add()`, and prefer a cmdlet's `-Filter` over piping to `Where-Object` on large sets.
66
+
-**Guard a value that must not change.** Declare it with `Set-Variable -Name Pi -Value 3.14159 -Option ReadOnly` — or `-Option Constant` for one that can never be reassigned or removed — so an accidental write fails loudly instead of quietly winning.
65
67
-**Keep secrets out of source, and never `Invoke-Expression` untrusted input.** Accept credentials as a `[PSCredential]` parameter with the `[Credential()]` attribute rather than calling `Get-Credential` inside a reusable function, so a caller can pass one they already hold, and take other sensitive values as `[securestring]`. Guard state-changing commands with `ShouldProcess` (see [Functions](Functions.md)); the wider rules live in the [Security](../Security.md) baseline.
0 commit comments